diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ba8d07f..94a11aa 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ "main", "development" ] + branches: [ "main", "development", "eddsa" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "main", "development" ] + branches: [ "main", "development", "eddsa" ] schedule: - cron: '23 5 * * 4' workflow_dispatch: @@ -36,7 +36,7 @@ jobs: language: [ 'cpp', 'python' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - mode: [ 'pico', 'esp32', 'local' ] + mode: [ 'pico', 'local' ] steps: - name: Checkout repository diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0a55586..cf65236 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,10 +13,10 @@ name: "Emulation and test" on: push: - branches: [ "main", "development" ] + branches: [ "main", "development", "eddsa" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "main", "development" ] + branches: [ "main", "development", "eddsa" ] schedule: - cron: '23 5 * * 4' workflow_dispatch: diff --git a/README.md b/README.md index 602182c..565da99 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ Pico FIDO includes the following features: - User verification with PIN - Discoverable credentials (resident keys) - Credential management -- ECDSA authentication -- Support for SECP256R1, SECP384R1, SECP521R1, and SECP256K1 curves +- ECDSA and EDDSA authentication +- Support for SECP256R1, SECP384R1, SECP521R1, SECP256K1 and Ed25519 curves - App registration and login - Device selection - Support for vendor configuration diff --git a/build_pico_fido.sh b/build_pico_fido.sh index a18a1dc..79873b3 100755 --- a/build_pico_fido.sh +++ b/build_pico_fido.sh @@ -1,7 +1,7 @@ #!/bin/bash VERSION_MAJOR="6" -VERSION_MINOR="4" +VERSION_MINOR="4-eddsa1" SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}" #if ! [[ -z "${GITHUB_SHA}" ]]; then # SUFFIX="${SUFFIX}.${GITHUB_SHA}" diff --git a/pico-keys-sdk b/pico-keys-sdk index 07415e6..6ec374a 160000 --- a/pico-keys-sdk +++ b/pico-keys-sdk @@ -1 +1 @@ -Subproject commit 07415e6e8be36255fbcd3e5c3aeec394c7b76bac +Subproject commit 6ec374a6ac53a4de34ed26ae19be126fe7c704e7 diff --git a/src/fido/cbor.c b/src/fido/cbor.c index 68842b7..50f7f0f 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -210,6 +210,9 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent, else if (key->grp.id == MBEDTLS_ECP_DP_CURVE25519) { alg = FIDO2_ALG_ECDH_ES_HKDF_256; } + else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) { + alg = FIDO2_ALG_EDDSA; + } return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder); } CborError COSE_key_shared(mbedtls_ecdh_context *key, diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index d4bb3b4..5838bc9 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -565,15 +565,27 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) { md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); } - ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash); - ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL); + else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) { + md = NULL; + } + + if (md != NULL) { + ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash); + ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL); + } + else { + ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_gen, NULL); + } } else { // Bogus signature olen = 64; memset(sig, 0x0B, olen); } - mbedtls_ecdsa_free(&ekey); + mbedtls_ecp_keypair_free(&ekey); + if (ret != 0) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } uint8_t lfields = 3; if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) { diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index ecdafe1..7594851 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -90,11 +90,14 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CRED_ID_LENGTH)); // MAX_CRED_ID_MAX_LENGTH CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A)); - CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4)); + + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 5)); CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256, &arrayEncoder, &mapEncoder2)); + CBOR_CHECK(COSE_public_key(FIDO2_ALG_EDDSA, &arrayEncoder, &mapEncoder2)); CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES384, &arrayEncoder, &mapEncoder2)); CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES512, &arrayEncoder, &mapEncoder2)); CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256K, &arrayEncoder, &mapEncoder2)); + CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B)); diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c index 3a5fc83..6fa52b6 100644 --- a/src/fido/cbor_make_credential.c +++ b/src/fido/cbor_make_credential.c @@ -221,6 +221,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) { if (curve <= 0) { curve = FIDO2_CURVE_P256K1; } + } + else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) { + if (curve <= 0) { + curve = FIDO2_CURVE_ED25519; + } } else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) { // pass @@ -385,16 +390,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) { ext_len = cbor_encoder_get_buffer_size(&encoder, ext); flags |= FIDO2_AUT_FLAG_ED; } - mbedtls_ecdsa_context ekey; - mbedtls_ecdsa_init(&ekey); + mbedtls_ecp_keypair ekey; + mbedtls_ecp_keypair_init(&ekey); int ret = fido_load_key(curve, cred_id, &ekey); if (ret != 0) { - mbedtls_ecdsa_free(&ekey); + mbedtls_ecp_keypair_free(&ekey); CBOR_ERROR(CTAP1_ERR_OTHER); } const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id); if (cinfo == NULL) { - mbedtls_ecdsa_free(&ekey); + mbedtls_ecp_keypair_free(&ekey); CBOR_ERROR(CTAP1_ERR_OTHER); } size_t olen = 0; @@ -416,7 +421,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) { memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs; memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len; if ((size_t)(pa - aut_data) != aut_data_len) { - mbedtls_ecdsa_free(&ekey); + mbedtls_ecp_keypair_free(&ekey); CBOR_ERROR(CTAP1_ERR_OTHER); } @@ -429,12 +434,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) { else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) { md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); } - ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash); + else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) { + md = NULL; + } + if (md != NULL) { + ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash); + } bool self_attestation = true; if (enterpriseAttestation == 2 || (ka && ka->use_self_attestation == pfalse)) { - mbedtls_ecdsa_free(&ekey); - mbedtls_ecdsa_init(&ekey); + mbedtls_ecp_keypair_free(&ekey); + mbedtls_ecp_keypair_init(&ekey); uint8_t key[32] = {0}; if (load_keydev(key) != 0) { CBOR_ERROR(CTAP1_ERR_OTHER); @@ -444,8 +454,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) { md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); self_attestation = false; } - ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL); - mbedtls_ecdsa_free(&ekey); + if (md != NULL) { + ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL); + } + else { + ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_gen, NULL); + } + mbedtls_ecp_keypair_free(&ekey); + if (ret != 0) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } if (user.id.len > 0 && user.parent.name.len > 0 && user.displayName.len > 0) { if (memcmp(user.parent.name.data, "+pico", 5) == 0) { diff --git a/src/fido/fido.c b/src/fido/fido.c index b7d39ef..9e11385 100644 --- a/src/fido/fido.c +++ b/src/fido/fido.c @@ -112,6 +112,12 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) { else if (curve == FIDO2_CURVE_X448) { return MBEDTLS_ECP_DP_CURVE448; } + else if (curve == FIDO2_CURVE_ED25519) { + return MBEDTLS_ECP_DP_ED25519; + } + else if (curve == FIDO2_CURVE_ED448) { + return MBEDTLS_ECP_DP_ED448; + } return MBEDTLS_ECP_DP_NONE; } int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) { @@ -133,10 +139,16 @@ int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) { else if (id == MBEDTLS_ECP_DP_CURVE448) { return FIDO2_CURVE_X448; } + else if (id == MBEDTLS_ECP_DP_ED25519) { + return FIDO2_CURVE_ED25519; + } + else if (id == MBEDTLS_ECP_DP_ED448) { + return FIDO2_CURVE_ED448; + } return 0; } -int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key) { +int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) { mbedtls_ecp_group_id mbedtls_curve = fido_curve_to_mbedtls(curve); if (mbedtls_curve == MBEDTLS_ECP_DP_NONE) { return CTAP2_ERR_UNSUPPORTED_ALGORITHM; @@ -202,7 +214,7 @@ int load_keydev(uint8_t *key) { return PICOKEY_OK; } -int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *key) { +int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) { for (int i = 0; i < KEY_PATH_ENTRIES; i++) { uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)]; if (!(k & 0x80000000)) { @@ -235,7 +247,7 @@ int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_con return memcmp(keyHandle + KEY_PATH_LEN, hmac, sizeof(hmac)); } -int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int curve, mbedtls_ecdsa_context *key) { +int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int curve, mbedtls_ecp_keypair *key) { uint8_t outk[67] = { 0 }; //SECP521R1 key is 66 bytes length int r = 0; memset(outk, 0, sizeof(outk)); @@ -280,6 +292,9 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur if (r != 0) { return r; } + if (curve == MBEDTLS_ECP_DP_ED25519) { + return mbedtls_ecp_point_edwards(&key->grp, &key->Q, &key->d, random_gen, NULL); + } return mbedtls_ecp_mul(&key->grp, &key->Q, &key->d, &key->grp.G, random_gen, NULL); } mbedtls_platform_zeroize(outk, sizeof(outk)); diff --git a/src/fido/fido.h b/src/fido/fido.h index 4666fda..f29b953 100644 --- a/src/fido/fido.h +++ b/src/fido/fido.h @@ -28,6 +28,7 @@ #endif #include "mbedtls/ecdsa.h" +#include "mbedtls/eddsa.h" #ifndef ENABLE_EMULATION #include "hid/ctap_hid.h" #else @@ -45,13 +46,13 @@ extern int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int, - mbedtls_ecdsa_context *key); -extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *); + mbedtls_ecp_keypair *key); +extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *); extern bool wait_button_pressed(); extern void init_fido(); extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve); extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id); -extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key); +extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key); extern int load_keydev(uint8_t *key); extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out); diff --git a/tests/pico-fido/test_020_register.py b/tests/pico-fido/test_020_register.py index 78377d8..28c37e6 100644 --- a/tests/pico-fido/test_020_register.py +++ b/tests/pico-fido/test_020_register.py @@ -19,7 +19,7 @@ from fido2.client import CtapError -from fido2.cose import ES256, ES384, ES512 +from fido2.cose import ES256, ES384, ES512, EdDSA import fido2.features fido2.features.webauthn_json_mapping.enabled = False from utils import ES256K @@ -124,7 +124,7 @@ def test_bad_type_pubKeyCredParams(device): device.doMC(key_params=["wrong"]) @pytest.mark.parametrize( - "alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM] + "alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM] ) def test_algorithms(device, info, alg): if ({'alg': alg, 'type': 'public-key'} in info.algorithms): diff --git a/tests/pico-fido/test_021_authenticate.py b/tests/pico-fido/test_021_authenticate.py index 2944779..14260b7 100644 --- a/tests/pico-fido/test_021_authenticate.py +++ b/tests/pico-fido/test_021_authenticate.py @@ -19,7 +19,7 @@ from fido2.client import CtapError -from fido2.cose import ES256, ES384, ES512 +from fido2.cose import ES256, ES384, ES512, EdDSA from utils import verify, ES256K import pytest @@ -49,7 +49,7 @@ def test_empty_allowList(device): assert e.value.code == CtapError.ERR.NO_CREDENTIALS @pytest.mark.parametrize( - "alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM] + "alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM] ) def test_algorithms(device, info, alg): if ({'alg': alg, 'type': 'public-key'} in info.algorithms):