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("