| // Copyright 2026 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <openssl/evp.h> | |
| #include <openssl/bytestring.h> | |
| #include <openssl/err.h> | |
| #include <openssl/evp_errors.h> | |
| #include <openssl/mem.h> | |
| #include <openssl/xwing.h> | |
| #include "../mem_internal.h" | |
| #include "internal.h" | |
| using namespace bssl; | |
| namespace { | |
| struct XWING_KEY { | |
| static constexpr bool kAllowUniquePtr = true; | |
| uint8_t pub[XWING_PUBLIC_KEY_BYTES]; | |
| XWING_private_key priv; | |
| bool has_private; | |
| }; | |
| extern const EVP_PKEY_ASN1_METHOD xwing_asn1_meth; | |
| extern const EVP_PKEY_CTX_METHOD xwing_pkey_meth; | |
| // This is the length of `eseed` in draft-connolly-cfrg-xwing-kem-06. | |
| constexpr size_t kXwingEncapEntropyBytes = 64; | |
| void xwing_free(EvpPkey *pkey) { | |
| Delete(reinterpret_cast<XWING_KEY *>(pkey->pkey)); | |
| } | |
| bool xwing_pub_equal(const EvpPkey *a, const EvpPkey *b) { | |
| const XWING_KEY *a_key = reinterpret_cast<const XWING_KEY *>(a->pkey); | |
| const XWING_KEY *b_key = reinterpret_cast<const XWING_KEY *>(b->pkey); | |
| return OPENSSL_memcmp(a_key->pub, b_key->pub, XWING_PUBLIC_KEY_BYTES) == 0; | |
| } | |
| bool xwing_pub_present(const EvpPkey *) { return true; } | |
| bool xwing_pub_copy(EvpPkey *out, const EvpPkey *pkey) { | |
| const XWING_KEY *pkey_xwing = reinterpret_cast<const XWING_KEY *>(pkey->pkey); | |
| auto public_copy = MakeUnique<XWING_KEY>(); | |
| if (public_copy == nullptr) { | |
| return false; | |
| } | |
| OPENSSL_memcpy(public_copy->pub, pkey_xwing->pub, XWING_PUBLIC_KEY_BYTES); | |
| public_copy->has_private = false; | |
| evp_pkey_set0(out, pkey->ameth, public_copy.release()); | |
| return true; | |
| } | |
| bool xwing_priv_present(const EvpPkey *pk) { | |
| const XWING_KEY *key = reinterpret_cast<const XWING_KEY *>(pk->pkey); | |
| return key->has_private; | |
| } | |
| int xwing_set_priv_seed(EvpPkey *pkey, const uint8_t *in, size_t len) { | |
| auto key = MakeUnique<XWING_KEY>(); | |
| if (key == nullptr) { | |
| return 0; | |
| } | |
| CBS cbs; | |
| CBS_init(&cbs, in, len); | |
| if (!XWING_parse_private_key(&key->priv, &cbs) || CBS_len(&cbs) != 0 || | |
| !XWING_public_from_private(key->pub, &key->priv)) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); | |
| return 0; | |
| } | |
| key->has_private = true; | |
| evp_pkey_set0(pkey, &xwing_asn1_meth, key.release()); | |
| return 1; | |
| } | |
| int xwing_get_priv_seed(const EvpPkey *pkey, uint8_t *out, size_t *out_len) { | |
| const XWING_KEY *key = reinterpret_cast<const XWING_KEY *>(pkey->pkey); | |
| if (key == nullptr || !key->has_private) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY); | |
| return 0; | |
| } | |
| if (out == nullptr) { | |
| *out_len = XWING_PRIVATE_KEY_BYTES; | |
| return 1; | |
| } | |
| if (*out_len < XWING_PRIVATE_KEY_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); | |
| return 0; | |
| } | |
| CBB cbb; | |
| CBB_init_fixed(&cbb, out, XWING_PRIVATE_KEY_BYTES); | |
| if (!XWING_marshal_private_key(&cbb, &key->priv)) { | |
| return 0; | |
| } | |
| *out_len = CBB_len(&cbb); | |
| assert(*out_len == XWING_PRIVATE_KEY_BYTES); | |
| return 1; | |
| } | |
| int xwing_set_pub_raw(EvpPkey *pkey, const uint8_t *in, size_t len) { | |
| if (len != XWING_PUBLIC_KEY_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); | |
| return 0; | |
| } | |
| auto key = MakeUnique<XWING_KEY>(); | |
| if (key == nullptr) { | |
| return 0; | |
| } | |
| OPENSSL_memcpy(key->pub, in, len); | |
| key->has_private = false; | |
| evp_pkey_set0(pkey, &xwing_asn1_meth, key.release()); | |
| return 1; | |
| } | |
| int xwing_get_pub_raw(const EvpPkey *pkey, uint8_t *out, size_t *out_len) { | |
| if (out == nullptr) { | |
| *out_len = XWING_PUBLIC_KEY_BYTES; | |
| return 1; | |
| } | |
| if (*out_len < XWING_PUBLIC_KEY_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_BUFFER_TOO_SMALL); | |
| return 0; | |
| } | |
| const XWING_KEY *key = reinterpret_cast<const XWING_KEY *>(pkey->pkey); | |
| OPENSSL_memcpy(out, key->pub, XWING_PUBLIC_KEY_BYTES); | |
| *out_len = XWING_PUBLIC_KEY_BYTES; | |
| return 1; | |
| } | |
| int xwing_size(const EvpPkey *pkey) { return XWING_CIPHERTEXT_BYTES; } | |
| int xwing_bits(const EvpPkey *pkey) { return XWING_PUBLIC_KEY_BYTES * 8; } | |
| // X-Wing has no parameters to copy. | |
| int pkey_xwing_copy_ctx(EvpPkeyCtx *dst, EvpPkeyCtx *src) { return 1; } | |
| int pkey_xwing_keygen(EvpPkeyCtx *ctx, EvpPkey *pkey) { | |
| auto key = MakeUnique<XWING_KEY>(); | |
| if (key == nullptr || !XWING_generate_key(key->pub, &key->priv)) { | |
| OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR); | |
| return 0; | |
| } | |
| key->has_private = true; | |
| evp_pkey_set0(pkey, &xwing_asn1_meth, key.release()); | |
| return 1; | |
| } | |
| int xwing_kem_encap(const EVP_KEM *kem, Span<uint8_t> out_ciphertext, | |
| Span<uint8_t> out_secret, const EVP_PKEY *peer_key) { | |
| if (out_ciphertext.size() != XWING_CIPHERTEXT_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_CIPHERTEXT_LENGTH); | |
| return 0; | |
| } | |
| if (out_secret.size() != XWING_SHARED_SECRET_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SECRET_LENGTH); | |
| return 0; | |
| } | |
| const XWING_KEY *peer_pubkey = | |
| reinterpret_cast<XWING_KEY *>(FromOpaque(peer_key)->pkey); | |
| return XWING_encap(out_ciphertext.data(), out_secret.data(), | |
| peer_pubkey->pub); | |
| } | |
| int xwing_kem_encap_external_entropy(const EVP_KEM *kem, | |
| Span<uint8_t> out_ciphertext, | |
| Span<uint8_t> out_secret, | |
| const EVP_PKEY *peer_key, | |
| Span<const uint8_t> entropy) { | |
| if (out_ciphertext.size() != XWING_CIPHERTEXT_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_CIPHERTEXT_LENGTH); | |
| return 0; | |
| } | |
| if (out_secret.size() != XWING_SHARED_SECRET_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SECRET_LENGTH); | |
| return 0; | |
| } | |
| if (entropy.size() != kXwingEncapEntropyBytes) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_ENTROPY_LENGTH); | |
| return 0; | |
| } | |
| const XWING_KEY *peer_pubkey = | |
| reinterpret_cast<XWING_KEY *>(FromOpaque(peer_key)->pkey); | |
| return XWING_encap_external_entropy(out_ciphertext.data(), out_secret.data(), | |
| peer_pubkey->pub, entropy.data()); | |
| } | |
| int xwing_kem_decap(const EVP_KEM *kem, Span<uint8_t> out_secret, | |
| Span<const uint8_t> ciphertext, const EVP_PKEY *key) { | |
| const XWING_KEY *priv = reinterpret_cast<XWING_KEY *>(FromOpaque(key)->pkey); | |
| if (priv == nullptr || !priv->has_private) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_NOT_A_PRIVATE_KEY); | |
| return 0; | |
| } | |
| if (out_secret.size() != XWING_SHARED_SECRET_BYTES) { | |
| OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_SECRET_LENGTH); | |
| return 0; | |
| } | |
| // XWING_decap does not accept wrong ciphertext lengths, so we must check for | |
| // the proper length here. For consistency, we don't add an error to the error | |
| // queue when a KEM decap fails due to incorrect ciphertext length. | |
| if (ciphertext.size() != XWING_CIPHERTEXT_BYTES) { | |
| return 0; | |
| } | |
| return XWING_decap(out_secret.data(), ciphertext.data(), &priv->priv); | |
| } | |
| const EVP_KEM xwing_evp_kem = { | |
| EVP_PKEY_XWING, // | |
| XWING_CIPHERTEXT_BYTES, // | |
| XWING_SHARED_SECRET_BYTES, // | |
| kXwingEncapEntropyBytes, // | |
| /*check_key=*/nullptr, // | |
| &xwing_kem_encap, // | |
| &xwing_kem_encap_external_entropy, // | |
| &xwing_kem_decap, // | |
| }; | |
| const EVP_PKEY_CTX_METHOD xwing_pkey_meth = { | |
| /*pkey_id=*/EVP_PKEY_XWING, | |
| /*init=*/nullptr, | |
| &pkey_xwing_copy_ctx, | |
| /*cleanup=*/nullptr, | |
| &pkey_xwing_keygen, | |
| /*sign=*/nullptr, | |
| /*sign_message=*/nullptr, | |
| /*verify=*/nullptr, | |
| /*verify_message=*/nullptr, | |
| /*verify_recover=*/nullptr, | |
| /*encrypt=*/nullptr, | |
| /*decrypt=*/nullptr, | |
| /*derive=*/nullptr, | |
| /*paramgen=*/nullptr, | |
| &KemAdapter<xwing_evp_kem>::EncapMethod, | |
| &KemAdapter<xwing_evp_kem>::DecapMethod, | |
| /*ctrl=*/nullptr, | |
| }; | |
| const EVP_PKEY_ASN1_METHOD xwing_asn1_meth = { | |
| EVP_PKEY_XWING, | |
| /*oid=*/{}, | |
| /*oid_len=*/0, | |
| &xwing_pkey_meth, | |
| /*pub_decode=*/nullptr, | |
| /*pub_encode=*/nullptr, | |
| &xwing_pub_equal, | |
| &xwing_pub_present, | |
| &xwing_pub_copy, | |
| /*priv_decode=*/nullptr, | |
| /*priv_encode=*/nullptr, | |
| &xwing_priv_present, | |
| /*set_priv_raw=*/nullptr, | |
| &xwing_set_priv_seed, | |
| &xwing_set_pub_raw, | |
| /*get_priv_raw=*/nullptr, | |
| &xwing_get_priv_seed, | |
| &xwing_get_pub_raw, | |
| /*set1_tls_encodedpoint=*/nullptr, | |
| /*get1_tls_encodedpoint=*/nullptr, | |
| /*pkey_opaque=*/nullptr, | |
| &xwing_size, | |
| &xwing_bits, | |
| /*param_missing=*/nullptr, | |
| /*param_copy=*/nullptr, | |
| /*param_equal=*/nullptr, | |
| /*pkey_free=*/&xwing_free, | |
| }; | |
| } // namespace | |
| const EVP_PKEY_ALG *EVP_pkey_xwing() { | |
| static const EVP_PKEY_ALG kAlg = {&xwing_asn1_meth, &xwing_pkey_meth}; | |
| return &kAlg; | |
| } | |
| const EVP_KEM *EVP_kem_xwing() { return &xwing_evp_kem; } |