summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml53
-rw-r--r--NK_C_API.cc15
-rw-r--r--NK_C_API.h19
-rw-r--r--NitrokeyManager.cc48
-rw-r--r--README.md83
-rw-r--r--device.cc79
-rw-r--r--libnitrokey/NitrokeyManager.h1
-rw-r--r--libnitrokey/device.h18
-rw-r--r--libnitrokey/device_proto.h2
-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_multiple.py4
-rw-r--r--unittest/test_offline.cc4
-rw-r--r--unittest/test_pro.py87
-rw-r--r--unittest/test_pro_bootloader.py71
-rw-r--r--unittest/test_storage.py4
20 files changed, 648 insertions, 171 deletions
diff --git a/.travis.yml b/.travis.yml
index 6bf5438..52a10e3 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,67 @@ 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
+ 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
- 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/NK_C_API.cc b/NK_C_API.cc
index 1d3fa3a..ddf7bef 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 */
@@ -236,6 +239,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 +304,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, "");
}
@@ -791,6 +803,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..fe4cdd0 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
};
/**
@@ -327,7 +334,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 +393,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
diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc
index 6c26a43..329d155 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(){
@@ -552,6 +578,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;
@@ -713,6 +740,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>();
@@ -834,6 +862,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);
@@ -858,6 +887,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);
@@ -907,6 +937,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()];
@@ -916,6 +947,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()];
@@ -940,6 +972,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
@@ -956,6 +989,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 1352bc7..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,30 +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){
- if (pInfo->path == nullptr || pInfo->serial_number == nullptr) {
+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(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);
+ 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;
}
@@ -237,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 {};
}
@@ -308,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/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_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 a8df7cd..d25a50e 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
@@ -735,6 +704,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' ])
@@ -977,31 +953,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):
@@ -1094,4 +1045,4 @@ def test_OTP_all_rw(C):
this_loop_codes.append(('H', i, code))
all_codes.append(this_loop_codes)
from pprint import pprint
- pprint(all_codes) \ No newline at end of file
+ pprint(all_codes)
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