diff --git a/tests/conftest.py b/tests/conftest.py index 3dd8181..b295431 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,7 +199,7 @@ class Device: return kids return [(resp[i],resp[i+1]) for i in range(0, len(resp), 2)] - def key_generation(self, type, param): + def key_generation(self, type, param, meta_data=b''): if (type in [KeyType.RSA, KeyType.ECC]): a = ASN1().add_tag(0x5f29, bytes([0])).add_tag(0x42, 'UTCA00001'.encode()) if (type == KeyType.RSA): @@ -211,13 +211,13 @@ class Device: raise ValueError('Bad elliptic curve name') dom = ec_domain(Device.EcDummy(param)) - pubctx = [dom.P, dom.A, dom.B, dom.G, dom.O, None, dom.F] + pubctx = {1: dom.P, 2: dom.A, 3: dom.B, 4: dom.G, 5: dom.O, 7: dom.F} a.add_object(0x7f49, oid.ID_TA_ECDSA_SHA_256, pubctx) a.add_tag(0x5f20, 'UTCDUMMY00001'.encode()) data = a.encode() keyid = self.get_first_free_id() - self.send(command=0x46, p1=keyid, data=list(data)) + self.send(command=0x46, p1=keyid, data=list(data + meta_data)) elif (type == KeyType.AES): if (param == 128): p2 = 0xB0 @@ -390,7 +390,7 @@ class Device: return p1 def exchange(self, keyid, pubkey): - resp = self.send(cla=0x80, command=0x62, p1=keyid, p2=Algorithm.ALGO_EC_DH.value, data=pubkey.public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)) + resp = self.send(cla=0x80, command=0x62, p1=keyid, p2=Algorithm.ALGO_EC_ECDH.value, data=pubkey.public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)) return resp def parse_cvc(self, data): @@ -417,7 +417,16 @@ class Device: def get_key_domain(self, key_domain=0): resp, code = self.send(cla=0x80, command=0x52, p2=key_domain, codes=[0x9000, 0x6A88, 0x6A86]) if (code == 0x9000): - return {'dkek': { 'total': resp[0], 'missing': resp[1]}, 'kcv': resp[2:10]} + ret = { + 'dkek': { + 'total': resp[0], + 'missing': resp[1] + }, + 'kcv': resp[2:10] + } + if (len(resp) > 10): + ret.update({'xkek': resp[10:]}) + return ret return {'error': code} def get_key_domains(self): @@ -584,6 +593,24 @@ class Device: self.send(command=0x22, p1=0x81, p2=0xA4, data=pukref) self.send(command=0x82, data=signature) + def create_xkek(self, kdm): + dicacert = ASN1().decode(kdm).find(0x30).find(0x61).data() + devcert = ASN1().decode(kdm).find(0x30).find(0x62).data() + gskcert = ASN1().decode(kdm).find(0x30).find(0x63).data() + gsksign = ASN1().decode(kdm).find(0x30).find(0x54).data(return_tag=True) + gskdata = CVC().decode(gskcert).req().data() + self.verify_certificate(devcert) + self.verify_certificate(dicacert) + status = self.send(cla=0x80, command=0x52, p1=0x02, data=gskdata + gsksign) + return status[2:10], status[10:] + + def generate_xkek_key(self, key_domain=0): + meta_data = b'\x91\x01\x84\x92\x01' + bytes([key_domain]) + key_id = self.key_generation(KeyType.ECC, 'brainpoolP256r1', meta_data=meta_data) + return key_id + + def derive_xkek(self, keyid, cert): + self.send(cla=0x80, command=0x62, p1=keyid, p2=Algorithm.ALGO_EC_ECDH_XKEK.value, data=cert) @pytest.fixture(scope="session") diff --git a/tests/pico-hsm/test_090_xkek.py b/tests/pico-hsm/test_090_xkek.py new file mode 100644 index 0000000..2e0fe24 --- /dev/null +++ b/tests/pico-hsm/test_090_xkek.py @@ -0,0 +1,75 @@ +""" +/* + * This file is part of the Pico HSM distribution (https://github.com/polhenarejos/pico-hsm). + * Copyright (c) 2023 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 . + */ +""" + +import pytest +from binascii import unhexlify, hexlify +from utils import APDUResponse, SWCodes +from utils import int_to_bytes +from const import TERM_CERT, DICA_CERT +from cvc.asn1 import ASN1 +from cvc.certificates import CVC +from cvc import oid +from cryptography.hazmat.primitives.asymmetric import ec +from utils import DOPrefixes + +KDM = unhexlify(b'30820420060b2b0601040181c31f0402016181ed7f2181e97f4e81a25f290100421045535049434f48534d434130303030317f494f060a04007f0007020202020386410421ee4a21c16a10f737f12e78e5091b266612038cdabebb722b15bf6d41b877fbf64d9ab69c39b9831b1ae00bef2a4e81976f7688d45189bb232a24703d8a96a55f201045535049434f48534d445630303030317f4c12060904007f000703010202530580000000005f25060202000801085f24060203000601045f37403f75c08fffc9186b56e6147199e82bfc327ceef72495bc567961cd54d702f13e3c2766fcd1d11bd6a9d1f4a229b76b248ceb9af88d59a74d0ab149448705159b6281e97f2181e57f4e819e5f290100421045535049434f48534d445630303030317f494f060a04007f00070202020203864104c8561b41e54fea81bb80dd4a6d537e7c3904344e8ca90bc5f668111811e02c8d5d51ca93ca89558f2a8a9cbb147434e3441ec174505ff980fd7a7106286196915f201045535049434f48534d54524a444736387f4c0e060904007f0007030102025301005f25060203000300065f24060204000300055f3740983de63d0975b715ebd8a93cb38fa9638882c8b7064d51a6facabed693b92edc098e458b713203413ef6de0958c44772cbdbc264205c7b1bdb8b4fcb2516437f638201f1678201ed7f218201937f4e82014b5f290100421045535049434f48534d54524a444736387f4982011d060a04007f000702020202038120a9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e537782207d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9832026dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b68441048bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f0469978520a9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a78641048b1f450912a2e4d428b7eefc5fa05618a9ef295e90009a61cbb0970181b333474ea94f94cde5a11aba0589e85d4225002789ff1cdcf25756f059647b49fc2a158701015f201045535049434f48534d54524a444736385f3740372407c20de7257c89dae1e6606c8a046ca65efaa010c0a22b75c402ee243de51f5f1507457193679ed9db4fbbfe8efb9d695b684492b665ad8ba98c1f84ea38421045535049434f48534d54524a444736385f374098718e2e14a44386b689b71a101530316b65ab49a91bab0dd56099c5161ecb8aadff6cf27449f94034e58b7306f01e6ffa2766a2f5bb1281e12e5f1f9174733454400cf8926ca5bec9a91bcd47bf391c15d94ef6e3243d5fd1fffeaafd586766bc3221eafd808f17f8450f238cc1fe7ab1854443db31d622f53a2b3fdb3ad750d5ce') + +def test_initialize(device): + device.initialize(key_domains=1) + device.logout() + +def test_create_xkek(device): + with pytest.raises(APDUResponse) as e: + device.create_xkek(KDM) + assert(e.value.sw == SWCodes.SW_CONDITIONS_NOT_SATISFIED.value) + + device.login() + kcv, did = device.create_xkek(KDM) + assert(bytes(kcv) == b'\x00'*8) + + gskcert = ASN1().decode(KDM).find(0x30).find(0x63).data() + gskQ = CVC().decode(gskcert).pubkey().find(0x86).data() + pub = ec.EllipticCurvePublicKey.from_encoded_point(ec.BrainpoolP256R1(), bytes(gskQ)) + assert(bytes(did) == int_to_bytes(pub.public_numbers().x)+int_to_bytes(pub.public_numbers().y)) + +def test_derive_xkek(device): + keyid = device.generate_xkek_key() + + resp = device.list_keys() + assert((DOPrefixes.KEY_PREFIX.value, keyid) in resp) + + xkek_dom = device.get_key_domain()['xkek'] + pkey = ec.generate_private_key(ec.BrainpoolP256R1()) + pubkey = pkey.public_key() + cert = CVC().cert(pubkey=pubkey, scheme=oid.ID_TA_ECDSA_SHA_256, signkey=pkey, signscheme=oid.ID_TA_ECDSA_SHA_256, car=b"UTCA00001", chr=b"UTCDUMMY00001", extensions=[ + { + 'tag': 0x73, + 'oid': b'\x2B\x06\x01\x04\x01\x81\xC3\x1F\x03\x02\x02', + 'contexts': { + 0: bytes(xkek_dom) + } + } + ]).encode() + device.derive_xkek(keyid, cert) + + resp = device.get_key_domain() + assert(bytes(resp['kcv']) != b'\x00'*8) + + device.delete_file(DOPrefixes.KEY_PREFIX.value << 8 | keyid) + device.delete_file(DOPrefixes.EE_CERTIFICATE_PREFIX.value << 8 | keyid) diff --git a/tests/utils.py b/tests/utils.py index 25510ff..ea992ef 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -109,7 +109,8 @@ class Algorithm(Enum): ALGO_EC_SHA256 = 0x73 ALGO_EC_SHA384 = 0x74 ALGO_EC_SHA512 = 0x75 - ALGO_EC_DH = 0x80 + ALGO_EC_ECDH = 0x80 + ALGO_EC_ECDH_XKEK = 0x84 ALGO_EC_DERIVE = 0x98 ALGO_RSA_RAW = 0x20 @@ -129,6 +130,8 @@ class Algorithm(Enum): ALGO_RSA_PSS_SHA384 = 0x44 ALGO_RSA_PSS_SHA512 = 0x45 + + class Padding(Enum): RAW = 0x21 PKCS = 0x22