summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml53
-rw-r--r--CMakeLists.txt1
-rw-r--r--NK_C_API.cc23
-rw-r--r--NK_C_API.h33
-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--python3_bindings_example.py157
-rw-r--r--unittest/requirements.txt1
-rw-r--r--unittest/test_memory.c63
-rw-r--r--unittest/test_offline.cc4
-rw-r--r--unittest/test_pro.py7
15 files changed, 524 insertions, 50 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/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 d993671..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 */
@@ -232,6 +235,10 @@ 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;
@@ -257,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;
@@ -320,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, "");
}
@@ -469,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([&]() {
@@ -812,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 6aab7ca..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
};
/**
@@ -353,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);
@@ -412,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
@@ -485,6 +500,7 @@ extern "C" {
/**
* 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;
@@ -496,6 +512,12 @@ 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
@@ -677,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 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 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/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/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_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 e61d8bf..0d8c536 100644
--- a/unittest/test_pro.py
+++ b/unittest/test_pro.py
@@ -728,6 +728,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' ])