Add support to PIN POLICY URL via VendorConfig.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2025-09-11 19:20:20 +02:00
parent e4f8caa1ba
commit 9b254a0738
6 changed files with 91 additions and 5 deletions

View File

@@ -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]);
}

View File

@@ -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:

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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()