SafeKey Protocol (CS)
This chapter contains a description of the protocol used for communication with device’s custom features.
Last updated
This chapter contains a description of the protocol used for communication with device’s custom features.
Last updated
To access the custom features, host application should send to the FIDO2 device a FIDO U2F or FIDO2 request, formatted in proper way, as specified in this chapter.
Messages are send over U2F, using U2F_AUTHENTICATE command (or its equivalent for FIDO2). U2F_AUTHENTICATE command is formatted as below:
where:
Off is short from offset
Len is short from length
Data for the custom commands are sent within the U2F_KEY_HANDLE field. Data in U2F_CHALLENGE field are unused due to a possible future change in the API, where they would be provided by the browser itself.
Data for the custom commands are received within the U2F_SIGNATURE field.
The data to send should be prefixed with the Command ID, e.g. as below:
Message
Message
where RESULT is an operation execution result.
Message
Complete Message
After completing all of the chunks, the full message is as follows:
Here all implemented custom commands for Custom Storage handling are listed. Both command parameters and results are transported as a CBOR (Concise Binary Object Representation) encoded structure.
CBOR (Concise Binary Object Representation) is a binary data serialization format loosely based on JSON. Like JSON it allows the transmission of data objects that contain name–value pairs, but in a more concise manner. This increases processing and transfer speeds at the cost of human-readability.
where for given command:
BACKPAR is {SLOTID, IV, HMAC, DATA};
plus + sign means a requirement for operation named by this specific column, whereas minus - sign on the contrary;
column Au, short from authentication, marks authentication requirement with the LOGIN command, before using this command;
column Bt, short from button, marks touch-button press requirement after the command is called, to proceed further;
column Ac, short from activation, marks device’s activation requirement before using the command.
The ID parameter is unique per origin, it could be thought of it, as if internally it would be processed as a logical pair {origin, ID}. commands prefixed with TEST_ are available only in the development build, for unit testing. In the production build the code is not compiled.
Error
CTAP2_ERR_INVALID_CBOR_TYPE - on invalid CBOR data;
CTAP2_ERR_REQUEST_TOO_LARGE - on too long ID;
ERR_NOT_FOUND - on non-existing ID;
ERR_SUCCESS - on success.
Error
ERR_BAD_FORMAT - on invalid CBOR data, or on too long ID;
ERR_ALREADY_IN_DATABASE - on already existing ID;
ERR_FAILED_LOADING_DATA - on read-after-write check fail for just written record;
ERR_SUCCESS - on success.
Error
ERR_NOT_FOUND - on non-existing ID;
CTAP2_ERR_INVALID_CBOR_TYPE - on invalid CBOR data;
CTAP2_ERR_REQUEST_TOO_LARGE - on too long ID;
ERR_BAD_FORMAT - on invalid CBOR data, or on too long ID;
ERR_SUCCESS - on success.
Error
BAD_FORMAT - on PAGE being outside [0,10) range;
CTAP2_ERR_INVALID_CBOR_TYPE - on invalid CBOR data written to the CS;
ERR_SUCCESS - on success.
Error
ERR_NOT_ALLOWED - calling command in current device’s state is not allowed. Such states include being in blocked state (either completely, or only in this power cycle);
ERR_USER_NOT_PRESENT - on not confirming the call by the user;
ERR_INVALID_PIN - on invalid PIN;
CTAP2_ERR_INVALID_CBOR_TYPE - on CBOR parsing issue;
ERR_SUCCESS - on success.
Error
ERR_NOT_ALLOWED - calling command in current device’s state is not allowed. Such states include being in blocked state (either completely, or only in this power cycle);
ERR_BAD_FORMAT - on too small input data;
ERR_INVALID_PIN - when the new PIN is not in the accepted range length (4, 63];
CTAP2_ERR_INVALID_CBOR_TYPE - on invalid input data;
CTAP2_ERR_REQUEST_TOO_LARGE - on too long input data;
ERR_SUCCESS - on success.
Error
ERR_NOT_ALLOWED - calling command in current device’s state is not allowed. Such states include being in blocked state (either completely, or only in this power cycle);
ERR_BAD_FORMAT - on too small input data;
ERR_INVALID_PIN - when the provided PIN is not equal to current one, or when the new PIN is not in the accepted range length (4, 63];
CTAP2_ERR_INVALID_CBOR_TYPE - on invalid input data;
CTAP2_ERR_REQUEST_TOO_LARGE - on too long input data;
ERR_SUCCESS - on success.
Error
ERR_NOT_ALLOWED - if the PIN is not set;
ERR_SUCCESS - on success.
Error
ERR_FAILED_LOADING_DATA - on error reading the slot data;
ERR_BAD_FORMAT - returned when the {SLOTID} is out of range;
ERR_NOT_ALLOWED - command not allowed, when the backup session is not active;
ERR_SUCCESS - on success.
Error
ERR_FAILED_LOADING_DATA - on error writing the backup record data to the device;
ERR_BAD_FORMAT - returned when the {SLOTID} is out of range, or the backup record is empty, or DATA/IV size not a multiply of 16;
ERR_NOT_ALLOWED - command not allowed, when the backup session is not active;
ERR_INVALID_CHECKSUM - if calculated HMAC is not equal to provided one, stop operation;
ERR_ALREADY_IN_DATABASE - record cannot be imported, since there is one already with such ID;
ERR_SUCCESS - on success.
Error
ERR_USER_NOT_PRESENT - on not confirming the call by the user;
ERR_FAILED_LOADING_DATA - on error creating output data - namely SALT;
ERR_BAD_FORMAT - returned when the PASS, or SALT, are too short;
ERR_SUCCESS - on success.
Error
ERR_FAILED_LOADING_DATA - on error creating output data - namely {NONCE, SN};
ERR_NOT_ALLOWED - fails, if activation is in process already (in that case ACTIVATION_FINISH should be called beforehand to close current activation session);
ERR_SUCCESS - on success.
Error
ERR_NOT_ALLOWED - fails, if activation is not in progress already (in that case ACTIVATION_BEGIN should be called beforehand);
ERR_BAD_FORMAT - if signature field is of invalid length, than expected (64 bytes);
ERR_INVALID_SIGNATURE - if the signature is invalid;
ERR_SUCCESS - on success.
Error
ERR_FAILED_LOADING_DATA - fail, if the output CBOR structure cannot be parsed;
ERR_SUCCESS - on success.
All Custom Storage commands requiring authorization should be parametrized with a temporary authorization token (field _TP), merged into the request CBOR structure.
The _TP token could be validated only with the LOGIN command, and will be invalidated automatically after 60 seconds, or after LOGOUT command call.
The _TP parametrization should be done automatically in the high level JavaScript API. For instance for low-level API, in case of command PIN_CHANGE - instead of call arguments {PIN, NEW_PIN}, {PIN, NEW_PIN, _TP} should be used, where _TP contains the value of temporary authentication token.
In the implementation all error names are prefixed with ERR_.
To interact with the device over a browser, a simple high-level API JavaScript is provided.
Wrappers over a low-level API will are provided over each available CS command.
JavaScript high-level API commands list:
Low level API consist of two functions - for sending and receiving - namely:
async function cs_device_receive(cmd) -> data_received.
async function cs_device_send(cmd, data_to_send);
where:
cmd is the command id;
data_to_send is the CBOR structure to send;
data_received is the CBOR structure received from the device.
Underneath these two use standard FIDO WebAuthn API function to send data: navigator.credentials.get
In the example used below, error checks are skipped for clarity
Whole device’s flash layout is as following:
where:
‘Bootloader’ is an application described in the Bootloader chapter;
‘Bootloader data’ is where the latest used firmware version stored, and potential future data usable for the bootloader;
‘Application’ is the main application run on the device, providing FIDO U2F, FIDO2 and CS features;
‘CS state’ means memory reserved for additional data structures, supporting access to the CS;
‘CS’ here means Custom Storage data - here all the CS records are stored in the encrypted form;
‘User data’ is FIDO U2F / FIDO2-related user data, as well as user PIN and the CS’ AES master key in the encrypted form.
All the ranges in the table are provided as [x,y], which means that the range starts from x inclusively, and uses all bytes until y exclusively.
The main STATE structure (which includes e.g. the CS AES master encryption key) is saved at 2 last MCU’s FLASH pages in the user data region:
STATE is a general application structure for the user data, and should not be mistaken with the CS state. Its type - AuthenticatorState - is defined as follows:
Custom Storage (CS) data are kept in the user data memory of the MCU flash. Currently 20 pages in range [-35, -15) are occupied by the data slots (where negative numbers mean pages index counting from the last).
Each page on STM32L432 flash takes 2048 bytes, which for 20 pages gives 40 kB. Structure describing data slot, ext_storage_record, takes 512 bytes, which yields total 80 slots to write. This could be potentially further extended.
At the moment firmware takes about 122 kB out of 256 kB. FIDO2 user data takes about 30 kB. This makes it possible to configure the storage to use up to 104 kB (256-122-30). With 512 bytes data slot size this allows to store up to 208 data slots, or increase the current slots maximum size to 1024 kB.
Data slot structure consist of two fields:
data[512 - 16] - to store the actual user data;
origin[16] - to store the first 16 bytes of the SHA256 hash of the origin of request to store the data record. This will later be compared before the access to the slot.
Full C structure:
The data field contains all the CBOR (description below) supplied data as-is.
For data slots, the only required CBOR field is ID, which cannot be longer than FIELD_SIZE_ID = 100 bytes (size could be changed in ext_storage.h).
The rest of the CBOR map could contain any named fields, which are not containing reserved names (ones prefixed with _). Whole field will be read back on the request.
Example CBOR encoded data:
human-readable representation: {ID=b'this is ID', DATA1=b'any binary data', date=b'2019-07-01'}
CBOR: b'\xa3bIDJthis is IDddateJ2019-07-01eDATA1Oany binary data'
CBOR hex: a36249444a7468697320697320494464646174654a32303139 2d30372d30316544415441314f616e792062696e6172792064 617461
CBOR is a data encoding method, which can be seen as a lower-level JSON.
By the definition from the official site, https://cbor.io, it is:
“The Concise Binary Object Representation (CBOR) is a data format whose design goals include the possibility of extremely small code size, fairly small message size, and extensibility without the need for version negotiation.”
More details on the main site, or in the RFC document: https://tools.ietf.org/html/rfc7049
Just after the decryption, before returning the data for further processing, the origin of the data record is compared against current one.
If the origin mismatch, the decrypted data is removed from RAM, and the low-level read function signalize this specific data slot is used, but inaccessible.
Having this check in the lowest access level guarantees, that all commands will operate only on the data sourced from the same origin, as the current one.