"""
/*
 * 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]
    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)
