Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
6068fb9868 | |||
74cbe00e3b | |||
7e490f17fc | |||
9bb706987f | |||
88a759566d | |||
44fa3bbb8e | |||
89e9296825 | |||
873d65b823 | |||
eb8e3ed46a | |||
8b97276e32 | |||
78579c27dc | |||
ca80329b4c | |||
7a49169492 | |||
46dd4fe818 | |||
c71bbd8689 | |||
7068be9cd5 | |||
f8635f1682 | |||
ffa9ad4923 | |||
5fc8d214fd | |||
5f49f4680e | |||
86393c46b4 | |||
4cc72bcd97 | |||
5e0edf3e11 | |||
bd810fff87 | |||
d9fb508949 | |||
c2b7acb6aa | |||
4690a7ce65 | |||
61f24d142d | |||
f5c6f99423 | |||
96de4f0850 | |||
331ebdfccf | |||
a6a6d653ad | |||
928bc0216d | |||
6d52c9ede7 |
@ -25,3 +25,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Merge of NFC functionality branch
|
||||
- Bug fix with compiled USB name being too long causing buffer overrun
|
||||
- Change upper byte of counter from `0xff` to `0x7f` to fix issues with some websites.
|
||||
|
||||
## [2.1.0] - 2019-03-31
|
||||
|
||||
WARNING: This update may break previous registrations! This is because we fixed the U2F counter for good (rather than arbitrarily set the upper byte high for backwards-compatibility reasons, which ends up causing other issues).
|
||||
|
||||
- Adds hmac-secret extension support. This extension is used for generating 32 or 64 byte symmetric keys using parameters from the platform and secrets on the authenticator. It's used by Windows Hello - - for offline authentication.
|
||||
- Fix bug in FIDO auth, where setting the pin requires all previous registrations to use pin. Only UV bit needs to be cleared.
|
||||
- Slightly change serial emulation USB descriptor to make it less abused by Linux Modem Manager.
|
||||
|
@ -119,8 +119,15 @@ Look at the issues to see what is currently being worked on. Feel free to add is
|
||||
# License
|
||||
|
||||
Solo is fully open source.
|
||||
|
||||
All software, unless otherwise noted, is dual licensed under Apache 2.0 and MIT.
|
||||
You may use Solo under the terms of either the Apache 2.0 license or MIT license.
|
||||
You may use Solo software under the terms of either the Apache 2.0 license or MIT license.
|
||||
|
||||
All hardware, unless otherwise noted, is dual licensed under CERN and CC-BY-SA.
|
||||
You may use Solo hardware under the terms of either the CERN 2.1 license or CC-BY-SA 4.0 license.
|
||||
|
||||
All documentation, unless otherwise noted, is licensed under CC-BY-SA.
|
||||
You may use Solo documentation under the terms of the CC-BY-SA 4.0 license
|
||||
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo?ref=badge_large)
|
||||
|
@ -1 +1 @@
|
||||
2.0.0
|
||||
2.1.0
|
||||
|
@ -1,5 +1,5 @@
|
||||
To build, develop and debug the firmware for the STM32L432. This will work
|
||||
for Solo Hacker, the Nucleo development board, or you own homemade Solo.
|
||||
for Solo Hacker, the Nucleo development board, or your own homemade Solo.
|
||||
|
||||
There exists a development board [NUCLEO-L432KC](https://www.st.com/en/evaluation-tools/nucleo-l432kc.html) you can use; The board does contain a debugger, so all you need is a USB cable (and some [udev](/udev) [rules](https://rust-embedded.github.io/book/intro/install/linux.html#udev-rules)).
|
||||
|
||||
@ -7,7 +7,7 @@ There exists a development board [NUCLEO-L432KC](https://www.st.com/en/evaluatio
|
||||
|
||||
Install the [latest ARM compiler toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) for your system. We recommend getting the latest compilers from ARM.
|
||||
|
||||
You can also install the ARM toolchain using a package manage like `apt-get` or `pacman`,
|
||||
You can also install the ARM toolchain using a package manager like `apt-get` or `pacman`,
|
||||
but be warned they might be out of date. Typically it will be called `gcc-arm-none-eabi binutils-arm-none-eabi`.
|
||||
|
||||
To program your build, you'll need one of the following programs.
|
||||
@ -52,7 +52,7 @@ make build-hacker DEBUG=1
|
||||
```
|
||||
|
||||
If you use `DEBUG=2`, that means Solo will not boot until something starts reading
|
||||
it's debug messages. So it basically it waits to tether to a serial terminal so that you don't
|
||||
its debug messages. So it basically waits to tether to a serial terminal so that you don't
|
||||
miss any debug messages.
|
||||
|
||||
We recommend using our `solo` tool as a serial emulator since it will automatically
|
||||
|
@ -5,22 +5,22 @@ and easy to understand, especially when paired with a high level overview.
|
||||
|
||||
## FIDO2 codebase
|
||||
|
||||
* main.c - calls high level functions and implements event loop.
|
||||
* `main.c` - calls high level functions and implements event loop.
|
||||
|
||||
* ctaphid.c - implements [USBHID protocol](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#usb) for FIDO.
|
||||
* `ctaphid.c` - implements [USBHID protocol](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#usb) for FIDO.
|
||||
|
||||
* u2f.c - implements [U2F protocol](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html).
|
||||
* `u2f.c` - implements [U2F protocol](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html).
|
||||
|
||||
* ctap.c - implements [CTAP2 protocol](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html).
|
||||
* `ctap.c` - implements [CTAP2 protocol](https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html).
|
||||
|
||||
* ctap_parse.c - implements parsing for CTAP protocol.
|
||||
* `ctap_parse.c` - implements parsing for CTAP protocol.
|
||||
* this could use some work minimizing.
|
||||
|
||||
* log.c - embedded friendly debug logging.
|
||||
* `log.c` - embedded friendly debug logging.
|
||||
|
||||
* crypto.c - software implementation of the crypto needs of the application. Generally this will be copied and edited for different platforms. API defined in crypto.h should be the same.
|
||||
* `crypto.c` - software implementation of the crypto needs of the application. Generally this will be copied and edited for different platforms. API defined in `crypto.h` should be the same.
|
||||
|
||||
* device.h - definitions of functions that are platform specific and should be implemented separately. See device.c in any of the implementations to see examples.
|
||||
* `device.h` - definitions of functions that are platform specific and should be implemented separately. See `device.c` in any of the implementations to see examples.
|
||||
|
||||
## Data flow
|
||||
|
||||
|
@ -3,7 +3,7 @@ Documentation of the `master` branch is deployed to Netlify automatically.
|
||||
To host or develop locally:
|
||||
|
||||
```
|
||||
pip install mkdocs mkdocs-material
|
||||
pip install mkdocs mkdocs-material markdown-include
|
||||
```
|
||||
|
||||
`mkdocs serve` and visit [localhost:8000](http://localhost:8000).
|
||||
|
@ -22,8 +22,8 @@ for FIDO2 operation.
|
||||
When you register a service with a FIDO2 or U2F authenticator, the
|
||||
authenticator must generate a new keypair unique to that service. This keypair
|
||||
could be stored on the authenticator to be used in subsequent authentications,
|
||||
but now a certain amount of memory needs to be allocated for this. On embedded
|
||||
devices, there isn't much memory to spare and users will allows frustratingly
|
||||
but a certain amount of memory would need to be allocated for this. On embedded
|
||||
devices, there isn't much memory to spare and users would frustratingly
|
||||
hit the limit of this memory.
|
||||
|
||||
The answer to this problem is to do key wrapping. The authenticator just
|
||||
@ -39,7 +39,7 @@ In essence, the following happens at registration.
|
||||
3. Return `P` and `R` to service. (`R` is in `KEYID` parameter)
|
||||
4. Service stores `P` and `R`.
|
||||
|
||||
Now on authenication.
|
||||
Now on authentication.
|
||||
|
||||
1. Service issues authentication request with `R` in `KEYID` parameter.
|
||||
2. \* Authenticator generates `K` by calculating `HMAC(M,R)`.
|
||||
|
@ -1,4 +1,4 @@
|
||||
Welcome to the technical documentation for [solokeys/solo](https://github.com/solokeys/solo).
|
||||
|
||||
For now, you can read the repository `README.md`, more documentation to come!
|
||||
Use the table of contents on the left to browse this documentation.
|
||||
|
||||
|
224
fido2/ctap.c
224
fido2/ctap.c
@ -11,6 +11,7 @@
|
||||
#include "cbor.h"
|
||||
|
||||
#include "ctap.h"
|
||||
#include "u2f.h"
|
||||
#include "ctaphid.h"
|
||||
#include "ctap_parse.h"
|
||||
#include "ctap_errors.h"
|
||||
@ -117,41 +118,12 @@ uint8_t ctap_get_info(CborEncoder * encoder)
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encode_uint(&map, RESP_maxMsgSize);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_int(&map, CTAP_MAX_MESSAGE_SIZE);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encode_uint(&map, RESP_pinProtocols);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encoder_create_array(&map, &pins, 1);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_int(&pins, 1);
|
||||
check_ret(ret);
|
||||
}
|
||||
ret = cbor_encoder_close_container(&map, &pins);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ret = cbor_encode_uint(&map, RESP_options);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encoder_create_map(&map, &options,4);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_text_string(&options, "plat", 4);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_boolean(&options, 0); // Not attached to platform
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encode_text_string(&options, "rk", 2);
|
||||
check_ret(ret);
|
||||
{
|
||||
@ -175,6 +147,15 @@ uint8_t ctap_get_info(CborEncoder * encoder)
|
||||
// ret = cbor_encode_boolean(&options, 0);
|
||||
// check_ret(ret);
|
||||
// }
|
||||
|
||||
ret = cbor_encode_text_string(&options, "plat", 4);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_boolean(&options, 0); // Not attached to platform
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
|
||||
ret = cbor_encode_text_string(&options, "clientPin", 9);
|
||||
check_ret(ret);
|
||||
{
|
||||
@ -188,6 +169,30 @@ uint8_t ctap_get_info(CborEncoder * encoder)
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encode_uint(&map, RESP_maxMsgSize);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_int(&map, CTAP_MAX_MESSAGE_SIZE);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encode_uint(&map, RESP_pinProtocols);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encoder_create_array(&map, &pins, 1);
|
||||
check_ret(ret);
|
||||
{
|
||||
ret = cbor_encode_int(&pins, 1);
|
||||
check_ret(ret);
|
||||
}
|
||||
ret = cbor_encoder_close_container(&map, &pins);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
ret = cbor_encoder_close_container(encoder, &map);
|
||||
@ -427,6 +432,12 @@ static int ctap_make_extensions(CTAP_extensions * ext, uint8_t * ext_encoder_buf
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int get_credential_id_size(CTAP_credentialDescriptor * cred)
|
||||
{
|
||||
if (cred->type == PUB_KEY_CRED_CTAP1)
|
||||
return U2F_KEY_HANDLE_SIZE;
|
||||
return sizeof(CredentialId);
|
||||
}
|
||||
|
||||
static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * auth_data_buf, uint32_t * len, CTAP_credInfo * credInfo)
|
||||
{
|
||||
@ -651,11 +662,25 @@ uint8_t ctap_add_attest_statement(CborEncoder * map, uint8_t * sigder, int len)
|
||||
// Return 1 if credential belongs to this token
|
||||
int ctap_authenticate_credential(struct rpId * rp, CTAP_credentialDescriptor * desc)
|
||||
{
|
||||
uint8_t rpIdHash[32];
|
||||
uint8_t tag[16];
|
||||
|
||||
make_auth_tag(desc->credential.id.rpIdHash, desc->credential.id.nonce, desc->credential.id.count, tag);
|
||||
switch(desc->type)
|
||||
{
|
||||
case PUB_KEY_CRED_PUB_KEY:
|
||||
make_auth_tag(desc->credential.id.rpIdHash, desc->credential.id.nonce, desc->credential.id.count, tag);
|
||||
return (memcmp(desc->credential.id.tag, tag, CREDENTIAL_TAG_SIZE) == 0);
|
||||
break;
|
||||
case PUB_KEY_CRED_CTAP1:
|
||||
printf1(TAG_CTAP,"PUB_KEY_CRED_CTAP1\r\n");
|
||||
crypto_sha256_init();
|
||||
crypto_sha256_update(rp->id, rp->size);
|
||||
crypto_sha256_final(rpIdHash);
|
||||
return u2f_authenticate_credential((struct u2f_key_handle *)&desc->credential.id, rpIdHash);
|
||||
break;
|
||||
}
|
||||
|
||||
return (memcmp(desc->credential.id.tag, tag, CREDENTIAL_TAG_SIZE) == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -730,6 +755,14 @@ uint8_t ctap_make_credential(CborEncoder * encoder, uint8_t * request, int lengt
|
||||
CborEncoder map;
|
||||
ret = cbor_encoder_create_map(encoder, &map, 3);
|
||||
check_ret(ret);
|
||||
|
||||
{
|
||||
ret = cbor_encode_int(&map,RESP_fmt);
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_text_stringz(&map, "packed");
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
uint32_t auth_data_sz = sizeof(auth_data_buf);
|
||||
|
||||
ret = ctap_make_auth_data(&MC.rp, &map, auth_data_buf, &auth_data_sz,
|
||||
@ -763,13 +796,6 @@ uint8_t ctap_make_credential(CborEncoder * encoder, uint8_t * request, int lengt
|
||||
ret = ctap_add_attest_statement(&map, sigder, sigder_sz);
|
||||
check_retr(ret);
|
||||
|
||||
{
|
||||
ret = cbor_encode_int(&map,RESP_fmt);
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_text_stringz(&map, "packed");
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encoder_close_container(encoder, &map);
|
||||
check_ret(ret);
|
||||
return CTAP1_ERR_SUCCESS;
|
||||
@ -797,6 +823,15 @@ static uint8_t ctap_add_credential_descriptor(CborEncoder * map, CTAP_credential
|
||||
ret = cbor_encoder_create_map(map, &desc, 2);
|
||||
check_ret(ret);
|
||||
|
||||
{
|
||||
ret = cbor_encode_text_string(&desc, "id", 2);
|
||||
check_ret(ret);
|
||||
|
||||
ret = cbor_encode_byte_string(&desc, (uint8_t*)&cred->credential.id,
|
||||
get_credential_id_size(cred));
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
{
|
||||
ret = cbor_encode_text_string(&desc, "type", 4);
|
||||
check_ret(ret);
|
||||
@ -804,13 +839,7 @@ static uint8_t ctap_add_credential_descriptor(CborEncoder * map, CTAP_credential
|
||||
ret = cbor_encode_text_string(&desc, "public-key", 10);
|
||||
check_ret(ret);
|
||||
}
|
||||
{
|
||||
ret = cbor_encode_text_string(&desc, "id", 2);
|
||||
check_ret(ret);
|
||||
|
||||
ret = cbor_encode_byte_string(&desc, (uint8_t*)&cred->credential.id, sizeof(CredentialId));
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encoder_close_container(map, &desc);
|
||||
check_ret(ret);
|
||||
@ -843,6 +872,13 @@ uint8_t ctap_add_user_entity(CborEncoder * map, CTAP_userEntity * user)
|
||||
|
||||
if (dispname)
|
||||
{
|
||||
|
||||
ret = cbor_encode_text_string(&entity, "icon", 4);
|
||||
check_ret(ret);
|
||||
|
||||
ret = cbor_encode_text_stringz(&entity, (const char *)user->icon);
|
||||
check_ret(ret);
|
||||
|
||||
ret = cbor_encode_text_string(&entity, "name", 4);
|
||||
check_ret(ret);
|
||||
|
||||
@ -855,13 +891,6 @@ uint8_t ctap_add_user_entity(CborEncoder * map, CTAP_userEntity * user)
|
||||
ret = cbor_encode_text_stringz(&entity, (const char *)user->displayName);
|
||||
check_ret(ret);
|
||||
|
||||
ret = cbor_encode_text_string(&entity, "icon", 4);
|
||||
check_ret(ret);
|
||||
|
||||
ret = cbor_encode_text_stringz(&entity, (const char *)user->icon);
|
||||
check_ret(ret);
|
||||
|
||||
|
||||
}
|
||||
|
||||
ret = cbor_encoder_close_container(map, &entity);
|
||||
@ -997,24 +1026,25 @@ static CTAP_credentialDescriptor * pop_credential()
|
||||
}
|
||||
|
||||
// adds 2 to map, or 3 if add_user is true
|
||||
uint8_t ctap_end_get_assertion(CborEncoder * map, CTAP_credentialDescriptor * cred, uint8_t * auth_data_buf, uint8_t * clientDataHash, int add_user)
|
||||
uint8_t ctap_end_get_assertion(CborEncoder * map, CTAP_credentialDescriptor * cred, uint8_t * auth_data_buf, unsigned int auth_data_buf_sz, uint8_t * clientDataHash)
|
||||
{
|
||||
int ret;
|
||||
uint8_t sigbuf[64];
|
||||
uint8_t sigder[72];
|
||||
int sigder_sz;
|
||||
|
||||
if (add_user)
|
||||
{
|
||||
printf1(TAG_GREEN, "adding user details to output\r\n");
|
||||
ret = ctap_add_user_entity(map, &cred->credential.user);
|
||||
check_retr(ret);
|
||||
}
|
||||
|
||||
ret = ctap_add_credential_descriptor(map, cred);
|
||||
ret = ctap_add_credential_descriptor(map, cred); // 1
|
||||
check_retr(ret);
|
||||
|
||||
crypto_ecc256_load_key((uint8_t*)&cred->credential.id, sizeof(CredentialId), NULL, 0);
|
||||
{
|
||||
ret = cbor_encode_int(map,RESP_authData); // 2
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_byte_string(map, auth_data_buf, auth_data_buf_sz);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
unsigned int cred_size = get_credential_id_size(cred);
|
||||
crypto_ecc256_load_key((uint8_t*)&cred->credential.id, cred_size, NULL, 0);
|
||||
|
||||
#ifdef ENABLE_U2F_EXTENSIONS
|
||||
if ( extend_fido2(&cred->credential.id, sigder) )
|
||||
@ -1028,11 +1058,20 @@ uint8_t ctap_end_get_assertion(CborEncoder * map, CTAP_credentialDescriptor * cr
|
||||
}
|
||||
|
||||
{
|
||||
ret = cbor_encode_int(map, RESP_signature);
|
||||
ret = cbor_encode_int(map, RESP_signature); // 3
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_byte_string(map, sigder, sigder_sz);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
if (cred->credential.user.id_size)
|
||||
{
|
||||
printf1(TAG_GREEN, "adding user details to output\r\n");
|
||||
ret = ctap_add_user_entity(map, &cred->credential.user); // 4
|
||||
check_retr(ret);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1051,9 +1090,8 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder)
|
||||
}
|
||||
|
||||
auth_data_update_count(&authData);
|
||||
int add_user_info = cred->credential.user.id_size;
|
||||
|
||||
if (add_user_info)
|
||||
if (cred->credential.user.id_size)
|
||||
{
|
||||
printf1(TAG_GREEN, "adding user info to assertion response\r\n");
|
||||
ret = cbor_encoder_create_map(encoder, &map, 4);
|
||||
@ -1064,18 +1102,9 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder)
|
||||
ret = cbor_encoder_create_map(encoder, &map, 3);
|
||||
}
|
||||
|
||||
|
||||
check_ret(ret);
|
||||
printf1(TAG_RED, "RPID hash: "); dump_hex1(TAG_RED, authData.rpIdHash, 32);
|
||||
|
||||
{
|
||||
ret = cbor_encode_int(&map,RESP_authData);
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_byte_string(&map, (uint8_t *)&authData, sizeof(CTAP_authDataHeader));
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
|
||||
// if only one account for this RP, null out the user details
|
||||
if (!getAssertionState.user_verified)
|
||||
{
|
||||
@ -1083,8 +1112,7 @@ uint8_t ctap_get_next_assertion(CborEncoder * encoder)
|
||||
memset(cred->credential.user.name, 0, USER_NAME_LIMIT);
|
||||
}
|
||||
|
||||
|
||||
ret = ctap_end_get_assertion(&map, cred, (uint8_t *)&authData, getAssertionState.clientDataHash, add_user_info);
|
||||
ret = ctap_end_get_assertion(&map, cred, (uint8_t *)&authData, sizeof(CTAP_authDataHeader), getAssertionState.clientDataHash);
|
||||
check_retr(ret);
|
||||
|
||||
ret = cbor_encoder_close_container(encoder, &map);
|
||||
@ -1127,13 +1155,12 @@ uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length)
|
||||
printf1(TAG_GA, "ALLOW_LIST has %d creds\n", GA.credLen);
|
||||
int validCredCount = ctap_filter_invalid_credentials(&GA);
|
||||
|
||||
int add_user_info = GA.creds[validCredCount - 1].credential.user.id_size;
|
||||
if (validCredCount > 1)
|
||||
{
|
||||
map_size += 1;
|
||||
}
|
||||
|
||||
if (add_user_info)
|
||||
if (GA.creds[validCredCount - 1].credential.user.id_size)
|
||||
{
|
||||
map_size += 1;
|
||||
}
|
||||
@ -1166,63 +1193,56 @@ uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length)
|
||||
printf1(TAG_GA,"CRED ID (# %d)\n", GA.creds[j].credential.id.count);
|
||||
}
|
||||
|
||||
if (validCredCount > 1)
|
||||
{
|
||||
ret = cbor_encode_int(&map, RESP_numberOfCredentials);
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_int(&map, validCredCount);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
CTAP_credentialDescriptor * cred = &GA.creds[validCredCount - 1];
|
||||
|
||||
GA.extensions.hmac_secret.credential = &cred->credential;
|
||||
|
||||
uint32_t auth_data_buf_sz = sizeof(auth_data_buf);
|
||||
|
||||
#ifdef ENABLE_U2F_EXTENSIONS
|
||||
if ( is_extension_request((uint8_t*)&GA.creds[validCredCount - 1].credential.id, sizeof(CredentialId)) )
|
||||
{
|
||||
ret = cbor_encode_int(&map,RESP_authData);
|
||||
check_ret(ret);
|
||||
memset(auth_data_buf,0,sizeof(CTAP_authDataHeader));
|
||||
ret = cbor_encode_byte_string(&map, auth_data_buf, sizeof(CTAP_authDataHeader));
|
||||
check_ret(ret);
|
||||
auth_data_buf_sz = sizeof(CTAP_authDataHeader);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
uint32_t len = sizeof(auth_data_buf);
|
||||
ret = ctap_make_auth_data(&GA.rp, &map, auth_data_buf, &len, NULL);
|
||||
|
||||
ret = ctap_make_auth_data(&GA.rp, &map, auth_data_buf, &auth_data_buf_sz, NULL);
|
||||
check_retr(ret);
|
||||
|
||||
((CTAP_authData *)auth_data_buf)->head.flags &= ~(1 << 2);
|
||||
((CTAP_authData *)auth_data_buf)->head.flags |= (getAssertionState.user_verified << 2);
|
||||
|
||||
|
||||
{
|
||||
unsigned int ext_encoder_buf_size = sizeof(auth_data_buf) - len;
|
||||
uint8_t * ext_encoder_buf = auth_data_buf + len;
|
||||
unsigned int ext_encoder_buf_size = sizeof(auth_data_buf) - auth_data_buf_sz;
|
||||
uint8_t * ext_encoder_buf = auth_data_buf + auth_data_buf_sz;
|
||||
|
||||
ret = ctap_make_extensions(&GA.extensions, ext_encoder_buf, &ext_encoder_buf_size);
|
||||
check_retr(ret);
|
||||
if (ext_encoder_buf_size)
|
||||
{
|
||||
((CTAP_authData *)auth_data_buf)->head.flags |= (1 << 7);
|
||||
len += ext_encoder_buf_size;
|
||||
auth_data_buf_sz += ext_encoder_buf_size;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ret = cbor_encode_int(&map,RESP_authData);
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_byte_string(&map, auth_data_buf, len);
|
||||
check_ret(ret);
|
||||
}
|
||||
}
|
||||
|
||||
save_credential_list((CTAP_authDataHeader*)auth_data_buf, GA.clientDataHash, GA.creds, validCredCount-1); // skip last one
|
||||
|
||||
ret = ctap_end_get_assertion(&map, cred, auth_data_buf, GA.clientDataHash, add_user_info);
|
||||
ret = ctap_end_get_assertion(&map, cred, auth_data_buf, auth_data_buf_sz, GA.clientDataHash); // 1,2,3,4
|
||||
check_retr(ret);
|
||||
|
||||
if (validCredCount > 1)
|
||||
{
|
||||
ret = cbor_encode_int(&map, RESP_numberOfCredentials); // 5
|
||||
check_ret(ret);
|
||||
ret = cbor_encode_int(&map, validCredCount);
|
||||
check_ret(ret);
|
||||
}
|
||||
|
||||
ret = cbor_encoder_close_container(encoder, &map);
|
||||
check_ret(ret);
|
||||
|
||||
|
@ -112,6 +112,7 @@
|
||||
#define CREDENTIAL_ENC_SIZE 176 // pad to multiple of 16 bytes
|
||||
|
||||
#define PUB_KEY_CRED_PUB_KEY 0x01
|
||||
#define PUB_KEY_CRED_CTAP1 0x41
|
||||
#define PUB_KEY_CRED_UNKNOWN 0x3F
|
||||
|
||||
#define CREDENTIAL_IS_SUPPORTED 1
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "cbor.h"
|
||||
|
||||
#include "ctap.h"
|
||||
#include "u2f.h"
|
||||
#include "ctap_parse.h"
|
||||
#include "ctap_errors.h"
|
||||
#include "cose_key.h"
|
||||
@ -890,10 +891,15 @@ uint8_t parse_credential_descriptor(CborValue * arr, CTAP_credentialDescriptor *
|
||||
|
||||
buflen = sizeof(CredentialId);
|
||||
cbor_value_copy_byte_string(&val, (uint8_t*)&cred->credential.id, &buflen, NULL);
|
||||
if (buflen != sizeof(CredentialId))
|
||||
|
||||
if (buflen == U2F_KEY_HANDLE_SIZE)
|
||||
{
|
||||
printf2(TAG_PARSE,"CTAP1 credential\n");
|
||||
cred->type = PUB_KEY_CRED_CTAP1;
|
||||
}
|
||||
else if (buflen != sizeof(CredentialId))
|
||||
{
|
||||
printf2(TAG_ERR,"Ignoring credential is incorrect length\n");
|
||||
//return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; // maybe just skip it instead of fail?
|
||||
}
|
||||
|
||||
ret = cbor_value_map_find_value(arr, "type", &val);
|
||||
@ -906,11 +912,15 @@ uint8_t parse_credential_descriptor(CborValue * arr, CTAP_credentialDescriptor *
|
||||
}
|
||||
|
||||
buflen = sizeof(type);
|
||||
cbor_value_copy_text_string(&val, type, &buflen, NULL);
|
||||
ret = cbor_value_copy_text_string(&val, type, &buflen, NULL);
|
||||
check_ret(ret);
|
||||
|
||||
if (strncmp(type, "public-key",11) == 0)
|
||||
{
|
||||
cred->type = PUB_KEY_CRED_PUB_KEY;
|
||||
if (PUB_KEY_CRED_CTAP1 != cred->type)
|
||||
{
|
||||
cred->type = PUB_KEY_CRED_PUB_KEY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
12
fido2/u2f.c
12
fido2/u2f.c
@ -183,21 +183,21 @@ int8_t u2f_new_keypair(struct u2f_key_handle * kh, uint8_t * appid, uint8_t * pu
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int8_t u2f_appid_eq(struct u2f_key_handle * kh, uint8_t * appid)
|
||||
// Return 1 if authenticate, 0 if not.
|
||||
int8_t u2f_authenticate_credential(struct u2f_key_handle * kh, uint8_t * appid)
|
||||
{
|
||||
uint8_t tag[U2F_KEY_HANDLE_TAG_SIZE];
|
||||
u2f_make_auth_tag(kh, appid, tag);
|
||||
if (memcmp(kh->tag, tag, U2F_KEY_HANDLE_TAG_SIZE) == 0)
|
||||
{
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf1(TAG_U2F, "key handle + appid not authentic\n");
|
||||
printf1(TAG_U2F, "calc tag: \n"); dump_hex1(TAG_U2F,tag, U2F_KEY_HANDLE_TAG_SIZE);
|
||||
printf1(TAG_U2F, "inp tag: \n"); dump_hex1(TAG_U2F,kh->tag, U2F_KEY_HANDLE_TAG_SIZE);
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,7 +214,7 @@ static int16_t u2f_authenticate(struct u2f_authenticate_request * req, uint8_t c
|
||||
if (control == U2F_AUTHENTICATE_CHECK)
|
||||
{
|
||||
printf1(TAG_U2F, "CHECK-ONLY\r\n");
|
||||
if (u2f_appid_eq(&req->kh, req->app) == 0)
|
||||
if (u2f_authenticate_credential(&req->kh, req->app))
|
||||
{
|
||||
return U2F_SW_CONDITIONS_NOT_SATISFIED;
|
||||
}
|
||||
@ -226,7 +226,7 @@ static int16_t u2f_authenticate(struct u2f_authenticate_request * req, uint8_t c
|
||||
if (
|
||||
(control != U2F_AUTHENTICATE_SIGN && control != U2F_AUTHENTICATE_SIGN_NO_USER) ||
|
||||
req->khl != U2F_KEY_HANDLE_SIZE ||
|
||||
u2f_appid_eq(&req->kh, req->app) != 0 || // Order of checks is important
|
||||
(!u2f_authenticate_credential(&req->kh, req->app)) || // Order of checks is important
|
||||
u2f_load_key(&req->kh, req->app) != 0
|
||||
|
||||
)
|
||||
|
@ -103,6 +103,7 @@ void u2f_request(struct u2f_request_apdu* req, CTAP_RESPONSE * resp);
|
||||
// @len data length
|
||||
void u2f_request_nfc(uint8_t * req, int len, CTAP_RESPONSE * resp);
|
||||
|
||||
int8_t u2f_authenticate_credential(struct u2f_key_handle * kh, uint8_t * appid);
|
||||
|
||||
int8_t u2f_response_writeback(const uint8_t * buf, uint16_t len);
|
||||
void u2f_reset_response();
|
||||
|
@ -27,7 +27,7 @@ void _putchar(char c)
|
||||
int _write (int fd, const void *buf, unsigned long int len)
|
||||
{
|
||||
uint8_t * data = (uint8_t *) buf;
|
||||
#if DEBUG_LEVEL>1
|
||||
#if DEBUG_LEVEL>0
|
||||
// static uint8_t logbuf[1000] = {0};
|
||||
// static int logbuflen = 0;
|
||||
// if (logbuflen + len > sizeof(logbuf)) {
|
||||
|
@ -2,17 +2,19 @@ from __future__ import print_function, absolute_import, unicode_literals
|
||||
import time
|
||||
from random import randint
|
||||
import array
|
||||
from functools import cmp_to_key
|
||||
|
||||
|
||||
from fido2 import cbor
|
||||
from fido2.ctap import CtapError
|
||||
|
||||
from fido2.ctap2 import ES256, PinProtocolV1
|
||||
from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData
|
||||
from fido2.utils import sha256, hmac_sha256
|
||||
from fido2.attestation import Attestation
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from .u2f import U2FTests
|
||||
from .tester import Tester, Test
|
||||
from .util import shannon_entropy
|
||||
|
||||
@ -33,9 +35,119 @@ def VerifyAttestation(attest, data):
|
||||
verifier().verify(attest.att_statement, attest.auth_data, data.hash)
|
||||
|
||||
|
||||
def cbor_key_to_representative(key):
|
||||
if isinstance(key, int):
|
||||
if key >= 0:
|
||||
return (0, key)
|
||||
return (1, -key)
|
||||
elif isinstance(key, bytes):
|
||||
return (2, key)
|
||||
elif isinstance(key, str):
|
||||
return (3, key)
|
||||
else:
|
||||
raise ValueError(key)
|
||||
|
||||
|
||||
def cbor_str_cmp(a, b):
|
||||
if isinstance(a, str) or isinstance(b, str):
|
||||
a = a.encode("utf8")
|
||||
b = b.encode("utf8")
|
||||
|
||||
if len(a) == len(b):
|
||||
for x, y in zip(a, b):
|
||||
if x != y:
|
||||
return x - y
|
||||
return 0
|
||||
else:
|
||||
return len(a) - len(b)
|
||||
|
||||
|
||||
def cmp_cbor_keys(a, b):
|
||||
a = cbor_key_to_representative(a)
|
||||
b = cbor_key_to_representative(b)
|
||||
if a[0] != b[0]:
|
||||
return a[0] - b[0]
|
||||
if a[0] in (2, 3):
|
||||
return cbor_str_cmp(a[1], b[1])
|
||||
else:
|
||||
return (a[1] > b[1]) - (a[1] < b[1])
|
||||
|
||||
|
||||
def TestCborKeysSorted(cbor_obj):
|
||||
# Cbor canonical ordering of keys.
|
||||
# https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ctap2-canonical-cbor-encoding-form
|
||||
|
||||
if isinstance(cbor_obj, bytes):
|
||||
cbor_obj = cbor.loads(cbor_obj)[0]
|
||||
|
||||
if isinstance(cbor_obj, dict):
|
||||
l = [x for x in cbor_obj]
|
||||
else:
|
||||
l = cbor_obj
|
||||
|
||||
l_sorted = sorted(l[:], key=cmp_to_key(cmp_cbor_keys))
|
||||
|
||||
for i in range(len(l)):
|
||||
|
||||
if not isinstance(l[i], (str, int)):
|
||||
raise ValueError(f"Cbor map key {l[i]} must be int or str for CTAP2")
|
||||
|
||||
if l[i] != l_sorted[i]:
|
||||
raise ValueError(f"Cbor map item {i}: {l[i]} is out of order")
|
||||
|
||||
return l
|
||||
|
||||
|
||||
# hot patch cbor map parsing to test the order of keys in map
|
||||
_load_map_old = cbor.load_map
|
||||
|
||||
|
||||
def _load_map_new(ai, data):
|
||||
values, data = _load_map_old(ai, data)
|
||||
TestCborKeysSorted(values)
|
||||
return values, data
|
||||
|
||||
|
||||
cbor.load_map = _load_map_new
|
||||
cbor._DESERIALIZERS[5] = _load_map_new
|
||||
|
||||
|
||||
class FIDO2Tests(Tester):
|
||||
def __init__(self, tester=None):
|
||||
super().__init__(tester)
|
||||
self.self_test()
|
||||
|
||||
def self_test(self,):
|
||||
cbor_key_list_sorted = [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
-1,
|
||||
-2,
|
||||
"b",
|
||||
"c",
|
||||
"aa",
|
||||
"aaa",
|
||||
"aab",
|
||||
"baa",
|
||||
"bbb",
|
||||
]
|
||||
with Test("Self test CBOR sorting"):
|
||||
TestCborKeysSorted(cbor_key_list_sorted)
|
||||
|
||||
with Test("Self test CBOR sorting integers", catch=ValueError):
|
||||
TestCborKeysSorted([1, 0])
|
||||
|
||||
with Test("Self test CBOR sorting major type", catch=ValueError):
|
||||
TestCborKeysSorted([-1, 0])
|
||||
|
||||
with Test("Self test CBOR sorting strings", catch=ValueError):
|
||||
TestCborKeysSorted(["bb", "a"])
|
||||
|
||||
with Test("Self test CBOR sorting same length strings", catch=ValueError):
|
||||
TestCborKeysSorted(["ab", "aa"])
|
||||
|
||||
def run(self,):
|
||||
self.test_fido2()
|
||||
@ -229,6 +341,8 @@ class FIDO2Tests(Tester):
|
||||
def test_get_info(self,):
|
||||
with Test("Get info"):
|
||||
info = self.ctap.get_info()
|
||||
print(bytes(info))
|
||||
print(cbor.loads(bytes(info)))
|
||||
|
||||
with Test("Check FIDO2 string is in VERSIONS field"):
|
||||
assert "FIDO_2_0" in info.versions
|
||||
@ -274,6 +388,7 @@ class FIDO2Tests(Tester):
|
||||
key_params,
|
||||
expectedError=CtapError.ERR.SUCCESS,
|
||||
)
|
||||
|
||||
allow_list = [
|
||||
{
|
||||
"id": prev_reg.auth_data.credential_data.credential_id,
|
||||
@ -766,6 +881,34 @@ class FIDO2Tests(Tester):
|
||||
allow_list + [{"type": b"public-key"}],
|
||||
)
|
||||
|
||||
self.testReset()
|
||||
|
||||
appid = sha256(rp["id"].encode("utf8"))
|
||||
chal = sha256(challenge.encode("utf8"))
|
||||
with Test("Send CTAP1 register request"):
|
||||
u2f = U2FTests(self)
|
||||
reg = u2f.register(chal, appid)
|
||||
reg.verify(appid, chal)
|
||||
|
||||
with Test("Authenticate CTAP1"):
|
||||
auth = u2f.authenticate(chal, appid, reg.key_handle)
|
||||
auth.verify(appid, chal, reg.public_key)
|
||||
|
||||
auth = self.testGA(
|
||||
"Authenticate CTAP1 registration with CTAP2",
|
||||
rp["id"],
|
||||
cdh,
|
||||
[{"id": reg.key_handle, "type": "public-key"}],
|
||||
expectedError=CtapError.ERR.SUCCESS,
|
||||
)
|
||||
|
||||
with Test("Check assertion is correct"):
|
||||
credential_data = AttestedCredentialData.from_ctap1(
|
||||
reg.key_handle, reg.public_key
|
||||
)
|
||||
auth.verify(cdh, credential_data.public_key)
|
||||
assert auth.credential["id"] == reg.key_handle
|
||||
|
||||
def test_rk(self, pin_code=None):
|
||||
|
||||
pin_auth = None
|
||||
|
@ -28,14 +28,21 @@ class Packet(object):
|
||||
|
||||
|
||||
class Test:
|
||||
def __init__(self, msg):
|
||||
def __init__(self, msg, catch=None):
|
||||
self.msg = msg
|
||||
self.catch = catch
|
||||
|
||||
def __enter__(self,):
|
||||
print(self.msg)
|
||||
|
||||
def __exit__(self, a, b, c):
|
||||
print("Pass")
|
||||
if self.catch is None:
|
||||
print("Pass")
|
||||
elif isinstance(b, self.catch):
|
||||
print("Pass")
|
||||
return b
|
||||
else:
|
||||
raise RuntimeError(f"Expected exception {self.catch} did not occur.")
|
||||
|
||||
|
||||
class Tester:
|
||||
|
Reference in New Issue
Block a user