Compare commits

...

34 Commits
2.1.0 ... 2.2.0

Author SHA1 Message Date
6068fb9868 Merge pull request #173 from solokeys/fix_u2f_on_fido2
Fix u2f on fido2
2019-04-17 22:42:38 -04:00
74cbe00e3b Enable debug logs for >0 2019-04-14 15:13:39 -04:00
7e490f17fc delete 2019-04-13 22:42:16 -04:00
9bb706987f solo ext bugfix 2019-04-13 22:42:05 -04:00
88a759566d Improve testing 2019-04-13 22:37:47 -04:00
44fa3bbb8e Add checks to use U2F key if necessary 2019-04-13 22:37:31 -04:00
89e9296825 Add test 2019-04-13 20:43:26 -04:00
873d65b823 Merge pull request #172 from StoyanDimitrov/patch-1
Improve readability of code filenames
2019-04-13 11:54:55 -04:00
eb8e3ed46a Improve readability of code filenames 2019-04-13 15:31:35 +00:00
8b97276e32 Merge pull request #171 from solokeys/fix_cbor
Fix cbor
2019-04-12 12:04:01 -04:00
78579c27dc Refactor and self test the CBOR sorting 2019-04-11 13:42:17 -04:00
ca80329b4c Compare string length and sort from start of string 2019-04-11 13:22:55 -04:00
7a49169492 consider major type and refactor 2019-04-11 00:04:33 -04:00
46dd4fe818 unused import 2019-04-10 13:56:23 -04:00
c71bbd8689 re-enable all tests 2019-04-10 13:42:38 -04:00
7068be9cd5 reorder options 2019-04-10 13:13:38 -04:00
f8635f1682 refactor tests 2019-04-10 13:12:33 -04:00
ffa9ad4923 refactor cbor sorting test 2019-04-10 12:47:39 -04:00
5fc8d214fd remove add_user param 2019-04-10 12:47:23 -04:00
5f49f4680e re-order items in get_assertion response 2019-04-10 12:22:35 -04:00
86393c46b4 Test it is correct 2019-04-10 12:11:43 -04:00
4cc72bcd97 rearrange cbor encoding order in make_credential and get_info 2019-04-10 12:11:31 -04:00
5e0edf3e11 Update index.md 2019-04-06 21:45:51 +02:00
bd810fff87 Merge pull request #165 from ehershey/patch-3
More docs fixups
2019-04-06 21:41:19 +02:00
d9fb508949 Merge pull request #166 from ehershey/patch-2
include markdown-include mkdocs dependency
2019-04-06 21:40:13 +02:00
c2b7acb6aa include markdown-include mkdocs dependency 2019-04-06 15:29:16 -04:00
4690a7ce65 More docs fixups 2019-04-06 13:50:51 -04:00
61f24d142d Merge pull request #163 from ehershey/patch-1
link to readme and reference TC in docs start page
2019-04-06 13:41:54 -04:00
f5c6f99423 Merge pull request #164 from ehershey/patch-2
Docs fixups
2019-04-06 13:40:51 -04:00
96de4f0850 Docs fixups
Spelling, grammar
2019-04-06 13:28:16 -04:00
331ebdfccf link to readme and reference TC in docs start page 2019-04-06 13:20:10 -04:00
a6a6d653ad Update README.md 2019-04-06 14:49:44 +02:00
928bc0216d Merge pull request #161 from solokeys/bump_2.1.0
bump to 2.1.0
2019-04-02 01:31:48 +02:00
6d52c9ede7 bump to 2.1.0 2019-03-31 23:49:29 -04:00
16 changed files with 332 additions and 135 deletions

View File

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

View File

@ -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
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fsolokeys%2Fsolo?ref=badge_large)

View File

@ -1 +1 @@
2.0.0
2.1.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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];
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 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);
ret = ctap_add_credential_descriptor(map, cred); // 1
check_retr(ret);
{
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);
}
ret = ctap_add_credential_descriptor(map, cred);
check_retr(ret);
crypto_ecc256_load_key((uint8_t*)&cred->credential.id, sizeof(CredentialId), NULL, 0);
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);

View File

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

View File

@ -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,12 +912,16 @@ 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)
{
if (PUB_KEY_CRED_CTAP1 != cred->type)
{
cred->type = PUB_KEY_CRED_PUB_KEY;
}
}
else
{
cred->type = PUB_KEY_CRED_UNKNOWN;

View File

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

View File

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

View File

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

View File

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

View File

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