diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 554e9e3..02bcd06 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -40,7 +40,7 @@ int cbor_config(const uint8_t *data, size_t len) { CborError error = CborNoError; uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0; CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 }; - CborCharString minPinLengthRPIDs[32] = { 0 }; + CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 }; size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0; CborEncoder encoder; //CborEncoder mapEncoder; @@ -79,6 +79,9 @@ int cbor_config(const uint8_t *data, size_t len) { else if (subpara == 0x03) { CBOR_FIELD_GET_UINT(vendorParamInt, 2); } + else if (subpara == 0x04) { + CBOR_FIELD_GET_TEXT(vendorParamTextString, 2); + } } else if (subcommand == 0x03) { // Extensions CBOR_FIELD_GET_UINT(subpara, 2); @@ -224,6 +227,21 @@ int cbor_config(const uint8_t *data, size_t len) { } low_flash_available(); } + else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) { + file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY); + if (ef_pin_policy) { + uint8_t *val = calloc(1, 2 + vendorParamByteString.len); + if (val) { + // Not ready yet for integer param + // val[0] = (uint8_t)(vendorParamInt >> 8); + // val[1] = (uint8_t)(vendorParamInt & 0xFF); + memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len); + file_put_data(ef_pin_policy, val, 2 + vendorParamByteString.len); + free(val); + } + } + low_flash_available(); + } else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } @@ -275,6 +293,7 @@ int cbor_config(const uint8_t *data, size_t len) { err: CBOR_FREE_BYTE_STRING(pinUvAuthParam); CBOR_FREE_BYTE_STRING(vendorParamByteString); + CBOR_FREE_BYTE_STRING(vendorParamTextString); for (size_t i = 0; i < minPinLengthRPIDs_len; i++) { CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]); } diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 6455290..216e7ee 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -35,6 +35,10 @@ int cbor_get_info() { #else lfields++; #endif + file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF); + if (file_has_data(ef_pin_policy)) { + lfields += 2; + } CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); @@ -164,7 +168,7 @@ int cbor_get_info() { if (phy_data.vid != 0x1050) { #endif CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); - uint8_t enabled_cmds = 3; + uint8_t enabled_cmds = 4; #ifndef ENABLE_EMULATION enabled_cmds += 4; #endif @@ -172,6 +176,7 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY)); #ifndef ENABLE_EMULATION CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS)); @@ -181,6 +186,13 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); #ifndef ENABLE_EMULATION } + if (file_has_data(ef_pin_policy)) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B)); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2)); + } + #endif CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); err: diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 08ef580..72e615b 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -44,6 +44,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { int64_t kty = 2, hmac_alg = 0, crv = 0; CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 }; bool hmac_secret_mc = false; + const bool *pin_complexity_policy = NULL; uint8_t *aut_data = NULL; size_t resp_size = 0; CredExtensions extensions = { 0 }; @@ -162,6 +163,8 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy); + CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); @@ -440,6 +443,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (hmac_secret_mc) { l++; } + if (pin_complexity_policy == ptrue) { + l++; + } if (l > 0) { CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); if (extensions.credBlob.present == true) { @@ -451,12 +457,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect)); } if (extensions.hmac_secret == ptrue) { - CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret")); CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true)); } if (minPinLen > 0) { - CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength")); CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen)); } @@ -511,6 +515,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) { encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len)); } + if (pin_complexity_policy == ptrue) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy")); + file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy))); + } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); ext_len = cbor_encoder_get_buffer_size(&encoder, ext); diff --git a/src/fido/ctap.h b/src/fido/ctap.h index ad248ba..c33b928 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -115,6 +115,7 @@ typedef struct { #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 #define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9 #define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf +#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897 #ifndef ENABLE_EMULATION #define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa #define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd diff --git a/src/fido/files.h b/src/fido/files.h index 176f1d5..d6590e5 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -31,6 +31,7 @@ #define EF_AUTHTOKEN 0x1090 #define EF_PAUTHTOKEN 0x1091 #define EF_MINPINLEN 0x1100 +#define EF_PIN_COMPLEXITY_POLICY 0x1102 #define EF_DEV_CONF 0x1122 #define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF #define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 88eb787..b874742 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -24,7 +24,7 @@ import argparse import platform from binascii import hexlify from threading import Event -from typing import Mapping, Any, Optional, Callable +from typing import List, Mapping, Any, Optional, Callable import struct import urllib.request import json @@ -77,6 +77,7 @@ class VendorConfig(Config): VENDOR_COMMAND_ID = 0x01 VENDOR_PARAM_BYTESTRING = 0x02 VENDOR_PARAM_INT = 0x03 + VENDOR_PARAM_TEXTSTRING = 0x04 class CMD(IntEnum): CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 @@ -86,6 +87,7 @@ class VendorConfig(Config): CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948 CONFIG_PHY_OPTS = 0x969f3b09eceb805f + CONFIG_PIN_POLICY = 0x6c07d70fe96c3897 class RESP(IntEnum): KEY_AGREEMENT = 0x01 @@ -155,6 +157,20 @@ class VendorConfig(Config): }, ) + def pin_policy(self, url: bytes|str = None, policy: int = None): + if (url is not None or policy is not None): + params = { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PIN_POLICY } + if (url is not None): + if (isinstance(url, str)): + url = url.encode() + params[VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING] = url + if (policy is not None): + params[VendorConfig.PARAM.VENDOR_PARAM_INT] = policy + self._call( + Config.CMD.VENDOR_PROTOTYPE, + params, + ) + class Ctap2Vendor(Ctap2): def __init__(self, device: CtapDevice, strict_cbor: bool = True): super().__init__(device=device, strict_cbor=strict_cbor) @@ -486,6 +502,18 @@ class Vendor: def enable_enterprise_attestation(self): self.vcfg.enable_enterprise_attestation() + def set_min_pin_length(self, length, rpids: list[str] = None, url=None): + params = { + Config.PARAM.NEW_MIN_PIN_LENGTH: length, + Config.PARAM.MIN_PIN_LENGTH_RPIDS: rpids if rpids else None, + } + self.vcfg.set_min_pin_length( + min_pin_length=length, + rp_ids=rpids if rpids else None, + ) + self.vcfg.pin_policy(url=url.encode() if url else None) + + def parse_args(): parser = argparse.ArgumentParser() subparser = parser.add_subparsers(title="commands", dest="command") @@ -516,6 +544,11 @@ def parse_args(): parser_mem = subparser.add_parser('memory', help='Get current memory usage.') + parser_pin_policy = subparser.add_parser('pin_policy', help='Manage PIN policy.') + parser_pin_policy.add_argument('length', type=int, help='Minimum PIN length (4-63).') + parser_pin_policy.add_argument('--rpids', help='Comma separated list of Relying Party IDs that have authorization to receive minimum PIN length.') + parser_pin_policy.add_argument('--url', help='URL where the user can consult PIN policy.') + args = parser.parse_args() return args @@ -584,6 +617,14 @@ def memory(vdr: Vendor, args): print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes') print(f'\tFiles: {mem["files"]}') + +def pin_policy(vdr: Vendor, args): + rpids = None + if (args.rpids): + rpids = args.rpids.split(',') + vdr.set_min_pin_length(args.length, rpids, args.url) + + def main(args): print('Pico Fido Tool v1.10') print('Author: Pol Henarejos') @@ -607,6 +648,9 @@ def main(args): phy(vdr, args) elif (args.command == 'memory'): memory(vdr, args) + elif (args.command == 'pin_policy'): + pin_policy(vdr, args) + def run(): args = parse_args()