diff --git a/CMakeLists.txt b/CMakeLists.txt index 966475f..f164fc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ target_sources(pico_fido PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_selection.c ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_cred_mgmt.c ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_config.c + ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_vendor.c ) set(HSM_DRIVER "hid") include(pico-hsm-sdk/pico_hsm_sdk_import.cmake) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index 3aa1b2b..ff26b24 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -27,7 +27,6 @@ const bool _btrue = true, _bfalse = false; -extern int cbor_process(const uint8_t *data, size_t len); int cbor_reset(); int cbor_get_info(); int cbor_make_credential(const uint8_t *data, size_t len); @@ -37,35 +36,42 @@ int cbor_get_next_assertion(const uint8_t *data, size_t len); int cbor_selection(); int cbor_cred_mgmt(const uint8_t *data, size_t len); int cbor_config(const uint8_t *data, size_t len); +int cbor_vendor(const uint8_t *data, size_t len); const uint8_t aaguid[16] = {0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45}; // First 16 bytes of SHA256("Pico FIDO2") -const uint8_t *cbor_data = NULL; -size_t cbor_len = 0; +static const uint8_t *cbor_data = NULL; +static size_t cbor_len = 0; +static uint8_t cmd = 0; -int cbor_parse(const uint8_t *data, size_t len) { +int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) { if (len == 0) return CTAP1_ERR_INVALID_LEN; DEBUG_DATA(data+1,len-1); driver_prepare_response(); - if (data[0] == CTAP_MAKE_CREDENTIAL) - return cbor_make_credential(data + 1, len - 1); - if (data[0] == CTAP_GET_INFO) - return cbor_get_info(); - else if (data[0] == CTAP_RESET) - return cbor_reset(); - else if (data[0] == CTAP_CLIENT_PIN) - return cbor_client_pin(data + 1, len - 1); - else if (data[0] == CTAP_GET_ASSERTION) - return cbor_get_assertion(data + 1, len - 1, false); - else if (data[0] == CTAP_GET_NEXT_ASSERTION) - return cbor_get_next_assertion(data + 1, len - 1); - else if (data[0] == CTAP_SELECTION) - return cbor_selection(); - else if (data[0] == CTAP_CREDENTIAL_MGMT) - return cbor_cred_mgmt(data + 1, len - 1); - else if (data[0] == CTAP_CONFIG) - return cbor_config(data + 1, len - 1); + if (cmd == CTAPHID_CBOR) { + if (data[0] == CTAP_MAKE_CREDENTIAL) + return cbor_make_credential(data + 1, len - 1); + if (data[0] == CTAP_GET_INFO) + return cbor_get_info(); + else if (data[0] == CTAP_RESET) + return cbor_reset(); + else if (data[0] == CTAP_CLIENT_PIN) + return cbor_client_pin(data + 1, len - 1); + else if (data[0] == CTAP_GET_ASSERTION) + return cbor_get_assertion(data + 1, len - 1, false); + else if (data[0] == CTAP_GET_NEXT_ASSERTION) + return cbor_get_next_assertion(data + 1, len - 1); + else if (data[0] == CTAP_SELECTION) + return cbor_selection(); + else if (data[0] == CTAP_CREDENTIAL_MGMT) + return cbor_cred_mgmt(data + 1, len - 1); + else if (data[0] == CTAP_CONFIG) + return cbor_config(data + 1, len - 1); + } + else if (cmd == CTAP_VENDOR_CBOR) { + return cbor_vendor(data, len); + } return CTAP2_ERR_INVALID_CBOR; } @@ -81,7 +87,7 @@ void cbor_thread() { break; } - apdu.sw = cbor_parse(cbor_data, cbor_len); + apdu.sw = cbor_parse(cmd, cbor_data, cbor_len); if (apdu.sw == 0) DEBUG_DATA(res_APDU + 1, res_APDU_size); @@ -91,9 +97,10 @@ void cbor_thread() { } } -int cbor_process(const uint8_t *data, size_t len) { +int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) { cbor_data = data; cbor_len = len; + cmd = last_cmd; res_APDU = ctap_resp->init.data + 1; res_APDU_size = 0; return 1; diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 91d132b..e822887 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -1,4 +1,3 @@ - /* * This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido). * Copyright (c) 2022 Pol Henarejos. @@ -236,26 +235,6 @@ int cbor_config(const uint8_t *data, size_t len) { } goto err; //No return } - else if (vendorCommandId == CTAP_CONFIG_BACKUP) { - if (vendorAutCt.present == false) { // Save - if (has_keydev_dec == false) - CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); - - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); - - CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc))); - } - else { // Load - uint8_t zeros[32]; - memset(zeros, 0, sizeof(zeros)); - flash_write_data_to_file(ef_keydev_enc, vendorAutCt.data, vendorAutCt.len); - flash_write_data_to_file(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0 - flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes - low_flash_available(); - goto err; - } - } else { CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); } diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 435f920..6011103 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -1,4 +1,3 @@ - /* * This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido). * Copyright (c) 2022 Pol Henarejos. diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index b8caeaf..842eba4 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -80,11 +80,10 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_KEY_AGREEMENT)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_UNLOCK)); - CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_BACKUP)); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c new file mode 100644 index 0000000..d22a9eb --- /dev/null +++ b/src/fido/cbor_vendor.c @@ -0,0 +1,121 @@ +/* + * This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido). + * 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 . + */ + +#include "ctap2_cbor.h" +#include "fido.h" +#include "ctap.h" +#include "files.h" +#include "apdu.h" +#include "hsm.h" + +extern bool has_keydev_dec; + +int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { + CborParser parser; + CborValue map; + CborError error = CborNoError; + CborByteString pinUvAuthParam = {0}, vendorParam = {0}; + size_t resp_size = 0; + uint64_t vendorCmd = 0, pinUvAuthProtocol = 0; + CborEncoder encoder, mapEncoder; + + CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map)); + uint64_t val_c = 1; + CBOR_PARSE_MAP_START(map, 1) { + uint64_t val_u = 0; + CBOR_FIELD_GET_UINT(val_u, 1); + if (val_c <= 1 && val_c != val_u) + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + if (val_u < val_c) + CBOR_ERROR(CTAP2_ERR_INVALID_CBOR); + val_c = val_u + 1; + if (val_u == 0x01) { + CBOR_FIELD_GET_UINT(vendorCmd, 1); + } + else if (val_u == 0x02) { + uint64_t subpara = 0; + CBOR_PARSE_MAP_START(_f1, 2) { + CBOR_FIELD_GET_UINT(subpara, 2); + if (subpara == 0x01) { + CBOR_FIELD_GET_BYTES(vendorParam, 2); + } + else + CBOR_ADVANCE(2); + } + CBOR_PARSE_MAP_END(_f1, 2); + } + else if (val_u == 0x03) { + CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1); + } + else if (val_u == 0x04) { + CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1); + } + } + CBOR_PARSE_MAP_END(map, 1); + + cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); + + if (cmd == CTAP_VENDOR_BACKUP) { + if (vendorCmd == 0x01) { + if (has_keydev_dec == false) + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); + + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc))); + } + else if (vendorCmd == 0x02) { + if (vendorParam.present == false) + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + uint8_t zeros[32]; + memset(zeros, 0, sizeof(zeros)); + flash_write_data_to_file(ef_keydev_enc, vendorParam.data, vendorParam.len); + flash_write_data_to_file(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0 + flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes + low_flash_available(); + goto err; + } + else { + CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); + } + } + else + CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); + CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); + resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1); + + err: + CBOR_FREE_BYTE_STRING(pinUvAuthParam); + CBOR_FREE_BYTE_STRING(vendorParam); + + if (error != CborNoError) { + if (error == CborErrorImproperValue) + return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; + return error; + } + res_APDU_size = resp_size; + return 0; +} + +int cbor_vendor(const uint8_t *data, size_t len) { + if (len == 0) + return CTAP1_ERR_INVALID_LEN; + if (data[0] == CTAP_VENDOR_BACKUP) + return cbor_vendor_generic(data[0], data + 1, len - 1); + return CTAP2_ERR_INVALID_CBOR; +} diff --git a/src/fido/ctap.h b/src/fido/ctap.h index 3386b56..10d5e79 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -119,7 +119,10 @@ typedef struct { #define CTAP_CONFIG_AUT 0x03e43f56b34285e2 #define CTAP_CONFIG_KEY_AGREEMENT 0x1831a40f04a25ed9 #define CTAP_CONFIG_UNLOCK 0x54365966c9a74770 -#define CTAP_CONFIG_BACKUP 0x6b1ede62beff0d5e + +#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1) + +#define CTAP_VENDOR_BACKUP 0x01 // Command status responses diff --git a/src/fido/ctap2_cbor.h b/src/fido/ctap2_cbor.h index 8b25fd3..de632c6 100644 --- a/src/fido/ctap2_cbor.h +++ b/src/fido/ctap2_cbor.h @@ -25,7 +25,7 @@ extern uint8_t *driver_prepare_response(); extern void driver_exec_finished(size_t size_next); -extern int cbor_process(const uint8_t *data, size_t len); +extern int cbor_process(uint8_t, const uint8_t *data, size_t len); extern const uint8_t aaguid[16]; extern const bool _btrue, _bfalse; diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py index 606cae3..a16953b 100644 --- a/tools/pico-fido-tool.py +++ b/tools/pico-fido-tool.py @@ -24,12 +24,20 @@ import argparse import platform from binascii import hexlify from words import words +from threading import Event +from typing import Mapping, Any, Optional, Callable +import struct +from enum import IntEnum, unique try: from fido2.ctap2.config import Config from fido2.ctap2 import Ctap2 - from fido2.hid import CtapHidDevice + from fido2.hid import CtapHidDevice, CTAPHID from fido2.utils import bytes2int, int2bytes + from fido2 import cbor + from fido2.ctap import CtapDevice, CtapError + from fido2.ctap2.pin import PinProtocol, _PinUv + from fido2.ctap2.base import args except: print('ERROR: fido2 module not found! Install fido2 package.\nTry with `pip install fido2`') sys.exit(-1) @@ -191,6 +199,177 @@ class VendorConfig(Config): }, ) +class Ctap2Vendor(Ctap2): + def __init__(self, device: CtapDevice, strict_cbor: bool = True): + super().__init__(device=device, strict_cbor=strict_cbor) + + + def send_vendor( + self, + cmd: int, + data: Optional[Mapping[int, Any]] = None, + *, + event: Optional[Event] = None, + on_keepalive: Optional[Callable[[int], None]] = None, + ) -> Mapping[int, Any]: + """Sends a VENDOR message to the device, and waits for a response. + + :param cmd: The command byte of the request. + :param data: The payload to send (to be CBOR encoded). + :param event: Optional threading.Event used to cancel the request. + :param on_keepalive: Optional function called when keep-alive is sent by + the authenticator. + """ + request = struct.pack(">B", cmd) + if data is not None: + request += cbor.encode(data) + response = self.device.call(CTAPHID.VENDOR_FIRST + 1, request, event, on_keepalive) + status = response[0] + if status != 0x00: + raise CtapError(status) + enc = response[1:] + if not enc: + return {} + decoded = cbor.decode(enc) + if self._strict_cbor: + expected = cbor.encode(decoded) + if expected != enc: + raise ValueError( + "Non-canonical CBOR from Authenticator.\n" + f"Got: {enc.hex()}\nExpected: {expected.hex()}" + ) + if isinstance(decoded, Mapping): + return decoded + raise TypeError("Decoded value of wrong type") + + def vendor( + self, + cmd: int, + sub_cmd: int, + sub_cmd_params: Optional[Mapping[int, Any]] = None, + pin_uv_protocol: Optional[int] = None, + pin_uv_param: Optional[bytes] = None, + ) -> Mapping[int, Any]: + """CTAP2 authenticator vendor command. + + This command is used to configure various authenticator features through the + use of its subcommands. + + This method is not intended to be called directly. It is intended to be used by + an instance of the Config class. + + :param sub_cmd: A Config sub command. + :param sub_cmd_params: Sub command specific parameters. + :param pin_uv_protocol: PIN/UV auth protocol version used. + :param pin_uv_param: PIN/UV Auth parameter. + """ + return self.send_vendor( + cmd, + args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param), + ) + + +class Vendor: + """Implementation of the CTAP2.1 Authenticator Vendor API. It is vendor implementation. + + :param ctap: An instance of a CTAP2Vendor object. + :param pin_uv_protocol: An instance of a PinUvAuthProtocol. + :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. + """ + + @unique + class CMD(IntEnum): + VENDOR_BACKUP = 0x01 + + @unique + class PARAM(IntEnum): + PARAM = 0x01 + + class SUBCMD(IntEnum): + ENABLE = 0x01 + DISABLE = 0x02 + + class RESP(IntEnum): + PARAM = 0x01 + + def __init__( + self, + ctap: Ctap2Vendor, + pin_uv_protocol: Optional[PinProtocol] = None, + pin_uv_token: Optional[bytes] = None, + ): + self.ctap = ctap + self.pin_uv = ( + _PinUv(pin_uv_protocol, pin_uv_token) + if pin_uv_protocol and pin_uv_token + else None + ) + + def _call(self, cmd, sub_cmd, params=None): + if params: + params = {k: v for k, v in params.items() if v is not None} + else: + params = None + if self.pin_uv: + msg = ( + b"\xff" * 32 + + b"\x0d" + + struct.pack("