From 083f9bc7875b6d867477f23cb1e00888d464ddf5 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:06:29 +0100 Subject: [PATCH 01/50] Moving HSM pointer to support EA. Signed-off-by: Pol Henarejos --- pico-hsm-sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pico-hsm-sdk b/pico-hsm-sdk index 0bc13df..fa54da9 160000 --- a/pico-hsm-sdk +++ b/pico-hsm-sdk @@ -1 +1 @@ -Subproject commit 0bc13df1a22ab6c9cceb3b7a87b294e604e79c1e +Subproject commit fa54da973caa6ca2f861cf59634d529af8bc5894 From 196430517f6d68577784a6dfaf699ce0acf36b42 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:07:02 +0100 Subject: [PATCH 02/50] Added credBlob in getInfo. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 8764f60..068ace7 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -26,7 +26,7 @@ int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; CborError error = CborNoError; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 12)); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 13)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); @@ -36,7 +36,8 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4)); + CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength")); @@ -115,6 +116,9 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0E)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 1024)); // maxCredBlobLength + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE)); From 4cb0af5045794a93139400cc12fb12f63262b16e Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:48:46 +0100 Subject: [PATCH 03/50] Defining max length for credBlobs Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 2 +- src/fido/fido.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 068ace7..9f0dd9e 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -117,7 +117,7 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 1024)); // maxCredBlobLength + CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); diff --git a/src/fido/fido.h b/src/fido/fido.h index c1d54e7..df8e888 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -78,6 +78,7 @@ extern void set_opts(uint8_t); #define MAX_CREDENTIAL_COUNT_IN_LIST 16 #define MAX_CRED_ID_LENGTH 1024 #define MAX_RESIDENT_CREDENTIALS 256 +#define MAX_CREDBLOB_LENGTH 128 typedef struct known_app { const uint8_t *rp_id_hash; From 9d79505c5a3915d7dac2106e73bee203a119ba37 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:54:10 +0100 Subject: [PATCH 04/50] Embed credBlob onto credId. Signed-off-by: Pol Henarejos --- src/fido/credential.c | 5 +++++ src/fido/credential.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/fido/credential.c b/src/fido/credential.c index 9c36525..213ad80 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -60,6 +60,10 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri if (extensions->present == true) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07)); CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength)); + if (extensions->credBlob.present == true && extensions->credBlob.len < MAX_CREDBLOB_LENGTH) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credBlob")); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, extensions->credBlob.data, extensions->credBlob.len)); + } if (extensions->credProtect != 0) { CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credProtect")); CBOR_CHECK(cbor_encode_uint(&mapEncoder2, extensions->credProtect)); @@ -155,6 +159,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r CBOR_FIELD_GET_KEY_TEXT(2); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", cred->extensions.hmac_secret); CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect); + CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", cred->extensions.credBlob); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); diff --git a/src/fido/credential.h b/src/fido/credential.h index 87d9cb1..50fc478 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -31,6 +31,7 @@ typedef struct CredExtensions { const bool *hmac_secret; uint64_t credProtect; const bool *minPinLength; + CborByteString credBlob; bool present; } CredExtensions; From 710e03f5a6bb3ce8106a5149c2fe141772419a49 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:54:57 +0100 Subject: [PATCH 05/50] Process credBlob on makeCredential. Signed-off-by: Pol Henarejos --- src/fido/cbor_make_credential.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index b9de407..a0a100e 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -119,6 +119,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret); CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength); + CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); @@ -289,7 +290,13 @@ int cbor_make_credential(const uint8_t *data, size_t len) { } } } + if (extensions.credBlob.present == true) + l++; CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); + if (extensions.credBlob.present == true) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob")); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder, extensions.credBlob.len < MAX_CREDBLOB_LENGTH)); + } if (extensions.credProtect != 0) { CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect")); CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect)); From fa5926a3cc4ba61a704551fbf60eceb940ef3490 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:55:27 +0100 Subject: [PATCH 06/50] credBlob is returned on getAssertion if requested. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 5179992..b74ab5d 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -88,6 +88,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { bool asserted = false; int64_t kty = 2, alg = 0, crv = 0; CborByteString kax = {0}, kay = {0}, salt_enc = {0}, salt_auth = {0}; + const bool *credBlob = NULL; CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map)); uint64_t val_c = 1; @@ -174,6 +175,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { continue; } CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "credBlob", credBlob); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); @@ -379,7 +381,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { l++; if (extensions.credProtect != 0) l++; + if (credBlob == ptrue) + l++; CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); + if (credBlob == ptrue) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob")); + if (selcred->extensions.credBlob.present == true) + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data, selcred->extensions.credBlob.len)); + else + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0)); + } if (extensions.credProtect != 0) { CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect")); CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect)); From 1376c515286f57e61d2c75b1a6a77a1499579c99 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 15:57:41 +0100 Subject: [PATCH 07/50] Fix credProtect should not be returned on getAssertion. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index b74ab5d..1a9d6de 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -174,7 +174,6 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_PARSE_MAP_END(_f2, 3); continue; } - CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "credBlob", credBlob); CBOR_ADVANCE(2); } @@ -379,8 +378,6 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { extensions.hmac_secret = NULL; if (extensions.hmac_secret != NULL) l++; - if (extensions.credProtect != 0) - l++; if (credBlob == ptrue) l++; CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); @@ -391,10 +388,6 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { else CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0)); } - if (extensions.credProtect != 0) { - CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect")); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect)); - } if (extensions.hmac_secret != NULL) { CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret")); From 8e9eafaec5fe144d3fa05abc685dd7dc207e473f Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 16:35:19 +0100 Subject: [PATCH 08/50] Fix important potential buffer overflow deriving the credential key. Signed-off-by: Pol Henarejos --- src/fido/credential.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/credential.c b/src/fido/credential.c index 213ad80..6d63e1c 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -314,7 +314,7 @@ int credential_derive_chacha_key(uint8_t *outk) { int r = 0; if ((r = load_keydev(outk)) != 0) return r; - const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"SLIP-0022", 9, outk); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)CRED_PROTO, 4, outk); From d7016f606517da3dd88aaedb29c9b9d49ba7b421 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 17:01:01 +0100 Subject: [PATCH 09/50] Add MAX_MSG_SIZE in getInfo. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 5 ++++- src/fido/fido.h | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 9f0dd9e..bc5c5b3 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -26,7 +26,7 @@ int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; CborError error = CborNoError; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 13)); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 14)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); @@ -67,6 +67,9 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true)); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_MSG_SIZE)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); CBOR_CHECK(cbor_encode_uint(&arrayEncoder, 1)); // PIN protocols diff --git a/src/fido/fido.h b/src/fido/fido.h index df8e888..6eb90e8 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -79,6 +79,8 @@ extern void set_opts(uint8_t); #define MAX_CRED_ID_LENGTH 1024 #define MAX_RESIDENT_CREDENTIALS 256 #define MAX_CREDBLOB_LENGTH 128 +#define MAX_MSG_SIZE 1024 +#define MAX_FRAGMENT_LENGTH (MAX_MSG_SIZE - 64) typedef struct known_app { const uint8_t *rp_id_hash; From 860cca53e069777005cefd967862a84c3eeb7280 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 17:07:16 +0100 Subject: [PATCH 10/50] Added key derivation for large blob. Signed-off-by: Pol Henarejos --- src/fido/credential.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/fido/credential.c b/src/fido/credential.c index 6d63e1c..d055ba7 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -321,3 +321,17 @@ int credential_derive_chacha_key(uint8_t *outk) { mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"Encryption key", 14, outk); return 0; } + +int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) { + memset(outk, 0, 32); + int r = 0; + if ((r = load_keydev(outk)) != 0) + return r; + const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + + mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"SLIP-0022", 9, outk); + mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)CRED_PROTO, 4, outk); + mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"largeBlobKey", 12, outk); + mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk); + return 0; +} From 315f01372eed6e4f0500baeb806c88fab3a710c7 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 21:02:08 +0100 Subject: [PATCH 11/50] Adding largeBlobKey in getInfo. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index bc5c5b3..3df5634 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -36,10 +36,11 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 5)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret")); + CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength")); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); From a151dc72e4e422c0fc846eecbb494fa2f3f06719 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 21:02:23 +0100 Subject: [PATCH 12/50] Embed largeBlobKey presence in credId. Signed-off-by: Pol Henarejos --- src/fido/credential.c | 5 +++++ src/fido/credential.h | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/fido/credential.c b/src/fido/credential.c index d055ba7..3c2d122 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -72,6 +72,10 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "hmac-secret")); CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, *extensions->hmac_secret)); } + if (extensions->largeBlobKey == ptrue) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "largeBlobKey")); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true)); + } CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); } CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08)); @@ -160,6 +164,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", cred->extensions.hmac_secret); CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect); CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", cred->extensions.credBlob); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKeys", cred->extensions.largeBlobKey); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); diff --git a/src/fido/credential.h b/src/fido/credential.h index 50fc478..4e4dce7 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -32,6 +32,7 @@ typedef struct CredExtensions { uint64_t credProtect; const bool *minPinLength; CborByteString credBlob; + const bool *largeBlobKey; bool present; } CredExtensions; @@ -63,5 +64,6 @@ extern void credential_free(Credential *cred); extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash); extern int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred); extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk); +extern int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk); #endif // _CREDENTIAL_H_ From 1707430593fb22f2882163d8162f91e19ec56208 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 21:02:52 +0100 Subject: [PATCH 13/50] Return largeBlobKey on makeCredential if requested. Signed-off-by: Pol Henarejos --- src/fido/cbor_make_credential.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index a0a100e..9657307 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -120,6 +120,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect); CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength); CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); @@ -243,6 +244,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); } + if (extensions.largeBlobKey == pfalse || (extensions.largeBlobKey == ptrue && options.rk != ptrue)) { + CBOR_ERROR(CTAP2_ERR_INVALID_OPTION); + } + if (options.up == ptrue || options.up == NULL) { //14.1 if (pinUvAuthParam.present == true) { if (getUserPresentFlagValue() == false) { @@ -384,8 +389,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) { ret = mbedtls_ecdsa_write_signature(&ekey, MBEDTLS_MD_SHA256, hash, 32, sig, sizeof(sig), &olen, random_gen, NULL); mbedtls_ecdsa_free(&ekey); + uint8_t largeBlobKey[32]; + if (extensions.largeBlobKey == ptrue && options.rk == ptrue) { + ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey); + if (ret != 0) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + } + cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 4)); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, extensions.largeBlobKey == ptrue && options.rk == ptrue ? 5 : 4)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed")); @@ -414,6 +427,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04)); CBOR_CHECK(cbor_encode_boolean(&mapEncoder, enterpriseAttestation == 2)); + + if (extensions.largeBlobKey == ptrue && options.rk == ptrue) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey))); + } + mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1); From 5c7be811e85cb7b60efba2c6b2f641c5d7de5e26 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 7 Dec 2022 21:03:30 +0100 Subject: [PATCH 14/50] Return largeBlobKey on getAssertion if credential has largeKeyBlob and if requested. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 1a9d6de..c275812 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -175,6 +175,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { continue; } CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "credBlob", credBlob); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); @@ -333,6 +334,10 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { clearPinUvAuthTokenPermissionsExceptLbw(); } + if (extensions.largeBlobKey == pfalse) { + CBOR_ERROR(CTAP2_ERR_INVALID_OPTION); + } + if (!(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) { selcred = &creds[0]; } @@ -369,6 +374,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } } + uint8_t largeBlobKey[32]; + if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { + ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey); + if (ret != 0) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + } + size_t ext_len = 0; uint8_t ext [512]; if (extensions.present == true) { @@ -470,6 +483,8 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { lfields++; if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) lfields++; + if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) + lfields++; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields)); @@ -514,6 +529,11 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials)); } + if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey))); + } + mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1); ctr++; From 5dcf89cd667fd5d4e3bb7bc8a96487ff483e1932 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 13:13:47 +0100 Subject: [PATCH 15/50] Fix critical bug caused by double free(). Signed-off-by: Pol Henarejos --- src/fido/fido.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/fido/fido.c b/src/fido/fido.c index 8b44acf..14334d9 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -116,7 +116,8 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe mbedtls_x509write_crt_set_authority_key_identifier(&ctx); mbedtls_x509write_crt_set_key_usage(&ctx, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_CERT_SIGN); int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, core1 ? random_gen : random_gen_core0, NULL); - mbedtls_pk_free(&key); + /* pk cannot be freed, as it is freed later */ + //mbedtls_pk_free(&key); return ret; } @@ -242,11 +243,15 @@ int scan_files(bool core1) { mbedtls_ecdsa_context key; mbedtls_ecdsa_init(&key); int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), file_get_size(ef_keydev)); - if (ret != 0) + if (ret != 0) { + mbedtls_ecdsa_free(&key); return ret; + } ret = mbedtls_ecp_mul(&key.grp, &key.Q, &key.d, &key.grp.G, core1 ? random_gen : random_gen_core0, NULL); - if (ret != 0) + if (ret != 0) { + mbedtls_ecdsa_free(&key); return ret; + } ret = x509_create_cert(&key, cert, sizeof(cert), core1); mbedtls_ecdsa_free(&key); if (ret <= 0) From a8364c281b6404d095d134d9a326b987ea14ad16 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 13:33:51 +0100 Subject: [PATCH 16/50] When doing GA, GET permission is necessary. Signed-off-by: Pol Henarejos --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9181a55..48631b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -296,7 +296,7 @@ class Device(): def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False): client_data = client_data if client_data is not Ellipsis else CollectedClientData.create( - type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32) + type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32) ) rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id'] if (ctap1 is True): From 270a54f3b7c954a13f7971e1fc4f50a3ae153d3e Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 14:19:28 +0100 Subject: [PATCH 17/50] Adding parenthesis for clearer statement Signed-off-by: Pol Henarejos --- src/fido/cbor_make_credential.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 9657307..ebe03fe 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -240,7 +240,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (strcmp(excludeList[e].type.data, "public-key") != 0) continue; Credential ecred; - if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || flags & FIDO2_AUT_FLAG_UV)) + if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); } From 3a92238c0c5315f7cb6498043eb1351da995cb27 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 14:20:42 +0100 Subject: [PATCH 18/50] Fix returning numberOfCredentials based on up and uv flags in the request (not in the response). Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index c275812..07f5fd9 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -85,7 +85,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = {0}; size_t allowList_len = 0, creds_len = 0; uint8_t *aut_data = NULL; - bool asserted = false; + bool asserted = false, up = true, uv = false; int64_t kty = 2, alg = 0, crv = 0; CborByteString kax = {0}, kay = {0}, salt_enc = {0}, salt_auth = {0}; const bool *credBlob = NULL; @@ -239,6 +239,10 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } //else if (options.up == NULL) //5.7 //rup = ptrue; + if (options.uv != NULL) + uv = *options.uv; + if (options.up != NULL) + up = *options.up; } if (pinUvAuthParam.present == true) { //6.1 @@ -338,7 +342,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_ERROR(CTAP2_ERR_INVALID_OPTION); } - if (!(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) { + if (up == false && uv == false) { selcred = &creds[0]; } else { @@ -481,7 +485,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { uint8_t lfields = 3; if (selcred->opts.present == true && selcred->opts.rk == ptrue) lfields++; - if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) + if (numberOfCredentials > 1 && next == false && up == false && uv == false) lfields++; if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) lfields++; @@ -525,7 +529,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); } - if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) { + if (numberOfCredentials > 1 && next == false && up == false && uv == false) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials)); } From 50418113a95879b94c025ea8b1032de345e6e693 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 14:21:04 +0100 Subject: [PATCH 19/50] Authenticator may return 1 number of credentials (not None). Signed-off-by: Pol Henarejos --- tests/pico-fido/test_authenticate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pico-fido/test_authenticate.py b/tests/pico-fido/test_authenticate.py index a002004..e466706 100644 --- a/tests/pico-fido/test_authenticate.py +++ b/tests/pico-fido/test_authenticate.py @@ -76,7 +76,7 @@ def test_get_assertion_allow_list_filtering_and_buffering(device): len(rp2_assertions) ) - assert counts in [(None, None), (l1, l2)] + assert counts in [(1, 1), (l1, l2)] def test_corrupt_credId(device, MCRes): # apply bit flip From 2cf211cbd0504bd0c1252bfade0bbefa44725b29 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 16:27:41 +0100 Subject: [PATCH 20/50] Fix clearing token rp link. Signed-off-by: Pol Henarejos --- src/fido/cbor_client_pin.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index bf6a21e..4e2d053 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -31,7 +31,6 @@ #include "hsm.h" #include "apdu.h" -uint8_t permissions_rp_id = 0, permission_set = 0; uint32_t usage_timer = 0, initial_usage_time_limit = 0; uint32_t max_usage_time_period = 600*1000; bool needs_power_cycle = false; @@ -63,11 +62,11 @@ void clearPinUvAuthTokenPermissionsExceptLbw() { } void stopUsingPinUvAuthToken() { - permissions_rp_id = 0; paut.permissions = 0; usage_timer = 0; paut.in_use = false; memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash)); + paut.has_rp_id = false; initial_usage_time_limit = 0; paut.user_present = paut.user_verified = false; user_present_time_limit = 0; @@ -547,7 +546,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) { mbedtls_sha256((uint8_t *)rpId.data, rpId.len, paut.rp_id_hash, 0); paut.has_rp_id = true; } - uint8_t pinUvAuthToken_enc[32+IV_SIZE]; + else + paut.has_rp_id = false; + uint8_t pinUvAuthToken_enc[32 + IV_SIZE]; encrypt(pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); From c5644d14b0323bdd50aa4c736dd6b220d0352a1b Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 16:28:03 +0100 Subject: [PATCH 21/50] Fix token precedence Signed-off-by: Pol Henarejos --- tests/pico-fido/test_cred_mgmt.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/pico-fido/test_cred_mgmt.py b/tests/pico-fido/test_cred_mgmt.py index b2d2cb7..7bdb864 100644 --- a/tests/pico-fido/test_cred_mgmt.py +++ b/tests/pico-fido/test_cred_mgmt.py @@ -3,25 +3,30 @@ import time import random from fido2.ctap import CtapError from fido2.ctap2 import CredentialManagement -from fido2.utils import sha256, hmac_sha256 -from fido2.ctap2.pin import PinProtocolV2 +from fido2.utils import sha256 +from fido2.ctap2.pin import PinProtocolV2, ClientPin from binascii import hexlify from utils import generate_random_user PIN = "12345678" - @pytest.fixture(params=[PIN], scope = 'function') -def PinToken(request, device, client_pin): +def client_pin_set(request, device, client_pin): #device.reboot() device.reset() pin = request.param client_pin.set_pin(pin) - return client_pin.get_pin_token(pin) + +@pytest.fixture(params=[PIN], scope = 'function') +def PinToken(request, device, client_pin): + pin = request.param + token = client_pin.get_pin_token(pin, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.CREDENTIAL_MGMT) + print(f"GET TOKEN: {hexlify(token)}") + return token @pytest.fixture(scope = 'function') -def MC_RK_Res(device, PinToken): +def MC_RK_Res(device, client_pin_set): rp = {"id": "ssh:", "name": "Bate Goiko"} device.doMC(rp=rp, rk=True) @@ -98,7 +103,7 @@ def test_get_info(info): assert 0x8 in info assert info[0x8] > 1 -def test_get_metadata(CredMgmt, MC_RK_Res): +def test_get_metadata_ok(MC_RK_Res, CredMgmt): metadata = CredMgmt.get_metadata() assert metadata[CredentialManagement.RESULT.EXISTING_CRED_COUNT] == 2 assert metadata[CredentialManagement.RESULT.MAX_REMAINING_COUNT] >= 48 From 00ba0db87ae51f848eedf16f9dafed0a5a6cb7e6 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 16:34:12 +0100 Subject: [PATCH 22/50] Test fixes for credMgmt. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_cred_mgmt.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/pico-fido/test_cred_mgmt.py b/tests/pico-fido/test_cred_mgmt.py index 7bdb864..f99b537 100644 --- a/tests/pico-fido/test_cred_mgmt.py +++ b/tests/pico-fido/test_cred_mgmt.py @@ -21,7 +21,7 @@ def client_pin_set(request, device, client_pin): def PinToken(request, device, client_pin): pin = request.param token = client_pin.get_pin_token(pin, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.CREDENTIAL_MGMT) - print(f"GET TOKEN: {hexlify(token)}") + print(f"GET TOKEN {hexlify(token)}") return token @@ -108,16 +108,16 @@ def test_get_metadata_ok(MC_RK_Res, CredMgmt): assert metadata[CredentialManagement.RESULT.EXISTING_CRED_COUNT] == 2 assert metadata[CredentialManagement.RESULT.MAX_REMAINING_COUNT] >= 48 -def test_enumerate_rps(CredMgmt, MC_RK_Res): +def test_enumerate_rps(MC_RK_Res, CredMgmt): res = CredMgmt.enumerate_rps() assert len(res) == 2 - assert res[0][CredentialManagement.RESULT.RP]["id"] == b"ssh:" + assert res[0][CredentialManagement.RESULT.RP]["id"] == "ssh:" assert res[0][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"ssh:") # Solo doesn't store rpId with the exception of "ssh:" - assert res[1][CredentialManagement.RESULT.RP]["id"] == b"xakcop.com" + assert res[1][CredentialManagement.RESULT.RP]["id"] == "xakcop.com" assert res[1][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"xakcop.com") -def test_enumarate_creds(CredMgmt, MC_RK_Res): +def test_enumarate_creds(MC_RK_Res, CredMgmt): res = CredMgmt.enumerate_creds(sha256(b"ssh:")) assert len(res) == 1 assert_cred_response_has_all_fields(res[0]) @@ -139,13 +139,13 @@ def test_rkbegin_wrong_pinauth(device, MC_RK_Res, PinToken): cmd = lambda credMgmt: credMgmt.enumerate_creds_begin(sha256(b"ssh:")) _test_wrong_pinauth(device, cmd, PinToken) -def test_rpnext_without_rpbegin(device, CredMgmt, MC_RK_Res): +def test_rpnext_without_rpbegin(device, MC_RK_Res, CredMgmt): CredMgmt.enumerate_creds_begin(sha256(b"ssh:")) with pytest.raises(CtapError) as e: CredMgmt.enumerate_rps_next() assert e.value.code == CtapError.ERR.NOT_ALLOWED -def test_rknext_without_rkbegin(device, CredMgmt, MC_RK_Res): +def test_rknext_without_rkbegin(device, MC_RK_Res, CredMgmt): CredMgmt.enumerate_rps_begin() with pytest.raises(CtapError) as e: CredMgmt.enumerate_creds_next() @@ -206,7 +206,7 @@ def test_add_delete(device, PinToken, CredMgmt): assert len(res) == 2 def test_multiple_creds_per_multiple_rps( - device, PinToken, CredMgmt, MC_RK_Res + device, MC_RK_Res, CredMgmt ): res = CredMgmt.enumerate_rps() assert len(res) == 2 @@ -234,7 +234,7 @@ def test_multiple_creds_per_multiple_rps( "enumeration_test", [_test_enumeration, _test_enumeration_interleaved] ) def test_multiple_enumeration( - device, PinToken, MC_RK_Res, CredMgmt, enumeration_test + device, MC_RK_Res, CredMgmt, enumeration_test ): """ Test enumerate still works after different commands """ @@ -270,7 +270,7 @@ def test_multiple_enumeration( "enumeration_test", [_test_enumeration, _test_enumeration_interleaved] ) def test_multiple_enumeration_with_deletions( - device, PinToken, MC_RK_Res, CredMgmt, enumeration_test + device, MC_RK_Res, CredMgmt, enumeration_test ): """ Create each credential in random order. Test enumerate still works after randomly deleting each credential""" From 866d69a82dc3aba4a5015877ad39ebf325575bb8 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 18:42:59 +0100 Subject: [PATCH 23/50] CredMgmt must be redeclared everytime, since PinToken might be changed due to underlaying doMC. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_cred_mgmt.py | 153 +++++++++++++++--------------- 1 file changed, 74 insertions(+), 79 deletions(-) diff --git a/tests/pico-fido/test_cred_mgmt.py b/tests/pico-fido/test_cred_mgmt.py index f99b537..36c3c71 100644 --- a/tests/pico-fido/test_cred_mgmt.py +++ b/tests/pico-fido/test_cred_mgmt.py @@ -17,14 +17,6 @@ def client_pin_set(request, device, client_pin): pin = request.param client_pin.set_pin(pin) -@pytest.fixture(params=[PIN], scope = 'function') -def PinToken(request, device, client_pin): - pin = request.param - token = client_pin.get_pin_token(pin, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.CREDENTIAL_MGMT) - print(f"GET TOKEN {hexlify(token)}") - return token - - @pytest.fixture(scope = 'function') def MC_RK_Res(device, client_pin_set): rp = {"id": "ssh:", "name": "Bate Goiko"} @@ -33,51 +25,54 @@ def MC_RK_Res(device, client_pin_set): rp = {"id": "xakcop.com", "name": "John Doe"} device.doMC(rp=rp, rk=True) +def PinToken(device): + return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.CREDENTIAL_MGMT) -@pytest.fixture(scope = 'function') -def CredMgmt(device, PinToken): +def CredMgmt(device): + pt = PinToken(device) pin_protocol = PinProtocolV2() - return CredentialManagement(device.client()._backend.ctap2, pin_protocol, PinToken) + return CredentialManagement(device.client()._backend.ctap2, pin_protocol, pt) -def _test_enumeration(CredMgmt, rp_map): +def _test_enumeration(device, rp_map): "Enumerate credentials using BFS" - res = CredMgmt.enumerate_rps() + res = CredMgmt(device).enumerate_rps() assert len(rp_map.keys()) == len(res) for rp in res: - creds = CredMgmt.enumerate_creds(sha256(rp[3]["id"])) - assert len(creds) == rp_map[rp[3]["id"].decode()] + creds = CredMgmt(device).enumerate_creds(sha256(rp[3]["id"].encode())) + assert len(creds) == rp_map[rp[3]["id"]] -def _test_enumeration_interleaved(CredMgmt, rp_map): +def _test_enumeration_interleaved(device, rp_map): "Enumerate credentials using DFS" - first_rp = CredMgmt.enumerate_rps_begin() + first_rp = CredMgmt(device).enumerate_rps_begin() assert len(rp_map.keys()) == first_rp[CredentialManagement.RESULT.TOTAL_RPS] rk_count = 1 - first_rk = CredMgmt.enumerate_creds_begin(sha256(first_rp[3]["id"])) + first_rk = CredMgmt(device).enumerate_creds_begin(sha256(first_rp[3]["id"].encode())) for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]): - c = CredMgmt.enumerate_creds_next() + c = CredMgmt(device).enumerate_creds_next() rk_count += 1 - assert rk_count == rp_map[first_rp[3]["id"].decode()] + assert rk_count == rp_map[first_rp[3]["id"]] for i in range(1, first_rp[CredentialManagement.RESULT.TOTAL_RPS]): - next_rp = CredMgmt.enumerate_rps_next() + next_rp = CredMgmt(device).enumerate_rps_next() rk_count = 1 - first_rk = CredMgmt.enumerate_creds_begin( - sha256(next_rp[3]["id"]) + first_rk = CredMgmt(device).enumerate_creds_begin( + sha256(next_rp[3]["id"].encode()) ) for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]): - c = CredMgmt.enumerate_creds_next() + c = CredMgmt(device).enumerate_creds_next() rk_count += 1 - assert rk_count == rp_map[next_rp[3]["id"].decode()] + assert rk_count == rp_map[next_rp[3]["id"]] -def CredMgmtWrongPinAuth(device, pin_token): +def CredMgmtWrongPinAuth(device): + pin_token = PinToken(device) pin_protocol = PinProtocolV2() wrong_pt = bytearray(pin_token) wrong_pt[0] = (wrong_pt[0] + 1) % 256 @@ -103,13 +98,13 @@ def test_get_info(info): assert 0x8 in info assert info[0x8] > 1 -def test_get_metadata_ok(MC_RK_Res, CredMgmt): - metadata = CredMgmt.get_metadata() +def test_get_metadata_ok(MC_RK_Res, device): + metadata = CredMgmt(device).get_metadata() assert metadata[CredentialManagement.RESULT.EXISTING_CRED_COUNT] == 2 assert metadata[CredentialManagement.RESULT.MAX_REMAINING_COUNT] >= 48 -def test_enumerate_rps(MC_RK_Res, CredMgmt): - res = CredMgmt.enumerate_rps() +def test_enumerate_rps(MC_RK_Res, device): + res = CredMgmt(device).enumerate_rps() assert len(res) == 2 assert res[0][CredentialManagement.RESULT.RP]["id"] == "ssh:" assert res[0][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"ssh:") @@ -117,41 +112,41 @@ def test_enumerate_rps(MC_RK_Res, CredMgmt): assert res[1][CredentialManagement.RESULT.RP]["id"] == "xakcop.com" assert res[1][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"xakcop.com") -def test_enumarate_creds(MC_RK_Res, CredMgmt): - res = CredMgmt.enumerate_creds(sha256(b"ssh:")) +def test_enumarate_creds(MC_RK_Res, device): + res = CredMgmt(device).enumerate_creds(sha256(b"ssh:")) assert len(res) == 1 assert_cred_response_has_all_fields(res[0]) - res = CredMgmt.enumerate_creds(sha256(b"xakcop.com")) + res = CredMgmt(device).enumerate_creds(sha256(b"xakcop.com")) assert len(res) == 1 assert_cred_response_has_all_fields(res[0]) - res = CredMgmt.enumerate_creds(sha256(b"missing.com")) + res = CredMgmt(device).enumerate_creds(sha256(b"missing.com")) assert not res -def test_get_metadata_wrong_pinauth(device, MC_RK_Res, PinToken): +def test_get_metadata_wrong_pinauth(device, MC_RK_Res): cmd = lambda credMgmt: credMgmt.get_metadata() - _test_wrong_pinauth(device, cmd, PinToken) + _test_wrong_pinauth(device, cmd) -def test_rpbegin_wrong_pinauth(device, MC_RK_Res, PinToken): +def test_rpbegin_wrong_pinauth(device, MC_RK_Res): cmd = lambda credMgmt: credMgmt.enumerate_rps_begin() - _test_wrong_pinauth(device, cmd, PinToken) + _test_wrong_pinauth(device, cmd) -def test_rkbegin_wrong_pinauth(device, MC_RK_Res, PinToken): +def test_rkbegin_wrong_pinauth(device, MC_RK_Res): cmd = lambda credMgmt: credMgmt.enumerate_creds_begin(sha256(b"ssh:")) - _test_wrong_pinauth(device, cmd, PinToken) + _test_wrong_pinauth(device, cmd) -def test_rpnext_without_rpbegin(device, MC_RK_Res, CredMgmt): - CredMgmt.enumerate_creds_begin(sha256(b"ssh:")) +def test_rpnext_without_rpbegin(device, MC_RK_Res): + CredMgmt(device).enumerate_creds_begin(sha256(b"ssh:")) with pytest.raises(CtapError) as e: - CredMgmt.enumerate_rps_next() + CredMgmt(device).enumerate_rps_next() assert e.value.code == CtapError.ERR.NOT_ALLOWED -def test_rknext_without_rkbegin(device, MC_RK_Res, CredMgmt): - CredMgmt.enumerate_rps_begin() +def test_rknext_without_rkbegin(device, MC_RK_Res): + CredMgmt(device).enumerate_rps_begin() with pytest.raises(CtapError) as e: - CredMgmt.enumerate_creds_next() + CredMgmt(device).enumerate_creds_next() assert e.value.code == CtapError.ERR.NOT_ALLOWED -def test_delete(device, PinToken, CredMgmt): +def test_delete(device): # create a new RK rp = {"id": "example_3.com", "name": "John Doe 2"} @@ -161,21 +156,21 @@ def test_delete(device, PinToken, CredMgmt): auth = device.doGA(rp_id=rp['id']) # get the ID from enumeration - creds = CredMgmt.enumerate_creds(reg.auth_data.rp_id_hash) + creds = CredMgmt(device).enumerate_creds(reg.auth_data.rp_id_hash) for cred in creds: if cred[7]["id"] == reg.auth_data.credential_data.credential_id: break # delete it cred = {"id": cred[7]["id"], "type": "public-key"} - CredMgmt.delete_cred(cred) + CredMgmt(device).delete_cred(cred) # make sure it doesn't work with pytest.raises(CtapError) as e: auth = device.doGA(rp_id=rp['id']) assert e.value.code == CtapError.ERR.NO_CREDENTIALS -def test_add_delete(device, PinToken, CredMgmt): +def test_add_delete(device): """ Delete a credential in the 'middle' and ensure other credentials are not affected. """ rp = {"id": "example_4.com", "name": "John Doe 3"} @@ -187,11 +182,11 @@ def test_add_delete(device, PinToken, CredMgmt): regs.append(reg) # Check they all enumerate - res = CredMgmt.enumerate_creds(regs[1].auth_data.rp_id_hash) + res = CredMgmt(device).enumerate_creds(regs[1].auth_data.rp_id_hash) assert len(res) == 3 # delete the middle one - creds = CredMgmt.enumerate_creds(reg.auth_data.rp_id_hash) + creds = CredMgmt(device).enumerate_creds(reg.auth_data.rp_id_hash) for cred in creds: if cred[7]["id"] == regs[1].auth_data.credential_data.credential_id: break @@ -199,16 +194,16 @@ def test_add_delete(device, PinToken, CredMgmt): assert cred[7]["id"] == regs[1].auth_data.credential_data.credential_id cred = {"id": cred[7]["id"], "type": "public-key"} - CredMgmt.delete_cred(cred) + CredMgmt(device).delete_cred(cred) # Check one less enumerates - res = CredMgmt.enumerate_creds(regs[0].auth_data.rp_id_hash) + res = CredMgmt(device).enumerate_creds(regs[0].auth_data.rp_id_hash) assert len(res) == 2 def test_multiple_creds_per_multiple_rps( - device, MC_RK_Res, CredMgmt + device, MC_RK_Res ): - res = CredMgmt.enumerate_rps() + res = CredMgmt(device).enumerate_rps() assert len(res) == 2 new_rps = [ @@ -222,27 +217,27 @@ def test_multiple_creds_per_multiple_rps( for i in range(0, 3): reg = device.doMC(rp=rp, rk=True, user=generate_random_user()) - res = CredMgmt.enumerate_rps() + res = CredMgmt(device).enumerate_rps() assert len(res) == 5 for rp in res: if rp[3]["id"][:12] == "new_example_": - creds = CredMgmt.enumerate_creds(sha256(rp[3]["id"].encode("utf8"))) + creds = CredMgmt(device).enumerate_creds(sha256(rp[3]["id"].encode("utf8"))) assert len(creds) == 3 @pytest.mark.parametrize( "enumeration_test", [_test_enumeration, _test_enumeration_interleaved] ) def test_multiple_enumeration( - device, MC_RK_Res, CredMgmt, enumeration_test + device, MC_RK_Res, enumeration_test ): """ Test enumerate still works after different commands """ - res = CredMgmt.enumerate_rps() + res = CredMgmt(device).enumerate_rps() expected_enumeration = {"xakcop.com": 1, "ssh:": 1} - enumeration_test(CredMgmt, expected_enumeration) + enumeration_test(device, expected_enumeration) new_rps = [ {"id": "example-2.com", "name": "Example-2-creds", "count": 2}, @@ -258,27 +253,27 @@ def test_multiple_enumeration( # Now expect creds from this RP expected_enumeration[rp["id"]] = rp["count"] - enumeration_test(CredMgmt, expected_enumeration) - enumeration_test(CredMgmt, expected_enumeration) + enumeration_test(device, expected_enumeration) + enumeration_test(device, expected_enumeration) - metadata = CredMgmt.get_metadata() + metadata = CredMgmt(device).get_metadata() - enumeration_test(CredMgmt, expected_enumeration) - enumeration_test(CredMgmt, expected_enumeration) + enumeration_test(device, expected_enumeration) + enumeration_test(device, expected_enumeration) @pytest.mark.parametrize( "enumeration_test", [_test_enumeration, _test_enumeration_interleaved] ) def test_multiple_enumeration_with_deletions( - device, MC_RK_Res, CredMgmt, enumeration_test + device, MC_RK_Res, enumeration_test ): """ Create each credential in random order. Test enumerate still works after randomly deleting each credential""" - res = CredMgmt.enumerate_rps() + res = CredMgmt(device).enumerate_rps() expected_enumeration = {"xakcop.com": 1, "ssh:": 1} - enumeration_test(CredMgmt, expected_enumeration) + enumeration_test(device, expected_enumeration) new_rps = [ {"id": "example-1.com", "name": "Example-1-creds"}, @@ -299,7 +294,7 @@ def test_multiple_enumeration_with_deletions( else: expected_enumeration[rp["id"]] += 1 - enumeration_test(CredMgmt, expected_enumeration) + enumeration_test(device, expected_enumeration) total_creds = len(new_rps) @@ -309,10 +304,10 @@ def test_multiple_enumeration_with_deletions( num = expected_enumeration[rp] index = 0 if num == 1 else random.randint(0, num - 1) - cred = CredMgmt.enumerate_creds(sha256(rp.encode("utf8")))[index] + cred = CredMgmt(device).enumerate_creds(sha256(rp.encode("utf8")))[index] # print('Delete %d index (%d total) cred of %s' % (index, expected_enumeration[rp], rp)) - CredMgmt.delete_cred({"id": cred[7]["id"], "type": "public-key"}) + CredMgmt(device).delete_cred({"id": cred[7]["id"], "type": "public-key"}) expected_enumeration[rp] -= 1 if expected_enumeration[rp] == 0: @@ -321,11 +316,11 @@ def test_multiple_enumeration_with_deletions( if len(list(expected_enumeration.keys())) == 0: break - enumeration_test(CredMgmt, expected_enumeration) + enumeration_test(device, expected_enumeration) -def _test_wrong_pinauth(device, cmd, PinToken): +def _test_wrong_pinauth(device, cmd): - credMgmt = CredMgmtWrongPinAuth(device, PinToken) + credMgmt = CredMgmtWrongPinAuth(device) for i in range(2): with pytest.raises(CtapError) as e: @@ -336,8 +331,8 @@ def _test_wrong_pinauth(device, cmd, PinToken): cmd(credMgmt) assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED - #device.reboot() - credMgmt = CredMgmtWrongPinAuth(device, PinToken) + device.reboot() + credMgmt = CredMgmtWrongPinAuth(device) for i in range(2): time.sleep(0.2) @@ -349,8 +344,8 @@ def _test_wrong_pinauth(device, cmd, PinToken): cmd(credMgmt) assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED - #device.reboot() - credMgmt = CredMgmtWrongPinAuth(device, PinToken) + device.reboot() + credMgmt = CredMgmtWrongPinAuth(device) for i in range(1): time.sleep(0.2) From d0924f5eccfb7490842a1086e76372f14728f366 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Fri, 9 Dec 2022 19:02:23 +0100 Subject: [PATCH 24/50] Some optimizations to speed up tests. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_cred_mgmt.py | 99 +++++++++++-------------------- 1 file changed, 34 insertions(+), 65 deletions(-) diff --git a/tests/pico-fido/test_cred_mgmt.py b/tests/pico-fido/test_cred_mgmt.py index 36c3c71..dd201cd 100644 --- a/tests/pico-fido/test_cred_mgmt.py +++ b/tests/pico-fido/test_cred_mgmt.py @@ -36,36 +36,38 @@ def CredMgmt(device): def _test_enumeration(device, rp_map): "Enumerate credentials using BFS" - res = CredMgmt(device).enumerate_rps() + credMgmt = CredMgmt(device) + res = credMgmt.enumerate_rps() assert len(rp_map.keys()) == len(res) for rp in res: - creds = CredMgmt(device).enumerate_creds(sha256(rp[3]["id"].encode())) + creds = credMgmt.enumerate_creds(sha256(rp[3]["id"].encode())) assert len(creds) == rp_map[rp[3]["id"]] def _test_enumeration_interleaved(device, rp_map): "Enumerate credentials using DFS" - first_rp = CredMgmt(device).enumerate_rps_begin() + credMgmt = CredMgmt(device) + first_rp = credMgmt.enumerate_rps_begin() assert len(rp_map.keys()) == first_rp[CredentialManagement.RESULT.TOTAL_RPS] rk_count = 1 - first_rk = CredMgmt(device).enumerate_creds_begin(sha256(first_rp[3]["id"].encode())) + first_rk = credMgmt.enumerate_creds_begin(sha256(first_rp[3]["id"].encode())) for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]): - c = CredMgmt(device).enumerate_creds_next() + c = credMgmt.enumerate_creds_next() rk_count += 1 assert rk_count == rp_map[first_rp[3]["id"]] for i in range(1, first_rp[CredentialManagement.RESULT.TOTAL_RPS]): - next_rp = CredMgmt(device).enumerate_rps_next() + next_rp = credMgmt.enumerate_rps_next() rk_count = 1 - first_rk = CredMgmt(device).enumerate_creds_begin( + first_rk =credMgmt.enumerate_creds_begin( sha256(next_rp[3]["id"].encode()) ) for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]): - c = CredMgmt(device).enumerate_creds_next() + c = credMgmt.enumerate_creds_next() rk_count += 1 assert rk_count == rp_map[next_rp[3]["id"]] @@ -113,13 +115,14 @@ def test_enumerate_rps(MC_RK_Res, device): assert res[1][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"xakcop.com") def test_enumarate_creds(MC_RK_Res, device): - res = CredMgmt(device).enumerate_creds(sha256(b"ssh:")) + credMgmt = CredMgmt(device) + res = credMgmt.enumerate_creds(sha256(b"ssh:")) assert len(res) == 1 assert_cred_response_has_all_fields(res[0]) - res = CredMgmt(device).enumerate_creds(sha256(b"xakcop.com")) + res = credMgmt.enumerate_creds(sha256(b"xakcop.com")) assert len(res) == 1 assert_cred_response_has_all_fields(res[0]) - res = CredMgmt(device).enumerate_creds(sha256(b"missing.com")) + res = credMgmt.enumerate_creds(sha256(b"missing.com")) assert not res def test_get_metadata_wrong_pinauth(device, MC_RK_Res): @@ -135,15 +138,17 @@ def test_rkbegin_wrong_pinauth(device, MC_RK_Res): _test_wrong_pinauth(device, cmd) def test_rpnext_without_rpbegin(device, MC_RK_Res): - CredMgmt(device).enumerate_creds_begin(sha256(b"ssh:")) + credMgmt = CredMgmt(device) + credMgmt.enumerate_creds_begin(sha256(b"ssh:")) with pytest.raises(CtapError) as e: - CredMgmt(device).enumerate_rps_next() + credMgmt.enumerate_rps_next() assert e.value.code == CtapError.ERR.NOT_ALLOWED def test_rknext_without_rkbegin(device, MC_RK_Res): - CredMgmt(device).enumerate_rps_begin() + credMgmt = CredMgmt(device) + credMgmt.enumerate_rps_begin() with pytest.raises(CtapError) as e: - CredMgmt(device).enumerate_creds_next() + credMgmt.enumerate_creds_next() assert e.value.code == CtapError.ERR.NOT_ALLOWED def test_delete(device): @@ -156,14 +161,15 @@ def test_delete(device): auth = device.doGA(rp_id=rp['id']) # get the ID from enumeration - creds = CredMgmt(device).enumerate_creds(reg.auth_data.rp_id_hash) + credMgmt = CredMgmt(device) + creds = credMgmt.enumerate_creds(reg.auth_data.rp_id_hash) for cred in creds: if cred[7]["id"] == reg.auth_data.credential_data.credential_id: break # delete it cred = {"id": cred[7]["id"], "type": "public-key"} - CredMgmt(device).delete_cred(cred) + credMgmt.delete_cred(cred) # make sure it doesn't work with pytest.raises(CtapError) as e: @@ -182,11 +188,12 @@ def test_add_delete(device): regs.append(reg) # Check they all enumerate - res = CredMgmt(device).enumerate_creds(regs[1].auth_data.rp_id_hash) + credMgmt = CredMgmt(device) + res = credMgmt.enumerate_creds(regs[1].auth_data.rp_id_hash) assert len(res) == 3 # delete the middle one - creds = CredMgmt(device).enumerate_creds(reg.auth_data.rp_id_hash) + creds = credMgmt.enumerate_creds(reg.auth_data.rp_id_hash) for cred in creds: if cred[7]["id"] == regs[1].auth_data.credential_data.credential_id: break @@ -194,10 +201,10 @@ def test_add_delete(device): assert cred[7]["id"] == regs[1].auth_data.credential_data.credential_id cred = {"id": cred[7]["id"], "type": "public-key"} - CredMgmt(device).delete_cred(cred) + credMgmt.delete_cred(cred) # Check one less enumerates - res = CredMgmt(device).enumerate_creds(regs[0].auth_data.rp_id_hash) + res = credMgmt.enumerate_creds(regs[0].auth_data.rp_id_hash) assert len(res) == 2 def test_multiple_creds_per_multiple_rps( @@ -217,12 +224,13 @@ def test_multiple_creds_per_multiple_rps( for i in range(0, 3): reg = device.doMC(rp=rp, rk=True, user=generate_random_user()) - res = CredMgmt(device).enumerate_rps() + credMgmt = CredMgmt(device) + res = credMgmt.enumerate_rps() assert len(res) == 5 for rp in res: if rp[3]["id"][:12] == "new_example_": - creds = CredMgmt(device).enumerate_creds(sha256(rp[3]["id"].encode("utf8"))) + creds = credMgmt.enumerate_creds(sha256(rp[3]["id"].encode("utf8"))) assert len(creds) == 3 @pytest.mark.parametrize( @@ -233,8 +241,6 @@ def test_multiple_enumeration( ): """ Test enumerate still works after different commands """ - res = CredMgmt(device).enumerate_rps() - expected_enumeration = {"xakcop.com": 1, "ssh:": 1} enumeration_test(device, expected_enumeration) @@ -254,12 +260,6 @@ def test_multiple_enumeration( expected_enumeration[rp["id"]] = rp["count"] enumeration_test(device, expected_enumeration) - enumeration_test(device, expected_enumeration) - - metadata = CredMgmt(device).get_metadata() - - enumeration_test(device, expected_enumeration) - enumeration_test(device, expected_enumeration) @pytest.mark.parametrize( "enumeration_test", [_test_enumeration, _test_enumeration_interleaved] @@ -269,8 +269,6 @@ def test_multiple_enumeration_with_deletions( ): """ Create each credential in random order. Test enumerate still works after randomly deleting each credential""" - res = CredMgmt(device).enumerate_rps() - expected_enumeration = {"xakcop.com": 1, "ssh:": 1} enumeration_test(device, expected_enumeration) @@ -304,10 +302,11 @@ def test_multiple_enumeration_with_deletions( num = expected_enumeration[rp] index = 0 if num == 1 else random.randint(0, num - 1) - cred = CredMgmt(device).enumerate_creds(sha256(rp.encode("utf8")))[index] + credMgmt = CredMgmt(device) + cred = credMgmt.enumerate_creds(sha256(rp.encode("utf8")))[index] # print('Delete %d index (%d total) cred of %s' % (index, expected_enumeration[rp], rp)) - CredMgmt(device).delete_cred({"id": cred[7]["id"], "type": "public-key"}) + credMgmt.delete_cred({"id": cred[7]["id"], "type": "public-key"}) expected_enumeration[rp] -= 1 if expected_enumeration[rp] == 0: @@ -326,33 +325,3 @@ def _test_wrong_pinauth(device, cmd): with pytest.raises(CtapError) as e: cmd(credMgmt) assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID - - with pytest.raises(CtapError) as e: - cmd(credMgmt) - assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED - - device.reboot() - credMgmt = CredMgmtWrongPinAuth(device) - - for i in range(2): - time.sleep(0.2) - with pytest.raises(CtapError) as e: - cmd(credMgmt) - assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID - - with pytest.raises(CtapError) as e: - cmd(credMgmt) - assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED - - device.reboot() - credMgmt = CredMgmtWrongPinAuth(device) - - for i in range(1): - time.sleep(0.2) - with pytest.raises(CtapError) as e: - cmd(credMgmt) - assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID - - with pytest.raises(CtapError) as e: - cmd(credMgmt) - assert e.value.code == CtapError.ERR.PIN_BLOCKED From 0d51d3c7275ed38924e450f511c960ccc3b00791 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 10 Dec 2022 20:49:09 +0100 Subject: [PATCH 25/50] Number of credentials is always returned in GA, as Pico Fido does not have any display. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index 07f5fd9..7f9059c 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -485,7 +485,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { uint8_t lfields = 3; if (selcred->opts.present == true && selcred->opts.rk == ptrue) lfields++; - if (numberOfCredentials > 1 && next == false && up == false && uv == false) + if (numberOfCredentials > 1 && next == false) lfields++; if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) lfields++; @@ -529,7 +529,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); } - if (numberOfCredentials > 1 && next == false && up == false && uv == false) { + if (numberOfCredentials > 1 && next == false) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials)); } From 360b8eadaabf2d16f8e277ecca506c9342afe854 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 10 Dec 2022 21:36:17 +0100 Subject: [PATCH 26/50] Add minimal test for minPinLength extension. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_minpinlength.py | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/pico-fido/test_minpinlength.py diff --git a/tests/pico-fido/test_minpinlength.py b/tests/pico-fido/test_minpinlength.py new file mode 100644 index 0000000..57ea515 --- /dev/null +++ b/tests/pico-fido/test_minpinlength.py @@ -0,0 +1,34 @@ +import pytest +from fido2.ctap2.extensions import CredProtectExtension +from fido2.webauthn import UserVerificationRequirement +from fido2.ctap import CtapError +from fido2.ctap2.pin import PinProtocolV2, ClientPin +from fido2.ctap2 import Config + +PIN='12345678' +MINPINLENGTH=6 + +@pytest.fixture(scope="function") +def MCMinPin(device): + res = device.doMC(rk=True, extensions={'minPinLength': True})['res'].attestation_object + return res + +@pytest.fixture(scope="function") +def SetMinPin(device): + device.reset() + ClientPin(device.client()._backend.ctap2).set_pin(PIN) + cfg = FidoConfig(device) + cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com']) + +def PinToken(device): + return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG) + +def FidoConfig(device): + pt = PinToken(device) + pin_protocol = PinProtocolV2() + return Config(device.client()._backend.ctap2, pin_protocol, pt) + +def test_minpin(MCMinPin, SetMinPin): + assert MCMinPin.auth_data.extensions + assert "minPinLength" in MCMinPin.auth_data.extensions + assert MCMinPin.auth_data.extensions['minPinLength'] == MINPINLENGTH From 6030f33977b16e73f1d6930f4e8c9a92349a6fcb Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 11 Dec 2022 00:07:22 +0100 Subject: [PATCH 27/50] Added more tests Signed-off-by: Pol Henarejos --- tests/pico-fido/test_minpinlength.py | 42 +++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/pico-fido/test_minpinlength.py b/tests/pico-fido/test_minpinlength.py index 57ea515..fdc4382 100644 --- a/tests/pico-fido/test_minpinlength.py +++ b/tests/pico-fido/test_minpinlength.py @@ -20,6 +20,13 @@ def SetMinPin(device): cfg = FidoConfig(device) cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com']) +@pytest.fixture(scope="function") +def SetMinPinWrongRpid(device): + device.reset() + ClientPin(device.client()._backend.ctap2).set_pin(PIN) + cfg = FidoConfig(device) + cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['notanexample.com']) + def PinToken(device): return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG) @@ -28,7 +35,40 @@ def FidoConfig(device): pin_protocol = PinProtocolV2() return Config(device.client()._backend.ctap2, pin_protocol, pt) -def test_minpin(MCMinPin, SetMinPin): +def test_minpin(SetMinPin, MCMinPin): assert MCMinPin.auth_data.extensions assert "minPinLength" in MCMinPin.auth_data.extensions assert MCMinPin.auth_data.extensions['minPinLength'] == MINPINLENGTH + +def test_minpin_bad_rpid(SetMinPinWrongRpid, MCMinPin): + assert not MCMinPin.auth_data.extensions + assert "minPinLength" not in MCMinPin.auth_data.extensions + +def test_setminpin(device, SetMinPin, MCMinPin): + cfg = FidoConfig(device) + cfg.set_min_pin_length(MINPINLENGTH+2,rp_ids=['example.com']) + res = device.doMC(rk=True, extensions={'minPinLength': True})['res'].attestation_object + assert res.auth_data.extensions + assert "minPinLength" in res.auth_data.extensions + assert res.auth_data.extensions['minPinLength'] == MINPINLENGTH+2 + +def test_no_setminpin(device, SetMinPin, MCMinPin): + cfg = FidoConfig(device) + with pytest.raises(CtapError) as e: + cfg.set_min_pin_length(MINPINLENGTH-2,rp_ids=['example.com']) + assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION + +def test_setminpin_check_force(device, SetMinPin, MCMinPin): + cfg = FidoConfig(device) + cfg.set_min_pin_length(len(PIN)+1,rp_ids=['example.com']) + info = device.client()._backend.ctap2.get_info() + assert info.force_pin_change == True + +@pytest.mark.parametrize( + "force", [True, False] +) +def test_setminpin_set_forcee(device, SetMinPin, MCMinPin, force): + cfg = FidoConfig(device) + cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com'],force_change_pin=force) + info = device.client()._backend.ctap2.get_info() + assert info.force_pin_change == force From 1d1d8ce5c31195eec723d4506f048cf0a3e41751 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 11 Dec 2022 21:04:35 +0100 Subject: [PATCH 28/50] Adding info test. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_minpinlength.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/pico-fido/test_minpinlength.py b/tests/pico-fido/test_minpinlength.py index fdc4382..6b4efd9 100644 --- a/tests/pico-fido/test_minpinlength.py +++ b/tests/pico-fido/test_minpinlength.py @@ -35,6 +35,13 @@ def FidoConfig(device): pin_protocol = PinProtocolV2() return Config(device.client()._backend.ctap2, pin_protocol, pt) +def test_supports_minpin(info): + assert info.extensions + assert 'minPinLength' in info.extensions + assert info.options + assert 'setMinPINLength' in info.options + assert info.options['setMinPINLength'] is True + def test_minpin(SetMinPin, MCMinPin): assert MCMinPin.auth_data.extensions assert "minPinLength" in MCMinPin.auth_data.extensions From 81717135f565b5504cc9c721c56f5c6ba58dee18 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sun, 11 Dec 2022 21:04:55 +0100 Subject: [PATCH 29/50] Add test for credBlob extension. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_blob.py | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/pico-fido/test_blob.py diff --git a/tests/pico-fido/test_blob.py b/tests/pico-fido/test_blob.py new file mode 100644 index 0000000..7458fb7 --- /dev/null +++ b/tests/pico-fido/test_blob.py @@ -0,0 +1,69 @@ +import pytest +from fido2.ctap import CtapError +from fido2.ctap2.pin import PinProtocolV2, ClientPin +from utils import verify +import os + +PIN='12345678' +SMALL_BLOB=b"A"*32 + +@pytest.fixture(scope="function") +def MCCredBlob(device): + res = device.doMC(extensions={'credBlob': SMALL_BLOB})['res'].attestation_object + return res + +@pytest.fixture(scope="function") +def GACredBlob(device, MCCredBlob): + res = device.doGA(allow_list=[ + {"id": MCCredBlob.auth_data.credential_data.credential_id, "type": "public-key"} + ], extensions={'getCredBlob': True}) + + assertions = res['res'].get_assertions() + for a in assertions: + verify(MCCredBlob, a, res['req']['client_data'].hash) + return assertions[0] + +def test_supports_credblob(info): + assert info.extensions + assert 'credBlob' in info.extensions + assert info.max_cred_blob_length + assert info.max_cred_blob_length > 0 + +def test_mc_credblob(MCCredBlob): + assert MCCredBlob.auth_data.extensions + assert "credBlob" in MCCredBlob.auth_data.extensions + assert MCCredBlob.auth_data.extensions['credBlob'] is True + +def test_ga_credblob(GACredBlob): + assert GACredBlob.auth_data.extensions + assert "credBlob" in GACredBlob.auth_data.extensions + assert GACredBlob.auth_data.extensions['credBlob'] == SMALL_BLOB + +def test_wrong_credblob(device, info): + device.reset() + cdh = os.urandom(32) + ClientPin(device.client()._backend.ctap2).set_pin(PIN) + pin_token = ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG) + protocol = PinProtocolV2() + MC = device.MC( + client_data_hash=cdh, + extensions={'credBlob': b'A'*(info.max_cred_blob_length+1)}, + pin_uv_protocol=protocol.VERSION, + pin_uv_param=protocol.authenticate(pin_token, cdh) + )['res'] + + assert MC.auth_data.extensions + assert "credBlob" in MC.auth_data.extensions + assert MC.auth_data.extensions['credBlob'] is False + + res = device.doGA(allow_list=[ + {"id": MC.auth_data.credential_data.credential_id, "type": "public-key"} + ], extensions={'getCredBlob': True}) + + assertions = res['res'].get_assertions() + for a in assertions: + verify(MC, a, res['req']['client_data'].hash) + + assert assertions[0].auth_data.extensions + assert "credBlob" in assertions[0].auth_data.extensions + assert len(assertions[0].auth_data.extensions['credBlob']) == 0 From 2431812a18ca1a6dfb69a3a854f0b15124763620 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 12 Dec 2022 00:16:17 +0100 Subject: [PATCH 30/50] Return largeBlobKey on cred management. Signed-off-by: Pol Henarejos --- src/fido/cbor_cred_mgmt.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 74523ba..2054df5 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -221,10 +221,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } cred_counter++; - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, subcommand == 0x04 ? 5 : 4)); + + uint8_t l = 4; + if (subcommand == 0x04) + l++; + if (cred.extensions.present == true) { + if (cred.extensions.credProtect > 0) + l++; + if (cred.extensions.largeBlobKey == ptrue) + l++; + } + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06)); - uint8_t l = 0; + l = 0; if (cred.userId.present == true) l++; if (cred.userName.present == true) @@ -279,8 +289,21 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { asserted = true; rpIdHashx = rpIdHash; } - CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A)); - CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred.extensions.credProtect)); + if (cred.extensions.present == true) { + if (cred.extensions.credProtect > 0) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred.extensions.credProtect)); + } + if (cred.extensions.largeBlobKey == ptrue) { + uint8_t largeBlobKey[32]; + int ret = credential_derive_large_blob_key(cred.id.data, cred.id.len, largeBlobKey); + if (ret != 0) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey))); + } + } credential_free(&cred); mbedtls_ecdsa_free(&key); } From e87ae34ab5a384cccef24f917d7f20f4475948e2 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 12 Dec 2022 00:31:05 +0100 Subject: [PATCH 31/50] Adde largeBlobs to get info. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 3df5634..8098bd0 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -48,7 +48,7 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid))); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04)); - CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 7)); + CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 8)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep")); CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk")); @@ -62,6 +62,8 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true)); else CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false)); + CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobs")); + CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "pinUvAuthToken")); CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true)); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "setMinPINLength")); From 839562130a4354c2647a0776ceae7d0e4cd2f24e Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 12 Dec 2022 00:37:56 +0100 Subject: [PATCH 32/50] Zeroize large blob key. Signed-off-by: Pol Henarejos --- src/fido/cbor_cred_mgmt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c index 2054df5..07dc6e3 100644 --- a/src/fido/cbor_cred_mgmt.c +++ b/src/fido/cbor_cred_mgmt.c @@ -302,6 +302,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) { } CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey))); + mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey)); } } credential_free(&cred); From 703e4697ec71d254702e829b86f7a0f3cc2abb6b Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 12 Dec 2022 00:39:49 +0100 Subject: [PATCH 33/50] Fix loading large blob key from a credential id. Signed-off-by: Pol Henarejos --- src/fido/credential.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/credential.c b/src/fido/credential.c index 3c2d122..fe52c9d 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -164,7 +164,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", cred->extensions.hmac_secret); CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect); CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", cred->extensions.credBlob); - CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKeys", cred->extensions.largeBlobKey); + CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", cred->extensions.largeBlobKey); CBOR_ADVANCE(2); } CBOR_PARSE_MAP_END(_f1, 2); From 24b66dcffcb38399e9997190b2d2ead034c23771 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Mon, 12 Dec 2022 00:48:17 +0100 Subject: [PATCH 34/50] Added some largeBlobKey tests. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_blob.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/pico-fido/test_blob.py b/tests/pico-fido/test_blob.py index 7458fb7..9183d3f 100644 --- a/tests/pico-fido/test_blob.py +++ b/tests/pico-fido/test_blob.py @@ -23,6 +23,26 @@ def GACredBlob(device, MCCredBlob): verify(MCCredBlob, a, res['req']['client_data'].hash) return assertions[0] +@pytest.fixture(scope="function") +def MCLBK(device): + res = device.doMC( + rk=True, + extensions={'largeBlob':{'support':'required'}} + )['res'] + return res + +@pytest.fixture(scope="function") +def GALBRead(device, MCLBK): + res = device.doGA( + allow_list=[ + {"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} + ],extensions={'largeBlob':{'read': True}} + ) + assertions = res['res'].get_assertions() + for a in assertions: + verify(MCLBK.attestation_object, a, res['req']['client_data'].hash) + return assertions[0] + def test_supports_credblob(info): assert info.extensions assert 'credBlob' in info.extensions @@ -67,3 +87,15 @@ def test_wrong_credblob(device, info): assert assertions[0].auth_data.extensions assert "credBlob" in assertions[0].auth_data.extensions assert len(assertions[0].auth_data.extensions['credBlob']) == 0 + +def test_supports_largeblobs(info): + assert info.extensions + assert 'largeBlobKey' in info.extensions + +def test_get_largeblobkey_mc(MCLBK): + assert 'supported' in MCLBK.extension_results + assert MCLBK.extension_results['supported'] is True + +def test_get_largeblobkey_ga(GALBRead): + assert GALBRead.large_blob_key is not None + From 4a3f957fdf3f10eef86cce4453ca7767b63c21de Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:40:02 +0100 Subject: [PATCH 35/50] Add initial large blob command. Signed-off-by: Pol Henarejos --- src/fido/cbor_large_blobs.c | 150 ++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/fido/cbor_large_blobs.c diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c new file mode 100644 index 0000000..606f4ea --- /dev/null +++ b/src/fido/cbor_large_blobs.c @@ -0,0 +1,150 @@ +/* + * 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 "version.h" +#include "hsm.h" +#include "mbedtls/sha256.h" + +static uint64_t expectedLength = 0, expectedNextOffset = 0; +uint8_t temp_lba[MAX_LARGE_BLOB_SIZE]; + +int cbor_large_blobs(const uint8_t *data, size_t len) { + CborParser parser; + CborValue map; + CborEncoder encoder, mapEncoder; + CborError error = CborNoError; + uint64_t get = 0, offset = UINT64_MAX, length = 0, pinUvAuthProtocol = 0; + CborByteString set = {0}, pinUvAuthParam = {0}; + + 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(get, 1); + } + else if (val_u == 0x02) { + CBOR_FIELD_GET_BYTES(set, 1); + } + else if (val_u == 0x03) { + CBOR_FIELD_GET_UINT(offset, 1); + } + else if (val_u == 0x04) { + CBOR_FIELD_GET_UINT(length, 1); + } + else if (val_u == 0x05) { + CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1); + } + else if (val_u == 0x06) { + CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1); + } + } + CBOR_PARSE_MAP_END(map, 1); + + if (offset == 0) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + if (get == 0 && set.present == false) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + if (get != 0 && set.present == true) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + + cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); + if (get > 0) { + if (length != 0) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + if (length > MAX_FRAGMENT_LENGTH) + CBOR_ERROR(CTAP1_ERR_INVALID_LEN); + if (offset > file_get_size(ef_largeblob)) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + 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_largeblob)+offset, MIN(get, file_get_size(ef_largeblob)-offset))); + } + else { + if (set.len > MAX_FRAGMENT_LENGTH) + CBOR_ERROR(CTAP1_ERR_INVALID_LEN); + if (offset == 0) { + if (length == 0) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + if (length > MAX_LARGE_BLOB_SIZE) { + CBOR_ERROR(CTAP2_ERR_LARGE_BLOB_STORAGE_FULL); + } + if (length < 17) { + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + expectedLength = length; + expectedNextOffset = 0; + } + else { + if (length != 0) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + if (offset != expectedNextOffset) + CBOR_ERROR(CTAP1_ERR_INVALID_SEQ); + if (pinUvAuthParam.present == false) + CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED); + if (pinUvAuthProtocol == 0) + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + uint8_t verify_data[70] = {0}; + memset(verify_data, 0xff, 32); + verify_data[32] = 0x0C; + verify_data[34] = offset & 0xff; + verify_data[35] = offset >> 8; + verify_data[36] = offset >> 16; + verify_data[37] = offset >> 24; + mbedtls_sha256(set.data, set.len, verify_data+38, 0); + if (verify(pinUvAuthProtocol, paut.data, verify_data, sizeof(verify_data), pinUvAuthParam.data) != 0) + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + if (!(paut.permissions & CTAP_PERMISSION_LBW)) + CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); + if (offset+set.len > expectedLength) + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + if (offset == 0) + memset(temp_lba, 0, sizeof(temp_lba)); + memcpy(temp_lba+expectedNextOffset, set.data, set.len); + expectedNextOffset += set.len; + if (expectedNextOffset == expectedLength) { + uint8_t sha[32]; + mbedtls_sha256(temp_lba, expectedLength, sha, 0); + if (expectedLength > 17 && memcmp(sha, temp_lba+expectedLength-16, 16) != 0) + CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE); + flash_write_data_to_file(ef_largeblob, temp_lba, expectedLength); + low_flash_available(); + } + goto err; + } + CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); + + err: + CBOR_FREE_BYTE_STRING(pinUvAuthParam); + CBOR_FREE_BYTE_STRING(set); + if (error != CborNoError) + return -CTAP2_ERR_INVALID_CBOR; + res_APDU_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1); + return 0; +} From a4d82136c2959d1667ac84b1aaf8bd337420b625 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:40:14 +0100 Subject: [PATCH 36/50] Compile large blob command. Signed-off-by: Pol Henarejos --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f164fc7..764ec7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ target_sources(pico_fido PUBLIC ${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 + ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_large_blobs.c ) set(HSM_DRIVER "hid") include(pico-hsm-sdk/pico_hsm_sdk_import.cmake) From aa4255b875f8a95a879a6faa5e8bb6f5bbe62ac8 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:41:19 +0100 Subject: [PATCH 37/50] Add large blob command to cbor. Signed-off-by: Pol Henarejos --- src/fido/cbor.c | 3 +++ src/fido/ctap.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index 3896916..23b5d69 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -37,6 +37,7 @@ 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); +int cbor_large_blobs(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") @@ -68,6 +69,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) { return cbor_cred_mgmt(data + 1, len - 1); else if (data[0] == CTAP_CONFIG) return cbor_config(data + 1, len - 1); + else if (data[0] == CTAP_LARGE_BLOBS) + return cbor_config(data + 1, len - 1); } else if (cmd == CTAP_VENDOR_CBOR) { return cbor_vendor(data, len); diff --git a/src/fido/ctap.h b/src/fido/ctap.h index db09d8f..ad012f9 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -114,6 +114,7 @@ typedef struct { #define CTAP_GET_NEXT_ASSERTION 0x08 #define CTAP_CREDENTIAL_MGMT 0x0A #define CTAP_SELECTION 0x0B +#define CTAP_LARGE_BLOBS 0x0C #define CTAP_CONFIG 0x0D #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 From f39a51afca84475cf3ebea43c67a794d656ea217 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:41:38 +0100 Subject: [PATCH 38/50] Add macro for large blob size. Signed-off-by: Pol Henarejos --- src/fido/fido.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fido/fido.h b/src/fido/fido.h index 6eb90e8..0e0445b 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -81,6 +81,7 @@ extern void set_opts(uint8_t); #define MAX_CREDBLOB_LENGTH 128 #define MAX_MSG_SIZE 1024 #define MAX_FRAGMENT_LENGTH (MAX_MSG_SIZE - 64) +#define MAX_LARGE_BLOB_SIZE 2048 typedef struct known_app { const uint8_t *rp_id_hash; From 4ddd45f16eab13ee86ed59053f68e10e9cdb509d Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:41:48 +0100 Subject: [PATCH 39/50] Add ef to large blob array. Signed-off-by: Pol Henarejos --- src/fido/fido.c | 1 + src/fido/files.c | 2 ++ src/fido/files.h | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/fido/fido.c b/src/fido/fido.c index 14334d9..4eb076f 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -289,6 +289,7 @@ int scan_files(bool core1) { else { printf("FATAL ERROR: Auth Token not found in memory!\r\n"); } + ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF); low_flash_available(); return CCID_OK; } diff --git a/src/fido/files.c b/src/fido/files.c index 0e7e52e..fe917da 100644 --- a/src/fido/files.c +++ b/src/fido/files.c @@ -29,6 +29,7 @@ file_t file_entries[] = { {.fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // AUTH TOKEN {.fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // MIN PIN LENGTH {.fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Global options + {.fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Large Blob { .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = {0} } //end }; @@ -40,3 +41,4 @@ file_t *ef_counter = NULL; file_t *ef_pin = NULL; file_t *ef_authtoken = NULL; file_t *ef_keydev_enc = NULL; +file_t *ef_largeblob = NULL; diff --git a/src/fido/files.h b/src/fido/files.h index ad96ca0..32fab52 100644 --- a/src/fido/files.h +++ b/src/fido/files.h @@ -31,6 +31,7 @@ #define EF_MINPINLEN 0x1100 #define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF #define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF +#define EF_LARGEBLOB 0x1101 // Large Blob Array extern file_t *ef_keydev; extern file_t *ef_certdev; @@ -38,5 +39,6 @@ extern file_t *ef_counter; extern file_t *ef_pin; extern file_t *ef_authtoken; extern file_t *ef_keydev_enc; +extern file_t *ef_largeblob; #endif //_FILES_H_ From cb24927a80ad12aec820d894cee0b35ae74ecdef Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:42:09 +0100 Subject: [PATCH 40/50] Update get info command to add max large blob array length. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_info.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index 8098bd0..689ddca 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -26,7 +26,7 @@ int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; CborError error = CborNoError; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 14)); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); @@ -107,6 +107,9 @@ int cbor_get_info() { CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2)); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_LARGE_BLOB_SIZE)); // maxSerializedLargeBlobArray + file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C)); if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) From 641c2fb880b9eee635465bab680d8f4221cdd2e4 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 19:42:24 +0100 Subject: [PATCH 41/50] Minor indent changes. Signed-off-by: Pol Henarejos --- src/fido/cbor_config.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index 402a931..7df0ba5 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -95,7 +95,7 @@ int cbor_config(const uint8_t *data, size_t len) { else if (val_u == 0x03) { CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1); } - else if (val_u == 0x04) { // pubKeyCredParams + else if (val_u == 0x04) { CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1); } } @@ -204,12 +204,11 @@ int cbor_config(const uint8_t *data, size_t len) { CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]); } - if (error != CborNoError) - { - if (error == CborErrorImproperValue) - return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; - return error; - } + if (error != CborNoError) { + if (error == CborErrorImproperValue) + return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; + return error; + } res_APDU_size = resp_size; return 0; } From 9bcfacfe08f13ffe3f8a1f09d1b7d5c5e3522399 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 23:51:03 +0100 Subject: [PATCH 42/50] Fix calling large blobs. Signed-off-by: Pol Henarejos --- src/fido/cbor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index 23b5d69..a0562b6 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -70,7 +70,7 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) { else if (data[0] == CTAP_CONFIG) return cbor_config(data + 1, len - 1); else if (data[0] == CTAP_LARGE_BLOBS) - return cbor_config(data + 1, len - 1); + return cbor_large_blobs(data + 1, len - 1); } else if (cmd == CTAP_VENDOR_CBOR) { return cbor_vendor(data, len); From 81d3da2645abb2454eb50a8ef2bb1bd3c0b75402 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 23:51:17 +0100 Subject: [PATCH 43/50] Activating LBW permission. Signed-off-by: Pol Henarejos --- src/fido/cbor_client_pin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index 4e2d053..5b4dadc 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -485,7 +485,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) { if (subcommand == 0x9) { if (permissions == 0) CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); - if ((permissions & CTAP_PERMISSION_BE) || (permissions & CTAP_PERMISSION_LBW)) // Not supported yet + if ((permissions & CTAP_PERMISSION_BE)) // Not supported yet CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION); } From 4c724d0e8b5c52b83b24b0bc7c57954003c8a1ae Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 23:51:30 +0100 Subject: [PATCH 44/50] Fix offset parameter. Signed-off-by: Pol Henarejos --- src/fido/cbor_large_blobs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c index 606f4ea..b57ae8a 100644 --- a/src/fido/cbor_large_blobs.c +++ b/src/fido/cbor_large_blobs.c @@ -66,7 +66,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) { } CBOR_PARSE_MAP_END(map, 1); - if (offset == 0) + if (offset == UINT64_MAX) CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); if (get == 0 && set.present == false) CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); From 19dd52f944fe093e09fdc6bdfdd53fddf738f9ba Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Wed, 14 Dec 2022 23:59:54 +0100 Subject: [PATCH 45/50] Fix with required parameters. LB required parameters are not at the begining of map. Signed-off-by: Pol Henarejos --- src/fido/cbor_large_blobs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c index b57ae8a..3b79296 100644 --- a/src/fido/cbor_large_blobs.c +++ b/src/fido/cbor_large_blobs.c @@ -40,7 +40,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) { 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) + if (val_c <= 0 && val_c != val_u) CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); if (val_u < val_c) CBOR_ERROR(CTAP2_ERR_INVALID_CBOR); From 9160bbb8fe966e180e400a28df91d689ad9bf63d Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 15 Dec 2022 00:00:07 +0100 Subject: [PATCH 46/50] Write default large blob array. Signed-off-by: Pol Henarejos --- src/fido/fido.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fido/fido.c b/src/fido/fido.c index 4eb076f..d4f5d13 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -290,6 +290,9 @@ int scan_files(bool core1) { printf("FATAL ERROR: Auth Token not found in memory!\r\n"); } ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF); + if (!file_has_data(ef_largeblob)) { + flash_write_data_to_file(ef_largeblob, (const uint8_t *)"\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17); + } low_flash_available(); return CCID_OK; } From c8775ec69f28bf6297747fca1a96061463211d95 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 15 Dec 2022 00:00:29 +0100 Subject: [PATCH 47/50] Fix computing sha256 of large blob array. Signed-off-by: Pol Henarejos --- src/fido/cbor_large_blobs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c index 3b79296..ed2ec46 100644 --- a/src/fido/cbor_large_blobs.c +++ b/src/fido/cbor_large_blobs.c @@ -130,7 +130,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) { expectedNextOffset += set.len; if (expectedNextOffset == expectedLength) { uint8_t sha[32]; - mbedtls_sha256(temp_lba, expectedLength, sha, 0); + mbedtls_sha256(temp_lba, expectedLength-16, sha, 0); if (expectedLength > 17 && memcmp(sha, temp_lba+expectedLength-16, 16) != 0) CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE); flash_write_data_to_file(ef_largeblob, temp_lba, expectedLength); From 6644b500fa3f9d26a05472f667f5af9621501451 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 15 Dec 2022 00:31:21 +0100 Subject: [PATCH 48/50] Add write and read large blob test. Signed-off-by: Pol Henarejos --- tests/pico-fido/test_blob.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/pico-fido/test_blob.py b/tests/pico-fido/test_blob.py index 9183d3f..d7faf2d 100644 --- a/tests/pico-fido/test_blob.py +++ b/tests/pico-fido/test_blob.py @@ -6,6 +6,7 @@ import os PIN='12345678' SMALL_BLOB=b"A"*32 +LARGE_BLOB=b"B"*1024 @pytest.fixture(scope="function") def MCCredBlob(device): @@ -41,7 +42,27 @@ def GALBRead(device, MCLBK): assertions = res['res'].get_assertions() for a in assertions: verify(MCLBK.attestation_object, a, res['req']['client_data'].hash) - return assertions[0] + return res['res'] + +@pytest.fixture(scope="function") +def GALBReadLBK(GALBRead): + return GALBRead.get_assertions()[0] + +@pytest.fixture(scope="function") +def GALBReadLB(GALBRead): + return GALBRead.get_response(0) + +@pytest.fixture(scope="function") +def GALBWrite(device, MCLBK): + res = device.doGA( + allow_list=[ + {"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} + ],extensions={'largeBlob':{'write': LARGE_BLOB}} + ) + assertions = res['res'].get_assertions() + for a in assertions: + verify(MCLBK.attestation_object, a, res['req']['client_data'].hash) + return res['res'].get_response(0) def test_supports_credblob(info): assert info.extensions @@ -91,11 +112,19 @@ def test_wrong_credblob(device, info): def test_supports_largeblobs(info): assert info.extensions assert 'largeBlobKey' in info.extensions + assert 'largeBlobs' in info.options + assert info.max_large_blob is None or (info.max_large_blob > 1024) def test_get_largeblobkey_mc(MCLBK): assert 'supported' in MCLBK.extension_results assert MCLBK.extension_results['supported'] is True -def test_get_largeblobkey_ga(GALBRead): - assert GALBRead.large_blob_key is not None +def test_get_largeblobkey_ga(GALBReadLBK): + assert GALBReadLBK.large_blob_key is not None +def test_get_largeblob_rw(GALBWrite, GALBReadLB): + assert 'written' in GALBWrite.extension_results + assert GALBWrite.extension_results['written'] is True + + assert 'blob' in GALBReadLB.extension_results + assert GALBReadLB.extension_results['blob'] == LARGE_BLOB From 483a0931a64eda32bdd53f71b100f78a3f36759d Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 15 Dec 2022 00:42:13 +0100 Subject: [PATCH 49/50] Update README with new enhancements and features Signed-off-by: Pol Henarejos --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9118295..2596e38 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,14 @@ Pico FIDO has implemented the following features: - Support for vendor Config - Backup with 24 words - Secure lock to protect the device from flash dumpings -- Permissions support (MC, GA, CM, ACFG) +- Permissions support (MC, GA, CM, ACFG, LBW) - Authenticator configuration - minPinLength extension - Self attestation +- Enterprise attestation +- credBlobs extension +- largeBlobKey extension +- largeBlobs support (2048 bytes máx.) All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue. @@ -34,10 +38,6 @@ If the Pico is stolen the contents of private and secret keys can be read. ## Download Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") and download the UF2 file for your board. -Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the VID/PID patcher as follows: `./pico-fido-patch-vidpid.sh VID:PID input_fido_file.uf2 output_fido_file.uf2` - -You can use whatever VID/PID, but remember that you are not authorized to distribute the binary with a VID/PID that you do not own. - ## Build Before building, ensure you have installed the toolchain for the Pico and the Pico SDK is properly located in your drive. @@ -52,6 +52,8 @@ Note that PICO_BOARD, USB_VID and USB_PID are optional. If not provided, pico bo After make ends, the binary file pico_fido.uf2 will be generated. Put your pico board into loading mode, by pushing BOOTSEL button while pluging on, and copy the UF2 to the new fresh usb mass storage Pico device. Once copied, the pico mass storage will be disconnected automatically and the pico board will reset with the new firmware. A blinking led will indicate the device is ready to work. +**Remark:** Pico Fido uses HID interface and thus, VID/PID values are irrelevant in terms of operativity. You can safely use any arbitrary value or the default ones. + ## Led blink Pico FIDO uses the led to indicate the current status. Four states are available: ### Press to confirm From fe9509348493465ce0bbb8f41d96091780bb7129 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 15 Dec 2022 00:42:38 +0100 Subject: [PATCH 50/50] Upgrading to Version 2.10. Signed-off-by: Pol Henarejos --- build_pico_fido.sh | 2 +- src/fido/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build_pico_fido.sh b/build_pico_fido.sh index 18984c1..a59020c 100755 --- a/build_pico_fido.sh +++ b/build_pico_fido.sh @@ -1,7 +1,7 @@ #!/bin/bash VERSION_MAJOR="2" -VERSION_MINOR="8" +VERSION_MINOR="10" rm -rf release/* cd build_release diff --git a/src/fido/version.h b/src/fido/version.h index f72ed36..ebd1954 100644 --- a/src/fido/version.h +++ b/src/fido/version.h @@ -18,7 +18,7 @@ #ifndef __VERSION_H_ #define __VERSION_H_ -#define PICO_FIDO_VERSION 0x0208 +#define PICO_FIDO_VERSION 0x020A #define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff) #define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)