1#!/usr/bin/env python2
2"""
3Copyright (c) 2015-2018 Nitrokey UG
4
5This file is part of libnitrokey.
6
7libnitrokey is free software: you can redistribute it and/or modify
8it under the terms of the GNU Lesser General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10any later version.
11
12libnitrokey is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU Lesser General Public License
18along with libnitrokey. If not, see <http://www.gnu.org/licenses/>.
19
20SPDX-License-Identifier: LGPL-3.0
21"""
22
23import cffi
24from enum import Enum
25
26"""
27This example will print 10 HOTP codes from just written HOTP#2 slot.
28For more examples of use please refer to unittest/test_*.py files.
29"""
30
31ffi = cffi.FFI()
32get_string = ffi.string
33
34class DeviceErrorCode(Enum):
35    STATUS_OK = 0
36    NOT_PROGRAMMED = 3
37    WRONG_PASSWORD = 4
38    STATUS_NOT_AUTHORIZED = 5
39    STATUS_AES_DEC_FAILED = 0xa
40
41
42def get_library():
43    fp = 'NK_C_API.h'  # path to C API header
44
45    declarations = []
46    with open(fp, 'r') as f:
47        declarations = f.readlines()
48
49    cnt = 0
50    a = iter(declarations)
51    for declaration in a:
52        if declaration.strip().startswith('NK_C_API'):
53            declaration = declaration.replace('NK_C_API', '').strip()
54            while ';' not in declaration:
55                declaration += (next(a)).strip()
56            # print(declaration)
57            ffi.cdef(declaration, override=True)
58            cnt +=1
59    print('Imported {} declarations'.format(cnt))
60
61
62    C = None
63    import os, sys
64    path_build = os.path.join(".", "build")
65    paths = [
66            os.environ.get('LIBNK_PATH', None),
67            os.path.join(path_build,"libnitrokey.so"),
68            os.path.join(path_build,"libnitrokey.dylib"),
69            os.path.join(path_build,"libnitrokey.dll"),
70            os.path.join(path_build,"nitrokey.dll"),
71    ]
72    for p in paths:
73        if not p: continue
74        print("Trying " +p)
75        p = os.path.abspath(p)
76        if os.path.exists(p):
77            print("Found: "+p)
78            C = ffi.dlopen(p)
79            break
80        else:
81            print("File does not exist: " + p)
82    if not C:
83        print("No library file found")
84        print("Please set the path using LIBNK_PATH environment variable to existing library or compile it (see "
85              "README.md for details)")
86        sys.exit(1)
87
88    return C
89
90
91def get_hotp_code(lib, i):
92    return get_string(lib.NK_get_hotp_code(i))
93
94def to_hex(ss):
95    return ''.join([ format(ord(s),'02x') for s in ss ])
96
97print('Warning!')
98print('This example will change your configuration on inserted stick and overwrite your HOTP#2 slot.')
99print('Please write "continue" to continue or any other string to quit')
100a = raw_input()
101
102if not a == 'continue':
103    exit()
104
105ADMIN = raw_input('Please enter your admin PIN (empty string uses 12345678) ')
106ADMIN = ADMIN or '12345678'  # use default if empty string
107
108show_log = raw_input('Should log messages be shown (please write "yes" to enable; this will make harder reading script output) ') == 'yes'
109libnitrokey = get_library()
110
111if show_log:
112    log_level = raw_input('Please select verbosity level (0-5, 2 is library default, 3 will be selected on empty input) ')
113    log_level = log_level or '3'
114    log_level = int(log_level)
115    libnitrokey.NK_set_debug_level(log_level)
116else:
117    libnitrokey.NK_set_debug_level(2)
118
119
120ADMIN_TEMP = '123123123'
121RFC_SECRET = to_hex('12345678901234567890')
122
123# libnitrokey.NK_login('S')  # connect only to Nitrokey Storage device
124# libnitrokey.NK_login('P')  # connect only to Nitrokey Pro device
125device_connected = libnitrokey.NK_login_auto()  # connect to any Nitrokey Stick
126if device_connected:
127    print('Connected to Nitrokey device!')
128else:
129    print('Could not connect to Nitrokey device!')
130    exit()
131use_8_digits = True
132pin_correct = libnitrokey.NK_first_authenticate(ADMIN, ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
133if pin_correct:
134    print('Your PIN is correct!')
135else:
136    print('Your PIN is not correct! Please try again. Please be careful to not lock your stick!')
137    retry_count_left = libnitrokey.NK_get_admin_retry_count()
138    print('Retry count left: %d' % retry_count_left )
139    exit()
140
141# For function parameters documentation please check NK_C_API.h
142assert libnitrokey.NK_write_config(255, 255, 255, False, True, ADMIN_TEMP) == DeviceErrorCode.STATUS_OK
143libnitrokey.NK_first_authenticate(ADMIN, ADMIN_TEMP)
144libnitrokey.NK_write_hotp_slot(1, 'python_test', RFC_SECRET, 0, use_8_digits, False, False, "",
145                            ADMIN_TEMP)
146# RFC test according to: https://tools.ietf.org/html/rfc4226#page-32
147test_data = [
148    1284755224, 1094287082, 137359152, 1726969429, 1640338314, 868254676, 1918287922, 82162583, 673399871,
149    645520489,
150]
151print('Getting HOTP code from Nitrokey Stick (RFC test, 8 digits): ')
152for i in range(10):
153    hotp_slot_1_code = get_hotp_code(libnitrokey, 1)
154    correct_str =  "correct!" if hotp_slot_1_code == str(test_data[i])[-8:] else  "not correct"
155    print('%d: %s, should be %s -> %s' % (i, hotp_slot_1_code, str(test_data[i])[-8:], correct_str))
156libnitrokey.NK_logout()  # disconnect device
157