diff options
| -rw-r--r-- | CMakeLists.txt | 9 | ||||
| -rw-r--r-- | NK_C_API.cc | 101 | ||||
| -rw-r--r-- | NK_C_API.h | 118 | ||||
| -rw-r--r-- | NitrokeyManager.cc | 6 | ||||
| -rw-r--r-- | libnitrokey/NitrokeyManager.h | 12 | ||||
| -rw-r--r-- | libnitrokey/version.h | 33 | ||||
| -rw-r--r-- | unittest/conftest.py | 2 | ||||
| -rw-r--r-- | unittest/test_offline.cc | 10 | ||||
| -rw-r--r-- | unittest/test_pro.py | 49 | ||||
| -rw-r--r-- | version.cc.in | 37 | 
10 files changed, 340 insertions, 37 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dacb48..06ab448 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,9 @@ set(SOURCE_FILES      NitrokeyManager.cc      NK_C_API.h      NK_C_API.cc -        DeviceCommunicationExceptions.cpp) +    DeviceCommunicationExceptions.cpp +    ${CMAKE_CURRENT_BINARY_DIR}/version.cc +    )  set(BUILD_SHARED_LIBS ON CACHE BOOL "Build all libraries as shared")  add_library(nitrokey ${SOURCE_FILES}) @@ -115,6 +117,11 @@ IF (LOG_VOLATILE_DATA)  ENDIF() +# generate version.h +exec_program("git" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "describe --always" OUTPUT_VARIABLE PROJECT_VERSION_GIT) +configure_file("version.cc.in" "version.cc" @ONLY) + +  file(GLOB LIB_INCLUDES "libnitrokey/*.h" "NK_C_API.h")  install (FILES ${LIB_INCLUDES} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})  install (TARGETS nitrokey DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/NK_C_API.cc b/NK_C_API.cc index 8e005b8..01963fc 100644 --- a/NK_C_API.cc +++ b/NK_C_API.cc @@ -21,10 +21,13 @@  #include "NK_C_API.h"  #include <iostream> +#include <tuple>  #include "libnitrokey/NitrokeyManager.h"  #include <cstring>  #include "libnitrokey/LibraryException.h"  #include "libnitrokey/cxx_semantics.h" +#include "libnitrokey/stick20_commands.h" +#include "version.h"  #ifdef _MSC_VER  #ifdef _WIN32 @@ -52,11 +55,11 @@ T* duplicate_vector_and_clear(std::vector<T> &v){      return d;  } -template <typename T> -uint8_t * get_with_array_result(T func){ +template <typename R, typename T> +std::tuple<int, R> get_with_status(T func, R fallback) {      NK_last_command_status = 0;      try { -        return func(); +        return std::make_tuple(0, func());      }      catch (CommandFailedException & commandFailedException){          NK_last_command_status = commandFailedException.last_command_status; @@ -67,43 +70,26 @@ uint8_t * get_with_array_result(T func){      catch (const DeviceCommunicationException &deviceException){        NK_last_command_status = 256-deviceException.getType();      } -    return nullptr; +    return std::make_tuple(NK_last_command_status, fallback); +} + +template <typename T> +uint8_t * get_with_array_result(T func){ +    return std::get<1>(get_with_status<uint8_t*>(func, nullptr));  }  template <typename T>  char* get_with_string_result(T func){ -    NK_last_command_status = 0; -    try { -        return func(); -    } -    catch (CommandFailedException & commandFailedException){ -        NK_last_command_status = commandFailedException.last_command_status; +    auto result = std::get<1>(get_with_status<char*>(func, nullptr)); +    if (result == nullptr) { +        return strndup("", MAXIMUM_STR_REPLY_LENGTH);      } -    catch (LibraryException & libraryException){ -        NK_last_command_status = libraryException.exception_id(); -    } -    catch (const DeviceCommunicationException &deviceException){ -      NK_last_command_status = 256-deviceException.getType(); -    } -    return strndup("", MAXIMUM_STR_REPLY_LENGTH); +    return result;  }  template <typename T>  auto get_with_result(T func){ -    NK_last_command_status = 0; -    try { -        return func(); -    } -    catch (CommandFailedException & commandFailedException){ -        NK_last_command_status = commandFailedException.last_command_status; -    } -    catch (LibraryException & libraryException){ -        NK_last_command_status = libraryException.exception_id(); -    } -    catch (const DeviceCommunicationException &deviceException){ -      NK_last_command_status = 256-deviceException.getType(); -    } -    return static_cast<decltype(func())>(0); +    return std::get<1>(get_with_status(func, static_cast<decltype(func())>(0)));  }  template <typename T> @@ -353,6 +339,18 @@ extern "C" {  		m->set_loglevel(level);  	} +	NK_C_API unsigned int NK_get_major_library_version() { +		return get_major_library_version(); +	} + +	NK_C_API unsigned int NK_get_minor_library_version() { +		return get_minor_library_version(); +	} + +	NK_C_API const char* NK_get_library_version() { +		return get_library_version(); +	} +  	NK_C_API int NK_totp_set_time(uint64_t time) {  		auto m = NitrokeyManager::instance();  		return get_without_result([&]() { @@ -360,11 +358,15 @@ extern "C" {  		});  	} -	NK_C_API int NK_totp_get_time() { +	NK_C_API int NK_totp_set_time_soft(uint64_t time) {  		auto m = NitrokeyManager::instance();  		return get_without_result([&]() { -			m->get_time(0); // FIXME check how that should work +			m->set_time_soft(time);  		}); +        } + +	NK_C_API int NK_totp_get_time() { +	  return 0;  	}  	NK_C_API int NK_change_admin_PIN(const char *current_PIN, const char *new_PIN) { @@ -596,6 +598,39 @@ extern "C" {  		});  	} +	NK_C_API int NK_get_status_storage(NK_storage_status* out) { +		if (out == nullptr) { +			return -1; +		} +		auto m = NitrokeyManager::instance(); +		auto result = get_with_status([&]() { +			return m->get_status_storage(); +		}, proto::stick20::DeviceConfigurationResponsePacket::ResponsePayload()); +		auto error_code = std::get<0>(result); +		if (error_code != 0) { +			return error_code; +		} + +		auto status = std::get<1>(result); +		out->unencrypted_volume_read_only = status.ReadWriteFlagUncryptedVolume_u8 != 0; +		out->unencrypted_volume_active = status.VolumeActiceFlag_st.unencrypted; +		out->encrypted_volume_read_only = status.ReadWriteFlagCryptedVolume_u8 != 0; +		out->encrypted_volume_active = status.VolumeActiceFlag_st.encrypted; +		out->hidden_volume_read_only = status.ReadWriteFlagHiddenVolume_u8 != 0; +		out->hidden_volume_active = status.VolumeActiceFlag_st.hidden; +		out->firmware_version_major = status.versionInfo.major; +		out->firmware_version_minor = status.versionInfo.minor; +		out->firmware_locked = status.FirmwareLocked_u8 != 0; +		out->serial_number_sd_card = status.ActiveSD_CardID_u32; +		out->serial_number_smart_card = status.ActiveSmartCardID_u32; +		out->user_retry_count = status.UserPwRetryCount; +		out->admin_retry_count = status.AdminPwRetryCount; +		out->new_sd_card_found = status.NewSDCardFound_st.NewCard; +		out->filled_with_random = (status.SDFillWithRandomChars_u8 & 0x01) != 0; +		out->stick_initialized = status.StickKeysNotInitiated == 0; +		return 0; +	} +  	NK_C_API char* NK_get_SD_usage_data_as_string() {  		auto m = NitrokeyManager::instance();  		return get_with_string_result([&]() { @@ -52,6 +52,77 @@ extern "C" {          };  	/** +	 * Stores the status of a Storage device. +	 */ +        struct NK_storage_status { +		/** +		 * Indicates whether the unencrypted volume is read-only. +		 */ +		bool unencrypted_volume_read_only; +		/** +		 * Indicates whether the unencrypted volume is active. +		 */ +		bool unencrypted_volume_active; +		/** +		 * Indicates whether the encrypted volume is read-only. +		 */ +		bool encrypted_volume_read_only; +		/** +		 * Indicates whether the encrypted volume is active. +		 */ +		bool encrypted_volume_active; +		/** +		 * Indicates whether the hidden volume is read-only. +		 */ +		bool hidden_volume_read_only; +		/** +		 * Indicates whether the hidden volume is active. +		 */ +		bool hidden_volume_active; +		/** +		 * The major firmware version, e. g. 0 in v0.40. +		 */ +		uint8_t firmware_version_major; +		/** +		 * The minor firmware version, e. g. 40 in v0.40. +		 */ +		uint8_t firmware_version_minor; +		/** +		 * Indicates whether the firmware is locked. +		 */ +		bool firmware_locked; +		/** +		 * The serial number of the SD card in the Storage stick. +		 */ +		uint32_t serial_number_sd_card; +		/** +		 * The serial number of the smart card in the Storage stick. +		 */ +		uint32_t serial_number_smart_card; +		/** +		 * The number of remaining login attempts for the user PIN. +		 */ +		uint8_t user_retry_count; +		/** +		 * The number of remaining login attempts for the admin PIN. +		 */ +		uint8_t admin_retry_count; +		/** +		 * Indicates whether a new SD card was found. +		 */ +		bool new_sd_card_found; +		/** +		 * Indicates whether the SD card is filled with random characters. +		 */ +		bool filled_with_random; +		/** +		 * Indicates whether the stick has been initialized by generating +		 * the AES keys. +		 */ +		bool stick_initialized; +        }; + +	/**  	 * Set debug level of messages written on stderr  	 * @param state state=True - most messages, state=False - only errors level  	 */ @@ -61,7 +132,28 @@ extern "C" {  	 * Set debug level of messages written on stderr  	 * @param level (int) 0-lowest verbosity, 5-highest verbosity  	 */ -  NK_C_API void NK_set_debug_level(const int level); +	NK_C_API void NK_set_debug_level(const int level); + +	/** +	 * Get the major library version, e. g. the 3 in v3.2. +	 * @return the major library version +	 */ +	NK_C_API unsigned int NK_get_major_library_version(); + +	/** +	 * Get the minor library version, e. g. the 2 in v3.2. +	 * @return the minor library version +	 */ +	NK_C_API unsigned int NK_get_minor_library_version(); + +	/** +	 * Get the library version as a string.  This is the output of +	 * `git describe --always` at compile time, for example "v3.3" or +	 * "v3.3-19-gaee920b". +	 * The return value is a string literal and must not be freed. +	 * @return the library version as a string +	 */ +	NK_C_API const char* NK_get_library_version();  	/**  	 * Connect to device of given model. Currently library can be connected only to one device at once. @@ -294,6 +386,19 @@ extern "C" {  	 */  	NK_C_API int NK_totp_set_time(uint64_t time); +	/** +	 * Set the device time used for TOTP to the given time.  Contrary to +	 * {@code set_time(uint64_t)}, this command fails if {@code old_time} +	 * > {@code time} or if {@code old_time} is zero (where {@code +	 * old_time} is the current time on the device). +	 * +	 * @param time new device time as Unix timestamp (seconds since +	 *        1970-01-01) +	 * @return command processing error code +	 */ +	NK_C_API int NK_totp_set_time_soft(uint64_t time); + +  [[deprecated("NK_totp_get_time is deprecated -- use NK_totp_set_time_soft instead")]]  	NK_C_API int NK_totp_get_time();  	//passwords @@ -587,6 +692,17 @@ extern "C" {  	NK_C_API char* NK_get_status_storage_as_string();  	/** +	 * Get the Storage stick status and return the command processing +	 * error code.  If the code is zero, i. e. the command was successful, +	 * the storage status is written to the output pointer's target. +	 * The output pointer must not be null. +	 * +	 * @param out the output pointer for the storage status +	 * @return command processing error code +	 */ +	NK_C_API int NK_get_status_storage(struct NK_storage_status* out); + +	/**  	 * Get SD card usage attributes as string.  	 * Usable during hidden volumes creation.  	 * Storage only diff --git a/NitrokeyManager.cc b/NitrokeyManager.cc index f86a3eb..ab4cac5 100644 --- a/NitrokeyManager.cc +++ b/NitrokeyManager.cc @@ -668,11 +668,15 @@ using nitrokey::misc::strcpyT;          return false;      } -    bool NitrokeyManager::get_time(uint64_t time) { +    void NitrokeyManager::set_time_soft(uint64_t time) {          auto p = get_payload<SetTime>();          p.reset = 0;          p.time = time;          SetTime::CommandTransaction::run(device, p); +    } + +    bool NitrokeyManager::get_time(uint64_t time) { +        set_time_soft(time);          return true;      } diff --git a/libnitrokey/NitrokeyManager.h b/libnitrokey/NitrokeyManager.h index d4630b0..0689c3f 100644 --- a/libnitrokey/NitrokeyManager.h +++ b/libnitrokey/NitrokeyManager.h @@ -65,6 +65,18 @@ char * strndup(const char* str, size_t maxlen);          stick10::ReadSlot::ResponsePayload get_HOTP_slot_data(const uint8_t slot_number);          bool set_time(uint64_t time); +        /** +         * Set the device time used for TOTP to the given time.  Contrary to +         * {@code set_time(uint64_t)}, this command fails if {@code old_time} +         * > {@code time} or if {@code old_time} is zero (where {@code +         * old_time} is the current time on the device). +         * +         * @param time new device time as Unix timestamp (seconds since +         *        1970-01-01) +         */ +        void set_time_soft(uint64_t time); + +        [[deprecated("get_time is deprecated -- use set_time_soft instead")]]          bool get_time(uint64_t time = 0);          bool erase_totp_slot(uint8_t slot_number, const char *temporary_password);          bool erase_hotp_slot(uint8_t slot_number, const char *temporary_password); diff --git a/libnitrokey/version.h b/libnitrokey/version.h new file mode 100644 index 0000000..6547af0 --- /dev/null +++ b/libnitrokey/version.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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 + */ + +#ifndef LIBNITROKEY_VERSION_H +#define LIBNITROKEY_VERSION_H + +namespace nitrokey { +    unsigned int get_major_library_version(); + +    unsigned int get_minor_library_version(); + +    const char* get_library_version(); +} + +#endif diff --git a/unittest/conftest.py b/unittest/conftest.py index 9af67ac..35cc714 100644 --- a/unittest/conftest.py +++ b/unittest/conftest.py @@ -86,7 +86,7 @@ def C(request=None):      assert nk_login != 0  # returns 0 if not connected or wrong model or 1 when connected      global device_type      firmware_version = C.NK_get_minor_firmware_version() -    model = 'P' if firmware_version in [7,8] else 'S' +    model = 'P' if firmware_version < 20 else 'S'      device_type = (model, firmware_version)      print('Connected device: {} {}'.format(model, firmware_version)) diff --git a/unittest/test_offline.cc b/unittest/test_offline.cc index e34eeb4..9d2f195 100644 --- a/unittest/test_offline.cc +++ b/unittest/test_offline.cc @@ -161,6 +161,16 @@ TEST_CASE("Test device commands ids", "[fast]") {  } +#include "version.h" +TEST_CASE("Test version getter", "[fast]") { +  REQUIRE(nitrokey::get_major_library_version() >= 3u); +  REQUIRE(nitrokey::get_minor_library_version() >= 3u); +  const char *library_version = nitrokey::get_library_version(); +  REQUIRE(library_version != nullptr); +  std::string s = library_version; +  REQUIRE(s.length() >= 8); +  REQUIRE(s.find("g") != std::string::npos); +}  TEST_CASE("Connect should not return true after the second attempt", "[fast]") {    int result = 0; diff --git a/unittest/test_pro.py b/unittest/test_pro.py index 53588f6..fb936f8 100644 --- a/unittest/test_pro.py +++ b/unittest/test_pro.py @@ -577,6 +577,55 @@ def test_get_code_user_authorize(C):      assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK +@pytest.mark.otp +def test_authorize_issue_admin(C): +    skip_if_device_version_lower_than({'S': 43, 'P': 9}) + +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + +    assert C.NK_first_authenticate(b"wrong pass", b"another temp pass") == DeviceErrorCode.WRONG_PASSWORD +    assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_NOT_AUTHORIZED + +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK + +@pytest.mark.otp +def test_authorize_issue_user(C): +    skip_if_device_version_lower_than({'S': 43, 'P': 9})  # issue fixed in Pro v0.9, Storage version chosen arbitrary + +    assert C.NK_lock_device() == DeviceErrorCode.STATUS_OK + +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_totp_slot(0, b'python_otp_auth', bbRFC_SECRET, 30, True, False, False, b'', +                                DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    # enable PIN protection of OTP codes with write_config +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, True, False, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    gs(C.NK_get_totp_code(0, 0, 0, 0)) +    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_NOT_AUTHORIZED + +    assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK +    gs(C.NK_get_totp_code_PIN(0, 0, 0, 0, DefaultPasswords.USER_TEMP)) +    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + +    assert C.NK_user_authenticate(b"wrong pass", b"another temp pass") == DeviceErrorCode.WRONG_PASSWORD +    gs(C.NK_get_totp_code_PIN(0, 0, 0, 0, DefaultPasswords.USER_TEMP)) +    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_NOT_AUTHORIZED + +    assert C.NK_user_authenticate(DefaultPasswords.USER, DefaultPasswords.USER_TEMP) == DeviceErrorCode.STATUS_OK +    gs(C.NK_get_totp_code_PIN(0, 0, 0, 0, DefaultPasswords.USER_TEMP)) +    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK + +    # disable PIN protection with write_config +    assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    assert C.NK_write_config(255, 255, 255, False, True, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK +    code = gs(C.NK_get_totp_code(0, 0, 0, 0)) +    assert code != b'' +    assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK +  def cast_pointer_to_tuple(obj, typen, len):      # usage:      #     config = cast_pointer_to_tuple(config_raw_data, 'uint8_t', 5) diff --git a/version.cc.in b/version.cc.in new file mode 100644 index 0000000..0eae647 --- /dev/null +++ b/version.cc.in @@ -0,0 +1,37 @@ +/* + * Copyright (c) 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 + */ + +#include "version.h" + +namespace nitrokey { +    unsigned int get_major_library_version() { +        return @PROJECT_VERSION_MAJOR@; +    } + +    unsigned int get_minor_library_version() { +        return @PROJECT_VERSION_MINOR@; +    } + +    const char* get_library_version() { +        return "@PROJECT_VERSION_GIT@"; +    } +} + | 
