summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.idea/dictionaries/sz.xml1
-rw-r--r--CMakeLists.txt10
-rw-r--r--NK_C_API.cc2
-rw-r--r--NK_C_API.h2
-rw-r--r--NitrokeyManager.cc93
-rw-r--r--device.cc18
-rw-r--r--include/CommandFailedException.h5
-rw-r--r--include/LibraryException.h6
-rw-r--r--include/NitrokeyManager.h6
-rw-r--r--include/command_id.h42
-rw-r--r--include/device.h6
-rw-r--r--include/device_proto.h480
-rw-r--r--include/dissect.h10
-rw-r--r--include/stick10_commands.h14
-rw-r--r--include/stick20_commands.h50
-rw-r--r--unittest/catch_main.cpp2
-rw-r--r--unittest/test_C_API.cpp34
-rw-r--r--unittest/test_bindings.py176
18 files changed, 668 insertions, 289 deletions
diff --git a/.idea/dictionaries/sz.xml b/.idea/dictionaries/sz.xml
index 59b56fb..c8c18a3 100644
--- a/.idea/dictionaries/sz.xml
+++ b/.idea/dictionaries/sz.xml
@@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="sz">
<words>
+ <w>loglevel</w>
<w>nitrokey</w>
<w>totp</w>
</words>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 39c0c34..eedfd35 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,13 @@
cmake_minimum_required(VERSION 3.5)
project(libnitrokey)
+set(CMAKE_CXX_COMPILER "/usr/bin/clang++-3.8" CACHE string "clang++ compiler" FORCE)
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wno-gnu-variable-sized-type-not-at-end -g3" )
+SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lhidapi-libusb" )
+
+include_directories(include unittest/Catch/include)
set(SOURCE_FILES
include/command.h
@@ -22,6 +28,8 @@ set(SOURCE_FILES
log.cc
misc.cc
NitrokeyManager.cc
- NK_C_API.cc include/CommandFailedException.h include/LibraryException.h)
+ NK_C_API.cc include/CommandFailedException.h include/LibraryException.h
+ unittest/test_C_API.cpp
+ unittest/catch_main.cpp)
add_executable(libnitrokey ${SOURCE_FILES}) \ No newline at end of file
diff --git a/NK_C_API.cc b/NK_C_API.cc
index 6265215..7110fca 100644
--- a/NK_C_API.cc
+++ b/NK_C_API.cc
@@ -223,7 +223,7 @@ 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,
+extern int NK_write_hotp_slot(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 m = NitrokeyManager::instance();
diff --git a/NK_C_API.h b/NK_C_API.h
index 3c851a5..728824d 100644
--- a/NK_C_API.h
+++ b/NK_C_API.h
@@ -167,7 +167,7 @@ extern int NK_erase_totp_slot(uint8_t slot_number, const char *temporary_passwor
* @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,
+extern int NK_write_hotp_slot(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);
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
index d827292..7f3bfd0 100644
--- a/NitrokeyManager.cc
+++ b/NitrokeyManager.cc
@@ -8,10 +8,15 @@ namespace nitrokey{
template <typename T>
void strcpyT(T& dest, const char* src){
- if (src == nullptr)
+
+ if (src == nullptr)
// throw EmptySourceStringException(slot_number);
return;
const size_t s_dest = sizeof dest;
+ nitrokey::log::Log::instance()(std::string("strcpyT sizes dest src ")
+ +std::to_string(s_dest)+ " "
+ +std::to_string(strlen(src))+ " "
+ ,nitrokey::log::Loglevel::DEBUG);
if (strlen(src) > s_dest){
throw TooLongStringException(strlen(src), s_dest, src);
}
@@ -45,7 +50,7 @@ namespace nitrokey{
}
bool NitrokeyManager::connect() {
- device = nullptr;
+ this->disconnect();
vector< shared_ptr<Device> > devices = { make_shared<Stick10>(), make_shared<Stick20>() };
for( auto & d : devices ){
if (d->connect()){
@@ -57,7 +62,8 @@ namespace nitrokey{
bool NitrokeyManager::connect(const char *device_model) {
- switch (device_model[0]){
+ this->disconnect();
+ switch (device_model[0]){
case 'P':
device = make_shared<Stick10>();
break;
@@ -78,7 +84,12 @@ namespace nitrokey{
}
bool NitrokeyManager::disconnect() {
- return device->disconnect();
+ if (device == nullptr){
+ return false;
+ }
+ const auto res = device->disconnect();
+ device = nullptr;
+ return res;
}
void NitrokeyManager::set_debug(bool state) {
@@ -168,9 +179,9 @@ namespace nitrokey{
std::copy(vec.begin(), vec.end(), dest);
}
- 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) {
+ bool NitrokeyManager::write_HOTP_slot(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) {
if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_hotp(slot_number);
@@ -180,7 +191,22 @@ namespace nitrokey{
vector_copy(payload.slot_secret, secret_bin);
strcpyT(payload.slot_name, slot_name);
strcpyT(payload.slot_token_id, token_ID);
- payload.slot_counter = hotp_counter;
+ 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);
+ strcpyT(payload.slot_counter, 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);
+ break;
+ }
payload.use_8_digits = use_8_digits;
payload.use_enter = use_enter;
payload.use_tokenID = use_tokenID;
@@ -280,11 +306,10 @@ namespace nitrokey{
auto p = get_payload<ChangeAdminUserPin20Current>();
strcpyT(p.old_pin, current_PIN);
p.set_kind(StoKind);
- ChangeAdminUserPin20Current::CommandTransaction::run(*device, p);
-
auto p2 = get_payload<ChangeAdminUserPin20New>();
strcpyT(p2.new_pin, new_PIN);
p2.set_kind(StoKind);
+ ChangeAdminUserPin20Current::CommandTransaction::run(*device, p);
ChangeAdminUserPin20New::CommandTransaction::run(*device, p2);
}
break;
@@ -312,10 +337,16 @@ namespace nitrokey{
}
uint8_t NitrokeyManager::get_user_retry_count() {
+ if(device->get_device_model() == DeviceModel::STORAGE){
+ stick20::GetDeviceStatus::CommandTransaction::run(*device);
+ }
auto response = GetUserPasswordRetryCount::CommandTransaction::run(*device);
return response.data().password_retry_count;
}
uint8_t NitrokeyManager::get_admin_retry_count() {
+ if(device->get_device_model() == DeviceModel::STORAGE){
+ stick20::GetDeviceStatus::CommandTransaction::run(*device);
+ }
auto response = GetPasswordRetryCount::CommandTransaction::run(*device);
return response.data().password_retry_count;
}
@@ -380,9 +411,21 @@ namespace nitrokey{
}
void NitrokeyManager::build_aes_key(const char *admin_password) {
- auto p = get_payload<BuildAESKey>();
- strcpyT(p.admin_password, admin_password);
- BuildAESKey::CommandTransaction::run(*device, p);
+ switch (device->get_device_model()) {
+ case DeviceModel::PRO: {
+ auto p = get_payload<BuildAESKey>();
+ strcpyT(p.admin_password, admin_password);
+ BuildAESKey::CommandTransaction::run(*device, p);
+ break;
+ }
+ case DeviceModel::STORAGE : {
+ auto p = get_payload<stick20::CreateNewKeys>();
+ strcpyT(p.admin_password, admin_password);
+ p.setKindPrefixed();
+ stick20::CreateNewKeys::CommandTransaction::run(*device, p);
+ break;
+ }
+ }
}
void NitrokeyManager::factory_reset(const char *admin_password) {
@@ -392,10 +435,26 @@ namespace nitrokey{
}
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);
+ switch (device->get_device_model()){
+ case DeviceModel::PRO: {
+ auto p = get_payload<stick10::UnlockUserPassword>();
+ strcpyT(p.admin_password, admin_password);
+ strcpyT(p.user_new_password, new_user_password);
+ stick10::UnlockUserPassword::CommandTransaction::run(*device, p);
+ break;
+ }
+ case DeviceModel::STORAGE : {
+ auto p2 = get_payload<ChangeAdminUserPin20Current>();
+ p2.set_kind(PasswordKind::Admin);
+ strcpyT(p2.old_pin, admin_password);
+ ChangeAdminUserPin20Current::CommandTransaction::run(*device, p2);
+ auto p3 = get_payload<stick20::UnlockUserPassword>();
+ p3.set_kind(PasswordKind::Admin);
+ strcpyT(p3.user_new_password, new_user_password);
+ stick20::UnlockUserPassword::CommandTransaction::run(*device, p3);
+ break;
+ }
+ }
}
diff --git a/device.cc b/device.cc
index 6660276..8b40984 100644
--- a/device.cc
+++ b/device.cc
@@ -13,7 +13,8 @@ using namespace nitrokey::log;
Device::Device()
: m_vid(0),
m_pid(0),
- m_retry_count(40),
+ m_retry_sending_count(3),
+ m_retry_receiving_count(40),
m_retry_timeout(100),
mp_devhandle(NULL),
last_command_status(0){}
@@ -21,14 +22,16 @@ Device::Device()
bool Device::disconnect() {
Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2);
- hid_exit();
+ if(mp_devhandle== nullptr) return false;
+ hid_close(mp_devhandle);
mp_devhandle = NULL;
+ hid_exit();
return true;
}
bool Device::connect() {
Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2);
- // hid_init();
+// hid_init();
mp_devhandle = hid_open(m_vid, m_pid, NULL);
// hid_init();
return mp_devhandle != NULL;
@@ -67,7 +70,7 @@ int Device::recv(void *packet) {
Loglevel::DEBUG_L2);
if (status > 0) break; // success
- if (retry_count++ >= m_retry_count) {
+ if (retry_count++ >= m_retry_receiving_count) {
Log::instance()(
"Maximum retry count reached" + std::to_string(retry_count),
Loglevel::WARNING);
@@ -86,13 +89,14 @@ Stick10::Stick10() {
m_pid = 0x4108;
m_model = DeviceModel::PRO;
m_send_receive_delay = 100ms;
- m_retry_count = 100;
+ m_retry_receiving_count = 100;
}
Stick20::Stick20() {
m_vid = 0x20a0;
m_pid = 0x4109;
- m_retry_timeout = std::chrono::milliseconds(500);
+ m_retry_timeout = 20ms;
m_model = DeviceModel::STORAGE;
- m_send_receive_delay = 1000ms;
+ m_send_receive_delay = 20ms;
+ m_retry_receiving_count = 40;
}
diff --git a/include/CommandFailedException.h b/include/CommandFailedException.h
index 3306f7b..9b0c59e 100644
--- a/include/CommandFailedException.h
+++ b/include/CommandFailedException.h
@@ -7,6 +7,7 @@
#include <exception>
#include <cstdint>
+#include <log.h>
class CommandFailedException : public std::exception {
public:
@@ -15,7 +16,9 @@ public:
CommandFailedException(uint8_t last_command_code, uint8_t last_command_status) :
last_command_code(last_command_code),
- last_command_status(last_command_status){}
+ last_command_status(last_command_status){
+ nitrokey::log::Log::instance()(std::string("CommandFailedException, status: ")+ std::to_string(last_command_status), nitrokey::log::Loglevel::DEBUG);
+ }
virtual const char *what() const throw() {
return "Command execution has failed on device";
diff --git a/include/LibraryException.h b/include/LibraryException.h
index 72891fb..3c3fab4 100644
--- a/include/LibraryException.h
+++ b/include/LibraryException.h
@@ -4,6 +4,7 @@
#include <exception>
#include <cstdint>
#include <string>
+#include "log.h"
class LibraryException: std::exception {
public:
@@ -83,7 +84,10 @@ public:
std::string message;
TooLongStringException(size_t size_source, size_t size_destination, const std::string &message = "") : size_source(
- size_source), size_destination(size_destination), message(message) {}
+ size_source), size_destination(size_destination), message(message) {
+ nitrokey::log::Log::instance()(std::string("TooLongStringException, size diff: ")+ std::to_string(size_source-size_destination), nitrokey::log::Loglevel::DEBUG);
+
+ }
virtual const char *what() const throw() override {
//TODO add sizes and message data to final message
diff --git a/include/NitrokeyManager.h b/include/NitrokeyManager.h
index 1e518f4..52c18d7 100644
--- a/include/NitrokeyManager.h
+++ b/include/NitrokeyManager.h
@@ -22,9 +22,9 @@ namespace nitrokey {
static shared_ptr <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, uint8_t hotp_counter,
- bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
- 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, 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);
diff --git a/include/command_id.h b/include/command_id.h
index 87d270e..8148cc1 100644
--- a/include/command_id.h
+++ b/include/command_id.h
@@ -4,8 +4,42 @@
namespace nitrokey {
namespace proto {
-
-#define STICK20_CMD_START_VALUE 0x20
+ namespace stick20 {
+ enum class device_status : uint8_t {
+ idle = 0,
+ ok,
+ busy,
+ wrong_password,
+ busy_progressbar,
+ password_matrix_ready,
+ no_user_password_unlock,
+ smartcard_error,
+ security_bit_active
+ };
+ const int CMD_START_VALUE = 0x20;
+ const int CMD_END_VALUE = 0x60;
+ }
+ namespace stick10 {
+ enum class command_status : uint8_t {
+ ok = 0,
+ wrong_CRC,
+ wrong_slot,
+ slot_not_programmed,
+ wrong_password = 4,
+ not_authorized,
+ timestamp_warning,
+ no_name_error,
+ not_supported,
+ unknown_command,
+ AES_dec_failed
+ };
+ enum class device_status : uint8_t {
+ ok = 0,
+ busy = 1,
+ error,
+ received_report,
+ };
+ }
enum class CommandID : uint8_t {
@@ -32,8 +66,8 @@ enum class CommandID : uint8_t {
CHANGE_USER_PIN = 0x14,
CHANGE_ADMIN_PIN = 0x15,
- STICK20_CMD_SEND_PASSWORD = STICK20_CMD_START_VALUE + 18,
- STICK20_CMD_SEND_NEW_PASSWORD = STICK20_CMD_START_VALUE + 19,
+ STICK20_CMD_SEND_PASSWORD = stick20::CMD_START_VALUE + 18,
+ STICK20_CMD_SEND_NEW_PASSWORD = stick20::CMD_START_VALUE + 19,
ENABLE_CRYPTED_PARI = 0x20,
DISABLE_CRYPTED_PARI,
diff --git a/include/device.h b/include/device.h
index 67b739c..34b7a5b 100644
--- a/include/device.h
+++ b/include/device.h
@@ -38,7 +38,8 @@ public:
*/
virtual int recv(void *packet);
- int get_retry_count() const { return m_retry_count; };
+ int get_retry_receiving_count() const { return m_retry_receiving_count; };
+ int get_retry_sending_count() const { return m_retry_sending_count; };
std::chrono::milliseconds get_retry_timeout() const { return m_retry_timeout; };
std::chrono::milliseconds get_send_receive_delay() const {return m_send_receive_delay;}
@@ -59,7 +60,8 @@ private:
* library, there's no way of doing it asynchronously,
* hence polling.
*/
- int m_retry_count;
+ int m_retry_sending_count;
+ int m_retry_receiving_count;
std::chrono::milliseconds m_retry_timeout;
std::chrono::milliseconds m_send_receive_delay;
diff --git a/include/device_proto.h b/include/device_proto.h
index 4044cdc..45f165b 100644
--- a/include/device_proto.h
+++ b/include/device_proto.h
@@ -1,5 +1,6 @@
#ifndef DEVICE_PROTO_H
#define DEVICE_PROTO_H
+
#include <utility>
#include <thread>
#include <type_traits>
@@ -31,7 +32,7 @@
#define PWS_SEND_CR 3
namespace nitrokey {
-namespace proto {
+ namespace proto {
/*
* POD types for HID proto commands
* Instances are meant to be __packed.
@@ -42,45 +43,45 @@ namespace proto {
/*
* Every packet is a USB HID report (check USB spec)
*/
-template <CommandID cmd_id, typename Payload>
-struct HIDReport {
- uint8_t _zero;
- CommandID command_id; // uint8_t
- union {
- uint8_t _padding[HID_REPORT_SIZE - 6];
- Payload payload;
- } __packed;
- uint32_t crc;
-
- // POD types can't have non-default constructors
- // used in Transaction<>::run()
- void initialize() {
- bzero(this, sizeof *this);
- command_id = cmd_id;
- }
-
- uint32_t calculate_CRC() const {
- // w/o leading zero, a part of each HID packet
- // w/o 4-byte crc
- return misc::stm_crc32((const uint8_t *)(this) + 1,
- (size_t)(HID_REPORT_SIZE - 5));
- }
-
- void update_CRC() { crc = calculate_CRC(); }
-
- bool isCRCcorrect() const { return crc == calculate_CRC(); }
-
- bool isValid() const {
- return true;
- // return !_zero && payload.isValid() && isCRCcorrect();
- }
-
- operator std::string() const {
- // Packet type is known upfront in normal operation.
- // Can't be used to dissect random packets.
- return QueryDissector<cmd_id, decltype(*this)>::dissect(*this);
- }
-} __packed;
+ template<CommandID cmd_id, typename Payload>
+ struct HIDReport {
+ uint8_t _zero;
+ CommandID command_id; // uint8_t
+ union {
+ uint8_t _padding[HID_REPORT_SIZE - 6];
+ Payload payload;
+ } __packed;
+ uint32_t crc;
+
+ // POD types can't have non-default constructors
+ // used in Transaction<>::run()
+ void initialize() {
+ bzero(this, sizeof *this);
+ command_id = cmd_id;
+ }
+
+ uint32_t calculate_CRC() const {
+ // w/o leading zero, a part of each HID packet
+ // w/o 4-byte crc
+ return misc::stm_crc32((const uint8_t *) (this) + 1,
+ (size_t) (HID_REPORT_SIZE - 5));
+ }
+
+ void update_CRC() { crc = calculate_CRC(); }
+
+ bool isCRCcorrect() const { return crc == calculate_CRC(); }
+
+ bool isValid() const {
+ return true;
+ // return !_zero && payload.isValid() && isCRCcorrect();
+ }
+
+ operator std::string() const {
+ // Packet type is known upfront in normal operation.
+ // Can't be used to dissect random packets.
+ return QueryDissector<cmd_id, decltype(*this)>::dissect(*this);
+ }
+ } __packed;
/*
* Response payload (the parametrized type inside struct HIDReport)
@@ -88,175 +89,238 @@ struct HIDReport {
* command_id member in incoming HIDReport structure carries the command
* type last used.
*/
-template <CommandID cmd_id, typename ResponsePayload>
-struct DeviceResponse {
- uint8_t _zero;
- uint8_t device_status;
- uint8_t command_id; // originally last_command_type
- uint32_t last_command_crc;
- uint8_t last_command_status;
- union {
- uint8_t _padding[HID_REPORT_SIZE - 12];
- ResponsePayload payload;
- } __packed;
- uint32_t crc;
-
- void initialize() { bzero(this, sizeof *this); }
-
- uint32_t calculate_CRC() const {
- // w/o leading zero, a part of each HID packet
- // w/o 4-byte crc
- return misc::stm_crc32((const uint8_t *)(this) + 1,
- (size_t)(HID_REPORT_SIZE - 5));
- }
-
- void update_CRC() { crc = calculate_CRC(); }
-
- bool isCRCcorrect() const { return crc == calculate_CRC(); }
-
- bool isValid() const {
- // return !_zero && payload.isValid() && isCRCcorrect() &&
- // command_id == (uint8_t)(cmd_id);
- return true;
- }
-
- operator std::string() const {
- return ResponseDissector<cmd_id, decltype(*this)>::dissect(*this);
- }
-} __packed;
-
-struct EmptyPayload {
- uint8_t _data[];
-
- bool isValid() const { return true; }
-
- std::string dissect() const { return std::string("Empty Payload."); }
-} __packed;
-
-template <typename command_packet, typename response_payload>
-class ClearingProxy{
-public:
- ClearingProxy(command_packet &p){
- packet = p;
- bzero(&p, sizeof(p));
- }
- ~ClearingProxy(){
- bzero(&packet, sizeof(packet));
- }
-
- response_payload & data() {
- return packet.payload;
- }
-
- command_packet packet;
-};
-
-template <CommandID cmd_id, typename command_payload, typename response_payload>
-class Transaction : semantics::non_constructible {
- public:
- // Types declared in command class scope can't be reached from there.
- typedef command_payload CommandPayload;
- typedef response_payload ResponsePayload;
-
- typedef struct HIDReport<cmd_id, CommandPayload> OutgoingPacket;
- typedef struct DeviceResponse<cmd_id, ResponsePayload> ResponsePacket;
-
- static_assert(std::is_pod<OutgoingPacket>::value,
- "outgoingpacket must be a pod type");
- static_assert(std::is_pod<ResponsePacket>::value,
- "ResponsePacket must be a POD type");
- static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE,
- "OutgoingPacket type is not the right size");
- static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE,
- "ResponsePacket type is not the right size");
-
- static uint32_t getCRC(
- const command_payload &payload) {
- OutgoingPacket outp;
- outp.initialize();
- outp.payload = payload;
- outp.update_CRC();
- return outp.crc;
- }
-
- template <typename T>
- static void clear_packet(T &st){
- bzero(&st, sizeof(st));
- }
-
-
- static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev,
- const command_payload &payload) {
- using namespace ::nitrokey::device;
- using namespace ::nitrokey::log;
- using namespace std::chrono_literals;
-
- Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2);
-
- int status;
- OutgoingPacket outp;
- ResponsePacket resp;
-
- // POD types can't have non-default constructors
- outp.initialize();
- resp.initialize();
-
- outp.payload = payload;
- outp.update_CRC();
-
- Log::instance()("Outgoing HID packet:", Loglevel::DEBUG);
- Log::instance()((std::string)(outp), Loglevel::DEBUG);
-
- if (!outp.isValid()) throw std::runtime_error("Invalid outgoing packet");
-
- status = dev.send(&outp);
- if (status <= 0)
- throw std::runtime_error(
- std::string("Device error while sending command ") +
- std::to_string((int)(status)));
-
- std::this_thread::sleep_for(dev.get_send_receive_delay());
-
- // FIXME make checks done in device:recv here
- int retry = dev.get_retry_count();
- while (retry-- > 0) {
- status = dev.recv(&resp);
-
- dev.set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv
-
- if (resp.device_status == 0 && resp.last_command_crc == outp.crc) break;
- Log::instance()("Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...",
+ template<CommandID cmd_id, typename ResponsePayload>
+ struct DeviceResponse {
+ uint8_t _zero;
+ uint8_t device_status;
+ uint8_t command_id; // originally last_command_type
+ uint32_t last_command_crc;
+ uint8_t last_command_status;
+ union {
+ uint8_t _padding[HID_REPORT_SIZE - 12];
+ ResponsePayload payload;
+ struct {
+ uint8_t _storage_status_padding[20 - 8 + 1]; //starts on 20th byte minus already 8 used + zero byte
+ uint8_t command_counter;
+ uint8_t command_id;
+ uint8_t device_status; //@see stick20::device_status
+ uint8_t progress_bar_value;
+ } storage_status __packed;
+ } __packed;
+ uint32_t crc;
+
+ void initialize() { bzero(this, sizeof *this); }
+
+ uint32_t calculate_CRC() const {
+ // w/o leading zero, a part of each HID packet
+ // w/o 4-byte crc
+ return misc::stm_crc32((const uint8_t *) (this) + 1,
+ (size_t) (HID_REPORT_SIZE - 5));
+ }
+
+ void update_CRC() { crc = calculate_CRC(); }
+
+ bool isCRCcorrect() const { return crc == calculate_CRC(); }
+
+ bool isValid() const {
+ // return !_zero && payload.isValid() && isCRCcorrect() &&
+ // command_id == (uint8_t)(cmd_id);
+ return crc != 0;
+ }
+
+ operator std::string() const {
+ return ResponseDissector<cmd_id, decltype(*this)>::dissect(*this);
+ }
+ } __packed;
+
+ struct EmptyPayload {
+ uint8_t _data[];
+
+ bool isValid() const { return true; }
+
+ std::string dissect() const { return std::string("Empty Payload."); }
+ } __packed;
+
+ template<typename command_packet, typename response_payload>
+ class ClearingProxy {
+ public:
+ ClearingProxy(command_packet &p) {
+ packet = p;
+ bzero(&p, sizeof(p));
+ }
+
+ ~ClearingProxy() {
+ bzero(&packet, sizeof(packet));
+ }
+
+ response_payload &data() {
+ return packet.payload;
+ }
+
+ command_packet packet;
+ };
+
+ template<CommandID cmd_id, typename command_payload, typename response_payload>
+ class Transaction : semantics::non_constructible {
+ public:
+ // Types declared in command class scope can't be reached from there.
+ typedef command_payload CommandPayload;
+ typedef response_payload ResponsePayload;
+
+ typedef struct HIDReport<cmd_id, CommandPayload> OutgoingPacket;
+ typedef struct DeviceResponse<cmd_id, ResponsePayload> ResponsePacket;
+
+ static_assert(std::is_pod<OutgoingPacket>::value,
+ "outgoingpacket must be a pod type");
+ static_assert(std::is_pod<ResponsePacket>::value,
+ "ResponsePacket must be a POD type");
+ static_assert(sizeof(OutgoingPacket) == HID_REPORT_SIZE,
+ "OutgoingPacket type is not the right size");
+ static_assert(sizeof(ResponsePacket) == HID_REPORT_SIZE,
+ "ResponsePacket type is not the right size");
+
+ static uint32_t getCRC(
+ const command_payload &payload) {
+ OutgoingPacket outp;
+ outp.initialize();
+ outp.payload = payload;
+ outp.update_CRC();
+ return outp.crc;
+ }
+
+ template<typename T>
+ static void clear_packet(T &st) {
+ bzero(&st, sizeof(st));
+ }
+
+
+ static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev,
+ const command_payload &payload) {
+ using namespace ::nitrokey::device;
+ using namespace ::nitrokey::log;
+ using namespace std::chrono_literals;
+
+ Log::instance()(__PRETTY_FUNCTION__, Loglevel::DEBUG_L2);
+
+ int status;
+ OutgoingPacket outp;
+ ResponsePacket resp;
+
+ // POD types can't have non-default constructors
+ outp.initialize();
+ resp.initialize();
+
+ outp.payload = payload;
+ outp.update_CRC();
+
+ Log::instance()("Outgoing HID packet:", Loglevel::DEBUG);
+ Log::instance()(static_cast<std::string>(outp), Loglevel::DEBUG);
+
+ if (!outp.isValid()) throw std::runtime_error("Invalid outgoing packet");
+
+ int receiving_retry_counter = 0;
+ int sending_retry_counter = dev.get_retry_sending_count();
+ while (sending_retry_counter-- > 0) {
+ status = dev.send(&outp);
+ if (status <= 0)
+ throw std::runtime_error(
+ std::string("Device error while sending command ") +
+ std::to_string(status));
+
+ std::this_thread::sleep_for(dev.get_send_receive_delay());
+
+ // FIXME make checks done in device:recv here
+ receiving_retry_counter = dev.get_retry_receiving_count();
+ while (receiving_retry_counter-- > 0) {
+ status = dev.recv(&resp);
+
+ if (dev.get_device_model() == DeviceModel::STORAGE &&
+ resp.command_id >= stick20::CMD_START_VALUE &&
+ resp.command_id < stick20::CMD_END_VALUE ) {
+ Log::instance()(std::string("Detected storage device cmd, status: ") +
+ std::to_string(resp.storage_status.device_status), Loglevel::DEBUG_L2);
+
+ resp.last_command_status = static_cast<uint8_t>(stick10::command_status::ok);
+ switch (static_cast<stick20::device_status>(resp.storage_status.device_status)) {
+ case stick20::device_status::idle :
+ case stick20::device_status::ok:
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::ok);
+ break;
+ case stick20::device_status::busy:
+ case stick20::device_status::busy_progressbar: //TODO this will be modified later for getting progressbar status
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::busy);
+ break;
+ case stick20::device_status::wrong_password:
+ resp.last_command_status = static_cast<uint8_t>(stick10::command_status::wrong_password);
+ resp.device_status = static_cast<uint8_t>(stick10::device_status::ok);
+ break;
+ default:
+ Log::instance()(std::string("Unknown storage device status, cannot translate: ") +
+ std::to_string(resp.storage_status.device_status), Loglevel::DEBUG);
+ resp.device_status = resp.storage_status.device_status;
+ break;
+ };
+ }
+
+ //SENDPASSWORD gives wrong CRC , for now rely on !=0 (TODO report)
+// if (resp.device_status == 0 && resp.last_command_crc == outp.crc && resp.isCRCcorrect()) break;
+ if (resp.device_status == static_cast<uint8_t>(stick10::device_status::ok) &&
+ resp.last_command_crc == outp.crc && resp.isValid()) break;
+ if (resp.device_status == static_cast<uint8_t>(stick10::device_status::busy)) {
+ receiving_retry_counter++;
+ Log::instance()("Status busy, not decresing receiving_retry_counter counter: " +
+ std::to_string(receiving_retry_counter), Loglevel::DEBUG_L2);
+ }
+ Log::instance()(std::string("Retry status - dev status, equal crc, correct CRC: ")
+ + std::to_string(resp.device_status) + " " +
+ std::to_string(resp.last_command_crc == outp.crc) +
+ " " + std::to_string(resp.isCRCcorrect()), Loglevel::DEBUG_L2);
+
+ Log::instance()(
+ "Device is not ready or received packet's last CRC is not equal to sent CRC packet, retrying...",
Loglevel::DEBUG);
- Log::instance()("Invalid incoming HID packet:", Loglevel::DEBUG_L2);
- Log::instance()((std::string)(resp), Loglevel::DEBUG_L2);
- std::this_thread::sleep_for(dev.get_retry_timeout());
- continue;
+ Log::instance()("Invalid incoming HID packet:", Loglevel::DEBUG_L2);
+ Log::instance()(static_cast<std::string>(resp), Loglevel::DEBUG_L2);
+ std::this_thread::sleep_for(dev.get_retry_timeout());
+ continue;
+ }
+ if (resp.device_status == 0 && resp.last_command_crc == outp.crc) break;
+ Log::instance()(std::string("Resending (outer loop) "), Loglevel::DEBUG_L2);
+ Log::instance()(std::string("sending_retry_counter count: ") + std::to_string(sending_retry_counter),
+ Loglevel::DEBUG);
+ }
+
+ dev.set_last_command_status(resp.last_command_status); // FIXME should be handled on device.recv
+
+ clear_packet(outp);
+
+ if (status <= 0)
+ throw std::runtime_error(
+ std::string("Device error while executing command ") +
+ std::to_string(status));
+
+ Log::instance()("Incoming HID packet:", Loglevel::DEBUG);
+ Log::instance()(static_cast<std::string>(resp), Loglevel::DEBUG);
+ Log::instance()(std::string("receiving_retry_counter count: ") + std::to_string(receiving_retry_counter),
+ Loglevel::DEBUG);
+
+ if (!resp.isValid()) throw std::runtime_error("Invalid incoming packet");
+ if (receiving_retry_counter <= 0)
+ throw std::runtime_error(
+ "Maximum receiving_retry_counter count reached for receiving response from the device!");
+ if (resp.last_command_status != static_cast<uint8_t>(stick10::command_status::ok))
+ throw CommandFailedException(resp.command_id, resp.last_command_status);
+
+
+ // See: DeviceResponse
+ return resp;
+ }
+
+ static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev) {
+ command_payload empty_payload;
+ return run(dev, empty_payload);
+ }
+ };
}
- clear_packet(outp);
-
- if (status <= 0)
- throw std::runtime_error(
- std::string("Device error while executing command ") +
- std::to_string(status));
-
- 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
- return resp;
- }
-
- static ClearingProxy<ResponsePacket, response_payload> run(device::Device &dev) {
- command_payload empty_payload;
- return run(dev, empty_payload);
- }
-};
-}
}
#endif
diff --git a/include/dissect.h b/include/dissect.h
index c83e648..59e6e9c 100644
--- a/include/dissect.h
+++ b/include/dissect.h
@@ -67,7 +67,7 @@ class ResponseDissector : semantics::non_constructible {
out << "Device status:\t" << pod.device_status + 0 << " "
<< status[pod.device_status] << std::endl;
- out << "Command ID:\t" << commandid_to_string((CommandID)(pod.command_id))
+ 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')
@@ -77,6 +77,14 @@ class ResponseDissector : semantics::non_constructible {
out << "CRC:\t"
<< std::hex << std::setw(2) << std::setfill('0')
<< pod.crc << std::endl;
+ out << "Storage stick status:" << std::endl;
+#define d(x) out << " "#x": \t"<< std::hex << std::setw(2) \
+ << std::setfill('0')<< static_cast<int>(x) << std::endl;
+ d(pod.storage_status.command_counter);
+ d(pod.storage_status.command_id);
+ d(pod.storage_status.device_status);
+ d(pod.storage_status.progress_bar_value);
+#undef d
out << "Payload:" << std::endl;
out << pod.payload.dissect();
diff --git a/include/stick10_commands.h b/include/stick10_commands.h
index a60be59..a947e1e 100644
--- a/include/stick10_commands.h
+++ b/include/stick10_commands.h
@@ -111,7 +111,8 @@ class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
std::stringstream ss;
ss << "slot_number:\t" << (int)(slot_number) << std::endl;
ss << "slot_name:\t" << slot_name << std::endl;
- ss << "slot_secret:\t" << slot_secret << std::endl;
+ ss << "slot_secret:" << std::endl
+ << ::nitrokey::misc::hexdump((const char *)(&slot_secret), sizeof slot_secret);
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;
@@ -121,8 +122,10 @@ class WriteToHOTPSlot : Command<CommandID::WRITE_TO_SLOT> {
for (auto i : slot_token_id)
ss << std::hex << std::setw(2) << std::setfill('0')<< (int) i << " " ;
ss << std::endl;
- ss << "slot_counter:\t" << (int)slot_counter << std::endl;
- return ss.str();
+ ss << "slot_counter:\t[" << (int)slot_counter << "]\t"
+ << ::nitrokey::misc::hexdump((const char *)(&slot_counter), sizeof slot_counter, false);
+
+ return ss.str();
}
} __packed;
@@ -331,7 +334,10 @@ class GetStatus : Command<CommandID::GET_STATUS> {
std::string dissect() const {
std::stringstream ss;
- ss << "firmware_version:\t" << firmware_version << std::endl;
+ ss << "firmware_version:\t"
+ << "[" << firmware_version << "]" << "\t"
+ << ::nitrokey::misc::hexdump(
+ (const char *)(&firmware_version), sizeof firmware_version, false);
ss << "card_serial:\t"
<< ::nitrokey::misc::hexdump((const char *)(card_serial),
sizeof card_serial, false);
diff --git a/include/stick20_commands.h b/include/stick20_commands.h
index c684e95..f4e7500 100644
--- a/include/stick20_commands.h
+++ b/include/stick20_commands.h
@@ -62,6 +62,26 @@ namespace stick20 {
CommandTransaction;
};
+
+ class UnlockUserPassword : Command<CommandID::UNLOCK_USER_PASSWORD> {
+ public:
+ struct CommandPayload {
+ uint8_t kind;
+ uint8_t user_new_password[20];
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " user_new_password:\t" << user_new_password<< std::endl;
+ return ss.str();
+ }
+ void set_kind(PasswordKind k){
+ kind = (uint8_t)k;
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
+
class EnableEncryptedPartition : semantics::non_constructible {
public:
struct CommandPayload {
@@ -126,15 +146,25 @@ class ExportFirmware : semantics::non_constructible {
struct EmptyPayload> CommandTransaction;
};
-class CreateNewKeys : semantics::non_constructible {
- public:
- struct CommandPayload {
- uint8_t password[30];
- };
+ class CreateNewKeys : Command<CommandID::GENERATE_NEW_KEYS> {
+ public:
+ struct CommandPayload {
+ uint8_t kind;
+ uint8_t admin_password[30]; //CS20_MAX_PASSWORD_LEN
+ std::string dissect() const {
+ std::stringstream ss;
+ ss << " admin_password:\t" << admin_password<< std::endl;
+ return ss.str();
+ }
+ void setKindPrefixed(){
+ kind = 'P';
+ }
+ } __packed;
+
+ typedef Transaction<command_id(), struct CommandPayload, struct EmptyPayload>
+ CommandTransaction;
+ };
- typedef Transaction<CommandID::GENERATE_NEW_KEYS, struct CommandPayload,
- struct EmptyPayload> CommandTransaction;
-};
class FillSDCardWithRandomChars : semantics::non_constructible {
public:
@@ -182,7 +212,7 @@ class SendPasswordMatrixSetup : semantics::non_constructible {
struct EmptyPayload> CommandTransaction;
};
-#define d(x) ss << #x":\t" << x << std::endl;
+#define d(x) ss << " "#x":\t" << (int)x << std::endl;
class GetDeviceStatus : Command<CommandID::GET_DEVICE_STATUS> {
public:
@@ -190,7 +220,7 @@ class SendPasswordMatrixSetup : semantics::non_constructible {
static const int payload_absolute_begin = 8;
static const int padding_size = OUTPUT_CMD_RESULT_STICK20_STATUS_START - payload_absolute_begin;
struct ResponsePayload {
- uint8_t _padding[padding_size];
+ uint8_t _padding[padding_size]; //TODO confirm padding in Storage firmware
//data starts from 21st byte of packet -> 13th byte of payload
uint8_t command_counter;
uint8_t last_command;
diff --git a/unittest/catch_main.cpp b/unittest/catch_main.cpp
new file mode 100644
index 0000000..c8270db
--- /dev/null
+++ b/unittest/catch_main.cpp
@@ -0,0 +1,2 @@
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main()
+#include "catch.hpp" \ No newline at end of file
diff --git a/unittest/test_C_API.cpp b/unittest/test_C_API.cpp
new file mode 100644
index 0000000..37d3c7f
--- /dev/null
+++ b/unittest/test_C_API.cpp
@@ -0,0 +1,34 @@
+static const int TOO_LONG_STRING = 200;
+
+#include "catch.hpp"
+
+#include <iostream>
+#include <string>
+#include "log.h"
+#include "../NK_C_API.h"
+
+TEST_CASE("C API connect", "[BASIC]") {
+ auto login = NK_login_auto();
+ REQUIRE(login != 0);
+ NK_logout();
+ login = NK_login_auto();
+ REQUIRE(login != 0);
+ NK_logout();
+ login = NK_login_auto();
+ REQUIRE(login != 0);
+}
+
+TEST_CASE("Check retry count", "[BASIC]") {
+ REQUIRE(NK_get_admin_retry_count() == 3);
+ REQUIRE(NK_get_user_retry_count() == 3);
+}
+
+TEST_CASE("Check long strings", "[STANDARD]") {
+ char* longPin = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ char *pin = "123123123";
+ auto result = NK_change_user_PIN(longPin, pin);
+ REQUIRE(result == TOO_LONG_STRING);
+ result = NK_change_user_PIN(pin, longPin);
+ REQUIRE(result == TOO_LONG_STRING);
+ CAPTURE(result);
+} \ No newline at end of file
diff --git a/unittest/test_bindings.py b/unittest/test_bindings.py
index 9c266aa..f7ade46 100644
--- a/unittest/test_bindings.py
+++ b/unittest/test_bindings.py
@@ -10,6 +10,13 @@ def to_hex(s):
return "".join("{:02x}".format(ord(c)) for c in s)
+def wait(t):
+ import time
+ msg = 'Waiting for %d seconds' % t
+ print(msg.center(40, '='))
+ time.sleep(t)
+
+
RFC_SECRET_HR = '12345678901234567890'
RFC_SECRET = to_hex(RFC_SECRET_HR) # '12345678901234567890'
@@ -78,6 +85,26 @@ def C(request):
return C
+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]
+ return firmware
+
+
+def is_pro_rtm_07(C):
+ firmware = get_firmware_version_from_status(C)
+ return '07 00' in firmware
+
+
+def is_storage(C):
+ """
+ exact firmware storage is sent by other function
+ """
+ firmware = get_firmware_version_from_status(C)
+ return '01 00' in firmware
+
+
def test_enable_password_safe(C):
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD
@@ -134,9 +161,10 @@ def test_password_safe_slot_status(C):
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):
+ if is_pro_rtm_07(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
assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
@@ -148,7 +176,7 @@ def test_regenerate_aes_key(C):
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")
+@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):
"""
Sometimes fails on NK Pro - slot name is not cleared ergo key generation has not succeed despite the success result
@@ -182,6 +210,8 @@ def test_destroy_password_safe(C):
def test_is_AES_supported(C):
+ if is_storage(C):
+ pytest.skip("Storage does not implement this command")
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
@@ -237,6 +267,7 @@ def test_invalid_slot(C):
def test_admin_retry_counts(C):
default_admin_retry_count = 3
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_get_admin_retry_count() == default_admin_retry_count
assert C.NK_change_admin_PIN('wrong_password', DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_get_admin_retry_count() == default_admin_retry_count - 1
@@ -244,8 +275,20 @@ def test_admin_retry_counts(C):
assert C.NK_get_admin_retry_count() == default_admin_retry_count
-def test_user_retry_counts(C):
+def test_user_retry_counts_change_PIN(C):
+ assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ wrong_password = 'wrong_password'
+ default_user_retry_count = 3
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+ assert C.NK_change_user_PIN(wrong_password, wrong_password) == DeviceErrorCode.WRONG_PASSWORD
+ assert C.NK_get_user_retry_count() == default_user_retry_count - 1
+ assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+ assert C.NK_get_user_retry_count() == default_user_retry_count
+
+def test_user_retry_counts_PWSafe(C):
default_user_retry_count = 3
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_get_user_retry_count() == default_user_retry_count
assert C.NK_enable_password_safe('wrong_password') == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_get_user_retry_count() == default_user_retry_count - 1
@@ -333,17 +376,91 @@ 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
+ """
+ use_pin_protection = False
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, 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
+ use_8_digits = True
+ HOTP_test_data = [
+ 1284755224, 1094287082, 137359152, 1726969429, 1640338314,
+ 868254676, 1918287922, 82162583, 673399871, 645520489,
+ ]
+ slot_number = 1
+ for counter, code in enumerate(HOTP_test_data):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_hotp_slot(slot_number, 'python_test', RFC_SECRET, counter, use_8_digits, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ r = C.NK_get_hotp_code(slot_number)
+ code = str(code)[-8:] if use_8_digits else str(code)[-6:]
+ assert int(code) == r
+
-@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")
+INT32_MAX = 2 ** 31 - 1
+def test_HOTP_64bit_counter(C):
+ if is_storage(C):
+ pytest.xfail('bug in NK Storage HOTP firmware - counter is set with a 8 digits string, '
+ 'however int32max takes 10 digits to be written')
+ oath = pytest.importorskip("oath")
+ lib_at = lambda t: oath.hotp(RFC_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(INT32_MAX - 5, INT32_MAX + 5, 1):
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ 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))
+ dev_res += (t, code_device)
+ lib_res += (t, lib_at(t))
+ assert dev_res == lib_res
+
+
+def test_TOTP_64bit_time(C):
+ if is_storage(C):
+ pytest.xfail('bug in NK Storage TOTP firmware')
+ oath = pytest.importorskip("oath")
+ T = 1
+ lib_at = lambda t: oath.totp(RFC_SECRET, t=t)
+ PIN_protection = 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
+ assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_write_totp_slot(slot_number, 'python_test', RFC_SECRET, 30, False, False, False, "",
+ DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+ dev_res = []
+ lib_res = []
+ for t in range(INT32_MAX - 5, INT32_MAX + 5, 1):
+ 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)))
+ 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.parametrize("PIN_protection", [False, True, ])
def test_TOTP_RFC_usepin(C, PIN_protection):
+ 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
# 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, False, False, "",
+ assert C.NK_write_totp_slot(slot_number, 'python_test', RFC_SECRET, 30, True, False, False, "",
DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
get_func = None
@@ -352,26 +469,29 @@ def test_TOTP_RFC_usepin(C, PIN_protection):
else:
get_func = C.NK_get_totp_code
+ # Mode: Sha1, time step X=30
test_data = [
- (59, 1, 94287082),
- (1111111109, 0x00000000023523EC, 7081804),
- (1111111111, 0x00000000023523ED, 14050471),
- (1234567890, 0x000000000273EF07, 89005924),
+ #Time T (hex) TOTP
+ (59, 0x1, 94287082),
+ (1111111109, 0x00000000023523EC, 7081804),
+ (1111111111, 0x00000000023523ED, 14050471),
+ (1234567890, 0x000000000273EF07, 89005924),
+ (2000000000, 0x0000000003F940AA, 69279037),
+ # (20000000000, 0x0000000027BC86AA, 65353130), # 64bit is also checked in other test
]
- for t, T, code in test_data:
- """
- 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)
+ responses = []
+ data = []
+ correct = 0
+ for t, T, expected_code in test_data:
if PIN_protection:
C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP)
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert C.NK_totp_set_time(t) == DeviceErrorCode.STATUS_OK
- r = get_func(1, T, 0, 30) # FIXME T is not changing the outcome
- assert code == r
-
+ code_from_device = get_func(slot_number, T, 0, 30) # FIXME T is not changing the outcome
+ data += [ (t, expected_code) ]
+ responses += [ (t, code_from_device) ]
+ 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)
@@ -468,14 +588,9 @@ def test_read_write_config(C):
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)
-
-
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
@@ -532,3 +647,8 @@ def test_warning_binary_bigger_than_secret_buffer(C):
invalid_hex_string = to_hex('1234567890') * 3
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