Add EVP_PKEY support for X25519. cryptography.io expects X25519 support to be exposed via EVP_PKEY. Also we're considering using EVP_PKEY to pass in keys for ESNI. This unfortunately requires adding some odd EVP_PKEY_set1_tls_encodedpoint and EVP_PKEY_get1_tls_encodedpoint APIs which cryptography.io uses for X25519 because the EVP_PKEY "raw" functions did not exist at the time. To test, implement EVP_PKEY_derive support in evp_tests.txt. Change-Id: Ie0666bb9aba13eecf203156dc047ac49ef6d0093 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/36788 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 81560c9..477faae 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt
@@ -282,6 +282,8 @@ evp/p_ed25519_asn1.c evp/p_rsa.c evp/p_rsa_asn1.c + evp/p_x25519.c + evp/p_x25519_asn1.c evp/pbkdf.c evp/print.c evp/scrypt.c
diff --git a/crypto/err/evp.errordata b/crypto/err/evp.errordata index aea4a94..771cd6a 100644 --- a/crypto/err/evp.errordata +++ b/crypto/err/evp.errordata
@@ -15,6 +15,7 @@ EVP,114,INVALID_OPERATION EVP,115,INVALID_PADDING_MODE EVP,133,INVALID_PARAMETERS +EVP,134,INVALID_PEER_KEY EVP,116,INVALID_PSS_SALTLEN EVP,131,INVALID_SIGNATURE EVP,117,KEYS_NOT_SET
diff --git a/crypto/evp/evp.c b/crypto/evp/evp.c index de4db7e..838353a 100644 --- a/crypto/evp/evp.c +++ b/crypto/evp/evp.c
@@ -200,6 +200,8 @@ return &dsa_asn1_meth; case EVP_PKEY_ED25519: return &ed25519_asn1_meth; + case EVP_PKEY_X25519: + return &x25519_asn1_meth; default: return NULL; }
diff --git a/crypto/evp/evp_asn1.c b/crypto/evp/evp_asn1.c index d56b93b..fc1dce3 100644 --- a/crypto/evp/evp_asn1.c +++ b/crypto/evp/evp_asn1.c
@@ -73,6 +73,7 @@ &ec_asn1_meth, &dsa_asn1_meth, &ed25519_asn1_meth, + &x25519_asn1_meth, }; static int parse_key_type(CBS *cbs, int *out_type) {
diff --git a/crypto/evp/evp_ctx.c b/crypto/evp/evp_ctx.c index daa1954..9ca2c55 100644 --- a/crypto/evp/evp_ctx.c +++ b/crypto/evp/evp_ctx.c
@@ -67,15 +67,14 @@ static const EVP_PKEY_METHOD *const evp_methods[] = { - &rsa_pkey_meth, - &ec_pkey_meth, - &ed25519_pkey_meth, + &rsa_pkey_meth, + &ec_pkey_meth, + &ed25519_pkey_meth, + &x25519_pkey_meth, }; static const EVP_PKEY_METHOD *evp_pkey_meth_find(int type) { - unsigned i; - - for (i = 0; i < sizeof(evp_methods)/sizeof(EVP_PKEY_METHOD*); i++) { + for (size_t i = 0; i < sizeof(evp_methods)/sizeof(EVP_PKEY_METHOD*); i++) { if (evp_methods[i]->pkey_id == type) { return evp_methods[i]; }
diff --git a/crypto/evp/evp_test.cc b/crypto/evp/evp_test.cc index 9df5493..b4be636 100644 --- a/crypto/evp/evp_test.cc +++ b/crypto/evp/evp_test.cc
@@ -119,6 +119,9 @@ if (name == "Ed25519") { return EVP_PKEY_ED25519; } + if (name == "X25519") { + return EVP_PKEY_X25519; + } ADD_FAILURE() << "Unknown key type: " << name; return EVP_PKEY_NONE; } @@ -245,7 +248,7 @@ // SetupContext configures |ctx| based on attributes in |t|, with the exception // of the signing digest which must be configured externally. -static bool SetupContext(FileTest *t, EVP_PKEY_CTX *ctx) { +static bool SetupContext(FileTest *t, KeyMap *key_map, EVP_PKEY_CTX *ctx) { if (t->HasAttribute("RSAPadding")) { int padding; if (!GetRSAPadding(t, &padding, t->GetAttributeOrDie("RSAPadding")) || @@ -285,6 +288,74 @@ } buf.release(); } + if (t->HasAttribute("DerivePeer")) { + std::string derive_peer = t->GetAttributeOrDie("DerivePeer"); + if (key_map->count(derive_peer) == 0) { + ADD_FAILURE() << "Could not find key " << derive_peer; + return false; + } + EVP_PKEY *derive_peer_key = (*key_map)[derive_peer].get(); + if (!EVP_PKEY_derive_set_peer(ctx, derive_peer_key)) { + return false; + } + } + return true; +} + +static bool TestDerive(FileTest *t, KeyMap *key_map, EVP_PKEY *key) { + bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr)); + if (!ctx || + !EVP_PKEY_derive_init(ctx.get()) || + !SetupContext(t, key_map, ctx.get())) { + return false; + } + + bssl::UniquePtr<EVP_PKEY_CTX> copy(EVP_PKEY_CTX_dup(ctx.get())); + if (!copy) { + return false; + } + + for (EVP_PKEY_CTX *pctx : {ctx.get(), copy.get()}) { + size_t len; + std::vector<uint8_t> actual, output; + if (!EVP_PKEY_derive(pctx, nullptr, &len)) { + return false; + } + actual.resize(len); + if (!EVP_PKEY_derive(pctx, actual.data(), &len)) { + return false; + } + actual.resize(len); + + // Defer looking up the attribute so Error works properly. + if (!t->GetBytes(&output, "Output")) { + return false; + } + EXPECT_EQ(Bytes(output), Bytes(actual)); + + // Test when the buffer is too large. + actual.resize(len + 1); + len = actual.size(); + if (!EVP_PKEY_derive(pctx, actual.data(), &len)) { + return false; + } + actual.resize(len); + EXPECT_EQ(Bytes(output), Bytes(actual)); + + // Test when the buffer is too small. + actual.resize(len - 1); + len = actual.size(); + if (t->HasAttribute("SmallBufferTruncates")) { + if (!EVP_PKEY_derive(pctx, actual.data(), &len)) { + return false; + } + actual.resize(len); + EXPECT_EQ(Bytes(output.data(), len), Bytes(actual)); + } else { + EXPECT_FALSE(EVP_PKEY_derive(pctx, actual.data(), &len)); + ERR_clear_error(); + } + } return true; } @@ -298,6 +369,14 @@ return ImportKey(t, key_map, EVP_parse_public_key, EVP_marshal_public_key); } + // Load the key. + const std::string &key_name = t->GetParameter(); + if (key_map->count(key_name) == 0) { + ADD_FAILURE() << "Could not find key " << key_name; + return false; + } + EVP_PKEY *key = (*key_map)[key_name].get(); + int (*key_op_init)(EVP_PKEY_CTX *ctx) = nullptr; int (*key_op)(EVP_PKEY_CTX *ctx, uint8_t *out, size_t *out_len, const uint8_t *in, size_t in_len) = nullptr; @@ -321,19 +400,13 @@ } else if (t->GetType() == "Encrypt") { key_op_init = EVP_PKEY_encrypt_init; key_op = EVP_PKEY_encrypt; + } else if (t->GetType() == "Derive") { + return TestDerive(t, key_map, key); } else { ADD_FAILURE() << "Unknown test " << t->GetType(); return false; } - // Load the key. - const std::string &key_name = t->GetParameter(); - if (key_map->count(key_name) == 0) { - ADD_FAILURE() << "Could not find key " << key_name; - return false; - } - EVP_PKEY *key = (*key_map)[key_name].get(); - const EVP_MD *digest = nullptr; if (t->HasAttribute("Digest")) { digest = GetDigest(t, t->GetAttributeOrDie("Digest")); @@ -355,7 +428,7 @@ bssl::ScopedEVP_MD_CTX ctx, copy; EVP_PKEY_CTX *pctx; if (!md_op_init(ctx.get(), &pctx, digest, nullptr, key) || - !SetupContext(t, pctx) || + !SetupContext(t, key_map, pctx) || !EVP_MD_CTX_copy_ex(copy.get(), ctx.get())) { return false; } @@ -402,7 +475,7 @@ !key_op_init(ctx.get()) || (digest != nullptr && !EVP_PKEY_CTX_set_signature_md(ctx.get(), digest)) || - !SetupContext(t, ctx.get())) { + !SetupContext(t, key_map, ctx.get())) { return false; } @@ -436,7 +509,7 @@ !EVP_PKEY_decrypt_init(decrypt_ctx.get()) || (digest != nullptr && !EVP_PKEY_CTX_set_signature_md(decrypt_ctx.get(), digest)) || - !SetupContext(t, decrypt_ctx.get()) || + !SetupContext(t, key_map, decrypt_ctx.get()) || !EVP_PKEY_decrypt(decrypt_ctx.get(), nullptr, &plaintext_len, actual.data(), actual.size())) { return false; @@ -456,7 +529,7 @@ !EVP_PKEY_verify_init(verify_ctx.get()) || (digest != nullptr && !EVP_PKEY_CTX_set_signature_md(verify_ctx.get(), digest)) || - !SetupContext(t, verify_ctx.get())) { + !SetupContext(t, key_map, verify_ctx.get())) { return false; } if (t->HasAttribute("VerifyPSSSaltLength")) {
diff --git a/crypto/evp/evp_tests.txt b/crypto/evp/evp_tests.txt index 6090f5f..2ac51d3 100644 --- a/crypto/evp/evp_tests.txt +++ b/crypto/evp/evp_tests.txt
@@ -1607,3 +1607,42 @@ Input = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" Output = e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b Error = OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE + + +# Derive tests. + +PrivateKey = ECDH-P256-Private +Type = EC +Input = 3041020100301306072a8648ce3d020106082a8648ce3d0301070427302502010104207d7dc5f71eb29ddaf80d6214632eeae03d9058af1fb6d22ed80badb62bc1a534 + +PublicKey = ECDH-P256-Peer +Type = EC +Input = 3059301306072a8648ce3d020106082a8648ce3d03010703420004700c48f77f56584c5cc632ca65640db91b6bacce3a4df6b42ce7cc838833d287db71e509e3fd9b060ddb20ba5c51dcc5948d46fbf640dfe0441782cab85fa4ac + +Derive = ECDH-P256-Private +DerivePeer = ECDH-P256-Peer +Output = 46fc62106420ff012e54a434fbdd2d25ccc5852060561e68040dd7778997bd7b +SmallBufferTruncates + +PrivateKey = X25519-Private +Type = X25519 +Input = 302e020100300506032b656e04220420a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4 +ExpectRawPrivate = a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4 + +PublicKey = X25519-Peer +Type = X25519 +Input = 302a300506032b656e032100e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c +ExpectRawPublic = e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c + +PublicKey = X25519-SmallOrderPeer +Type = X25519 +ExpectRawPublic = e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800 +Input = 302a300506032b656e032100e0eb7a7c3b41b8ae1656e3faf19fc46ada098deb9c32b1fd866205165f49b800 + +Derive = X25519-Private +DerivePeer = X25519-Peer +Output = c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552 + +Derive = X25519-Private +DerivePeer = X25519-SmallOrderPeer +Error = INVALID_PEER_KEY
diff --git a/crypto/evp/internal.h b/crypto/evp/internal.h index a599706..8b6a583 100644 --- a/crypto/evp/internal.h +++ b/crypto/evp/internal.h
@@ -244,14 +244,22 @@ char has_private; } ED25519_KEY; +typedef struct { + uint8_t pub[32]; + uint8_t priv[32]; + char has_private; +} X25519_KEY; + extern const EVP_PKEY_ASN1_METHOD dsa_asn1_meth; extern const EVP_PKEY_ASN1_METHOD ec_asn1_meth; extern const EVP_PKEY_ASN1_METHOD rsa_asn1_meth; extern const EVP_PKEY_ASN1_METHOD ed25519_asn1_meth; +extern const EVP_PKEY_ASN1_METHOD x25519_asn1_meth; extern const EVP_PKEY_METHOD rsa_pkey_meth; extern const EVP_PKEY_METHOD ec_pkey_meth; extern const EVP_PKEY_METHOD ed25519_pkey_meth; +extern const EVP_PKEY_METHOD x25519_pkey_meth; #if defined(__cplusplus)
diff --git a/crypto/evp/p_x25519.c b/crypto/evp/p_x25519.c new file mode 100644 index 0000000..ed7df39 --- /dev/null +++ b/crypto/evp/p_x25519.c
@@ -0,0 +1,110 @@ +/* Copyright (c) 2019, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include <openssl/evp.h> + +#include <openssl/curve25519.h> +#include <openssl/err.h> +#include <openssl/mem.h> + +#include "internal.h" + + +// X25519 has no parameters to copy. +static int pkey_x25519_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { return 1; } + +static int pkey_x25519_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) { + X25519_KEY *key = OPENSSL_malloc(sizeof(X25519_KEY)); + if (key == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE); + return 0; + } + + if (!EVP_PKEY_set_type(pkey, EVP_PKEY_X25519)) { + OPENSSL_free(key); + return 0; + } + + X25519_keypair(key->pub, key->priv); + key->has_private = 1; + + OPENSSL_free(pkey->pkey.ptr); + pkey->pkey.ptr = key; + return 1; +} + +static int pkey_x25519_derive(EVP_PKEY_CTX *ctx, uint8_t *out, + size_t *out_len) { + if (ctx->pkey == NULL || ctx->peerkey == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET); + return 0; + } + + const X25519_KEY *our_key = ctx->pkey->pkey.ptr; + const X25519_KEY *peer_key = ctx->peerkey->pkey.ptr; + if (our_key == NULL || peer_key == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_KEYS_NOT_SET); + return 0; + } + + if (!our_key->has_private) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY); + return 0; + } + + if (out != NULL) { + if (*out_len < 32) { + OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); + return 0; + } + if (!X25519(out, our_key->priv, peer_key->pub)) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY); + return 0; + } + } + + *out_len = 32; + return 1; +} + +static int pkey_x25519_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) { + switch (type) { + case EVP_PKEY_CTRL_PEER_KEY: + // |EVP_PKEY_derive_set_peer| requires the key implement this command, + // even if it is a no-op. + return 1; + + default: + OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED); + return 0; + } +} + +const EVP_PKEY_METHOD x25519_pkey_meth = { + EVP_PKEY_X25519, + NULL /* init */, + pkey_x25519_copy, + NULL /* cleanup */, + pkey_x25519_keygen, + NULL /* sign */, + NULL /* sign_message */, + NULL /* verify */, + NULL /* verify_message */, + NULL /* verify_recover */, + NULL /* encrypt */, + NULL /* decrypt */, + pkey_x25519_derive, + NULL /* paramgen */, + pkey_x25519_ctrl, +};
diff --git a/crypto/evp/p_x25519_asn1.c b/crypto/evp/p_x25519_asn1.c new file mode 100644 index 0000000..9025623 --- /dev/null +++ b/crypto/evp/p_x25519_asn1.c
@@ -0,0 +1,249 @@ +/* Copyright (c) 2019, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include <openssl/evp.h> + +#include <openssl/buf.h> +#include <openssl/bytestring.h> +#include <openssl/curve25519.h> +#include <openssl/err.h> +#include <openssl/mem.h> + +#include "internal.h" +#include "../internal.h" + + +static void x25519_free(EVP_PKEY *pkey) { + OPENSSL_free(pkey->pkey.ptr); + pkey->pkey.ptr = NULL; +} + +static int x25519_set_priv_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) { + if (len != 32) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + X25519_KEY *key = OPENSSL_malloc(sizeof(X25519_KEY)); + if (key == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE); + return 0; + } + + OPENSSL_memcpy(key->priv, in, 32); + X25519_public_from_private(key->pub, key->priv); + key->has_private = 1; + + x25519_free(pkey); + pkey->pkey.ptr = key; + return 1; +} + +static int x25519_set_pub_raw(EVP_PKEY *pkey, const uint8_t *in, size_t len) { + if (len != 32) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + X25519_KEY *key = OPENSSL_malloc(sizeof(X25519_KEY)); + if (key == NULL) { + OPENSSL_PUT_ERROR(EVP, ERR_R_MALLOC_FAILURE); + return 0; + } + + OPENSSL_memcpy(key->pub, in, 32); + key->has_private = 0; + + x25519_free(pkey); + pkey->pkey.ptr = key; + return 1; +} + +static int x25519_get_priv_raw(const EVP_PKEY *pkey, uint8_t *out, + size_t *out_len) { + const X25519_KEY *key = pkey->pkey.ptr; + if (!key->has_private) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY); + return 0; + } + + if (out == NULL) { + *out_len = 32; + return 1; + } + + if (*out_len < 32) { + OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); + return 0; + } + + OPENSSL_memcpy(out, key->priv, 32); + *out_len = 32; + return 1; +} + +static int x25519_get_pub_raw(const EVP_PKEY *pkey, uint8_t *out, + size_t *out_len) { + const X25519_KEY *key = pkey->pkey.ptr; + if (out == NULL) { + *out_len = 32; + return 1; + } + + if (*out_len < 32) { + OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); + return 0; + } + + OPENSSL_memcpy(out, key->pub, 32); + *out_len = 32; + return 1; +} + +static int x25519_pub_decode(EVP_PKEY *out, CBS *params, CBS *key) { + // See RFC 8410, section 4. + + // The parameters must be omitted. Public keys have length 32. + if (CBS_len(params) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + return x25519_set_pub_raw(out, CBS_data(key), CBS_len(key)); +} + +static int x25519_pub_encode(CBB *out, const EVP_PKEY *pkey) { + const X25519_KEY *key = pkey->pkey.ptr; + + // See RFC 8410, section 4. + CBB spki, algorithm, oid, key_bitstring; + if (!CBB_add_asn1(out, &spki, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&spki, &algorithm, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&oid, x25519_asn1_meth.oid, x25519_asn1_meth.oid_len) || + !CBB_add_asn1(&spki, &key_bitstring, CBS_ASN1_BITSTRING) || + !CBB_add_u8(&key_bitstring, 0 /* padding */) || + !CBB_add_bytes(&key_bitstring, key->pub, 32) || + !CBB_flush(out)) { + OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR); + return 0; + } + + return 1; +} + +static int x25519_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) { + const X25519_KEY *a_key = a->pkey.ptr; + const X25519_KEY *b_key = b->pkey.ptr; + return OPENSSL_memcmp(a_key->pub, b_key->pub, 32) == 0; +} + +static int x25519_priv_decode(EVP_PKEY *out, CBS *params, CBS *key) { + // See RFC 8410, section 7. + + // Parameters must be empty. The key is a 32-byte value wrapped in an extra + // OCTET STRING layer. + CBS inner; + if (CBS_len(params) != 0 || + !CBS_get_asn1(key, &inner, CBS_ASN1_OCTETSTRING) || + CBS_len(key) != 0) { + OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); + return 0; + } + + return x25519_set_priv_raw(out, CBS_data(&inner), CBS_len(&inner)); +} + +static int x25519_priv_encode(CBB *out, const EVP_PKEY *pkey) { + X25519_KEY *key = pkey->pkey.ptr; + if (!key->has_private) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY); + return 0; + } + + // See RFC 8410, section 7. + CBB pkcs8, algorithm, oid, private_key, inner; + if (!CBB_add_asn1(out, &pkcs8, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1_uint64(&pkcs8, 0 /* version */) || + !CBB_add_asn1(&pkcs8, &algorithm, CBS_ASN1_SEQUENCE) || + !CBB_add_asn1(&algorithm, &oid, CBS_ASN1_OBJECT) || + !CBB_add_bytes(&oid, x25519_asn1_meth.oid, x25519_asn1_meth.oid_len) || + !CBB_add_asn1(&pkcs8, &private_key, CBS_ASN1_OCTETSTRING) || + !CBB_add_asn1(&private_key, &inner, CBS_ASN1_OCTETSTRING) || + // The PKCS#8 encoding stores only the 32-byte seed which is the first 32 + // bytes of the private key. + !CBB_add_bytes(&inner, key->priv, 32) || + !CBB_flush(out)) { + OPENSSL_PUT_ERROR(EVP, EVP_R_ENCODE_ERROR); + return 0; + } + + return 1; +} + +static int x25519_size(const EVP_PKEY *pkey) { return 32; } + +static int x25519_bits(const EVP_PKEY *pkey) { return 253; } + +const EVP_PKEY_ASN1_METHOD x25519_asn1_meth = { + EVP_PKEY_X25519, + {0x2b, 0x65, 0x6e}, + 3, + x25519_pub_decode, + x25519_pub_encode, + x25519_pub_cmp, + x25519_priv_decode, + x25519_priv_encode, + x25519_set_priv_raw, + x25519_set_pub_raw, + x25519_get_priv_raw, + x25519_get_pub_raw, + NULL /* pkey_opaque */, + x25519_size, + x25519_bits, + NULL /* param_missing */, + NULL /* param_copy */, + NULL /* param_cmp */, + x25519_free, +}; + +int EVP_PKEY_set1_tls_encodedpoint(EVP_PKEY *pkey, const uint8_t *in, + size_t len) { + // TODO(davidben): In OpenSSL, this function also works for |EVP_PKEY_EC| + // keys. Add support if it ever comes up. + if (pkey->type != EVP_PKEY_X25519) { + OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_PUBLIC_KEY_TYPE); + return 0; + } + + return x25519_set_pub_raw(pkey, in, len); +} + +size_t EVP_PKEY_get1_tls_encodedpoint(const EVP_PKEY *pkey, uint8_t **out_ptr) { + // TODO(davidben): In OpenSSL, this function also works for |EVP_PKEY_EC| + // keys. Add support if it ever comes up. + if (pkey->type != EVP_PKEY_X25519) { + OPENSSL_PUT_ERROR(EVP, EVP_R_UNSUPPORTED_PUBLIC_KEY_TYPE); + return 0; + } + + const X25519_KEY *key = pkey->pkey.ptr; + if (key == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_NO_KEY_SET); + return 0; + } + + *out_ptr = BUF_memdup(key->pub, 32); + return *out_ptr == NULL ? 0 : 32; +}
diff --git a/include/openssl/evp.h b/include/openssl/evp.h index 39c418b..a7b4fcf 100644 --- a/include/openssl/evp.h +++ b/include/openssl/evp.h
@@ -176,6 +176,7 @@ #define EVP_PKEY_DSA NID_dsa #define EVP_PKEY_EC NID_X9_62_id_ecPublicKey #define EVP_PKEY_ED25519 NID_ED25519 +#define EVP_PKEY_X25519 NID_X25519 // EVP_PKEY_assign sets the underlying key of |pkey| to |key|, which must be of // the given type. It returns one if successful or zero if the |type| argument @@ -906,6 +907,23 @@ OPENSSL_EXPORT int EVP_PKEY_CTX_set_ec_param_enc(EVP_PKEY_CTX *ctx, int encoding); +// EVP_PKEY_set1_tls_encodedpoint replaces |pkey| with a public key encoded by +// |in|. It returns one on success and zero on error. +// +// This function only works on X25519 keys. +OPENSSL_EXPORT int EVP_PKEY_set1_tls_encodedpoint(EVP_PKEY *pkey, + const uint8_t *in, + size_t len); + +// EVP_PKEY_get1_tls_encodedpoint sets |*out_ptr| to a newly-allocated buffer +// containing the raw encoded public key for |pkey|. The caller must call +// |OPENSSL_free| to release this buffer. The function returns the length of the +// buffer on success and zero on error. +// +// This function only works on X25519 keys. +OPENSSL_EXPORT size_t EVP_PKEY_get1_tls_encodedpoint(const EVP_PKEY *pkey, + uint8_t **out_ptr); + // Preprocessor compatibility section (hidden). // @@ -994,5 +1012,6 @@ #define EVP_R_INVALID_SIGNATURE 131 #define EVP_R_MEMORY_LIMIT_EXCEEDED 132 #define EVP_R_INVALID_PARAMETERS 133 +#define EVP_R_INVALID_PEER_KEY 134 #endif // OPENSSL_HEADER_EVP_H