aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NK_C_API.cc76
-rw-r--r--NK_C_API.h43
-rw-r--r--NitrokeyManager.cc38
-rw-r--r--device.cc5
-rw-r--r--include/NitrokeyManager.h16
-rw-r--r--include/device.h2
-rw-r--r--include/device_proto.h5
-rw-r--r--include/stick10_commands.h126
-rw-r--r--unittest/test_bindings.py230
9 files changed, 382 insertions, 159 deletions
diff --git a/NK_C_API.cc b/NK_C_API.cc
index 81a18b5..d2aa38a 100644
--- a/NK_C_API.cc
+++ b/NK_C_API.cc
@@ -13,9 +13,9 @@ T* array_dup(std::vector<T>& v){
template <typename T>
uint8_t * get_with_array_result(T func){
+ NK_last_command_status = 0;
try {
return func();
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -25,9 +25,9 @@ uint8_t * get_with_array_result(T func){
template <typename T>
const char* get_with_string_result(T func){
+ NK_last_command_status = 0;
try {
return func();
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -37,21 +37,22 @@ const char* get_with_string_result(T func){
template <typename T>
auto get_with_result(T func){
+ NK_last_command_status = 0;
try {
return func();
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
return commandFailedException.last_command_status;
+// return (uint8_t) 0;
}
}
template <typename T>
uint8_t get_without_result(T func){
+ NK_last_command_status = 0;
try {
func();
- NK_last_command_status = 0;
return 0;
}
catch (CommandFailedException & commandFailedException){
@@ -71,8 +72,8 @@ extern uint8_t NK_get_last_command_status(){
extern int NK_login(const char *device_model) {
auto m = NitrokeyManager::instance();
try {
- m->connect(device_model);
NK_last_command_status = 0;
+ m->connect(device_model);
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -84,8 +85,8 @@ extern int NK_login(const char *device_model) {
extern int NK_logout() {
auto m = NitrokeyManager::instance();
try {
- m->disconnect();
NK_last_command_status = 0;
+ m->disconnect();
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -105,31 +106,32 @@ extern int NK_first_authenticate(const char* admin_password, const char* admin_t
extern int NK_user_authenticate(const char* user_password, const char* user_temporary_password){
auto m = NitrokeyManager::instance();
return get_without_result( [&](){
- return m->user_authenticate(user_password, user_temporary_password);
+ m->user_authenticate(user_password, user_temporary_password);
});
}
extern int NK_factory_reset(const char* admin_password){
auto m = NitrokeyManager::instance();
return get_without_result( [&](){
- return m->factory_reset(admin_password);
+ m->factory_reset(admin_password);
});
}
extern int NK_build_aes_key(const char* admin_password){
auto m = NitrokeyManager::instance();
return get_without_result( [&](){
- return m->build_aes_key(admin_password);
+ m->build_aes_key(admin_password);
});
}
-extern int NK_unlock_user_password(const char* admin_password){
+extern int NK_unlock_user_password(const char *admin_password, const char *new_user_password) {
auto m = NitrokeyManager::instance();
return get_without_result( [&](){
- return m->unlock_user_password(admin_password);
+ m->unlock_user_password(admin_password, new_user_password);
});
}
-extern int NK_write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, bool delete_user_password,
+extern int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
+ bool delete_user_password,
const char *admin_temporary_password) {
auto m = NitrokeyManager::instance();
return get_without_result( [&](){
@@ -148,10 +150,10 @@ extern uint8_t* NK_read_config(){
extern const char * NK_status() {
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
string s = m->get_status();
- NK_last_command_status = 0;
return strdup(s.c_str()); //FIXME leak?
}
catch (CommandFailedException & commandFailedException){
@@ -166,10 +168,10 @@ extern uint32_t NK_get_hotp_code(uint8_t slot_number) {
}
extern uint32_t NK_get_hotp_code_PIN(uint8_t slot_number, const char* user_temporary_password){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
const auto code = m->get_HOTP_code(slot_number, user_temporary_password);
- NK_last_command_status = 0;
return code;
}
catch (CommandFailedException & commandFailedException){
@@ -185,10 +187,10 @@ extern uint32_t NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64
extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
uint8_t last_interval, const char* user_temporary_password){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
const auto totp_code = m->get_TOTP_code(slot_number, challenge, last_totp_time, last_interval, user_temporary_password);
- NK_last_command_status = 0;
return totp_code;
}
catch (CommandFailedException & commandFailedException){
@@ -198,10 +200,10 @@ extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, ui
}
extern int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_password) {
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->erase_hotp_slot(slot_number, temporary_password);
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -211,10 +213,10 @@ extern int NK_erase_hotp_slot(uint8_t slot_number, const char *temporary_passwor
}
extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_password) {
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->erase_totp_slot(slot_number, temporary_password);
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -224,11 +226,13 @@ extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_passwor
}
extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter,
- bool use_8_digits, const char *temporary_password) {
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) {
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
- m->write_HOTP_slot(slot_number, slot_name, secret, hotp_counter, use_8_digits, temporary_password);
- NK_last_command_status = 0;
+ m->write_HOTP_slot(slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID, token_ID,
+ temporary_password);
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -238,11 +242,13 @@ extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const
}
extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
- bool use_8_digits, const char *temporary_password) {
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) {
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
- m->write_TOTP_slot(slot_number, slot_name, secret, time_window, use_8_digits, temporary_password);
- NK_last_command_status = 0;
+ m->write_TOTP_slot(slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID, token_ID,
+ temporary_password);
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -252,10 +258,10 @@ extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const
}
extern const char* NK_get_totp_slot_name(uint8_t slot_number){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
const auto slot_name = m->get_totp_slot_name(slot_number);
- NK_last_command_status = 0;
return slot_name;
}
catch (CommandFailedException & commandFailedException){
@@ -264,10 +270,10 @@ extern const char* NK_get_totp_slot_name(uint8_t slot_number){
}
}
extern const char* NK_get_hotp_slot_name(uint8_t slot_number){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
const auto slot_name = m->get_hotp_slot_name(slot_number);
- NK_last_command_status = 0;
return slot_name;
}
catch (CommandFailedException & commandFailedException){
@@ -282,10 +288,10 @@ extern void NK_set_debug(bool state){
}
extern int NK_totp_set_time(uint64_t time){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->set_time(time);
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -295,10 +301,10 @@ extern int NK_totp_set_time(uint64_t time){
}
extern int NK_totp_get_time(){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->get_time();
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -308,10 +314,10 @@ extern int NK_totp_get_time(){
}
extern int NK_change_admin_PIN(char *current_PIN, char *new_PIN){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->change_admin_PIN(current_PIN, new_PIN);
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -321,10 +327,10 @@ extern int NK_change_admin_PIN(char *current_PIN, char *new_PIN){
}
extern int NK_change_user_PIN(char *current_PIN, char *new_PIN){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->change_user_PIN(current_PIN, new_PIN);
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -334,10 +340,10 @@ extern int NK_change_user_PIN(char *current_PIN, char *new_PIN){
}
extern int NK_enable_password_safe(const char *user_pin){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
try {
m->enable_password_safe(user_pin);
- NK_last_command_status = 0;
}
catch (CommandFailedException & commandFailedException){
NK_last_command_status = commandFailedException.last_command_status;
@@ -346,12 +352,12 @@ extern int NK_enable_password_safe(const char *user_pin){
return 0;
}
extern uint8_t * NK_get_password_safe_slot_status(){
+ NK_last_command_status = 0;
auto m = NitrokeyManager::instance();
auto res = new uint8_t[16];
memset(res, 0, 16);
try {
const auto slot_status = m->get_password_safe_slot_status();
- NK_last_command_status = 0;
return slot_status; //TODO FIXME
}
catch (CommandFailedException & commandFailedException){
@@ -415,5 +421,13 @@ extern int NK_erase_password_safe_slot(uint8_t slot_number) {
});
}
+extern int NK_is_AES_supported(const char *user_password) {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&](){
+ return (uint8_t) m->is_AES_supported(user_password);
+ });
+}
+
+
}
diff --git a/NK_C_API.h b/NK_C_API.h
index 1882545..43a2e34 100644
--- a/NK_C_API.h
+++ b/NK_C_API.h
@@ -8,8 +8,6 @@
extern "C"
{
-//Make sure each function's declaration is in one line (for automatic python declaration processing)
-
/**
* Set debug level of messages written on stderr
* @param state state=True - all messages, state=False - only errors level
@@ -83,22 +81,25 @@ extern int NK_build_aes_key(const char* admin_password);
* @param admin_password char[20](Pro) current administrator PIN
* @return command processing error code
*/
-extern int NK_unlock_user_password(const char* admin_password);
+extern int NK_unlock_user_password(const char *admin_password, const char *new_user_password);
/**
* Write general config to the device
- * @param numlock set True to send OTP code after double pressing numlock
- * @param capslock set True to send OTP code after double pressing capslock
- * @param scrolllock set True to send OTP code after double pressing scrolllock
+ * @param numlock set value in range [0-1] to send HOTP code from slot 'numlock' after double pressing numlock
+ * or outside the range to disable this function
+ * @param capslock similar to numlock but with capslock
+ * @param scrolllock similar to numlock but with scrolllock
* @param enable_user_password set True to enable OTP PIN protection (request PIN each OTP code request)
* @param delete_user_password set True to disable OTP PIN protection (request PIN each OTP code request)
* @param admin_temporary_password current admin temporary password
* @return command processing error code
*/
-extern int NK_write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, bool delete_user_password, const char *admin_temporary_password);
+extern int NK_write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock,
+ bool enable_user_password, bool delete_user_password, const char *admin_temporary_password);
/**
- * Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and PIN protected OTP
+ * Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and is enabled PIN protected OTP
+ * @see NK_write_config
* @return uint8_t general_config[5]:
* uint8_t numlock;
uint8_t capslock;
@@ -148,10 +149,15 @@ extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_passwor
* @param secret char[20](Pro) 160-bit secret
* @param hotp_counter uint32_t starting value of HOTP counter
* @param use_8_digits should returned codes be 6 (false) or 8 digits (true)
+ * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock
+ * @param use_tokenID @see token_ID
+ * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section
* @param temporary_password char[25](Pro) admin temporary password
* @return command processing error code
*/
-extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter, bool use_8_digits, const char *temporary_password);
+extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
/**
* Write TOTP slot data to the device
@@ -160,10 +166,15 @@ extern int NK_write_hotp_slot(uint8_t slot_number, const char *slot_name, const
* @param secret char[20](Pro) 160-bit secret
* @param time_window uint16_t time window for this TOTP
* @param use_8_digits should returned codes be 6 (false) or 8 digits (true)
+ * @param use_enter press ENTER key after sending OTP code using double-pressed scroll/num/capslock
+ * @param use_tokenID @see token_ID
+ * @param token_ID @see https://openauthentication.org/token-specs/, 'Class A' section
* @param temporary_password char[20](Pro) admin temporary password
* @return command processing error code
*/
-extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window, bool use_8_digits, const char *temporary_password);
+extern int NK_write_totp_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
/**
* Get HOTP code from the device
@@ -201,7 +212,8 @@ extern uint32_t NK_get_totp_code(uint8_t slot_number, uint64_t challenge, uint64
* otherwise should be set to empty string - ''
* @return TOTP code
*/
-extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, uint8_t last_interval, const char* user_temporary_password);
+extern uint32_t NK_get_totp_code_PIN(uint8_t slot_number, uint64_t challenge,
+ uint64_t last_totp_time, uint8_t last_interval, const char* user_temporary_password);
/**
* Set time on the device (for TOTP requests)
@@ -284,7 +296,8 @@ extern const char *NK_get_password_safe_slot_password(uint8_t slot_number);
* @param slot_password char[20](Pro) password string
* @return command processing error code
*/
-extern int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login, const char *slot_password);
+extern int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_name,
+ const char *slot_login, const char *slot_password);
/**
* Erase the password safe slot from the device
@@ -292,6 +305,12 @@ extern int NK_write_password_safe_slot(uint8_t slot_number, const char *slot_nam
* @return command processing error code
*/
extern int NK_erase_password_safe_slot(uint8_t slot_number);
+
+/**
+ * Check whether AES is supported by the device
+ * @return 0 for no and 1 for yes
+ */
+extern int NK_is_AES_supported(const char *user_password);
}
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
index fc1daa5..ed9c7b4 100644
--- a/NitrokeyManager.cc
+++ b/NitrokeyManager.cc
@@ -6,6 +6,7 @@ namespace nitrokey{
template <typename T>
void strcpyT(T& dest, const char* src){
+ assert(src != nullptr);
const int s = sizeof dest;
assert(strlen(src) <= s);
strncpy((char*) &dest, src, s);
@@ -138,8 +139,9 @@ namespace nitrokey{
}
- bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter,
- bool use_8_digits, const char *temporary_password) {
+ bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) {
assert(is_valid_hotp_slot_number(slot_number));
assert(strlen(secret)==20); //160 bits
assert(strlen(slot_name)<=15);
@@ -149,8 +151,11 @@ namespace nitrokey{
payload.slot_number = slot_number;
strcpyT(payload.slot_secret, secret);
strcpyT(payload.slot_name, slot_name);
+ strcpyT(payload.slot_token_id, token_ID);
payload.slot_counter = hotp_counter;
payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
auto auth = get_payload<Authorize>();
strcpyT(auth.temporary_password, temporary_password);
@@ -161,8 +166,9 @@ namespace nitrokey{
return true;
}
- bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret,
- uint16_t time_window, bool use_8_digits, const char *temporary_password) {
+ bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) {
auto payload = get_payload<WriteToTOTPSlot>();
assert(is_valid_totp_slot_number(slot_number));
assert(strlen(secret) == sizeof payload.slot_secret); //160 bits
@@ -172,18 +178,18 @@ namespace nitrokey{
payload.slot_number = slot_number;
strcpyT(payload.slot_secret, secret);
strcpyT(payload.slot_name, slot_name);
+ strcpyT(payload.slot_token_id, token_ID);
payload.slot_interval = time_window; //FIXME naming
payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
auto auth = get_payload<Authorize>();
strcpyT(auth.temporary_password, temporary_password);
auth.crc_to_authorize = WriteToTOTPSlot::CommandTransaction::getCRC(payload);
Authorize::CommandTransaction::run(*device, auth);
-// auto auth_successful = device->last_command_sucessfull();
auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload);
-// auto write_successful = device->last_command_sucessfull();
-// return auth_successful && write_successful; //left to show alternative approach
return true;
}
@@ -270,6 +276,11 @@ namespace nitrokey{
}
void NitrokeyManager::enable_password_safe(const char *user_pin) {
+ //The following command will cancel enabling PWS if it is not supported
+ auto a = get_payload<IsAESSupported>();
+ strcpyT(a.user_password, user_pin);
+ IsAESSupported::CommandTransaction::run(*device, a);
+
auto p = get_payload<EnablePasswordSafe>();
strcpyT(p.user_password, user_pin);
EnablePasswordSafe::CommandTransaction::run(*device, p);
@@ -363,14 +374,16 @@ namespace nitrokey{
FactoryReset::CommandTransaction::run(*device, p);
}
- void NitrokeyManager::unlock_user_password(const char *admin_password) {
+ void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) {
auto p = get_payload<UnlockUserPassword>();
strcpyT(p.admin_password, admin_password);
+ strcpyT(p.user_new_password, new_user_password);
UnlockUserPassword::CommandTransaction::run(*device, p);
}
- void NitrokeyManager::write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password, bool delete_user_password, const char *admin_temporary_password) {
+ void NitrokeyManager::write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
+ bool delete_user_password, const char *admin_temporary_password) {
auto p = get_payload<WriteGeneralConfig>();
p.numlock = (uint8_t) numlock;
p.capslock = (uint8_t) capslock;
@@ -390,4 +403,11 @@ namespace nitrokey{
return v;
}
+ bool NitrokeyManager::is_AES_supported(const char *user_password) {
+ auto a = get_payload<IsAESSupported>();
+ strcpyT(a.user_password, user_password);
+ IsAESSupported::CommandTransaction::run(*device, a);
+ return true;
+ }
+
} \ No newline at end of file
diff --git a/device.cc b/device.cc
index 326f7f4..6660276 100644
--- a/device.cc
+++ b/device.cc
@@ -14,7 +14,7 @@ Device::Device()
: m_vid(0),
m_pid(0),
m_retry_count(40),
- m_retry_timeout(50),
+ m_retry_timeout(100),
mp_devhandle(NULL),
last_command_status(0){}
@@ -85,7 +85,8 @@ Stick10::Stick10() {
m_vid = 0x20a0;
m_pid = 0x4108;
m_model = DeviceModel::PRO;
- m_send_receive_delay = 10ms;
+ m_send_receive_delay = 100ms;
+ m_retry_count = 100;
}
Stick20::Stick20() {
diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h
index 4f1dcfa..7bc2673 100644
--- a/include/NitrokeyManager.h
+++ b/include/NitrokeyManager.h
@@ -21,10 +21,12 @@ namespace nitrokey {
static NitrokeyManager *instance();
bool first_authenticate(const char *pin, const char *temporary_password);
- bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter,
- bool use_8_digits, const char *temporary_password);
- bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret,
- uint16_t time_window, bool use_8_digits, const char *temporary_password);
+ bool write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint8_t hotp_counter,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
+ bool write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
uint32_t get_HOTP_code(uint8_t slot_number, const char *user_temporary_password);
uint32_t get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time, uint8_t last_interval,
const char *user_temporary_password);
@@ -68,13 +70,15 @@ namespace nitrokey {
void build_aes_key(const char *admin_password);
- void unlock_user_password(const char *admin_password);
+ void unlock_user_password(const char *admin_password, const char *new_user_password);
- void write_config(bool numlock, bool capslock, bool scrolllock, bool enable_user_password,
+ void write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
bool delete_user_password, const char *admin_temporary_password);
vector<uint8_t> read_config();
+ bool is_AES_supported(const char *user_password);
+
private:
NitrokeyManager();
~NitrokeyManager();
diff --git a/include/device.h b/include/device.h
index ffc38e5..0553d2e 100644
--- a/include/device.h
+++ b/include/device.h
@@ -50,7 +50,7 @@ public:
std::chrono::milliseconds get_retry_timeout() const { return m_retry_timeout; };
std::chrono::milliseconds get_send_receive_delay() const {return m_send_receive_delay;}
- int get_last_command_status() const;
+ int get_last_command_status() {auto a = last_command_status; last_command_status = 0; return a;};
void set_last_command_status(uint8_t _err) { last_command_status = _err;} ;
bool last_command_sucessfull() const {return last_command_status == 0;};
DeviceModel get_device_model() const {return m_model;}
diff --git a/include/device_proto.h b/include/device_proto.h
index 6e21f9f..78abe38 100644
--- a/include/device_proto.h
+++ b/include/device_proto.h
@@ -215,11 +215,14 @@ class Transaction : semantics::non_constructible {
Log::instance()("Incoming HID packet:", Loglevel::DEBUG);
Log::instance()((std::string)(resp), Loglevel::DEBUG);
+ Log::instance()(std::string("Retry count: ") + std::to_string(retry), Loglevel::DEBUG);
if (!resp.isValid()) throw std::runtime_error("Invalid incoming packet");
+ if (retry <= 0) throw std::runtime_error("Maximum retry count reached for receiving response from the device!");
if (resp.last_command_status!=0) throw CommandFailedException(resp.command_id, resp.last_command_status);
- // See: DeviceResponse
+
+ // See: DeviceResponse
return resp.payload;
}
diff --git a/include/stick10_commands.h b/include/stick10_commands.h
index 6df8727..ef83747 100644
--- a/include/stick10_commands.h
+++ b/include/stick10_commands.h
@@ -81,7 +81,6 @@ class SetTime : Command<CommandID::SET_TIME> {
};
-// TODO duplicate TOTP
class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
public:
struct CommandPayload {
@@ -96,7 +95,15 @@ class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
bool use_tokenID : 1;
};
};
- uint8_t slot_token_id[13];
+ union{
+ uint8_t slot_token_id[13]; /** OATH Token Identifier */
+ struct{ /** @see https://openauthentication.org/token-specs/ */
+ uint8_t omp[2];
+ uint8_t tt[2];
+ uint8_t mui[8];
+ uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805
+ } slot_token_fields;
+ };
uint64_t slot_counter;
bool isValid() const { return !(slot_number & 0xF0); }
@@ -137,7 +144,15 @@ class WriteToTOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
bool use_tokenID : 1;
};
};
- uint8_t slot_token_id[13];
+ union{
+ uint8_t slot_token_id[13]; /** OATH Token Identifier */
+ struct{ /** @see https://openauthentication.org/token-specs/ */
+ uint8_t omp[2];
+ uint8_t tt[2];
+ uint8_t mui[8];
+ uint8_t keyboard_layout; //disabled feature in nitroapp as of 20160805
+ } slot_token_fields;
+ };
uint16_t slot_interval;
bool isValid() const { return !(slot_number & 0xF0); } //TODO check
@@ -160,27 +175,6 @@ class WriteToTOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
CommandTransaction;
};
-class GetCode : Command<CommandID::GET_CODE> {
- public:
- struct CommandPayload {
- uint8_t slot_number;
- uint64_t challenge;
- uint64_t last_totp_time;
- uint8_t last_interval;
-
- bool isValid() const { return !(slot_number & 0xF0); }
- } __packed;
-
- struct ResponsePayload {
- uint8_t code[18];
-
- bool isValid() const { return true; }
- } __packed;
-
- typedef Transaction<command_id(), struct CommandPayload,
- struct ResponsePayload> CommandTransaction;
-};
-
class GetTOTP : Command<CommandID::GET_CODE> {
public:
struct CommandPayload {
@@ -202,18 +196,28 @@ class GetTOTP : Command<CommandID::GET_CODE> {
struct ResponsePayload {
union {
- uint8_t whole_response[18]; //TODO remove if not needed
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
struct {
uint32_t code;
- uint8_t config;
- } __packed;
- } __packed;
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ } __packed ;
+ } __packed ;
bool isValid() const { return true; }
std::string dissect() const {
std::stringstream ss;
ss << "code:\t" << (code) << std::endl;
- ss << "config:\t" << "TODO" /*(config) */<< std::endl; //TODO show byte field options
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
return ss.str();
}
} __packed;
@@ -237,10 +241,17 @@ class GetHOTP : Command<CommandID::GET_CODE> {
struct ResponsePayload {
union {
- uint8_t whole_response[18]; //TODO remove if not needed
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
struct {
uint32_t code;
- uint8_t config;
+ union{
+ uint8_t _slot_config;
+ struct{
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
} __packed;
} __packed;
@@ -248,7 +259,10 @@ class GetHOTP : Command<CommandID::GET_CODE> {
std::string dissect() const {
std::stringstream ss;
ss << "code:\t" << (code) << std::endl;
- ss << "config:\t" << "TODO" /*(config) */<< std::endl; //TODO show byte field options
+ ss << "slot_config:\t" << std::bitset<8>((int)_slot_config) << std::endl;
+ ss << "\tuse_8_digits(0):\t" << use_8_digits << std::endl;
+ ss << "\tuse_enter(1):\t" << use_enter << std::endl;
+ ss << "\tuse_tokenID(2):\t" << use_tokenID << std::endl;
return ss.str();
}
} __packed;
@@ -301,14 +315,14 @@ class GetStatus : Command<CommandID::GET_STATUS> {
union {
uint8_t general_config[5];
struct{
- uint8_t numlock;
- uint8_t capslock;
- uint8_t scrolllock;
+ uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */
+ uint8_t capslock; /** same as numlock */
+ uint8_t scrolllock; /** same as numlock */
uint8_t enable_user_password;
uint8_t delete_user_password;
};
};
- bool isValid() const { return true; }
+ bool isValid() const { return enable_user_password!=delete_user_password; }
std::string dissect() const {
std::stringstream ss;
@@ -319,9 +333,9 @@ class GetStatus : Command<CommandID::GET_STATUS> {
ss << "general_config:\t"
<< ::nitrokey::misc::hexdump((const char *)(general_config),
sizeof general_config);
- ss << "numlock:\t" << (bool)numlock << std::endl;
- ss << "capslock:\t" << (bool)capslock << std::endl;
- ss << "scrolllock:\t" << (bool)scrolllock << std::endl;
+ ss << "numlock:\t" << (int)numlock << std::endl;
+ ss << "capslock:\t" << (int)capslock << std::endl;
+ ss << "scrolllock:\t" << (int)scrolllock << std::endl;
ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl;
ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl;
@@ -557,13 +571,18 @@ class EnablePasswordSafe : Command<CommandID::PW_SAFE_ENABLE> {
};
class PasswordSafeInitKey : Command<CommandID::PW_SAFE_INIT_KEY> {
+ /**
+ * never used in Nitrokey App
+ */
public:
typedef Transaction<command_id(), struct EmptyPayload, struct EmptyPayload>
CommandTransaction;
};
-// TODO naming screwed up, see above
class PasswordSafeSendSlotViaHID : Command<CommandID::PW_SAFE_SEND_DATA> {
+ /**
+ * never used in Nitrokey App
+ */
public:
struct CommandPayload {
uint8_t slot_number;
@@ -584,18 +603,18 @@ class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> {
union{
uint8_t config[5];
struct{
- uint8_t numlock;
- uint8_t capslock;
- uint8_t scrolllock;
+ uint8_t numlock; /** 0-1: HOTP slot number from which the code will be get on double press, other value - function disabled */
+ uint8_t capslock; /** same as numlock */
+ uint8_t scrolllock; /** same as numlock */
uint8_t enable_user_password;
uint8_t delete_user_password;
};
};
std::string dissect() const {
std::stringstream ss;
- ss << "numlock:\t" << (bool)numlock << std::endl;
- ss << "capslock:\t" << (bool)capslock << std::endl;
- ss << "scrolllock:\t" << (bool)scrolllock << std::endl;
+ ss << "numlock:\t" << (int)numlock << std::endl;
+ ss << "capslock:\t" << (int)capslock << std::endl;
+ ss << "scrolllock:\t" << (int)scrolllock << std::endl;
ss << "enable_user_password:\t" << (bool) enable_user_password << std::endl;
ss << "delete_user_password:\t" << (bool) delete_user_password << std::endl;
return ss.str();
@@ -666,7 +685,7 @@ class Authorize : Command<CommandID::AUTHORIZE> {
class UserAuthorize : Command<CommandID::USER_AUTHORIZE> {
public:
struct CommandPayload {
- uint64_t crc_to_authorize;
+ uint32_t crc_to_authorize;
uint8_t temporary_password[25];
std::string dissect() const {
std::stringstream ss;
@@ -683,7 +702,8 @@ class UserAuthorize : Command<CommandID::USER_AUTHORIZE> {
class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> {
public:
struct CommandPayload {
- uint8_t admin_password[20];
+ uint8_t admin_password[25];
+ uint8_t user_new_password[25];
std::string dissect() const {
std::stringstream ss;
ss << " admin_password:\t" << admin_password<< std::endl;
@@ -691,8 +711,6 @@ class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> {
}
} __packed;
- // TODO could we get the stick to return the retry count?
-
typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
CommandTransaction;
};
@@ -714,11 +732,15 @@ class ChangeUserPin : Command<CommandID::CHANGE_USER_PIN> {
CommandTransaction;
};
-// TODO why is it needed?
class IsAESSupported : Command<CommandID::DETECT_SC_AES> {
public:
struct CommandPayload {
- uint8_t password[20];
+ uint8_t user_password[20];
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " user_password:\t" << user_password<< std::endl;
+ return ss.str();
+ }
} __packed;
typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
diff --git a/unittest/test_bindings.py b/unittest/test_bindings.py
index afe7d0d..5688adb 100644
--- a/unittest/test_bindings.py
+++ b/unittest/test_bindings.py
@@ -20,6 +20,7 @@ class DeviceErrorCode(Enum):
NOT_PROGRAMMED = 3
WRONG_PASSWORD = 4
STATUS_NOT_AUTHORIZED = 5
+ STATUS_AES_DEC_FAILED = 0xa
@pytest.fixture(scope="module")
@@ -30,25 +31,28 @@ def C(request):
with open(fp, 'r') as f:
declarations = f.readlines()
- for declaration in declarations:
- # extern int NK_write_totp_slot(int slot_number, char* secret, int time_window);
- if 'extern' in declaration and not '"C"' in declaration:
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.startswith('extern') and not '"C"' in declaration:
declaration = declaration.replace('extern', '').strip()
+ while not ';' in declaration:
+ declaration += (next(a)).strip()
print(declaration)
ffi.cdef(declaration)
C = ffi.dlopen("../build/libnitrokey.so")
C.NK_set_debug(False)
C.NK_login('P')
+
# assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
# assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK
# C.NK_status()
def fin():
- print ('\nFinishing connection to device')
+ print('\nFinishing connection to device')
C.NK_logout()
- print ('Finished')
+ print('Finished')
request.addfinalizer(fin)
C.NK_set_debug(True)
@@ -107,11 +111,64 @@ def test_password_safe_slot_status(C):
safe_slot_status = C.NK_get_password_safe_slot_status()
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
is_slot_programmed = list(ffi.cast("uint8_t [16]", safe_slot_status)[0:16])
- print ((is_slot_programmed, len(is_slot_programmed)))
+ print((is_slot_programmed, len(is_slot_programmed)))
assert is_slot_programmed[0] == 0
assert is_slot_programmed[1] == 1
+@pytest.mark.xfail(run=False, reason="issue to register: device locks up after below commands sequence (reinsertion fixes), skipping for now")
+def test_issue_device_locks_on_second_key_generation_in_sequence(C):
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+
+
+def test_regenerate_aes_key(C):
+ C.NK_set_debug(True)
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.xfail(reason="firmware bug: regenerating AES key command not always results in cleared slot data")
+def test_destroy_password_safe(C):
+ """
+ Sometimes fails on NK Pro - slot name is not cleared ergo key generation has not succeed despite the success result
+ returned from the device
+ """
+ C.NK_set_debug(True)
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ # write password safe slot
+ assert C.NK_write_password_safe_slot(0, 'slotname1', 'login1', 'pass1') == DeviceErrorCode.STATUS_OK
+ # read slot
+ assert gs(C.NK_get_password_safe_slot_name(0)) == 'slotname1'
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ slot_login = C.NK_get_password_safe_slot_login(0)
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ assert gs(slot_login) == 'login1'
+ # destroy password safe by regenerating aes key
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ assert gs(C.NK_get_password_safe_slot_name(0)) != 'slotname1'
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+ # check was slot status cleared
+ safe_slot_status = C.NK_get_password_safe_slot_status()
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+ is_slot_programmed = list(ffi.cast("uint8_t [16]", safe_slot_status)[0:16])
+ assert is_slot_programmed[0] == 0
+
+
+def test_is_AES_supported(C):
+ assert C.NK_is_AES_supported('wrong password') != 1
+ assert C.NK_get_last_command_status() == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_is_AES_supported(DefaultPasswords.USER) == 1
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
+
def test_admin_PIN_change(C):
new_password = '123123123'
assert C.NK_change_admin_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
@@ -143,60 +200,106 @@ def test_user_retry_counts(C):
assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
assert C.NK_get_user_retry_count() == default_user_retry_count
+
+def test_unlock_user_password(C):
+ C.NK_set_debug(True)
+ default_user_retry_count = 3
+ default_admin_retry_count = 3
+ new_password = '123123123'
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_change_user_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_change_user_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_change_user_PIN('wrong_password', new_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_user_retry_count() == default_user_retry_count - 3
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count
+
+ assert C.NK_unlock_user_password('wrong password', DefaultPasswords.USER) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count - 1
+ assert C.NK_unlock_user_password(DefaultPasswords.ADMIN, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_get_admin_retry_count() == default_admin_retry_count
+
+
def test_admin_auth(C):
assert C.NK_first_authenticate('wrong_password', DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
def test_user_auth(C):
assert C.NK_user_authenticate('wrong_password', DefaultPasswords.USER_TEMP) == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK
-def check_RFC_codes(C, func, prep=None):
+def check_HOTP_RFC_codes(C, func, prep=None, use_8_digits=False):
+ """
+ # https://tools.ietf.org/html/rfc4226#page-32
+ """
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
test_data = [
- 755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489,
+ 1284755224, 1094287082, 137359152, 1726969429, 1640338314, 868254676, 1918287922, 82162583, 673399871,
+ 645520489,
]
for code in test_data:
if prep:
prep()
r = func(1)
- assert code == r
+ code = str(code)[-8:] if use_8_digits else str(code)[-6:]
+ assert int(code) == r
-@pytest.mark.skip(reason="not working correctly, skipping for now")
-def test_HOTP_RFC_pin_protection(C):
- C.NK_set_debug(True)
- assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, True, True, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+@pytest.mark.parametrize("use_8_digits", [False, True, ])
+@pytest.mark.parametrize("use_pin_protection", [False, True, ])
+def test_HOTP_RFC_use8digits_usepin(C, use_8_digits, use_pin_protection):
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- # check_RFC_codes(C, lambda x: C.NK_get_hotp_code_PIN(x, DefaultPasswords.USER_TEMP), lambda: C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP))
- assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_get_hotp_code_PIN(1, DefaultPasswords.USER_TEMP) == 755224
- assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
-
-
-@pytest.mark.skip(reason="not implemented yet")
-def test_HOTP_RFC_no_pin_protection_8digits(C):
- assert False # TODO to write
-
-def test_HOTP_RFC_no_pin_protection(C):
+ assert C.NK_write_config(255, 255, 255, use_pin_protection, not use_pin_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ if use_pin_protection:
+ check_HOTP_RFC_codes(C,
+ lambda x: C.NK_get_hotp_code_PIN(x, DefaultPasswords.USER_TEMP),
+ lambda: C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP),
+ use_8_digits=use_8_digits)
+ else:
+ check_HOTP_RFC_codes(C, C.NK_get_hotp_code, use_8_digits=use_8_digits)
+
+
+def test_HOTP_token(C):
+ """
+ Check HOTP routine with written token ID to slot.
+ """
+ use_pin_protection = False
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, use_pin_protection, not use_pin_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- # https://tools.ietf.org/html/rfc4226#page-32
- check_RFC_codes(C, C.NK_get_hotp_code)
+ token_ID = "AAV100000022"
+ assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, False, True, token_ID,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ for i in range(5):
+ hotp_code = C.NK_get_hotp_code(1)
+ assert hotp_code != 0
+ assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
-def test_TOTP_RFC_no_pin_protection(C):
+@pytest.mark.xfail(reason="firmware bug: set time command not always changes the time on stick thus failing this test, "
+ "this does not influence normal use since setting time is not done every TOTP code request")
+@pytest.mark.parametrize("PIN_protection", [False, True, ])
+def test_TOTP_RFC_usepin(C, PIN_protection):
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
# test according to https://tools.ietf.org/html/rfc6238#appendix-B
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_totp_slot(1, 'python_test', RFC_SECRET, 30, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(1, 'python_test', RFC_SECRET, 30, True, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
+ get_func = None
+ if PIN_protection:
+ get_func = lambda x, y, z, r: C.NK_get_totp_code_PIN(x, y, z, r, DefaultPasswords.USER_TEMP)
+ else:
+ get_func = C.NK_get_totp_code
+
test_data = [
(59, 1, 94287082),
(1111111109, 0x00000000023523EC, 7081804),
@@ -204,8 +307,17 @@ def test_TOTP_RFC_no_pin_protection(C):
(1234567890, 0x000000000273EF07, 89005924),
]
for t, T, code in test_data:
- C.NK_totp_set_time(t)
- r = C.NK_get_totp_code(1, T, 0, 30) # FIXME T is not changing the outcome
+ """
+ FIXME without the delay 50% of tests fails, with it only 12%, higher delay removes fails
+ -> set_time function not always works, to investigate why
+ """
+ # import time
+ # time.sleep(2)
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ if PIN_protection:
+ C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP)
+ assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK # FIXME needs admin authentication
+ r = get_func(1, T, 0, 30) # FIXME T is not changing the outcome
assert code == r
@@ -228,7 +340,8 @@ def test_get_slot_names(C):
def test_get_OTP_codes(C):
- C.NK_set_debug(True)
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_erase_hotp_slot(0, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
@@ -247,18 +360,18 @@ def test_get_OTP_codes(C):
def test_get_code_user_authorize(C):
C.NK_set_debug(True)
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_totp_slot(0, 'python_otp_auth', RFC_SECRET, 30, True,
+ assert C.NK_write_totp_slot(0, 'python_otp_auth', RFC_SECRET, 30, True, False, False, "",
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
# enable PIN protection of OTP codes with write_config
# TODO create convinience function on C API side to enable/disable OTP USER_PIN protection
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, True, True, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
code = C.NK_get_totp_code(0, 0, 0, 0)
assert code == 0
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_NOT_AUTHORIZED
# disable PIN protection with write_config
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
code = C.NK_get_totp_code(0, 0, 0, 0)
assert code != 0
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
@@ -273,18 +386,45 @@ def cast_pointer_to_tuple(obj, typen, len):
def test_read_write_config(C):
C.NK_set_debug(True)
- # let's set sample config with pin protection and disabled capslock
+ # let's set sample config with pin protection and disabled scrolllock
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, False, True, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(0, 1, 2, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
config_raw_data = C.NK_read_config()
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
- assert config == (True, False, True, True, False)
+ assert config == (0, 1, 2, True, False)
# restore defaults and check
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_write_config(True, True, True, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
config_raw_data = C.NK_read_config()
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
- assert config == (True, True, True, False, True)
+ assert config == (255, 255, 255, False, True)
+
+
+def wait(t):
+ import time
+ msg = 'Waiting for %d seconds' % t
+ print(msg.center(40, '='))
+ time.sleep(t)
+
+
+# @pytest.mark.skip()
+def test_factory_reset(C):
+ C.NK_set_debug(True)
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, False, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_hotp_code(1) == 755224
+ assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ wait(10)
+ assert C.NK_get_hotp_code(1) != 287082
+ assert C.NK_get_last_command_status() == DeviceErrorCode.NOT_PROGRAMMED
+ # restore AES key
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK