Adding support for specific vendor HID command (0x41).

It is a self implementation, based on CBOR command.
data[0] conveys the command and the contents mapped in CBOR encoding.
The map uses the authConfig template, where the fist item in the map is the subcommand (enable/disable at this moment), the second is a map of the parameters, the third and fourth are the pinUvParam and pinUvProtocol.

With this format only a single vendor HID command is necessary (0x41), which will be used for all my own commands, by using the command id in data[0] like with CBOR.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos 2022-10-29 19:41:00 +02:00
parent 43cd8869f9
commit e21d985344
No known key found for this signature in database
GPG Key ID: C0095B7870A4CCD3
9 changed files with 342 additions and 54 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
/*
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.

View File

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

121
src/fido/cbor_vendor.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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;
}

View File

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

View File

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

View File

@ -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("<b", sub_cmd)
+ (cbor.encode(params) if params else b"")
)
pin_uv_protocol = self.pin_uv.protocol.VERSION
pin_uv_param = self.pin_uv.protocol.authenticate(self.pin_uv.token, msg)
else:
pin_uv_protocol = None
pin_uv_param = None
return self.ctap.vendor(cmd, sub_cmd, params, pin_uv_protocol, pin_uv_param)
def backup_save(self, filename):
ret = self._call(
Vendor.CMD.VENDOR_BACKUP,
Vendor.SUBCMD.ENABLE,
)
data = ret[Vendor.RESP.PARAM]
d = int.from_bytes(skey.get_secure_key(), 'big')
with open(filename, 'wb') as fp:
fp.write(b'\x01')
fp.write(data)
pk = ec.derive_private_key(d, ec.SECP256R1())
signature = pk.sign(data, ec.ECDSA(hashes.SHA256()))
fp.write(signature)
print('Remember the following words in this order:')
for c in range(24):
coef = (d//(2048**c))%2048
print(f'{(c+1):02d} - {words[coef]}')
def backup_load(self, filename):
d = 0
if (d == 0):
for c in range(24):
word = input(f'Introduce word {(c+1):02d}: ')
while (word not in words):
word = input(f'Word not found. Please, tntroduce the correct word {(c+1):02d}: ')
coef = words.index(word)
d = d+(2048**c)*coef
pk = ec.derive_private_key(d, ec.SECP256R1())
pb = pk.public_key()
with open(filename, 'rb') as fp:
format = fp.read(1)[0]
if (format == 0x1):
data = fp.read(60)
signature = fp.read()
pb.verify(signature, data, ec.ECDSA(hashes.SHA256()))
skey.set_secure_key(pk)
return self._call(
Vendor.CMD.VENDOR_BACKUP,
Vendor.SUBCMD.DISABLE,
{
Vendor.PARAM.PARAM: data
},
)
def parse_args():
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command")
@ -215,12 +394,12 @@ def secure(dev, args):
vcfg.disable_device_aut()
def backup(dev, args):
vcfg = VendorConfig(Ctap2(dev))
vdr = Vendor(Ctap2Vendor(dev))
if (args.subcommand == 'save'):
vcfg.backup_save(args.filename)
vdr.backup_save(args.filename)
elif (args.subcommand == 'load'):
vcfg.backup_load(args.filename)
vdr.backup_load(args.filename)
def main(args):