259 lines
10 KiB
Python
259 lines
10 KiB
Python
"""
|
|
/*
|
|
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
|
|
* Copyright (c) 2022 Pol Henarejos.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3.
|
|
*
|
|
* This program 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 General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
"""
|
|
|
|
import pytest
|
|
from utils import *
|
|
import hmac, hashlib
|
|
|
|
INS_PUT = 0x01
|
|
INS_DELETE = 0x02
|
|
INS_SET_CODE = 0x03
|
|
INS_RESET = 0x04
|
|
INS_LIST = 0xa1
|
|
INS_CALCULATE = 0xa2
|
|
INS_VALIDATE = 0xa3
|
|
INS_CALC_ALL = 0xa4
|
|
INS_SEND_REMAINING = 0xa5
|
|
|
|
RESP_MORE_DATA = 0x61
|
|
|
|
TAG_NAME = 0x71
|
|
TAG_NAME_LIST = 0x72
|
|
TAG_KEY = 0x73
|
|
TAG_CHALLENGE = 0x74
|
|
TAG_RESPONSE = 0x75
|
|
TAG_T_RESPONSE = 0x76
|
|
TAG_NO_RESPONSE = 0x77
|
|
TAG_PROPERTY = 0x78
|
|
TAG_VERSION = 0x79
|
|
TAG_IMF = 0x7a
|
|
TAG_ALGO = 0x7b
|
|
TAG_TOUCH_RESPONSE = 0x7c
|
|
|
|
TYPE_MASK = 0xf0
|
|
TYPE_HOTP = 0x10
|
|
TYPE_TOTP = 0x20
|
|
|
|
ALG_MASK = 0x0f
|
|
ALG_SHA1 = 0x01
|
|
ALG_SHA256 = 0x02
|
|
|
|
PROP_ALWAYS_INC = 0x01
|
|
PROP_REQUIRE_TOUCH = 0x02
|
|
|
|
## Based on tests on https://github.com/Yubico/ykneo-oath/blob/master/test/test/pkgYkneoOathTest/YkneoOathTest.java
|
|
|
|
def test_select_oath(select_oath):
|
|
pass
|
|
|
|
def list_apdu(ccid_card):
|
|
resp = send_apdu(ccid_card, INS_LIST, p1=0, p2=0)
|
|
return resp
|
|
|
|
name_kaka = [ord('k'), ord('a'), ord('k'), ord('a')]
|
|
data_name = [TAG_NAME] + [len(name_kaka)] + name_kaka
|
|
data_key = [TAG_KEY, 0x16, 0x21, 0x06, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b]
|
|
data_chal = [TAG_CHALLENGE, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
|
|
|
|
def test_life(reset_oath):
|
|
data = data_name + data_key
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=list(data))
|
|
assert(len(resp) == 0)
|
|
resp = list_apdu(reset_oath)
|
|
exp = [TAG_NAME_LIST, 5, 0x21] + name_kaka
|
|
assert(resp == exp)
|
|
|
|
data = data_name + data_chal
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=data)
|
|
exp = [TAG_RESPONSE, 0x15, 0x06, 0xb3, 0x99, 0xbd, 0xfc, 0x9d, 0x05, 0xd1, 0x2a, 0xc4, 0x35, 0xc4, 0xc8, 0xd6, 0xcb, 0xd2, 0x47, 0xc4, 0x0a, 0x30, 0xf1]
|
|
assert(resp == exp)
|
|
|
|
data = data_name
|
|
resp = send_apdu(reset_oath, INS_DELETE, p1=0, p2=0, data=data)
|
|
resp = list_apdu(reset_oath)
|
|
assert(len(resp) == 0)
|
|
|
|
def test_overwrite(reset_oath):
|
|
data = data_name + data_key
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=list(data))
|
|
assert(len(resp) == 0)
|
|
resp = list_apdu(reset_oath)
|
|
exp = [TAG_NAME_LIST, 5, 0x21] + name_kaka
|
|
assert(resp == exp)
|
|
|
|
data = data_name + [TAG_CHALLENGE, 0x8] + list(bytes(b'\xff'*8))
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=data)
|
|
exp = [TAG_RESPONSE, 0x15, 0x06, 0x79, 0x3e, 0x1b, 0xbd, 0xbf, 0xa7, 0x75, 0xa8, 0x63,0xcc, 0x80, 0x02, 0xce, 0xe4, 0xbd, 0x6c, 0xd7, 0xce, 0xb8, 0xcd]
|
|
assert(resp == exp)
|
|
|
|
resp = list_apdu(reset_oath)
|
|
exp = [TAG_NAME_LIST, 5, 0x21] + name_kaka
|
|
assert(resp == exp)
|
|
|
|
data = data_name + [TAG_CHALLENGE, 0x8] + list(bytes(b'\xff\x00'*4))
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=data)
|
|
exp = [TAG_RESPONSE, 0x15, 0x06, 0x3b, 0x0e, 0x3c, 0x63, 0x1c, 0x01, 0x67, 0xb0, 0x93, 0xa5, 0xec, 0xb9, 0x09, 0x7d, 0x0b, 0x8e, 0x9a, 0xcc, 0x2f, 0x7f]
|
|
assert(resp == exp)
|
|
|
|
def test_auth(reset_oath):
|
|
key = list(bytes(b'kaka blahonga'))
|
|
chal = [1,2,3,4,5,6,7,8]
|
|
resp = [0x0c, 0x42, 0x8e, 0x9c, 0xba, 0xa3, 0xb3, 0xab, 0x18, 0x53, 0xd8, 0x79, 0xb9, 0xd2, 0x26, 0xf7, 0xce, 0xcc, 0x4a, 0x7a]
|
|
data = [TAG_KEY, len(key)+1, ALG_SHA1 | TYPE_TOTP] + key + [TAG_CHALLENGE, len(chal)] + chal + [TAG_RESPONSE, len(resp)] + resp
|
|
resp = send_apdu(reset_oath, INS_SET_CODE, p1=0, p2=0, data=data)
|
|
|
|
reset_oath.connection.reconnect()
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = list_apdu(reset_oath)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
|
|
resp = send_apdu(reset_oath, 0xA4, 0x04, 0x00, aid)
|
|
assert(resp[15] == TAG_CHALLENGE)
|
|
assert(resp[16] == 8)
|
|
resp2 = hmac.digest(bytes(key), bytes(resp[17:17+8]), 'sha1')
|
|
data = [TAG_RESPONSE, len(resp2)] + list(resp2) + [TAG_CHALLENGE, len(chal)] + chal
|
|
resp = send_apdu(reset_oath, INS_VALIDATE, p1=0, p2=0, data=data)
|
|
exp = [TAG_RESPONSE, 20] + list(hmac.digest(bytes(key), bytes(chal), 'sha1'))
|
|
assert(exp == resp)
|
|
resp = list_apdu(reset_oath)
|
|
|
|
def test_bothoath(reset_oath):
|
|
digits = 6
|
|
tname = list(bytes(b'totp'))
|
|
data = [TAG_NAME, len(tname)] + tname + [TAG_KEY, 9, TYPE_TOTP | ALG_SHA1, digits] + list(bytes(b'foo bar'))
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
data[2] = ord('h')
|
|
data[8] = TYPE_HOTP | ALG_SHA1
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
|
|
hname = tname[:]
|
|
hname[0] = ord('h')
|
|
data = [TAG_CHALLENGE, 8, 0, 0, 0, 0, 0x02, 0xbc, 0xad, 0xc8]
|
|
resp = send_apdu(reset_oath, INS_CALC_ALL, p1=0, p2=1, data=data)
|
|
exp = [TAG_NAME, len(tname)] + tname + [TAG_T_RESPONSE, 5, digits, 0x3d, 0xc6, 0xbf, 0x3d] + [TAG_NAME, len(hname)] + hname + [TAG_NO_RESPONSE, 0x01, digits]
|
|
assert(exp == resp)
|
|
|
|
data = [TAG_NAME, len(hname)] + hname + [TAG_CHALLENGE]
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
|
exp = [TAG_T_RESPONSE, 5, digits, 0x17, 0xfa, 0x2d, 0x40]
|
|
assert(resp == exp)
|
|
|
|
def test_imf_overwrite(reset_oath):
|
|
key = list(bytes(b'kaka'))
|
|
imf = [0xff, 0x00, 0xff, 0xff]
|
|
name = list(bytes(b'kaka'))
|
|
|
|
data = [TAG_NAME, len(name)] + name + [TAG_KEY, len(key)+2, ALG_SHA1 | TYPE_HOTP, 6] + key + [TAG_IMF, len(imf)] + imf
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
data = [TAG_NAME, len(name)] + name + [TAG_CHALLENGE]
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
|
exp = [TAG_T_RESPONSE, 5, 6, 0x45, 0xd9, 0x0f, 0x25]
|
|
assert(exp == resp)
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
|
exp = [TAG_T_RESPONSE, 5, 6, 0x1b, 0xc5, 0x4a, 0x85]
|
|
assert(exp == resp)
|
|
|
|
data = [TAG_NAME, len(name)] + name + [TAG_KEY, len(key)+2, ALG_SHA1 | TYPE_HOTP, 6] + key
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
data = [TAG_NAME, len(name)] + name + [TAG_CHALLENGE]
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
|
exp = [TAG_T_RESPONSE, 5, 6, 0x16, 0x53, 0x24, 0xdb]
|
|
assert(exp == resp)
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
|
exp = [TAG_T_RESPONSE, 5, 6, 0x53, 0xed, 0x5e, 0xb2]
|
|
assert(exp == resp)
|
|
|
|
def test_imf_more(reset_oath):
|
|
key = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
|
|
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30]
|
|
imf = [0, 0, 0, 1]
|
|
name = list(bytes(b'kaka'))
|
|
|
|
data = [TAG_NAME, len(name)] + name + [TAG_KEY, len(key)+2, ALG_SHA1 | TYPE_HOTP, 6] + key + [TAG_IMF, len(imf)] + imf
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
data = [TAG_NAME, len(name)] + name + [TAG_CHALLENGE]
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
|
exp = [TAG_T_RESPONSE, 5, 6, 0x41, 0x39, 0x7e, 0xea]
|
|
assert(exp == resp)
|
|
|
|
def test_delete(reset_oath):
|
|
key = list(bytes(b'blahonga!'))
|
|
firstname = list(bytes(b'one'))
|
|
secondname = list(bytes(b'two'))
|
|
thirdname = list(bytes(b'three'))
|
|
type = ALG_SHA1 | TYPE_TOTP
|
|
|
|
data = [TAG_NAME, len(firstname)] + firstname + [TAG_KEY, len(key)+2, type, 6] + key
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
data = [TAG_NAME, len(secondname)] + secondname + [TAG_KEY, len(key)+2, type, 6] + key
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
resp = list_apdu(reset_oath)
|
|
exp = [TAG_NAME_LIST, len(firstname)+1, type] + firstname + [TAG_NAME_LIST, len(secondname)+1, type] + secondname
|
|
assert(exp == resp)
|
|
|
|
data = [TAG_NAME, len(firstname)] + firstname
|
|
resp = send_apdu(reset_oath, INS_DELETE, p1=0, p2=0, data=data)
|
|
resp = list_apdu(reset_oath)
|
|
exp = [TAG_NAME_LIST, len(secondname)+1, type] + secondname
|
|
assert(exp == resp)
|
|
|
|
data = [TAG_NAME, len(thirdname)] + thirdname + [TAG_KEY, len(key)+2, type, 6] + key
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
|
resp = list_apdu(reset_oath)
|
|
exp = [TAG_NAME_LIST, len(thirdname)+1, type] + thirdname + [TAG_NAME_LIST, len(secondname)+1, type] + secondname
|
|
assert(exp == resp)
|
|
|
|
def test_noauth(reset_oath):
|
|
key = list(bytes(b'kaka blahonga'))
|
|
chal = [1,2,3,4,5,6,7,8]
|
|
resp = [0x0c, 0x42, 0x8e, 0x9c, 0xba, 0xa3, 0xb3, 0xab, 0x18, 0x53, 0xd8, 0x79, 0xb9, 0xd2, 0x26, 0xf7, 0xce, 0xcc, 0x4a, 0x7a]
|
|
data = [TAG_KEY, len(key)+1, ALG_SHA1 | TYPE_TOTP] + key + [TAG_CHALLENGE, len(chal)] + chal + [TAG_RESPONSE, len(resp)] + resp
|
|
resp = send_apdu(reset_oath, INS_SET_CODE, p1=0, p2=0, data=data)
|
|
|
|
reset_oath.connection.reconnect()
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = list_apdu(reset_oath)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=None)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = send_apdu(reset_oath, INS_DELETE, p1=0, p2=0, data=None)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = send_apdu(reset_oath, INS_SET_CODE, p1=0, p2=0, data=None)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=None)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = send_apdu(reset_oath, INS_CALC_ALL, p1=0, p2=0, data=None)
|
|
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
|
|
|
with pytest.raises(APDUResponse) as e:
|
|
resp = send_apdu(reset_oath, INS_RESET, p1=0, p2=0, data=None)
|
|
assert([e.value.sw1, e.value.sw2] == [0x6A, 0x86])
|
|
resp = send_apdu(reset_oath, INS_RESET, p1=0xde, p2=0xad, data=None)
|