diff --git a/pico_hsm_attestation.py b/pico_hsm_attestation.py new file mode 100644 index 0000000..c389dd8 --- /dev/null +++ b/pico_hsm_attestation.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +/* + * This file is part of the Pico HSM distribution (https://github.com/polhenarejos/pico-hsm). + * 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 . + */ +""" + +from smartcard.CardType import AnyCardType +from smartcard.CardRequest import CardRequest +from smartcard.Exceptions import CardRequestTimeoutException +from cvc.certificates import CVC, ASN1 +from cvc.oid import oid2scheme +from cvc.utils import scheme_rsa +import argparse +import logging +import sys +from binascii import hexlify +from cryptography.hazmat.primitives.asymmetric import ec + +logger = logging.getLogger(__name__) + +class APDUResponse(Exception): + def __init__(self, sw1, sw2): + self.sw1 = sw1 + self.sw2 = sw2 + super().__init__(f'SW:{sw1:02X}{sw2:02X}') + + +def send_apdu(card, command, p1, p2, data): + lc = [0x00] + list(len(data).to_bytes(2, 'big')) + le = [0x00, 0x00] + if (isinstance(command, list) and len(command) > 1): + apdu = command + else: + apdu = [0x00, command] + + apdu = apdu + [p1, p2] + lc + data + le + response, sw1, sw2 = card.connection.transmit(apdu) + if (sw1 != 0x90): + raise APDUResponse(sw1, sw2) + return bytearray(response) + + +def parse_args(): + parser = argparse.ArgumentParser(description='Prints the certificate of attestated key in a Pico HSM') + parser.add_argument('--version', help='Displays the current version', action='store_true') + parser.add_argument('kid',help='Certificate to print', metavar='KEY_ID') + if ('--version' in sys.argv): + print('Pico HSM Attestation tool') + print('Author: Pol Henarejos') + print(f'Version 1.0') + print('') + print('Report bugs to http://github.com/polhenarejos/pico-hsm/issues') + print('') + sys.exit(0) + args = parser.parse_args() + return args + +def main(args): + cardtype = AnyCardType() + try: + # request card insertion + cardrequest = CardRequest(timeout=10, cardType=cardtype) + card = cardrequest.waitforcard() + + # connect to the card and perform a few transmits + card.connection.connect() + + except CardRequestTimeoutException: + print('time-out: no card inserted during last 10s') + + try: + response = send_apdu(card, 0xB1, 0x2F, 0x02, [0x54, 0x02, 0x00, 0x00]) + except APDUResponse as a: + print('ERROR: There is an error with the device certificate.') + sys.exit(1) + + devcert = ASN1().decode(response).find(0x7f21, pos=0).data(return_tag=True) + dica = ASN1().decode(response).find(0x7f21, pos=1).data(return_tag=True) + + try: + cert = send_apdu(card, 0xB1, 0xCE, int(args.kid), [0x54, 0x02, 0x00, 0x00]) + except APDUResponse as a: + if (a.sw1 == 0x6a and a.sw2 == 0x82): + print('ERROR: Key not found') + sys.exit(1) + + print(f'Details of key {args.kid}:\n') + print(f' CAR: {(CVC().decode(cert).car()).decode()}') + print(' Public Key:') + puboid = CVC().decode(cert).pubkey().oid() + print(f' Scheme: {oid2scheme(puboid)}') + chr = CVC().decode(cert).chr() + car = CVC().decode(cert).car() + if (scheme_rsa(puboid)): + print(f' Modulus: {hexlify(CVC().decode(cert).pubkey().find(0x81).data()).decode()}') + print(f' Exponent: {hexlify(CVC().decode(cert).pubkey().find(0x82).data()).decode()}') + else: + print(f' Public Point: {hexlify(CVC().decode(cert).pubkey().find(0x86).data()).decode()}') + print(f' CHR: {chr.decode()}') + print(' Key signature:') + inret = CVC().decode(cert).verify() + if (inret): + print(' Status: VALID') + print(f' This certificate is signed with private key {args.kid}') + else: + print(' Status: NOT VALID') + print(f' This certificate is NOT signed with private key {args.kid}') + print(' Cert signature:') + print(f' Outer CAR: {CVC().decode(cert).outer_car().decode()}') + outret = CVC().decode(cert).verify(outer=True, dica=devcert, curve=ec.SECP256R1()) + if (outret): + print(' Status: VALID') + print(' This certificate is signed with the device key') + else: + print(' Status: NOT VALID') + print(' This certificate is NOT signed with the device key') + + if (inret is True and outret is True): + print(f'Key {args.kid} is generated by device {chr.decode()}') + else: + print(f'Key {args.kid} is NOT generated by device {chr.decode()}') + +def run(): + args = parse_args() + main(args) + +if __name__ == "__main__": + run()