summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaiki Ueno <ueno@gnu.org>2021-09-23 07:50:38 +0000
committerDaiki Ueno <ueno@gnu.org>2021-09-23 07:50:38 +0000
commit7a220bf3899f3a32d6ff667d3861d15312ccd35b (patch)
tree131eb151d1185653a4c34ad345872a980420e4c0
parent970000abfe3f88a8659da084852db258310246fd (diff)
parenta00a79ddf41eb14d56bdea076b5c252029896431 (diff)
downloadgnutls-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--NEWS3
-rw-r--r--lib/algorithms.h16
-rw-r--r--lib/algorithms/ecc.c2
-rw-r--r--lib/algorithms/publickey.c4
-rw-r--r--lib/nettle/pk.c67
-rw-r--r--lib/pk.c4
-rw-r--r--lib/privkey.c2
-rw-r--r--lib/pubkey.c143
-rw-r--r--lib/x509/common.c3
-rw-r--r--lib/x509/common.h3
-rw-r--r--lib/x509/key_decode.c22
-rw-r--r--lib/x509/key_encode.c39
-rw-r--r--lib/x509/output.c2
-rw-r--r--lib/x509/privkey_pkcs8.c56
-rw-r--r--src/certtool-args.def2
-rw-r--r--src/certtool-common.c8
-rw-r--r--src/certtool-common.h1
-rw-r--r--src/certtool.c4
-rw-r--r--tests/cert-tests/Makefile.am2
-rw-r--r--tests/cert-tests/data/cert-eddsa.pem10
-rwxr-xr-xtests/cert-tests/x25519-and-x448.sh101
21 files changed, 485 insertions, 9 deletions
diff --git a/NEWS b/NEWS
index 0fcd043aa3..0ae6410098 100644
--- a/NEWS
+++ b/NEWS
@@ -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);
diff --git a/lib/pk.c b/lib/pk.c
index f932589ede..1904f59453 100644
--- a/lib/pk.c
+++ b/lib/pk.c
@@ -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(&params->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(&params->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