aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorszszszsz <szszszsz@users.noreply.github.com>2016-12-12 17:06:35 +0100
committerGitHub <noreply@github.com>2016-12-12 17:06:35 +0100
commited5044da43172d86a1aa475473561a4818b7c69c (patch)
treea6d3775f20ac86e7cdbbc151e0f51620d1399e56
parentf60f2cf0144a91769a5fc00fac1314d2e00cdf0d (diff)
parente26c6da38c674d8ec37e402132dab823bd22bd36 (diff)
downloadlibnitrokey-ed5044da43172d86a1aa475473561a4818b7c69c.tar.gz
libnitrokey-ed5044da43172d86a1aa475473561a4818b7c69c.tar.bz2
Merge pull request #53 from Nitrokey/nk_pro_0.8_authorization_fix-longer_secretv2.0
Support for Nitrokey Pro 0.8
-rw-r--r--CMakeLists.txt2
-rw-r--r--NK_C_API.cc7
-rw-r--r--NK_C_API.h6
-rw-r--r--NitrokeyManager.cc237
-rw-r--r--command_id.cc6
-rw-r--r--include/NitrokeyManager.h20
-rw-r--r--include/command_id.h2
-rw-r--r--include/device.h9
-rw-r--r--include/dissect.h59
-rw-r--r--include/stick10_commands_0.8.h312
-rw-r--r--include/stick20_commands.h10
-rw-r--r--misc.cc7
-rw-r--r--unittest/conftest.py16
-rw-r--r--unittest/constants.py2
-rw-r--r--unittest/misc.py24
-rw-r--r--unittest/requirements.txt4
-rw-r--r--unittest/test3.cc220
-rw-r--r--unittest/test_library.py15
-rw-r--r--unittest/test_pro.py276
-rw-r--r--unittest/test_storage.py22
20 files changed, 1149 insertions, 107 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9cd54f..c324067 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,7 +32,9 @@ set(SOURCE_FILES
unittest/test_C_API.cpp
unittest/catch_main.cpp
unittest/test2.cc
+ unittest/test3.cc
include/LongOperationInProgressException.h
+ include/stick10_commands_0.8.h
)
add_executable(libnitrokey ${SOURCE_FILES}) \ No newline at end of file
diff --git a/NK_C_API.cc b/NK_C_API.cc
index d42840b..e513a3b 100644
--- a/NK_C_API.cc
+++ b/NK_C_API.cc
@@ -471,6 +471,13 @@ extern int NK_get_progress_bar_value() {
});
}
+extern int NK_get_major_firmware_version(){
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&](){
+ return m->get_major_firmware_version();
+ });
+}
+
}
diff --git a/NK_C_API.h b/NK_C_API.h
index a446a62..7f01900 100644
--- a/NK_C_API.h
+++ b/NK_C_API.h
@@ -324,7 +324,11 @@ extern int NK_erase_password_safe_slot(uint8_t slot_number);
*/
extern int NK_is_AES_supported(const char *user_password);
-
+/**
+ * Get device's major firmware version
+ * @return 7,8 for Pro and major for Storage
+ */
+extern int NK_get_major_firmware_version();
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
index 20f4f14..ddec600 100644
--- a/NitrokeyManager.cc
+++ b/NitrokeyManager.cc
@@ -3,6 +3,8 @@
#include "include/NitrokeyManager.h"
#include "include/LibraryException.h"
#include <algorithm>
+#include <unordered_map>
+#include <stick20_commands.h>
#include "include/misc.h"
namespace nitrokey{
@@ -35,7 +37,10 @@ namespace nitrokey{
// package type to auth, auth type [Authorize,UserAuthorize]
template <typename S, typename A, typename T>
- void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device){
+ void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device){
+ if (!is_authorization_command_supported()){
+ Log::instance()("Authorization command not supported, skipping", Loglevel::WARNING);
+ }
auto auth = get_payload<A>();
strcpyT(auth.temporary_password, admin_temporary_password);
auth.crc_to_authorize = S::CommandTransaction::getCRC(package);
@@ -112,16 +117,25 @@ namespace nitrokey{
}
uint32_t NitrokeyManager::get_HOTP_code(uint8_t slot_number, const char *user_temporary_password) {
- if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+
+ if (is_authorization_command_supported()){
auto gh = get_payload<GetHOTP>();
gh.slot_number = get_internal_slot_number_for_hotp(slot_number);
-
if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
authorize_packet<GetHOTP, UserAuthorize>(gh, user_temporary_password, device);
}
-
auto resp = GetHOTP::CommandTransaction::run(*device, gh);
return resp.data().code;
+ } else {
+ auto gh = get_payload<stick10_08::GetHOTP>();
+ gh.slot_number = get_internal_slot_number_for_hotp(slot_number);
+ if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0) {
+ strcpyT(gh.temporary_user_password, user_temporary_password);
+ }
+ auto resp = stick10_08::GetHOTP::CommandTransaction::run(*device, gh);
+ return resp.data().code;
+ }
}
@@ -135,26 +149,41 @@ namespace nitrokey{
const char *user_temporary_password) {
if(!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_totp(slot_number);
- auto gt = get_payload<GetTOTP>();
- gt.slot_number = slot_number;
- gt.challenge = challenge;
- gt.last_interval = last_interval;
- gt.last_totp_time = last_totp_time;
- if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
- authorize_packet<GetTOTP, UserAuthorize>(gt, user_temporary_password, device);
+ if (is_authorization_command_supported()){
+ auto gt = get_payload<GetTOTP>();
+ gt.slot_number = slot_number;
+ gt.challenge = challenge;
+ gt.last_interval = last_interval;
+ gt.last_totp_time = last_totp_time;
+
+ if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
+ authorize_packet<GetTOTP, UserAuthorize>(gt, user_temporary_password, device);
+ }
+ auto resp = GetTOTP::CommandTransaction::run(*device, gt);
+ return resp.data().code;
+ } else {
+ auto gt = get_payload<stick10_08::GetTOTP>();
+ strcpyT(gt.temporary_user_password, user_temporary_password);
+ gt.slot_number = slot_number;
+ auto resp = stick10_08::GetTOTP::CommandTransaction::run(*device, gt);
+ return resp.data().code;
}
- auto resp = GetTOTP::CommandTransaction::run(*device, gt);
- return resp.data().code;
+
}
bool NitrokeyManager::erase_slot(uint8_t slot_number, const char *temporary_password) {
+ if (is_authorization_command_supported()){
auto p = get_payload<EraseSlot>();
p.slot_number = slot_number;
-
authorize_packet<EraseSlot, Authorize>(p, temporary_password, device);
-
auto resp = EraseSlot::CommandTransaction::run(*device,p);
+ } else {
+ auto p = get_payload<stick10_08::EraseSlot>();
+ p.slot_number = slot_number;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ auto resp = stick10_08::EraseSlot::CommandTransaction::run(*device,p);
+ }
return true;
}
@@ -171,6 +200,16 @@ namespace nitrokey{
}
template <typename T, typename U>
+ void vector_copy_ranged(T& dest, std::vector<U> &vec, size_t begin, size_t elements_to_copy){
+ const size_t d_size = sizeof(dest);
+ if(d_size < elements_to_copy){
+ throw TargetBufferSmallerThanSource(elements_to_copy, d_size);
+ }
+ std::fill(dest, dest+d_size, 0);
+ std::copy(vec.begin() + begin, vec.begin() +begin + elements_to_copy, dest);
+ }
+
+ template <typename T, typename U>
void vector_copy(T& dest, std::vector<U> &vec){
const size_t d_size = sizeof(dest);
if(d_size < vec.size()){
@@ -185,60 +224,127 @@ namespace nitrokey{
const char *temporary_password) {
if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
- slot_number = get_internal_slot_number_for_hotp(slot_number);
- auto payload = get_payload<WriteToHOTPSlot>();
- payload.slot_number = slot_number;
- auto secret_bin = misc::hex_string_to_byte(secret);
- vector_copy(payload.slot_secret, secret_bin);
- strcpyT(payload.slot_name, slot_name);
- strcpyT(payload.slot_token_id, token_ID);
+ int internal_slot_number = get_internal_slot_number_for_hotp(slot_number);
+ if (is_authorization_command_supported()){
+ write_HOTP_slot_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ } else {
+ write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ }
+ return true;
+ }
+
+ void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret,
+ uint64_t hotp_counter, bool use_8_digits, bool use_enter,
+ bool use_tokenID, const char *token_ID, const char *temporary_password) {
+ auto payload = get_payload<WriteToHOTPSlot>();
+ payload.slot_number = slot_number;
+ auto secret_bin = misc::hex_string_to_byte(secret);
+ vector_copy(payload.slot_secret, secret_bin);
+ strcpyT(payload.slot_name, slot_name);
+ strcpyT(payload.slot_token_id, token_ID);
switch (device->get_device_model() ){
case DeviceModel::PRO: {
payload.slot_counter = hotp_counter;
break;
}
case DeviceModel::STORAGE: {
- std::string counter = std::to_string(hotp_counter);
+ string counter = to_string(hotp_counter);
strcpyT(payload.slot_counter_s, counter.c_str());
break;
}
default:
- nitrokey::log::Log::instance()( std::string(__FILE__) + std::to_string(__LINE__) +
- std::string(__FUNCTION__) + std::string(" Unhandled device model for HOTP")
- , nitrokey::log::Loglevel::DEBUG);
+ Log::instance()(string(__FILE__) + to_string(__LINE__) +
+ string(__FUNCTION__) + string(" Unhandled device model for HOTP")
+ , Loglevel::DEBUG);
break;
}
- payload.use_8_digits = use_8_digits;
- payload.use_enter = use_enter;
- payload.use_tokenID = use_tokenID;
+ payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
- authorize_packet<WriteToHOTPSlot, Authorize>(payload, temporary_password, device);
+ authorize_packet<WriteToHOTPSlot, Authorize>(payload, temporary_password, device);
- auto resp = WriteToHOTPSlot::CommandTransaction::run(*device, payload);
- return true;
+ auto resp = WriteToHOTPSlot::CommandTransaction::run(*device, payload);
}
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>();
if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
+ int internal_slot_number = get_internal_slot_number_for_totp(slot_number);
+
+ if (is_authorization_command_supported()){
+ write_TOTP_slot_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ } else {
+ write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID,
+ token_ID, temporary_password);
+ }
- slot_number = get_internal_slot_number_for_totp(slot_number);
- payload.slot_number = slot_number;
- auto secret_bin = misc::hex_string_to_byte(secret);
- vector_copy(payload.slot_secret, secret_bin);
- 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;
-
- authorize_packet<WriteToTOTPSlot, Authorize>(payload, temporary_password, device);
-
- auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload);
- return true;
+ return true;
+ }
+
+ void NitrokeyManager::write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name,
+ const char *secret,
+ uint64_t counter_or_interval, bool use_8_digits, bool use_enter,
+ bool use_tokenID, const char *token_ID,
+ const char *temporary_password) const {
+
+ auto payload2 = get_payload<stick10_08::SendOTPData>();
+ strcpyT(payload2.temporary_admin_password, temporary_password);
+ strcpyT(payload2.data, slot_name);
+ payload2.setTypeName();
+ stick10_08::SendOTPData::CommandTransaction::run(*device, payload2);
+
+ payload2.setTypeSecret();
+ payload2.id = 0;
+ auto secret_bin = misc::hex_string_to_byte(secret);
+ auto remaining_secret_length = secret_bin.size();
+ const auto maximum_OTP_secret_size = 40;
+ if(remaining_secret_length > maximum_OTP_secret_size){
+ throw TargetBufferSmallerThanSource(remaining_secret_length, maximum_OTP_secret_size);
+ }
+
+ while (remaining_secret_length>0){
+ const auto bytesToCopy = std::min(sizeof(payload2.data), remaining_secret_length);
+ const auto start = secret_bin.size() - remaining_secret_length;
+ memset(payload2.data, 0, sizeof(payload2.data));
+ vector_copy_ranged(payload2.data, secret_bin, start, bytesToCopy);
+ stick10_08::SendOTPData::CommandTransaction::run(*device, payload2);
+ remaining_secret_length -= bytesToCopy;
+ payload2.id++;
+ }
+
+ auto payload = get_payload<stick10_08::WriteToOTPSlot>();
+ strcpyT(payload.temporary_admin_password, temporary_password);
+ strcpyT(payload.slot_token_id, token_ID);
+ payload.use_8_digits = use_8_digits;
+ payload.use_enter = use_enter;
+ payload.use_tokenID = use_tokenID;
+ payload.slot_counter_or_interval = counter_or_interval;
+ payload.slot_number = internal_slot_number;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(*device, payload);
+ }
+
+ void NitrokeyManager::write_TOTP_slot_authorize(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>();
+ payload.slot_number = slot_number;
+ auto secret_bin = misc::hex_string_to_byte(secret);
+ vector_copy(payload.slot_secret, secret_bin);
+ 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;
+
+ authorize_packet<WriteToTOTPSlot, Authorize>(payload, temporary_password, device);
+
+ auto resp = WriteToTOTPSlot::CommandTransaction::run(*device, payload);
}
const char * NitrokeyManager::get_totp_slot_name(uint8_t slot_number) {
@@ -461,16 +567,18 @@ namespace nitrokey{
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>();
+ auto p = get_payload<stick10_08::WriteGeneralConfig>();
p.numlock = (uint8_t) numlock;
p.capslock = (uint8_t) capslock;
p.scrolllock = (uint8_t) scrolllock;
p.enable_user_password = (uint8_t) enable_user_password;
p.delete_user_password = (uint8_t) delete_user_password;
-
- authorize_packet<WriteGeneralConfig, Authorize>(p, admin_temporary_password, device);
-
- WriteGeneralConfig::CommandTransaction::run(*device, p);
+ if (is_authorization_command_supported()){
+ authorize_packet<stick10_08::WriteGeneralConfig, Authorize>(p, admin_temporary_password, device);
+ } else {
+ strcpyT(p.temporary_admin_password, admin_temporary_password);
+ }
+ stick10_08::WriteGeneralConfig::CommandTransaction::run(*device, p);
}
vector<uint8_t> NitrokeyManager::read_config() {
@@ -480,6 +588,29 @@ namespace nitrokey{
return v;
}
+ bool NitrokeyManager::is_authorization_command_supported(){
+ //authorization command is supported for versions equal or below:
+ auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
+ {DeviceModel::PRO, 7},
+ {DeviceModel::STORAGE, 43},
+ });
+ return get_major_firmware_version() <= m[device->get_device_model()];
+ }
+
+ int NitrokeyManager::get_major_firmware_version(){
+ switch(device->get_device_model()){
+ case DeviceModel::PRO:{
+ auto status_p = GetStatus::CommandTransaction::run(*device);
+ return status_p.data().firmware_version; //7 or 8
+ }
+ case DeviceModel::STORAGE:{
+ auto status = stick20::GetDeviceStatus::CommandTransaction::run(*device);
+ return status.data().versionInfo.major;
+ }
+ }
+ return 0;
+ }
+
bool NitrokeyManager::is_AES_supported(const char *user_password) {
auto a = get_payload<IsAESSupported>();
strcpyT(a.user_password, user_password);
diff --git a/command_id.cc b/command_id.cc
index 9512b7d..f76a358 100644
--- a/command_id.cc
+++ b/command_id.cc
@@ -139,6 +139,12 @@ const char *commandid_to_string(CommandID id) {
return "DETECT_SC_AES";
case CommandID::NEW_AES_KEY:
return "NEW_AES_KEY";
+ case CommandID::WRITE_TO_SLOT_2:
+ return "WRITE_TO_SLOT_2";
+ break;
+ case CommandID::SEND_OTP_DATA:
+ return "SEND_OTP_DATA";
+ break;
}
return "UNKNOWN";
}
diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h
index 60fa753..fd39445 100644
--- a/include/NitrokeyManager.h
+++ b/include/NitrokeyManager.h
@@ -5,6 +5,7 @@
#include "log.h"
#include "device_proto.h"
#include "stick10_commands.h"
+#include "stick10_commands_0.8.h"
#include "stick20_commands.h"
#include <vector>
#include <memory>
@@ -110,6 +111,12 @@ namespace nitrokey {
int get_progress_bar_value();
~NitrokeyManager();
+ bool is_authorization_command_supported();
+
+ template <typename S, typename A, typename T>
+ void authorize_packet(T &package, const char *admin_temporary_password, shared_ptr<Device> device);
+ int get_major_firmware_version();
+
private:
NitrokeyManager();
@@ -128,6 +135,19 @@ namespace nitrokey {
template <typename ProCommand, PasswordKind StoKind>
void change_PIN_general(char *current_PIN, char *new_PIN);
+ void write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password);
+
+ void write_TOTP_slot_authorize(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);
+
+ void write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name, const char *secret,
+ uint64_t counter_or_interval,
+ bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
+ const char *temporary_password) const;
+
};
}
diff --git a/include/command_id.h b/include/command_id.h
index a3806f0..346b750 100644
--- a/include/command_id.h
+++ b/include/command_id.h
@@ -65,6 +65,8 @@ enum class CommandID : uint8_t {
FACTORY_RESET = 0x13,
CHANGE_USER_PIN = 0x14,
CHANGE_ADMIN_PIN = 0x15,
+ WRITE_TO_SLOT_2 = 0x16,
+ SEND_OTP_DATA = 0x17,
ENABLE_CRYPTED_PARI = 0x20,
DISABLE_CRYPTED_PARI = 0x20 + 1, //@unused
diff --git a/include/device.h b/include/device.h
index 3f18921..62c4073 100644
--- a/include/device.h
+++ b/include/device.h
@@ -12,6 +12,15 @@ namespace nitrokey {
namespace device {
using namespace std::chrono_literals;
+ struct EnumClassHash
+ {
+ template <typename T>
+ std::size_t operator()(T t) const
+ {
+ return static_cast<std::size_t>(t);
+ }
+ };
+
enum class DeviceModel{
PRO,
STORAGE
diff --git a/include/dissect.h b/include/dissect.h
index 59e6e9c..8c975c5 100644
--- a/include/dissect.h
+++ b/include/dissect.h
@@ -36,44 +36,65 @@ class QueryDissector : semantics::non_constructible {
}
};
+
+
+
template <CommandID id, class HIDPacket>
class ResponseDissector : semantics::non_constructible {
public:
+ static std::string status_translate_device(int status){
+ auto enum_status = static_cast<proto::stick10::device_status>(status);
+ switch (enum_status){
+ case stick10::device_status::ok: return "OK";
+ case stick10::device_status::busy: return "BUSY";
+ case stick10::device_status::error: return "ERROR";
+ case stick10::device_status::received_report: return "RECEIVED_REPORT";
+ }
+ return std::string("UNKNOWN: ") + std::to_string(status);
+ }
+
+ static std::string to_upper(std::string str){
+ for (auto & c: str) c = toupper(c);
+ return str;
+ }
+ static std::string status_translate_command(int status){
+ auto enum_status = static_cast<proto::stick10::command_status >(status);
+ switch (enum_status) {
+#define p(X) case X: return to_upper(std::string(#X));
+ p(stick10::command_status::ok)
+ p(stick10::command_status::wrong_CRC)
+ p(stick10::command_status::wrong_slot)
+ p(stick10::command_status::slot_not_programmed)
+ p(stick10::command_status::wrong_password)
+ p(stick10::command_status::not_authorized)
+ p(stick10::command_status::timestamp_warning)
+ p(stick10::command_status::no_name_error)
+ p(stick10::command_status::not_supported)
+ p(stick10::command_status::unknown_command)
+ p(stick10::command_status::AES_dec_failed)
+#undef p
+ }
+ return std::string("UNKNOWN: ") + std::to_string(status);
+ }
+
static std::string dissect(const HIDPacket &pod) {
std::stringstream out;
// FIXME use values from firmware (possibly generate separate
// header automatically)
- std::string status[4];
- status[0] = " STATUS_READY";
- status[1] = " STATUS_BUSY";
- status[2] = " STATUS_ERROR";
- status[3] = " STATUS_RECEIVED_REPORT";
- std::string cmd[11];
- cmd[0] = " CMD_STATUS_OK";
- cmd[1] = " CMD_STATUS_WRONG_CRC";
- cmd[2] = " CMD_STATUS_WRONG_SLOT";
- cmd[3] = " CMD_STATUS_SLOT_NOT_PROGRAMMED";
- cmd[4] = " CMD_STATUS_WRONG_PASSWORD";
- cmd[5] = " CMD_STATUS_NOT_AUTHORIZED";
- cmd[6] = " CMD_STATUS_TIMESTAMP_WARNING";
- cmd[7] = " CMD_STATUS_NO_NAME_ERROR";
- cmd[8] = " CMD_STATUS_NOT_SUPPORTED";
- cmd[9] = " CMD_STATUS_UNKNOWN_COMMAND";
- cmd[10] = " CMD_STATUS_AES_DEC_FAILED";
out << "Raw HID packet:" << std::endl;
out << ::nitrokey::misc::hexdump((const char *)(&pod), sizeof pod);
out << "Device status:\t" << pod.device_status + 0 << " "
- << status[pod.device_status] << std::endl;
+ << status_translate_device(pod.device_status) << std::endl;
out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id)) << " hex: " << std::hex << (int)pod.command_id
<< std::endl;
out << "Last command CRC:\t"
<< std::hex << std::setw(2) << std::setfill('0')
<< pod.last_command_crc << std::endl;
out << "Last command status:\t" << pod.last_command_status + 0 << " "
- << cmd[pod.last_command_status] << std::endl;
+ << status_translate_command(pod.last_command_status) << std::endl;
out << "CRC:\t"
<< std::hex << std::setw(2) << std::setfill('0')
<< pod.crc << std::endl;
diff --git a/include/stick10_commands_0.8.h b/include/stick10_commands_0.8.h
new file mode 100644
index 0000000..9594d1e
--- /dev/null
+++ b/include/stick10_commands_0.8.h
@@ -0,0 +1,312 @@
+//
+// Created by sz on 08.11.16.
+//
+
+#ifndef LIBNITROKEY_STICK10_COMMANDS_0_8_H
+#define LIBNITROKEY_STICK10_COMMANDS_0_8_H
+
+#include <bitset>
+#include <iomanip>
+#include <string>
+#include <sstream>
+#include "inttypes.h"
+#include "command.h"
+#include "device_proto.h"
+#include "stick10_commands.h"
+
+namespace nitrokey {
+ namespace proto {
+
+/*
+ * Stick10 protocol definition
+ */
+ namespace stick10_08 {
+ using stick10::FirstAuthenticate;
+ using stick10::UserAuthenticate;
+ using stick10::SetTime;
+ using stick10::GetStatus;
+ using stick10::BuildAESKey;
+ using stick10::ChangeAdminPin;
+ using stick10::ChangeUserPin;
+ using stick10::EnablePasswordSafe;
+ using stick10::ErasePasswordSafeSlot;
+ using stick10::FactoryReset;
+ using stick10::GetPasswordRetryCount;
+ using stick10::GetUserPasswordRetryCount;
+ using stick10::GetPasswordSafeSlotLogin;
+ using stick10::GetPasswordSafeSlotName;
+ using stick10::GetPasswordSafeSlotPassword;
+ using stick10::GetPasswordSafeSlotStatus;
+ using stick10::GetSlotName;
+ using stick10::IsAESSupported;
+ using stick10::LockDevice;
+ using stick10::PasswordSafeInitKey;
+ using stick10::PasswordSafeSendSlotViaHID;
+ using stick10::SetPasswordSafeSlotData;
+ using stick10::SetPasswordSafeSlotData2;
+ using stick10::UnlockUserPassword;
+ using stick10::ReadSlot;
+
+ class EraseSlot : Command<CommandID::ERASE_SLOT> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint8_t temporary_admin_password[25];
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+ class SendOTPData : Command<CommandID::SEND_OTP_DATA> {
+ //admin auth
+ public:
+ struct CommandPayload {
+ uint8_t temporary_admin_password[25];
+ uint8_t type; //S-secret, N-name
+ uint8_t id; //multiple reports for values longer than 30 bytes
+ uint8_t data[30]; //data, does not need null termination
+
+ bool isValid() const { return true; }
+
+ void setTypeName(){
+ type = 'N';
+ }
+ void setTypeSecret(){
+ type = 'S';
+ }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl;
+ ss << "type:\t" << type << std::endl;
+ ss << "id:\t" << (int)id << std::endl;
+ ss << "data:" << std::endl
+ << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data);
+ return ss.str();
+ }
+ } __packed;
+
+
+ struct ResponsePayload {
+ union {
+ uint8_t data[40];
+ } __packed;
+
+ bool isValid() const { return true; }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "data:" << std::endl
+ << ::nitrokey::misc::hexdump((const char *) (&data), sizeof data);
+ return ss.str();
+ }
+ } __packed;
+
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+ class WriteToOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
+ //admin auth
+ public:
+ struct CommandPayload {
+ uint8_t temporary_admin_password[25];
+ uint8_t slot_number;
+ union {
+ uint64_t slot_counter_or_interval;
+ uint8_t slot_counter_s[8];
+ } __packed;
+ union {
+ uint8_t _slot_config;
+ struct {
+ bool use_8_digits : 1;
+ bool use_enter : 1;
+ bool use_tokenID : 1;
+ };
+ };
+ 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;
+ };
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "temporary_admin_password:\t" << temporary_admin_password << std::endl;
+ 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;
+ ss << "slot_number:\t" << (int) (slot_number) << std::endl;
+ ss << "slot_counter_or_interval:\t[" << (int) slot_counter_or_interval << "]\t"
+ << ::nitrokey::misc::hexdump((const char *) (&slot_counter_or_interval), sizeof slot_counter_or_interval, false);
+
+ ss << "slot_token_id:\t";
+ for (auto i : slot_token_id)
+ ss << std::hex << std::setw(2) << std::setfill('0') << (int) i << " ";
+ ss << std::endl;
+
+ return ss.str();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
+ class GetHOTP : Command<CommandID::GET_CODE> {
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ struct {
+ uint64_t challenge; //@unused
+ uint64_t last_totp_time; //@unused
+ uint8_t last_interval; //@unused
+ } __packed _unused;
+ uint8_t temporary_user_password[25];
+
+ bool isValid() const { return (slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "temporary_user_password:\t" << temporary_user_password << std::endl;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ union {
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
+ struct {
+ uint32_t code;
+ 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 << "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;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+
+ class GetTOTP : Command<CommandID::GET_CODE> {
+ //user auth
+ public:
+ struct CommandPayload {
+ uint8_t slot_number;
+ uint64_t challenge; //@unused
+ uint64_t last_totp_time; //@unused
+ uint8_t last_interval; //@unused
+ uint8_t temporary_user_password[25];
+
+ bool isValid() const { return !(slot_number & 0xF0); }
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << "temporary_user_password:\t" << temporary_user_password << std::endl;
+ ss << "slot_number:\t" << (int)(slot_number) << std::endl;
+ ss << "challenge:\t" << (challenge) << std::endl;
+ ss << "last_totp_time:\t" << (last_totp_time) << std::endl;
+ ss << "last_interval:\t" << (int)(last_interval) << std::endl;
+ return ss.str();
+ }
+ } __packed;
+
+ struct ResponsePayload {
+ union {
+ uint8_t whole_response[18]; //14 bytes reserved for config, but used only 1
+ struct {
+ uint32_t code;
+ 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 << "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;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct ResponsePayload>
+ CommandTransaction;
+ };
+
+
+ class WriteGeneralConfig : Command<CommandID::WRITE_CONFIG> {
+ //admin auth
+ public:
+ struct CommandPayload {
+ union{
+ uint8_t config[5];
+ struct{
+ 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;
+ };
+ };
+ uint8_t temporary_admin_password[25];
+
+ std::string dissect() const {
+ std::stringstream ss;
+ 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();
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+ }
+ }
+}
+#endif //LIBNITROKEY_STICK10_COMMANDS_0_8_H
diff --git a/include/stick20_commands.h b/include/stick20_commands.h
index 1af9da3..e6df770 100644
--- a/include/stick20_commands.h
+++ b/include/stick20_commands.h
@@ -125,7 +125,17 @@ namespace nitrokey {
uint16_t MagicNumber_StickConfig_u16;
uint8_t ReadWriteFlagUncryptedVolume_u8;
uint8_t ReadWriteFlagCryptedVolume_u8;
+
+ union{
uint8_t VersionInfo_au8[4];
+ struct {
+ uint8_t __unused;
+ uint8_t major;
+ uint8_t __unused2;
+ uint8_t minor;
+ } __packed versionInfo;
+ };
+
uint8_t ReadWriteFlagHiddenVolume_u8;
uint8_t FirmwareLocked_u8;
uint8_t NewSDCardFound_u8;
diff --git a/misc.cc b/misc.cc
index c9d38cb..3f15520 100644
--- a/misc.cc
+++ b/misc.cc
@@ -13,10 +13,11 @@ std::vector<uint8_t> hex_string_to_byte(const char* hexString){
const size_t big_string_size = 256; //arbitrary 'big' number
const size_t s_size = strlen(hexString);
const size_t d_size = s_size/2;
- if (s_size%2!=0 || s_size==0 || s_size>big_string_size){
+ if (s_size%2!=0 || s_size>big_string_size){
throw InvalidHexString(0);
}
- auto data = std::vector<uint8_t>(d_size, 0);
+ auto data = std::vector<uint8_t>();
+ data.reserve(d_size);
char buf[2];
for(int i=0; i<s_size; i++){
@@ -28,7 +29,7 @@ std::vector<uint8_t> hex_string_to_byte(const char* hexString){
}
buf[i%2] = c;
if (i%2==1){
- data[i/2] = strtoul(buf, NULL, 16) & 0xFF;
+ data.push_back( strtoul(buf, NULL, 16) & 0xFF );
}
}
return data;
diff --git a/unittest/conftest.py b/unittest/conftest.py
index 68227d5..88bf7d0 100644
--- a/unittest/conftest.py
+++ b/unittest/conftest.py
@@ -2,6 +2,16 @@ import pytest
from misc import ffi
+device_type = None
+
+def skip_if_device_version_lower_than(allowed_devices):
+ global device_type
+ model, version = device_type
+ infinite_version_number = 999
+ if allowed_devices.get(model, infinite_version_number) > version:
+ pytest.skip('This device model is not applicable to run this test')
+
+
@pytest.fixture(scope="module")
def C(request):
fp = '../NK_C_API.h'
@@ -24,7 +34,11 @@ def C(request):
nk_login = C.NK_login_auto()
if nk_login != 1:
print('No devices detected!')
- assert nk_login == 1 # returns 0 if not connected or wrong model or 1 when connected
+ assert nk_login != 0 # returns 0 if not connected or wrong model or 1 when connected
+ global device_type
+ firmware_version = C.NK_get_major_firmware_version()
+ model = 'P' if firmware_version in [7,8] else 'S'
+ device_type = (model, firmware_version)
# 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
diff --git a/unittest/constants.py b/unittest/constants.py
index 78a219b..0897b42 100644
--- a/unittest/constants.py
+++ b/unittest/constants.py
@@ -2,7 +2,7 @@ from enum import Enum
from misc import to_hex
RFC_SECRET_HR = '12345678901234567890'
-RFC_SECRET = to_hex(RFC_SECRET_HR) # '12345678901234567890'
+RFC_SECRET = to_hex(RFC_SECRET_HR) # '31323334353637383930...'
# print( repr((RFC_SECRET, RFC_SECRET_, len(RFC_SECRET))) )
diff --git a/unittest/misc.py b/unittest/misc.py
index b45436d..f4d7731 100644
--- a/unittest/misc.py
+++ b/unittest/misc.py
@@ -20,21 +20,29 @@ def cast_pointer_to_tuple(obj, typen, len):
# config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
return tuple(ffi.cast("%s [%d]" % (typen, len), obj)[0:len])
-def get_firmware_version_from_status(C):
- status = gs(C.NK_status())
- status = [s if 'firmware_version' in s else '' for s in status.split('\n')]
- firmware = status[0].split(':')[1]
+
+def get_devices_firmware_version(C):
+ firmware = C.NK_get_major_firmware_version()
return firmware
def is_pro_rtm_07(C):
- firmware = get_firmware_version_from_status(C)
- return '07 00' in firmware
+ firmware = get_devices_firmware_version(C)
+ return firmware == 7
+
+
+def is_pro_rtm_08(C):
+ firmware = get_devices_firmware_version(C)
+ return firmware == 8
def is_storage(C):
"""
exact firmware storage is sent by other function
"""
- firmware = get_firmware_version_from_status(C)
- return '01 00' in firmware \ No newline at end of file
+ # TODO identify connected device directly
+ return not is_pro_rtm_08(C) and not is_pro_rtm_07(C)
+
+
+def is_long_OTP_secret_handled(C):
+ return is_pro_rtm_08(C) or is_storage(C) and get_devices_firmware_version(C) > 43
diff --git a/unittest/requirements.txt b/unittest/requirements.txt
new file mode 100644
index 0000000..7224741
--- /dev/null
+++ b/unittest/requirements.txt
@@ -0,0 +1,4 @@
+cffi
+pytest-repeat
+pytest-randomly
+enum
diff --git a/unittest/test3.cc b/unittest/test3.cc
new file mode 100644
index 0000000..9049365
--- /dev/null
+++ b/unittest/test3.cc
@@ -0,0 +1,220 @@
+//
+// Created by sz on 08.11.16.
+//
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main()
+
+static const char *const default_admin_pin = "12345678";
+static const char *const default_user_pin = "123456";
+const char * temporary_password = "123456789012345678901234";
+const char * RFC_SECRET = "12345678901234567890";
+
+#include "catch.hpp"
+
+#include <iostream>
+#include <string.h>
+#include <NitrokeyManager.h>
+#include "device_proto.h"
+#include "log.h"
+#include "stick10_commands_0.8.h"
+//#include "stick20_commands.h"
+
+using namespace std;
+using namespace nitrokey::device;
+using namespace nitrokey::proto;
+using namespace nitrokey::proto::stick10_08;
+using namespace nitrokey::log;
+using namespace nitrokey::misc;
+
+void connect_and_setup(Stick10 &stick) {
+ bool connected = stick.connect();
+ REQUIRE(connected == true);
+ Log::instance().set_loglevel(Loglevel::DEBUG);
+}
+
+void authorize(Stick10 &stick) {
+ auto authreq = get_payload<FirstAuthenticate>();
+ strcpy((char *) (authreq.card_password), default_admin_pin);
+ strcpy((char *) (authreq.temporary_password), temporary_password);
+ FirstAuthenticate::CommandTransaction::run(stick, authreq);
+
+ auto user_auth = get_payload<UserAuthenticate>();
+ strcpyT(user_auth.temporary_password, temporary_password);
+ strcpyT(user_auth.card_password, default_user_pin);
+ UserAuthenticate::CommandTransaction::run(stick, user_auth);
+}
+
+TEST_CASE("write slot", "[pronew]"){
+ Stick10 stick;
+ connect_and_setup(stick);
+ authorize(stick);
+
+ auto p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ p2.setTypeName();
+ strcpyT(p2.data, "test name aaa");
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ strcpyT(p2.data, RFC_SECRET);
+ p2.setTypeSecret();
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ auto p = get_payload<WriteToOTPSlot>();
+ strcpyT(p.temporary_admin_password, temporary_password);
+ p.use_8_digits = true;
+ p.slot_number = 0 + 0x10;
+ p.slot_counter_or_interval = 0;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p);
+
+ auto pc = get_payload<WriteGeneralConfig>();
+ pc.enable_user_password = 0;
+ strcpyT(pc.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, pc);
+
+ auto p3 = get_payload<GetHOTP>();
+ p3.slot_number = 0 + 0x10;
+ GetHOTP::CommandTransaction::run(stick, p3);
+
+}
+
+
+TEST_CASE("erase slot", "[pronew]"){
+ Stick10 stick;
+ connect_and_setup(stick);
+ authorize(stick);
+
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 0;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+
+ auto p3 = get_payload<GetHOTP>();
+ p3.slot_number = 0 + 0x10;
+ GetHOTP::CommandTransaction::run(stick, p3);
+
+ auto erase_payload = get_payload<EraseSlot>();
+ erase_payload.slot_number = 0 + 0x10;
+ strcpyT(erase_payload.temporary_admin_password, temporary_password);
+ EraseSlot::CommandTransaction::run(stick, erase_payload);
+
+ auto p4 = get_payload<GetHOTP>();
+ p4.slot_number = 0 + 0x10;
+ REQUIRE_THROWS(
+ GetHOTP::CommandTransaction::run(stick, p4)
+ );
+}
+
+TEST_CASE("write general config", "[pronew]") {
+ Stick10 stick;
+ connect_and_setup(stick);
+ authorize(stick);
+
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 1;
+ REQUIRE_THROWS(
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+ );
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+}
+
+TEST_CASE("authorize user HOTP", "[pronew]") {
+ Stick10 stick;
+ connect_and_setup(stick);
+ authorize(stick);
+
+ {
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 1;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+ }
+
+ auto p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ p2.setTypeName();
+ strcpyT(p2.data, "test name aaa");
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ strcpyT(p2.data, RFC_SECRET);
+ p2.setTypeSecret();
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ auto p = get_payload<WriteToOTPSlot>();
+ strcpyT(p.temporary_admin_password, temporary_password);
+ p.use_8_digits = true;
+ p.slot_number = 0 + 0x10;
+ p.slot_counter_or_interval = 0;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p);
+
+
+ auto p3 = get_payload<GetHOTP>();
+ p3.slot_number = 0 + 0x10;
+ REQUIRE_THROWS(
+ GetHOTP::CommandTransaction::run(stick, p3);
+ );
+ strcpyT(p3.temporary_user_password, temporary_password);
+ auto code_response = GetHOTP::CommandTransaction::run(stick, p3);
+ REQUIRE(code_response.data().code == 84755224);
+
+}
+
+TEST_CASE("check firmware version", "[pronew]") {
+ Stick10 stick;
+ connect_and_setup(stick);
+
+ auto p = GetStatus::CommandTransaction::run(stick);
+ REQUIRE(p.data().firmware_version == 8);
+}
+
+TEST_CASE("authorize user TOTP", "[pronew]") {
+ Stick10 stick;
+ connect_and_setup(stick);
+ authorize(stick);
+
+ {
+ auto p = get_payload<WriteGeneralConfig>();
+ p.enable_user_password = 1;
+ strcpyT(p.temporary_admin_password, temporary_password);
+ WriteGeneralConfig::CommandTransaction::run(stick, p);
+ }
+
+ auto p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ p2.setTypeName();
+ strcpyT(p2.data, "test name TOTP");
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ p2 = get_payload<SendOTPData>();
+ strcpyT(p2.temporary_admin_password, temporary_password);
+ strcpyT(p2.data, RFC_SECRET);
+ p2.setTypeSecret();
+ stick10_08::SendOTPData::CommandTransaction::run(stick, p2);
+
+ auto p = get_payload<WriteToOTPSlot>();
+ strcpyT(p.temporary_admin_password, temporary_password);
+ p.use_8_digits = true;
+ p.slot_number = 0 + 0x20;
+ p.slot_counter_or_interval = 30;
+ stick10_08::WriteToOTPSlot::CommandTransaction::run(stick, p);
+
+ auto p_get_totp = get_payload<GetTOTP>();
+ p_get_totp.slot_number = 0 + 0x20;
+
+ REQUIRE_THROWS(
+ GetTOTP::CommandTransaction::run(stick, p_get_totp);
+ );
+ strcpyT(p_get_totp.temporary_user_password, temporary_password);
+
+ auto p_set_time = get_payload<SetTime>();
+ p_set_time.reset = 1;
+ p_set_time.time = 59;
+ SetTime::CommandTransaction::run(stick, p_set_time);
+ auto code = GetTOTP::CommandTransaction::run(stick, p_get_totp);
+ REQUIRE(code.data().code == 94287082);
+
+} \ No newline at end of file
diff --git a/unittest/test_library.py b/unittest/test_library.py
index d0eef80..b24c72a 100644
--- a/unittest/test_library.py
+++ b/unittest/test_library.py
@@ -1,8 +1,9 @@
import pytest
-from misc import ffi, gs, to_hex
+from misc import ffi, gs, to_hex, is_pro_rtm_07, is_long_OTP_secret_handled
from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors
+
def test_too_long_strings(C):
new_password = '123123123'
long_string = 'a' * 100
@@ -35,30 +36,28 @@ def test_invalid_slot(C):
assert gs(C.NK_get_password_safe_slot_login(invalid_slot)) == ''
assert C.NK_get_last_command_status() == LibraryErrors.INVALID_SLOT
+
@pytest.mark.parametrize("invalid_hex_string",
- ['text', '00 ', '0xff', 'zzzzzzzzzzzz', 'fff', '', 'f' * 257, 'f' * 258])
+ ['text', '00 ', '0xff', 'zzzzzzzzzzzz', 'fff', 'f' * 257, 'f' * 258])
def test_invalid_secret_hex_string_for_OTP_write(C, invalid_hex_string):
"""
Tests for invalid secret hex string during writing to OTP slot. Invalid strings are not hexadecimal number,
empty or longer than 255 characters.
"""
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '',
DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING
assert C.NK_write_totp_slot(1, 'python_test', invalid_hex_string, 30, True, False, False, "",
DefaultPasswords.ADMIN_TEMP) == LibraryErrors.INVALID_HEX_STRING
-
def test_warning_binary_bigger_than_secret_buffer(C):
invalid_hex_string = to_hex('1234567890') * 3
+ if is_long_OTP_secret_handled(C):
+ invalid_hex_string *= 2
assert C.NK_write_hotp_slot(1, 'slot_name', invalid_hex_string, 0, True, False, False, '',
DefaultPasswords.ADMIN_TEMP) == LibraryErrors.TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE
-@pytest.mark.xfail(reason="TODO")
-def test_OTP_secret_started_from_null(C):
- assert False
-
-
@pytest.mark.skip(reason='Experimental')
def test_clear(C):
d = 'asdasdasd'
diff --git a/unittest/test_pro.py b/unittest/test_pro.py
index 6ab2af9..4a2a504 100644
--- a/unittest/test_pro.py
+++ b/unittest/test_pro.py
@@ -1,10 +1,15 @@
import pytest
+from conftest import skip_if_device_version_lower_than
from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET
from misc import ffi, gs, wait, cast_pointer_to_tuple
-from misc import is_pro_rtm_07, is_storage
+from misc import is_pro_rtm_07, is_pro_rtm_08, is_storage
+
def test_enable_password_safe(C):
+ """
+ All Password Safe tests depend on AES keys being initialized. They will fail otherwise.
+ """
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
@@ -61,7 +66,7 @@ def test_password_safe_slot_status(C):
def test_issue_device_locks_on_second_key_generation_in_sequence(C):
- if is_pro_rtm_07(C):
+ if is_pro_rtm_07(C) or is_pro_rtm_08(C):
pytest.skip("issue to register: device locks up "
"after below commands sequence (reinsertion fixes), skipping for now")
assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
@@ -75,6 +80,19 @@ def test_regenerate_aes_key(C):
assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+def test_enable_password_safe_after_factory_reset(C):
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ wait(10)
+ if is_storage(C):
+ assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ enable_password_safe_result = C.NK_enable_password_safe(DefaultPasswords.USER)
+ assert enable_password_safe_result == DeviceErrorCode.STATUS_AES_DEC_FAILED \
+ or is_storage(C) and enable_password_safe_result == DeviceErrorCode.WRONG_PASSWORD
+ 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="NK Pro firmware bug: regenerating AES key command not always results in cleared slot data")
def test_destroy_password_safe(C):
"""
@@ -96,6 +114,7 @@ def test_destroy_password_safe(C):
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_lock_device() == 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'
@@ -242,6 +261,7 @@ def test_HOTP_token(C):
assert hotp_code != 0
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
+
def test_HOTP_counters(C):
"""
# https://tools.ietf.org/html/rfc4226#page-32
@@ -285,6 +305,7 @@ def test_HOTP_64bit_counter(C):
assert C.NK_write_hotp_slot(slot_number, 'python_test', RFC_SECRET, t, use_8_digits, False, False, "",
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
code_device = str(C.NK_get_hotp_code(slot_number))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
dev_res += (t, code_device)
lib_res += (t, lib_at(t))
assert dev_res == lib_res
@@ -310,14 +331,18 @@ def test_TOTP_64bit_time(C):
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK
code_device = str((C.NK_get_totp_code(slot_number, T, 0, 30)))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
dev_res += (t, code_device)
lib_res += (t, lib_at(t))
assert dev_res == lib_res
-@pytest.mark.xfail(reason="NK Pro: possible firmware bug or communication issue: 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"
- "Rarely fail occurs on NK Storage")
+@pytest.mark.xfail(reason="NK Pro: Test fails in 50% of cases due to test vectors set 1 second before interval count change"
+ "Here time is changed on seconds side only and miliseconds part is not being reset apparently"
+ "This results in available time to test of half a second on average, thus 50% failed cases"
+ "With disabled two first test vectors test passess 10/10 times"
+ "Fail may also occurs on NK Storage with lower occurrency since it needs less time to execute "
+ "commands")
@pytest.mark.parametrize("PIN_protection", [False, True, ])
def test_TOTP_RFC_usepin(C, PIN_protection):
slot_number = 1
@@ -338,8 +363,8 @@ def test_TOTP_RFC_usepin(C, PIN_protection):
# Mode: Sha1, time step X=30
test_data = [
#Time T (hex) TOTP
- (59, 0x1, 94287082),
- (1111111109, 0x00000000023523EC, 7081804),
+ (59, 0x1, 94287082), # Warning - test vector time 1 second before interval count changes
+ (1111111109, 0x00000000023523EC, 7081804), # Warning - test vector time 1 second before interval count changes
(1111111111, 0x00000000023523ED, 14050471),
(1234567890, 0x000000000273EF07, 89005924),
(2000000000, 0x0000000003F940AA, 69279037),
@@ -359,6 +384,7 @@ def test_TOTP_RFC_usepin(C, PIN_protection):
correct += expected_code == code_from_device
assert data == responses or correct == len(test_data)
+
def test_get_slot_names(C):
C.NK_set_debug(True)
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
@@ -455,8 +481,6 @@ def test_read_write_config(C):
def test_factory_reset(C):
- if is_storage(C):
- pytest.skip('Recovery not implemented for NK Storage')
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
@@ -473,6 +497,8 @@ def test_factory_reset(C):
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
+ if is_storage(C):
+ assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
def test_get_status(C):
@@ -486,3 +512,235 @@ def test_get_serial_number(C):
sn = gs(sn)
assert len(sn) > 0
print(('Serial number of the device: ', sn))
+
+
+@pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102',
+ '00'*29+'ff', '00'*39+'ff', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ])
+def test_OTP_secret_started_from_null(C, secret):
+ """
+ NK Pro 0.8+, NK Storage 0.43+
+ """
+ skip_if_device_version_lower_than({'S': 43, 'P': 8})
+ if len(secret) > 40:
+ # feature: 320 bit long secret handling
+ skip_if_device_version_lower_than({'S': 44, 'P': 8})
+
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: oath.hotp(secret, t, format='dec6')
+ PIN_protection = False
+ use_8_digits = False
+ slot_number = 1
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ dev_res = []
+ lib_res = []
+ for t in range(1,5):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, 'null_secret', secret, t, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = str(C.NK_get_hotp_code(slot_number))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
+ dev_res += (t, code_device)
+ lib_res += (t, lib_at(t))
+ assert dev_res == lib_res
+
+
+@pytest.mark.parametrize("counter", [0, 3, 7, 0xffff,
+ 0xffffffff,
+ 0xffffffffffffffff] )
+def test_HOTP_slots_read_write_counter(C, counter):
+ """
+ Write different counters to all HOTP slots, read code and compare with 3rd party
+ :param counter:
+ """
+ if counter >= 1e7:
+ # Storage does not handle counters longer than 7 digits
+ skip_if_device_version_lower_than({'P': 7})
+
+ secret = RFC_SECRET
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: oath.hotp(secret, t, format='dec6')
+ PIN_protection = False
+ use_8_digits = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ dev_res = []
+ lib_res = []
+ for slot_number in range(3):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, 'HOTP rw' + str(slot_number), secret, counter, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = str(C.NK_get_hotp_code(slot_number))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
+ dev_res += (counter, code_device)
+ lib_res += (counter, lib_at(counter))
+ assert dev_res == lib_res
+
+
+@pytest.mark.parametrize("period", [30,60] )
+@pytest.mark.parametrize("time", range(21,70,20) )
+def test_TOTP_slots_read_write_at_time_period(C, time, period):
+ """
+ Write to all TOTP slots with specified period, read code at specified time
+ and compare with 3rd party
+ """
+ secret = RFC_SECRET
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: oath.totp(RFC_SECRET, t=t, period=period)
+ PIN_protection = False
+ use_8_digits = False
+ T = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ dev_res = []
+ lib_res = []
+ for slot_number in range(15):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(slot_number, 'TOTP rw' + str(slot_number), secret, period, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_totp_set_time(time) == DeviceErrorCode.STATUS_OK
+ code_device = str(C.NK_get_totp_code(slot_number, T, 0, period))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
+ dev_res += (time, code_device)
+ lib_res += (time, lib_at(time))
+ assert dev_res == lib_res
+
+
+@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] )
+def test_TOTP_secrets(C, secret):
+ '''
+ NK Pro 0.8+, NK Storage 0.44+
+ '''
+ skip_if_device_version_lower_than({'S': 44, 'P': 8})
+
+ if is_pro_rtm_07(C) and len(secret)>20*2: #*2 since secret is in hex
+ pytest.skip("Secret lengths over 20 bytes are not supported by NK Pro 0.7 ")
+ slot_number = 0
+ time = 0
+ period = 30
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: oath.totp(secret, t=t, period=period)
+ PIN_protection = False
+ use_8_digits = False
+ T = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ dev_res = []
+ lib_res = []
+ assert C.NK_write_totp_slot(slot_number, 'secret' + str(len(secret)), secret, period, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_totp_set_time(time) == DeviceErrorCode.STATUS_OK
+ code_device = str(C.NK_get_totp_code(slot_number, T, 0, period))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
+ dev_res += (time, code_device)
+ lib_res += (time, lib_at(time))
+ assert dev_res == lib_res
+
+
+@pytest.mark.parametrize("secret", [RFC_SECRET, 2*RFC_SECRET, '12'*10, '12'*30] )
+def test_HOTP_secrets(C, secret):
+ """
+ NK Pro 0.8+, NK Storage 0.44+
+ feature needed: support for 320bit secrets
+ """
+ skip_if_device_version_lower_than({'S': 44, 'P': 8})
+
+ slot_number = 0
+ counter = 0
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: oath.hotp(secret, counter=t)
+ PIN_protection = False
+ use_8_digits = False
+ T = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ dev_res = []
+ lib_res = []
+ assert C.NK_write_hotp_slot(slot_number, 'secret' + str(len(secret)), secret, counter, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ code_device = str(C.NK_get_hotp_code(slot_number))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
+ dev_res += (counter, code_device)
+ lib_res += (counter, lib_at(counter))
+ assert dev_res == lib_res
+
+
+def test_special_double_press(C):
+ """
+ requires manual check after function run
+ double press each of num-, scroll-, caps-lock and check inserted OTP codes (each 1st should be 755224)
+ on nkpro 0.7 scrolllock should do nothing, on nkpro 0.8+ should return OTP code
+ """
+ secret = RFC_SECRET
+ counter = 0
+ PIN_protection = False
+ use_8_digits = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_config(0, 1, 2, PIN_protection, not PIN_protection,
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ for slot_number in range(3):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, 'double' + str(slot_number), secret, counter, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ # requires manual check
+
+
+def test_edit_OTP_slot(C):
+ """
+ should change slots counter and name without changing its secret (using null secret for second update)
+ """
+ # counter does not reset under Storage v0.43
+ skip_if_device_version_lower_than({'S': 44, 'P': 7})
+
+ secret = RFC_SECRET
+ counter = 0
+ PIN_protection = False
+ use_8_digits = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ slot_number = 0
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ first_name = 'edit slot'
+ assert C.NK_write_hotp_slot(slot_number, first_name, secret, counter, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert gs(C.NK_get_hotp_slot_name(slot_number)) == first_name
+
+
+ first_code = C.NK_get_hotp_code(slot_number)
+ changed_name = 'changedname'
+ empty_secret = ''
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, changed_name, empty_secret, counter, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ second_code = C.NK_get_hotp_code(slot_number)
+ assert first_code == second_code
+ assert gs(C.NK_get_hotp_slot_name(slot_number)) == changed_name
+
+
+@pytest.mark.skip
+@pytest.mark.parametrize("secret", ['31323334353637383930'*2,'31323334353637383930'*4] )
+def test_TOTP_codes_from_nitrokeyapp(secret, C):
+ """
+ Helper test for manual TOTP check of written secret by Nitrokey App
+ Destined to run by hand
+ """
+ slot_number = 0
+ PIN_protection = False
+ period = 30
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ code_device = str(C.NK_get_totp_code(slot_number, 0, 0, period))
+ code_device = '0'+code_device if len(code_device) < 6 else code_device
+
+ oath = pytest.importorskip("oath")
+ lib_at = lambda : oath.totp(secret, period=period)
+ print (lib_at())
+ assert lib_at() == code_device
diff --git a/unittest/test_storage.py b/unittest/test_storage.py
index 01276ce..a1c59aa 100644
--- a/unittest/test_storage.py
+++ b/unittest/test_storage.py
@@ -1,9 +1,10 @@
-import pytest
+import pprint
-from misc import ffi, gs, wait, cast_pointer_to_tuple
-from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, LibraryErrors
+import pytest
-import pprint
+from conftest import skip_if_device_version_lower_than
+from constants import DefaultPasswords, DeviceErrorCode
+from misc import gs, wait
pprint = pprint.PrettyPrinter(indent=4).pprint
@@ -22,6 +23,7 @@ def get_dict_from_dissect(status):
def test_get_status_storage(C):
+ skip_if_device_version_lower_than({'S': 43})
status_pointer = C.NK_get_status_storage_as_string()
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
status_string = gs(status_pointer)
@@ -32,6 +34,7 @@ def test_get_status_storage(C):
def test_sd_card_usage(C):
+ skip_if_device_version_lower_than({'S': 43})
data_pointer = C.NK_get_SD_usage_data_as_string()
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
data_string = gs(data_pointer)
@@ -41,11 +44,13 @@ def test_sd_card_usage(C):
def test_encrypted_volume_unlock(C):
+ skip_if_device_version_lower_than({'S': 43})
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
def test_encrypted_volume_unlock_hidden(C):
+ skip_if_device_version_lower_than({'S': 43})
hidden_volume_password = 'hiddenpassword'
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_unlock_encrypted_volume(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
@@ -54,6 +59,7 @@ def test_encrypted_volume_unlock_hidden(C):
@pytest.mark.skip(reason='hangs device, to report')
def test_encrypted_volume_setup_multiple_hidden(C):
+ skip_if_device_version_lower_than({'S': 43})
hidden_volume_password = 'hiddenpassword'
p = lambda i: hidden_volume_password + str(i)
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
@@ -67,25 +73,30 @@ def test_encrypted_volume_setup_multiple_hidden(C):
def test_unencrypted_volume_set_read_only(C):
+ skip_if_device_version_lower_than({'S': 43})
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_set_unencrypted_read_only(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
def test_unencrypted_volume_set_read_write(C):
+ skip_if_device_version_lower_than({'S': 43})
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_set_unencrypted_read_write(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
def test_export_firmware(C):
+ skip_if_device_version_lower_than({'S': 43})
assert C.NK_export_firmware(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
def test_clear_new_sd_card_notification(C):
+ skip_if_device_version_lower_than({'S': 43})
assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
@pytest.mark.skip
def test_fill_SD_card(C):
+ skip_if_device_version_lower_than({'S': 43})
status = C.NK_fill_SD_card_with_random_data(DefaultPasswords.ADMIN)
assert status == DeviceErrorCode.STATUS_OK or status == DeviceErrorCode.BUSY
while 1:
@@ -97,12 +108,14 @@ def test_fill_SD_card(C):
def test_get_busy_progress_on_idle(C):
+ skip_if_device_version_lower_than({'S': 43})
value = C.NK_get_progress_bar_value()
assert value == -1
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
def test_change_update_password(C):
+ skip_if_device_version_lower_than({'S': 43})
wrong_password = 'aaaaaaaaaaa'
assert C.NK_change_update_password(wrong_password, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_change_update_password(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK
@@ -110,5 +123,6 @@ def test_change_update_password(C):
def test_send_startup(C):
+ skip_if_device_version_lower_than({'S': 43})
time_seconds_from_epoch = 0 # FIXME set proper date
assert C.NK_send_startup(time_seconds_from_epoch) == DeviceErrorCode.STATUS_OK