pico-hsm/pico_hsm_attestation.py
Pol Henarejos c5e4583762
Add a tool for attestation of a private key.
It looks for a particular private key and generates a report with some useful information and validates the source of the private key, whether it is generated in this device or outside.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-08-23 14:54:38 +02:00

143 lines
5 KiB
Python

#!/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 <http://www.gnu.org/licenses/>.
*/
"""
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()