diff options
author | Daiki Ueno <ueno@gnu.org> | 2021-09-23 07:50:38 +0000 |
---|---|---|
committer | Daiki Ueno <ueno@gnu.org> | 2021-09-23 07:50:38 +0000 |
commit | 7a220bf3899f3a32d6ff667d3861d15312ccd35b (patch) | |
tree | 131eb151d1185653a4c34ad345872a980420e4c0 | |
parent | 970000abfe3f88a8659da084852db258310246fd (diff) | |
parent | a00a79ddf41eb14d56bdea076b5c252029896431 (diff) | |
download | gnutls-7a220bf3899f3a32d6ff667d3861d15312ccd35b.tar.gz |
Merge branch 'x25519-and-x448' into 'master'
certtool: generate, parse, and manipulate X25519 and X448 pubkeys, privkeys, and certificates
See merge request gnutls/gnutls!1428
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | lib/algorithms.h | 16 | ||||
-rw-r--r-- | lib/algorithms/ecc.c | 2 | ||||
-rw-r--r-- | lib/algorithms/publickey.c | 4 | ||||
-rw-r--r-- | lib/nettle/pk.c | 67 | ||||
-rw-r--r-- | lib/pk.c | 4 | ||||
-rw-r--r-- | lib/privkey.c | 2 | ||||
-rw-r--r-- | lib/pubkey.c | 143 | ||||
-rw-r--r-- | lib/x509/common.c | 3 | ||||
-rw-r--r-- | lib/x509/common.h | 3 | ||||
-rw-r--r-- | lib/x509/key_decode.c | 22 | ||||
-rw-r--r-- | lib/x509/key_encode.c | 39 | ||||
-rw-r--r-- | lib/x509/output.c | 2 | ||||
-rw-r--r-- | lib/x509/privkey_pkcs8.c | 56 | ||||
-rw-r--r-- | src/certtool-args.def | 2 | ||||
-rw-r--r-- | src/certtool-common.c | 8 | ||||
-rw-r--r-- | src/certtool-common.h | 1 | ||||
-rw-r--r-- | src/certtool.c | 4 | ||||
-rw-r--r-- | tests/cert-tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/cert-tests/data/cert-eddsa.pem | 10 | ||||
-rwxr-xr-x | tests/cert-tests/x25519-and-x448.sh | 101 |
21 files changed, 485 insertions, 9 deletions
@@ -7,6 +7,9 @@ See the end for copying conditions. * Version 3.7.3 (unreleased) +** certtool: Certtool can now generate, manipulate, and evaluate x25519 and + x448 public keys, private keys, and certificates. + ** API and ABI modifications: GNUTLS_PRIVKEY_FLAG_RSA_PSS_FIXED_SALT_LENGTH: new flag in gnutls_privkey_flags_t GNUTLS_VERIFY_RSA_PSS_FIXED_SALT_LENGTH: new flag in gnutls_certificate_verify_flags diff --git a/lib/algorithms.h b/lib/algorithms.h index 7a051b6365..5172bd2784 100644 --- a/lib/algorithms.h +++ b/lib/algorithms.h @@ -475,12 +475,28 @@ inline static int _curve_is_eddsa(const gnutls_ecc_curve_entry_st * e) return 0; } +inline static int _curve_is_modern_ecdh(const gnutls_ecc_curve_entry_st * e) +{ + if (unlikely(e == NULL)) + return 0; + if (e->pk == GNUTLS_PK_ECDH_X25519 || + e->pk == GNUTLS_PK_ECDH_X448) + return 1; + return 0; +} + inline static int curve_is_eddsa(gnutls_ecc_curve_t id) { const gnutls_ecc_curve_entry_st *e = _gnutls_ecc_curve_get_params(id); return _curve_is_eddsa(e); } +inline static int curve_is_modern_ecdh(gnutls_ecc_curve_t id) +{ + const gnutls_ecc_curve_entry_st *e = _gnutls_ecc_curve_get_params(id); + return _curve_is_modern_ecdh(e); +} + static inline int _gnutls_kx_is_ecc(gnutls_kx_algorithm_t kx) { if (kx == GNUTLS_KX_ECDHE_RSA || kx == GNUTLS_KX_ECDHE_ECDSA || diff --git a/lib/algorithms/ecc.c b/lib/algorithms/ecc.c index 917f83a624..adab4e1c18 100644 --- a/lib/algorithms/ecc.c +++ b/lib/algorithms/ecc.c @@ -81,6 +81,7 @@ gnutls_ecc_curve_entry_st ecc_curves[] = { }, { .name = "X25519", + .oid = ECDH_X25519_OID, .id = GNUTLS_ECC_CURVE_X25519, .group = GNUTLS_GROUP_X25519, .pk = GNUTLS_PK_ECDH_X25519, @@ -98,6 +99,7 @@ gnutls_ecc_curve_entry_st ecc_curves[] = { }, { .name = "X448", + .oid = ECDH_X448_OID, .id = GNUTLS_ECC_CURVE_X448, .pk = GNUTLS_PK_ECDH_X448, .size = 56, diff --git a/lib/algorithms/publickey.c b/lib/algorithms/publickey.c index c298a38936..b4cd6b1df0 100644 --- a/lib/algorithms/publickey.c +++ b/lib/algorithms/publickey.c @@ -146,9 +146,9 @@ static const gnutls_pk_entry pk_algorithms[] = { .curve = GNUTLS_ECC_CURVE_ED448, .no_prehashed = 1 }, { .name = "DH", .oid = NULL, .id = GNUTLS_PK_DH, .curve = GNUTLS_ECC_CURVE_INVALID }, - { .name = "ECDH (X25519)", .oid = "1.3.101.110", .id = GNUTLS_PK_ECDH_X25519, + { .name = "ECDH (X25519)", .oid = ECDH_X25519_OID, .id = GNUTLS_PK_ECDH_X25519, .curve = GNUTLS_ECC_CURVE_X25519 }, - { .name = "ECDH (X448)", .oid = "1.3.101.111", .id = GNUTLS_PK_ECDH_X448, + { .name = "ECDH (X448)", .oid = ECDH_X448_OID, .id = GNUTLS_PK_ECDH_X448, .curve = GNUTLS_ECC_CURVE_X448 }, { .name = "UNKNOWN", .oid = NULL, .id = GNUTLS_PK_UNKNOWN, .curve = GNUTLS_ECC_CURVE_INVALID }, diff --git a/lib/nettle/pk.c b/lib/nettle/pk.c index 16d9b4a04c..6af19c459f 100644 --- a/lib/nettle/pk.c +++ b/lib/nettle/pk.c @@ -831,6 +831,19 @@ get_eddsa_curve(gnutls_pk_algorithm_t algo) } } +static inline gnutls_ecc_curve_t +get_ecdh_curve(gnutls_pk_algorithm_t algo) +{ + switch (algo) { + case GNUTLS_PK_ECDH_X25519: + return GNUTLS_ECC_CURVE_X25519; + case GNUTLS_PK_ECDH_X448: + return GNUTLS_ECC_CURVE_X448; + default: + return gnutls_assert_val(GNUTLS_ECC_CURVE_INVALID); + } +} + static inline int eddsa_sign(gnutls_pk_algorithm_t algo, const uint8_t *pub, @@ -1789,6 +1802,8 @@ wrap_nettle_pk_generate_params(gnutls_pk_algorithm_t algo, case GNUTLS_PK_ECDSA: case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: #if ENABLE_GOST case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: @@ -3098,6 +3113,34 @@ wrap_nettle_pk_verify_priv_params(gnutls_pk_algorithm_t algo, ret = 0; break; } + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: { + gnutls_ecc_curve_t curve; + const gnutls_ecc_curve_entry_st *e; + uint8_t pub[57]; /* can accommodate both curves */ + + curve = get_ecdh_curve(algo); + e = _gnutls_ecc_curve_get_params(curve); + if (e == NULL) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (params->raw_pub.data == NULL) { + return 0; /* nothing to verify */ + } + + if (params->raw_pub.size != e->size) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + ret = edwards_curve_mul_g(algo, pub, params->raw_priv.data); + if (ret < 0) + return ret; + + if (memcmp(params->raw_pub.data, pub, e->size) != 0) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + ret = 0; + break; + } #if ENABLE_GOST case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: @@ -3464,6 +3507,30 @@ wrap_nettle_pk_fixup(gnutls_pk_algorithm_t algo, } params->raw_pub.size = params->raw_priv.size; + } else if (algo == GNUTLS_PK_ECDH_X25519 || + algo == GNUTLS_PK_ECDH_X448) { + if (unlikely(get_ecdh_curve(algo) != params->curve)) + return gnutls_assert_val(GNUTLS_E_ECC_UNSUPPORTED_CURVE); + + if (params->raw_priv.data == NULL) + return gnutls_assert_val(GNUTLS_E_PK_INVALID_PRIVKEY); + + if (params->raw_pub.data == NULL) { + params->raw_pub.data = gnutls_malloc(params->raw_priv.size); + } + + if (params->raw_pub.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + ret = edwards_curve_mul_g(algo, + params->raw_pub.data, + params->raw_priv.data); + if (ret < 0) { + gnutls_free(params->raw_pub.data); + return ret; + } + + params->raw_pub.size = params->raw_priv.size; } else if (algo == GNUTLS_PK_RSA_PSS) { if (params->params_nr < RSA_PRIVATE_PARAMS - 3) return gnutls_assert_val(GNUTLS_E_PK_INVALID_PRIVKEY); @@ -1031,7 +1031,7 @@ int _gnutls_params_get_ecc_raw(const gnutls_pk_params_st* params, e = _gnutls_ecc_curve_get_params(params->curve); - if (_curve_is_eddsa(e)) { + if (_curve_is_eddsa(e) || _curve_is_modern_ecdh(e)) { if (x) { ret = _gnutls_set_datum(x, params->raw_pub.data, params->raw_pub.size); if (ret < 0) { @@ -1216,6 +1216,8 @@ pk_prepare_hash(gnutls_pk_algorithm_t pk, case GNUTLS_PK_ECDSA: case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: case GNUTLS_PK_GOST_12_512: diff --git a/lib/privkey.c b/lib/privkey.c index 0e8d29561f..7b983b145d 100644 --- a/lib/privkey.c +++ b/lib/privkey.c @@ -206,6 +206,8 @@ privkey_to_pubkey(gnutls_pk_algorithm_t pk, break; case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: ret = _gnutls_set_datum(&pub->raw_pub, priv->raw_pub.data, priv->raw_pub.size); if (ret < 0) return gnutls_assert_val(ret); diff --git a/lib/pubkey.c b/lib/pubkey.c index a1735cf766..14171f68d7 100644 --- a/lib/pubkey.c +++ b/lib/pubkey.c @@ -62,6 +62,8 @@ unsigned pubkey_to_bits(const gnutls_pk_params_st * params) case GNUTLS_PK_ECDSA: case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: case GNUTLS_PK_GOST_12_512: @@ -497,6 +499,139 @@ gnutls_pubkey_import_ecc_eddsa(gnutls_pubkey_t key, return ret; } +/* Same as above, but for Edwards key agreement */ +static int +gnutls_pubkey_parse_ecc_ecdh_params(const gnutls_datum_t *parameters, + gnutls_ecc_curve_t *outcurve) +{ + gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID; + ASN1_TYPE asn1 = ASN1_TYPE_EMPTY; + unsigned int etype = ASN1_ETYPE_INVALID; + char str[MAX_OID_SIZE]; + int str_size; + int ret; + + ret = asn1_create_element(_gnutls_get_gnutls_asn(), + "GNUTLS.pkcs-11-ec-Parameters", &asn1); + if (ret != ASN1_SUCCESS) { + gnutls_assert(); + return _gnutls_asn2err(ret); + } + + ret = asn1_der_decoding(&asn1, parameters->data, parameters->size, + NULL); + if (ret != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(ret); + goto cleanup; + } + + /* Read the type of choice. + */ + str_size = sizeof(str) - 1; + ret = asn1_read_value(asn1, "", str, &str_size); + if (ret != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(ret); + goto cleanup; + } + str[str_size] = 0; + + /* Convert the choice to enum type */ + if (strcmp(str, "oId") == 0) { + etype = ASN1_ETYPE_OBJECT_ID; + } else if (strcmp(str, "curveName") == 0) { + etype = ASN1_ETYPE_PRINTABLE_STRING; + } + + str_size = sizeof(str) - 1; + switch (etype) { + case ASN1_ETYPE_OBJECT_ID: + ret = asn1_read_value(asn1, "oId", str, &str_size); + if (ret != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(ret); + break; + } + + curve = gnutls_oid_to_ecc_curve(str); + if (curve != GNUTLS_ECC_CURVE_X25519 && + curve != GNUTLS_ECC_CURVE_X448) { + _gnutls_debug_log("Curve %s is not supported for Edwards-based key agreement\n", str); + gnutls_assert(); + curve = GNUTLS_ECC_CURVE_INVALID; + ret = GNUTLS_E_ECC_UNSUPPORTED_CURVE; + break; + } + + ret = GNUTLS_E_SUCCESS; + break; + + case ASN1_ETYPE_PRINTABLE_STRING: + ret = asn1_read_value(asn1, "curveName", str, &str_size); + if (ret != ASN1_SUCCESS) { + gnutls_assert(); + ret = _gnutls_asn2err(ret); + break; + } + + if (str_size == strlen("x25519") && + strncmp(str, "x25519", str_size) == 0) { + curve = GNUTLS_ECC_CURVE_X25519; + ret = GNUTLS_E_SUCCESS; + break; + } else if (str_size == strlen("x448") && + strncmp(str, "x448", str_size) == 0) { + curve = GNUTLS_ECC_CURVE_X448; + ret = GNUTLS_E_SUCCESS; + break; + } + /* FALLTHROUGH */ + + default: + /* Neither of CHOICEs found. Fail */ + gnutls_assert(); + ret = GNUTLS_E_ECC_UNSUPPORTED_CURVE; + curve = GNUTLS_ECC_CURVE_INVALID; + break; + } + + + cleanup: + asn1_delete_structure(&asn1); + *outcurve = curve; + return ret; +} + +static int +gnutls_pubkey_import_ecc_ecdh(gnutls_pubkey_t key, + const gnutls_datum_t * parameters, + const gnutls_datum_t * ecpoint) +{ + int ret; + + gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID; + gnutls_datum_t raw_point = {NULL, 0}; + + ret = gnutls_pubkey_parse_ecc_ecdh_params(parameters, &curve); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + ret = _gnutls_x509_decode_string(ASN1_ETYPE_OCTET_STRING, + ecpoint->data, ecpoint->size, + &raw_point, 0); + if (ret < 0) { + gnutls_assert(); + gnutls_free(raw_point.data); + return ret; + } + ret = gnutls_pubkey_import_ecc_raw(key, curve, &raw_point, NULL); + + gnutls_free(raw_point.data); + return ret; +} + /** * gnutls_pubkey_import_pkcs11: * @key: The public key @@ -577,6 +712,10 @@ gnutls_pubkey_import_pkcs11(gnutls_pubkey_t key, ret = gnutls_pubkey_import_ecc_eddsa(key, &obj->pubkey[0], &obj->pubkey[1]); break; + case GNUTLS_PK_ECDH_X25519: + ret = gnutls_pubkey_import_ecc_ecdh(key, &obj->pubkey[0], + &obj->pubkey[1]); + break; default: gnutls_assert(); return GNUTLS_E_UNIMPLEMENTED_FEATURE; @@ -1038,7 +1177,9 @@ gnutls_pubkey_export_ecc_raw2(gnutls_pubkey_t key, *curve = key->params.curve; if (key->params.algo == GNUTLS_PK_EDDSA_ED25519 || - key->params.algo == GNUTLS_PK_EDDSA_ED448) { + key->params.algo == GNUTLS_PK_EDDSA_ED448 || + key->params.algo == GNUTLS_PK_ECDH_X25519 || + key->params.algo == GNUTLS_PK_ECDH_X448) { if (x) { ret = _gnutls_set_datum(x, key->params.raw_pub.data, key->params.raw_pub.size); if (ret < 0) diff --git a/lib/x509/common.c b/lib/x509/common.c index c156bd96a9..94d206ff75 100644 --- a/lib/x509/common.c +++ b/lib/x509/common.c @@ -630,7 +630,8 @@ _gnutls_x509_decode_string(unsigned int etype, if (td.data == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); - memcpy(td.data, str, str_size); + if (str_size > 0) + memcpy(td.data, str, str_size); td.data[str_size] = 0; if (allow_ber) diff --git a/lib/x509/common.h b/lib/x509/common.h index 4690d68825..c1df15980f 100644 --- a/lib/x509/common.h +++ b/lib/x509/common.h @@ -97,6 +97,9 @@ #define SIG_RSA_SHA3_384_OID "2.16.840.1.101.3.4.3.15" #define SIG_RSA_SHA3_512_OID "2.16.840.1.101.3.4.3.16" +#define ECDH_X25519_OID "1.3.101.110" +#define ECDH_X448_OID "1.3.101.111" + #define SIG_EDDSA_SHA512_OID "1.3.101.112" #define SIG_ED448_OID "1.3.101.113" diff --git a/lib/x509/key_decode.c b/lib/x509/key_decode.c index c7e69d8e1f..44e4297db0 100644 --- a/lib/x509/key_decode.c +++ b/lib/x509/key_decode.c @@ -41,6 +41,9 @@ static int _gnutls_x509_read_ecc_pubkey(uint8_t * der, int dersize, static int _gnutls_x509_read_eddsa_pubkey(gnutls_ecc_curve_t curve, uint8_t * der, int dersize, gnutls_pk_params_st * params); +static int _gnutls_x509_read_ecdh_pubkey(gnutls_ecc_curve_t curve, + uint8_t * der, int dersize, + gnutls_pk_params_st * params); static int _gnutls_x509_read_gost_pubkey(uint8_t * der, int dersize, gnutls_pk_params_st * params); @@ -125,6 +128,17 @@ int _gnutls_x509_read_eddsa_pubkey(gnutls_ecc_curve_t curve, return _gnutls_set_datum(¶ms->raw_pub, der, dersize); } +int _gnutls_x509_read_ecdh_pubkey(gnutls_ecc_curve_t curve, + uint8_t * der, int dersize, + gnutls_pk_params_st * params) +{ + int size = gnutls_ecc_curve_get_size(curve); + if (dersize != size) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + return _gnutls_set_datum(¶ms->raw_pub, der, dersize); +} + /* Pubkey is a concatenation of X (in little endian) and Y (also LE) * encoded into OCTET STRING. */ static int @@ -564,6 +578,12 @@ int _gnutls_x509_read_pubkey(gnutls_pk_algorithm_t algo, uint8_t * der, case GNUTLS_PK_EDDSA_ED448: ret = _gnutls_x509_read_eddsa_pubkey(GNUTLS_ECC_CURVE_ED448, der, dersize, params); break; + case GNUTLS_PK_ECDH_X25519: + ret = _gnutls_x509_read_ecdh_pubkey(GNUTLS_ECC_CURVE_X25519, der, dersize, params); + break; + case GNUTLS_PK_ECDH_X448: + ret = _gnutls_x509_read_ecdh_pubkey(GNUTLS_ECC_CURVE_X448, der, dersize, params); + break; case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: case GNUTLS_PK_GOST_12_512: @@ -635,6 +655,8 @@ int _gnutls_x509_check_pubkey_params(gnutls_pk_params_st * params) case GNUTLS_PK_ECDSA: case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: case GNUTLS_PK_GOST_12_512: diff --git a/lib/x509/key_encode.c b/lib/x509/key_encode.c index c3ff2a9b05..8428cd1733 100644 --- a/lib/x509/key_encode.c +++ b/lib/x509/key_encode.c @@ -161,6 +161,35 @@ _gnutls_x509_write_eddsa_pubkey(const gnutls_pk_params_st * params, return 0; } +/* + * some x509 certificate functions that relate to MPI parameter + * setting. This writes a raw public key. + * + * Allocates the space used to store the data. + */ +static int +_gnutls_x509_write_modern_ecdh_pubkey(const gnutls_pk_params_st * params, + gnutls_datum_t * raw) +{ + int ret; + + raw->data = NULL; + raw->size = 0; + + if (params->raw_pub.size == 0) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (params->curve != GNUTLS_ECC_CURVE_X25519 && + params->curve != GNUTLS_ECC_CURVE_X448) + return gnutls_assert_val(GNUTLS_E_ECC_UNSUPPORTED_CURVE); + + ret = _gnutls_set_datum(raw, params->raw_pub.data, params->raw_pub.size); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + int _gnutls_x509_write_gost_pubkey(const gnutls_pk_params_st * params, gnutls_datum_t * der) @@ -254,6 +283,8 @@ _gnutls_x509_write_pubkey_params(const gnutls_pk_params_st * params, return _gnutls_x509_write_ecc_params(params->curve, der); case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: der->data = NULL; der->size = 0; @@ -282,6 +313,9 @@ _gnutls_x509_write_pubkey(const gnutls_pk_params_st * params, case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: return _gnutls_x509_write_eddsa_pubkey(params, der); + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: + return _gnutls_x509_write_modern_ecdh_pubkey(params, der); case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: case GNUTLS_PK_GOST_12_512: @@ -835,7 +869,8 @@ _gnutls_asn1_encode_ecc(asn1_node * c2, gnutls_pk_params_st * params) goto cleanup; } - if (curve_is_eddsa(params->curve)) { + if (curve_is_eddsa(params->curve) || + curve_is_modern_ecdh(params->curve)) { if (params->raw_pub.size == 0 || params->raw_priv.size == 0) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); ret = @@ -1039,6 +1074,8 @@ int _gnutls_asn1_encode_privkey(asn1_node * c2, case GNUTLS_PK_ECDSA: case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: return _gnutls_asn1_encode_ecc(c2, params); case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: diff --git a/lib/x509/output.c b/lib/x509/output.c index 70210847b8..1e58c3ca92 100644 --- a/lib/x509/output.c +++ b/lib/x509/output.c @@ -1453,6 +1453,8 @@ print_pubkey(gnutls_buffer_st * str, const char *key_name, case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: case GNUTLS_PK_ECDSA: { gnutls_datum_t x, y; diff --git a/lib/x509/privkey_pkcs8.c b/lib/x509/privkey_pkcs8.c index c54ad4a8b2..4aa8993307 100644 --- a/lib/x509/privkey_pkcs8.c +++ b/lib/x509/privkey_pkcs8.c @@ -69,6 +69,8 @@ _encode_privkey(gnutls_x509_privkey_t pkey, gnutls_datum_t * raw) switch (pkey->params.algo) { case GNUTLS_PK_EDDSA_ED25519: case GNUTLS_PK_EDDSA_ED448: + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: /* we encode as octet string (which is going to be stored inside * another octet string). No comments. */ ret = _gnutls_x509_encode_string(ASN1_ETYPE_OCTET_STRING, @@ -1136,6 +1138,56 @@ _decode_pkcs8_eddsa_key(asn1_node pkcs8_asn, gnutls_x509_privkey_t pkey, const c } } +static int +_decode_pkcs8_modern_ecdh_key(ASN1_TYPE pkcs8_asn, gnutls_x509_privkey_t pkey, const char *oid) +{ + int ret; + gnutls_datum_t tmp; + gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID; + const gnutls_ecc_curve_entry_st *ce; + + gnutls_pk_params_init(&pkey->params); + + curve = gnutls_oid_to_ecc_curve(oid); + if (curve == GNUTLS_ECC_CURVE_INVALID) { + _gnutls_debug_log("PKCS#8: unknown curve OID %s\n", oid); + return gnutls_assert_val(GNUTLS_E_ECC_UNSUPPORTED_CURVE); + } + + ce = _gnutls_ecc_curve_get_params(curve); + if (_curve_is_modern_ecdh(ce)) { + ret = _gnutls_x509_read_string(pkcs8_asn, "privateKey", &tmp, ASN1_ETYPE_OCTET_STRING, 1); + if (ret < 0) { + gnutls_assert(); + return gnutls_assert_val(ret); + } + + if (tmp.size != ce->size) { + gnutls_free(tmp.data); + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + } + gnutls_free(pkey->params.raw_priv.data); + switch (curve) { + case GNUTLS_ECC_CURVE_X25519: + pkey->params.algo = GNUTLS_PK_ECDH_X25519; + break; + case GNUTLS_ECC_CURVE_X448: + pkey->params.algo = GNUTLS_PK_ECDH_X448; + break; + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + pkey->params.raw_priv.data = tmp.data; + pkey->params.raw_priv.size = tmp.size; + pkey->params.curve = curve; + + tmp.data = NULL; + return 0; + } else { + return gnutls_assert_val(GNUTLS_E_ECC_UNSUPPORTED_CURVE); + } +} + /* Converts a GOST key to * an internal structure (gnutls_private_key) */ @@ -1461,6 +1513,10 @@ decode_private_key_info(const gnutls_datum_t * der, case GNUTLS_PK_EDDSA_ED448: result = _decode_pkcs8_eddsa_key(pkcs8_asn, pkey, oid); break; + case GNUTLS_PK_ECDH_X25519: + case GNUTLS_PK_ECDH_X448: + result = _decode_pkcs8_modern_ecdh_key(pkcs8_asn, pkey, oid); + break; case GNUTLS_PK_GOST_01: case GNUTLS_PK_GOST_12_256: case GNUTLS_PK_GOST_12_512: diff --git a/src/certtool-args.def b/src/certtool-args.def index 5eef8e7110..61dcb712a5 100644 --- a/src/certtool-args.def +++ b/src/certtool-args.def @@ -200,7 +200,7 @@ flag = { arg-type = string; descrip = "Specify the key type to use on key generation"; doc = "This option can be combined with --generate-privkey, to specify -the key type to be generated. Valid options are, 'rsa', 'rsa-pss', 'dsa', 'ecdsa', 'ed25519, and 'ed448'.'. +the key type to be generated. Valid options are, 'rsa', 'rsa-pss', 'dsa', 'ecdsa', 'ed25519, 'ed448', 'x25519', and 'x448'.'. When combined with certificate generation it can be used to specify an RSA-PSS certificate when an RSA key is given."; }; diff --git a/src/certtool-common.c b/src/certtool-common.c index 31e1c2619f..3fd63ab7aa 100644 --- a/src/certtool-common.c +++ b/src/certtool-common.c @@ -1289,7 +1289,9 @@ static void privkey_info_int(FILE *outfile, common_info_st * cinfo, } } else if (key_type == GNUTLS_PK_ECDSA || key_type == GNUTLS_PK_EDDSA_ED25519 || - key_type == GNUTLS_PK_EDDSA_ED448) { + key_type == GNUTLS_PK_EDDSA_ED448 || + key_type == GNUTLS_PK_ECDH_X25519 || + key_type == GNUTLS_PK_ECDH_X448) { gnutls_datum_t y, x, k; gnutls_ecc_curve_t curve; @@ -1645,6 +1647,10 @@ gnutls_pk_algorithm_t figure_key_type(const char *key_type) return GNUTLS_PK_EDDSA_ED25519; else if (strcasecmp(key_type, "ed448") == 0) return GNUTLS_PK_EDDSA_ED448; + else if (strcasecmp(key_type, "x25519") == 0) + return GNUTLS_PK_ECDH_X25519; + else if (strcasecmp(key_type, "x448") == 0) + return GNUTLS_PK_ECDH_X448; else if (strcasecmp(key_type, "dsa") == 0) return GNUTLS_PK_DSA; else if (strcasecmp(key_type, "ecdsa") == 0 || strcasecmp(key_type, "ecc") == 0) diff --git a/src/certtool-common.h b/src/certtool-common.h index 04c7a3e91a..db7b1bde3a 100644 --- a/src/certtool-common.h +++ b/src/certtool-common.h @@ -91,6 +91,7 @@ void switch_to_pkcs8_when_needed(common_info_st *cinfo, gnutls_x509_privkey_t ke return; if (key_type == GNUTLS_PK_RSA_PSS || key_type == GNUTLS_PK_EDDSA_ED25519 || key_type == GNUTLS_PK_EDDSA_ED448 || + key_type == GNUTLS_PK_ECDH_X25519 || key_type == GNUTLS_PK_ECDH_X448 || key_type == GNUTLS_PK_GOST_01 || key_type == GNUTLS_PK_GOST_12_256 || key_type == GNUTLS_PK_GOST_12_512) { if (cinfo->verbose) diff --git a/src/certtool.c b/src/certtool.c index 1e0814a51f..825a306bc9 100644 --- a/src/certtool.c +++ b/src/certtool.c @@ -566,6 +566,10 @@ generate_certificate(gnutls_privkey_t * ret_key, if (result) usage |= GNUTLS_KEY_KEY_ENCIPHERMENT; + } else if (pk == GNUTLS_PK_ECDH_X25519 || + pk == GNUTLS_PK_ECDH_X448) { + /* X25519 and X448 are only for key agreement. */ + usage |= GNUTLS_KEY_KEY_AGREEMENT; } else { usage |= GNUTLS_KEY_DIGITAL_SIGNATURE; } diff --git a/tests/cert-tests/Makefile.am b/tests/cert-tests/Makefile.am index 58041a9567..ef5f9029dd 100644 --- a/tests/cert-tests/Makefile.am +++ b/tests/cert-tests/Makefile.am @@ -112,7 +112,7 @@ dist_check_SCRIPTS = pathlen.sh aki.sh invalid-sig.sh email.sh \ pkcs12.sh certtool-crl-decoding.sh pkcs12-encode.sh pkcs12-corner-cases.sh inhibit-anypolicy.sh \ smime.sh cert-time.sh alt-chain.sh pkcs7-list-sign.sh pkcs7-eddsa.sh certtool-ecdsa.sh \ key-id.sh pkcs8.sh pkcs8-decode.sh ecdsa.sh illegal-rsa.sh pkcs8-invalid.sh key-invalid.sh \ - pkcs8-eddsa.sh certtool-subca.sh certtool-verify-profiles.sh x509-duplicate-ext.sh + pkcs8-eddsa.sh certtool-subca.sh certtool-verify-profiles.sh x509-duplicate-ext.sh x25519-and-x448.sh dist_check_SCRIPTS += key-id.sh ecdsa.sh pkcs8-invalid.sh key-invalid.sh pkcs8-decode.sh pkcs8.sh pkcs8-eddsa.sh \ certtool-utf8.sh crq.sh diff --git a/tests/cert-tests/data/cert-eddsa.pem b/tests/cert-tests/data/cert-eddsa.pem index b402a97042..dd328011a0 100644 --- a/tests/cert-tests/data/cert-eddsa.pem +++ b/tests/cert-tests/data/cert-eddsa.pem @@ -7,6 +7,11 @@ X.509 Certificate Information: Not After: Mon Dec 31 23:59:59 UTC 2040 Subject: CN=IETF Test Demo Subject Public Key Algorithm: ECDH (X25519) + Algorithm Security Level: High (256 bits) + Curve: X25519 + X: + 85:20:f0:09:89:30:a7:54:74:8b:7d:dc:b4:3e:f7:5a + 0d:bf:3a:0d:26:38:1a:f4:eb:a4:a9:8e:aa:9b:4e:6a Extensions: Basic Constraints (critical): Certificate Authority (CA): FALSE @@ -24,6 +29,11 @@ Other Information: Fingerprint: sha1:8b011a41d9b72f9848b1dcbd3a038fa8c9d0a536 sha256:180516f0a03e4893d234a28f3ad28921bc35d1b12bd35134847240dafb715a11 + Public Key ID: + sha1:fa752c35cb9a51d2069147e6e682155aaf2e5836 + sha256:291c5293e030452a599851a7c7298f3f16c3ff1bdfafcb598927f2631f9fa641 + Public Key PIN: + pin-sha256:KRxSk+AwRSpZmFGnxymPPxbD/xvfr8tZiSfyYx+fpkE= -----BEGIN CERTIFICATE----- MIIBLDCB36ADAgECAghWAUdKKo3DMDAFBgMrZXAwGTEXMBUGA1UEAwwOSUVURiBU diff --git a/tests/cert-tests/x25519-and-x448.sh b/tests/cert-tests/x25519-and-x448.sh new file mode 100755 index 0000000000..23fbd5f1ca --- /dev/null +++ b/tests/cert-tests/x25519-and-x448.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +# Copyright (C) 2021 Free Software Foundation, Inc. +# +# Author: Daniel Kahn Gillmor +# +# This file is part of GnuTLS. +# +# GnuTLS 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; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS 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 GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#set -e + +: ${srcdir=.} +: ${CERTTOOL=../../src/certtool${EXEEXT}} +TMPFILE=crfg-kx.$$.tmp +TMPCA=eddsa-ca.$$.tmp +TMPCAKEY=eddsa-ca-key.$$.tmp +TMPSUBCA=eddsa-subca.$$.tmp +TMPSUBCAKEY=eddsa-subca-key.$$.tmp +TMPKEY=kx-key.$$.tmp +TMPTEMPL=template.$$.tmp +TMPUSER=user.$$.tmp +VERIFYOUT=verify.$$.tmp + +if ! test -x "${CERTTOOL}"; then + exit 77 +fi + +for curve in 25519 448; do + echo ca > $TMPTEMPL + echo "cn = Ed$curve CA" >> $TMPTEMPL + + "${CERTTOOL}" --generate-privkey --key-type=ed$curve > $TMPCAKEY 2>/dev/null + + "${CERTTOOL}" -d 2 --generate-self-signed --template $TMPTEMPL \ + --load-privkey $TMPCAKEY \ + --outfile $TMPCA >$TMPFILE 2>&1 + + if [ $? != 0 ]; then + cat $TMPFILE + exit 1 + fi + + echo ca > $TMPTEMPL + echo "cn = Ed$curve Mid CA" >> $TMPTEMPL + + "${CERTTOOL}" --generate-privkey --key-type=ed$curve > $TMPSUBCAKEY 2>/dev/null + + "${CERTTOOL}" -d 2 --generate-certificate --template $TMPTEMPL \ + --load-ca-privkey $TMPCAKEY \ + --load-ca-certificate $TMPCA \ + --load-privkey $TMPSUBCAKEY \ + --outfile $TMPSUBCA >$TMPFILE 2>&1 + + if [ $? != 0 ]; then + cat $TMPFILE + exit 1 + fi + + echo "cn = End-user" > $TMPTEMPL + echo email_protection_key >> $TMPTEMPL + echo encryption_key >> $TMPTEMPL + + "${CERTTOOL}" --generate-privkey --key-type=x$curve > $TMPKEY 2>/dev/null + + "${CERTTOOL}" -d 2 --generate-certificate --template $TMPTEMPL \ + --load-ca-privkey $TMPSUBCAKEY \ + --load-ca-certificate $TMPSUBCA \ + --load-privkey $TMPKEY \ + --outfile $TMPUSER >$TMPFILE 2>&1 + + if [ $? != 0 ]; then + cat $TMPFILE + exit 1 + fi + + cat $TMPUSER $TMPSUBCA $TMPCA > $TMPFILE + "${CERTTOOL}" --verify-chain <$TMPFILE > $VERIFYOUT + + if [ $? != 0 ]; then + cat $VERIFYOUT + exit 1 + fi + + rm -f $VERIFYOUT $TMPUSER $TMPCA $TMPSUBCA $TMPTEMPL $TMPFILE + rm -f $TMPSUBCAKEY $TMPCAKEY $TMPKEY +done + +exit 0 |