From 7e720e8c230b1177115caa937840638ba4d5acc5 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 11 Sep 2025 12:56:02 +0200 Subject: [PATCH] Enable enterprise attestation through VendorConfig. Add a subcommand to enable through pico-tool. Signed-off-by: Pol Henarejos --- src/fido/cbor_config.c | 37 +++++++++++++++++----------- src/fido/ctap.h | 1 + tools/pico-fido-tool.py | 54 +++++++++++++++++++++++------------------ 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 1d145fa..554e9e3 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -38,8 +38,8 @@ int cbor_config(const uint8_t *data, size_t len) { CborParser parser; CborValue map; CborError error = CborNoError; - uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0; - CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 }; + uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0; + CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 }; CborCharString minPinLengthRPIDs[32] = { 0 }; size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0; CborEncoder encoder; @@ -74,10 +74,10 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_FIELD_GET_UINT(vendorCommandId, 2); } else if (subpara == 0x02) { - CBOR_FIELD_GET_BYTES(vendorAutCt, 2); + CBOR_FIELD_GET_BYTES(vendorParamByteString, 2); } else if (subpara == 0x03) { - CBOR_FIELD_GET_UINT(vendorParam, 2); + CBOR_FIELD_GET_UINT(vendorParamInt, 2); } } else if (subcommand == 0x03) { // Extensions @@ -147,8 +147,7 @@ int cbor_config(const uint8_t *data, size_t len) { vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS || vendorCommandId == CTAP_CONFIG_PHY_OPTS); #endif - if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) - { + if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){ if (!file_has_data(ef_keydev_enc)) { CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); } @@ -169,7 +168,7 @@ int cbor_config(const uint8_t *data, size_t len) { } mbedtls_chachapoly_context chatx; - int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len); + int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len); if (ret != 0) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } @@ -177,7 +176,7 @@ int cbor_config(const uint8_t *data, size_t len) { uint8_t key_dev_enc[12 + 32 + 16]; random_gen(NULL, key_dev_enc, 12); mbedtls_chachapoly_init(&chatx); - mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data); + mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.data); ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev)); mbedtls_chachapoly_free(&chatx); if (ret != 0) { @@ -193,20 +192,20 @@ int cbor_config(const uint8_t *data, size_t len) { #ifndef ENABLE_EMULATION else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) { - phy_data.vid = (vendorParam >> 16) & 0xFFFF; - phy_data.pid = vendorParam & 0xFFFF; + phy_data.vid = (vendorParamInt >> 16) & 0xFFFF; + phy_data.pid = vendorParamInt & 0xFFFF; phy_data.vidpid_present = true; } else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) { - phy_data.led_gpio = (uint8_t)vendorParam; + phy_data.led_gpio = (uint8_t)vendorParamInt; phy_data.led_gpio_present = true; } else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) { - phy_data.led_brightness = (uint8_t)vendorParam; + phy_data.led_brightness = (uint8_t)vendorParamInt; phy_data.led_brightness_present = true; } else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) { - phy_data.opts = (uint16_t)vendorParam; + phy_data.opts = (uint16_t)vendorParamInt; } else { CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); @@ -215,6 +214,16 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_PROCESSING); } #endif + else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) { + if (vendorParamByteString.present == false) { + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + } + file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF); + if (ef_ee_ea) { + file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len); + } + low_flash_available(); + } else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } @@ -265,7 +274,7 @@ int cbor_config(const uint8_t *data, size_t len) { err: CBOR_FREE_BYTE_STRING(pinUvAuthParam); - CBOR_FREE_BYTE_STRING(vendorAutCt); + CBOR_FREE_BYTE_STRING(vendorParamByteString); for (size_t i = 0; i < minPinLengthRPIDs_len; i++) { CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]); } diff --git a/src/fido/ctap.h b/src/fido/ctap.h index 46ad887..ad248ba 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -114,6 +114,7 @@ typedef struct { #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 #define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9 +#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf #ifndef ENABLE_EMULATION #define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index a9b2387..88eb787 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -75,16 +75,17 @@ def get_pki_data(url, data=None, method='GET'): class VendorConfig(Config): class PARAM(IntEnum): VENDOR_COMMAND_ID = 0x01 - VENDOR_AUT_CT = 0x02 - VENDOR_PARAM = 0x03 + VENDOR_PARAM_BYTESTRING = 0x02 + VENDOR_PARAM_INT = 0x03 class CMD(IntEnum): CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9 + CONFIG_EA_UPLOAD = 0x66f2a674c29a8dcf CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa - CONFIG_PHY_OPTS = 0x969f3b09eceb805f - CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd + CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 + CONFIG_PHY_OPTS = 0x969f3b09eceb805f class RESP(IntEnum): KEY_AGREEMENT = 0x01 @@ -97,7 +98,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE, - VendorConfig.PARAM.VENDOR_AUT_CT: ct + VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: ct }, ) @@ -114,7 +115,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID, - VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid + VendorConfig.PARAM.VENDOR_PARAM_INT: (vid & 0xFFFF) << 16 | pid }, ) @@ -123,7 +124,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO, - VendorConfig.PARAM.VENDOR_PARAM: gpio + VendorConfig.PARAM.VENDOR_PARAM_INT: gpio }, ) @@ -132,7 +133,7 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS, - VendorConfig.PARAM.VENDOR_PARAM: brightness + VendorConfig.PARAM.VENDOR_PARAM_INT: brightness }, ) @@ -141,7 +142,16 @@ class VendorConfig(Config): Config.CMD.VENDOR_PROTOTYPE, { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS, - VendorConfig.PARAM.VENDOR_PARAM: opts + VendorConfig.PARAM.VENDOR_PARAM_INT: opts + }, + ) + + def upload_ea(self, der): + self._call( + Config.CMD.VENDOR_PROTOTYPE, + { + VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_EA_UPLOAD, + VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: der }, ) @@ -242,7 +252,6 @@ class Vendor: DISABLE = 0x02 KEY_AGREEMENT = 0x01 EA_CSR = 0x01 - EA_UPLOAD = 0x02 class RESP(IntEnum): PARAM = 0x01 @@ -430,13 +439,7 @@ class Vendor: )[Vendor.RESP.PARAM] def upload_ea(self, der): - self._call( - Vendor.CMD.VENDOR_EA, - Vendor.SUBCMD.EA_UPLOAD, - { - Vendor.PARAM.PARAM: der - } - ) + self.vcfg.upload_ea(der) def vidpid(self, vid, pid): return self.vcfg.vidpid(vid, pid) @@ -480,6 +483,9 @@ class Vendor: ) return { 'free': resp[1], 'used': resp[2], 'total': resp[3], 'files': resp[4], 'size': resp[5] } + def enable_enterprise_attestation(self): + self.vcfg.enable_enterprise_attestation() + def parse_args(): parser = argparse.ArgumentParser() subparser = parser.add_subparsers(title="commands", dest="command") @@ -492,7 +498,7 @@ def parse_args(): 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']) + parser_attestation.add_argument('subcommand', choices=['csr','enable']) 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.') parser_phy = subparser.add_parser('phy', help='Set PHY options.') @@ -513,7 +519,7 @@ def parse_args(): args = parser.parse_args() return args -def secure(vdr, args): +def secure(vdr: Vendor, args): if (args.subcommand == 'enable'): vdr.enable_device_aut() elif (args.subcommand == 'unlock'): @@ -521,13 +527,13 @@ def secure(vdr, args): elif (args.subcommand == 'disable'): vdr.disable_device_aut() -def backup(vdr, args): +def backup(vdr: Vendor, args): if (args.subcommand == 'save'): vdr.backup_save(args.filename) elif (args.subcommand == 'load'): vdr.backup_load(args.filename) -def attestation(vdr, args): +def attestation(vdr: Vendor, args): if (args.subcommand == 'csr'): if (args.filename is None): csr = x509.load_der_x509_csr(vdr.csr()) @@ -542,8 +548,10 @@ def attestation(vdr, args): except ValueError: cert = x509.load_pem_x509_certificate(dataf) vdr.upload_ea(cert.public_bytes(Encoding.DER)) + elif (args.subcommand == 'enable'): + vdr.enable_enterprise_attestation() -def phy(vdr, args): +def phy(vdr: Vendor, args): val = args.value if 'value' in args else None if (val): if (args.subcommand == 'vidpid'): @@ -567,7 +575,7 @@ def phy(vdr, args): else: print('Command executed successfully. Please, restart your Pico Key.') -def memory(vdr, args): +def memory(vdr: Vendor, args): mem = vdr.memory() print(f'Memory usage:') print(f'\tFree: {mem["free"]/1024:.2f} kilobytes ({mem["free"]*100/mem["total"]:.2f}%)')