From 6452fa9fc6f33dac80ee572764b9fe29a469f8ce Mon Sep 17 00:00:00 2001 From: beck <> Date: Thu, 14 Aug 2025 15:48:48 +0000 Subject: Add a reasonable ML-KEM API for public use. Adapt the tests to use this API. This does not yet make the symbols public in Symbols.list which will happen shortly with a bump. This includes some partial rototilling of the non-public interfaces which will be shortly continued when the internal code is deduplicated to not have multiple copies for ML-KEM 768 and ML-KEM 1024 (which is just an artifact of unravelling the boring C++ code). ok jsing@, tb@ --- src/lib/libcrypto/Makefile | 5 +- src/lib/libcrypto/hidden/openssl/mlkem.h | 40 +- src/lib/libcrypto/mlkem/mlkem.c | 638 +++++++++++++++++++++++++++++++ src/lib/libcrypto/mlkem/mlkem.h | 329 +++++++--------- src/lib/libcrypto/mlkem/mlkem1024.c | 71 ++-- src/lib/libcrypto/mlkem/mlkem768.c | 73 ++-- src/lib/libcrypto/mlkem/mlkem_internal.h | 331 +++++++++++++++- src/lib/libcrypto/mlkem/mlkem_key.c | 200 ++++++++++ 8 files changed, 1385 insertions(+), 302 deletions(-) create mode 100644 src/lib/libcrypto/mlkem/mlkem.c create mode 100644 src/lib/libcrypto/mlkem/mlkem_key.c (limited to 'src/lib/libcrypto') diff --git a/src/lib/libcrypto/Makefile b/src/lib/libcrypto/Makefile index b0ab507983..459b0c9235 100644 --- a/src/lib/libcrypto/Makefile +++ b/src/lib/libcrypto/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.241 2025/07/16 15:59:26 tb Exp $ +# $OpenBSD: Makefile,v 1.242 2025/08/14 15:48:48 beck Exp $ LIB= crypto LIBREBUILD=y @@ -374,8 +374,10 @@ SRCS+= md4.c SRCS+= md5.c # mlkem/ +SRCS+= mlkem.c SRCS+= mlkem768.c SRCS+= mlkem1024.c +SRCS+= mlkem_key.c # modes/ SRCS+= cbc128.c @@ -668,6 +670,7 @@ HDRS=\ ${LCRYPTO_SRC}/lhash/lhash.h \ ${LCRYPTO_SRC}/md4/md4.h \ ${LCRYPTO_SRC}/md5/md5.h \ + ${LCRYPTO_SRC}/mlkem/mlkem.h \ ${LCRYPTO_SRC}/modes/modes.h \ ${LCRYPTO_SRC}/objects/objects.h \ ${LCRYPTO_SRC}/ocsp/ocsp.h \ diff --git a/src/lib/libcrypto/hidden/openssl/mlkem.h b/src/lib/libcrypto/hidden/openssl/mlkem.h index 8cd80eb3af..3807b3fa1e 100644 --- a/src/lib/libcrypto/hidden/openssl/mlkem.h +++ b/src/lib/libcrypto/hidden/openssl/mlkem.h @@ -1,6 +1,6 @@ -/* $OpenBSD: mlkem.h,v 1.4 2024/12/20 15:10:31 tb Exp $ */ +/* $OpenBSD: mlkem.h,v 1.5 2025/08/14 15:48:48 beck Exp $ */ /* - * Copyright (c) 2024 Bob Beck + * Copyright (c) 2025 Bob Beck * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,9 +18,6 @@ #ifndef _LIBCRYPTO_MLKEM_H #define _LIBCRYPTO_MLKEM_H -/* Undo when making public */ -#ifdef LIBRESSL_HAS_MLKEM - #ifndef _MSC_VER #include_next #else @@ -28,22 +25,21 @@ #endif #include "crypto_namespace.h" -LCRYPTO_USED(MLKEM768_generate_key); -LCRYPTO_USED(MLKEM768_public_from_private); -LCRYPTO_USED(MLKEM768_encap); -LCRYPTO_USED(MLKEM768_decap); -LCRYPTO_USED(MLKEM768_marshal_public_key); -LCRYPTO_USED(MLKEM768_parse_public_key); -LCRYPTO_USED(MLKEM768_private_key_from_seed); -LCRYPTO_USED(MLKEM768_parse_private_key); -LCRYPTO_USED(MLKEM1024_generate_key); -LCRYPTO_USED(MLKEM1024_public_from_private); -LCRYPTO_USED(MLKEM1024_encap); -LCRYPTO_USED(MLKEM1024_decap); -LCRYPTO_USED(MLKEM1024_marshal_public_key); -LCRYPTO_USED(MLKEM1024_parse_public_key); -LCRYPTO_USED(MLKEM1024_private_key_from_seed); -LCRYPTO_USED(MLKEM1024_parse_private_key); -#endif /* LIBRESSL_HAS_MLKEM */ +LCRYPTO_USED(MLKEM_private_key_new); +LCRYPTO_USED(MLKEM_private_key_free); +LCRYPTO_USED(MLKEM_private_key_ciphertext_length); +LCRYPTO_USED(MLKEM_private_key_encoded_length); +LCRYPTO_USED(MLKEM_public_key_new); +LCRYPTO_USED(MLKEM_public_key_free); +LCRYPTO_USED(MLKEM_public_key_ciphertext_length); +LCRYPTO_USED(MLKEM_public_key_encoded_length); +LCRYPTO_USED(MLKEM_generate_key); +LCRYPTO_USED(MLKEM_private_key_from_seed); +LCRYPTO_USED(MLKEM_public_from_private); +LCRYPTO_USED(MLKEM_encap); +LCRYPTO_USED(MLKEM_decap); +LCRYPTO_USED(MLKEM_marshal_public_key); +LCRYPTO_USED(MLKEM_parse_public_key); +LCRYPTO_USED(MLKEM_parse_private_key); #endif /* _LIBCRYPTO_MLKEM_H */ diff --git a/src/lib/libcrypto/mlkem/mlkem.c b/src/lib/libcrypto/mlkem/mlkem.c new file mode 100644 index 0000000000..3202656dfd --- /dev/null +++ b/src/lib/libcrypto/mlkem/mlkem.c @@ -0,0 +1,638 @@ +/* $OpenBSD: mlkem.c,v 1.1 2025/08/14 15:48:48 beck Exp $ */ +/* + * Copyright (c) 2025, Bob Beck + * + * 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 + +#include +#include "mlkem_internal.h" + +static inline int +private_key_is_new(const MLKEM_private_key *key) +{ + return (key != NULL && + key->state == MLKEM_PRIVATE_KEY_UNINITIALIZED && + (key->rank == RANK768 || key->rank == RANK1024)); +} + +static inline int +private_key_is_valid(const MLKEM_private_key *key) +{ + return (key != NULL && + key->state == MLKEM_PRIVATE_KEY_INITIALIZED && + (key->rank == RANK768 || key->rank == RANK1024)); +} + +static inline int +public_key_is_new(const MLKEM_public_key *key) +{ + return (key != NULL && + key->state == MLKEM_PUBLIC_KEY_UNINITIALIZED && + (key->rank == RANK768 || key->rank == RANK1024)); +} + +static inline int +public_key_is_valid(const MLKEM_public_key *key) +{ + return (key != NULL && + key->state == MLKEM_PUBLIC_KEY_INITIALIZED && + (key->rank == RANK768 || key->rank == RANK1024)); +} + +/* + * ML-KEM operations + */ + +int +MLKEM_generate_key_external_entropy(MLKEM_private_key *private_key, + uint8_t **out_encoded_public_key, size_t *out_encoded_public_key_len, + const uint8_t *entropy) +{ + uint8_t *k = NULL; + size_t k_len = 0; + int ret = 0; + + if (*out_encoded_public_key != NULL) + goto err; + + if (!private_key_is_new(private_key)) + goto err; + + k_len = MLKEM768_PUBLIC_KEY_BYTES; + if (private_key->rank == RANK1024) + k_len = MLKEM1024_PUBLIC_KEY_BYTES; + + if ((k = calloc(1, k_len)) == NULL) + goto err; + + switch (private_key->rank) { + case RANK768: + if (!MLKEM768_generate_key_external_entropy(k, private_key, + entropy)) + goto err; + break; + case RANK1024: + if (!MLKEM1024_generate_key_external_entropy(k, private_key, + entropy)) + goto err; + break; + } + + private_key->state = MLKEM_PRIVATE_KEY_INITIALIZED; + + *out_encoded_public_key = k; + *out_encoded_public_key_len = k_len; + k = NULL; + + ret = 1; + + err: + freezero(k, k_len); + + return ret; +} + +int +MLKEM_generate_key(MLKEM_private_key *private_key, + uint8_t **out_encoded_public_key, size_t *out_encoded_public_key_len, + uint8_t **out_optional_seed, size_t *out_optional_seed_len) +{ + uint8_t *entropy_buf = NULL; + int ret = 0; + + if (*out_encoded_public_key != NULL) + goto err; + + if (out_optional_seed != NULL && *out_optional_seed != NULL) + goto err; + + if ((entropy_buf = calloc(1, MLKEM_SEED_LENGTH)) == NULL) + goto err; + + arc4random_buf(entropy_buf, MLKEM_SEED_LENGTH); + if (!MLKEM_generate_key_external_entropy(private_key, + out_encoded_public_key, out_encoded_public_key_len, + entropy_buf)) + goto err; + + if (out_optional_seed != NULL) { + *out_optional_seed = entropy_buf; + *out_optional_seed_len = MLKEM_SEED_LENGTH; + entropy_buf = NULL; + } + + ret = 1; + + err: + freezero(entropy_buf, MLKEM_SEED_LENGTH); + + return ret; +} +LCRYPTO_ALIAS(MLKEM_generate_key); + +int +MLKEM_private_key_from_seed(MLKEM_private_key *private_key, + const uint8_t *seed, size_t seed_len) +{ + int ret = 0; + + if (!private_key_is_new(private_key)) + goto err; + + if (seed_len != MLKEM_SEED_LENGTH) + goto err; + + switch (private_key->rank) { + case RANK768: + if (!MLKEM768_private_key_from_seed(seed, + seed_len, private_key)) + goto err; + break; + case RANK1024: + if (!MLKEM1024_private_key_from_seed(private_key, + seed, seed_len)) + goto err; + break; + } + + private_key->state = MLKEM_PRIVATE_KEY_INITIALIZED; + + ret = 1; + + err: + + return ret; +} +LCRYPTO_ALIAS(MLKEM_private_key_from_seed); + +int +MLKEM_public_from_private(const MLKEM_private_key *private_key, + MLKEM_public_key *public_key) +{ + if (!private_key_is_valid(private_key)) + return 0; + if (!public_key_is_new(public_key)) + return 0; + if (public_key->rank != private_key->rank) + return 0; + switch (private_key->rank) { + case RANK768: + MLKEM768_public_from_private(private_key, public_key); + break; + case RANK1024: + MLKEM1024_public_from_private(private_key, public_key); + break; + } + + public_key->state = MLKEM_PUBLIC_KEY_INITIALIZED; + + return 1; +} +LCRYPTO_ALIAS(MLKEM_public_from_private); + +int +MLKEM_encap_external_entropy(const MLKEM_public_key *public_key, + const uint8_t *entropy, uint8_t **out_ciphertext, + size_t *out_ciphertext_len, uint8_t **out_shared_secret, + size_t *out_shared_secret_len) +{ + uint8_t *secret = NULL; + uint8_t *ciphertext = NULL; + size_t ciphertext_len = 0; + int ret = 0; + + if (*out_ciphertext != NULL) + goto err; + + if (*out_shared_secret != NULL) + goto err; + + if (!public_key_is_valid(public_key)) + goto err; + + if ((secret = calloc(1, MLKEM_SHARED_SECRET_LENGTH)) == NULL) + goto err; + + ciphertext_len = MLKEM_public_key_ciphertext_length(public_key); + + if ((ciphertext = calloc(1, ciphertext_len)) == NULL) + goto err; + + switch (public_key->rank) { + case RANK768: + MLKEM768_encap_external_entropy(ciphertext, secret, public_key, + entropy); + break; + + case RANK1024: + MLKEM1024_encap_external_entropy(ciphertext, secret, public_key, + entropy); + break; + } + *out_ciphertext = ciphertext; + *out_ciphertext_len = ciphertext_len; + ciphertext = NULL; + *out_shared_secret = secret; + *out_shared_secret_len = MLKEM_SHARED_SECRET_LENGTH; + secret = NULL; + + ret = 1; + + err: + freezero(secret, MLKEM_SHARED_SECRET_LENGTH); + freezero(ciphertext, ciphertext_len); + + return ret; +} + +int +MLKEM_encap(const MLKEM_public_key *public_key, + uint8_t **out_ciphertext, size_t *out_ciphertext_len, + uint8_t **out_shared_secret, size_t *out_shared_secret_len) +{ + uint8_t entropy[MLKEM_ENCAP_ENTROPY]; + + arc4random_buf(entropy, MLKEM_ENCAP_ENTROPY); + + return MLKEM_encap_external_entropy(public_key, entropy, out_ciphertext, + out_ciphertext_len, out_shared_secret, out_shared_secret_len); +} +LCRYPTO_ALIAS(MLKEM_encap); + +int +MLKEM_decap(const MLKEM_private_key *private_key, + const uint8_t *ciphertext, size_t ciphertext_len, + uint8_t **out_shared_secret, size_t *out_shared_secret_len) +{ + uint8_t *s = NULL; + int ret = 0; + + if (*out_shared_secret != NULL) + goto err; + + if (!private_key_is_valid(private_key)) + goto err; + + if (ciphertext_len != MLKEM_private_key_ciphertext_length(private_key)) + goto err; + + if ((s = calloc(1, MLKEM_SHARED_SECRET_LENGTH)) == NULL) + goto err; + + switch (private_key->rank) { + case RANK768: + MLKEM768_decap(private_key, ciphertext, ciphertext_len, s); + break; + + case RANK1024: + MLKEM1024_decap(private_key, ciphertext, ciphertext_len, s); + break; + } + + *out_shared_secret = s; + *out_shared_secret_len = MLKEM_SHARED_SECRET_LENGTH; + s = NULL; + + ret = 1; + + err: + freezero(s, MLKEM_SHARED_SECRET_LENGTH); + + return ret; +} +LCRYPTO_ALIAS(MLKEM_decap); + +int +MLKEM_marshal_public_key(const MLKEM_public_key *public_key, uint8_t **out, + size_t *out_len) +{ + if (*out != NULL) + return 0; + + if (!public_key_is_valid(public_key)) + return 0; + + switch (public_key->rank) { + case RANK768: + return MLKEM768_marshal_public_key(public_key, out, out_len); + case RANK1024: + return MLKEM1024_marshal_public_key(public_key, out, out_len); + default: + return 0; + } +} +LCRYPTO_ALIAS(MLKEM_marshal_public_key); + +/* + * Not exposed publicly, becuase the NIST private key format is gigantisch, and + * seeds should be used instead. Used for the NIST tests. + */ +int +MLKEM_marshal_private_key(const MLKEM_private_key *private_key, uint8_t **out, + size_t *out_len) +{ + if (*out != NULL) + return 0; + + if (!private_key_is_valid(private_key)) + return 0; + + switch (private_key->rank) { + case RANK768: + return MLKEM768_marshal_private_key(private_key, out, out_len); + case RANK1024: + return MLKEM1024_marshal_private_key(private_key, out, out_len); + default: + return 0; + } +} + +int +MLKEM_parse_public_key(MLKEM_public_key *public_key, const uint8_t *in, + size_t in_len) +{ + if (!public_key_is_new(public_key)) + return 0; + + if (in_len != MLKEM_public_key_encoded_length(public_key)) + return 0; + + switch (public_key->rank) { + case RANK768: + if (!MLKEM768_parse_public_key(in, in_len, + public_key)) + return 0; + break; + case RANK1024: + if (!MLKEM1024_parse_public_key(in, in_len, + public_key)) + return 0; + break; + } + + public_key->state = MLKEM_PUBLIC_KEY_INITIALIZED; + + return 1; +} +LCRYPTO_ALIAS(MLKEM_parse_public_key); + +int +MLKEM_parse_private_key(MLKEM_private_key *private_key, const uint8_t *in, + size_t in_len) +{ + if (!private_key_is_new(private_key)) + return 0; + + if (in_len != MLKEM_private_key_encoded_length(private_key)) + return 0; + + switch (private_key->rank) { + case RANK768: + if (!MLKEM768_parse_private_key(in, in_len, private_key)) + return 0; + break; + case RANK1024: + if (!MLKEM1024_parse_private_key(in, in_len, private_key)) + return 0; + break; + } + + private_key->state = MLKEM_PRIVATE_KEY_INITIALIZED; + + return 1; +} +LCRYPTO_ALIAS(MLKEM_parse_private_key); +/* $OpenBSD: mlkem.c,v 1.1 2025/08/14 15:48:48 beck Exp $ */ +/* + * Copyright (c) 2025, Bob Beck + * + * 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 + + +int MLKEM768_generate_key( + uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], + uint8_t optional_out_seed[MLKEM_SEED_BYTES], + struct MLKEM768_private_key *out_private_key); + +/* + * MLKEM768_private_key_from_seed derives a private key from a seed that was + * generated by |MLKEM768_generate_key|. It fails and returns 0 if |seed_len| is + * incorrect, otherwise it writes |*out_private_key| and returns 1. + */ +int MLKEM768_private_key_from_seed(struct MLKEM768_private_key *out_private_key, + const uint8_t *seed, size_t seed_len); + +/* + * MLKEM_public_from_private sets |*out_public_key| to the public key that + * corresponds to |private_key|. (This is faster than parsing the output of + * |MLKEM_generate_key| if, for some reason, you need to encapsulate to a key + * that was just generated.) + */ +void MLKEM768_public_from_private(struct MLKEM768_public_key *out_public_key, + const struct MLKEM768_private_key *private_key); + +/* MLKEM768_CIPHERTEXT_BYTES is number of bytes in the ML-KEM768 ciphertext. */ +#define MLKEM768_CIPHERTEXT_BYTES 1088 + +/* + * MLKEM768_encap encrypts a random shared secret for |public_key|, writes the + * ciphertext to |out_ciphertext|, and writes the random shared secret to + * |out_shared_secret|. + */ +void MLKEM768_encap(uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], + const struct MLKEM768_public_key *public_key); + +/* + * MLKEM768_decap decrypts a shared secret from |ciphertext| using |private_key| + * and writes it to |out_shared_secret|. If |ciphertext_len| is incorrect it + * returns 0, otherwise it rreturns 1. If |ciphertext| is invalid, + * |out_shared_secret| is filled with a key that will always be the same for the + * same |ciphertext| and |private_key|, but which appears to be random unless + * one has access to |private_key|. These alternatives occur in constant time. + * Any subsequent symmetric encryption using |out_shared_secret| must use an + * authenticated encryption scheme in order to discover the decapsulation + * failure. + */ +int MLKEM768_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], + const uint8_t *ciphertext, size_t ciphertext_len, + const struct MLKEM768_private_key *private_key); + +/* Serialisation of keys. */ + +/* + * MLKEM768_marshal_public_key serializes |public_key| to |out| in the standard + * format for ML-KEM public keys. It returns one on success or zero on allocation + * error. + */ +int MLKEM768_marshal_public_key(uint8_t **output, size_t *output_len, + const struct MLKEM768_public_key *public_key); + +/* + * MLKEM768_parse_public_key parses a public key, in the format generated by + * |MLKEM_marshal_public_key|, from |in| and writes the result to + * |out_public_key|. It returns one on success or zero on parse error or if + * there are trailing bytes in |in|. + */ +int MLKEM768_parse_public_key(struct MLKEM768_public_key *out_public_key, + const uint8_t *input, size_t input_len); + +/* + * MLKEM_parse_private_key parses a private key, in the format generated by + * |MLKEM_marshal_private_key|, from |in| and writes the result to + * |out_private_key|. It returns one on success or zero on parse error or if + * there are trailing bytes in |in|. This formate is verbose and should be avoided. + * Private keys should be stored as seeds and parsed using |MLKEM768_private_key_from_seed|. + */ +int MLKEM768_parse_private_key(struct MLKEM768_private_key *out_private_key, + const uint8_t *input, size_t input_len); + +/* + * ML-KEM-1024 + * + * ML-KEM-1024 also exists. You should prefer ML-KEM-768 where possible. + */ + +/* + * MLKEM1024_public_key contains an ML-KEM-1024 public key. The contents of this + * object should never leave the address space since the format is unstable. + */ +struct MLKEM1024_public_key { + union { + uint8_t bytes[512 * (4 + 16) + 32 + 32]; + uint16_t alignment; + } opaque; +}; + +/* + * MLKEM1024_private_key contains a ML-KEM-1024 private key. The contents of + * this object should never leave the address space since the format is + * unstable. + */ +struct MLKEM1024_private_key { + union { + uint8_t bytes[512 * (4 + 4 + 16) + 32 + 32 + 32]; + uint16_t alignment; + } opaque; +}; + +/* + * MLKEM1024_PUBLIC_KEY_BYTES is the number of bytes in an encoded ML-KEM-1024 + * public key. + */ +#define MLKEM1024_PUBLIC_KEY_BYTES 1568 + +/* + * MLKEM1024_generate_key generates a random public/private key pair, writes the + * encoded public key to |out_encoded_public_key| and sets |out_private_key| to + * the private key. If |optional_out_seed| is not NULL then the seed used to + * generate the private key is written to it. + */ +int MLKEM1024_generate_key( + uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES], + uint8_t optional_out_seed[MLKEM_SEED_BYTES], + struct MLKEM1024_private_key *out_private_key); + +/* + * MLKEM1024_private_key_from_seed derives a private key from a seed that was + * generated by |MLKEM1024_generate_key|. It fails and returns 0 if |seed_len| + * is incorrect, otherwise it writes |*out_private_key| and returns 1. + */ +int MLKEM1024_private_key_from_seed( + struct MLKEM1024_private_key *out_private_key, const uint8_t *seed, + size_t seed_len); + +/* + * MLKEM1024_public_from_private sets |*out_public_key| to the public key that + * corresponds to |private_key|. (This is faster than parsing the output of + * |MLKEM1024_generate_key| if, for some reason, you need to encapsulate to a + * key that was just generated.) + */ +void MLKEM1024_public_from_private(struct MLKEM1024_public_key *out_public_key, + const struct MLKEM1024_private_key *private_key); + +/* MLKEM1024_CIPHERTEXT_BYTES is number of bytes in the ML-KEM-1024 ciphertext. */ +#define MLKEM1024_CIPHERTEXT_BYTES 1568 + +/* + * MLKEM1024_encap encrypts a random shared secret for |public_key|, writes the + * ciphertext to |out_ciphertext|, and writes the random shared secret to + * |out_shared_secret|. + */ +void MLKEM1024_encap(uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], + const struct MLKEM1024_public_key *public_key); + +/* + * MLKEM1024_decap decrypts a shared secret from |ciphertext| using + * |private_key| and writes it to |out_shared_secret|. If |ciphertext_len| is + * incorrect it returns 0, otherwise it returns 1. If |ciphertext| is invalid + * (but of the correct length), |out_shared_secret| is filled with a key that + * will always be the same for the same |ciphertext| and |private_key|, but + * which appears to be random unless one has access to |private_key|. These + * alternatives occur in constant time. Any subsequent symmetric encryption + * using |out_shared_secret| must use an authenticated encryption scheme in + * order to discover the decapsulation failure. + */ +int MLKEM1024_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], + const uint8_t *ciphertext, size_t ciphertext_len, + const struct MLKEM1024_private_key *private_key); + +/* + * Serialisation of ML-KEM-1024 keys. + * MLKEM1024_marshal_public_key serializes |public_key| to |out| in the standard + * format for ML-KEM-1024 public keys. It returns one on success or zero on + * allocation error. + */ +int MLKEM1024_marshal_public_key(uint8_t **output, size_t *output_len, + const struct MLKEM1024_public_key *public_key); + +/* + * MLKEM1024_parse_public_key parses a public key, in the format generated by + * |MLKEM1024_marshal_public_key|, from |in| and writes the result to + * |out_public_key|. It returns one on success or zero on parse error or if + * there are trailing bytes in |in|. + */ +int MLKEM1024_parse_public_key(struct MLKEM1024_public_key *out_public_key, + const uint8_t *input, size_t input_len); + +/* + * MLKEM1024_parse_private_key parses a private key, in NIST's format for + * private keys, from |in| and writes the result to |out_private_key|. It + * returns one on success or zero on parse error or if there are trailing bytes + * in |in|. This format is verbose and should be avoided. Private keys should be + * stored as seeds and parsed using |MLKEM1024_private_key_from_seed|. + */ +int MLKEM1024_parse_private_key(struct MLKEM1024_private_key *out_private_key, + const uint8_t *input, size_t input_len); + +#if defined(__cplusplus) +} +#endif + +#endif /* OPENSSL_HEADER_MLKEM_H */ diff --git a/src/lib/libcrypto/mlkem/mlkem.h b/src/lib/libcrypto/mlkem/mlkem.h index a2c5d7fed0..31d4858195 100644 --- a/src/lib/libcrypto/mlkem/mlkem.h +++ b/src/lib/libcrypto/mlkem/mlkem.h @@ -1,6 +1,6 @@ -/* $OpenBSD: mlkem.h,v 1.6 2025/05/19 06:47:40 beck Exp $ */ +/* $OpenBSD: mlkem.h,v 1.7 2025/08/14 15:48:48 beck Exp $ */ /* - * Copyright (c) 2024, Google Inc. + * Copyright (c) 2025 Bob Beck * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -26,253 +26,202 @@ extern "C" { #endif /* - * ML-KEM-768 - * - * This implements the Module-Lattice-Based Key-Encapsulation Mechanism from - * https://csrc.nist.gov/pubs/fips/204/final + * ML-KEM constants */ -/* - * MLKEM768_public_key contains a ML-KEM-768 public key. The contents of this - * object should never leave the address space since the format is unstable. - */ -struct MLKEM768_public_key { - union { - uint8_t bytes[512 * (3 + 9) + 32 + 32]; - uint16_t alignment; - } opaque; -}; - -/* - * MLKEM768_private_key contains a ML-KEM-768 private key. The contents of this - * object should never leave the address space since the format is unstable. - */ -struct MLKEM768_private_key { - union { - uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; - uint16_t alignment; - } opaque; -}; +#define RANK768 3 +#define RANK1024 4 /* - * MLKEM768_PUBLIC_KEY_BYTES is the number of bytes in an encoded ML-KEM768 public - * key. + * ML-KEM keys */ -#define MLKEM768_PUBLIC_KEY_BYTES 1184 -/* MLKEM_SEED_BYTES is the number of bytes in an ML-KEM seed. */ -#define MLKEM_SEED_BYTES 64 +typedef struct MLKEM_private_key_st MLKEM_private_key; +typedef struct MLKEM_public_key_st MLKEM_public_key; /* - * MLKEM_SHARED_SECRET_BYTES is the number of bytes in the ML-KEM768 shared - * secret. Although the round-3 specification has a variable-length output, the - * final ML-KEM construction is expected to use a fixed 32-byte output. To - * simplify the future transition, we apply the same restriction. + * MLKEM_private_key_new allocates a new uninitialized ML-KEM private key for + * |rank|, which must be RANK768 or RANK1024. It returns a pointer to an + * allocated structure suitable for holding a generated private key of the + * corresponding rank on success, NULL is returned on failure. The caller is + * responsible for deallocating the resulting key with |MLKEM_private_key_free|. */ -#define MLKEM_SHARED_SECRET_BYTES 32 +MLKEM_private_key *MLKEM_private_key_new(int rank); /* - * MLKEM_generate_key generates a random public/private key pair, writes the - * encoded public key to |out_encoded_public_key| and sets |out_private_key| to - * the private key. If |optional_out_seed| is not NULL then the seed used to - * generate the private key is written to it. + * MLKEM_private_key_free zeroes and frees all memory for |key| if |key| is + * non NULL. If |key| is NULL it does nothing and returns. */ -int MLKEM768_generate_key( - uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], - uint8_t optional_out_seed[MLKEM_SEED_BYTES], - struct MLKEM768_private_key *out_private_key); +void MLKEM_private_key_free(MLKEM_private_key *key); /* - * MLKEM768_private_key_from_seed derives a private key from a seed that was - * generated by |MLKEM768_generate_key|. It fails and returns 0 if |seed_len| is - * incorrect, otherwise it writes |*out_private_key| and returns 1. + * MLKEM_private_key_encoded_length the number of bytes used by the encoded form + * of |key|. Thie corresponds to the length of the buffer allocated for the + * encoded_public_key from |MLKEM_marshal_private_key|. Zero is returned if + * |key| is NULL or has an invalid rank. */ -int MLKEM768_private_key_from_seed(struct MLKEM768_private_key *out_private_key, - const uint8_t *seed, size_t seed_len); +size_t MLKEM_private_key_encoded_length(const MLKEM_private_key *key); /* - * MLKEM_public_from_private sets |*out_public_key| to the public key that - * corresponds to |private_key|. (This is faster than parsing the output of - * |MLKEM_generate_key| if, for some reason, you need to encapsulate to a key - * that was just generated.) + * MLKEM_private_key_ciphertext_length returns the number of bytes of ciphertext + * required to decrypt a shared secret with |key| using |MLKEM_decap|. Zero is + * returned if |key| is NULL or has an invalid rank. */ -void MLKEM768_public_from_private(struct MLKEM768_public_key *out_public_key, - const struct MLKEM768_private_key *private_key); - -/* MLKEM768_CIPHERTEXT_BYTES is number of bytes in the ML-KEM768 ciphertext. */ -#define MLKEM768_CIPHERTEXT_BYTES 1088 +size_t MLKEM_private_key_ciphertext_length(const MLKEM_private_key *key); /* - * MLKEM768_encap encrypts a random shared secret for |public_key|, writes the - * ciphertext to |out_ciphertext|, and writes the random shared secret to - * |out_shared_secret|. + * MLKEM_public_key_new allocates a new uninitialized ML-KEM public key for + * |rank|, which must be RANK768 or RANK1024. It returns a pointer to an + * allocated structure suitable for holding a generated public key of the + * corresponding rank on success, NULL is returned on failure. The caller is + * responsible for deallocating the resulting key with |MLKEM_public_key_free|. */ -void MLKEM768_encap(uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], - uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM768_public_key *public_key); +MLKEM_public_key *MLKEM_public_key_new(int rank); /* - * MLKEM768_decap decrypts a shared secret from |ciphertext| using |private_key| - * and writes it to |out_shared_secret|. If |ciphertext_len| is incorrect it - * returns 0, otherwise it rreturns 1. If |ciphertext| is invalid, - * |out_shared_secret| is filled with a key that will always be the same for the - * same |ciphertext| and |private_key|, but which appears to be random unless - * one has access to |private_key|. These alternatives occur in constant time. - * Any subsequent symmetric encryption using |out_shared_secret| must use an - * authenticated encryption scheme in order to discover the decapsulation - * failure. + * MLKEM_public_key_free zeros and deallocates all memory for |key| if |key| is + * non NULL. If |key| is NULL it does nothing and returns. */ -int MLKEM768_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const uint8_t *ciphertext, size_t ciphertext_len, - const struct MLKEM768_private_key *private_key); - -/* Serialisation of keys. */ +void MLKEM_public_key_free(MLKEM_public_key *key); /* - * MLKEM768_marshal_public_key serializes |public_key| to |out| in the standard - * format for ML-KEM public keys. It returns one on success or zero on allocation - * error. + * MLKEM_public_key_encoded_length the number of bytes used by the encoded form + * of |key|. Thie corresponds to the length of the buffer allocated for the + * encoded_public_key from |MLKEM_generate_key| or |MLKEM_marshal_public_key|. + * Zero is returned if |key| is NULL or has an invalid rank. */ -int MLKEM768_marshal_public_key(uint8_t **output, size_t *output_len, - const struct MLKEM768_public_key *public_key); +size_t MLKEM_public_key_encoded_length(const MLKEM_public_key *key); /* - * MLKEM768_parse_public_key parses a public key, in the format generated by - * |MLKEM_marshal_public_key|, from |in| and writes the result to - * |out_public_key|. It returns one on success or zero on parse error or if - * there are trailing bytes in |in|. + * MLKEM_public_key_cipertext_length returns the number of bytes produced as the + * ciphertext when encrypting a shared secret with |key| using |MLKEM_encap|. Zero + * is returned if |key| is NULL or has an invalid rank. */ -int MLKEM768_parse_public_key(struct MLKEM768_public_key *out_public_key, - const uint8_t *input, size_t input_len); +size_t MLKEM_public_key_ciphertext_length(const MLKEM_public_key *key); /* - * MLKEM_parse_private_key parses a private key, in the format generated by - * |MLKEM_marshal_private_key|, from |in| and writes the result to - * |out_private_key|. It returns one on success or zero on parse error or if - * there are trailing bytes in |in|. This formate is verbose and should be avoided. - * Private keys should be stored as seeds and parsed using |MLKEM768_private_key_from_seed|. + * ML-KEM operations */ -int MLKEM768_parse_private_key(struct MLKEM768_private_key *out_private_key, - const uint8_t *input, size_t input_len); /* - * ML-KEM-1024 + * MLKEM_generate_key generates a random private/public key pair, initializing + * |private_key|. It returns one on success, and zero on failure or error. + * |private_key| must be a new uninitialized key. |*out_encoded_public_key| and + * |*out_optional_seed|, if provided, must have the value of NULL. On success, a + * pointer to the encoded public key of the correct size for |key| is returned + * in |out_encoded_public_key|, and the length in bytes of + * |*out_encoded_public_key| is returned in |out_encoded_public_key_len|. If + * |out_optional_seed| is not NULL, a pointer to the seed used to generate the + * private key is returned in |*out_optional_seed| and the length in bytes of + * the seed is returned in |*out_optional_seed_len|. The caller is responsible + * for freeing the values returned in |out_encoded_public_key|, and + * |out_optional_seed|. * - * ML-KEM-1024 also exists. You should prefer ML-KEM-768 where possible. - */ - -/* - * MLKEM1024_public_key contains an ML-KEM-1024 public key. The contents of this - * object should never leave the address space since the format is unstable. - */ -struct MLKEM1024_public_key { - union { - uint8_t bytes[512 * (4 + 16) + 32 + 32]; - uint16_t alignment; - } opaque; -}; - -/* - * MLKEM1024_private_key contains a ML-KEM-1024 private key. The contents of - * this object should never leave the address space since the format is - * unstable. - */ -struct MLKEM1024_private_key { - union { - uint8_t bytes[512 * (4 + 4 + 16) + 32 + 32 + 32]; - uint16_t alignment; - } opaque; -}; - -/* - * MLKEM1024_PUBLIC_KEY_BYTES is the number of bytes in an encoded ML-KEM-1024 - * public key. + * In the event a private key needs to be saved, The normal best practice is to + * save |out_optional_seed| as the private key, along with the ML-KEM rank value. + * An MLKEM_private_key of the correct rank can then be constructed using + * |MLKEM_private_key_from_seed|. */ -#define MLKEM1024_PUBLIC_KEY_BYTES 1568 +int MLKEM_generate_key(MLKEM_private_key *private_key, + uint8_t **out_encoded_public_key, size_t *out_encoded_public_key_len, + uint8_t **out_optional_seed, size_t *out_optional_seed_len); /* - * MLKEM1024_generate_key generates a random public/private key pair, writes the - * encoded public key to |out_encoded_public_key| and sets |out_private_key| to - * the private key. If |optional_out_seed| is not NULL then the seed used to - * generate the private key is written to it. + * MLKEM_private_key_from_seed derives a private key from a seed that was + * generated by |MLKEM_generate_key| initializing |private_key|. It returns one + * on success, and zero on failure or error. |private_key| must be a new + * uninitialized key. |seed_len| must be MLKEM_SEED_LENGTH. + * + * For |private_key| to match the key generated by |MLKEM_generate_key|, + * |private_key| must have been created with the same rank as used when generating + * the key. */ -int MLKEM1024_generate_key( - uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES], - uint8_t optional_out_seed[MLKEM_SEED_BYTES], - struct MLKEM1024_private_key *out_private_key); +int MLKEM_private_key_from_seed(MLKEM_private_key *private_key, + const uint8_t *seed, size_t seed_len); /* - * MLKEM1024_private_key_from_seed derives a private key from a seed that was - * generated by |MLKEM1024_generate_key|. It fails and returns 0 if |seed_len| - * is incorrect, otherwise it writes |*out_private_key| and returns 1. + * MLKEM_public_from_private initializes |public_key| with the public key that + * corresponds to |private_key|. It returns one on success and zero on + * error. This is faster than parsing the output of |MLKEM_generate_key| if, for + * some reason, you need to encapsulate to a key that was just + * generated. |private key| must be a new uninitialized key, of the same rank as + * |public_key|. */ -int MLKEM1024_private_key_from_seed( - struct MLKEM1024_private_key *out_private_key, const uint8_t *seed, - size_t seed_len); +int MLKEM_public_from_private(const MLKEM_private_key *private_key, + MLKEM_public_key *public_key); /* - * MLKEM1024_public_from_private sets |*out_public_key| to the public key that - * corresponds to |private_key|. (This is faster than parsing the output of - * |MLKEM1024_generate_key| if, for some reason, you need to encapsulate to a - * key that was just generated.) + * MLKEM_encap encrypts a random shared secret for an initialized + * |public_key|. It returns one on success, and zero on failure or error. |*out + * ciphertext| and |*out_shared_secret| must have the value NULL. On success, a + * pointer to the ciphertext of the correct size for |key| is returned in + * |out_ciphertext|, the length in bytes of |*out_ciphertext| is returned in + * |*out_ciphertext_len|, a pointer to the random shared secret is returned in + * |out_shared_secret|, and the length in bytes of |*out_shared_secret| is + * returned in |*out_ciphtertext_len|. The caller is responsible for zeroing and + * freeing the values returned in |out_ciphertext| and |out_shared_secret| */ -void MLKEM1024_public_from_private(struct MLKEM1024_public_key *out_public_key, - const struct MLKEM1024_private_key *private_key); - -/* MLKEM1024_CIPHERTEXT_BYTES is number of bytes in the ML-KEM-1024 ciphertext. */ -#define MLKEM1024_CIPHERTEXT_BYTES 1568 +int MLKEM_encap(const MLKEM_public_key *public_key, + uint8_t **out_ciphertext, size_t *out_ciphertext_len, + uint8_t **out_shared_secret, size_t *out_shared_secret_len); /* - * MLKEM1024_encap encrypts a random shared secret for |public_key|, writes the - * ciphertext to |out_ciphertext|, and writes the random shared secret to - * |out_shared_secret|. - */ -void MLKEM1024_encap(uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], - uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM1024_public_key *public_key); - -/* - * MLKEM1024_decap decrypts a shared secret from |ciphertext| using - * |private_key| and writes it to |out_shared_secret|. If |ciphertext_len| is - * incorrect it returns 0, otherwise it returns 1. If |ciphertext| is invalid - * (but of the correct length), |out_shared_secret| is filled with a key that - * will always be the same for the same |ciphertext| and |private_key|, but - * which appears to be random unless one has access to |private_key|. These - * alternatives occur in constant time. Any subsequent symmetric encryption - * using |out_shared_secret| must use an authenticated encryption scheme in - * order to discover the decapsulation failure. + * MLKEM_decap decrypts a shared secret from |ciphertext| using an initialized + * |private_key|. It returns a pointer to the shared secret|out_shared_secret| + * and the length in bytes of |*out_shared_secret| in |*out_shared_secret_len|. + * + * If |ciphertext_len| is incorrect for |private_key|, |*out_shared_secret| is + * not NULL, or memory can not be allocated, it returns zero, otherwise it + * returns one. If |ciphertext| is invalid, a pointer is returned in + * |out_shared_secret| pointing to a key that will always be the same for the + * same |ciphertext| and |private_key|, but which appears to be random unless + * one has access to |private_key|. These alternatives occur in constant time. + * Any subsequent symmetric encryption using |out_shared_secret| must use an + * authenticated encryption scheme in order to discover the decapsulation + * failure. The caller is responsible for zeroing and freeing the value returned + * in |out_shared_secret|. */ -int MLKEM1024_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], +int MLKEM_decap(const MLKEM_private_key *private_key, const uint8_t *ciphertext, size_t ciphertext_len, - const struct MLKEM1024_private_key *private_key); + uint8_t **out_shared_secret, size_t *out_shared_secret_len); + +/* Serialization of ML-KEM keys. */ /* - * Serialisation of ML-KEM-1024 keys. - * MLKEM1024_marshal_public_key serializes |public_key| to |out| in the standard - * format for ML-KEM-1024 public keys. It returns one on success or zero on - * allocation error. + * MLKEM_marshal_public_key serializes an initialized |public_key| in the + * standard format for ML-KEM public keys. It returns one on success or zero on + * allocation error or failure. |*out| must have the value NULL. On success a + * pointer is returned in |out| to the encoded public key matching |public_key|, + * and a pointer to the length in bytes of the encoded public key is stored in + * |out_len|. The caller is responsible for freeing the values returned in + * |out|. */ -int MLKEM1024_marshal_public_key(uint8_t **output, size_t *output_len, - const struct MLKEM1024_public_key *public_key); +int MLKEM_marshal_public_key(const MLKEM_public_key *public_key, uint8_t **out, + size_t *out_len); /* - * MLKEM1024_parse_public_key parses a public key, in the format generated by - * |MLKEM1024_marshal_public_key|, from |in| and writes the result to - * |out_public_key|. It returns one on success or zero on parse error or if - * there are trailing bytes in |in|. + * MLKEM_parse_public_key parses a public key, in the format generated by + * |MLKEM_marshal_public_key|, from |in|. It returns one on success or zero on + * error or failure. |public_key| must be a new uninitialized key. |in_len| must + * be the correct length for the encoded format of |public_key. On success + * |public_key| is initialized to the value parsed from |in|. */ -int MLKEM1024_parse_public_key(struct MLKEM1024_public_key *out_public_key, - const uint8_t *input, size_t input_len); +int MLKEM_parse_public_key(MLKEM_public_key *public_key, const uint8_t *in, + size_t in_len); /* - * MLKEM1024_parse_private_key parses a private key, in NIST's format for - * private keys, from |in| and writes the result to |out_private_key|. It - * returns one on success or zero on parse error or if there are trailing bytes - * in |in|. This format is verbose and should be avoided. Private keys should be - * stored as seeds and parsed using |MLKEM1024_private_key_from_seed|. + * MLKEM_parse_private_key parses a private key, in the format generated by + * |MLKEM_marshal_private_key|, from |in|. It returns one on success or zero on + * error or failure. |private_key| must be a new uninitialized key. |in_len| + * must be the correct length for the encoded format of |private_key. On success + * |private_key| is initialized to the value parsed from |in|. + * + * This format is wastefully verbose and should be avoided. Private keys should + * be stored as seeds from |MLKEM_generate_key|, and then parsed using + * |MLKEM_private_key_from_seed|. */ -int MLKEM1024_parse_private_key(struct MLKEM1024_private_key *out_private_key, - const uint8_t *input, size_t input_len); +int MLKEM_parse_private_key(MLKEM_private_key *private_key, const uint8_t *in, + size_t in_len); #if defined(__cplusplus) } diff --git a/src/lib/libcrypto/mlkem/mlkem1024.c b/src/lib/libcrypto/mlkem/mlkem1024.c index 26c4716539..8f4f41f8ff 100644 --- a/src/lib/libcrypto/mlkem/mlkem1024.c +++ b/src/lib/libcrypto/mlkem/mlkem1024.c @@ -1,7 +1,7 @@ -/* $OpenBSD: mlkem1024.c,v 1.11 2025/05/21 02:18:11 kenjiro Exp $ */ +/* $OpenBSD: mlkem1024.c,v 1.12 2025/08/14 15:48:48 beck Exp $ */ /* * Copyright (c) 2024, Google Inc. - * Copyright (c) 2024, Bob Beck + * Copyright (c) 2024, 2025 Bob Beck * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,18 +20,14 @@ #include #include -#include "bytestring.h" -#include "mlkem.h" +#include +#include "bytestring.h" #include "sha3_internal.h" #include "mlkem_internal.h" #include "constant_time.h" #include "crypto_internal.h" -/* Remove later */ -#undef LCRYPTO_ALIAS -#define LCRYPTO_ALIAS(A) - /* * See * https://csrc.nist.gov/pubs/fips/203/final @@ -80,7 +76,6 @@ kdf(uint8_t out[MLKEM_SHARED_SECRET_BYTES], const uint8_t failure_secret[32], } #define DEGREE 256 -#define RANK1024 4 static const size_t kBarrettMultiplier = 5039; static const unsigned kBarrettShift = 24; @@ -809,9 +804,11 @@ struct public_key { CTASSERT(sizeof(struct MLKEM1024_public_key) == sizeof(struct public_key)); static struct public_key * -public_key_1024_from_external(const struct MLKEM1024_public_key *external) +public_key_1024_from_external(const MLKEM_public_key *external) { - return (struct public_key *)external; + if (external->rank != RANK1024) + return NULL; + return (struct public_key *)external->key_1024; } struct private_key { @@ -823,9 +820,11 @@ struct private_key { CTASSERT(sizeof(struct MLKEM1024_private_key) == sizeof(struct private_key)); static struct private_key * -private_key_1024_from_external(const struct MLKEM1024_private_key *external) +private_key_1024_from_external(const MLKEM_private_key *external) { - return (struct private_key *)external; + if (external->rank != RANK1024) + return NULL; + return (struct private_key *)external->key_1024; } /* @@ -835,7 +834,7 @@ private_key_1024_from_external(const struct MLKEM1024_private_key *external) int MLKEM1024_generate_key(uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES], uint8_t optional_out_seed[MLKEM_SEED_BYTES], - struct MLKEM1024_private_key *out_private_key) + MLKEM_private_key *out_private_key) { uint8_t entropy_buf[MLKEM_SEED_BYTES]; uint8_t *entropy = optional_out_seed != NULL ? optional_out_seed : @@ -845,10 +844,9 @@ MLKEM1024_generate_key(uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES return MLKEM1024_generate_key_external_entropy(out_encoded_public_key, out_private_key, entropy); } -LCRYPTO_ALIAS(MLKEM1024_generate_key); int -MLKEM1024_private_key_from_seed(struct MLKEM1024_private_key *out_private_key, +MLKEM1024_private_key_from_seed(MLKEM_private_key *out_private_key, const uint8_t *seed, size_t seed_len) { uint8_t public_key_bytes[MLKEM1024_PUBLIC_KEY_BYTES]; @@ -859,7 +857,6 @@ MLKEM1024_private_key_from_seed(struct MLKEM1024_private_key *out_private_key, return MLKEM1024_generate_key_external_entropy(public_key_bytes, out_private_key, seed); } -LCRYPTO_ALIAS(MLKEM1024_private_key_from_seed); static int mlkem_marshal_public_key(CBB *out, const struct public_key *pub) @@ -872,7 +869,7 @@ mlkem_marshal_public_key(CBB *out, const struct public_key *pub) int MLKEM1024_generate_key_external_entropy( uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES], - struct MLKEM1024_private_key *out_private_key, + MLKEM_private_key *out_private_key, const uint8_t entropy[MLKEM_SEED_BYTES]) { struct private_key *priv = private_key_1024_from_external( @@ -920,8 +917,8 @@ MLKEM1024_generate_key_external_entropy( } void -MLKEM1024_public_from_private(struct MLKEM1024_public_key *out_public_key, - const struct MLKEM1024_private_key *private_key) +MLKEM1024_public_from_private(const MLKEM_private_key *private_key, + MLKEM_public_key *out_public_key) { struct public_key *const pub = public_key_1024_from_external( out_public_key); @@ -930,7 +927,6 @@ MLKEM1024_public_from_private(struct MLKEM1024_public_key *out_public_key, *pub = priv->pub; } -LCRYPTO_ALIAS(MLKEM1024_public_from_private); /* * Encrypts a message with given randomness to the ciphertext in |out|. Without @@ -972,9 +968,9 @@ encrypt_cpa(uint8_t out[MLKEM1024_CIPHERTEXT_BYTES], /* Calls MLKEM1024_encap_external_entropy| with random bytes */ void -MLKEM1024_encap(uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], - uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM1024_public_key *public_key) +MLKEM1024_encap(const MLKEM_public_key *public_key, + uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]) { uint8_t entropy[MLKEM_ENCAP_ENTROPY]; @@ -982,14 +978,13 @@ MLKEM1024_encap(uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], MLKEM1024_encap_external_entropy(out_ciphertext, out_shared_secret, public_key, entropy); } -LCRYPTO_ALIAS(MLKEM1024_encap); /* See section 6.2 of the spec. */ void MLKEM1024_encap_external_entropy( uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM1024_public_key *public_key, + const MLKEM_public_key *public_key, const uint8_t entropy[MLKEM_ENCAP_ENTROPY]) { const struct public_key *pub = public_key_1024_from_external(public_key); @@ -1025,10 +1020,10 @@ decrypt_cpa(uint8_t out[32], const struct private_key *priv, /* See section 6.3 */ int -MLKEM1024_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], +MLKEM1024_decap(const MLKEM_private_key *private_key, const uint8_t *ciphertext, size_t ciphertext_len, - const struct MLKEM1024_private_key *private_key) -{ + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]) + { const struct private_key *priv = private_key_1024_from_external( private_key); uint8_t expected_ciphertext[MLKEM1024_CIPHERTEXT_BYTES]; @@ -1059,11 +1054,10 @@ MLKEM1024_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], return 1; } -LCRYPTO_ALIAS(MLKEM1024_decap); int -MLKEM1024_marshal_public_key(uint8_t **output, size_t *output_len, - const struct MLKEM1024_public_key *public_key) +MLKEM1024_marshal_public_key(const MLKEM_public_key *public_key, + uint8_t **output, size_t *output_len) { int ret = 0; CBB cbb; @@ -1083,7 +1077,6 @@ MLKEM1024_marshal_public_key(uint8_t **output, size_t *output_len, return ret; } -LCRYPTO_ALIAS(MLKEM1024_marshal_public_key); /* * mlkem_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate @@ -1107,8 +1100,8 @@ mlkem_parse_public_key_no_hash(struct public_key *pub, CBS *in) } int -MLKEM1024_parse_public_key(struct MLKEM1024_public_key *public_key, - const uint8_t *input, size_t input_len) +MLKEM1024_parse_public_key(const uint8_t *input, size_t input_len, + MLKEM_public_key *public_key) { struct public_key *pub = public_key_1024_from_external(public_key); CBS cbs; @@ -1123,10 +1116,9 @@ MLKEM1024_parse_public_key(struct MLKEM1024_public_key *public_key, return 1; } -LCRYPTO_ALIAS(MLKEM1024_parse_public_key); int -MLKEM1024_marshal_private_key(const struct MLKEM1024_private_key *private_key, +MLKEM1024_marshal_private_key(const MLKEM_private_key *private_key, uint8_t **out_private_key, size_t *out_private_key_len) { const struct private_key *const priv = private_key_1024_from_external( @@ -1160,8 +1152,8 @@ MLKEM1024_marshal_private_key(const struct MLKEM1024_private_key *private_key, } int -MLKEM1024_parse_private_key(struct MLKEM1024_private_key *out_private_key, - const uint8_t *input, size_t input_len) +MLKEM1024_parse_private_key(const uint8_t *input, size_t input_len, + MLKEM_private_key *out_private_key) { struct private_key *const priv = private_key_1024_from_external( out_private_key); @@ -1189,4 +1181,3 @@ MLKEM1024_parse_private_key(struct MLKEM1024_private_key *out_private_key, return 1; } -LCRYPTO_ALIAS(MLKEM1024_parse_private_key); diff --git a/src/lib/libcrypto/mlkem/mlkem768.c b/src/lib/libcrypto/mlkem/mlkem768.c index 653b92d8d8..1a44b9413f 100644 --- a/src/lib/libcrypto/mlkem/mlkem768.c +++ b/src/lib/libcrypto/mlkem/mlkem768.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mlkem768.c,v 1.12 2025/05/20 00:30:38 beck Exp $ */ +/* $OpenBSD: mlkem768.c,v 1.13 2025/08/14 15:48:48 beck Exp $ */ /* * Copyright (c) 2024, Google Inc. * Copyright (c) 2024, Bob Beck @@ -19,19 +19,16 @@ #include #include #include +#include -#include "bytestring.h" -#include "mlkem.h" +#include +#include "bytestring.h" #include "sha3_internal.h" #include "mlkem_internal.h" #include "constant_time.h" #include "crypto_internal.h" -/* Remove later */ -#undef LCRYPTO_ALIAS -#define LCRYPTO_ALIAS(A) - /* * See * https://csrc.nist.gov/pubs/fips/203/final @@ -80,7 +77,6 @@ kdf(uint8_t out[MLKEM_SHARED_SECRET_BYTES], const uint8_t failure_secret[32], } #define DEGREE 256 -#define RANK768 3 static const size_t kBarrettMultiplier = 5039; static const unsigned kBarrettShift = 24; @@ -809,9 +805,11 @@ struct public_key { CTASSERT(sizeof(struct MLKEM768_public_key) == sizeof(struct public_key)); static struct public_key * -public_key_768_from_external(const struct MLKEM768_public_key *external) +public_key_768_from_external(const MLKEM_public_key *external) { - return (struct public_key *)external; + if (external->rank != RANK768) + return NULL; + return (struct public_key *)external->key_768; } struct private_key { @@ -823,9 +821,11 @@ struct private_key { CTASSERT(sizeof(struct MLKEM768_private_key) == sizeof(struct private_key)); static struct private_key * -private_key_768_from_external(const struct MLKEM768_private_key *external) +private_key_768_from_external(const MLKEM_private_key *external) { - return (struct private_key *)external; + if (external->rank != RANK768) + return NULL; + return (struct private_key *)external->key_768; } /* @@ -835,7 +835,7 @@ private_key_768_from_external(const struct MLKEM768_private_key *external) int MLKEM768_generate_key(uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], uint8_t optional_out_seed[MLKEM_SEED_BYTES], - struct MLKEM768_private_key *out_private_key) + MLKEM_private_key *out_private_key) { uint8_t entropy_buf[MLKEM_SEED_BYTES]; uint8_t *entropy = optional_out_seed != NULL ? optional_out_seed : @@ -845,12 +845,12 @@ MLKEM768_generate_key(uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], return MLKEM768_generate_key_external_entropy(out_encoded_public_key, out_private_key, entropy); } -LCRYPTO_ALIAS(MLKEM768_generate_key); int -MLKEM768_private_key_from_seed(struct MLKEM768_private_key *out_private_key, - const uint8_t *seed, size_t seed_len) +MLKEM768_private_key_from_seed(const uint8_t *seed, size_t seed_len, + MLKEM_private_key *out_private_key) { + /* XXX stack */ uint8_t public_key_bytes[MLKEM768_PUBLIC_KEY_BYTES]; if (seed_len != MLKEM_SEED_BYTES) { @@ -859,7 +859,6 @@ MLKEM768_private_key_from_seed(struct MLKEM768_private_key *out_private_key, return MLKEM768_generate_key_external_entropy(public_key_bytes, out_private_key, seed); } -LCRYPTO_ALIAS(MLKEM768_private_key_from_seed); static int mlkem_marshal_public_key(CBB *out, const struct public_key *pub) @@ -872,7 +871,7 @@ mlkem_marshal_public_key(CBB *out, const struct public_key *pub) int MLKEM768_generate_key_external_entropy( uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], - struct MLKEM768_private_key *out_private_key, + MLKEM_private_key *out_private_key, const uint8_t entropy[MLKEM_SEED_BYTES]) { struct private_key *priv = private_key_768_from_external( @@ -920,9 +919,8 @@ MLKEM768_generate_key_external_entropy( } void -MLKEM768_public_from_private(struct MLKEM768_public_key *out_public_key, - const struct MLKEM768_private_key *private_key) -{ +MLKEM768_public_from_private(const MLKEM_private_key *private_key, + MLKEM_public_key *out_public_key) { struct public_key *const pub = public_key_768_from_external( out_public_key); const struct private_key *const priv = private_key_768_from_external( @@ -930,7 +928,6 @@ MLKEM768_public_from_private(struct MLKEM768_public_key *out_public_key, *pub = priv->pub; } -LCRYPTO_ALIAS(MLKEM768_public_from_private); /* * Encrypts a message with given randomness to the ciphertext in |out|. Without @@ -972,9 +969,9 @@ encrypt_cpa(uint8_t out[MLKEM768_CIPHERTEXT_BYTES], /* Calls MLKEM768_encap_external_entropy| with random bytes */ void -MLKEM768_encap(uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], - uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM768_public_key *public_key) +MLKEM768_encap(const MLKEM_public_key *public_key, + uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]) { uint8_t entropy[MLKEM_ENCAP_ENTROPY]; @@ -982,14 +979,13 @@ MLKEM768_encap(uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], MLKEM768_encap_external_entropy(out_ciphertext, out_shared_secret, public_key, entropy); } -LCRYPTO_ALIAS(MLKEM768_encap); /* See section 6.2 of the spec. */ void MLKEM768_encap_external_entropy( uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM768_public_key *public_key, + const MLKEM_public_key *public_key, const uint8_t entropy[MLKEM_ENCAP_ENTROPY]) { const struct public_key *pub = public_key_768_from_external(public_key); @@ -1025,9 +1021,8 @@ decrypt_cpa(uint8_t out[32], const struct private_key *priv, /* See section 6.3 */ int -MLKEM768_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const uint8_t *ciphertext, size_t ciphertext_len, - const struct MLKEM768_private_key *private_key) +MLKEM768_decap(const MLKEM_private_key *private_key, const uint8_t *ciphertext, + size_t ciphertext_len, uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]) { const struct private_key *priv = private_key_768_from_external( private_key); @@ -1059,11 +1054,10 @@ MLKEM768_decap(uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], return 1; } -LCRYPTO_ALIAS(MLKEM768_decap); int -MLKEM768_marshal_public_key(uint8_t **output, size_t *output_len, - const struct MLKEM768_public_key *public_key) +MLKEM768_marshal_public_key(const MLKEM_public_key *public_key, + uint8_t **output, size_t *output_len) { int ret = 0; CBB cbb; @@ -1083,7 +1077,6 @@ MLKEM768_marshal_public_key(uint8_t **output, size_t *output_len, return ret; } -LCRYPTO_ALIAS(MLKEM768_marshal_public_key); /* * mlkem_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate @@ -1107,8 +1100,8 @@ mlkem_parse_public_key_no_hash(struct public_key *pub, CBS *in) } int -MLKEM768_parse_public_key(struct MLKEM768_public_key *public_key, - const uint8_t *input, size_t input_len) +MLKEM768_parse_public_key(const uint8_t *input, size_t input_len, + MLKEM_public_key *public_key) { struct public_key *pub = public_key_768_from_external(public_key); CBS cbs; @@ -1123,10 +1116,9 @@ MLKEM768_parse_public_key(struct MLKEM768_public_key *public_key, return 1; } -LCRYPTO_ALIAS(MLKEM768_parse_public_key); int -MLKEM768_marshal_private_key(const struct MLKEM768_private_key *private_key, +MLKEM768_marshal_private_key(const MLKEM_private_key *private_key, uint8_t **out_private_key, size_t *out_private_key_len) { const struct private_key *const priv = private_key_768_from_external( @@ -1160,8 +1152,8 @@ MLKEM768_marshal_private_key(const struct MLKEM768_private_key *private_key, } int -MLKEM768_parse_private_key(struct MLKEM768_private_key *out_private_key, - const uint8_t *input, size_t input_len) +MLKEM768_parse_private_key(const uint8_t *input, size_t input_len, + MLKEM_private_key *out_private_key) { struct private_key *const priv = private_key_768_from_external( out_private_key); @@ -1189,4 +1181,3 @@ MLKEM768_parse_private_key(struct MLKEM768_private_key *out_private_key, return 1; } -LCRYPTO_ALIAS(MLKEM768_parse_private_key); diff --git a/src/lib/libcrypto/mlkem/mlkem_internal.h b/src/lib/libcrypto/mlkem/mlkem_internal.h index 1e051970a8..776f8aac17 100644 --- a/src/lib/libcrypto/mlkem/mlkem_internal.h +++ b/src/lib/libcrypto/mlkem/mlkem_internal.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mlkem_internal.h,v 1.7 2025/05/20 00:33:40 beck Exp $ */ +/* $OpenBSD: mlkem_internal.h,v 1.8 2025/08/14 15:48:48 beck Exp $ */ /* * Copyright (c) 2023, Google Inc. * @@ -26,6 +26,253 @@ extern "C" { #endif __BEGIN_HIDDEN_DECLS +/* + * MLKEM_SEED_LENGTH is the number of bytes in an ML-KEM seed. An ML-KEM + * seed is normally used to represent a private key. + */ +#define MLKEM_SEED_LENGTH 64 + +/* + * MLKEM_SHARED_SECRET_LENGTH is the number of bytes in an ML-KEM shared + * secret. + */ +#define MLKEM_SHARED_SECRET_LENGTH 32 + +/* + * |MLKEM_encap_external_entropy| behaves exactly like the public |MLKEM_encap| + * with the entropy provided by the caller. It is directly called internally + * and by tests. + */ +int +MLKEM_encap_external_entropy(const MLKEM_public_key *public_key, + const uint8_t *entropy, uint8_t **out_ciphertext, + size_t *out_ciphertext_len, uint8_t **out_shared_secret, + size_t *out_shared_secret_len); + +/* + * |MLKEM_generate_key_external_entropy| behaves exactly like the public + * |MLKEM_generate_key| with the entropy provided by the caller. + * It is directly called internally and by tests. + */ +int +MLKEM_generate_key_external_entropy(MLKEM_private_key *private_key, + uint8_t **out_encoded_public_key, size_t *out_encoded_public_key_len, + const uint8_t *entropy); +/* + * Marshals a private key to encoded format, used for NIST tests. + */ +int MLKEM_marshal_private_key(const MLKEM_private_key *private_key, + uint8_t **out, size_t *out_len); + +/* + * ML-KEM-768 + * + * This implements the Module-Lattice-Based Key-Encapsulation Mechanism from + * https://csrc.nist.gov/pubs/fips/204/final + */ + +/* + * MLKEM768_PUBLIC_KEY_BYTES is the number of bytes in an encoded ML-KEM768 public + * key. + */ +#define MLKEM768_PUBLIC_KEY_BYTES 1184 + +/* MLKEM_SEED_BYTES is the number of bytes in an ML-KEM seed. */ +#define MLKEM_SEED_BYTES 64 + +/* + * MLKEM_SHARED_SECRET_BYTES is the number of bytes in the ML-KEM768 shared + * secret. Although the round-3 specification has a variable-length output, the + * final ML-KEM construction is expected to use a fixed 32-byte output. To + * simplify the future transition, we apply the same restriction. + */ +#define MLKEM_SHARED_SECRET_BYTES 32 + +/* + * MLKEM_generate_key generates a random public/private key pair, writes the + * encoded public key to |out_encoded_public_key| and sets |out_private_key| to + * the private key. If |optional_out_seed| is not NULL then the seed used to + * generate the private key is written to it. + */ +int MLKEM768_generate_key( + uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], + uint8_t optional_out_seed[MLKEM_SEED_BYTES], + MLKEM_private_key *out_private_key); + +/* + * MLKEM768_private_key_from_seed derives a private key from a seed that was + * generated by |MLKEM768_generate_key|. It fails and returns 0 if |seed_len| is + * incorrect, otherwise it writes |*out_private_key| and returns 1. + */ +int MLKEM768_private_key_from_seed(const uint8_t *seed, size_t seed_len, + MLKEM_private_key *out_private_key); + +/* + * MLKEM_public_from_private sets |*out_public_key| to the public key that + * corresponds to |private_key|. (This is faster than parsing the output of + * |MLKEM_generate_key| if, for some reason, you need to encapsulate to a key + * that was just generated.) + */ +void MLKEM768_public_from_private(const MLKEM_private_key *private_key, + MLKEM_public_key *out_public_key); + +/* MLKEM768_CIPHERTEXT_BYTES is number of bytes in the ML-KEM768 ciphertext. */ +#define MLKEM768_CIPHERTEXT_BYTES 1088 + +/* + * MLKEM768_encap encrypts a random shared secret for |public_key|, writes the + * ciphertext to |out_ciphertext|, and writes the random shared secret to + * |out_shared_secret|. + */ +void MLKEM768_encap(const MLKEM_public_key *public_key, + uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]); + +/* + * MLKEM768_decap decrypts a shared secret from |ciphertext| using |private_key| + * and writes it to |out_shared_secret|. If |ciphertext_len| is incorrect it + * returns 0, otherwise it rreturns 1. If |ciphertext| is invalid, + * |out_shared_secret| is filled with a key that will always be the same for the + * same |ciphertext| and |private_key|, but which appears to be random unless + * one has access to |private_key|. These alternatives occur in constant time. + * Any subsequent symmetric encryption using |out_shared_secret| must use an + * authenticated encryption scheme in order to discover the decapsulation + * failure. + */ +int MLKEM768_decap(const MLKEM_private_key *private_key, + const uint8_t *ciphertext, size_t ciphertext_len, + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]); + +/* Serialisation of keys. */ + +/* + * MLKEM768_marshal_public_key serializes |public_key| to |out| in the standard + * format for ML-KEM public keys. It returns one on success or zero on allocation + * error. + */ +int MLKEM768_marshal_public_key(const MLKEM_public_key *public_key, + uint8_t **output, size_t *output_len); + +/* + * MLKEM768_parse_public_key parses a public key, in the format generated by + * |MLKEM_marshal_public_key|, from |in| and writes the result to + * |out_public_key|. It returns one on success or zero on parse error or if + * there are trailing bytes in |in|. + */ +int MLKEM768_parse_public_key(const uint8_t *input, size_t input_len, + MLKEM_public_key *out_public_key); + +/* + * MLKEM_parse_private_key parses a private key, in the format generated by + * |MLKEM_marshal_private_key|, from |in| and writes the result to + * |out_private_key|. It returns one on success or zero on parse error or if + * there are trailing bytes in |in|. This formate is verbose and should be avoided. + * Private keys should be stored as seeds and parsed using |MLKEM768_private_key_from_seed|. + */ +int MLKEM768_parse_private_key(const uint8_t *input, size_t input_len, + MLKEM_private_key *out_private_key); + +/* + * ML-KEM-1024 + * + * ML-KEM-1024 also exists. You should prefer ML-KEM-768 where possible. + */ + +/* + * MLKEM1024_PUBLIC_KEY_BYTES is the number of bytes in an encoded ML-KEM-1024 + * public key. + */ +#define MLKEM1024_PUBLIC_KEY_BYTES 1568 + +/* + * MLKEM1024_generate_key generates a random public/private key pair, writes the + * encoded public key to |out_encoded_public_key| and sets |out_private_key| to + * the private key. If |optional_out_seed| is not NULL then the seed used to + * generate the private key is written to it. + */ +int MLKEM1024_generate_key( + uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES], + uint8_t optional_out_seed[MLKEM_SEED_BYTES], + MLKEM_private_key *out_private_key); + +/* + * MLKEM1024_private_key_from_seed derives a private key from a seed that was + * generated by |MLKEM1024_generate_key|. It fails and returns 0 if |seed_len| + * is incorrect, otherwise it writes |*out_private_key| and returns 1. + */ +int MLKEM1024_private_key_from_seed( + MLKEM_private_key *out_private_key, const uint8_t *seed, + size_t seed_len); + +/* + * MLKEM1024_public_from_private sets |*out_public_key| to the public key that + * corresponds to |private_key|. (This is faster than parsing the output of + * |MLKEM1024_generate_key| if, for some reason, you need to encapsulate to a + * key that was just generated.) + */ +void MLKEM1024_public_from_private(const MLKEM_private_key *private_key, + MLKEM_public_key *out_public_key); + +/* MLKEM1024_CIPHERTEXT_BYTES is number of bytes in the ML-KEM-1024 ciphertext. */ +#define MLKEM1024_CIPHERTEXT_BYTES 1568 + +/* + * MLKEM1024_encap encrypts a random shared secret for |public_key|, writes the + * ciphertext to |out_ciphertext|, and writes the random shared secret to + * |out_shared_secret|. + */ +void MLKEM1024_encap(const MLKEM_public_key *public_key, + uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]); + + +/* + * MLKEM1024_decap decrypts a shared secret from |ciphertext| using + * |private_key| and writes it to |out_shared_secret|. If |ciphertext_len| is + * incorrect it returns 0, otherwise it returns 1. If |ciphertext| is invalid + * (but of the correct length), |out_shared_secret| is filled with a key that + * will always be the same for the same |ciphertext| and |private_key|, but + * which appears to be random unless one has access to |private_key|. These + * alternatives occur in constant time. Any subsequent symmetric encryption + * using |out_shared_secret| must use an authenticated encryption scheme in + * order to discover the decapsulation failure. + */ +int MLKEM1024_decap(const MLKEM_private_key *private_key, + const uint8_t *ciphertext, size_t ciphertext_len, + uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES]); + +/* + * Serialisation of ML-KEM-1024 keys. + * MLKEM1024_marshal_public_key serializes |public_key| to |out| in the standard + * format for ML-KEM-1024 public keys. It returns one on success or zero on + * allocation error. + */ +int MLKEM1024_marshal_public_key(const MLKEM_public_key *public_key, + uint8_t **output, size_t *output_len); + + +/* + * MLKEM1024_parse_public_key parses a public key, in the format generated by + * |MLKEM1024_marshal_public_key|, from |in| and writes the result to + * |out_public_key|. It returns one on success or zero on parse error or if + * there are trailing bytes in |in|. + */ +int MLKEM1024_parse_public_key(const uint8_t *input, size_t input_len, + MLKEM_public_key *out_public_key); + + +/* + * MLKEM1024_parse_private_key parses a private key, in NIST's format for + * private keys, from |in| and writes the result to |out_private_key|. It + * returns one on success or zero on parse error or if there are trailing bytes + * in |in|. This format is verbose and should be avoided. Private keys should be + * stored as seeds and parsed using |MLKEM1024_private_key_from_seed|. + */ +int MLKEM1024_parse_private_key(const uint8_t *input, size_t input_len, + MLKEM_private_key *out_private_key); + + +/* XXXX Truly internal stuff below, also in need of de-duping */ /* * MLKEM_ENCAP_ENTROPY is the number of bytes of uniformly random entropy @@ -34,6 +281,49 @@ __BEGIN_HIDDEN_DECLS */ #define MLKEM_ENCAP_ENTROPY 32 +/* + * MLKEM768_public_key contains a ML-KEM-768 public key. The contents of this + * object should never leave the address space since the format is unstable. + */ +struct MLKEM768_public_key { + union { + uint8_t bytes[512 * (3 + 9) + 32 + 32]; + uint16_t alignment; + } opaque; +}; + +/* + * MLKEM768_private_key contains a ML-KEM-768 private key. The contents of this + * object should never leave the address space since the format is unstable. + */ +struct MLKEM768_private_key { + union { + uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; + uint16_t alignment; + } opaque; +}; + +/* Public opaque ML-KEM key structures. */ + +#define MLKEM_PUBLIC_KEY_UNINITIALIZED 1 +#define MLKEM_PUBLIC_KEY_INITIALIZED 2 +#define MLKEM_PRIVATE_KEY_UNINITIALIZED 3 +#define MLKEM_PRIVATE_KEY_INITIALIZED 4 + +struct MLKEM_public_key_st { + uint16_t rank; + int state; + struct MLKEM768_public_key *key_768; + struct MLKEM1024_public_key *key_1024; +}; + +struct MLKEM_private_key_st { + uint16_t rank; + int state; + struct MLKEM768_private_key *key_768; + struct MLKEM1024_private_key *key_1024; +}; + /* * MLKEM768_generate_key_external_entropy is a deterministic function to create a * pair of ML-KEM 768 keys, using the supplied entropy. The entropy needs to be @@ -43,7 +333,7 @@ __BEGIN_HIDDEN_DECLS */ int MLKEM768_generate_key_external_entropy( uint8_t out_encoded_public_key[MLKEM768_PUBLIC_KEY_BYTES], - struct MLKEM768_private_key *out_private_key, + MLKEM_private_key *out_private_key, const uint8_t entropy[MLKEM_SEED_BYTES]); /* @@ -57,11 +347,11 @@ int MLKEM768_generate_key_external_entropy( * format for ML-KEM private keys. It returns one on success or zero on * allocation error. */ -int MLKEM768_marshal_private_key(const struct MLKEM768_private_key *private_key, +int MLKEM768_marshal_private_key(const MLKEM_private_key *private_key, uint8_t **out_private_key, size_t *out_private_key_len); /* - * MLKEM_encap_external_entropy behaves like |MLKEM_encap|, but uses + * MLKEM768_encap_external_entropy behaves like |MLKEM768_encap|, but uses * |MLKEM_ENCAP_ENTROPY| bytes of |entropy| for randomization. The decapsulating * side will be able to recover |entropy| in full. This function should only be * used for tests, regular callers should use the non-deterministic @@ -70,9 +360,34 @@ int MLKEM768_marshal_private_key(const struct MLKEM768_private_key *private_key, void MLKEM768_encap_external_entropy( uint8_t out_ciphertext[MLKEM768_CIPHERTEXT_BYTES], uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM768_public_key *public_key, + const MLKEM_public_key *public_key, const uint8_t entropy[MLKEM_ENCAP_ENTROPY]); + +/* + * MLKEM1024_public_key contains an ML-KEM-1024 public key. The contents of this + * object should never leave the address space since the format is unstable. + */ +struct MLKEM1024_public_key { + union { + uint8_t bytes[512 * (4 + 16) + 32 + 32]; + uint16_t alignment; + } opaque; +}; + +/* + * MLKEM1024_private_key contains a ML-KEM-1024 private key. The contents of + * this object should never leave the address space since the format is + * unstable. + */ +struct MLKEM1024_private_key { + union { + uint8_t bytes[512 * (4 + 4 + 16) + 32 + 32 + 32]; + uint16_t alignment; + } opaque; +}; + + /* * MLKEM1024_generate_key_external_entropy is a deterministic function to create a * pair of ML-KEM 1024 keys, using the supplied entropy. The entropy needs to be @@ -82,7 +397,7 @@ void MLKEM768_encap_external_entropy( */ int MLKEM1024_generate_key_external_entropy( uint8_t out_encoded_public_key[MLKEM1024_PUBLIC_KEY_BYTES], - struct MLKEM1024_private_key *out_private_key, + MLKEM_private_key *out_private_key, const uint8_t entropy[MLKEM_SEED_BYTES]); /* @@ -97,7 +412,7 @@ int MLKEM1024_generate_key_external_entropy( * allocation error. */ int MLKEM1024_marshal_private_key( - const struct MLKEM1024_private_key *private_key, uint8_t **out_private_key, + const MLKEM_private_key *private_key, uint8_t **out_private_key, size_t *out_private_key_len); /* @@ -110,7 +425,7 @@ int MLKEM1024_marshal_private_key( void MLKEM1024_encap_external_entropy( uint8_t out_ciphertext[MLKEM1024_CIPHERTEXT_BYTES], uint8_t out_shared_secret[MLKEM_SHARED_SECRET_BYTES], - const struct MLKEM1024_public_key *public_key, + const MLKEM_public_key *public_key, const uint8_t entropy[MLKEM_ENCAP_ENTROPY]); __END_HIDDEN_DECLS diff --git a/src/lib/libcrypto/mlkem/mlkem_key.c b/src/lib/libcrypto/mlkem/mlkem_key.c new file mode 100644 index 0000000000..051d8f2b88 --- /dev/null +++ b/src/lib/libcrypto/mlkem/mlkem_key.c @@ -0,0 +1,200 @@ +/* $OpenBSD: mlkem_key.c,v 1.1 2025/08/14 15:48:48 beck Exp $ */ +/* + * Copyright (c) 2025 Bob Beck + * + * Permission to use, copy, modify, and 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 +#include + +#include + +#include "mlkem_internal.h" + +MLKEM_private_key * +MLKEM_private_key_new(int rank) +{ + struct MLKEM1024_private_key *key_1024 = NULL; + struct MLKEM768_private_key *key_768 = NULL; + MLKEM_private_key *key = NULL; + MLKEM_private_key *ret = NULL; + + if ((key = calloc(1, sizeof(MLKEM_private_key))) == NULL) + goto err; + + switch (rank) { + case RANK768: + if ((key_768 = calloc(1, sizeof(*key_768))) == + NULL) + goto err; + key->key_768 = key_768; + break; + case RANK1024: + if ((key_1024 = calloc(1, sizeof(*key_1024))) == + NULL) + goto err; + key->key_1024 = key_1024; + break; + default: + goto err; + } + key->rank = rank; + key->state = MLKEM_PRIVATE_KEY_UNINITIALIZED; + + ret = key; + key= NULL; + + err: + MLKEM_private_key_free(key); + + return ret; +} +LCRYPTO_ALIAS(MLKEM_private_key_new); + +void +MLKEM_private_key_free(MLKEM_private_key *key) +{ + if (key == NULL) + return; + + freezero(key->key_768, sizeof(*key->key_768)); + freezero(key->key_1024, sizeof(*key->key_1024)); + freezero(key, sizeof(*key)); +} +LCRYPTO_ALIAS(MLKEM_private_key_free); + +size_t +MLKEM_private_key_encoded_length(const MLKEM_private_key *key) +{ + if (key == NULL) + return 0; + + switch (key->rank) { + case RANK768: + return MLKEM768_PRIVATE_KEY_BYTES; + case RANK1024: + return MLKEM1024_PRIVATE_KEY_BYTES; + default: + return 0; + } + return 0; +} +LCRYPTO_ALIAS(MLKEM_private_key_encoded_length); + +size_t +MLKEM_private_key_ciphertext_length(const MLKEM_private_key *key) +{ + if (key == NULL) + return 0; + + switch (key->rank) { + case RANK768: + return MLKEM768_CIPHERTEXT_BYTES; + case RANK1024: + return MLKEM1024_CIPHERTEXT_BYTES; + default: + return 0; + } + return 0; +} +LCRYPTO_ALIAS(MLKEM_private_key_ciphertext_length); + +MLKEM_public_key * +MLKEM_public_key_new(int rank) +{ + struct MLKEM1024_public_key *key_1024 = NULL; + struct MLKEM768_public_key *key_768 = NULL; + MLKEM_public_key *key = NULL; + MLKEM_public_key *ret = NULL; + + if ((key = calloc(1, sizeof(MLKEM_public_key))) == NULL) + goto err; + + switch (rank) { + case RANK768: + if ((key_768 = calloc(1, sizeof(*key_768))) == + NULL) + goto err; + key->key_768 = key_768; + break; + case RANK1024: + if ((key_1024 = calloc(1, sizeof(*key_1024))) == + NULL) + goto err; + key->key_1024 = key_1024; + break; + default: + goto err; + } + + key->rank = rank; + key->state = MLKEM_PUBLIC_KEY_UNINITIALIZED; + + ret = key; + key = NULL; + + err: + MLKEM_public_key_free(key); + + return ret; +} +LCRYPTO_ALIAS(MLKEM_public_key_new); + +void +MLKEM_public_key_free(MLKEM_public_key *key) +{ + if (key == NULL) + return; + + freezero(key->key_768, sizeof(*key->key_768)); + freezero(key->key_1024, sizeof(*key->key_1024)); + freezero(key, sizeof(*key)); +} +LCRYPTO_ALIAS(MLKEM_public_key_free); + +size_t +MLKEM_public_key_encoded_length(const MLKEM_public_key *key) +{ + if (key == NULL) + return 0; + + switch (key->rank) { + case RANK768: + return MLKEM768_PUBLIC_KEY_BYTES; + case RANK1024: + return MLKEM1024_PUBLIC_KEY_BYTES; + default: + return 0; + } + return 0; +} +LCRYPTO_ALIAS(MLKEM_public_key_encoded_length); + +size_t +MLKEM_public_key_ciphertext_length(const MLKEM_public_key *key) +{ + if (key == NULL) + return 0; + + switch (key->rank) { + case RANK768: + return MLKEM768_CIPHERTEXT_BYTES; + case RANK1024: + return MLKEM1024_CIPHERTEXT_BYTES; + default: + return 0; + } + return 0; +} +LCRYPTO_ALIAS(MLKEM_public_key_ciphertext_length); -- cgit v1.2.3-55-g6feb