From 0d8d72e2862fdd1aaf9672d1f05636413e0aa781 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Sun, 24 Dec 2023 16:08:47 +0800 Subject: [PATCH 01/13] refactor piv algorithm extensions --- applets/admin/admin.c | 5 --- applets/piv/piv.c | 100 +++++++++++++++++++++++++----------------- include/admin.h | 3 -- include/piv.h | 10 +++++ src/fs.c | 3 +- 5 files changed, 72 insertions(+), 49 deletions(-) diff --git a/applets/admin/admin.c b/applets/admin/admin.c index a14e8c92..1f05fd4a 100644 --- a/applets/admin/admin.c +++ b/applets/admin/admin.c @@ -54,8 +54,6 @@ uint8_t cfg_is_webusb_landing_enable(void) { return current_config.webusb_landin uint8_t cfg_is_kbd_with_return_enable(void) { return current_config.kbd_with_return_en; } -uint8_t cfg_is_piv_algo_extension_enable(void) { return current_config.piv_algo_ext_en; } - void admin_poweroff(void) { pin.is_validated = 0; } int admin_install(const uint8_t reset) { @@ -131,9 +129,6 @@ static int admin_config(const CAPDU *capdu, RAPDU *rapdu) { case ADMIN_P1_CFG_KBD_WITH_RETURN: current_config.kbd_with_return_en = P2 & 1; break; - case ADMIN_P1_CFG_PIV_ALGO_EXT: - current_config.piv_algo_ext_en = P2 & 1; - break; default: EXCEPT(SW_WRONG_P1P2); } diff --git a/applets/piv/piv.c b/applets/piv/piv.c index 82c448ed..1fca0ed9 100644 --- a/applets/piv/piv.c +++ b/applets/piv/piv.c @@ -35,17 +35,18 @@ #define CARD_ADMIN_KEY_PATH "piv-admk" // 9B // alg +#define ALGORITHM_EXT_CONFIG_PATH "piv-alg" #define ALG_DEFAULT 0x00 #define ALG_TDEA_3KEY 0x03 #define ALG_RSA_2048 0x07 #define ALG_ECC_256 0x11 #define ALG_ECC_384 0x14 -#define ALG_ED25519 0x22 // Not defined in NIST SP 800-78-4, defined in https://github.com/go-piv/piv-go/pull/69 -#define ALG_RSA_3072 0x50 // Not defined in NIST SP 800-78-4 -#define ALG_RSA_4096 0x51 // Not defined in NIST SP 800-78-4 -#define ALG_X25519 0x52 // Not defined in NIST SP 800-78-4 -#define ALG_SECP256K1 0x53 // Not defined in NIST SP 800-78-4 -#define ALG_SM2 0x54 // Not defined in NIST SP 800-78-4 +#define ALG_ED25519_DEFAULT 0x22 // defined in https://github.com/go-piv/piv-go/pull/69 +#define ALG_RSA_3072_DEFAULT 0x50 +#define ALG_RSA_4096_DEFAULT 0x51 +#define ALG_X25519_DEFAULT 0x52 +#define ALG_SECP256K1_DEFAULT 0x53 +#define ALG_SM2_DEFAULT 0x54 #define TDEA_BLOCK_SIZE 8 @@ -97,6 +98,7 @@ static char piv_do_path[MAX_DO_PATH_LEN]; // data object file path during chaini static int piv_do_write; // -1: not in chaining write, otherwise: count of remaining bytes static int piv_do_read; // -1: not in chaining read mode, otherwise: data object offset static uint32_t last_touch = UINT32_MAX; +static piv_algorithm_extension_config_t alg_ext_cfg; static pin_t pin = {.min_length = 8, .max_length = 8, .is_validated = 0, .path = "piv-pin"}; static pin_t puk = {.min_length = 8, .max_length = 8, .is_validated = 0, .path = "piv-puk"}; @@ -126,45 +128,50 @@ static key_type_t algo_id_to_key_type(const uint8_t id) { return SECP384R1; case ALG_RSA_2048: return RSA2048; - case ALG_ED25519: - return ED25519; case ALG_DEFAULT: case ALG_TDEA_3KEY: return TDEA; default: - - if (!cfg_is_piv_algo_extension_enable()) return KEY_TYPE_PKC_END; - - switch (id) { - case ALG_X25519: - return X25519; - case ALG_SECP256K1: - return SECP256K1; - case ALG_SM2: - return SM2; - case ALG_RSA_3072: - return RSA3072; - case ALG_RSA_4096: - return RSA4096; - default: - return KEY_TYPE_PKC_END; - } + break; } + + if (alg_ext_cfg.enabled == 0) return KEY_TYPE_PKC_END; + if (id == alg_ext_cfg.ed25519) return ED25519; + if (id == alg_ext_cfg.rsa3072) return RSA3072; + if (id == alg_ext_cfg.rsa4096) return RSA4096; + if (id == alg_ext_cfg.x25519) return X25519; + if (id == alg_ext_cfg.secp256k1) return SECP256K1; + if (id == alg_ext_cfg.sm2) return SM2; + return KEY_TYPE_PKC_END; } -static uint8_t key_type_to_algo_id[] = { - [SECP256R1] = ALG_ECC_256, - [SECP384R1] = ALG_ECC_384, - [RSA2048] = ALG_RSA_2048, - [ED25519] = ALG_ED25519, - [X25519] = ALG_X25519, - [SECP256K1] = ALG_SECP256K1, - [SM2] = ALG_SM2, - [RSA3072] = ALG_RSA_3072, - [RSA4096] = ALG_RSA_4096, - [TDEA] = ALG_TDEA_3KEY, - [KEY_TYPE_PKC_END] = ALG_DEFAULT, -}; +static uint8_t key_type_to_algo_id(const key_type_t type) { + switch (type) { + case SECP256R1: + return ALG_ECC_256; + case SECP384R1: + return ALG_ECC_384; + case RSA2048: + return ALG_RSA_2048; + case ED25519: + return alg_ext_cfg.ed25519; + case X25519: + return alg_ext_cfg.x25519; + case SECP256K1: + return alg_ext_cfg.secp256k1; + case SM2: + return alg_ext_cfg.sm2; + case RSA3072: + return alg_ext_cfg.rsa3072; + case RSA4096: + return alg_ext_cfg.rsa4096; + case TDEA: + return ALG_TDEA_3KEY; + case KEY_TYPE_PKC_END: + default: + return ALG_DEFAULT; + } +} int piv_security_status_check(uint8_t id __attribute__((unused)), const key_meta_t *meta) { switch (meta->pin_policy) { @@ -194,7 +201,10 @@ void piv_poweroff(void) { int piv_install(const uint8_t reset) { piv_poweroff(); - if (!reset && get_file_size(PIV_AUTH_CERT_PATH) >= 0) return 0; + if (!reset && get_file_size(ALGORITHM_EXT_CONFIG_PATH) >= 0) { + if (read_file(ALGORITHM_EXT_CONFIG_PATH, &alg_ext_cfg, 0, sizeof(alg_ext_cfg)) < 0) return -1; + return 0; + } // objects if (write_file(PIV_AUTH_CERT_PATH, NULL, 0, 0, 1) < 0) return -1; @@ -244,6 +254,16 @@ int piv_install(const uint8_t reset) { if (pin_create(&puk, DEFAULT_PUK, 8, 3) < 0) return -1; if (write_attr(puk.path, TAG_PIN_KEY_DEFAULT, &tmp, sizeof(tmp)) < 0) return -1; + // Algorithm extensions + alg_ext_cfg.enabled = 0; + alg_ext_cfg.ed25519 = ALG_ED25519_DEFAULT; + alg_ext_cfg.rsa3072 = ALG_RSA_3072_DEFAULT; + alg_ext_cfg.rsa4096 = ALG_RSA_4096_DEFAULT; + alg_ext_cfg.x25519 = ALG_X25519_DEFAULT; + alg_ext_cfg.secp256k1 = ALG_SECP256K1_DEFAULT; + alg_ext_cfg.sm2 = ALG_SM2_DEFAULT; + if (write_file(ALGORITHM_EXT_CONFIG_PATH, &alg_ext_cfg, 0, sizeof(alg_ext_cfg), 1) < 0) return -1; + return 0; } @@ -1048,7 +1068,7 @@ static int piv_get_metadata(const CAPDU *capdu, RAPDU *rapdu) { RDATA[pos++] = 0x01; // Algorithm RDATA[pos++] = 0x01; - RDATA[pos++] = key_type_to_algo_id[key.meta.type]; + RDATA[pos++] = key_type_to_algo_id(key.meta.type); RDATA[pos++] = 0x02; // Policy RDATA[pos++] = 0x02; RDATA[pos++] = key.meta.pin_policy; diff --git a/include/admin.h b/include/admin.h index d54b740c..3a0a21a4 100644 --- a/include/admin.h +++ b/include/admin.h @@ -28,7 +28,6 @@ #define ADMIN_P1_CFG_NDEF 0x04 #define ADMIN_P1_CFG_WEBUSB_LANDING 0x05 #define ADMIN_P1_CFG_KBD_WITH_RETURN 0x06 -#define ADMIN_P1_CFG_PIV_ALGO_EXT 0x07 typedef struct { uint32_t reserved; @@ -38,7 +37,6 @@ typedef struct { uint32_t ndef_en : 1; uint32_t webusb_landing_en : 1; uint32_t kbd_with_return_en : 1; - uint32_t piv_algo_ext_en : 1; } __packed admin_device_config_t; void admin_poweroff(void); @@ -54,6 +52,5 @@ uint8_t cfg_is_kbd_interface_enable(void); uint8_t cfg_is_ndef_enable(void); uint8_t cfg_is_webusb_landing_enable(void); uint8_t cfg_is_kbd_with_return_enable(void); -uint8_t cfg_is_piv_algo_extension_enable(void); #endif // CANOKEY_CORE_ADMIN_ADMIN_H_ diff --git a/include/piv.h b/include/piv.h index 6f30d2ee..427a023c 100644 --- a/include/piv.h +++ b/include/piv.h @@ -20,6 +20,16 @@ #define PIV_INS_IMPORT_ASYMMETRIC_KEY 0xFE #define PIV_INS_SET_MANAGEMENT_KEY 0xFF +typedef struct { + uint8_t enabled; + uint8_t ed25519; + uint8_t rsa3072; + uint8_t rsa4096; + uint8_t x25519; + uint8_t secp256k1; + uint8_t sm2; +} __packed piv_algorithm_extension_config_t; + int piv_install(uint8_t reset); void piv_poweroff(void); int piv_process_apdu(const CAPDU *capdu, RAPDU *rapdu); diff --git a/src/fs.c b/src/fs.c index 75c89407..1bed45bf 100644 --- a/src/fs.c +++ b/src/fs.c @@ -29,7 +29,8 @@ int read_file(const char *path, void *buf, lfs_soff_t off, lfs_size_t len) { err = lfs_file_close(&lfs, &f); if (err < 0) return err; return read_length; - err_close: + +err_close: lfs_file_close(&lfs, &f); return err; } From 32d623a5b70fdb20ecc819db95274741579e820b Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Sun, 24 Dec 2023 16:24:59 +0800 Subject: [PATCH 02/13] add INS to modify the config of PIV alg_ext --- applets/piv/piv.c | 22 +++++++++++++++++++++- include/piv.h | 2 ++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/applets/piv/piv.c b/applets/piv/piv.c index 1fca0ed9..52591e1a 100644 --- a/applets/piv/piv.c +++ b/applets/piv/piv.c @@ -1099,7 +1099,7 @@ static int piv_get_version(const CAPDU *capdu, RAPDU *rapdu) { if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2); if (LC != 0) EXCEPT(SW_WRONG_LENGTH); RDATA[0] = 0x05; - RDATA[1] = 0x03; + RDATA[1] = 0x04; RDATA[2] = 0x00; LL = 3; return 0; @@ -1113,6 +1113,23 @@ static int piv_get_serial(const CAPDU *capdu, RAPDU *rapdu) { return 0; } +static int piv_algorithm_extension(const CAPDU *capdu, RAPDU *rapdu) { + if (P1 != 0x01 && P1 != 0x02) EXCEPT(SW_WRONG_P1P2); + if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2); + + if (P1 == 0x01) { + if (read_file(ALGORITHM_EXT_CONFIG_PATH, RDATA, 0, sizeof(alg_ext_cfg)) < 0) return -1; + LL = sizeof(alg_ext_cfg); + } else { + if (LC != sizeof(alg_ext_cfg)) EXCEPT(SW_WRONG_LENGTH); + if (DATA[0] != 0 && DATA[0] != 1) EXCEPT(SW_WRONG_DATA); + // We trust the rest data because no dangerous result will be caused even if the IDs are not unique. + if (write_file(ALGORITHM_EXT_CONFIG_PATH, DATA, 0, sizeof(alg_ext_cfg), 1) < 0) return -1; + } + + return 0; +} + int piv_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { LL = 0; SW = SW_NO_ERROR; @@ -1170,6 +1187,9 @@ int piv_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { case PIV_INS_GET_METADATA: ret = piv_get_metadata(capdu, rapdu); break; + case PIV_INS_ALGORITHM_EXTENSION: + ret = piv_algorithm_extension(capdu, rapdu); + break; default: EXCEPT(SW_INS_NOT_SUPPORTED); } diff --git a/include/piv.h b/include/piv.h index 427a023c..cf1672f7 100644 --- a/include/piv.h +++ b/include/piv.h @@ -20,6 +20,8 @@ #define PIV_INS_IMPORT_ASYMMETRIC_KEY 0xFE #define PIV_INS_SET_MANAGEMENT_KEY 0xFF +#define PIV_INS_ALGORITHM_EXTENSION 0xEE + typedef struct { uint8_t enabled; uint8_t ed25519; From 49c272783b233610602e0484f4cfd61d75e47a52 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Sun, 24 Dec 2023 17:44:28 +0800 Subject: [PATCH 03/13] fix PIV test --- test-via-pcsc/piv_test.go | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/test-via-pcsc/piv_test.go b/test-via-pcsc/piv_test.go index 1a835843..4f044e4f 100644 --- a/test-via-pcsc/piv_test.go +++ b/test-via-pcsc/piv_test.go @@ -77,19 +77,7 @@ func (o *PIVApplet) Send(apdu []byte) ([]byte, uint16, error) { return res[0 : len(res)-2], uint16(res[len(res)-2])<<8 | uint16(res[len(res)-1]), nil } func (app *PIVApplet) ConfigPIVAlgoExt(enable uint8) { - verifyPin := func(pin []byte) (code uint16) { - _, code, err := app.Send(append([]byte{0x00, 0x20, 0x00, 0x00, byte(len(pin))}, pin...)) - So(err, ShouldBeNil) - return - } - - _, code, err := app.Send([]byte{0x00, 0xA4, 0x04, 0x00, 0x05, 0xF0, 0x00, 0x00, 0x00, 0x00}) - So(err, ShouldBeNil) - So(code, ShouldEqual, 0x9000) - So(verifyPin([]byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36}), ShouldEqual, 0x9000) - - apdu := []byte{0x00, 0x40, uint8(ADMIN_P1_CFG_PIV_ALGO_EXT), enable} - _, code, err = app.Send(apdu) + _, code, err := app.Send([]byte{0x00, 0xEE, 0x02, 0x00, 0x07, enable, 0x00, 0x00, 0x00, 0x00}) So(err, ShouldBeNil) So(code, ShouldEqual, 0x9000) } From 0c2a5b8f214d4ff7b4855d500e34b16305060d18 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Sun, 24 Dec 2023 18:18:09 +0800 Subject: [PATCH 04/13] fix orders in test --- applets/piv/piv.c | 4 ++++ test-via-pcsc/piv_test.go | 15 +++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/applets/piv/piv.c b/applets/piv/piv.c index 52591e1a..d374a477 100644 --- a/applets/piv/piv.c +++ b/applets/piv/piv.c @@ -1114,6 +1114,10 @@ static int piv_get_serial(const CAPDU *capdu, RAPDU *rapdu) { } static int piv_algorithm_extension(const CAPDU *capdu, RAPDU *rapdu) { +#ifndef FUZZ + if (!in_admin_status) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED); +#endif + if (P1 != 0x01 && P1 != 0x02) EXCEPT(SW_WRONG_P1P2); if (P2 != 0x00) EXCEPT(SW_WRONG_P1P2); diff --git a/test-via-pcsc/piv_test.go b/test-via-pcsc/piv_test.go index 4f044e4f..129de7b7 100644 --- a/test-via-pcsc/piv_test.go +++ b/test-via-pcsc/piv_test.go @@ -77,7 +77,7 @@ func (o *PIVApplet) Send(apdu []byte) ([]byte, uint16, error) { return res[0 : len(res)-2], uint16(res[len(res)-2])<<8 | uint16(res[len(res)-1]), nil } func (app *PIVApplet) ConfigPIVAlgoExt(enable uint8) { - _, code, err := app.Send([]byte{0x00, 0xEE, 0x02, 0x00, 0x07, enable, 0x00, 0x00, 0x00, 0x00}) + _, code, err := app.Send([]byte{0x00, 0xEE, 0x02, 0x00, 0x07, enable, 0x22, 0x50, 0x51, 0x52, 0x53, 0x54}) So(err, ShouldBeNil) So(code, ShouldEqual, 0x9000) } @@ -117,15 +117,15 @@ func TestPIVExtensions(t *testing.T) { So(err, ShouldBeNil) defer app.Close() - Convey("Enable algorithm extension", func(ctx C) { - app.ConfigPIVAlgoExt(1) - }) - Convey("Select the Applet and Authenticate", func(ctx C) { app.Select() app.Authenticate() }) + Convey("Enable algorithm extension", func(ctx C) { + app.ConfigPIVAlgoExt(1) + }) + Convey("Generate the key", func(ctx C) { for keyID := 0x50; keyID <= 0x54; keyID++ { _, code, err := app.Send([]byte{0x00, 0x47, 0x00, 0x9E, 0x05, 0xAC, 0x03, 0x80, 0x01, byte(keyID)}) @@ -140,11 +140,6 @@ func TestPIVExtensions(t *testing.T) { app.ConfigPIVAlgoExt(0) }) - Convey("Select the Applet and Authenticate again", func(ctx C) { - app.Select() - app.Authenticate() - }) - Convey("Generate the key again", func(ctx C) { for keyID := 0x50; keyID <= 0x54; keyID++ { _, code, err := app.Send([]byte{0x00, 0x47, 0x00, 0x9E, 0x05, 0xAC, 0x03, 0x80, 0x01, byte(keyID)}) From a784504383366d81d5fe7bee7aa57f76a35adeba Mon Sep 17 00:00:00 2001 From: Yuxiang Zhang Date: Mon, 25 Dec 2023 17:35:58 +0800 Subject: [PATCH 05/13] algorithm config update effective immediately --- applets/piv/piv.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applets/piv/piv.c b/applets/piv/piv.c index d374a477..6ec638de 100644 --- a/applets/piv/piv.c +++ b/applets/piv/piv.c @@ -1129,6 +1129,8 @@ static int piv_algorithm_extension(const CAPDU *capdu, RAPDU *rapdu) { if (DATA[0] != 0 && DATA[0] != 1) EXCEPT(SW_WRONG_DATA); // We trust the rest data because no dangerous result will be caused even if the IDs are not unique. if (write_file(ALGORITHM_EXT_CONFIG_PATH, DATA, 0, sizeof(alg_ext_cfg), 1) < 0) return -1; + // Effective immediately + memcpy(&alg_ext_cfg, DATA, sizeof(alg_ext_cfg)); } return 0; From 0ddb93bcf101fd5b4b54d0c3c6cd3ac39ae174a1 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Mon, 25 Dec 2023 22:18:32 +0800 Subject: [PATCH 06/13] add SM2 settings for ctap --- applets/ctap/cose-key.h | 19 ++-- applets/ctap/ctap-internal.h | 7 ++ applets/ctap/ctap-parser.c | 2 +- applets/ctap/ctap.c | 182 ++++++++++++++++++++--------------- applets/ctap/secret.c | 47 +++++---- canokey-crypto | 2 +- 6 files changed, 148 insertions(+), 111 deletions(-) diff --git a/applets/ctap/cose-key.h b/applets/ctap/cose-key.h index 2d316f3e..d9ecdd7a 100644 --- a/applets/ctap/cose-key.h +++ b/applets/ctap/cose-key.h @@ -4,23 +4,18 @@ #define COSE_KEY_LABEL_KTY 1 #define COSE_KEY_LABEL_ALG 3 -#define COSE_KEY_LABEL_CRV (-1) -#define COSE_KEY_LABEL_X (-2) -#define COSE_KEY_LABEL_Y (-3) +#define COSE_KEY_LABEL_CRV -1 +#define COSE_KEY_LABEL_X -2 +#define COSE_KEY_LABEL_Y -3 +#define COSE_KEY_KTY_OKP 1 #define COSE_KEY_KTY_EC2 2 #define COSE_KEY_CRV_P256 1 #define COSE_KEY_CRV_ED25519 6 -#define COSE_KEY_CRV_SM2 9 -#define COSE_ALG_ES256 (-7) -#define COSE_ALG_EDDSA (-8) -#define COSE_ALG_ECDH_ES_HKDF_256 (-25) -#define COSE_ALG_SM2 (-48) - -#define COSE_KEY_ES256_SIZE 77 -#define COSE_KEY_ECDH_ES_HKDF_257_SIZE 78 -#define COSE_KEY_EDDSA_SIZE 42 +#define COSE_ALG_ES256 -7 +#define COSE_ALG_EDDSA -8 +#define COSE_ALG_ECDH_ES_HKDF_256 -25 #endif // CANOKEY_CORE_FIDO2_COSE_KEY_H_ diff --git a/applets/ctap/ctap-internal.h b/applets/ctap/ctap-internal.h index 8d23dc48..ec133b62 100644 --- a/applets/ctap/ctap-internal.h +++ b/applets/ctap/ctap-internal.h @@ -19,6 +19,7 @@ #define PIN_CTR_ATTR 0x03 #define KH_KEY_ATTR 0x04 #define HE_KEY_ATTR 0x05 +#define SM2_ATTR 0x06 #define DC_FILE "ctap_dc" #define DC_GENERAL_ATTR 0x00 #define DC_META_FILE "ctap_dm" @@ -364,6 +365,12 @@ typedef struct { uint8_t pin_uv_auth_param[SHA256_DIGEST_LENGTH]; } CTAP_large_blobs; +typedef struct { + uint8_t enabled; + int32_t curve_id; + int32_t algo_id; +} CTAP_sm2_attr; + int u2f_register(const CAPDU *capdu, RAPDU *rapdu); int u2f_authenticate(const CAPDU *capdu, RAPDU *rapdu); int u2f_version(const CAPDU *capdu, RAPDU *rapdu); diff --git a/applets/ctap/ctap-parser.c b/applets/ctap/ctap-parser.c index 4a9fcab0..37d20f7f 100644 --- a/applets/ctap/ctap-parser.c +++ b/applets/ctap/ctap-parser.c @@ -314,7 +314,7 @@ uint8_t parse_cose_key(CborValue *val, uint8_t *public_key) { if (cbor_value_get_type(&map) != CborIntegerType) return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; ret = cbor_value_get_int_checked(&map, &key); CHECK_CBOR_RET(ret); - if (key != COSE_ALG_ES256 && key != COSE_ALG_ECDH_ES_HKDF_256) return CTAP2_ERR_UNHANDLED_REQUEST; + if (key != COSE_ALG_ECDH_ES_HKDF_256) return CTAP2_ERR_UNHANDLED_REQUEST; ++parsed_keys; break; diff --git a/applets/ctap/ctap.c b/applets/ctap/ctap.c index 87fb998e..5dec4196 100644 --- a/applets/ctap/ctap.c +++ b/applets/ctap/ctap.c @@ -5,7 +5,6 @@ #include "ctap-parser.h" #include "secret.h" #include "u2f.h" -#include #include #include #include @@ -59,12 +58,15 @@ static const uint8_t aaguid[] = {0x24, 0x4e, 0xb2, 0x9e, 0xe0, 0x90, 0x4e, 0x49, // pin & command states static uint8_t consecutive_pin_counter, last_cmd; +// SM2 attr +CTAP_sm2_attr ctap_sm2_attr; uint8_t ctap_install(uint8_t reset) { consecutive_pin_counter = 3; last_cmd = CTAP_INVALID_CMD; cp_initialize(); - if (!reset && get_file_size(CTAP_CERT_FILE) >= 0) { + if (!reset && get_file_size(LB_FILE) >= 0) { + if (read_attr(CTAP_CERT_FILE, SM2_ATTR, &ctap_sm2_attr, sizeof(ctap_sm2_attr)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; DBG_MSG("CTAP initialized\n"); return 0; } @@ -79,6 +81,10 @@ uint8_t ctap_install(uint8_t reset) { if (write_attr(CTAP_CERT_FILE, KH_KEY_ATTR, kh_key, sizeof(kh_key)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; random_buffer(kh_key, sizeof(kh_key)); if (write_attr(CTAP_CERT_FILE, HE_KEY_ATTR, kh_key, sizeof(kh_key)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; + ctap_sm2_attr.enabled = 0; + ctap_sm2_attr.curve_id = 9; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves + ctap_sm2_attr.algo_id = -48; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms + if (write_attr(CTAP_CERT_FILE, SM2_ATTR, &ctap_sm2_attr, sizeof(ctap_sm2_attr)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; memcpy(kh_key, (uint8_t[]) {0x80, 0x76, 0xbe, 0x8b, 0x52, 0x8d, 0x00, 0x75, 0xf7, 0xaa, 0xe9, 0x8d, 0x6f, 0xa5, 0x7a, 0x6d, 0x3c}, 17); @@ -98,72 +104,70 @@ int ctap_install_cert(const CAPDU *capdu, RAPDU *rapdu) { return write_file(CTAP_CERT_FILE, DATA, 0, LC, 1); } -static void build_cose_key(uint8_t *data, uint8_t ecdh) { - // format public key as - // A5 - // 01 02 - // 03 26 (ecdsa) or 03 38 18 (ecdh) - // 20 01 - // 21 58 20 x - // 22 58 20 y - if (ecdh) { - memmove(data + 46, data + 32, 32); - memmove(data + 11, data, 32); - } else { - memmove(data + 45, data + 32, 32); - memmove(data + 10, data, 32); - } - data[0] = 0xA5; - data[1] = 0x01; - data[2] = 0x02; - data[3] = 0x03; - if (ecdh) { - data[4] = 0x38; - data[5] = 0x18; - data[6] = 0x20; - data[7] = 0x01; - data[8] = 0x21; - data[9] = 0x58; - data[10] = 0x20; - data[43] = 0x22; - data[44] = 0x58; - data[45] = 0x20; - } else { - data[4] = 0x26; - data[5] = 0x20; - data[6] = 0x01; - data[7] = 0x21; - data[8] = 0x58; - data[9] = 0x20; - data[42] = 0x22; - data[43] = 0x58; - data[44] = 0x20; - } +static int build_ecdsa_cose_key(uint8_t *data, int algo, int curve) { + uint8_t buf[80]; + CborEncoder encoder, map_encoder; + + cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + CborError ret = cbor_encoder_create_map(&encoder, &map_encoder, 5); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_KTY); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_KTY_EC2); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_ALG); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, algo); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_CRV); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, curve); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_X); + CHECK_CBOR_RET(ret); + ret = cbor_encode_byte_string(&map_encoder, data, 32); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_Y); + CHECK_CBOR_RET(ret); + ret = cbor_encode_byte_string(&map_encoder, data + 32, 32); + CHECK_CBOR_RET(ret); + ret = cbor_encoder_close_container(&encoder, &map_encoder); + CHECK_CBOR_RET(ret); + + const int len = cbor_encoder_get_buffer_size(&encoder, buf); + memcpy(data, buf, len); + return len; } -static void build_ed25519_cose_key(uint8_t *data) { - // A4 # map(4) - // 01 # unsigned(1) kty = - // 01 # unsigned(2) OKP (1) - // 03 # unsigned(3) alg = - // 27 # negative(7) EdDSA (-8) - // 20 # negative(0) crv = - // 06 # unsigned(6) Ed25519 (6) - // 21 # negative(1) x = - // 58 20 # bytes(32) [bstr] - // (32 bytes x) - - memmove(data + 10, data, 32); - data[0] = 0xa4; - data[1] = 0x01; - data[2] = 0x01; - data[3] = 0x03; - data[4] = 0x27; - data[5] = 0x20; - data[6] = 0x06; - data[7] = 0x21; - data[8] = 0x58; - data[9] = 0x20; +static int build_ed25519_cose_key(uint8_t *data) { + uint8_t buf[50]; + CborEncoder encoder, map_encoder; + + cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + CborError ret = cbor_encoder_create_map(&encoder, &map_encoder, 4); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_KTY); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_KTY_OKP); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_ALG); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_ALG_EDDSA); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_CRV); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_CRV_ED25519); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&map_encoder, COSE_KEY_LABEL_X); + CHECK_CBOR_RET(ret); + ret = cbor_encode_byte_string(&map_encoder, data, 32); + CHECK_CBOR_RET(ret); + ret = cbor_encoder_close_container(&encoder, &map_encoder); + CHECK_CBOR_RET(ret); + + const int len = cbor_encoder_get_buffer_size(&encoder, buf); + memcpy(data, buf, len); + return len; } int ctap_consistency_check(void) { @@ -259,16 +263,18 @@ uint8_t ctap_make_auth_data(uint8_t *rp_id_hash, uint8_t *buf, uint8_t flags, co DBG_MSG("Fail to generate a key handle\n"); return CTAP2_ERR_UNHANDLED_REQUEST; } + int cose_key_size; if (alg_type == COSE_ALG_ES256) { - build_cose_key(ad->at.public_key, 0); - outLen += sizeof(ad->at) - sizeof(ad->at.public_key) + COSE_KEY_ES256_SIZE; + cose_key_size = build_ecdsa_cose_key(ad->at.public_key, COSE_ALG_ES256, COSE_KEY_CRV_P256); } else if (alg_type == COSE_ALG_EDDSA) { - build_ed25519_cose_key(ad->at.public_key); - outLen += sizeof(ad->at) - sizeof(ad->at.public_key) + COSE_KEY_EDDSA_SIZE; + cose_key_size = build_ed25519_cose_key(ad->at.public_key); + } else if (alg_type == ctap_sm2_attr.algo_id) { + cose_key_size = build_ecdsa_cose_key(ad->at.public_key, ctap_sm2_attr.algo_id, ctap_sm2_attr.curve_id); } else { DBG_MSG("Unknown algorithm type\n"); return CTAP2_ERR_UNHANDLED_REQUEST; } + outLen += sizeof(ad->at) - sizeof(ad->at.public_key) + cose_key_size; } if (flags & FLAGS_ED) { if (*len < outLen + extension_size) { @@ -1326,14 +1332,14 @@ static uint8_t ctap_get_info(CborEncoder *encoder) { // algorithms ret = cbor_encode_int(&map, GI_RESP_ALGORITHMS); CHECK_CBOR_RET(ret); - ret = cbor_encoder_create_array(&map, &array, 2); + ret = cbor_encoder_create_array(&map, &array, ctap_sm2_attr.enabled ? 3 : 2); CHECK_CBOR_RET(ret); ret = cbor_encoder_create_map(&array, &sub_map, 2); CHECK_CBOR_RET(ret); { ret = cbor_encode_text_stringz(&sub_map, "alg"); CHECK_CBOR_RET(ret); - ret = cbor_encode_int(&sub_map, -7); // ES256 (P-256) + ret = cbor_encode_int(&sub_map, COSE_ALG_ES256); CHECK_CBOR_RET(ret); ret = cbor_encode_text_stringz(&sub_map, "type"); CHECK_CBOR_RET(ret); @@ -1347,7 +1353,7 @@ static uint8_t ctap_get_info(CborEncoder *encoder) { { ret = cbor_encode_text_stringz(&sub_map, "alg"); CHECK_CBOR_RET(ret); - ret = cbor_encode_int(&sub_map, -8); // EdDSA + ret = cbor_encode_int(&sub_map, COSE_ALG_EDDSA); CHECK_CBOR_RET(ret); ret = cbor_encode_text_stringz(&sub_map, "type"); CHECK_CBOR_RET(ret); @@ -1356,6 +1362,22 @@ static uint8_t ctap_get_info(CborEncoder *encoder) { } ret = cbor_encoder_close_container(&array, &sub_map); CHECK_CBOR_RET(ret); + if (ctap_sm2_attr.enabled) { + ret = cbor_encoder_create_map(&array, &sub_map, 2); + CHECK_CBOR_RET(ret); + { + ret = cbor_encode_text_stringz(&sub_map, "alg"); + CHECK_CBOR_RET(ret); + ret = cbor_encode_int(&sub_map, ctap_sm2_attr.algo_id); + CHECK_CBOR_RET(ret); + ret = cbor_encode_text_stringz(&sub_map, "type"); + CHECK_CBOR_RET(ret); + ret = cbor_encode_text_stringz(&sub_map, "public-key"); + CHECK_CBOR_RET(ret); + } + ret = cbor_encoder_close_container(&array, &sub_map); + CHECK_CBOR_RET(ret); + } ret = cbor_encoder_close_container(&map, &array); CHECK_CBOR_RET(ret); @@ -1392,7 +1414,7 @@ static uint8_t ctap_client_pin(CborEncoder *encoder, const uint8_t *params, size uint8_t iv[16], buf[PIN_ENC_SIZE_P2 + PIN_HASH_SIZE_P2], i; memzero(iv, sizeof(iv)); uint8_t *ptr; - int err, retries; + int err, retries, cose_key_size; switch (cp.sub_command) { case CP_CMD_GET_PIN_RETRIES: DBG_MSG("Subcommand Get Pin Retries\n"); @@ -1417,8 +1439,8 @@ static uint8_t ctap_client_pin(CborEncoder *encoder, const uint8_t *params, size CHECK_CBOR_RET(ret); ptr = key_map.data.ptr - 1; cp_get_public_key(ptr); - build_cose_key(ptr, 1); - key_map.data.ptr = ptr + MAX_COSE_KEY_SIZE; + cose_key_size = build_ecdsa_cose_key(ptr, COSE_ALG_ECDH_ES_HKDF_256, COSE_KEY_CRV_P256); + key_map.data.ptr = ptr + cose_key_size; ret = cbor_encoder_close_container(&map, &key_map); CHECK_CBOR_RET(ret); ret = cbor_encoder_close_container(encoder, &map); @@ -1809,11 +1831,11 @@ static uint8_t ctap_credential_management(CborEncoder *encoder, const uint8_t *p uint8_t *ptr = sub_map.data.ptr - 1; memcpy(ptr, key.pub, PUBLIC_KEY_LENGTH[key_type]); if (dc.credential_id.alg_type == COSE_ALG_ES256) { - build_cose_key(ptr, 0); - sub_map.data.ptr = ptr + COSE_KEY_ES256_SIZE; + int cose_key_size = build_ecdsa_cose_key(ptr, COSE_ALG_ES256, COSE_KEY_CRV_P256); + sub_map.data.ptr = ptr + cose_key_size; } else if (dc.credential_id.alg_type == COSE_ALG_EDDSA) { - build_ed25519_cose_key(ptr); - sub_map.data.ptr = ptr + COSE_KEY_EDDSA_SIZE; + int cose_key_size = build_ed25519_cose_key(ptr); + sub_map.data.ptr = ptr + cose_key_size; } ret = cbor_encoder_close_container(&map, &sub_map); CHECK_CBOR_RET(ret); diff --git a/applets/ctap/secret.c b/applets/ctap/secret.c index 2cbb7b53..2978e6e5 100644 --- a/applets/ctap/secret.c +++ b/applets/ctap/secret.c @@ -10,6 +10,9 @@ #include #include #include +#include + +extern CTAP_sm2_attr ctap_sm2_attr; static uint8_t pin_token[PIN_TOKEN_SIZE]; static ecc_key_t ka_key; @@ -212,9 +215,8 @@ key_type_t cose_alg_to_key_type(int alg) { return SECP256R1; case COSE_ALG_EDDSA: return ED25519; - case COSE_ALG_SM2: - return SM2; default: + if (ctap_sm2_attr.enabled && alg == ctap_sm2_attr.algo_id) return SM2; return KEY_TYPE_PKC_END; } } @@ -282,18 +284,17 @@ int generate_key_handle(credential_id *kh, uint8_t *pubkey, int32_t alg_type, ui ecc_key_t key; uint8_t kh_key[KH_KEY_SIZE]; - if (alg_type != COSE_ALG_ES256 && alg_type != COSE_ALG_EDDSA && alg_type != COSE_ALG_SM2) { + kh->alg_type = alg_type; + const key_type_t key_type = cose_alg_to_key_type(alg_type); + if (key_type == KEY_TYPE_PKC_END) { DBG_MSG("Unsupported algo key_type\n"); return -1; } - kh->alg_type = alg_type; - key_type_t key_type = cose_alg_to_key_type(alg_type); - kh->nonce[CREDENTIAL_NONCE_DC_POS] = dc; kh->nonce[CREDENTIAL_NONCE_CP_POS] = cp; - int ret = read_kh_key(kh_key); + const int ret = read_kh_key(kh_key); if (ret < 0) return ret; do { generate_credential_id_nonce_tag(kh, kh_key, &key); @@ -340,9 +341,13 @@ size_t sign_with_device_key(const uint8_t *input, size_t input_len, uint8_t *sig } int sign_with_private_key(int32_t alg_type, ecc_key_t *key, const uint8_t *input, size_t len, uint8_t *sig) { - key_type_t key_type = cose_alg_to_key_type(alg_type); + const key_type_t key_type = cose_alg_to_key_type(alg_type); DBG_MSG("Sign key type: %d, private key: ", key_type); PRINT_HEX(key->pri, PRIVATE_KEY_LENGTH[key_type]); + if (key_type == KEY_TYPE_PKC_END) { + DBG_MSG("Unsupported algo key_type\n"); + return -1; + } if (key_type == ED25519) { if (ecc_complete_key(key_type, key) < 0) { @@ -354,20 +359,28 @@ int sign_with_private_key(int32_t alg_type, ecc_key_t *key, const uint8_t *input return -1; } return SIGNATURE_LENGTH[key_type]; + } + if (key_type == SM2) { + uint8_t z[SM3_DIGEST_LENGTH]; + sm2_z(SM2_ID_DEFAULT, key, z); + sm3_init(); + sm3_update(z, SM3_DIGEST_LENGTH); + sm3_update(input, len); + sm3_final(sig); } else { sha256_init(); sha256_update(input, len); sha256_final(sig); - DBG_MSG("Digest: "); - PRINT_HEX(sig, PRIVATE_KEY_LENGTH[key_type]); - if (ecc_sign(key_type, key, sig, PRIVATE_KEY_LENGTH[key_type], sig) < 0) { - ERR_MSG("Failed to sign\n"); - return -1; - } - DBG_MSG("Raw signature: "); - PRINT_HEX(sig, SIGNATURE_LENGTH[key_type]); - return (int) ecdsa_sig2ansi(PRIVATE_KEY_LENGTH[key_type], sig, sig); } + DBG_MSG("Digest: "); + PRINT_HEX(sig, PRIVATE_KEY_LENGTH[key_type]); + if (ecc_sign(key_type, key, sig, PRIVATE_KEY_LENGTH[key_type], sig) < 0) { + ERR_MSG("Failed to sign\n"); + return -1; + } + DBG_MSG("Raw signature: "); + PRINT_HEX(sig, SIGNATURE_LENGTH[key_type]); + return ecdsa_sig2ansi(PRIVATE_KEY_LENGTH[key_type], sig, sig); } int get_cert(uint8_t *buf) { return read_file(CTAP_CERT_FILE, buf, 0, MAX_CERT_SIZE); } diff --git a/canokey-crypto b/canokey-crypto index 7007cbab..c65b9015 160000 --- a/canokey-crypto +++ b/canokey-crypto @@ -1 +1 @@ -Subproject commit 7007cbab720c9b29a62df193b10a47a0ced05d30 +Subproject commit c65b9015d44115182ac5106f58a228efb569c93f From e9efc306e8d92ec417c71eb7ccfadf5cbba42757 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Tue, 26 Dec 2023 10:54:15 +0800 Subject: [PATCH 07/13] enable PIV extension by default --- applets/piv/piv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applets/piv/piv.c b/applets/piv/piv.c index 6ec638de..03a36dee 100644 --- a/applets/piv/piv.c +++ b/applets/piv/piv.c @@ -255,7 +255,7 @@ int piv_install(const uint8_t reset) { if (write_attr(puk.path, TAG_PIN_KEY_DEFAULT, &tmp, sizeof(tmp)) < 0) return -1; // Algorithm extensions - alg_ext_cfg.enabled = 0; + alg_ext_cfg.enabled = 1; alg_ext_cfg.ed25519 = ALG_ED25519_DEFAULT; alg_ext_cfg.rsa3072 = ALG_RSA_3072_DEFAULT; alg_ext_cfg.rsa4096 = ALG_RSA_4096_DEFAULT; From 0be54177dcdfa83b239c9db7ed5ddfc90ade8945 Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Tue, 26 Dec 2023 13:22:36 +0800 Subject: [PATCH 08/13] change default algo id for RSA3072 to 0x05 according to NIST SP 800-78-5 --- applets/piv/piv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applets/piv/piv.c b/applets/piv/piv.c index 03a36dee..92908071 100644 --- a/applets/piv/piv.c +++ b/applets/piv/piv.c @@ -42,7 +42,7 @@ #define ALG_ECC_256 0x11 #define ALG_ECC_384 0x14 #define ALG_ED25519_DEFAULT 0x22 // defined in https://github.com/go-piv/piv-go/pull/69 -#define ALG_RSA_3072_DEFAULT 0x50 +#define ALG_RSA_3072_DEFAULT 0x05 // defined in NIST SP 800-78-5 (Initial Public Draft) #define ALG_RSA_4096_DEFAULT 0x51 #define ALG_X25519_DEFAULT 0x52 #define ALG_SECP256K1_DEFAULT 0x53 From c81f322f95eec1f143aa215b7aa736ed6a13e62f Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Tue, 26 Dec 2023 23:01:33 +0800 Subject: [PATCH 09/13] fix SM2 signature issue --- applets/ctap/ctap-parser.c | 7 ++++++- applets/ctap/secret.c | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/applets/ctap/ctap-parser.c b/applets/ctap/ctap-parser.c index 37d20f7f..4e71a541 100644 --- a/applets/ctap/ctap-parser.c +++ b/applets/ctap/ctap-parser.c @@ -17,6 +17,8 @@ if (ret != CborNoError) return CTAP2_ERR_INVALID_CBOR; \ } while (0) +extern CTAP_sm2_attr ctap_sm2_attr; + static void maybe_truncate_rpid(uint8_t stored_rpid[MAX_STORED_RPID_LENGTH], size_t *stored_len, const uint8_t *rpid, size_t rpid_len) { if (rpid_len <= MAX_STORED_RPID_LENGTH) { @@ -183,7 +185,9 @@ uint8_t parse_verify_pub_key_cred_params(CborValue *val, int32_t *alg_type) { for (size_t i = 0; i < arr_length; ++i) { ret = parse_pub_key_cred_param(&arr, &cur_alg_type); CHECK_PARSER_RET(ret); - if (ret == 0 && (cur_alg_type == COSE_ALG_ES256 || cur_alg_type == COSE_ALG_EDDSA)) { + if (ret == 0 && (cur_alg_type == COSE_ALG_ES256 || + cur_alg_type == COSE_ALG_EDDSA || + ctap_sm2_attr.enabled && cur_alg_type == ctap_sm2_attr.algo_id)) { // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorMakeCredential // // > This sequence is ordered from most preferred (by the RP) to least preferred. @@ -674,6 +678,7 @@ uint8_t parse_make_credential(CborParser *parser, CTAP_make_credential *mc, cons CHECK_PARSER_RET(ret); if (mc->alg_type == COSE_ALG_ES256) DBG_MSG("EcDSA found\n"); else if (mc->alg_type == COSE_ALG_EDDSA) DBG_MSG("EdDSA found\n"); + else if (mc->alg_type == ctap_sm2_attr.algo_id) DBG_MSG("SM2 found\n"); else DBG_MSG("Found other algorithm\n"); mc->parsed_params |= PARAM_PUB_KEY_CRED_PARAMS; diff --git a/applets/ctap/secret.c b/applets/ctap/secret.c index 2978e6e5..a37c3a17 100644 --- a/applets/ctap/secret.c +++ b/applets/ctap/secret.c @@ -361,6 +361,10 @@ int sign_with_private_key(int32_t alg_type, ecc_key_t *key, const uint8_t *input return SIGNATURE_LENGTH[key_type]; } if (key_type == SM2) { + if (ecc_complete_key(key_type, key) < 0) { // Compute Z requiring the public key + ERR_MSG("Failed to complete key\n"); + return -1; + } uint8_t z[SM3_DIGEST_LENGTH]; sm2_z(SM2_ID_DEFAULT, key, z); sm3_init(); @@ -378,6 +382,10 @@ int sign_with_private_key(int32_t alg_type, ecc_key_t *key, const uint8_t *input ERR_MSG("Failed to sign\n"); return -1; } + + if (key_type == SM2) return SIGNATURE_LENGTH[key_type]; + + // For ES256, convert the signature to ansi format DBG_MSG("Raw signature: "); PRINT_HEX(sig, SIGNATURE_LENGTH[key_type]); return ecdsa_sig2ansi(PRIVATE_KEY_LENGTH[key_type], sig, sig); From 276d9784864e357dd8c6a051b794f986a766b80b Mon Sep 17 00:00:00 2001 From: Fan DANG Date: Tue, 26 Dec 2023 23:16:10 +0800 Subject: [PATCH 10/13] add configs for CTAP SM2 support --- applets/admin/admin.c | 9 +++++++++ applets/ctap/ctap-parser.c | 2 +- applets/ctap/ctap.c | 15 +++++++++++++++ include/admin.h | 3 +++ include/ctap.h | 2 ++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/applets/admin/admin.c b/applets/admin/admin.c index 1f05fd4a..d2e443d5 100644 --- a/applets/admin/admin.c +++ b/applets/admin/admin.c @@ -261,6 +261,15 @@ int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) { case ADMIN_INS_TOGGLE_NDEF_READ_ONLY: ret = ndef_toggle_read_only(capdu, rapdu); break; + case ADMIN_INS_RESET_CTAP: + ret = ctap_install(1); + break; + case ADMIN_INS_READ_CTAP_SM2_CONFIG: + ret = ctap_read_sm2_config(capdu, rapdu); + break; + case ADMIN_INS_WRITE_CTAP_SM2_CONFIG: + ret = ctap_write_sm2_config(capdu, rapdu); + break; case ADMIN_INS_CHANGE_PIN: ret = admin_change_pin(capdu, rapdu); break; diff --git a/applets/ctap/ctap-parser.c b/applets/ctap/ctap-parser.c index 4e71a541..16c0aa7e 100644 --- a/applets/ctap/ctap-parser.c +++ b/applets/ctap/ctap-parser.c @@ -187,7 +187,7 @@ uint8_t parse_verify_pub_key_cred_params(CborValue *val, int32_t *alg_type) { CHECK_PARSER_RET(ret); if (ret == 0 && (cur_alg_type == COSE_ALG_ES256 || cur_alg_type == COSE_ALG_EDDSA || - ctap_sm2_attr.enabled && cur_alg_type == ctap_sm2_attr.algo_id)) { + (ctap_sm2_attr.enabled && cur_alg_type == ctap_sm2_attr.algo_id))) { // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorMakeCredential // // > This sequence is ordered from most preferred (by the RP) to least preferred. diff --git a/applets/ctap/ctap.c b/applets/ctap/ctap.c index 5dec4196..ab8215aa 100644 --- a/applets/ctap/ctap.c +++ b/applets/ctap/ctap.c @@ -104,6 +104,21 @@ int ctap_install_cert(const CAPDU *capdu, RAPDU *rapdu) { return write_file(CTAP_CERT_FILE, DATA, 0, LC, 1); } +int ctap_read_sm2_config(const CAPDU *capdu, RAPDU *rapdu) { + UNUSED(capdu); + const int ret = read_attr(CTAP_CERT_FILE, SM2_ATTR, RDATA, sizeof(ctap_sm2_attr)); + if (ret < 0) return ret; + LL = ret; + return 0; +} + +int ctap_write_sm2_config(const CAPDU *capdu, RAPDU *rapdu) { + if (LC != sizeof(ctap_sm2_attr)) EXCEPT(SW_WRONG_LENGTH); + const int ret = write_attr(CTAP_CERT_FILE, SM2_ATTR, DATA, sizeof(ctap_sm2_attr)); + memcpy(&ctap_sm2_attr, DATA, sizeof(ctap_sm2_attr)); + return ret; +} + static int build_ecdsa_cose_key(uint8_t *data, int algo, int curve) { uint8_t buf[80]; CborEncoder encoder, map_encoder; diff --git a/include/admin.h b/include/admin.h index 3a0a21a4..3a872a21 100644 --- a/include/admin.h +++ b/include/admin.h @@ -11,6 +11,9 @@ #define ADMIN_INS_RESET_OATH 0x05 #define ADMIN_INS_RESET_NDEF 0x07 #define ADMIN_INS_TOGGLE_NDEF_READ_ONLY 0x08 +#define ADMIN_INS_RESET_CTAP 0x09 +#define ADMIN_INS_READ_CTAP_SM2_CONFIG 0x11 +#define ADMIN_INS_WRITE_CTAP_SM2_CONFIG 0x12 #define ADMIN_INS_VERIFY 0x20 #define ADMIN_INS_CHANGE_PIN 0x21 #define ADMIN_INS_WRITE_SN 0x30 diff --git a/include/ctap.h b/include/ctap.h index fe94f09d..375a8aab 100644 --- a/include/ctap.h +++ b/include/ctap.h @@ -8,6 +8,8 @@ uint8_t ctap_install(uint8_t reset); int ctap_install_private_key(const CAPDU *capdu, RAPDU *rapdu); int ctap_install_cert(const CAPDU *capdu, RAPDU *rapdu); +int ctap_read_sm2_config(const CAPDU *capdu, RAPDU *rapdu); +int ctap_write_sm2_config(const CAPDU *capdu, RAPDU *rapdu); int ctap_process_cbor(uint8_t *req, size_t req_len, uint8_t *resp, size_t *resp_len); int ctap_process_apdu(const CAPDU *capdu, RAPDU *rapdu); int ctap_wink(void); From 5c633f3c3eef62a3d5e7ea7f661edeaf387de794 Mon Sep 17 00:00:00 2001 From: z4yx Date: Wed, 27 Dec 2023 20:06:20 +0800 Subject: [PATCH 11/13] patch fido2 for SM2 --- test-via-pcsc/build_fido_tests.sh | 1 + test-via-pcsc/fido2_SM2_COSE_key.patch | 47 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 test-via-pcsc/fido2_SM2_COSE_key.patch diff --git a/test-via-pcsc/build_fido_tests.sh b/test-via-pcsc/build_fido_tests.sh index fdc4552e..670e5904 100755 --- a/test-via-pcsc/build_fido_tests.sh +++ b/test-via-pcsc/build_fido_tests.sh @@ -26,6 +26,7 @@ patch -p1 -u -d ~/.local/lib/python3.*/site-packages/fido2 < Date: Mon, 1 Jan 2024 20:16:04 +0800 Subject: [PATCH 12/13] make attr packed --- applets/ctap/ctap-internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applets/ctap/ctap-internal.h b/applets/ctap/ctap-internal.h index ec133b62..f6cc1362 100644 --- a/applets/ctap/ctap-internal.h +++ b/applets/ctap/ctap-internal.h @@ -369,7 +369,7 @@ typedef struct { uint8_t enabled; int32_t curve_id; int32_t algo_id; -} CTAP_sm2_attr; +} __packed CTAP_sm2_attr; int u2f_register(const CAPDU *capdu, RAPDU *rapdu); int u2f_authenticate(const CAPDU *capdu, RAPDU *rapdu); From 0d11de44958586a032644b96bc49a0f17194bf8a Mon Sep 17 00:00:00 2001 From: z4yx Date: Tue, 2 Jan 2024 17:33:51 +0800 Subject: [PATCH 13/13] keep SM2 config during FIDO2 reset --- applets/ctap/ctap.c | 9 +++++---- virt-card/fabrication.c | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/applets/ctap/ctap.c b/applets/ctap/ctap.c index ab8215aa..11d9ea6b 100644 --- a/applets/ctap/ctap.c +++ b/applets/ctap/ctap.c @@ -81,10 +81,6 @@ uint8_t ctap_install(uint8_t reset) { if (write_attr(CTAP_CERT_FILE, KH_KEY_ATTR, kh_key, sizeof(kh_key)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; random_buffer(kh_key, sizeof(kh_key)); if (write_attr(CTAP_CERT_FILE, HE_KEY_ATTR, kh_key, sizeof(kh_key)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; - ctap_sm2_attr.enabled = 0; - ctap_sm2_attr.curve_id = 9; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves - ctap_sm2_attr.algo_id = -48; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms - if (write_attr(CTAP_CERT_FILE, SM2_ATTR, &ctap_sm2_attr, sizeof(ctap_sm2_attr)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; memcpy(kh_key, (uint8_t[]) {0x80, 0x76, 0xbe, 0x8b, 0x52, 0x8d, 0x00, 0x75, 0xf7, 0xaa, 0xe9, 0x8d, 0x6f, 0xa5, 0x7a, 0x6d, 0x3c}, 17); @@ -96,6 +92,11 @@ uint8_t ctap_install(uint8_t reset) { int ctap_install_private_key(const CAPDU *capdu, RAPDU *rapdu) { if (LC != PRI_KEY_SIZE) EXCEPT(SW_WRONG_LENGTH); + // initialize SM2 config + ctap_sm2_attr.enabled = 0; + ctap_sm2_attr.curve_id = 9; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves + ctap_sm2_attr.algo_id = -48; // An unused one. See https://www.iana.org/assignments/cose/cose.xhtml#algorithms + if (write_attr(CTAP_CERT_FILE, SM2_ATTR, &ctap_sm2_attr, sizeof(ctap_sm2_attr)) < 0) return CTAP2_ERR_UNHANDLED_REQUEST; return write_attr(CTAP_CERT_FILE, KEY_ATTR, DATA, LC); } diff --git a/virt-card/fabrication.c b/virt-card/fabrication.c index 9c690167..6f9c1e09 100644 --- a/virt-card/fabrication.c +++ b/virt-card/fabrication.c @@ -5,6 +5,7 @@ #include "oath.h" #include "openpgp.h" #include "piv.h" +#include <../applets/ctap/ctap-internal.h> #include #include #include @@ -62,6 +63,7 @@ static void fake_fido_personalization() { uint8_t c_buf[1024], r_buf[1024]; CAPDU capdu; RAPDU rapdu; + CTAP_sm2_attr sm2_attr; capdu.data = c_buf; rapdu.data = r_buf; @@ -82,6 +84,20 @@ static void fake_fido_personalization() { capdu.lc = sizeof(cert); admin_process_apdu(&capdu, &rapdu); assert(rapdu.sw == 0x9000); + + capdu.ins = ADMIN_INS_READ_CTAP_SM2_CONFIG; + capdu.lc = 0; + admin_process_apdu(&capdu, &rapdu); + assert(rapdu.sw == 0x9000); + + memcpy(&sm2_attr, r_buf, sizeof(sm2_attr)); + sm2_attr.enabled = 1; + + capdu.ins = ADMIN_INS_WRITE_CTAP_SM2_CONFIG; + capdu.data = &sm2_attr; + capdu.lc = sizeof(sm2_attr); + admin_process_apdu(&capdu, &rapdu); + assert(rapdu.sw == 0x9000); } static void fido2_init() { fake_fido_personalization(); }