Skip to content

Commit

Permalink
Merge branch 'test-update'
Browse files Browse the repository at this point in the history
Update tests for NK Pro bootloader
Update UDEV rules
Correct version setup while building with QMake
  • Loading branch information
szszszsz committed Sep 4, 2021
2 parents 28335de + 095fded commit c5e57e2
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 14 deletions.
4 changes: 2 additions & 2 deletions data/41-nitrokey.rules
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b1", TAG+="uaccess"
# Nitrokey 3 NFC
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b3", TAG+="uaccess"
# Nitrokey Pro Bootloader
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", TAG+="uaccess"

LABEL="u2f_end"

Expand All @@ -47,6 +45,8 @@ ACTION!="add", GOTO="gnupg_rules_end"
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
## Nitrokey Pro
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
## Nitrokey Pro Bootloader
ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", TAG+="uaccess"
## Nitrokey Storage
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", TAG+="uaccess"
## Nitrokey Start
Expand Down
4 changes: 2 additions & 2 deletions data/41-nitrokey_old.rules
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b1", MODE="0660", GROUP+="plugdev"
# Nitrokey 3 NFC
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b3", MODE="0660", GROUP+="plugdev"
# Nitrokey Pro Bootloader
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", MODE="0660", GROUP+="plugdev"

LABEL="u2f_end"

Expand All @@ -48,6 +46,8 @@ ACTION!="add", GOTO="gnupg_rules_end"
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4107", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
## Nitrokey Pro
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4108", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
## Nitrokey Pro Bootloader
ATTRS{idVendor}=="20a0", ATTRS{idProduct}=="42b4", MODE="0660", GROUP+="plugdev"
## Nitrokey Storage
ATTR{idVendor}=="20a0", ATTR{idProduct}=="4109", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg", MODE="0660", GROUP+="plugdev"
## Nitrokey Start
Expand Down
3 changes: 3 additions & 0 deletions unittest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ def C_offline(request=None):

@pytest.fixture(scope="session")
def C(request=None):
import platform
print(f"Python version: {platform.python_version()}")
print(f"OS: {platform.system()} {platform.release()} {platform.version()}")
print("Getting library with connection initialized")
return get_library(request)

Expand Down
39 changes: 36 additions & 3 deletions unittest/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
SPDX-License-Identifier: LGPL-3.0
"""
from enum import Enum
from sys import stderr

from misc import to_hex, bb
from conftest import print

RFC_SECRET_HR = '12345678901234567890'
RFC_SECRET = to_hex(RFC_SECRET_HR) # '31323334353637383930...'
Expand All @@ -39,23 +43,52 @@ class DefaultPasswords:
UPDATE_TOO_SHORT = UPDATE_LONG[:7]


class DeviceErrorCode:
class DeviceErrorCode(Enum):
STATUS_OK = 0
BUSY = 1 # busy or busy progressbar in place of wrong_CRC status
NOT_PROGRAMMED = 3
WRONG_PASSWORD = 4
STATUS_NOT_AUTHORIZED = 5
STATUS_AES_DEC_FAILED = 0xa
STATUS_AES_DEC_FAILED = 10
STATUS_WRONG_SLOT = 2
STATUS_TIMESTAMP_WARNING = 6
STATUS_NO_NAME_ERROR = 7
STATUS_NOT_SUPPORTED = 8
STATUS_UNKNOWN_COMMAND = 9
STATUS_AES_CREATE_KEY_FAILED = 11
STATUS_ERROR_CHANGING_USER_PASSWORD = 12
STATUS_ERROR_CHANGING_ADMIN_PASSWORD = 13
STATUS_ERROR_UNBLOCKING_PIN = 14

STATUS_UNKNOWN_ERROR = 100
STATUS_DISCONNECTED = 255

def __eq__(self, other):
other_name = 'Unknown'
try:
other_name = str(DeviceErrorCode(other).name)
except:
pass
result = self.value == other
print(f'Returned {other_name}, expected {self.name} => {result}')
return result

class LibraryErrors:
class LibraryErrors(Enum):
TOO_LONG_STRING = 200
INVALID_SLOT = 201
INVALID_HEX_STRING = 202
TARGET_BUFFER_SIZE_SMALLER_THAN_SOURCE = 203

def __eq__(self, other):
other_name = 'Unknown'
try:
other_name = str(LibraryErrors(other).name)
except:
pass
result = self.value == other
print(f'Returned {other_name}, expected {self.name} => {result}')
return result


HOTP_slot_count = 3
TOTP_slot_count = 15
Expand Down
54 changes: 52 additions & 2 deletions unittest/test_issues.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from enum import Enum

import pytest

from conftest import skip_if_device_version_lower_than
from constants import DefaultPasswords, DeviceErrorCode
from misc import gs
from test_pro import check_HOTP_RFC_codes
from misc import gs, ffi
from test_pro import check_HOTP_RFC_codes, test_random


def test_destroy_encrypted_data_leaves_OTP_intact(C):
Expand All @@ -9,6 +14,8 @@ def test_destroy_encrypted_data_leaves_OTP_intact(C):
Test for Nitrokey Storage.
Details: https://github.com/Nitrokey/libnitrokey/issues/199
"""
skip_if_device_version_lower_than({'S': 55})

assert C.NK_enable_password_safe(DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
# write password safe slot
assert C.NK_write_password_safe_slot(0, b'slotname1', b'login1', b'pass1') == DeviceErrorCode.STATUS_OK
Expand Down Expand Up @@ -44,3 +51,46 @@ def test_destroy_encrypted_data_leaves_OTP_intact(C):
# confirm OTP
assert C.NK_first_authenticate(DefaultPasswords.ADMIN, DefaultPasswords.ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
assert gs(C.NK_get_hotp_slot_name(1)) == b'python_test'


class Modes(Enum):
EmptyBody = 0
FactoryResetWithAES = 1
FactoryReset = 2
AESGeneration = 3

@pytest.mark.firmware
@pytest.mark.factory_reset
@pytest.mark.parametrize("mode", map(Modes, reversed(range(4))))
def test_pro_factory_reset_breaks_update_password(C, mode: Modes):
from test_pro_bootloader import test_bootloader_password_change_pro, test_bootloader_password_change_pro_length
from test_pro import test_factory_reset
skip_if_device_version_lower_than({'P': 14})

func = {
Modes.EmptyBody: lambda: True,
Modes.FactoryResetWithAES: lambda: test_factory_reset(C) or True,
Modes.FactoryReset: lambda: C.NK_factory_reset(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK,
Modes.AESGeneration: lambda: C.NK_build_aes_key(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK,
}

def boot_test(C):
test_bootloader_password_change_pro(C)
# test_bootloader_password_change_pro_length(C)

def random(C):
data = ffi.new('struct GetRandom_t *')
req_count = 50
res = C.NK_get_random(req_count, data)
assert res == DeviceErrorCode.STATUS_OK
assert C.NK_get_last_command_status() == DeviceErrorCode.STATUS_OK
assert data.op_success == 1
assert data.size_effective == req_count

random(C)
boot_test(C)
random(C)
func[mode]()
random(C) # fails here
boot_test(C)
random(C)
19 changes: 15 additions & 4 deletions unittest/test_pro.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@
import pytest

from conftest import skip_if_device_version_lower_than
from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bbRFC_SECRET, LibraryErrors, HOTP_slot_count, \
from constants import DefaultPasswords, DeviceErrorCode, RFC_SECRET, bbRFC_SECRET, HOTP_slot_count, \
TOTP_slot_count
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.aes
def test_regenerate_aes_key_2(C):
test_regenerate_aes_key(C)


@pytest.mark.lock_device
@pytest.mark.PWS
def test_enable_password_safe(C):
Expand Down Expand Up @@ -164,9 +170,13 @@ def test_enable_password_safe_after_factory_reset(C):
if is_storage(C):
assert C.NK_clear_new_sd_card_warning(DefaultPasswords.ADMIN) == DeviceErrorCode.STATUS_OK
enable_password_safe_result = C.NK_enable_password_safe(DefaultPasswords.USER)
assert enable_password_safe_result == DeviceErrorCode.STATUS_AES_DEC_FAILED \
or is_storage(C) and enable_password_safe_result in \
[DeviceErrorCode.WRONG_PASSWORD, DeviceErrorCode.STATUS_UNKNOWN_ERROR] # UNKNOWN_ERROR since v0.51
pro_case = enable_password_safe_result in [DeviceErrorCode.STATUS_AES_DEC_FAILED,
DeviceErrorCode.STATUS_AES_CREATE_KEY_FAILED] # STATUS_AES_CREATE_KEY_FAILED since v0.14
storage_case = enable_password_safe_result in [DeviceErrorCode.WRONG_PASSWORD,
DeviceErrorCode.STATUS_UNKNOWN_ERROR] # UNKNOWN_ERROR since v0.51
assert not is_storage(C) and pro_case \
or is_storage(C) and storage_case
C.NK_build_aes_key(DefaultPasswords.ADMIN) == 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

Expand Down Expand Up @@ -246,6 +256,7 @@ def test_admin_retry_counts(C):
@pytest.mark.lock_device
@pytest.mark.pin
def test_user_retry_counts_change_PIN(C):
# TODO can get device with AES key not set, and fail because of that - generate the key or reorder tests
assert C.NK_change_user_PIN(DefaultPasswords.USER, DefaultPasswords.USER) == DeviceErrorCode.STATUS_OK
wrong_password = b'wrong_password'
default_user_retry_count = 3
Expand Down
20 changes: 19 additions & 1 deletion unittest/test_pro_bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,30 @@ def test_bootloader_password_change_pro_too_long(C):

@pytest.mark.skip_by_default
@pytest.mark.firmware
def test_bootloader_data_rention(C):
def test_bootloader_data_retention(C):
skip_if_device_version_lower_than({'P': 11})
# Not enabled due to lack of side-effect removal at this point

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)


@pytest.mark.firmware
def test_factory_reset_does_not_change_update_password(C):
"""
Check if factory reset changes the update password, which should not happen
"""
skip_if_device_version_lower_than({'P': 13})
from test_pro import test_factory_reset
# Revert effects of a broken test run, if needed
C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE)

# actual test
assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.STATUS_OK
test_factory_reset(C)
assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE, DefaultPasswords.UPDATE_TEMP) == DeviceErrorCode.WRONG_PASSWORD
assert C.NK_change_firmware_password_pro(DefaultPasswords.UPDATE_TEMP, DefaultPasswords.UPDATE) == DeviceErrorCode.STATUS_OK

0 comments on commit c5e57e2

Please sign in to comment.