/*
* 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 .
*
* SPDX-License-Identifier: LGPL-3.0
*/
#include
#include
#include "libnitrokey/NitrokeyManager.h"
#include "libnitrokey/LibraryException.h"
#include
#include
#include
#include "libnitrokey/misc.h"
#include
#include "libnitrokey/cxx_semantics.h"
#include "libnitrokey/misc.h"
#include
#include
std::mutex nitrokey::proto::send_receive_mtx;
namespace nitrokey{
std::mutex mex_dev_com_manager;
#ifndef strndup
#ifdef _WIN32
#pragma message "Using own strndup"
char * strndup(const char* str, size_t maxlen){
size_t len = strnlen(str, maxlen);
char* dup = (char *) malloc(len + 1);
memcpy(dup, str, len);
dup[len] = 0;
return dup;
}
#endif
#endif
using nitrokey::misc::strcpyT;
template
typename T::CommandPayload get_payload(){
//Create, initialize and return by value command payload
typename T::CommandPayload st;
bzero(&st, sizeof(st));
return st;
}
// package type to auth, auth type [Authorize,UserAuthorize]
template
void NitrokeyManager::authorize_packet(T &package, const char *admin_temporary_password, shared_ptr device){
if (!is_authorization_command_supported()){
LOG("Authorization command not supported, skipping", Loglevel::WARNING);
}
auto auth = get_payload();
strcpyT(auth.temporary_password, admin_temporary_password);
auth.crc_to_authorize = S::CommandTransaction::getCRC(package);
A::CommandTransaction::run(device, auth);
}
shared_ptr NitrokeyManager::_instance = nullptr;
NitrokeyManager::NitrokeyManager() : device(nullptr)
{
set_debug(false);
}
NitrokeyManager::~NitrokeyManager() {
std::lock_guard lock(mex_dev_com_manager);
for (auto d : connected_devices){
if (d.second == nullptr) continue;
d.second->disconnect();
connected_devices[d.first] = nullptr;
}
}
bool NitrokeyManager::set_current_device_speed(int retry_delay, int send_receive_delay){
if (retry_delay < 20 || send_receive_delay < 20){
LOG("Delay set too low: " + to_string(retry_delay) +" "+ to_string(send_receive_delay), Loglevel::WARNING);
return false;
}
std::lock_guard lock(mex_dev_com_manager);
if(device == nullptr) {
return false;
}
device->set_receiving_delay(std::chrono::duration(send_receive_delay));
device->set_retry_delay(std::chrono::duration(retry_delay));
return true;
}
std::vector NitrokeyManager::list_devices(){
std::lock_guard lock(mex_dev_com_manager);
return Device::enumerate();
}
std::vector NitrokeyManager::list_devices_by_cpuID(){
using misc::toHex;
//disconnect default device
disconnect();
std::lock_guard lock(mex_dev_com_manager);
LOGD1("Disconnecting registered devices");
for (auto & kv : connected_devices_byID){
if (kv.second != nullptr)
kv.second->disconnect();
}
connected_devices_byID.clear();
LOGD1("Enumerating devices");
std::vector res;
const auto v = Device::enumerate();
LOGD1("Discovering IDs");
for (auto & i: v){
if (i.m_deviceModel != DeviceModel::STORAGE)
continue;
auto p = i.m_path;
auto d = make_shared();
LOGD1( std::string("Found: ") + p );
d->set_path(p);
try{
if (d->connect()){
device = d;
std::string id;
try {
const auto status = get_status_storage();
const auto sc_id = toHex(status.ActiveSmartCardID_u32);
const auto sd_id = toHex(status.ActiveSD_CardID_u32);
id += sc_id + ":" + sd_id;
id += "_p_" + p;
}
catch (const LongOperationInProgressException &e) {
LOGD1(std::string("Long operation in progress, setting ID to: ") + p);
id = p;
}
connected_devices_byID[id] = d;
res.push_back(id);
LOGD1( std::string("Found: ") + p + " => " + id);
} else{
LOGD1( std::string("Could not connect to: ") + p);
}
}
catch (const DeviceCommunicationException &e){
LOGD1( std::string("Exception encountered: ") + p);
}
}
return res;
}
bool NitrokeyManager::connect_with_ID(const std::string id) {
std::lock_guard lock(mex_dev_com_manager);
auto position = connected_devices_byID.find(id);
if (position == connected_devices_byID.end()) {
LOGD1(std::string("Could not find device ")+id + ". Refresh devices list with list_devices_by_cpuID().");
return false;
}
auto d = connected_devices_byID[id];
device = d;
current_device_id = id;
//validate connection
try{
get_status();
}
catch (const LongOperationInProgressException &){
//ignore
}
catch (const DeviceCommunicationException &){
d->disconnect();
current_device_id = "";
connected_devices_byID[id] = nullptr;
connected_devices_byID.erase(position);
return false;
}
nitrokey::log::Log::setPrefix(id);
LOGD1("Device successfully changed");
return true;
}
/**
* Connects device to path.
* Assumes devices are not being disconnected and caches connections (param cache_connections).
* @param path os-dependent device path
* @return false, when could not connect, true otherwise
*/
bool NitrokeyManager::connect_with_path(std::string path) {
const bool cache_connections = false;
std::lock_guard lock(mex_dev_com_manager);
if (cache_connections){
if(connected_devices.find(path) != connected_devices.end()
&& connected_devices[path] != nullptr) {
device = connected_devices[path];
return true;
}
}
auto info_ptr = hid_enumerate(NITROKEY_VID, 0);
auto first_info_ptr = info_ptr;
if (!info_ptr)
return false;
misc::Option model;
while (info_ptr && !model.has_value()) {
if (path == std::string(info_ptr->path)) {
model = product_id_to_model(info_ptr->product_id);
}
info_ptr = info_ptr->next;
}
hid_free_enumeration(first_info_ptr);
if (!model.has_value())
return false;
auto p = Device::create(model.value());
if (!p)
return false;
p->set_path(path);
if(!p->connect()) return false;
if(cache_connections){
connected_devices [path] = p;
}
device = p; //previous device will be disconnected automatically
current_device_id = path;
nitrokey::log::Log::setPrefix(path);
LOGD1("Device successfully changed");
return true;
}
bool NitrokeyManager::connect() {
std::lock_guard lock(mex_dev_com_manager);
vector< shared_ptr > devices = { make_shared(), make_shared() };
bool connected = false;
for( auto & d : devices ){
if (d->connect()){
device = std::shared_ptr(d);
connected = true;
}
}
return connected;
}
void NitrokeyManager::set_log_function(std::function log_function){
static nitrokey::log::FunctionalLogHandler handler(log_function);
nitrokey::log::Log::instance().set_handler(&handler);
}
bool NitrokeyManager::set_default_commands_delay(int delay){
if (delay < 20){
LOG("Delay set too low: " + to_string(delay), Loglevel::WARNING);
return false;
}
Device::set_default_device_speed(delay);
return true;
}
bool NitrokeyManager::connect(const char *device_model) {
std::lock_guard lock(mex_dev_com_manager);
LOG(__FUNCTION__, nitrokey::log::Loglevel::DEBUG_L2);
switch (device_model[0]){
case 'P':
device = make_shared();
break;
case 'S':
device = make_shared();
break;
default:
throw std::runtime_error("Unknown model");
}
return device->connect();
}
bool NitrokeyManager::connect(device::DeviceModel device_model) {
const char *model_string;
switch (device_model) {
case device::DeviceModel::PRO:
model_string = "P";
break;
case device::DeviceModel::STORAGE:
model_string = "S";
break;
default:
throw std::runtime_error("Unknown model");
}
return connect(model_string);
}
shared_ptr NitrokeyManager::instance() {
static std::mutex mutex;
std::lock_guard lock(mutex);
if (_instance == nullptr){
_instance = make_shared();
}
return _instance;
}
bool NitrokeyManager::disconnect() {
std::lock_guard lock(mex_dev_com_manager);
return _disconnect_no_lock();
}
bool NitrokeyManager::_disconnect_no_lock() {
//do not use directly without locked mutex,
//used by could_be_enumerated, disconnect
if (device == nullptr){
return false;
}
const auto res = device->disconnect();
device = nullptr;
return res;
}
bool NitrokeyManager::is_connected() throw(){
std::lock_guard lock(mex_dev_com_manager);
if(device != nullptr){
auto connected = device->could_be_enumerated();
if(connected){
return true;
} else {
_disconnect_no_lock();
return false;
}
}
return false;
}
bool NitrokeyManager::could_current_device_be_enumerated() {
std::lock_guard lock(mex_dev_com_manager);
if (device != nullptr) {
return device->could_be_enumerated();
}
return false;
}
void NitrokeyManager::set_loglevel(int loglevel) {
loglevel = max(loglevel, static_cast(Loglevel::ERROR));
loglevel = min(loglevel, static_cast(Loglevel::DEBUG_L2));
Log::instance().set_loglevel(static_cast(loglevel));
}
void NitrokeyManager::set_loglevel(Loglevel loglevel) {
Log::instance().set_loglevel(loglevel);
}
void NitrokeyManager::set_debug(bool state) {
if (state){
Log::instance().set_loglevel(Loglevel::DEBUG);
} else {
Log::instance().set_loglevel(Loglevel::ERROR);
}
}
string NitrokeyManager::get_serial_number() {
if (device == nullptr) { return ""; };
switch (device->get_device_model()) {
case DeviceModel::PRO: {
auto response = GetStatus::CommandTransaction::run(device);
return nitrokey::misc::toHex(response.data().card_serial_u32);
}
break;
case DeviceModel::STORAGE:
{
auto response = stick20::GetDeviceStatus::CommandTransaction::run(device);
return nitrokey::misc::toHex(response.data().ActiveSmartCardID_u32);
}
break;
}
return "NA";
}
stick10::GetStatus::ResponsePayload NitrokeyManager::get_status(){
try{
auto response = GetStatus::CommandTransaction::run(device);
auto data = response.data();
if (device != nullptr && device->get_device_model() == DeviceModel::STORAGE) {
// The GET_STATUS command does not work for the Nitrokey Storage,
// see https://github.com/Nitrokey/nitrokey-storage-firmware/issues/96
// Therefore, we have to use the GET_DEVICE_STATUS command to query
// the correct serial number and firmware version.
auto response2 = stick20::GetDeviceStatus::CommandTransaction::run(device);
auto data2 = response2.data();
data.firmware_version_st.major = data2.versionInfo.major;
data.firmware_version_st.minor = data2.versionInfo.minor;
data.card_serial_u32 = data2.ActiveSmartCardID_u32;
}
return data;
}
catch (DeviceSendingFailure &e){
// disconnect();
throw;
}
}
string NitrokeyManager::get_status_as_string() {
auto response = GetStatus::CommandTransaction::run(device);
return response.data().dissect();
}
string getFilledOTPCode(uint32_t code, bool use_8_digits){
stringstream s;
s << std::right << std::setw(use_8_digits ? 8 : 6) << std::setfill('0') << code;
return s.str();
}
string NitrokeyManager::get_HOTP_code(uint8_t slot_number, const char *user_temporary_password) {
if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
if (is_authorization_command_supported()){
auto gh = get_payload();
gh.slot_number = get_internal_slot_number_for_hotp(slot_number);
if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
authorize_packet(gh, user_temporary_password, device);
}
auto resp = GetHOTP::CommandTransaction::run(device, gh);
return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
} else {
auto gh = get_payload();
gh.slot_number = get_internal_slot_number_for_hotp(slot_number);
if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0) {
strcpyT(gh.temporary_user_password, user_temporary_password);
}
auto resp = stick10_08::GetHOTP::CommandTransaction::run(device, gh);
return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
}
return "";
}
bool NitrokeyManager::is_internal_hotp_slot_number(uint8_t slot_number) const { return slot_number < 0x20; }
bool NitrokeyManager::is_valid_hotp_slot_number(uint8_t slot_number) const { return slot_number < 3; }
bool NitrokeyManager::is_valid_totp_slot_number(uint8_t slot_number) const { return slot_number < 0x10-1; } //15
uint8_t NitrokeyManager::get_internal_slot_number_for_totp(uint8_t slot_number) const { return (uint8_t) (0x20 + slot_number); }
uint8_t NitrokeyManager::get_internal_slot_number_for_hotp(uint8_t slot_number) const { return (uint8_t) (0x10 + slot_number); }
string NitrokeyManager::get_TOTP_code(uint8_t slot_number, uint64_t challenge, uint64_t last_totp_time,
uint8_t last_interval,
const char *user_temporary_password) {
if(!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_totp(slot_number);
if (is_authorization_command_supported()){
auto gt = get_payload();
gt.slot_number = slot_number;
gt.challenge = challenge;
gt.last_interval = last_interval;
gt.last_totp_time = last_totp_time;
if(user_temporary_password != nullptr && strlen(user_temporary_password)!=0){ //FIXME use string instead of strlen
authorize_packet(gt, user_temporary_password, device);
}
auto resp = GetTOTP::CommandTransaction::run(device, gt);
return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
} else {
auto gt = get_payload();
strcpyT(gt.temporary_user_password, user_temporary_password);
gt.slot_number = slot_number;
auto resp = stick10_08::GetTOTP::CommandTransaction::run(device, gt);
return getFilledOTPCode(resp.data().code, resp.data().use_8_digits);
}
return "";
}
bool NitrokeyManager::erase_slot(uint8_t slot_number, const char *temporary_password) {
if (is_authorization_command_supported()){
auto p = get_payload();
p.slot_number = slot_number;
authorize_packet(p, temporary_password, device);
auto resp = EraseSlot::CommandTransaction::run(device,p);
} else {
auto p = get_payload();
p.slot_number = slot_number;
strcpyT(p.temporary_admin_password, temporary_password);
auto resp = stick10_08::EraseSlot::CommandTransaction::run(device,p);
}
return true;
}
bool NitrokeyManager::erase_hotp_slot(uint8_t slot_number, const char *temporary_password) {
if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_hotp(slot_number);
return erase_slot(slot_number, temporary_password);
}
bool NitrokeyManager::erase_totp_slot(uint8_t slot_number, const char *temporary_password) {
if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_totp(slot_number);
return erase_slot(slot_number, temporary_password);
}
template
void vector_copy_ranged(T& dest, std::vector &vec, size_t begin, size_t elements_to_copy){
const size_t d_size = sizeof(dest);
if(d_size < elements_to_copy){
throw TargetBufferSmallerThanSource(elements_to_copy, d_size);
}
std::fill(dest, dest+d_size, 0);
std::copy(vec.begin() + begin, vec.begin() +begin + elements_to_copy, dest);
}
template
void vector_copy(T& dest, std::vector &vec){
const size_t d_size = sizeof(dest);
if(d_size < vec.size()){
throw TargetBufferSmallerThanSource(vec.size(), d_size);
}
std::fill(dest, dest+d_size, 0);
std::copy(vec.begin(), vec.end(), dest);
}
bool NitrokeyManager::write_HOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint64_t hotp_counter,
bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
const char *temporary_password) {
if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
int internal_slot_number = get_internal_slot_number_for_hotp(slot_number);
if (is_authorization_command_supported()){
write_HOTP_slot_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID,
token_ID, temporary_password);
} else {
write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, hotp_counter, use_8_digits, use_enter, use_tokenID,
token_ID, temporary_password);
}
return true;
}
void NitrokeyManager::write_HOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret,
uint64_t hotp_counter, bool use_8_digits, bool use_enter,
bool use_tokenID, const char *token_ID, const char *temporary_password) {
auto payload = get_payload();
payload.slot_number = slot_number;
auto secret_bin = misc::hex_string_to_byte(secret);
vector_copy(payload.slot_secret, secret_bin);
strcpyT(payload.slot_name, slot_name);
strcpyT(payload.slot_token_id, token_ID);
switch (device->get_device_model() ){
case DeviceModel::PRO: {
payload.slot_counter = hotp_counter;
break;
}
case DeviceModel::STORAGE: {
string counter = to_string(hotp_counter);
strcpyT(payload.slot_counter_s, counter.c_str());
break;
}
default:
LOG(string(__FILE__) + to_string(__LINE__) +
string(__FUNCTION__) + string(" Unhandled device model for HOTP")
, Loglevel::DEBUG);
break;
}
payload.use_8_digits = use_8_digits;
payload.use_enter = use_enter;
payload.use_tokenID = use_tokenID;
authorize_packet(payload, temporary_password, device);
auto resp = WriteToHOTPSlot::CommandTransaction::run(device, payload);
}
bool NitrokeyManager::write_TOTP_slot(uint8_t slot_number, const char *slot_name, const char *secret, uint16_t time_window,
bool use_8_digits, bool use_enter, bool use_tokenID, const char *token_ID,
const char *temporary_password) {
if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
int internal_slot_number = get_internal_slot_number_for_totp(slot_number);
if (is_authorization_command_supported()){
write_TOTP_slot_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID,
token_ID, temporary_password);
} else {
write_OTP_slot_no_authorize(internal_slot_number, slot_name, secret, time_window, use_8_digits, use_enter, use_tokenID,
token_ID, temporary_password);
}
return true;
}
void NitrokeyManager::write_OTP_slot_no_authorize(uint8_t internal_slot_number, const char *slot_name,
const char *secret,
uint64_t counter_or_interval, bool use_8_digits, bool use_enter,
bool use_tokenID, const char *token_ID,
const char *temporary_password) const {
auto payload2 = get_payload();
strcpyT(payload2.temporary_admin_password, temporary_password);
strcpyT(payload2.data, slot_name);
payload2.setTypeName();
stick10_08::SendOTPData::CommandTransaction::run(device, payload2);
payload2.setTypeSecret();
payload2.id = 0;
auto secret_bin = misc::hex_string_to_byte(secret);
auto remaining_secret_length = secret_bin.size();
const auto maximum_OTP_secret_size = 40;
if(remaining_secret_length > maximum_OTP_secret_size){
throw TargetBufferSmallerThanSource(remaining_secret_length, maximum_OTP_secret_size);
}
while (remaining_secret_length>0){
const auto bytesToCopy = std::min(sizeof(payload2.data), remaining_secret_length);
const auto start = secret_bin.size() - remaining_secret_length;
memset(payload2.data, 0, sizeof(payload2.data));
vector_copy_ranged(payload2.data, secret_bin, start, bytesToCopy);
stick10_08::SendOTPData::CommandTransaction::run(device, payload2);
remaining_secret_length -= bytesToCopy;
payload2.id++;
}
auto payload = get_payload();
strcpyT(payload.temporary_admin_password, temporary_password);
strcpyT(payload.slot_token_id, token_ID);
payload.use_8_digits = use_8_digits;
payload.use_enter = use_enter;
payload.use_tokenID = use_tokenID;
payload.slot_counter_or_interval = counter_or_interval;
payload.slot_number = internal_slot_number;
stick10_08::WriteToOTPSlot::CommandTransaction::run(device, payload);
}
void NitrokeyManager::write_TOTP_slot_authorize(uint8_t slot_number, const char *slot_name, const char *secret,
uint16_t time_window, bool use_8_digits, bool use_enter,
bool use_tokenID, const char *token_ID, const char *temporary_password) {
auto payload = get_payload();
payload.slot_number = slot_number;
auto secret_bin = misc::hex_string_to_byte(secret);
vector_copy(payload.slot_secret, secret_bin);
strcpyT(payload.slot_name, slot_name);
strcpyT(payload.slot_token_id, token_ID);
payload.slot_interval = time_window; //FIXME naming
payload.use_8_digits = use_8_digits;
payload.use_enter = use_enter;
payload.use_tokenID = use_tokenID;
authorize_packet(payload, temporary_password, device);
auto resp = WriteToTOTPSlot::CommandTransaction::run(device, payload);
}
char * NitrokeyManager::get_totp_slot_name(uint8_t slot_number) {
if (!is_valid_totp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_totp(slot_number);
return get_slot_name(slot_number);
}
char * NitrokeyManager::get_hotp_slot_name(uint8_t slot_number) {
if (!is_valid_hotp_slot_number(slot_number)) throw InvalidSlotException(slot_number);
slot_number = get_internal_slot_number_for_hotp(slot_number);
return get_slot_name(slot_number);
}
static const int max_string_field_length = 2*1024; //storage's status string is ~1k
char * NitrokeyManager::get_slot_name(uint8_t slot_number) {
auto payload = get_payload();
payload.slot_number = slot_number;
auto resp = GetSlotName::CommandTransaction::run(device, payload);
return strndup((const char *) resp.data().slot_name, max_string_field_length);
}
bool NitrokeyManager::first_authenticate(const char *pin, const char *temporary_password) {
auto authreq = get_payload();
strcpyT(authreq.card_password, pin);
strcpyT(authreq.temporary_password, temporary_password);
FirstAuthenticate::CommandTransaction::run(device, authreq);
return true;
}
bool NitrokeyManager::set_time(uint64_t time) {
auto p = get_payload();
p.reset = 1;
p.time = time;
SetTime::CommandTransaction::run(device, p);
return false;
}
void NitrokeyManager::set_time_soft(uint64_t time) {
auto p = get_payload();
p.reset = 0;
p.time = time;
SetTime::CommandTransaction::run(device, p);
}
bool NitrokeyManager::get_time(uint64_t time) {
set_time_soft(time);
return true;
}
void NitrokeyManager::change_user_PIN(const char *current_PIN, const char *new_PIN) {
change_PIN_general(current_PIN, new_PIN);
}
void NitrokeyManager::change_admin_PIN(const char *current_PIN, const char *new_PIN) {
change_PIN_general(current_PIN, new_PIN);
}
template
void NitrokeyManager::change_PIN_general(const char *current_PIN, const char *new_PIN) {
switch (device->get_device_model()){
case DeviceModel::PRO:
{
auto p = get_payload();
strcpyT(p.old_pin, current_PIN);
strcpyT(p.new_pin, new_PIN);
ProCommand::CommandTransaction::run(device, p);
}
break;
//in Storage change admin/user pin is divided to two commands with 20 chars field len
case DeviceModel::STORAGE:
{
auto p = get_payload();
strcpyT(p.password, current_PIN);
p.set_kind(StoKind);
auto p2 = get_payload();
strcpyT(p2.password, new_PIN);
p2.set_kind(StoKind);
ChangeAdminUserPin20Current::CommandTransaction::run(device, p);
ChangeAdminUserPin20New::CommandTransaction::run(device, p2);
}
break;
}
}
void NitrokeyManager::enable_password_safe(const char *user_pin) {
//The following command will cancel enabling PWS if it is not supported
auto a = get_payload();
strcpyT(a.user_password, user_pin);
IsAESSupported::CommandTransaction::run(device, a);
auto p = get_payload();
strcpyT(p.user_password, user_pin);
EnablePasswordSafe::CommandTransaction::run(device, p);
}
vector NitrokeyManager::get_password_safe_slot_status() {
auto responsePayload = GetPasswordSafeSlotStatus::CommandTransaction::run(device);
vector v = vector(responsePayload.data().password_safe_status,
responsePayload.data().password_safe_status
+ sizeof(responsePayload.data().password_safe_status));
return v;
}
uint8_t NitrokeyManager::get_user_retry_count() {
if(device->get_device_model() == DeviceModel::STORAGE){
stick20::GetDeviceStatus::CommandTransaction::run(device);
}
auto response = GetUserPasswordRetryCount::CommandTransaction::run(device);
return response.data().password_retry_count;
}
uint8_t NitrokeyManager::get_admin_retry_count() {
if(device->get_device_model() == DeviceModel::STORAGE){
stick20::GetDeviceStatus::CommandTransaction::run(device);
}
auto response = GetPasswordRetryCount::CommandTransaction::run(device);
return response.data().password_retry_count;
}
void NitrokeyManager::lock_device() {
LockDevice::CommandTransaction::run(device);
}
char * NitrokeyManager::get_password_safe_slot_name(uint8_t slot_number) {
if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
auto p = get_payload();
p.slot_number = slot_number;
auto response = GetPasswordSafeSlotName::CommandTransaction::run(device, p);
return strndup((const char *) response.data().slot_name, max_string_field_length);
}
bool NitrokeyManager::is_valid_password_safe_slot_number(uint8_t slot_number) const { return slot_number < 16; }
char * NitrokeyManager::get_password_safe_slot_login(uint8_t slot_number) {
if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
auto p = get_payload();
p.slot_number = slot_number;
auto response = GetPasswordSafeSlotLogin::CommandTransaction::run(device, p);
return strndup((const char *) response.data().slot_login, max_string_field_length);
}
char * NitrokeyManager::get_password_safe_slot_password(uint8_t slot_number) {
if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
auto p = get_payload();
p.slot_number = slot_number;
auto response = GetPasswordSafeSlotPassword::CommandTransaction::run(device, p);
return strndup((const char *) response.data().slot_password, max_string_field_length); //FIXME use secure way
}
void NitrokeyManager::write_password_safe_slot(uint8_t slot_number, const char *slot_name, const char *slot_login,
const char *slot_password) {
if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
auto p = get_payload();
p.slot_number = slot_number;
strcpyT(p.slot_name, slot_name);
strcpyT(p.slot_password, slot_password);
SetPasswordSafeSlotData::CommandTransaction::run(device, p);
auto p2 = get_payload();
p2.slot_number = slot_number;
strcpyT(p2.slot_login_name, slot_login);
SetPasswordSafeSlotData2::CommandTransaction::run(device, p2);
}
void NitrokeyManager::erase_password_safe_slot(uint8_t slot_number) {
if (!is_valid_password_safe_slot_number(slot_number)) throw InvalidSlotException(slot_number);
auto p = get_payload();
p.slot_number = slot_number;
ErasePasswordSafeSlot::CommandTransaction::run(device, p);
}
void NitrokeyManager::user_authenticate(const char *user_password, const char *temporary_password) {
auto p = get_payload();
strcpyT(p.card_password, user_password);
strcpyT(p.temporary_password, temporary_password);
UserAuthenticate::CommandTransaction::run(device, p);
}
void NitrokeyManager::build_aes_key(const char *admin_password) {
switch (device->get_device_model()) {
case DeviceModel::PRO: {
auto p = get_payload();
strcpyT(p.admin_password, admin_password);
BuildAESKey::CommandTransaction::run(device, p);
break;
}
case DeviceModel::STORAGE : {
auto p = get_payload();
strcpyT(p.password, admin_password);
p.set_defaults();
stick20::CreateNewKeys::CommandTransaction::run(device, p);
break;
}
}
}
void NitrokeyManager::factory_reset(const char *admin_password) {
auto p = get_payload();
strcpyT(p.admin_password, admin_password);
FactoryReset::CommandTransaction::run(device, p);
}
void NitrokeyManager::unlock_user_password(const char *admin_password, const char *new_user_password) {
switch (device->get_device_model()){
case DeviceModel::PRO: {
auto p = get_payload();
strcpyT(p.admin_password, admin_password);
strcpyT(p.user_new_password, new_user_password);
stick10::UnlockUserPassword::CommandTransaction::run(device, p);
break;
}
case DeviceModel::STORAGE : {
auto p2 = get_payload();
p2.set_defaults();
strcpyT(p2.password, admin_password);
ChangeAdminUserPin20Current::CommandTransaction::run(device, p2);
auto p3 = get_payload();
p3.set_defaults();
strcpyT(p3.password, new_user_password);
stick20::UnlockUserPin::CommandTransaction::run(device, p3);
break;
}
}
}
void NitrokeyManager::write_config(uint8_t numlock, uint8_t capslock, uint8_t scrolllock, bool enable_user_password,
bool delete_user_password, const char *admin_temporary_password) {
auto p = get_payload();
p.numlock = numlock;
p.capslock = capslock;
p.scrolllock = scrolllock;
p.enable_user_password = static_cast(enable_user_password ? 1 : 0);
p.delete_user_password = static_cast(delete_user_password ? 1 : 0);
if (is_authorization_command_supported()){
authorize_packet(p, admin_temporary_password, device);
} else {
strcpyT(p.temporary_admin_password, admin_temporary_password);
}
stick10_08::WriteGeneralConfig::CommandTransaction::run(device, p);
}
vector NitrokeyManager::read_config() {
auto responsePayload = GetStatus::CommandTransaction::run(device);
vector v = vector(responsePayload.data().general_config,
responsePayload.data().general_config+sizeof(responsePayload.data().general_config));
return v;
}
bool NitrokeyManager::is_authorization_command_supported(){
//authorization command is supported for versions equal or below:
auto m = std::unordered_map({
{DeviceModel::PRO, 7},
{DeviceModel::STORAGE, 53},
});
return get_minor_firmware_version() <= m[device->get_device_model()];
}
bool NitrokeyManager::is_320_OTP_secret_supported(){
// 320 bit OTP secret is supported by version bigger or equal to:
auto m = std::unordered_map({
{DeviceModel::PRO, 8},
{DeviceModel::STORAGE, 54},
});
return get_minor_firmware_version() >= m[device->get_device_model()];
}
DeviceModel NitrokeyManager::get_connected_device_model() const{
if (device == nullptr){
throw DeviceNotConnected("device not connected");
}
return device->get_device_model();
}
bool NitrokeyManager::is_smartcard_in_use(){
try{
stick20::CheckSmartcardUsage::CommandTransaction::run(device);
}
catch(const CommandFailedException & e){
return e.reason_smartcard_busy();
}
return false;
}
uint8_t NitrokeyManager::get_minor_firmware_version(){
switch(device->get_device_model()){
case DeviceModel::PRO:{
auto status_p = GetStatus::CommandTransaction::run(device);
return status_p.data().firmware_version_st.minor; //7 or 8
}
case DeviceModel::STORAGE:{
auto status = stick20::GetDeviceStatus::CommandTransaction::run(device);
auto test_firmware = status.data().versionInfo.build_iteration != 0;
if (test_firmware)
LOG("Development firmware detected. Increasing minor version number.", nitrokey::log::Loglevel::WARNING);
return status.data().versionInfo.minor + (test_firmware? 1 : 0);
}
}
return 0;
}
uint8_t NitrokeyManager::get_major_firmware_version(){
switch(device->get_device_model()){
case DeviceModel::PRO:{
auto status_p = GetStatus::CommandTransaction::run(device);
return status_p.data().firmware_version_st.major; //0
}
case DeviceModel::STORAGE:{
auto status = stick20::GetDeviceStatus::CommandTransaction::run(device);
return status.data().versionInfo.major;
}
}
return 0;
}
bool NitrokeyManager::is_AES_supported(const char *user_password) {
auto a = get_payload();
strcpyT(a.user_password, user_password);
IsAESSupported::CommandTransaction::run(device, a);
return true;
}
//storage commands
void NitrokeyManager::send_startup(uint64_t seconds_from_epoch){
auto p = get_payload();
// p.set_defaults(); //set current time
p.localtime = seconds_from_epoch;
stick20::SendStartup::CommandTransaction::run(device, p);
}
void NitrokeyManager::unlock_encrypted_volume(const char* user_pin){
misc::execute_password_command(device, user_pin);
}
void NitrokeyManager::unlock_hidden_volume(const char* hidden_volume_password) {
misc::execute_password_command(device, hidden_volume_password);
}
void NitrokeyManager::set_encrypted_volume_read_only(const char* admin_pin) {
misc::execute_password_command(device, admin_pin);
}
void NitrokeyManager::set_encrypted_volume_read_write(const char* admin_pin) {
misc::execute_password_command(device, admin_pin);
}
//TODO check is encrypted volume unlocked before execution
//if not return library exception
void NitrokeyManager::create_hidden_volume(uint8_t slot_nr, uint8_t start_percent, uint8_t end_percent,
const char *hidden_volume_password) {
auto p = get_payload();
p.SlotNr_u8 = slot_nr;
p.StartBlockPercent_u8 = start_percent;
p.EndBlockPercent_u8 = end_percent;
strcpyT(p.HiddenVolumePassword_au8, hidden_volume_password);
stick20::SetupHiddenVolume::CommandTransaction::run(device, p);
}
void NitrokeyManager::set_unencrypted_read_only_admin(const char* admin_pin) {
//from v0.49, v0.52+ it needs Admin PIN
if (set_unencrypted_volume_rorw_pin_type_user()){
LOG("set_unencrypted_read_only_admin is not supported for this version of Storage device. "
"Please update firmware to v0.52+. Doing nothing.", nitrokey::log::Loglevel::WARNING);
return;
}
misc::execute_password_command(device, admin_pin);
}
void NitrokeyManager::set_unencrypted_read_only(const char *user_pin) {
//until v0.48 (incl. v0.50 and v0.51) User PIN was sufficient
LOG("set_unencrypted_read_only is deprecated. Use set_unencrypted_read_only_admin instead.",
nitrokey::log::Loglevel::WARNING);
if (!set_unencrypted_volume_rorw_pin_type_user()){
LOG("set_unencrypted_read_only is not supported for this version of Storage device. Doing nothing.",
nitrokey::log::Loglevel::WARNING);
return;
}
misc::execute_password_command(device, user_pin);
}
void NitrokeyManager::set_unencrypted_read_write_admin(const char* admin_pin) {
//from v0.49, v0.52+ it needs Admin PIN
if (set_unencrypted_volume_rorw_pin_type_user()){
LOG("set_unencrypted_read_write_admin is not supported for this version of Storage device. "
"Please update firmware to v0.52+. Doing nothing.", nitrokey::log::Loglevel::WARNING);
return;
}
misc::execute_password_command(device, admin_pin);
}
void NitrokeyManager::set_unencrypted_read_write(const char *user_pin) {
//until v0.48 (incl. v0.50 and v0.51) User PIN was sufficient
LOG("set_unencrypted_read_write is deprecated. Use set_unencrypted_read_write_admin instead.",
nitrokey::log::Loglevel::WARNING);
if (!set_unencrypted_volume_rorw_pin_type_user()){
LOG("set_unencrypted_read_write is not supported for this version of Storage device. Doing nothing.",
nitrokey::log::Loglevel::WARNING);
return;
}
misc::execute_password_command(device, user_pin);
}
bool NitrokeyManager::set_unencrypted_volume_rorw_pin_type_user(){
auto minor_firmware_version = get_minor_firmware_version();
return minor_firmware_version <= 48 || minor_firmware_version == 50 || minor_firmware_version == 51;
}
void NitrokeyManager::export_firmware(const char* admin_pin) {
misc::execute_password_command(device, admin_pin);
}
void NitrokeyManager::enable_firmware_update(const char* firmware_pin) {
misc::execute_password_command(device, firmware_pin);
}
void NitrokeyManager::clear_new_sd_card_warning(const char* admin_pin) {
misc::execute_password_command(device, admin_pin);
}
void NitrokeyManager::fill_SD_card_with_random_data(const char* admin_pin) {
auto p = get_payload();
p.set_defaults();
strcpyT(p.admin_pin, admin_pin);
stick20::FillSDCardWithRandomChars::CommandTransaction::run(device, p);
}
void NitrokeyManager::change_update_password(const char* current_update_password, const char* new_update_password) {
auto p = get_payload();
strcpyT(p.current_update_password, current_update_password);
strcpyT(p.new_update_password, new_update_password);
stick20::ChangeUpdatePassword::CommandTransaction::run(device, p);
}
char * NitrokeyManager::get_status_storage_as_string(){
auto p = stick20::GetDeviceStatus::CommandTransaction::run(device);
return strndup(p.data().dissect().c_str(), max_string_field_length);
}
stick20::DeviceConfigurationResponsePacket::ResponsePayload NitrokeyManager::get_status_storage(){
auto p = stick20::GetDeviceStatus::CommandTransaction::run(device);
return p.data();
}
char * NitrokeyManager::get_SD_usage_data_as_string(){
auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(device);
return strndup(p.data().dissect().c_str(), max_string_field_length);
}
std::pair NitrokeyManager::get_SD_usage_data(){
auto p = stick20::GetSDCardOccupancy::CommandTransaction::run(device);
return std::make_pair(p.data().WriteLevelMin, p.data().WriteLevelMax);
}
int NitrokeyManager::get_progress_bar_value(){
try{
stick20::GetDeviceStatus::CommandTransaction::run(device);
return -1;
}
catch (LongOperationInProgressException &e){
return e.progress_bar_value;
}
}
string NitrokeyManager::get_TOTP_code(uint8_t slot_number, const char *user_temporary_password) {
return get_TOTP_code(slot_number, 0, 0, 0, user_temporary_password);
}
/**
* Returns ReadSlot structure, describing OTP slot configuration. Always return binary counter -
* does the necessary conversion, if needed, to unify the behavior across Pro and Storage.
* @private For internal use only
* @param slot_number which OTP slot to use (usual format)
* @return ReadSlot structure
*/
stick10::ReadSlot::ResponsePayload NitrokeyManager::get_OTP_slot_data(const uint8_t slot_number) {
auto p = get_payload();
p.slot_number = slot_number;
p.data_format = stick10::ReadSlot::CounterFormat::BINARY; // ignored for devices other than Storage v0.54+
auto data = stick10::ReadSlot::CommandTransaction::run(device, p);
auto &payload = data.data();
// if fw <=v0.53 and asked binary - do the conversion from ASCII
if (device->get_device_model() == DeviceModel::STORAGE && get_minor_firmware_version() <= 53
&& is_internal_hotp_slot_number(slot_number))
{
//convert counter from string to ull
auto counter_s = std::string(payload.slot_counter_s, payload.slot_counter_s + sizeof(payload.slot_counter_s));
payload.slot_counter = std::stoull(counter_s);
}
return payload;
}
stick10::ReadSlot::ResponsePayload NitrokeyManager::get_TOTP_slot_data(const uint8_t slot_number) {
return get_OTP_slot_data(get_internal_slot_number_for_totp(slot_number));
}
stick10::ReadSlot::ResponsePayload NitrokeyManager::get_HOTP_slot_data(const uint8_t slot_number) {
return get_OTP_slot_data(get_internal_slot_number_for_hotp(slot_number));
}
void NitrokeyManager::lock_encrypted_volume() {
misc::execute_password_command(device, "");
}
void NitrokeyManager::lock_hidden_volume() {
misc::execute_password_command(device, "");
}
uint8_t NitrokeyManager::get_SD_card_size() {
auto data = stick20::ProductionTest::CommandTransaction::run(device);
return data.data().SD_Card_Size_u8;
}
const string NitrokeyManager::get_current_device_id() const {
return current_device_id;
}
void NitrokeyManager::wink(){
stick20::Wink::CommandTransaction::run(device);
};
stick20::ProductionTest::ResponsePayload NitrokeyManager::production_info(){
auto data = stick20::ProductionTest::CommandTransaction::run(device);
return data.data();
};
void NitrokeyManager::enable_firmware_update_pro(const char *firmware_pin) {
auto p = get_payload();
strcpyT(p.firmware_password, firmware_pin);
FirmwareUpdate::CommandTransaction::run(device, p);
}
void
NitrokeyManager::change_firmware_update_password_pro(const char *firmware_pin_current, const char *firmware_pin_new) {
auto p = get_payload();
strcpyT(p.firmware_password_current, firmware_pin_current);
strcpyT(p.firmware_password_new, firmware_pin_new);
FirmwarePasswordChange::CommandTransaction::run(device, p);
}
}