diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c index 6d4ea11..387404c 100644 --- a/src/fido/cbor_vendor.c +++ b/src/fido/cbor_vendor.c @@ -26,6 +26,7 @@ #include "mbedtls/ecdh.h" #include "mbedtls/chachapoly.h" #include "mbedtls/hkdf.h" +#include "mbedtls/x509_csr.h" extern uint8_t keydev_dec[32]; extern bool has_keydev_dec; @@ -226,6 +227,45 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { has_keydev_dec = true; goto err; } + else if (cmd == CTAP_VENDOR_EA) { + if (vendorCmd == 0x01) { + uint8_t buffer[1024]; + mbedtls_ecdsa_context ekey; + mbedtls_ecdsa_init(&ekey); + int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev)); + if (ret != 0) { + mbedtls_ecdsa_free(&ekey); + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + ret = mbedtls_ecp_mul(&ekey.grp, &ekey.Q, &ekey.d, &ekey.grp.G, random_gen, NULL); + if (ret != 0) { + mbedtls_ecdsa_free(&ekey); + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + pico_unique_board_id_t rpiid; + pico_get_unique_board_id(&rpiid); + mbedtls_x509write_csr ctx; + mbedtls_x509write_csr_init(&ctx); + snprintf((char *)buffer, sizeof(buffer), "C=ES,O=Pico Keys,OU=Authenticator Attestation,CN=Pico Fido EE Serial %llu", ((uint64_t)rpiid.id[0] << 56) | ((uint64_t)rpiid.id[1] << 48) | ((uint64_t)rpiid.id[2] << 40) | ((uint64_t)rpiid.id[3] << 32) | (rpiid.id[4] << 24) | (rpiid.id[5] << 16) | (rpiid.id[6] << 8) | rpiid.id[7]); + mbedtls_x509write_csr_set_subject_name(&ctx, (char *)buffer); + mbedtls_pk_context key; + mbedtls_pk_init(&key); + mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + key.pk_ctx = &ekey; + mbedtls_x509write_csr_set_key(&ctx, &key); + mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256); + mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid)); + ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_gen, NULL); + mbedtls_ecdsa_free(&ekey); + if (ret <= 0) { + mbedtls_x509write_csr_free(&ctx); + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret)); + } + } else CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); diff --git a/src/fido/ctap.h b/src/fido/ctap.h index 4614349..db09d8f 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -124,6 +124,7 @@ typedef struct { #define CTAP_VENDOR_BACKUP 0x01 #define CTAP_VENDOR_MSE 0x02 #define CTAP_VENDOR_UNLOCK 0x03 +#define CTAP_VENDOR_EA 0x04 #define CTAP_PERMISSION_MC 0x01 // MakeCredential #define CTAP_PERMISSION_GA 0x02 // GetAssertion diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 51d49a5..bd53ef1 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -27,6 +27,8 @@ from words import words from threading import Event from typing import Mapping, Any, Optional, Callable import struct +import urllib.request +import json from enum import IntEnum, unique try: @@ -48,6 +50,7 @@ try: from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 from cryptography.hazmat.primitives import hashes + from cryptography import x509 except: print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`') sys.exit(-1) @@ -63,6 +66,21 @@ else: print('ERROR: platform not supported') sys.exit(-1) +def get_pki_data(url, data=None, method='GET'): + user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; ' + 'rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7' + method = 'GET' + if (data is not None): + method = 'POST' + req = urllib.request.Request(f"https://www.picokeys.com/pico/pico-fido/{url}/", + method=method, + data=data, + headers={'User-Agent': user_agent, }) + response = urllib.request.urlopen(req) + resp = response.read().decode('utf-8') + j = json.loads(resp) + return j + class VendorConfig(Config): class PARAM(IntEnum): @@ -179,6 +197,7 @@ class Vendor: VENDOR_BACKUP = 0x01 VENDOR_MSE = 0x02 VENDOR_UNLOCK = 0x03 + VENDOR_CSR = 0x04 @unique class PARAM(IntEnum): @@ -189,6 +208,7 @@ class Vendor: ENABLE = 0x01 DISABLE = 0x02 KEY_AGREEMENT = 0x01 + CSR = 0x01 class RESP(IntEnum): PARAM = 0x01 @@ -342,6 +362,12 @@ class Vendor: def disable_device_aut(self): self.vcfg.disable_device_aut() + def csr(self): + return self._call( + Vendor.CMD.VENDOR_CSR, + Vendor.SUBCMD.CSR, + ) + def parse_args(): parser = argparse.ArgumentParser() subparser = parser.add_subparsers(title="commands", dest="command") @@ -352,6 +378,10 @@ def parse_args(): parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.') parser_backup.add_argument('filename', help='File to save or load the backup.') + parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation') + parser_attestation.add_argument('subcommand', choices=['csr', 'upload']) + parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.') + args = parser.parse_args() return args @@ -369,9 +399,16 @@ def backup(vdr, args): elif (args.subcommand == 'load'): vdr.backup_load(args.filename) +def attestation(vdr, args): + if (args.subcommand == 'csr'): + csr = x509.load_der_x509_csr(vdr.csr()[1]) + data = urllib.parse.urlencode({'csr': csr.public_bytes(Encoding.PEM)}).encode() + j = get_pki_data('csr', data=data) + cert = x509.load_pem_x509_certificate(j['x509'].encode()) + print(cert) def main(args): - print('Pico Fido Tool v1.2') + print('Pico Fido Tool v1.4') print('Author: Pol Henarejos') print('Report bugs to https://github.com/polhenarejos/pico-fido/issues') print('') @@ -385,6 +422,8 @@ def main(args): secure(vdr, args) elif (args.command == 'backup'): backup(vdr, args) + elif (args.command == 'attestation'): + attestation(vdr, args) def run(): args = parse_args()