aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSzczepan Zalega <szczepan@nitrokey.com>2020-06-15 19:15:17 +0200
committerSzczepan Zalega <szczepan@nitrokey.com>2020-06-15 19:15:17 +0200
commit3dd267e062a2f3e182d84f15e13b15b6dd4958e0 (patch)
tree8994b48f6aa359bc52694f1b732c7f1b5ffc4184
parentdd0cdcd2130699be8dde5dbadcf18cd9c00ef78f (diff)
parent89a73d88c9026b6347bea113896d24e419c4370c (diff)
downloadlibnitrokey-3dd267e062a2f3e182d84f15e13b15b6dd4958e0.tar.gz
libnitrokey-3dd267e062a2f3e182d84f15e13b15b6dd4958e0.tar.bz2
Merge branch 'master' into get_status
Update to latest codebase
-rw-r--r--.travis.yml59
-rw-r--r--CMakeLists.txt1
-rw-r--r--NK_C_API.cc44
-rw-r--r--NK_C_API.h76
-rw-r--r--NitrokeyManager.cc48
-rw-r--r--README.md83
-rw-r--r--device.cc80
-rw-r--r--libnitrokey/NitrokeyManager.h1
-rw-r--r--libnitrokey/device.h18
-rw-r--r--libnitrokey/device_proto.h2
-rw-r--r--meson.build18
-rw-r--r--python3_bindings_example.py157
-rw-r--r--unittest/conftest.py104
-rw-r--r--unittest/constants.py14
-rw-r--r--unittest/helpers.py51
-rw-r--r--unittest/misc.py4
-rw-r--r--unittest/requirements.txt1
-rw-r--r--unittest/test_memory.c63
-rw-r--r--unittest/test_multiple.py4
-rw-r--r--unittest/test_offline.cc4
-rw-r--r--unittest/test_pro.py109
-rw-r--r--unittest/test_pro_bootloader.py71
-rw-r--r--unittest/test_storage.py4
23 files changed, 840 insertions, 176 deletions
diff --git a/.travis.yml b/.travis.yml
index 6bf5438..6f3cec0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,12 +7,8 @@ env:
matrix:
include:
-# - osx_image: xcode7.3 #default
-# before_install: &brew
-# - brew update
-# - brew install hidapi
+ - osx_image: xcode11.5
- osx_image: xcode9.1
- - osx_image: xcode8.2
- os: linux
dist: trusty
env: COMPILER_NAME=gcc CXX=g++-5 CC=gcc-5
@@ -22,50 +18,73 @@ matrix:
- cmake
- libhidapi-dev
- g++-5
- - python3
- - python3-pip
- - python3-requests
- - git
sources: &sources
- ubuntu-toolchain-r-test
+ - os: linux
+ dist: trusty
+ env: COMPILER_NAME=gcc CXX=g++-7 CC=gcc-7
+ addons:
+ apt:
+ packages:
+ - cmake
+ - libhidapi-dev
+ - g++-7
+ sources: *sources
+ - os: linux
+ dist: bionic
+ env: COMPILER_NAME=gcc CXX=g++-10 CC=gcc-10
+ addons:
+ apt:
+ packages:
+ - cmake
+ - libhidapi-dev
+ - g++-10
+ - python3
+ - python3-pip
+ - meson
+ - ninja-build
+ sources: *sources
script:
- make -j2
- ctest -VV
- mkdir install && make install DESTDIR=install
- - pip3 install pytest --user
- cd ../
- - pip3 install -r unittest/requirements.txt --user
+ - python3 -m pip install -r unittest/requirements.txt --user
- cd unittest && python3 -m pytest -sv test_offline.py
+ - cd ../
+ - mkdir -p build-meson && meson build-meson
+ - cd build-meson && ninja
+ - env DESTDIR=install ninja install
- os: linux
dist: trusty
- env: COMPILER_NAME=gcc CXX=g++-6 CC=gcc-6
+ env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8
addons:
apt:
packages:
- cmake
- libhidapi-dev
- - g++-6
+ - g++-5
+ - clang-3.8
sources: *sources
- os: linux
- dist: trusty
- env: COMPILER_NAME=gcc CXX=g++-7 CC=gcc-7
+ dist: bionic
+ env: COMPILER_NAME=clang CXX=clang++-6.0 CC=clang-6.0
addons:
apt:
packages:
- cmake
- libhidapi-dev
- - g++-7
+ - clang-6.0
sources: *sources
- os: linux
- dist: trusty
- env: COMPILER_NAME=clang CXX=clang++-3.8 CC=clang-3.8
+ dist: bionic
+ env: COMPILER_NAME=clang CXX=clang++-9 CC=clang-9
addons:
apt:
packages:
- cmake
- libhidapi-dev
- - g++-5
- - clang-3.8
+ - clang-9
sources: *sources
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1169e94..db8d2fb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -216,6 +216,7 @@ IF (COMPILE_TESTS)
unittest/test_HOTP.cc
unittest/test1.cc
unittest/test_issues.cc
+ unittest/test_memory.c
unittest/test_multiple_devices.cc
unittest/test_strdup.cpp
unittest/test_safe.cpp
diff --git a/NK_C_API.cc b/NK_C_API.cc
index 1d3fa3a..538a6a1 100644
--- a/NK_C_API.cc
+++ b/NK_C_API.cc
@@ -158,6 +158,9 @@ extern "C" {
case NK_STORAGE:
model_string = "S";
break;
+ case NK_LIBREM:
+ model_string = "L";
+ break;
case NK_DISCONNECTED:
default:
/* no such enum value -- return error code */
@@ -217,6 +220,12 @@ extern "C" {
});
}
+ NK_C_API int NK_write_config_struct(struct NK_config config,
+ const char *admin_temporary_password) {
+ return NK_write_config(config.numlock, config.capslock, config.scrolllock, config.enable_user_password,
+ config.disable_user_password, admin_temporary_password);
+ }
+
NK_C_API uint8_t* NK_read_config() {
auto m = NitrokeyManager::instance();
@@ -226,6 +235,25 @@ extern "C" {
});
}
+ NK_C_API void NK_free_config(uint8_t* config) {
+ delete[] config;
+ }
+
+ NK_C_API int NK_read_config_struct(struct NK_config* out) {
+ if (out == nullptr) {
+ return -1;
+ }
+ auto m = NitrokeyManager::instance();
+ return get_without_result([&]() {
+ auto v = m->read_config();
+ out->numlock = v[0];
+ out->capslock = v[1];
+ out->scrolllock = v[2];
+ out->enable_user_password = v[3];
+ out->disable_user_password = v[4];
+ });
+ }
+
NK_C_API enum NK_device_model NK_get_device_model() {
auto m = NitrokeyManager::instance();
@@ -236,6 +264,8 @@ extern "C" {
return NK_PRO;
case DeviceModel::STORAGE:
return NK_STORAGE;
+ case DeviceModel::LIBREM:
+ return NK_LIBREM;
default:
/* unknown or not connected device */
return NK_device_model::NK_DISCONNECTED;
@@ -299,6 +329,13 @@ extern "C" {
});
}
+ NK_C_API uint32_t NK_device_serial_number_as_u32() {
+ auto m = NitrokeyManager::instance();
+ return get_with_result([&]() {
+ return m->get_serial_number_as_u32();
+ });
+ }
+
NK_C_API char * NK_get_hotp_code(uint8_t slot_number) {
return NK_get_hotp_code_PIN(slot_number, "");
}
@@ -448,6 +485,10 @@ extern "C" {
}
+ NK_C_API void NK_free_password_safe_slot_status(uint8_t* status) {
+ delete[] status;
+ }
+
NK_C_API uint8_t NK_get_user_retry_count() {
auto m = NitrokeyManager::instance();
return get_with_result([&]() {
@@ -791,6 +832,9 @@ NK_C_API char* NK_get_SD_usage_data_as_string() {
case DeviceModel::STORAGE:
target->model = NK_STORAGE;
break;
+ case DeviceModel::LIBREM:
+ target->model = NK_LIBREM;
+ break;
default:
return false;
}
diff --git a/NK_C_API.h b/NK_C_API.h
index d5c54a3..5341c08 100644
--- a/NK_C_API.h
+++ b/NK_C_API.h
@@ -67,6 +67,9 @@
* case NK_STORAGE:
* printf("a Nitrokey Storage");
* break;
+ * case NK_LIBREM:
+ * printf("a Librem Key");
+ * break;
* default:
* printf("an unsupported Nitrokey");
* break;
@@ -111,7 +114,11 @@ extern "C" {
/**
* Nitrokey Storage.
*/
- NK_STORAGE = 2
+ NK_STORAGE = 2,
+ /**
+ * Librem Key.
+ */
+ NK_LIBREM = 3
};
/**
@@ -265,6 +272,32 @@ extern "C" {
uint8_t write_level_max;
};
+ /**
+ * The general configuration of a Nitrokey device.
+ */
+ struct NK_config {
+ /**
+ * value in range [0-1] to send HOTP code from slot 'numlock' after double pressing numlock
+ * or outside the range to disable this function
+ */
+ uint8_t numlock;
+ /**
+ * similar to numlock but with capslock
+ */
+ uint8_t capslock;
+ /**
+ * similar to numlock but with scrolllock
+ */
+ uint8_t scrolllock;
+ /**
+ * True to enable OTP PIN protection (require PIN each OTP code request)
+ */
+ bool enable_user_password;
+ /**
+ * Unused.
+ */
+ bool disable_user_password;
+ };
struct NK_storage_ProductionTest{
uint8_t FirmwareVersion_au8[2];
@@ -327,7 +360,7 @@ extern "C" {
/**
* Connect to device of given model. Currently library can be connected only to one device at once.
- * @param device_model NK_device_model: NK_PRO: Nitrokey Pro, NK_STORAGE: Nitrokey Storage
+ * @param device_model NK_device_model: NK_PRO: Nitrokey Pro, NK_STORAGE: Nitrokey Storage, NK_LIBREM: Librem Key
* @return 1 if connected, 0 if wrong model or cannot connect
*/
NK_C_API int NK_login_enum(enum NK_device_model device_model);
@@ -386,6 +419,14 @@ extern "C" {
NK_C_API char * NK_device_serial_number();
/**
+ * Return the device's serial number string as an integer. Use
+ * NK_last_command_status to check for an error if this function
+ * returns zero.
+ * @return device's serial number as an integer
+ */
+ NK_C_API uint32_t NK_device_serial_number_as_u32();
+
+ /**
* Get last command processing status. Useful for commands which returns the results of their own and could not return
* an error code.
* @return previous command processing error code
@@ -450,7 +491,16 @@ extern "C" {
bool enable_user_password, bool delete_user_password, const char *admin_temporary_password);
/**
+ * Write general config to the device
+ * @param config the configuration data
+ * @param admin_temporary_password current admin temporary password
+ * @return command processing error code
+ */
+ NK_C_API int NK_write_config_struct(struct NK_config config, const char *admin_temporary_password);
+
+ /**
* Get currently set config - status of function Numlock/Capslock/Scrollock OTP sending and is enabled PIN protected OTP
+ * The return value must be freed using NK_free_config.
* @see NK_write_config
* @return uint8_t general_config[5]:
* uint8_t numlock;
@@ -462,6 +512,21 @@ extern "C" {
*/
NK_C_API uint8_t* NK_read_config();
+ /**
+ * Free a value returned by NK_read_config. May be called with a NULL
+ * argument.
+ */
+ NK_C_API void NK_free_config(uint8_t* config);
+
+ /**
+ * Get currently set config and write it to the given pointer.
+ * @see NK_read_config
+ * @see NK_write_config_struct
+ * @param out a pointer to the struct that should be written to
+ * @return command processing error code
+ */
+ NK_C_API int NK_read_config_struct(struct NK_config* out);
+
//OTP
/**
@@ -634,10 +699,17 @@ extern "C" {
/**
* Get password safe slots' status
+ * The return value must be freed using NK_free_password_safe_slot_status.
* @return uint8_t[16] slot statuses - each byte represents one slot with 0 (not programmed) and 1 (programmed)
*/
NK_C_API uint8_t * NK_get_password_safe_slot_status();
+ /**
+ * Free a value returned by NK_get_password_safe_slot_status. May be
+ * called with a NULL argument.
+ */
+ NK_C_API void NK_free_password_safe_slot_status(uint8_t* status);
+
/**
* Get password safe slot name
* @param slot_number password safe slot number, slot_number<16
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
index f345692..6b0ec8c 100644
--- a/NitrokeyManager.cc
+++ b/NitrokeyManager.cc
@@ -217,7 +217,12 @@ using nitrokey::misc::strcpyT;
}
}
- auto info_ptr = hid_enumerate(NITROKEY_VID, 0);
+ auto vendor_id = NITROKEY_VID;
+ auto info_ptr = hid_enumerate(vendor_id, 0);
+ if (!info_ptr) {
+ vendor_id = PURISM_VID;
+ info_ptr = hid_enumerate(vendor_id, 0);
+ }
auto first_info_ptr = info_ptr;
if (!info_ptr)
return false;
@@ -225,7 +230,7 @@ using nitrokey::misc::strcpyT;
misc::Option<DeviceModel> model;
while (info_ptr && !model.has_value()) {
if (path == std::string(info_ptr->path)) {
- model = product_id_to_model(info_ptr->product_id);
+ model = product_id_to_model(info_ptr->vendor_id, info_ptr->product_id);
}
info_ptr = info_ptr->next;
}
@@ -254,7 +259,8 @@ using nitrokey::misc::strcpyT;
bool NitrokeyManager::connect() {
std::lock_guard<std::mutex> lock(mex_dev_com_manager);
- vector< shared_ptr<Device> > devices = { make_shared<Stick10>(), make_shared<Stick20>() };
+ vector< shared_ptr<Device> > devices = { make_shared<Stick10>(), make_shared<Stick20>(),
+ make_shared<LibremKey>() };
bool connected = false;
for( auto & d : devices ){
if (d->connect()){
@@ -290,6 +296,9 @@ using nitrokey::misc::strcpyT;
case 'S':
device = make_shared<Stick20>();
break;
+ case 'L':
+ device = make_shared<LibremKey>();
+ break;
default:
throw std::runtime_error("Unknown model");
}
@@ -305,6 +314,9 @@ using nitrokey::misc::strcpyT;
case device::DeviceModel::STORAGE:
model_string = "S";
break;
+ case device::DeviceModel::LIBREM:
+ model_string = "L";
+ break;
default:
throw std::runtime_error("Unknown model");
}
@@ -380,22 +392,36 @@ using nitrokey::misc::strcpyT;
string NitrokeyManager::get_serial_number() {
- if (device == nullptr) { return ""; };
+ try {
+ auto serial_number = this->get_serial_number_as_u32();
+ if (serial_number == 0) {
+ return "NA";
+ } else {
+ return nitrokey::misc::toHex(serial_number);
+ }
+ } catch (DeviceNotConnected& e) {
+ return "";
+ }
+ }
+
+ uint32_t NitrokeyManager::get_serial_number_as_u32() {
+ if (device == nullptr) { throw DeviceNotConnected("device not connected"); }
switch (device->get_device_model()) {
+ case DeviceModel::LIBREM:
case DeviceModel::PRO: {
auto response = GetStatus::CommandTransaction::run(device);
- return nitrokey::misc::toHex(response.data().card_serial_u32);
+ return response.data().card_serial_u32;
}
break;
case DeviceModel::STORAGE:
{
auto response = stick20::GetDeviceStatus::CommandTransaction::run(device);
- return nitrokey::misc::toHex(response.data().ActiveSmartCardID_u32);
+ return response.data().ActiveSmartCardID_u32;
}
break;
}
- return "NA";
+ return 0;
}
stick10::GetStatus::ResponsePayload NitrokeyManager::get_status(){
@@ -564,6 +590,7 @@ using nitrokey::misc::strcpyT;
strcpyT(payload.slot_name, slot_name);
strcpyT(payload.slot_token_id, token_ID);
switch (device->get_device_model() ){
+ case DeviceModel::LIBREM:
case DeviceModel::PRO: {
payload.slot_counter = hotp_counter;
break;
@@ -725,6 +752,7 @@ using nitrokey::misc::strcpyT;
template <typename ProCommand, PasswordKind StoKind>
void NitrokeyManager::change_PIN_general(const char *current_PIN, const char *new_PIN) {
switch (device->get_device_model()){
+ case DeviceModel::LIBREM:
case DeviceModel::PRO:
{
auto p = get_payload<ProCommand>();
@@ -846,6 +874,7 @@ using nitrokey::misc::strcpyT;
void NitrokeyManager::build_aes_key(const char *admin_password) {
switch (device->get_device_model()) {
+ case DeviceModel::LIBREM:
case DeviceModel::PRO: {
auto p = get_payload<BuildAESKey>();
strcpyT(p.admin_password, admin_password);
@@ -870,6 +899,7 @@ using nitrokey::misc::strcpyT;
void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) {
switch (device->get_device_model()){
+ case DeviceModel::LIBREM:
case DeviceModel::PRO: {
auto p = get_payload<stick10::UnlockUserPassword>();
strcpyT(p.admin_password, admin_password);
@@ -919,6 +949,7 @@ using nitrokey::misc::strcpyT;
//authorization command is supported for versions equal or below:
auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
{DeviceModel::PRO, 7},
+ {DeviceModel::LIBREM, 7},
{DeviceModel::STORAGE, 53},
});
return get_minor_firmware_version() <= m[device->get_device_model()];
@@ -928,6 +959,7 @@ using nitrokey::misc::strcpyT;
// 320 bit OTP secret is supported by version bigger or equal to:
auto m = std::unordered_map<DeviceModel , int, EnumClassHash>({
{DeviceModel::PRO, 8},
+ {DeviceModel::LIBREM, 8},
{DeviceModel::STORAGE, 54},
});
return get_minor_firmware_version() >= m[device->get_device_model()];
@@ -952,6 +984,7 @@ using nitrokey::misc::strcpyT;
uint8_t NitrokeyManager::get_minor_firmware_version(){
switch(device->get_device_model()){
+ case DeviceModel::LIBREM:
case DeviceModel::PRO:{
auto status_p = GetStatus::CommandTransaction::run(device);
return status_p.data().firmware_version_st.minor; //7 or 8
@@ -968,6 +1001,7 @@ using nitrokey::misc::strcpyT;
}
uint8_t NitrokeyManager::get_major_firmware_version(){
switch(device->get_device_model()){
+ case DeviceModel::LIBREM:
case DeviceModel::PRO:{
auto status_p = GetStatus::CommandTransaction::run(device);
return status_p.data().firmware_version_st.major; //0
diff --git a/README.md b/README.md
index a3683c0..aa5ca1a 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,7 @@ To use libnitrokey with Python a [CFFI](http://cffi.readthedocs.io/en/latest/ove
pip install --user cffi # for python 2.x
pip3 install cffi # for python 3.x
```
+## Python2
Just import it, read the C API header and it is done! You have access to the library. Here is an example (in Python 2) printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python_bindings_example.py):
```python
#!/usr/bin/env python2
@@ -158,6 +159,88 @@ print('Getting HOTP code from Nitrokey device: ')
print(hotp_slot_code)
libnitrokey.NK_logout() # disconnect device
```
+In case no devices are connected, a friendly message will be printed.
+All available functions for C and Python are listed in [NK_C_API.h](NK_C_API.h). Please check `Documentation` section below.
+
+## Python3
+Just import it, read the C API header and it is done! You have access to the library. Here is an example (in Python 3) printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python3_bindings_example.py):
+```python
+#!/usr/bin/env python3
+import cffi
+
+ffi = cffi.FFI()
+get_string = ffi.string
+
+def get_library():
+ fp = 'NK_C_API.h' # path to C API header
+
+ declarations = []
+ with open(fp, 'r') as f:
+ declarations = f.readlines()
+
+ cnt = 0
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.strip().startswith('NK_C_API'):
+ declaration = declaration.replace('NK_C_API', '').strip()
+ while ';' not in declaration:
+ declaration += (next(a)).strip()
+ # print(declaration)
+ ffi.cdef(declaration, override=True)
+ cnt +=1
+ print('Imported {} declarations'.format(cnt))
+
+
+ C = None
+ import os, sys
+ path_build = os.path.join(".", "build")
+ paths = [
+ os.environ.get('LIBNK_PATH', None),
+ os.path.join(path_build,"libnitrokey.so"),
+ os.path.join(path_build,"libnitrokey.dylib"),
+ os.path.join(path_build,"libnitrokey.dll"),
+ os.path.join(path_build,"nitrokey.dll"),
+ ]
+ for p in paths:
+ if not p: continue
+ print("Trying " +p)
+ p = os.path.abspath(p)
+ if os.path.exists(p):
+ print("Found: "+p)
+ C = ffi.dlopen(p)
+ break
+ else:
+ print("File does not exist: " + p)
+ if not C:
+ print("No library file found")
+ sys.exit(1)
+
+ return C
+
+
+def get_hotp_code(lib, i):
+ return lib.NK_get_hotp_code(i)
+
+def connect_device(lib):
+ # lib.NK_login('S'.encode('ascii')) # connect only to Nitrokey Storage device
+ # lib.NK_login('P'.encode('ascii')) # connect only to Nitrokey Pro device
+ device_connected = lib.NK_login_auto() # connect to any Nitrokey Stick
+ if device_connected:
+ print('Connected to Nitrokey device!')
+ else:
+ print('Could not connect to Nitrokey device!')
+ exit()
+
+libnitrokey = get_library()
+libnitrokey.NK_set_debug(False) # do not show debug messages (log library only)
+
+connect_device(libnitrokey)
+
+hotp_slot_code = get_hotp_code(libnitrokey, 1)
+print('Getting HOTP code from Nitrokey device: ')
+print(ffi.string(hotp_slot_code).decode('ascii'))
+libnitrokey.NK_logout() # disconnect device
+```
In case no devices are connected, a friendly message will be printed.
All available functions for C and Python are listed in [NK_C_API.h](NK_C_API.h). Please check `Documentation` section below.
diff --git a/device.cc b/device.cc
index bc42965..20bb0c6 100644
--- a/device.cc
+++ b/device.cc
@@ -45,14 +45,33 @@ const uint16_t nitrokey::device::NITROKEY_VID = 0x20a0;
const uint16_t nitrokey::device::NITROKEY_PRO_PID = 0x4108;
const uint16_t nitrokey::device::NITROKEY_STORAGE_PID = 0x4109;
+const uint16_t nitrokey::device::PURISM_VID = 0x316d;
+const uint16_t nitrokey::device::LIBREM_KEY_PID = 0x4c4b;
+
Option<DeviceModel> nitrokey::device::product_id_to_model(uint16_t product_id) {
- switch (product_id) {
+ return product_id_to_model(NITROKEY_VID, product_id);
+}
+
+Option<DeviceModel> nitrokey::device::product_id_to_model(uint16_t vendor_id, uint16_t product_id) {
+ switch (vendor_id) {
+ case NITROKEY_VID:
+ switch (product_id) {
case NITROKEY_PRO_PID:
return DeviceModel::PRO;
case NITROKEY_STORAGE_PID:
return DeviceModel::STORAGE;
default:
return {};
+ }
+ case PURISM_VID:
+ switch (product_id) {
+ case LIBREM_KEY_PID:
+ return DeviceModel::LIBREM;
+ default:
+ return {};
+ }
+ default:
+ return {};
}
}
@@ -67,6 +86,9 @@ std::ostream& nitrokey::device::operator<<(std::ostream& stream, DeviceModel mod
case DeviceModel::STORAGE:
stream << "Storage";
break;
+ case DeviceModel::LIBREM:
+ stream << "Librem";
+ break;
default:
stream << "Unknown";
break;
@@ -99,7 +121,9 @@ bool Device::disconnect() {
}
bool Device::_disconnect() {
- LOG(std::string(__FUNCTION__) + std::string(m_model == DeviceModel::PRO ? "PRO" : "STORAGE"), Loglevel::DEBUG_L2);
+ LOG(std::string(__FUNCTION__) +
+ std::string(m_model == DeviceModel::PRO ? "PRO" : (m_model == DeviceModel::STORAGE ? "STORAGE" : "LIBREM")),
+ Loglevel::DEBUG_L2);
LOG(std::string(__FUNCTION__) + std::string(" *IN* "), Loglevel::DEBUG_L2);
if(mp_devhandle == nullptr) {
@@ -204,27 +228,36 @@ int Device::recv(void *packet) {
return status;
}
-std::vector<DeviceInfo> Device::enumerate(){
- auto pInfo = hid_enumerate(NITROKEY_VID, 0);
- auto pInfo_ = pInfo;
- std::vector<DeviceInfo> res;
- while (pInfo != nullptr){
- auto deviceModel = product_id_to_model(pInfo->product_id);
- if (deviceModel.has_value()) {
- std::string path(pInfo->path);
- std::wstring serialNumberW(pInfo->serial_number);
- std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
- std::string serialNumber = converter.to_bytes(serialNumberW);
- DeviceInfo info = { deviceModel.value(), path, serialNumber };
- res.push_back(info);
+namespace {
+ void add_vendor_devices(std::vector<DeviceInfo>& res, uint16_t vendor_id){
+ auto pInfo = hid_enumerate(vendor_id, 0);
+ auto pInfo_ = pInfo;
+ while (pInfo != nullptr){
+ if (pInfo->path == nullptr || pInfo->serial_number == nullptr) {
+ continue;
+ }
+ auto deviceModel = product_id_to_model(vendor_id, pInfo->product_id);
+ if (deviceModel.has_value()) {
+ std::string path(pInfo->path);
+ std::wstring serialNumberW(pInfo->serial_number);
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
+ std::string serialNumber = converter.to_bytes(serialNumberW);
+ DeviceInfo info = { deviceModel.value(), path, serialNumber };
+ res.push_back(info);
+ }
+ pInfo = pInfo->next;
}
- pInfo = pInfo->next;
- }
- if (pInfo_ != nullptr){
- hid_free_enumeration(pInfo_);
+ if (pInfo_ != nullptr){
+ hid_free_enumeration(pInfo_);
+ }
}
+}
+std::vector<DeviceInfo> Device::enumerate(){
+ std::vector<DeviceInfo> res;
+ ::add_vendor_devices(res, NITROKEY_VID);
+ ::add_vendor_devices(res, PURISM_VID);
return res;
}
@@ -234,6 +267,8 @@ std::shared_ptr<Device> Device::create(DeviceModel model) {
return std::make_shared<Stick10>();
case DeviceModel::STORAGE:
return std::make_shared<Stick20>();
+ case DeviceModel::LIBREM:
+ return std::make_shared<LibremKey>();
default:
return {};
}
@@ -305,6 +340,13 @@ Stick20::Stick20():
setDefaultDelay();
}
+
+LibremKey::LibremKey():
+ Device(PURISM_VID, LIBREM_KEY_PID, DeviceModel::LIBREM, 100ms, 5, 100ms)
+ {
+ setDefaultDelay();
+ }
+
#include <sstream>
#define p(x) ss << #x << " " << x << ", ";
std::string Device::ErrorCounters::get_as_string() {
diff --git a/libnitrokey/NitrokeyManager.h b/libnitrokey/NitrokeyManager.h
index 33ede1b..163a799 100644
--- a/libnitrokey/NitrokeyManager.h
+++ b/libnitrokey/NitrokeyManager.h
@@ -104,6 +104,7 @@ char * strndup(const char* str, size_t maxlen);
stick10::GetStatus::ResponsePayload get_status();
string get_status_as_string();
string get_serial_number();
+ uint32_t get_serial_number_as_u32();
char * get_totp_slot_name(uint8_t slot_number);
char * get_hotp_slot_name(uint8_t slot_number);
diff --git a/libnitrokey/device.h b/libnitrokey/device.h
index d50080d..917e0d0 100644
--- a/libnitrokey/device.h
+++ b/libnitrokey/device.h
@@ -50,7 +50,8 @@ namespace device {
enum class DeviceModel{
PRO,
- STORAGE
+ STORAGE,
+ LIBREM
};
std::ostream& operator<<(std::ostream& stream, DeviceModel model);
@@ -67,12 +68,21 @@ extern const uint16_t NITROKEY_PRO_PID;
* The USB product ID for the Nitrokey Storage.
*/
extern const uint16_t NITROKEY_STORAGE_PID;
+/**
+ * The USB vendor ID for Purism devices.
+ */
+extern const uint16_t PURISM_VID;
+/**
+ * The USB product ID for the Librem Key.
+ */
+extern const uint16_t LIBREM_KEY_PID;
/**
* Convert the given USB product ID to a Nitrokey model. If there is no model
* with that ID, return an absent value.
*/
misc::Option<DeviceModel> product_id_to_model(uint16_t product_id);
+misc::Option<DeviceModel> product_id_to_model(uint16_t vendor_id, uint16_t product_id);
/**
* Information about a connected device.
@@ -219,6 +229,12 @@ class Stick20 : public Device {
public:
Stick20();
};
+
+class LibremKey : public Device {
+ public:
+ LibremKey();
+};
+
}
}
#endif
diff --git a/libnitrokey/device_proto.h b/libnitrokey/device_proto.h
index 45a6c16..6ffe5fb 100644
--- a/libnitrokey/device_proto.h
+++ b/libnitrokey/device_proto.h
@@ -249,7 +249,7 @@ namespace nitrokey {
}
dev->m_counters.total_comm_runs++;
- int status;
+ int status = 0;
OutgoingPacket outp;
ResponsePacket resp;
diff --git a/meson.build b/meson.build
index 600be6d..c758a7a 100644
--- a/meson.build
+++ b/meson.build
@@ -1,6 +1,6 @@
project(
'libnitrokey', 'cpp',
- version : '3.4.1',
+ version : '3.5.0',
license : 'LGPL-3.0+',
default_options : [
'cpp_std=c++14'
@@ -150,12 +150,16 @@ if get_option('offline-tests')
endif
if get_option('tests')
tests += [
- ['test_C_API', 'test_C_API.cpp'],
- ['test1', 'test1.cc'],
- ['test2', 'test2.cc'],
- ['test3', 'test3.cc'],
- ['test_HOTP', 'test_HOTP.cc'],
- ['test_issues', 'test_issues.cc'],
+ ['test_C_API', 'test_C_API.cpp'],
+ ['test1', 'test1.cc'],
+ ['test2', 'test2.cc'],
+ ['test3', 'test3.cc'],
+ ['test_HOTP', 'test_HOTP.cc'],
+ ['test_memory', 'test_memory.c'],
+ ['test_issues', 'test_issues.cc'],
+ ['test_multiple_devices', 'test_multiple_devices.cc'],
+ ['test_strdup', 'test_strdup.cpp'],
+ ['test_safe', 'test_safe.cpp'],
]
endif
foreach tst : tests
diff --git a/python3_bindings_example.py b/python3_bindings_example.py
new file mode 100644
index 0000000..fb24eff
--- /dev/null
+++ b/python3_bindings_example.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+"""
+Copyright (c) 2015-2018 Nitrokey UG
+
+This file is part of libnitrokey.
+
+libnitrokey is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+any later version.
+
+libnitrokey is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with libnitrokey. If not, see <http://www.gnu.org/licenses/>.
+
+SPDX-License-Identifier: LGPL-3.0
+"""
+
+import cffi
+from enum import Enum
+
+"""
+This example will print 10 HOTP codes from just written HOTP#2 slot.
+For more examples of use please refer to unittest/test_*.py files.
+"""
+
+ffi = cffi.FFI()
+get_string = ffi.string
+
+class DeviceErrorCode(Enum):
+ STATUS_OK = 0
+ NOT_PROGRAMMED = 3
+ WRONG_PASSWORD = 4
+ STATUS_NOT_AUTHORIZED = 5
+ STATUS_AES_DEC_FAILED = 0xa
+
+
+def get_library():
+ fp = 'NK_C_API.h' # path to C API header
+
+ declarations = []
+ with open(fp, 'r') as f:
+ declarations = f.readlines()
+
+ cnt = 0
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.strip().startswith('NK_C_API'):
+ declaration = declaration.replace('NK_C_API', '').strip()
+ while ';' not in declaration:
+ declaration += (next(a)).strip()
+ # print(declaration)
+ ffi.cdef(declaration, override=True)
+ cnt +=1
+ print('Imported {} declarations'.format(cnt))
+
+
+ C = None
+ import os, sys
+ path_build = os.path.join(".", "build")
+ paths = [
+ os.environ.get('LIBNK_PATH', None),
+ os.path.join(path_build,"libnitrokey.so"),
+ os.path.join(path_build,"libnitrokey.dylib"),
+ os.path.join(path_build,"libnitrokey.dll"),
+ os.path.join(path_build,"nitrokey.dll"),
+ ]
+ for p in paths:
+ if not p: continue
+ print("Trying " +p)
+ p = os.path.abspath(p)
+ if os.path.exists(p):
+ print("Found: "+p)
+ C = ffi.dlopen(p)
+ break
+ else:
+ print("File does not exist: " + p)
+ if not C:
+ print("No library file found")
+ print("Please set the path using LIBNK_PATH environment variable to existing library or compile it (see "
+ "README.md for details)")
+ sys.exit(1)
+
+ return C
+
+
+def get_hotp_code(lib, i):
+ return get_string(lib.NK_get_hotp_code(i))
+
+def to_hex(ss):
+ return ''.join([ format(ord(s),'02x') for s in ss ])
+
+print('Warning!')
+print('This example will change your configuration on inserted stick and overwrite your HOTP#2 slot.')
+print('Please write "continue" to continue or any other string to quit')
+a = input()
+
+if not a == 'continue':
+ exit()
+
+ADMIN = input('Please enter your admin PIN (empty string uses 12345678) ')
+ADMIN = ADMIN or '12345678' # use default if empty string
+
+show_log = input('Should log messages be shown (please write "yes" to enable; this will make harder reading script output) ') == 'yes'
+libnitrokey = get_library()
+
+if show_log:
+ log_level = input('Please select verbosity level (0-5, 2 is library default, 3 will be selected on empty input) ')
+ log_level = log_level or '3'
+ log_level = int(log_level)
+ libnitrokey.NK_set_debug_level(log_level)
+else:
+ libnitrokey.NK_set_debug_level(2)
+
+
+ADMIN_TEMP = '123123123'
+RFC_SECRET = to_hex('12345678901234567890')
+
+# libnitrokey.NK_login('S') # connect only to Nitrokey Storage device
+# libnitrokey.NK_login('P') # connect only to Nitrokey Pro device
+device_connected = libnitrokey.NK_login_auto() # connect to any Nitrokey Stick
+if device_connected:
+ print('Connected to Nitrokey device!')
+else:
+ print('Could not connect to Nitrokey device!')
+ exit()
+
+use_8_digits = True
+pin_correct = libnitrokey.NK_first_authenticate(ADMIN.encode('ascii'), ADMIN_TEMP.encode('ascii')) == DeviceErrorCode.STATUS_OK.value
+if pin_correct:
+ print('Your PIN is correct!')
+else:
+ print('Your PIN is not correct! Please try again. Please be careful to not lock your stick!')
+ retry_count_left = libnitrokey.NK_get_admin_retry_count()
+ print('Retry count left: %d' % retry_count_left )
+ exit()
+
+# For function parameters documentation please check NK_C_API.h
+assert libnitrokey.NK_write_config(255, 255, 255, False, True, ADMIN_TEMP.encode('ascii')) == DeviceErrorCode.STATUS_OK.value
+libnitrokey.NK_first_authenticate(ADMIN.encode('ascii'), ADMIN_TEMP.encode('ascii'))
+libnitrokey.NK_write_hotp_slot(1, 'python_test'.encode('ascii'), RFC_SECRET.encode('ascii'), 0, use_8_digits, False, False, "".encode('ascii'),
+ ADMIN_TEMP.encode('ascii'))
+# RFC test according to: https://tools.ietf.org/html/rfc4226#page-32
+test_data = [
+ 1284755224, 1094287082, 137359152, 1726969429, 1640338314, 868254676, 1918287922, 82162583, 673399871,
+ 645520489,
+]
+print('Getting HOTP code from Nitrokey Stick (RFC test, 8 digits): ')
+for i in range(10):
+ hotp_slot_1_code = get_hotp_code(libnitrokey, 1)
+ correct_str = "correct!" if hotp_slot_1_code.decode('ascii') == str(test_data[i])[-8:] else "not correct"
+ print('%d: %s, should be %s -> %s' % (i, hotp_slot_1_code.decode('ascii'), str(test_data[i])[-8:], correct_str))
+libnitrokey.NK_logout() # disconnect device
diff --git a/unittest/conftest.py b/unittest/conftest.py
index 49b4f02..17d9ef5 100644
--- a/unittest/conftest.py
+++ b/unittest/conftest.py
@@ -20,6 +20,7 @@ SPDX-License-Identifier: LGPL-3.0
"""
import pytest
+import os, sys
from misc import ffi, gs
@@ -82,47 +83,8 @@ def C(request=None):
def get_library(request, allow_offline=False):
- fp = '../NK_C_API.h'
-
- declarations = []
- with open(fp, 'r') as f:
- declarations = f.readlines()
-
- cnt = 0
- a = iter(declarations)
- for declaration in a:
- if declaration.strip().startswith('NK_C_API') \
- or declaration.strip().startswith('struct'):
- declaration = declaration.replace('NK_C_API', '').strip()
- while ');' not in declaration and '};' not in declaration:
- declaration += (next(a)).strip()+'\n'
- ffi.cdef(declaration, override=True)
- cnt += 1
- print('Imported {} declarations'.format(cnt))
-
- C = None
- import os, sys
- path_build = os.path.join("..", "build")
- paths = [
- os.environ.get('LIBNK_PATH', None),
- os.path.join(path_build,"libnitrokey.so"),
- os.path.join(path_build,"libnitrokey.dylib"),
- os.path.join(path_build,"libnitrokey.dll"),
- os.path.join(path_build,"nitrokey.dll"),
- ]
- for p in paths:
- if not p: continue
- print("Trying " +p)
- p = os.path.abspath(p)
- if os.path.exists(p):
- print("Found: "+p)
- C = ffi.dlopen(p)
- break
- else:
- print("File does not exist: " + p)
- if not C:
- print("No library file found")
- sys.exit(1)
+ library_read_declarations()
+ C = library_open_lib()
C.NK_set_debug_level(int(os.environ.get('LIBNK_DEBUG', 2)))
@@ -155,3 +117,63 @@ def get_library(request, allow_offline=False):
return AttrProxy(C, "libnitrokey C")
+
+def library_open_lib():
+ C = None
+ path_build = os.path.join("..", "build")
+ paths = [
+ os.environ.get('LIBNK_PATH', None),
+ os.path.join(path_build, "libnitrokey.so"),
+ os.path.join(path_build, "libnitrokey.dylib"),
+ os.path.join(path_build, "libnitrokey.dll"),
+ os.path.join(path_build, "nitrokey.dll"),
+ ]
+ for p in paths:
+ if not p: continue
+ print("Trying " + p)
+ p = os.path.abspath(p)
+ if os.path.exists(p):
+ print("Found: " + p)
+ C = ffi.dlopen(p)
+ break
+ else:
+ print("File does not exist: " + p)
+ if not C:
+ print("No library file found")
+ sys.exit(1)
+ return C
+
+
+def library_read_declarations():
+ fp = '../NK_C_API.h'
+ declarations = []
+ with open(fp, 'r') as f:
+ declarations = f.readlines()
+ cnt = 0
+ a = iter(declarations)
+ for declaration in a:
+ if declaration.strip().startswith('NK_C_API') \
+ or declaration.strip().startswith('struct'):
+ declaration = declaration.replace('NK_C_API', '').strip()
+ while ');' not in declaration and '};' not in declaration:
+ declaration += (next(a)).strip() + '\n'
+ ffi.cdef(declaration, override=True)
+ cnt += 1
+ print('Imported {} declarations'.format(cnt))
+
+
+def pytest_addoption(parser):
+ parser.addoption("--run-skipped", action="store_true",
+ help="run the tests skipped by default, e.g. adding side effects")
+
+def pytest_runtest_setup(item):
+ if 'skip_by_default' in item.keywords and not item.config.getoption("--run-skipped"):
+ pytest.skip("need --run-skipped option to run this test")
+
+
+def library_device_reconnect(C):
+ C.NK_logout()
+ C = library_open_lib()
+ C.NK_logout()
+ assert C.NK_login_auto() == 1, 'Device not found'
+ return C \ No newline at end of file
diff --git a/unittest/constants.py b/unittest/constants.py
index 645ef6a..4047f59 100644
--- a/unittest/constants.py
+++ b/unittest/constants.py
@@ -18,12 +18,7 @@ along with libnitrokey. If not, see <http://www.gnu.org/licenses/>.
SPDX-License-Identifier: LGPL-3.0
"""
-
-from misc import to_hex
-
-def bb(x):
- return bytes(x, encoding='ascii')
-
+from misc import to_hex, bb
RFC_SECRET_HR = '12345678901234567890'
RFC_SECRET = to_hex(RFC_SECRET_HR) # '31323334353637383930...'
@@ -39,6 +34,9 @@ class DefaultPasswords:
USER_TEMP = b'234234234'
UPDATE = b'12345678'
UPDATE_TEMP = b'123update123'
+ UPDATE_LONG = b'1234567890'*2
+ UPDATE_TOO_LONG = UPDATE_LONG + b'x'
+ UPDATE_TOO_SHORT = UPDATE_LONG[:7]
class DeviceErrorCode:
@@ -49,6 +47,7 @@ class DeviceErrorCode:
STATUS_NOT_AUTHORIZED = 5
STATUS_AES_DEC_FAILED = 0xa
STATUS_UNKNOWN_ERROR = 100
+ STATUS_DISCONNECTED = 255
class LibraryErrors:
@@ -59,4 +58,5 @@ class LibraryErrors:
HOTP_slot_count = 3
-TOTP_slot_count = 15 \ No newline at end of file
+TOTP_slot_count = 15
+PWS_SLOT_COUNT = 16
diff --git a/unittest/helpers.py b/unittest/helpers.py
new file mode 100644
index 0000000..90c818e
--- /dev/null
+++ b/unittest/helpers.py
@@ -0,0 +1,51 @@
+from constants import DeviceErrorCode, PWS_SLOT_COUNT, DefaultPasswords
+from misc import gs, bb
+
+
+def helper_fill(str_to_fill, target_width):
+ assert target_width >= len(str_to_fill)
+ numbers = '1234567890' * 4
+ str_to_fill += numbers[:target_width - len(str_to_fill)]
+ assert len(str_to_fill) == target_width
+ return bb(str_to_fill)
+
+
+def helper_PWS_get_pass(suffix):
+ return helper_fill('pass' + suffix, 20)
+
+
+def helper_PWS_get_loginname(suffix):
+ return helper_fill('login' + suffix, 32)
+
+
+def helper_PWS_get_slotname(suffix):
+ return helper_fill('slotname' + suffix, 11)
+
+
+def helper_check_device_for_data(C):
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ for i in range(0, PWS_SLOT_COUNT):
+ iss = str(i)
+ assert gs(C.NK_get_password_safe_slot_name(i)) == helper_PWS_get_slotname(iss)
+ assert gs(C.NK_get_password_safe_slot_login(i)) == helper_PWS_get_loginname(iss)
+ assert gs(C.NK_get_password_safe_slot_password(i)) == helper_PWS_get_pass(iss)
+ return True
+
+
+def helper_populate_device(C):
+ # FIXME use object with random data, and check against it
+ # FIXME generate OTP as well, and check codes against its secrets
+ assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
+ res = C.NK_enable_password_safe(DefaultPasswords.USER)
+ if res != DeviceErrorCode.STATUS_OK:
+ assert C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
+ assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
+
+ for i in range(0, PWS_SLOT_COUNT):
+ iss = str(i)
+ assert C.NK_write_password_safe_slot(i,
+ helper_PWS_get_slotname(iss), helper_PWS_get_loginname(iss),
+ helper_PWS_get_pass(iss)) == DeviceErrorCode.STATUS_OK
+ return True
diff --git a/unittest/misc.py b/unittest/misc.py
index e9e1753..6a0d486 100644
--- a/unittest/misc.py
+++ b/unittest/misc.py
@@ -72,3 +72,7 @@ def is_long_OTP_secret_handled(C):
def has_binary_counter(C):
return (not is_storage(C)) or (is_storage(C) and get_devices_firmware_version(C) >= 54)
+
+
+def bb(x):
+ return bytes(x, encoding='ascii') \ No newline at end of file
diff --git a/unittest/requirements.txt b/unittest/requirements.txt
index 6d718ad..d8a7e29 100644
--- a/unittest/requirements.txt
+++ b/unittest/requirements.txt
@@ -1,4 +1,5 @@
cffi
+pytest
pytest-repeat
pytest-randomly
tqdm
diff --git a/unittest/test_memory.c b/unittest/test_memory.c
new file mode 100644
index 0000000..20b11b2
--- /dev/null
+++ b/unittest/test_memory.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020 Nitrokey UG
+ *
+ * This file is part of libnitrokey.
+ *
+ * libnitrokey is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * libnitrokey is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libnitrokey. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0
+ */
+
+#include <stdlib.h>
+#include "../NK_C_API.h"
+
+// This test should be run with valgrind to make sure that there are no
+// memory leaks in the tested functions:
+// valgrind ./test_memory
+int main() {
+ int result = NK_login_auto();
+ if (result != 1)
+ return 1;
+
+ int retry_count = NK_get_admin_retry_count();
+ if (retry_count != 3)
+ return 1;
+ retry_count = NK_get_user_retry_count();
+ if (retry_count != 3)
+ return 1;
+
+ enum NK_device_model model = NK_get_device_model();
+ if (model != NK_PRO && model != NK_STORAGE)
+ return 1;
+
+ uint8_t *config = NK_read_config();
+ if (config == NULL)
+ return 1;
+ NK_free_config(config);
+
+ result = NK_enable_password_safe("123456");
+ if (result != 0)
+ return 1;
+
+ uint8_t *slot_status = NK_get_password_safe_slot_status();
+ if (slot_status == NULL) {
+ return 1;
+ }
+ NK_free_password_safe_slot_status(slot_status);
+
+ NK_logout();
+
+ return 0;
+}
+
diff --git a/unittest/test_multiple.py b/unittest/test_multiple.py
index 821a3b7..96b23d7 100644
--- a/unittest/test_multiple.py
+++ b/unittest/test_multiple.py
@@ -28,8 +28,8 @@ from collections import defaultdict
from tqdm import tqdm
from conftest import skip_if_device_version_lower_than
-from constants import DefaultPasswords, DeviceErrorCode, bb
-from misc import gs, wait, ffi
+from constants import DefaultPasswords, DeviceErrorCode
+from misc import gs, wait, ffi, bb
pprint = pprint.PrettyPrinter(indent=4).pprint
diff --git a/unittest/test_offline.cc b/unittest/test_offline.cc
index 320ad48..3ca3905 100644
--- a/unittest/test_offline.cc
+++ b/unittest/test_offline.cc
@@ -67,6 +67,10 @@ TEST_CASE("Test C++ side behaviour in offline", "[fast]") {
REQUIRE(serial_number.empty());
REQUIRE_THROWS_AS(
+ i->get_serial_number_as_u32(), DeviceNotConnected
+ );
+
+ REQUIRE_THROWS_AS(
i->get_status(), DeviceNotConnected
);
diff --git a/unittest/test_pro.py b/unittest/test_pro.py
index 203c8b1..30cd5e1 100644
--- a/unittest/test_pro.py
+++ b/unittest/test_pro.py
@@ -22,9 +22,10 @@ SPDX-License-Identifier: LGPL-3.0
import pytest
from conftest import skip_if_device_version_lower_than
-from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bb, bbRFC_SECRET, LibraryErrors, HOTP_slot_count, \
+from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bbRFC_SECRET, LibraryErrors, HOTP_slot_count, \
TOTP_slot_count
-from misc import ffi, gs, wait, cast_pointer_to_tuple, has_binary_counter
+from helpers import helper_PWS_get_slotname, helper_PWS_get_loginname, helper_PWS_get_pass
+from misc import ffi, gs, wait, cast_pointer_to_tuple, has_binary_counter, bb
from misc import is_storage
@pytest.mark.lock_device
@@ -50,37 +51,21 @@ def test_write_password_safe_slot(C):
@pytest.mark.PWS
@pytest.mark.slowtest
def test_write_all_password_safe_slots_and_read_10_times(C):
- def fill(s, wid):
- assert wid >= len(s)
- numbers = '1234567890'*4
- s += numbers[:wid-len(s)]
- assert len(s) == wid
- return bb(s)
-
- def get_pass(suffix):
- return fill('pass' + suffix, 20)
-
- def get_loginname(suffix):
- return fill('login' + suffix, 32)
-
- def get_slotname(suffix):
- return fill('slotname' + suffix, 11)
-
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
PWS_slot_count = 16
for i in range(0, PWS_slot_count):
iss = str(i)
assert C.NK_write_password_safe_slot(i,
- get_slotname(iss), get_loginname(iss),
- get_pass(iss)) == DeviceErrorCode.STATUS_OK
+ helper_PWS_get_slotname(iss), helper_PWS_get_loginname(iss),
+ helper_PWS_get_pass(iss)) == DeviceErrorCode.STATUS_OK
for j in range(0, 10):
for i in range(0, PWS_slot_count):
iss = str(i)
- assert gs(C.NK_get_password_safe_slot_name(i)) == get_slotname(iss)
- assert gs(C.NK_get_password_safe_slot_login(i)) == get_loginname(iss)
- assert gs(C.NK_get_password_safe_slot_password(i)) == get_pass(iss)
+ assert gs(C.NK_get_password_safe_slot_name(i)) == helper_PWS_get_slotname(iss)
+ assert gs(C.NK_get_password_safe_slot_login(i)) == helper_PWS_get_loginname(iss)
+ assert gs(C.NK_get_password_safe_slot_password(i)) == helper_PWS_get_pass(iss)
@pytest.mark.lock_device
@@ -88,22 +73,6 @@ def test_write_all_password_safe_slots_and_read_10_times(C):
@pytest.mark.slowtest
@pytest.mark.xfail(reason="This test should be run directly after test_write_all_password_safe_slots_and_read_10_times")
def test_read_all_password_safe_slots_10_times(C):
- def fill(s, wid):
- assert wid >= len(s)
- numbers = '1234567890'*4
- s += numbers[:wid-len(s)]
- assert len(s) == wid
- return bb(s)
-
- def get_pass(suffix):
- return fill('pass' + suffix, 20)
-
- def get_loginname(suffix):
- return fill('login' + suffix, 32)
-
- def get_slotname(suffix):
- return fill('slotname' + suffix, 11)
-
assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK
assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
PWS_slot_count = 16
@@ -111,9 +80,9 @@ def test_read_all_password_safe_slots_10_times(C):
for j in range(0, 10):
for i in range(0, PWS_slot_count):
iss = str(i)
- assert gs(C.NK_get_password_safe_slot_name(i)) == get_slotname(iss)
- assert gs(C.NK_get_password_safe_slot_login(i)) == get_loginname(iss)
- assert gs(C.NK_get_password_safe_slot_password(i)) == get_pass(iss)
+ assert gs(C.NK_get_password_safe_slot_name(i)) == helper_PWS_get_slotname(iss)
+ assert gs(C.NK_get_password_safe_slot_login(i)) == helper_PWS_get_loginname(iss)
+ assert gs(C.NK_get_password_safe_slot_password(i)) == helper_PWS_get_pass(iss)
@pytest.mark.lock_device
@@ -678,6 +647,30 @@ def test_read_write_config(C):
config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5)
assert config == (0, 1, 2, True, False)
+ # use structs: read I
+ config_st = ffi.new('struct NK_config *')
+ if not config_st:
+ raise Exception("Could not allocate config")
+ assert C.NK_read_config_struct(config_st) == DeviceErrorCode.STATUS_OK
+ assert config_st.numlock == 0
+ assert config_st.capslock == 1
+ assert config_st.scrolllock == 2
+ assert config_st.enable_user_password
+ assert not config_st.disable_user_password
+
+ # use structs: write
+ config_st.numlock = 3
+ assert C.NK_write_config_struct(config_st[0], DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
+
+ # use structs: read II
+ err = C.NK_read_config_struct(config_st)
+ assert err == 0
+ assert config_st.numlock == 3
+ assert config_st.capslock == 1
+ assert config_st.scrolllock == 2
+ assert config_st.enable_user_password
+ assert not config_st.disable_user_password
+
# restore defaults and check
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
@@ -754,6 +747,13 @@ def test_get_serial_number(C):
print(('Serial number of the device: ', sn))
+@pytest.mark.status
+def test_get_serial_number_as_u32(C):
+ sn = C.NK_device_serial_number_as_u32()
+ assert sn > 0
+ print(('Serial number of the device (u32): ', sn))
+
+
@pytest.mark.otp
@pytest.mark.parametrize("secret", ['000001', '00'*10+'ff', '00'*19+'ff', '000102',
'00'*29+'ff', '00'*39+'ff', '002EF43F51AFA97BA2B46418768123C9E1809A5B' ])
@@ -996,31 +996,6 @@ def test_get_device_model(C):
# assert C.NK_get_device_model() != C.NK_DISCONNECTED
-@pytest.mark.firmware
-def test_bootloader_password_change_pro(C):
- skip_if_device_version_lower_than({'P': 11})
- assert C.NK_change_firmware_password_pro(b'zxcasd', b'zxcasd') == DeviceErrorCode.WRONG_PASSWORD
-
- assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK
- assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
-
-
-@pytest.mark.firmware
-def test_bootloader_run_pro(C):
- skip_if_device_version_lower_than({'P': 11})
- assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD
- # Not enabled due to lack of side-effect removal at this point
- # assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
-
-
-@pytest.mark.firmware
-def test_bootloader_password_change_pro_too_long(C):
- skip_if_device_version_lower_than({'P': 11})
- long_string = b'a' * 100
- assert C.NK_change_firmware_password_pro(long_string, long_string) == LibraryErrors.TOO_LONG_STRING
- assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, long_string) == LibraryErrors.TOO_LONG_STRING
-
-
@pytest.mark.otp
@pytest.mark.parametrize('counter_mid', [10**3-1, 10**4-1, 10**7-1, 10**8-10, 2**16, 2**31-1, 2**32-1, 2**33, 2**50, 2**60, 2**63]) # 2**64-1
def test_HOTP_counter_getter(C, counter_mid: int):
diff --git a/unittest/test_pro_bootloader.py b/unittest/test_pro_bootloader.py
new file mode 100644
index 0000000..4cb7470
--- /dev/null
+++ b/unittest/test_pro_bootloader.py
@@ -0,0 +1,71 @@
+import pytest
+
+from conftest import skip_if_device_version_lower_than, library_device_reconnect
+from constants import DefaultPasswords, DeviceErrorCode, LibraryErrors
+from helpers import helper_populate_device, helper_check_device_for_data
+
+
+@pytest.mark.firmware
+def test_bootloader_password_change_pro_length(C):
+ skip_if_device_version_lower_than({'P': 11})
+
+ # Test whether the correct password is set
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+ # Change to the longest possible password
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_LONG) == DeviceErrorCode.STATUS_OK
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_LONG, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+ # Use longer or shorter passwords than possible
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TOO_LONG) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TOO_SHORT) == DeviceErrorCode.WRONG_PASSWORD
+
+
+
+@pytest.mark.firmware
+def test_bootloader_password_change_pro(C):
+ skip_if_device_version_lower_than({'P': 11})
+ assert C.NK_change_firmware_password_pro(b'zxcasd', b'zxcasd') == DeviceErrorCode.WRONG_PASSWORD
+
+ # Revert effects of broken test run, if needed
+ C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE)
+
+ # Change to the same password
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+ # Change password
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK
+
+
+@pytest.mark.firmware
+def test_bootloader_run_pro_wrong_password(C):
+ skip_if_device_version_lower_than({'P': 11})
+ assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD
+
+
+@pytest.mark.skip_by_default
+@pytest.mark.firmware
+def test_bootloader_run_pro_real(C):
+ skip_if_device_version_lower_than({'P': 11})
+ # Not enabled due to lack of side-effect removal at this point
+ assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_DISCONNECTED
+
+
+@pytest.mark.firmware
+def test_bootloader_password_change_pro_too_long(C):
+ skip_if_device_version_lower_than({'P': 11})
+ long_string = b'a' * 100
+ assert C.NK_change_firmware_password_pro(long_string, long_string) == LibraryErrors.TOO_LONG_STRING
+ assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, long_string) == LibraryErrors.TOO_LONG_STRING
+
+
+@pytest.mark.skip_by_default
+@pytest.mark.firmware
+def test_bootloader_data_rention(C):
+ skip_if_device_version_lower_than({'P': 11})
+
+ assert helper_populate_device(C)
+ assert C.NK_enable_firmware_update_pro(DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_DISCONNECTED
+ input('Please press ENTER after uploading new firmware to the device')
+ C = library_device_reconnect(C)
+ assert helper_check_device_for_data(C)
+
diff --git a/unittest/test_storage.py b/unittest/test_storage.py
index 0f960cc..a435a15 100644
--- a/unittest/test_storage.py
+++ b/unittest/test_storage.py
@@ -23,8 +23,8 @@ import pprint
import pytest
from conftest import skip_if_device_version_lower_than
-from constants import DefaultPasswords, DeviceErrorCode, bb
-from misc import gs, wait, ffi
+from constants import DefaultPasswords, DeviceErrorCode
+from misc import gs, wait, ffi, bb
pprint = pprint.PrettyPrinter(indent=4).pprint