mirror of
https://github.com/openssl/openssl.git
synced 2025-04-06 20:20:50 +08:00
Add ML-DSA Keygen support
The key generation algorithm requires a significant portion of the many algorithms present in FIPS 204. This work is derived from the BoringSSL code located at https://boringssl.googlesource.com/boringssl/+/refs/heads/master/crypto/mldsa/mldsa.cc Instead of c++ templates it uses an ML_DSA_PARAMS object to store constants such as k & l. To perform hash operations a temporary EVP_MD_CTX object is used, which is supplied with a prefetched EVP_MD shake128 or shake256 object that reside in the ML_DSA_KEY object. The ML_DSA_KEY object stores the encoded public and/or private key whenever a key is loaded or generated. A public key is always present if the private key component exists. Reviewed-by: Viktor Dukhovni <viktor@openssl.org> Reviewed-by: Tim Hudson <tjh@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/26127)
This commit is contained in:
parent
5949918f9a
commit
d3a7ae64b3
@ -486,6 +486,7 @@ my @disablables = (
|
||||
"md2",
|
||||
"md4",
|
||||
"mdc2",
|
||||
"ml-dsa",
|
||||
"module",
|
||||
"msan",
|
||||
"multiblock",
|
||||
|
@ -885,6 +885,11 @@ Disabling this also disables the legacy algorithms: MD2 (already disabled by def
|
||||
|
||||
Don't generate dependencies.
|
||||
|
||||
### no-ml-dsa
|
||||
|
||||
Disable Module-Lattice-Based Digital Signature Standard (ML-DSA) Support.
|
||||
(ML-DSA is based on CRYSTALS-DILITHIUM. See NIST.FIPS.204)
|
||||
|
||||
### no-module
|
||||
|
||||
Don't build any dynamically loadable engines.
|
||||
|
@ -6,7 +6,7 @@ SUBDIRS=objects buffer bio stack lhash hashtable rand evp asn1 pem x509 conf \
|
||||
siphash sm3 des aes rc2 rc4 rc5 idea aria bf cast camellia \
|
||||
seed sm4 chacha modes bn ec rsa dsa dh sm2 dso engine \
|
||||
err comp http ocsp cms ts srp cmac ct async ess crmf cmp encode_decode \
|
||||
ffc hpke thread
|
||||
ffc hpke thread ml_dsa
|
||||
|
||||
LIBS=../libcrypto
|
||||
|
||||
|
8
crypto/ml_dsa/build.info
Normal file
8
crypto/ml_dsa/build.info
Normal file
@ -0,0 +1,8 @@
|
||||
LIBS=../../libcrypto
|
||||
|
||||
$COMMON=ml_dsa_ctx.c ml_dsa_encoders.c ml_dsa_key_compress.c ml_dsa_key.c \
|
||||
ml_dsa_matrix.c ml_dsa_ntt.c ml_dsa_params.c ml_dsa_sample.c
|
||||
|
||||
IF[{- !$disabled{'ml_dsa'} -}]
|
||||
SOURCE[../../libcrypto]=$COMMON
|
||||
ENDIF
|
81
crypto/ml_dsa/ml_dsa_ctx.c
Normal file
81
crypto/ml_dsa/ml_dsa_ctx.c
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_params.h"
|
||||
|
||||
static EVP_MD_CTX *md_ctx_new(EVP_MD *md)
|
||||
{
|
||||
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
|
||||
if (EVP_DigestInit_ex2(ctx, md, NULL) != 1) {
|
||||
EVP_MD_CTX_free(ctx);
|
||||
ctx = NULL;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a ML_DSA_CTX that contains parameters, and pre-fetched hash
|
||||
* related objects for a ML-DSA algorithm. This context is passed
|
||||
* to many ML-DSA related functions.
|
||||
*
|
||||
* @param alg An ML-DSA algorithm name such as "ML-DSA-65"
|
||||
* @param lib_ctx A library context used for fetching. Can be NULL
|
||||
* @param propq A property query to use for algorithm fetching. Can be NULL.
|
||||
*
|
||||
* @returns The created ML_DSA_CTX object or NULL on failure.
|
||||
*/
|
||||
ML_DSA_CTX *ossl_ml_dsa_ctx_new(const char *alg,
|
||||
OSSL_LIB_CTX *lib_ctx, const char *propq)
|
||||
{
|
||||
ML_DSA_CTX *ret;
|
||||
EVP_MD *shake128_md = NULL;
|
||||
EVP_MD *shake256_md = NULL;
|
||||
const ML_DSA_PARAMS *params = ossl_ml_dsa_params_get(alg);
|
||||
|
||||
if (params == NULL)
|
||||
return NULL;
|
||||
|
||||
ret = OPENSSL_zalloc(sizeof(*ret));
|
||||
if (ret == NULL)
|
||||
return NULL;
|
||||
|
||||
shake128_md = EVP_MD_fetch(lib_ctx, "SHAKE-128", propq);
|
||||
shake256_md = EVP_MD_fetch(lib_ctx, "SHAKE-256", propq);
|
||||
if (shake128_md == NULL || shake256_md == NULL)
|
||||
goto err;
|
||||
ret->g_ctx = md_ctx_new(shake128_md);
|
||||
ret->h_ctx = md_ctx_new(shake256_md);
|
||||
EVP_MD_free(shake128_md);
|
||||
EVP_MD_free(shake256_md);
|
||||
if (ret->h_ctx == NULL || ret->g_ctx == NULL)
|
||||
goto err;
|
||||
ret->params = params;
|
||||
return ret;
|
||||
err:
|
||||
ossl_ml_dsa_ctx_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy a ML_DSA_CTX
|
||||
*
|
||||
* @param ctx The ML_DSA_CTX object to destroy.
|
||||
*/
|
||||
void ossl_ml_dsa_ctx_free(ML_DSA_CTX *ctx)
|
||||
{
|
||||
EVP_MD_CTX_free(ctx->g_ctx);
|
||||
EVP_MD_CTX_free(ctx->h_ctx);
|
||||
OPENSSL_free(ctx);
|
||||
}
|
541
crypto/ml_dsa/ml_dsa_encoders.c
Normal file
541
crypto/ml_dsa/ml_dsa_encoders.c
Normal file
@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_key.h"
|
||||
#include "ml_dsa_params.h"
|
||||
#include "internal/packet.h"
|
||||
|
||||
typedef int (PRIV_ENCODE_FN)(WPACKET *pkt, const POLY *s);
|
||||
typedef int (PRIV_DECODE_FN)(PACKET *pkt, POLY *s);
|
||||
|
||||
static PRIV_ENCODE_FN poly_encode_signed_2;
|
||||
static PRIV_ENCODE_FN poly_encode_signed_4;
|
||||
static PRIV_DECODE_FN poly_decode_signed_2;
|
||||
static PRIV_DECODE_FN poly_decode_signed_4;
|
||||
|
||||
static ossl_inline int constant_time_declassify_int(int v)
|
||||
{
|
||||
return value_barrier_32(v);
|
||||
}
|
||||
|
||||
/* Bit packing Algorithms */
|
||||
|
||||
/*
|
||||
* Encodes a polynomial into a byte string, assuming that all coefficients are
|
||||
* 10 bits.
|
||||
*
|
||||
* See FIPS 204, Algorithm 16, SimpleBitPack(w, b) where b = 10 bits
|
||||
*
|
||||
* i.e. Use 10 bits from each coefficient and pack them into bytes
|
||||
* So every 4 coefficients (c0..c3) fit into 5 bytes.
|
||||
* |c0||c1||c2||c3|
|
||||
* |\ |\ |\ |\
|
||||
* |8|2 6|4 4|6 2|8|
|
||||
*
|
||||
* This is used to save t1 (the high part of public key polynomial t)
|
||||
*
|
||||
* @param p A polynomial with coefficients all in the range (0..1023)
|
||||
* @param pkt A packet object to write 320 bytes to.
|
||||
*
|
||||
* @returns 1 on success, or 0 on error.
|
||||
*/
|
||||
static int poly_encode_10_bits(WPACKET *pkt, const POLY *p)
|
||||
{
|
||||
uint8_t *out;
|
||||
const uint32_t *in = p->coeff, *end = in + 256;
|
||||
|
||||
if (!WPACKET_allocate_bytes(pkt, 5 * (256 / 4), &out))
|
||||
return 0;
|
||||
|
||||
while (in < end) {
|
||||
uint32_t c0 = *in++;
|
||||
uint32_t c1 = *in++;
|
||||
uint32_t c2 = *in++;
|
||||
uint32_t c3 = *in++;
|
||||
|
||||
*out++ = (uint8_t)c0;
|
||||
*out++ = (uint8_t)((c0 >> 8) | (c1 << 2));
|
||||
*out++ = (uint8_t)((c1 >> 6) | (c2 << 4));
|
||||
*out++ = (uint8_t)((c2 >> 4) | (c3 << 6));
|
||||
*out++ = (uint8_t)(c3 >> 2);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Reverses the procedure of poly_encode_10_bits().
|
||||
* See FIPS 204, Algorithm 18, SimpleBitUnpack(v, b) where b = 10.
|
||||
*
|
||||
* @param pkt A packet object to read 320 bytes from.
|
||||
* @param p A polynomial to write coefficients to.
|
||||
*
|
||||
* @returns 1 on success, or 0 on error.
|
||||
*/
|
||||
static int poly_decode_10_bits(PACKET *pkt, POLY *p)
|
||||
{
|
||||
int i, ret = 0;
|
||||
const uint8_t *in = NULL;
|
||||
uint32_t v, *out = p->coeff;
|
||||
|
||||
for (i = 0; i < (ML_DSA_NUM_POLY_COEFFICIENTS / 4); i++) {
|
||||
if (!PACKET_get_bytes(pkt, &in, 5))
|
||||
goto err;
|
||||
memcpy(&v, in, sizeof(v));
|
||||
*out++ = v & 0x3ff;
|
||||
*out++ = (v >> 10) & 0x3ff;
|
||||
*out++ = (v >> 20) & 0x3ff;
|
||||
*out++ = (v >> 30) | (((uint32_t)in[4]) << 2);
|
||||
}
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Encodes a polynomial into a byte string, assuming that all
|
||||
* coefficients are in the range -4..4.
|
||||
* See FIPS 204, Algorithm 17, BitPack(w, a, b). (a = 4, b = 4)
|
||||
*
|
||||
* It uses a nibble from each coefficient and packs them into bytes
|
||||
* So every 2 coefficients fit into 1 byte.
|
||||
*
|
||||
* This is used to encode the private key polynomial elements of s1 and s2
|
||||
* for ML-DSA-65 (i.e. eta = 4)
|
||||
*
|
||||
* @param pkt A packet to write 128 bytes of encoded polynomial coefficients to.
|
||||
* @param p An array of 256 coefficients all in the range -4..4
|
||||
*
|
||||
* @returns 1 on success, or 0 on error.
|
||||
*/
|
||||
static int poly_encode_signed_4(WPACKET *pkt, const POLY *p)
|
||||
{
|
||||
uint8_t *out;
|
||||
const uint32_t *in = p->coeff, *end = in + 256;
|
||||
|
||||
if (!WPACKET_allocate_bytes(pkt, 32 * 4, &out))
|
||||
return 0;
|
||||
|
||||
while (in < end) {
|
||||
uint32_t z0 = mod_sub(4, *in++); /* 0..8 */
|
||||
uint32_t z1 = mod_sub(4, *in++); /* 0..8 */
|
||||
|
||||
*out++ = z0 | (z1 << 4);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Reverses the procedure of poly_encode_signed_4().
|
||||
* See FIPS 204, Algorithm 19, BitUnpack(v, a, b) where a = b = 4.
|
||||
*
|
||||
* @param pkt A packet object to read 128 bytes from.
|
||||
* @param p A polynomial to write coefficients to.
|
||||
*
|
||||
* @returns 1 on success, or 0 on error. An error will occur if any of the
|
||||
* coefficients are not in the correct range.
|
||||
*/
|
||||
static int poly_decode_signed_4(PACKET *pkt, POLY *s)
|
||||
{
|
||||
int i, ret = 0;
|
||||
uint32_t v, *out = s->coeff;
|
||||
const uint8_t *in;
|
||||
uint32_t msbs, mask;
|
||||
|
||||
for (i = 0; i < (ML_DSA_NUM_POLY_COEFFICIENTS / 8); i++) {
|
||||
if (!PACKET_get_bytes(pkt, &in, 4))
|
||||
goto err;
|
||||
memcpy(&v, &in, 4);
|
||||
|
||||
/*
|
||||
* None of the nibbles may be >= 9. So if the MSB of any nibble is set,
|
||||
* none of the other bits may be set. First, select all the MSBs.
|
||||
*/
|
||||
msbs = v & 0x88888888u;
|
||||
/* For each nibble where the MSB is set, form a mask of all the other bits. */
|
||||
mask = (msbs >> 1) | (msbs >> 2) | (msbs >> 3);
|
||||
/*
|
||||
* A nibble is only out of range in the case of invalid input, in which case
|
||||
* it is okay to leak the value.
|
||||
*/
|
||||
if (constant_time_declassify_int((mask & v) != 0))
|
||||
goto err;
|
||||
|
||||
*out++ = mod_sub(4, v & 15);
|
||||
*out++ = mod_sub(4, (v >> 4) & 15);
|
||||
*out++ = mod_sub(4, (v >> 8) & 15);
|
||||
*out++ = mod_sub(4, (v >> 12) & 15);
|
||||
*out++ = mod_sub(4, (v >> 16) & 15);
|
||||
*out++ = mod_sub(4, (v >> 20) & 15);
|
||||
*out++ = mod_sub(4, (v >> 24) & 15);
|
||||
*out++ = mod_sub(4, v >> 28);
|
||||
}
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Encodes a polynomial into a byte string, assuming that all
|
||||
* coefficients are in the range -2..2.
|
||||
* See FIPS 204, Algorithm 17, BitPack(w, a, b). where a = b = 2.
|
||||
*
|
||||
* This is used to encode the private key polynomial elements of s1 and s2
|
||||
* for ML-DSA-44 and ML-DSA-87 (i.e. eta = 2)
|
||||
*
|
||||
* @param pkt A packet to write 128 bytes of encoded polynomial coefficients to.
|
||||
* @param p An array of 256 coefficients all in the range -2..2
|
||||
*
|
||||
* Use 3 bits from each coefficient and pack them into bytes
|
||||
* So every 8 coefficients fit into 3 bytes.
|
||||
* |c0 c1 c2 c3 c4 c5 c6 c7|
|
||||
* | / / | | / / | | /
|
||||
* |3 3 2| 1 3 3 1| 2 3 3|
|
||||
*
|
||||
* @param pkt A packet to write 64 bytes of encoded polynomial coefficients to.
|
||||
* @param p An array of 256 coefficients all in the range -2..2
|
||||
*
|
||||
* @returns 1 on success, or 0 on error.
|
||||
*/
|
||||
static int poly_encode_signed_2(WPACKET *pkt, const POLY *s)
|
||||
{
|
||||
uint8_t *out;
|
||||
const uint32_t *in = s->coeff, *end = in + 256;
|
||||
|
||||
if (!WPACKET_allocate_bytes(pkt, 32 * 3, &out))
|
||||
return 0;
|
||||
|
||||
while (in < end) {
|
||||
uint32_t z0 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z1 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z2 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z3 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z4 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z5 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z6 = mod_sub(2, *in++); /* 0..7 */
|
||||
uint32_t z7 = mod_sub(2, *in++); /* 0..7 */
|
||||
|
||||
*out++ = (uint8_t)z0 | (uint8_t)(z1 << 3) | (uint8_t)(z2 << 6);
|
||||
*out++ = (uint8_t)(z2 >> 2) | (uint8_t)(z3 << 1) | (uint8_t)(z4 << 4) | (uint8_t)(z5 << 7);
|
||||
*out++ = (uint8_t)(z5 >> 1) | (uint8_t)(z6 << 2) | (uint8_t)(z7 << 5);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Reverses the procedure of poly_encode_signed_2().
|
||||
* See FIPS 204, Algorithm 19, BitUnpack(v, a, b) where a = b = 2.
|
||||
*
|
||||
* @param pkt A packet object to read 64 encoded bytes from.
|
||||
* @param p A polynomial to write coefficients to.
|
||||
*
|
||||
* @returns 1 on success, or 0 on error. An error will occur if any of the
|
||||
* coefficients are not in the correct range.
|
||||
*/
|
||||
static int poly_decode_signed_2(PACKET *pkt, POLY *p)
|
||||
{
|
||||
int i, ret = 0;
|
||||
uint32_t v = 0, *out = p->coeff;
|
||||
uint32_t msbs, mask;
|
||||
const uint8_t *in;
|
||||
|
||||
for (i = 0; i < (ML_DSA_NUM_POLY_COEFFICIENTS / 8); i++) {
|
||||
if (!PACKET_get_bytes(pkt, &in, 3))
|
||||
goto err;
|
||||
memcpy(&v, &in, 3);
|
||||
/*
|
||||
* Each octal value (3 bits) must be <= 4, So if the MSB is set then the
|
||||
* bottom 2 bits must not be set.
|
||||
* First, select all the MSBs (Use octal representation for the mask)
|
||||
*/
|
||||
msbs = v & 044444444;
|
||||
/* For each octal value where the MSB is set, form a mask of the 2 other bits. */
|
||||
mask = (msbs >> 1) | (msbs >> 2);
|
||||
/*
|
||||
* A nibble is only out of range in the case of invalid input, in which
|
||||
* case it is okay to leak the value.
|
||||
*/
|
||||
if (constant_time_declassify_int((mask & v) != 0))
|
||||
goto err;
|
||||
|
||||
*out++ = mod_sub(2, v & 7);
|
||||
*out++ = mod_sub(2, (v >> 3) & 7);
|
||||
*out++ = mod_sub(2, (v >> 6) & 7);
|
||||
*out++ = mod_sub(2, (v >> 9) & 7);
|
||||
*out++ = mod_sub(2, (v >> 12) & 7);
|
||||
*out++ = mod_sub(2, (v >> 15) & 7);
|
||||
*out++ = mod_sub(2, (v >> 18) & 7);
|
||||
*out++ = mod_sub(2, (v >> 21) & 7);
|
||||
}
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Encodes a polynomial into a byte string, assuming that all
|
||||
* coefficients are in the range (-2^12 + 1)..2^12.
|
||||
* See FIPS 204, Algorithm 17, BitPack(w, a, b). where a = 2^12 - 1, b = 2^12.
|
||||
*
|
||||
* This is used to encode the LSB of the public key polynomial elements of t0
|
||||
* (which are encoded as part of the encoded private key).
|
||||
*
|
||||
* Use 13 bits from each coefficient and pack them into bytes
|
||||
*
|
||||
* The code below packs them into 2 64 bits blocks by doing..
|
||||
* z0 z1 z2 z3 z4 z5 z6 z7 0
|
||||
* | | | | / \ | | | |
|
||||
* |13 13 13 13 12 |1 13 13 13 24
|
||||
*
|
||||
* @param pkt A packet to write 416 (13 * 256 / 8) bytes of encoded polynomial
|
||||
* coefficients to.
|
||||
* @param p An array of 256 coefficients all in the range -2^12+1..2^12
|
||||
*
|
||||
* @returns 1 on success, or 0 on error.
|
||||
*/
|
||||
static int poly_encode_signed_two_to_power_12(WPACKET *pkt, const POLY *p)
|
||||
{
|
||||
static const uint32_t range = 1u << 12;
|
||||
const uint32_t *in = p->coeff, *end = in + 256;
|
||||
|
||||
while (in < end) {
|
||||
uint64_t z0 = mod_sub(range, *in++); /* < 2^13 */
|
||||
uint64_t z1 = mod_sub(range, *in++);
|
||||
uint64_t z2 = mod_sub(range, *in++);
|
||||
uint64_t z3 = mod_sub(range, *in++);
|
||||
uint64_t z4 = mod_sub(range, *in++);
|
||||
uint64_t z5 = mod_sub(range, *in++);
|
||||
uint64_t z6 = mod_sub(range, *in++);
|
||||
uint64_t z7 = mod_sub(range, *in++);
|
||||
uint64_t a1 = (z0) | (z1 << 13) | (z2 << 26) | (z3 << 39) | (z4 << 52);
|
||||
uint64_t a2 = (z4 >> 12) | (z5 << 1) | (z6 << 14) | (z7 << 27);
|
||||
|
||||
if (!WPACKET_memcpy(pkt, &a1, 8)
|
||||
|| !WPACKET_memcpy(pkt, &a2, 5))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Reverses the procedure of poly_encode_signed_two_to_power_12().
|
||||
* See FIPS 204, Algorithm 19, BitUnpack(v, a, b) where a = 2^12 - 1, b = 2^12.
|
||||
*
|
||||
* @param pkt A packet object to read 416 encoded bytes from.
|
||||
* @param p A polynomial to write coefficients to.
|
||||
*
|
||||
* @returns 1 on success, or 0 on error.
|
||||
*/
|
||||
static int poly_decode_signed_two_to_power_12(PACKET *pkt, POLY *p)
|
||||
{
|
||||
int i, ret = 0;
|
||||
uint64_t a1 = 0, a2 = 0;
|
||||
uint32_t *out = p->coeff;
|
||||
const uint8_t *in;
|
||||
static const uint32_t range = 1u << 12;
|
||||
static const uint32_t mask_13_bits = (1u << 13) - 1;
|
||||
|
||||
for (i = 0; i < (ML_DSA_NUM_POLY_COEFFICIENTS / 8); i++) {
|
||||
if (!PACKET_get_bytes(pkt, &in, 13))
|
||||
goto err;
|
||||
memcpy(&a1, in, 8);
|
||||
memcpy(&a2, in + 8, 5);
|
||||
|
||||
/*
|
||||
* It is not possible for a 13-bit number to be out of range when the
|
||||
* max is 2^12.
|
||||
*/
|
||||
*out++ = mod_sub(range, a1 & mask_13_bits);
|
||||
*out++ = mod_sub(range, (a1 >> 13) & mask_13_bits);
|
||||
*out++ = mod_sub(range, (a1 >> 26) & mask_13_bits);
|
||||
*out++ = mod_sub(range, (a1 >> 39) & mask_13_bits);
|
||||
*out++ = mod_sub(range, (a1 >> 52) | ((a2 << 12) & mask_13_bits));
|
||||
*out++ = mod_sub(range, (a2 >> 1) & mask_13_bits);
|
||||
*out++ = mod_sub(range, (a2 >> 14) & mask_13_bits);
|
||||
*out++ = mod_sub(range, (a2 >> 27) & mask_13_bits);
|
||||
}
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Encode the public key as an array of bytes.
|
||||
* See FIPS 204, Algorithm 22, pkEncode().
|
||||
*
|
||||
* @param key A key object containing public key values. The encoded public
|
||||
* key data is stored in this key.
|
||||
* @returns 1 if the public key was encoded successfully or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_pk_encode(ML_DSA_KEY *key)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t i;
|
||||
const POLY *t1 = key->t1.poly;
|
||||
size_t t1_len = key->t1.num_poly;
|
||||
size_t enc_len = key->params->pk_len;
|
||||
uint8_t *enc = OPENSSL_zalloc(enc_len);
|
||||
WPACKET pkt;
|
||||
|
||||
if (enc == NULL)
|
||||
return 0;
|
||||
|
||||
if (!WPACKET_init_static_len(&pkt, enc, enc_len, 0)
|
||||
|| !WPACKET_memcpy(&pkt, key->rho, sizeof(key->rho)))
|
||||
goto err;
|
||||
for (i = 0; i < t1_len; i++)
|
||||
if (!poly_encode_10_bits(&pkt, t1 + i))
|
||||
goto err;
|
||||
OPENSSL_free(key->pub_encoding);
|
||||
key->pub_encoding = enc;
|
||||
ret = 1;
|
||||
err:
|
||||
WPACKET_finish(&pkt);
|
||||
if (ret == 0)
|
||||
OPENSSL_free(enc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief The reverse of ossl_ml_dsa_pk_encode().
|
||||
* See FIPS 204, Algorithm 23, pkDecode().
|
||||
*
|
||||
* @param in An encoded public key.
|
||||
* @param in_len The size of |in|
|
||||
* @param key A key object to store the decoded public key into.
|
||||
*
|
||||
* @returns 1 if the public key was decoded successfully or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_pk_decode(const uint8_t *in, size_t in_len, ML_DSA_KEY *key)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t i;
|
||||
PACKET pkt;
|
||||
|
||||
if (in_len != key->params->pk_len)
|
||||
return 0;
|
||||
|
||||
if (!PACKET_buf_init(&pkt, in, in_len)
|
||||
|| PACKET_copy_bytes(&pkt, key->rho, sizeof(key->rho)))
|
||||
goto err;
|
||||
for (i = 0; i < key->t1.num_poly; i++)
|
||||
if (!poly_decode_10_bits(&pkt, &key->t1.poly[i]))
|
||||
goto err;
|
||||
memcpy(key->pub_encoding, in, in_len);
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Encode the private key as an array of bytes.
|
||||
* See FIPS 204, Algorithm 24, skEncode().
|
||||
*
|
||||
* @param key A key object containing private key values. The encoded private
|
||||
* key data is stored in this key.
|
||||
* @returns 1 if the private key was encoded successfully or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_sk_encode(ML_DSA_KEY *key)
|
||||
{
|
||||
int ret = 0;
|
||||
const ML_DSA_PARAMS *params = key->params;
|
||||
size_t i, k = params->k, l = params->l;
|
||||
PRIV_ENCODE_FN *encode_fn;
|
||||
size_t enc_len = params->sk_len;
|
||||
const POLY *t0 = key->t0.poly;
|
||||
WPACKET pkt;
|
||||
uint8_t *enc = OPENSSL_zalloc(enc_len);
|
||||
|
||||
if (enc == NULL)
|
||||
return 0;
|
||||
|
||||
/* eta is the range of private key coefficients (-eta...eta) */
|
||||
if (params->eta == 4)
|
||||
encode_fn = poly_encode_signed_4;
|
||||
else
|
||||
encode_fn = poly_encode_signed_2;
|
||||
|
||||
if (!WPACKET_init_static_len(&pkt, enc, enc_len, 0)
|
||||
|| !WPACKET_memcpy(&pkt, key->rho, sizeof(key->rho))
|
||||
|| !WPACKET_memcpy(&pkt, key->K, sizeof(key->K))
|
||||
|| !WPACKET_memcpy(&pkt, key->tr, sizeof(key->tr)))
|
||||
goto err;
|
||||
for (i = 0; i < l; ++i)
|
||||
if (!encode_fn(&pkt, &key->s1.poly[i]))
|
||||
goto err;
|
||||
for (i = 0; i < k; ++i)
|
||||
if (!encode_fn(&pkt, &key->s2.poly[i]))
|
||||
goto err;
|
||||
for (i = 0; i < k; ++i, t0++)
|
||||
if (!poly_encode_signed_two_to_power_12(&pkt, t0))
|
||||
goto err;
|
||||
OPENSSL_clear_free(key->priv_encoding, enc_len);
|
||||
key->priv_encoding = enc;
|
||||
ret = 1;
|
||||
err:
|
||||
WPACKET_finish(&pkt);
|
||||
if (ret == 0)
|
||||
OPENSSL_clear_free(enc, enc_len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief The reverse of ossl_ml_dsa_sk_encode().
|
||||
* See FIPS 204, Algorithm 24, skDecode().
|
||||
*
|
||||
* @param in An encoded private key.
|
||||
* @param in_len The size of |in|
|
||||
* @param key A key object to store the decoded private key into.
|
||||
*
|
||||
* @returns 1 if the private key was decoded successfully or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_sk_decode(const uint8_t *in, size_t in_len, ML_DSA_KEY *key)
|
||||
{
|
||||
int ret = 0;
|
||||
uint8_t *enc = NULL;
|
||||
PRIV_DECODE_FN *decode_fn;
|
||||
const ML_DSA_PARAMS *params = key->params;
|
||||
size_t i, k = params->k, l = params->l;
|
||||
PACKET pkt;
|
||||
|
||||
if (in_len != key->params->sk_len)
|
||||
return 0;
|
||||
enc = OPENSSL_memdup(in, in_len);
|
||||
if (enc == NULL)
|
||||
return 0;
|
||||
|
||||
/* eta is the range of private key coefficients (-eta...eta) */
|
||||
if (params->eta == 4)
|
||||
decode_fn = poly_decode_signed_4;
|
||||
else
|
||||
decode_fn = poly_decode_signed_2;
|
||||
|
||||
if (!PACKET_buf_init(&pkt, in, in_len)
|
||||
|| !PACKET_copy_bytes(&pkt, key->rho, sizeof(key->rho))
|
||||
|| !PACKET_copy_bytes(&pkt, key->K, sizeof(key->K))
|
||||
|| !PACKET_copy_bytes(&pkt, key->tr, sizeof(key->tr)))
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < l; ++i)
|
||||
if (!decode_fn(&pkt, key->s1.poly + i))
|
||||
goto err;
|
||||
for (i = 0; i < k; ++i)
|
||||
if (!decode_fn(&pkt, key->s2.poly + i))
|
||||
goto err;
|
||||
for (i = 0; i < k; ++i)
|
||||
if (!poly_decode_signed_two_to_power_12(&pkt, key->t0.poly + i))
|
||||
goto err;
|
||||
if (PACKET_remaining(&pkt) != 0)
|
||||
goto err;
|
||||
OPENSSL_clear_free(key->priv_encoding, in_len);
|
||||
key->priv_encoding = enc;
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
380
crypto/ml_dsa/ml_dsa_key.c
Normal file
380
crypto/ml_dsa/ml_dsa_key.c
Normal file
@ -0,0 +1,380 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/core_dispatch.h>
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/params.h>
|
||||
#include <openssl/rand.h>
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_key.h"
|
||||
#include "ml_dsa_params.h"
|
||||
#include "ml_dsa_matrix.h"
|
||||
|
||||
/**
|
||||
* @brief Create a new ML_DSA_KEY object
|
||||
*
|
||||
* @param libctx A OSSL_LIB_CTX object used for fetching algorithms.
|
||||
* @param alg The algorithm name associated with the key type
|
||||
* @returns The new ML_DSA_KEY object on success, or NULL on malloc failure
|
||||
*/
|
||||
ML_DSA_KEY *ossl_ml_dsa_key_new(OSSL_LIB_CTX *libctx, const char *alg)
|
||||
{
|
||||
ML_DSA_KEY *ret;
|
||||
const ML_DSA_PARAMS *params = ossl_ml_dsa_params_get(alg);
|
||||
|
||||
if (params == NULL)
|
||||
return NULL;
|
||||
|
||||
ret = OPENSSL_zalloc(sizeof(*ret));
|
||||
if (ret != NULL) {
|
||||
if (!CRYPTO_NEW_REF(&ret->references, 1)) {
|
||||
OPENSSL_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
ret->libctx = libctx;
|
||||
ret->params = params;
|
||||
vector_init(&ret->t0, params->k);
|
||||
vector_init(&ret->t1, params->k);
|
||||
vector_init(&ret->s2, params->k);
|
||||
vector_init(&ret->s1, params->l);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroy a ML_DSA_KEY object
|
||||
*/
|
||||
void ossl_ml_dsa_key_free(ML_DSA_KEY *key)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (key == NULL)
|
||||
return;
|
||||
|
||||
CRYPTO_DOWN_REF(&key->references, &i);
|
||||
REF_PRINT_COUNT("ML_DSA_KEY", key);
|
||||
if (i > 0)
|
||||
return;
|
||||
REF_ASSERT_ISNT(i < 0);
|
||||
|
||||
OPENSSL_free(key->pub_encoding);
|
||||
OPENSSL_free(key->priv_encoding);
|
||||
OPENSSL_free(key->propq);
|
||||
CRYPTO_FREE_REF(&key->references);
|
||||
OPENSSL_free(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Increase the reference count for a ML_DSA_KEY object.
|
||||
* @returns 1 on success or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_key_up_ref(ML_DSA_KEY *key)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (CRYPTO_UP_REF(&key->references, &i) <= 0)
|
||||
return 0;
|
||||
|
||||
REF_PRINT_COUNT("ML_DSA_KEY", key);
|
||||
REF_ASSERT_ISNT(i < 2);
|
||||
return ((i > 1) ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Are 2 keys equal?
|
||||
*
|
||||
* To be equal the keys must have the same key data and algorithm name.
|
||||
*
|
||||
* @param key1 A ML_DSA_KEY object
|
||||
* @param key2 A ML_DSA_KEY object
|
||||
* @param selection to select public and/or private component comparison.
|
||||
* @returns 1 if the keys are equal otherwise it returns 0.
|
||||
*/
|
||||
int ossl_ml_dsa_key_equal(const ML_DSA_KEY *key1, const ML_DSA_KEY *key2,
|
||||
int selection)
|
||||
{
|
||||
if (key1->params != key2->params)
|
||||
return 0;
|
||||
if (key1->pub_encoding != NULL) {
|
||||
if (key2->pub_encoding == NULL
|
||||
|| memcmp(key1->pub_encoding, key1->pub_encoding,
|
||||
key1->params->pk_len) != 0)
|
||||
return 0;
|
||||
} else if (key2->pub_encoding != NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (key1->priv_encoding != NULL) {
|
||||
if (key2->priv_encoding == NULL
|
||||
|| memcmp(key1->priv_encoding, key1->priv_encoding,
|
||||
key1->params->sk_len) != 0)
|
||||
return 0;
|
||||
} else if (key2->priv_encoding != NULL) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ossl_ml_dsa_key_has(const ML_DSA_KEY *key, int selection)
|
||||
{
|
||||
if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) != 0) {
|
||||
if (key->pub_encoding == NULL)
|
||||
return 0; /* No public key */
|
||||
if ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0
|
||||
&& key->priv_encoding == 0)
|
||||
return 0; /* No private key */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Load a ML_DSA key from raw data.
|
||||
*
|
||||
* @param key An ML_DSA key to load into
|
||||
* @param params An array of parameters containing key data.
|
||||
* @param include_private Set to 1 to optionally include the private key data
|
||||
* if it exists.
|
||||
* @returns 1 on success, or 0 on failure.
|
||||
*/
|
||||
int ossl_ml_dsa_key_fromdata(ML_DSA_KEY *key, const OSSL_PARAM params[],
|
||||
int include_private)
|
||||
{
|
||||
const OSSL_PARAM *p = NULL;
|
||||
|
||||
/* Private key is optional */
|
||||
if (include_private) {
|
||||
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PRIV_KEY);
|
||||
if (p != NULL) {
|
||||
if (p->data_type != OSSL_PARAM_OCTET_STRING
|
||||
|| !ossl_ml_dsa_sk_decode(p->data, p->data_size, key))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PUB_KEY);
|
||||
if (p != NULL) {
|
||||
if (p->data_type != OSSL_PARAM_OCTET_STRING
|
||||
|| !ossl_ml_dsa_pk_decode(p->data, p->data_size, key))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Given a key containing private key values for rho, s1 & s2
|
||||
* generate the public value t and return the compressed values t1, t0.
|
||||
*
|
||||
* @param ctx A Object containing algorithm specific constants and hash contexts.
|
||||
* @param key A private key containing rh0, s1 & s2.
|
||||
* @param t1 The returned polynomial encoding of the 10 MSB of each coefficient
|
||||
* of the uncompressed public key polynomial t.
|
||||
* @param t0 The returned polynomial encoding of the 13 LSB of each coefficient
|
||||
* of the uncompressed public key polynomial t.
|
||||
* @returns 1 on success, or 0 on failure.
|
||||
*/
|
||||
static int public_from_private(ML_DSA_CTX *ctx, const ML_DSA_KEY *key,
|
||||
VECTOR *t1, VECTOR *t0)
|
||||
{
|
||||
MATRIX a_ntt;
|
||||
VECTOR s1_ntt;
|
||||
VECTOR t;
|
||||
const ML_DSA_PARAMS *params = ctx->params;
|
||||
|
||||
matrix_init(&a_ntt, params->k, params->l);
|
||||
vector_init(&s1_ntt, params->l);
|
||||
vector_init(&t, params->k);
|
||||
|
||||
/* Using rho generate A' = A in NTT form */
|
||||
if (!ossl_ml_dsa_sample_expandA(ctx->g_ctx, key->rho, &a_ntt))
|
||||
return 0;
|
||||
|
||||
/* t = NTT_inv(A' * NTT(s1)) + s2 */
|
||||
vector_copy(&s1_ntt, &key->s1);
|
||||
vector_ntt(&s1_ntt);
|
||||
|
||||
ossl_ml_dsa_matrix_mult_vector(&a_ntt, &s1_ntt, &t);
|
||||
vector_ntt_inverse(&t);
|
||||
vector_add(&t, &key->s2, &t);
|
||||
|
||||
/* Compress t */
|
||||
vector_power2_round(&t, t1, t0);
|
||||
|
||||
/* Zeroize secret */
|
||||
vector_zero(&s1_ntt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ossl_ml_dsa_key_pairwise_check(const ML_DSA_KEY *key)
|
||||
{
|
||||
int ret = 0;
|
||||
ML_DSA_CTX *ctx = NULL;
|
||||
VECTOR t1, t0;
|
||||
|
||||
if (key->pub_encoding == NULL || key->priv_encoding == 0)
|
||||
return 0;
|
||||
|
||||
ctx = ossl_ml_dsa_ctx_new(key->params->alg, key->libctx, key->propq);
|
||||
if (ctx == NULL)
|
||||
return 0;
|
||||
|
||||
vector_init(&t1, key->params->k);
|
||||
vector_init(&t0, key->params->k);
|
||||
if (!public_from_private(ctx, key, &t1, &t0))
|
||||
goto err;
|
||||
|
||||
ret = vector_equal(&t1, &key->t1) && vector_equal(&t0, &key->t0);
|
||||
err:
|
||||
ossl_ml_dsa_ctx_free(ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int shake_xof(EVP_MD_CTX *ctx, const uint8_t *in, size_t in_len,
|
||||
uint8_t *out, size_t out_len)
|
||||
{
|
||||
return (EVP_DigestInit_ex2(ctx, NULL, NULL) == 1
|
||||
&& EVP_DigestUpdate(ctx, in, in_len) == 1
|
||||
&& EVP_DigestFinalXOF(ctx, out, out_len) == 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Generate a public-private key pair from a seed.
|
||||
* See FIPS 204, Algorithm 6 ML-DSA.KeyGen_internal().
|
||||
*
|
||||
* @param entropy The input seed
|
||||
* @param entropy_len The size of entropy (Should be 32 bytes)
|
||||
*
|
||||
*
|
||||
* @returns 1 on success or 0 on failure.
|
||||
*/
|
||||
static int keygen_internal(ML_DSA_CTX *ctx, const uint8_t *seed, size_t seed_len,
|
||||
ML_DSA_KEY *out)
|
||||
{
|
||||
int ret = 0;
|
||||
uint8_t augmented_seed[ML_DSA_SEED_BYTES + 2];
|
||||
uint8_t expanded_seed[ML_DSA_RHO_BYTES + ML_DSA_PRIV_SEED_BYTES + ML_DSA_K_BYTES];
|
||||
const uint8_t *const rho = expanded_seed; /* p = Public Random Seed */
|
||||
const uint8_t *const priv_seed = expanded_seed + ML_DSA_RHO_BYTES;
|
||||
const uint8_t *const K = priv_seed + ML_DSA_PRIV_SEED_BYTES;
|
||||
const ML_DSA_PARAMS *params = ctx->params;
|
||||
|
||||
/* augmented_seed = seed || k || l */
|
||||
memcpy(augmented_seed, seed, seed_len);
|
||||
augmented_seed[ML_DSA_SEED_BYTES] = (uint8_t)params->k;
|
||||
augmented_seed[ML_DSA_SEED_BYTES + 1] = (uint8_t)params->l;
|
||||
/* Expand the seed into p[32], p'[64], K[32] */
|
||||
if (!shake_xof(ctx->h_ctx, augmented_seed, sizeof(augmented_seed),
|
||||
expanded_seed, sizeof(expanded_seed)))
|
||||
goto err;
|
||||
|
||||
memcpy(out->rho, rho, sizeof(out->rho));
|
||||
memcpy(out->K, K, sizeof(out->K));
|
||||
|
||||
ret = ossl_ml_dsa_sample_expandS(ctx->h_ctx, params->eta, priv_seed,
|
||||
&out->s1, &out->s2)
|
||||
&& public_from_private(ctx, out, &out->t1, &out->t0)
|
||||
&& ossl_ml_dsa_pk_encode(out)
|
||||
&& shake_xof(ctx->h_ctx, out->pub_encoding, out->params->pk_len,
|
||||
out->tr, sizeof(out->tr))
|
||||
&& ossl_ml_dsa_sk_encode(out);
|
||||
err:
|
||||
OPENSSL_cleanse(augmented_seed, sizeof(augmented_seed));
|
||||
OPENSSL_cleanse(expanded_seed, sizeof(expanded_seed));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ossl_ml_dsa_generate_key(ML_DSA_CTX *ctx, OSSL_LIB_CTX *lib_ctx,
|
||||
const uint8_t *entropy, size_t entropy_len,
|
||||
ML_DSA_KEY *out)
|
||||
{
|
||||
int ret = 0;
|
||||
uint8_t seed[32];
|
||||
size_t seed_len = sizeof(seed);
|
||||
|
||||
if (ctx->params != out->params)
|
||||
return 0;
|
||||
|
||||
if (entropy != NULL && entropy_len != 0) {
|
||||
if (entropy_len < seed_len)
|
||||
goto err;
|
||||
memcpy(seed, entropy, seed_len);
|
||||
} else {
|
||||
if (RAND_priv_bytes_ex(lib_ctx, seed, seed_len, 0) <= 0)
|
||||
goto err;
|
||||
}
|
||||
ret = keygen_internal(ctx, seed, seed_len, out);
|
||||
err:
|
||||
OPENSSL_cleanse(seed, seed_len);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This is used when a ML DSA key is used for an operation.
|
||||
* This checks that the algorithm is the same (i.e. uses the same parameters)
|
||||
*
|
||||
* @param ctx Contains ML_DSA algorithm functions and constants to be used for
|
||||
* an operation.
|
||||
* @param key A ML_DSA key to use for an operation.
|
||||
*
|
||||
* @returns 1 if the algorithm matches, or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_key_type_matches(ML_DSA_CTX *ctx, const ML_DSA_KEY *key)
|
||||
{
|
||||
return (key->params == ctx->params);
|
||||
}
|
||||
|
||||
/* Returns the public key data or NULL if there is no public key */
|
||||
const uint8_t *ossl_ml_dsa_key_get_pub(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->pub_encoding;
|
||||
}
|
||||
|
||||
/* Returns the constant 2 * |n| which is the size of PK_SEED + PK_ROOT */
|
||||
size_t ossl_ml_dsa_key_get_pub_len(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->params->pk_len;
|
||||
}
|
||||
|
||||
size_t ossl_ml_dsa_key_get_collision_strength_bits(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->params->strength;
|
||||
}
|
||||
|
||||
/* Returns the private key data or NULL if there is no private key */
|
||||
const uint8_t *ossl_ml_dsa_key_get_priv(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->priv_encoding;
|
||||
}
|
||||
|
||||
size_t ossl_ml_dsa_key_get_priv_len(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->params->sk_len;
|
||||
}
|
||||
|
||||
size_t ossl_ml_dsa_key_get_sig_len(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->params->sig_len;
|
||||
}
|
||||
void ossl_ml_dsa_key_set0_libctx(ML_DSA_KEY *key, OSSL_LIB_CTX *lib_ctx)
|
||||
{
|
||||
key->libctx = lib_ctx;
|
||||
}
|
||||
|
||||
const char *ossl_ml_dsa_key_get_name(const ML_DSA_KEY *key)
|
||||
{
|
||||
return key->params->alg;
|
||||
}
|
||||
|
||||
int ossl_ml_dsa_set_priv(ML_DSA_KEY *key, const uint8_t *priv, size_t priv_len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ossl_ml_dsa_set_pub(ML_DSA_KEY *key, const uint8_t *pub, size_t pub_len)
|
||||
{
|
||||
return 0;
|
||||
}
|
47
crypto/ml_dsa/ml_dsa_key.h
Normal file
47
crypto/ml_dsa/ml_dsa_key.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/e_os2.h>
|
||||
#include "crypto/types.h"
|
||||
#include "internal/refcount.h"
|
||||
#include "ml_dsa_vector.h"
|
||||
|
||||
struct ml_dsa_key_st {
|
||||
|
||||
CRYPTO_REF_COUNT references;
|
||||
OSSL_LIB_CTX *libctx;
|
||||
const ML_DSA_PARAMS *params;
|
||||
char *propq;
|
||||
|
||||
uint8_t rho[ML_DSA_RHO_BYTES]; /* public random seed */
|
||||
uint8_t tr[ML_DSA_TR_BYTES]; /* Pre-cached public key Hash */
|
||||
uint8_t K[ML_DSA_K_BYTES]; /* Private random seed for signing */
|
||||
/*
|
||||
* t0 is the Polynomial encoding of the 13 LSB of each coefficient of the
|
||||
* uncompressed public key polynomial t. This is saved as part of the
|
||||
* private key. It is column vector of K polynomials.
|
||||
*/
|
||||
VECTOR t0;
|
||||
/*
|
||||
* t1 is the Polynomial encoding of the 10 MSB of each coefficient of the
|
||||
* uncompressed public key polynomial t. This is saved as part of the
|
||||
* public key. It is column vector of K polynomials.
|
||||
* (There are 23 bits in q-modulus.. i.e 10 bits = 23 - 13)
|
||||
*/
|
||||
VECTOR t1;
|
||||
VECTOR s1; /* private secret of size L with short coefficients (-4..4) or (-2..2) */
|
||||
VECTOR s2; /* private secret of size K with short coefficients (-4..4) or (-2..2) */
|
||||
|
||||
/*
|
||||
* The encoded public and private keys, these are non NULL if the key
|
||||
* components are generated or loaded.
|
||||
*/
|
||||
uint8_t *pub_encoding;
|
||||
uint8_t *priv_encoding;
|
||||
};
|
149
crypto/ml_dsa/ml_dsa_key_compress.c
Normal file
149
crypto/ml_dsa/ml_dsa_key_compress.c
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include "ml_dsa_local.h"
|
||||
|
||||
/* Key Compression related functions (Rounding & hints) */
|
||||
|
||||
/**
|
||||
* @brief Decompose r into (r1, r0) such that r == r1 * 2^13 + r0 mod q
|
||||
* See FIPS 204, Algorithm 35, Power2Round()
|
||||
*
|
||||
* Note: that this code is more complex than the FIPS 204 spec since it keeps
|
||||
* r0 as a positive number
|
||||
*
|
||||
* r mod +- 2^13 is defined as having a range of -4095..4096
|
||||
*
|
||||
* i.e for r = 0..4096 r1 = 0 and r0 = 0..4096
|
||||
* at r = 4097..8191 r1 = 1 and r0 = -4095..0
|
||||
* (but since r0 is kept positive it effectively adds q and then reduces by q if needed)
|
||||
* Similarly for the range r = 8192..8192+4096 r1=1 and r0=0..4096
|
||||
* & 12289..16383 r1=2 and r0=-4095..0
|
||||
*
|
||||
* @param r is in the range 0..q-1
|
||||
* @param r1 The returned top 10 MSB (i.e it ranges from 0..1023)
|
||||
* @param r0 The remainder in the range (0..4096 or q-4095..q-1)
|
||||
* So r0 has an effective range of 8192 (i.e. 13 bits).
|
||||
*/
|
||||
void ossl_ml_dsa_key_compress_power2_round(uint32_t r, uint32_t *r1, uint32_t *r0)
|
||||
{
|
||||
unsigned int mask;
|
||||
uint32_t r0_adjusted, r1_adjusted;
|
||||
|
||||
*r1 = r >> ML_DSA_D_BITS; /* top 13 bits */
|
||||
*r0 = r - (*r1 << ML_DSA_D_BITS); /* The remainder mod q */
|
||||
|
||||
r0_adjusted = mod_sub(*r0, 1 << ML_DSA_D_BITS);
|
||||
r1_adjusted = *r1 + 1;
|
||||
|
||||
/* Mask is set iff r0 > (2^(dropped_bits))/2. */
|
||||
mask = constant_time_lt((uint32_t)(1 << (ML_DSA_D_BITS - 1)), *r0);
|
||||
/* r0 = mask ? r0_adjusted : r0 */
|
||||
*r0 = constant_time_select_int(mask, r0_adjusted, *r0);
|
||||
/* r1 = mask ? r1_adjusted : r1 */
|
||||
*r1 = constant_time_select_int(mask, r1_adjusted, *r1);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief return the r1 component of Decomposing r into (r1, r0) such that
|
||||
* r == r1 * (2 & gamma) + r0 mod q
|
||||
* See FIPS 204, Algorithm 37, HighBits()
|
||||
*
|
||||
* @param r A value to decompose in the range (0..q-1)
|
||||
* @param gamma2 Depending on the algorithm gamma2 is either (q-1)/32 or (q-1)/88
|
||||
* @returns r1
|
||||
*/
|
||||
uint32_t ossl_ml_dsa_key_compress_high_bits(uint32_t r, uint32_t gamma2)
|
||||
{
|
||||
uint32_t r1 = (r + 127) >> 7;
|
||||
|
||||
/* TODO - figure out what this is doing */
|
||||
if (gamma2 == ML_DSA_Q_MINUS1_DIV32) {
|
||||
r1 = (r1 * 1025 + (1 << 21)) >> 22;
|
||||
r1 &= 15; /* mod 16 */
|
||||
return r1;
|
||||
} else {
|
||||
r1 = (r1 * 11275 + (1 << 23)) >> 24;
|
||||
r1 ^= ((43 - r1) >> 31) & r1;
|
||||
return r1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO - document this
|
||||
* See FIPS 204, Algorithm 36, Decompose()
|
||||
*/
|
||||
void ossl_ml_dsa_key_compress_decompose(uint32_t r, uint32_t gamma2,
|
||||
uint32_t *r1, int32_t *r0)
|
||||
{
|
||||
*r1 = ossl_ml_dsa_key_compress_high_bits(r, gamma2);
|
||||
|
||||
*r0 = r - *r1 * 2 * (int32_t)gamma2;
|
||||
*r0 -= (((int32_t)ML_DSA_Q_MINUS1_DIV2 - *r0) >> 31) & (int32_t)ML_DSA_Q;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO - document this
|
||||
* See FIPS 204, Algorithm 38, LowBits()
|
||||
*/
|
||||
int32_t ossl_ml_dsa_key_compress_low_bits(uint32_t r, uint32_t gamma2)
|
||||
{
|
||||
uint32_t r1;
|
||||
int32_t r0;
|
||||
|
||||
ossl_ml_dsa_key_compress_decompose(r, gamma2, &r1, &r0);
|
||||
return r0;
|
||||
}
|
||||
|
||||
/*
|
||||
* See FIPS 204, Algorithm 39 (`MakeHint`).
|
||||
*
|
||||
* In the spec this takes two arguments, z and r, and is called with
|
||||
* z = -ct0
|
||||
* r = w - cs2 + ct0
|
||||
*
|
||||
* It then computes HighBits (algorithm 37) of z and z+r.
|
||||
* But z+r is just w - cs2, so this takes three arguments and saves an addition.
|
||||
*/
|
||||
int32_t ossl_ml_dsa_key_compress_make_hint(uint32_t ct0, uint32_t cs2,
|
||||
uint32_t gamma2, uint32_t w)
|
||||
{
|
||||
uint32_t r_plus_z = mod_sub(w, cs2);
|
||||
uint32_t r = reduce_once(r_plus_z + ct0);
|
||||
|
||||
return ossl_ml_dsa_key_compress_high_bits(r, gamma2)
|
||||
!= ossl_ml_dsa_key_compress_high_bits(r_plus_z, gamma2);
|
||||
}
|
||||
|
||||
/*
|
||||
* FIPS 204, Algorithm 40 (`UseHint`).
|
||||
* This is not constant time
|
||||
*/
|
||||
uint32_t ossl_ml_dsa_key_compress_use_hint(uint32_t hint, uint32_t r,
|
||||
uint32_t gamma2)
|
||||
{
|
||||
uint32_t r1;
|
||||
int32_t r0;
|
||||
|
||||
ossl_ml_dsa_key_compress_decompose(r, gamma2, &r1, &r0);
|
||||
|
||||
if (hint == 0)
|
||||
return r1;
|
||||
|
||||
if (gamma2 == ((ML_DSA_Q - 1) / 32)) {
|
||||
/* m = 16, thus |mod m| in the spec turns into |& 15| */
|
||||
return r0 > 0 ? (r1 + 1) & 15 : (r1 - 1) & 15;
|
||||
} else {
|
||||
/* m = 44 if gamma2 = ((q - 1) / 88) */
|
||||
if (r1 > 0)
|
||||
return (r1 == 43) ? 0 : r1 + 1;
|
||||
else
|
||||
return (r1 == 0) ? 43 : r1 - 1;
|
||||
}
|
||||
}
|
110
crypto/ml_dsa/ml_dsa_local.h
Normal file
110
crypto/ml_dsa/ml_dsa_local.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#ifndef OSSL_CRYPTO_ML_DSA_LOCAL_H
|
||||
# define OSSL_CRYPTO_ML_DSA_LOCAL_H
|
||||
|
||||
# include "crypto/ml_dsa.h"
|
||||
# include "internal/constant_time.h"
|
||||
|
||||
/* Maximimum size of the 'A' matrix */
|
||||
# define ML_DSA_K_MAX 8
|
||||
# define ML_DSA_L_MAX 7
|
||||
|
||||
# define ML_DSA_SEED_BYTES 32
|
||||
# define ML_DSA_Q 8380417 /* The modulus is 23 bits (2^23 - 2^13 + 1) */
|
||||
# define ML_DSA_Q_MINUS1_DIV2 ((ML_DSA_Q - 1) / 2)
|
||||
# define ML_DSA_Q_MINUS1_DIV32 ((ML_DSA_Q - 1) / 32)
|
||||
# define ML_DSA_Q_BITS 23
|
||||
# define ML_DSA_Q_INV 58728449 /* q^-1 satisfies: q^-1 * q = 1 mod 2^32 */
|
||||
# define ML_DSA_Q_NEG_INV 4236238847 /* Inverse of -q modulo 2^32 */
|
||||
# define ML_DSA_DEGREE_INV_MONTGOMERY 41978 /* Inverse of 256 mod q, in Montgomery form. */
|
||||
|
||||
# define ML_DSA_D_BITS 13 /* The number of bits dropped from t */
|
||||
# define ML_DSA_NUM_POLY_COEFFICIENTS 256 /* The number of coefficients in the polynomials */
|
||||
# define ML_DSA_RHO_BYTES 32 /* p = Public Random Seed */
|
||||
# define ML_DSA_PRIV_SEED_BYTES 64 /* p' = Private random seed */
|
||||
# define ML_DSA_K_BYTES 32 /* K = Private random seed for signing */
|
||||
# define ML_DSA_TR_BYTES 64 /* Hash of public key used for signing */
|
||||
# define ML_DSA_MU_BYTES 64
|
||||
# define ML_DSA_RHO_PRIME_BYTES 64
|
||||
|
||||
typedef struct ml_dsa_params_st ML_DSA_PARAMS;
|
||||
typedef struct poly_st POLY;
|
||||
typedef struct vector_st VECTOR;
|
||||
typedef struct matrix_st MATRIX;
|
||||
|
||||
/*
|
||||
* FIPS 204 ML-DSA algorithms have different parameters which includes:
|
||||
* - A set of constants (Section 4. contains 3 parameter sets)
|
||||
*
|
||||
* - OpenSSL also uses pre-fetched EVP_MD_CTX objects for Hashing purposes.
|
||||
*
|
||||
* ML_DSA_CTX is a container to hold all these objects. This object is
|
||||
* resolved early and is then passed to most ML-DSA related functions.
|
||||
*/
|
||||
struct ml_dsa_ctx_st {
|
||||
const ML_DSA_PARAMS *params;
|
||||
EVP_MD_CTX *h_ctx; /* SHAKE-256 */
|
||||
EVP_MD_CTX *g_ctx; /* SHAKE-128 */
|
||||
};
|
||||
|
||||
int ossl_ml_dsa_sample_expandA(EVP_MD_CTX *g_ctx, const uint8_t *rho, MATRIX *out);
|
||||
int ossl_ml_dsa_sample_expandS(EVP_MD_CTX *h_ctx, int eta, const uint8_t *seed,
|
||||
VECTOR *s1, VECTOR *s2);
|
||||
void ossl_ml_dsa_poly_ntt(POLY *s);
|
||||
void ossl_ml_dsa_poly_ntt_inverse(POLY *s);
|
||||
void ossl_ml_dsa_poly_ntt_mult(const POLY *lhs, const POLY *rhs, POLY *out);
|
||||
|
||||
void ossl_ml_dsa_key_compress_power2_round(uint32_t r, uint32_t *r1, uint32_t *r0);
|
||||
uint32_t ossl_ml_dsa_key_compress_high_bits(uint32_t r, uint32_t gamma2);
|
||||
void ossl_ml_dsa_key_compress_decompose(uint32_t r, uint32_t gamma2,
|
||||
uint32_t *r1, int32_t *r0);
|
||||
void ossl_ml_dsa_key_compress_decompose(uint32_t r, uint32_t gamma2,
|
||||
uint32_t *r1, int32_t *r0);
|
||||
int32_t ossl_ml_dsa_key_compress_low_bits(uint32_t r, uint32_t gamma2);
|
||||
int32_t ossl_ml_dsa_key_compress_make_hint(uint32_t ct0, uint32_t cs2,
|
||||
uint32_t gamma2, uint32_t w);
|
||||
uint32_t ossl_ml_dsa_key_compress_use_hint(uint32_t hint, uint32_t r,
|
||||
uint32_t gamma2);
|
||||
|
||||
int ossl_ml_dsa_pk_encode(ML_DSA_KEY *key);
|
||||
int ossl_ml_dsa_pk_decode(const uint8_t *in, size_t in_len, ML_DSA_KEY *key);
|
||||
int ossl_ml_dsa_sk_encode(ML_DSA_KEY *key);
|
||||
int ossl_ml_dsa_sk_decode(const uint8_t *in, size_t in_len, ML_DSA_KEY *key);
|
||||
|
||||
/*
|
||||
* @brief Reduces x mod q in constant time
|
||||
* i.e. return x < q ? x : x - q;
|
||||
*
|
||||
* @param x Where x is assumed to be in the range 0 <= x < 2*q
|
||||
* @returns the difference in the range 0..q-1
|
||||
*/
|
||||
static ossl_inline ossl_unused uint32_t reduce_once(uint32_t x)
|
||||
{
|
||||
return constant_time_select_32(constant_time_lt(x, ML_DSA_Q), x, x - ML_DSA_Q);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Calculate The positive value of (a-b) mod q in constant time.
|
||||
*
|
||||
* a - b mod q gives a value in the range -(q-1)..(q-1)
|
||||
* By adding q we get a range of 1..(2q-1).
|
||||
* Reducing this once then gives the range 0..q-1
|
||||
*
|
||||
* @param a The minuend assumed to be in the range 0..q-1
|
||||
* @param b The subtracthend assumed to be in the range 0..q-1.
|
||||
* @returns The value (q + a - b) mod q
|
||||
*/
|
||||
static ossl_inline ossl_unused uint32_t mod_sub(uint32_t a, uint32_t b)
|
||||
{
|
||||
return reduce_once(ML_DSA_Q + a - b);
|
||||
}
|
||||
|
||||
#endif /* OSSL_CRYPTO_ML_DSA_LOCAL_H */
|
38
crypto/ml_dsa/ml_dsa_matrix.c
Normal file
38
crypto/ml_dsa/ml_dsa_matrix.c
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_vector.h"
|
||||
#include "ml_dsa_matrix.h"
|
||||
|
||||
/*
|
||||
* Matrix multiply of a k*l matrix of polynomials by a 1 * l vector of
|
||||
* polynomials to produce a 1 * k vector of polynomial results.
|
||||
* i.e. t = a * s
|
||||
*
|
||||
* @param a A k * l matrix of polynomials in NTT form
|
||||
* @param s A 1 * l vector of polynomials in NTT form
|
||||
* @param t 1 * k vector of polynomial results in NTT form
|
||||
*/
|
||||
void ossl_ml_dsa_matrix_mult_vector(const MATRIX *a, const VECTOR *s,
|
||||
VECTOR *t)
|
||||
{
|
||||
size_t i, j;
|
||||
|
||||
vector_zero(t);
|
||||
|
||||
for (i = 0; i < a->k; i++) {
|
||||
for (j = 0; j < a->l; j++) {
|
||||
POLY product;
|
||||
|
||||
ossl_ml_dsa_poly_ntt_mult(&a->m_poly[i][j], &s->poly[j], &product);
|
||||
poly_add(&product, &t->poly[i], &t->poly[i]);
|
||||
}
|
||||
}
|
||||
}
|
24
crypto/ml_dsa/ml_dsa_matrix.h
Normal file
24
crypto/ml_dsa/ml_dsa_matrix.h
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
/* A 'k' by 'l' Matrix object ('k' rows and 'l' columns) containing polynomial entries */
|
||||
struct matrix_st {
|
||||
POLY m_poly[ML_DSA_K_MAX][ML_DSA_L_MAX];
|
||||
size_t k, l;
|
||||
};
|
||||
|
||||
static ossl_inline ossl_unused void
|
||||
matrix_init(MATRIX *m, size_t k, size_t l)
|
||||
{
|
||||
m->k = k;
|
||||
m->l = l;
|
||||
}
|
||||
|
||||
void ossl_ml_dsa_matrix_mult_vector(const MATRIX *matrix_kl, const VECTOR *vl,
|
||||
VECTOR *vk);
|
197
crypto/ml_dsa/ml_dsa_ntt.c
Normal file
197
crypto/ml_dsa/ml_dsa_ntt.c
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_poly.h"
|
||||
|
||||
/*
|
||||
* This file has multiple parts required for fast matrix multiplication,
|
||||
* 1) NTT (See https://eprint.iacr.org/2024/585.pdf)
|
||||
* NTT and NTT inverse transformations are Discrete Fourier Transforms in a
|
||||
* polynomial ring. Fast-Fourier Transformations can then be applied to make
|
||||
* multiplications n log(n). This uses the symmetry of the transformation to
|
||||
* reduce computations.
|
||||
*
|
||||
* 2) Montgomery multiplication
|
||||
* The multiplication of a.b mod q requires division by q which is a slow operation.
|
||||
*
|
||||
* When many multiplications mod q are required montgomery multiplication
|
||||
* can be used. This requires a number R > N such that R & N are coprime
|
||||
* (i.e. GCD(N, R) = 1), so that division happens using R instead of q.
|
||||
* If r is a power of 2 then this division can be done as a bit shift.
|
||||
*
|
||||
* Given that q = 2^23 - 2^13 + 1
|
||||
* We can chose a Montgomery multiplier of R = 2^32.
|
||||
*
|
||||
* To transform a into montgomery form m
|
||||
* m = a mod q * ((2^32)*(2^32) mod q)
|
||||
*/
|
||||
|
||||
/*
|
||||
* The table in FIPS 204 Appendix B uses the following formula
|
||||
* zeta[k]= 1753^bitrev(k) mod q for (k = 1..255) (The first value is not used).
|
||||
*
|
||||
* As this implementation uses montgomery form with a multiplier of 2^32.
|
||||
* The values need to be transformed i.e.
|
||||
*
|
||||
* zetasMontgomery[k] = reduce_montgomery(zeta[k] * (2^32 * 2^32 mod(q)))
|
||||
* reduce_montgomery() is defined below..
|
||||
*/
|
||||
static const uint32_t zetas_montgomery[256] = {
|
||||
4193792, 25847, 5771523, 7861508, 237124, 7602457, 7504169, 466468,
|
||||
1826347, 2353451, 8021166, 6288512, 3119733, 5495562, 3111497, 2680103,
|
||||
2725464, 1024112, 7300517, 3585928, 7830929, 7260833, 2619752, 6271868,
|
||||
6262231, 4520680, 6980856, 5102745, 1757237, 8360995, 4010497, 280005,
|
||||
2706023, 95776, 3077325, 3530437, 6718724, 4788269, 5842901, 3915439,
|
||||
4519302, 5336701, 3574422, 5512770, 3539968, 8079950, 2348700, 7841118,
|
||||
6681150, 6736599, 3505694, 4558682, 3507263, 6239768, 6779997, 3699596,
|
||||
811944, 531354, 954230, 3881043, 3900724, 5823537, 2071892, 5582638,
|
||||
4450022, 6851714, 4702672, 5339162, 6927966, 3475950, 2176455, 6795196,
|
||||
7122806, 1939314, 4296819, 7380215, 5190273, 5223087, 4747489, 126922,
|
||||
3412210, 7396998, 2147896, 2715295, 5412772, 4686924, 7969390, 5903370,
|
||||
7709315, 7151892, 8357436, 7072248, 7998430, 1349076, 1852771, 6949987,
|
||||
5037034, 264944, 508951, 3097992, 44288, 7280319, 904516, 3958618,
|
||||
4656075, 8371839, 1653064, 5130689, 2389356, 8169440, 759969, 7063561,
|
||||
189548, 4827145, 3159746, 6529015, 5971092, 8202977, 1315589, 1341330,
|
||||
1285669, 6795489, 7567685, 6940675, 5361315, 4499357, 4751448, 3839961,
|
||||
2091667, 3407706, 2316500, 3817976, 5037939, 2244091, 5933984, 4817955,
|
||||
266997, 2434439, 7144689, 3513181, 4860065, 4621053, 7183191, 5187039,
|
||||
900702, 1859098, 909542, 819034, 495491, 6767243, 8337157, 7857917,
|
||||
7725090, 5257975, 2031748, 3207046, 4823422, 7855319, 7611795, 4784579,
|
||||
342297, 286988, 5942594, 4108315, 3437287, 5038140, 1735879, 203044,
|
||||
2842341, 2691481, 5790267, 1265009, 4055324, 1247620, 2486353, 1595974,
|
||||
4613401, 1250494, 2635921, 4832145, 5386378, 1869119, 1903435, 7329447,
|
||||
7047359, 1237275, 5062207, 6950192, 7929317, 1312455, 3306115, 6417775,
|
||||
7100756, 1917081, 5834105, 7005614, 1500165, 777191, 2235880, 3406031,
|
||||
7838005, 5548557, 6709241, 6533464, 5796124, 4656147, 594136, 4603424,
|
||||
6366809, 2432395, 2454455, 8215696, 1957272, 3369112, 185531, 7173032,
|
||||
5196991, 162844, 1616392, 3014001, 810149, 1652634, 4686184, 6581310,
|
||||
5341501, 3523897, 3866901, 269760, 2213111, 7404533, 1717735, 472078,
|
||||
7953734, 1723600, 6577327, 1910376, 6712985, 7276084, 8119771, 4546524,
|
||||
5441381, 6144432, 7959518, 6094090, 183443, 7403526, 1612842, 4834730,
|
||||
7826001, 3919660, 8332111, 7018208, 3937738, 1400424, 7534263, 1976782
|
||||
};
|
||||
|
||||
/*
|
||||
* @brief When multiplying 2 numbers mod q that are in montgomery form, the
|
||||
* product mod q needs to be multiplied by 2^-32 to be in montgomery form.
|
||||
* See FIPS 204, Algorithm 49, MontgomeryReduce()
|
||||
* Note it is slightly different due to the input range being positive
|
||||
*
|
||||
* @param a is the result of a multiply of 2 numbers in montgomery form,
|
||||
* in the range 0...(2^32)*q
|
||||
* @returns The Montgomery form of 'a' with multiplier 2^32 in the range 0..q-1
|
||||
* The result is congruent to x * 2^-32 mod q
|
||||
*/
|
||||
static uint32_t reduce_montgomery(uint64_t a)
|
||||
{
|
||||
uint64_t t = (uint32_t)a * (uint32_t)ML_DSA_Q_NEG_INV; /* a * -qinv */
|
||||
uint64_t b = a + t * ML_DSA_Q; /* a - t * q */
|
||||
uint32_t c = b >> 32; /* /2^32 = 0..2q */
|
||||
|
||||
return reduce_once(c); /* 0..q */
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Multiply two polynomials in the number theoretically transformed state.
|
||||
* See FIPS 204, Algorithm 45, MultiplyNTT()
|
||||
* This function has been modified to use montgomery multiplication
|
||||
*
|
||||
* @param lhs A polynomial multiplicand
|
||||
* @param rhs A polynomial multiplier
|
||||
* @param out The returned result of the polynomial multiply
|
||||
*/
|
||||
void ossl_ml_dsa_poly_ntt_mult(const POLY *lhs, const POLY *rhs, POLY *out)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ML_DSA_NUM_POLY_COEFFICIENTS; i++)
|
||||
out->coeff[i] =
|
||||
reduce_montgomery((uint64_t)lhs->coeff[i] * (uint64_t)rhs->coeff[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* In place number theoretic transform of a given polynomial.
|
||||
*
|
||||
* See FIPS 204, Algorithm 41, NTT()
|
||||
* This function uses montgomery multiplication.
|
||||
*
|
||||
* @param p a polynomial that is used as the input, that is replaced with
|
||||
* the NTT of the polynomial
|
||||
*/
|
||||
void ossl_ml_dsa_poly_ntt(POLY *p)
|
||||
{
|
||||
int i, j, k;
|
||||
int step;
|
||||
int offset = ML_DSA_NUM_POLY_COEFFICIENTS;
|
||||
|
||||
/* Step: 1, 2, 4, 8, ..., 128 */
|
||||
for (step = 1; step < ML_DSA_NUM_POLY_COEFFICIENTS; step <<= 1) {
|
||||
k = 0;
|
||||
offset >>= 1; /* Offset: 128, 64, 32, 16, ..., 1 */
|
||||
for (i = 0; i < step; i++) {
|
||||
const uint32_t z_step_root = zetas_montgomery[step + i];
|
||||
|
||||
for (j = k; j < k + offset; j++) {
|
||||
uint32_t w_even = p->coeff[j];
|
||||
uint32_t t_odd =
|
||||
reduce_montgomery((uint64_t)z_step_root
|
||||
* (uint64_t)p->coeff[j + offset]);
|
||||
|
||||
p->coeff[j] = reduce_once(w_even + t_odd);
|
||||
p->coeff[j + offset] = mod_sub(w_even, t_odd);
|
||||
}
|
||||
k += 2 * offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief In place inverse number theoretic transform of a given polynomial.
|
||||
* See FIPS 204, Algorithm 42, NTT^-1()
|
||||
*
|
||||
* @param p a polynomial that is used as the input, that is overwritten with
|
||||
* the inverse of the NTT.
|
||||
*/
|
||||
void ossl_ml_dsa_poly_ntt_inverse(POLY *p)
|
||||
{
|
||||
/*
|
||||
* Step: 128, 64, 32, 16, ..., 1
|
||||
* Offset: 1, 2, 4, 8, ..., 128
|
||||
*/
|
||||
int i, j, k, offset, step = ML_DSA_NUM_POLY_COEFFICIENTS;
|
||||
/*
|
||||
* The multiplicative inverse of 256 mod q, in Montgomery form is
|
||||
* ((256^-1 mod q) * ((2^32 * 2^32) mod q)) mod q = (8347681 * 2365951) mod 8380417
|
||||
*/
|
||||
static const uint32_t inverse_degree_montgomery = 41978;
|
||||
|
||||
for (offset = 1; offset < ML_DSA_NUM_POLY_COEFFICIENTS; offset <<= 1) {
|
||||
step >>= 1;
|
||||
k = 0;
|
||||
for (i = 0; i < step; i++) {
|
||||
const uint32_t step_root =
|
||||
ML_DSA_Q - zetas_montgomery[step + (step - 1 - i)];
|
||||
|
||||
for (j = k; j < k + offset; j++) {
|
||||
uint32_t even = p->coeff[j];
|
||||
uint32_t odd = p->coeff[j + offset];
|
||||
|
||||
p->coeff[j] = reduce_once(odd + even);
|
||||
p->coeff[j + offset] =
|
||||
reduce_montgomery((uint64_t)step_root
|
||||
* (uint64_t)(ML_DSA_Q + even - odd));
|
||||
}
|
||||
k += 2 * offset;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < ML_DSA_NUM_POLY_COEFFICIENTS; i++)
|
||||
p->coeff[i] = reduce_montgomery((uint64_t)p->coeff[i] *
|
||||
(uint64_t)inverse_degree_montgomery);
|
||||
}
|
39
crypto/ml_dsa/ml_dsa_params.c
Normal file
39
crypto/ml_dsa/ml_dsa_params.c
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_params.h"
|
||||
|
||||
/*
|
||||
* See FIPS 204 Section 4 Table 1 & Table 2
|
||||
* tau strength gamma1 k l eta beta omega sc sklen pklen siglen
|
||||
*/
|
||||
#define OSSL_ML_DSA_65 49, 192, 1 << 19, 6, 5, 4, 196, 55, 3, 4032, 1952, 3309
|
||||
|
||||
static const ML_DSA_PARAMS ml_dsa_params[] = {
|
||||
{"ML-DSA-65", OSSL_ML_DSA_65},
|
||||
{NULL},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A getter to convert an algorithm name into a ML_DSA_PARAMS object
|
||||
*/
|
||||
const ML_DSA_PARAMS *ossl_ml_dsa_params_get(const char *alg)
|
||||
{
|
||||
const ML_DSA_PARAMS *p;
|
||||
|
||||
if (alg == NULL)
|
||||
return NULL;
|
||||
for (p = ml_dsa_params; p->alg != NULL; ++p) {
|
||||
if (strcmp(p->alg, alg) == 0)
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
31
crypto/ml_dsa/ml_dsa_params.h
Normal file
31
crypto/ml_dsa/ml_dsa_params.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/e_os2.h>
|
||||
|
||||
/*
|
||||
* Refer to FIPS 204 Section 4 Parameter sets.
|
||||
* Fields that are shared between all algorithms (such as q & d) have been omitted.
|
||||
*/
|
||||
struct ml_dsa_params_st {
|
||||
const char *alg;
|
||||
int tau; /* Number of +/-1's in polynomial c */
|
||||
int strength; /* The collision strength */
|
||||
int gamma1; /* coefficient range of y */
|
||||
size_t k, l; /* matrix dimensions of 'A' */
|
||||
int eta; /* Private key range */
|
||||
int beta; /* tau * eta */
|
||||
int omega; /* Number of 1's in the hint 'h' */
|
||||
int security_category; /* Category is related to Security strength */
|
||||
size_t sk_len; /* private key size */
|
||||
size_t pk_len; /* public key size */
|
||||
size_t sig_len; /* signature size */
|
||||
};
|
||||
|
||||
const struct ml_dsa_params_st *ossl_ml_dsa_params_get(const char *alg);
|
80
crypto/ml_dsa/ml_dsa_poly.h
Normal file
80
crypto/ml_dsa/ml_dsa_poly.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#define ML_DSA_NUM_POLY_COEFFICIENTS 256
|
||||
|
||||
/* Polynomial object with 256 coefficients. The coefficients are unsigned 32 bits */
|
||||
struct poly_st {
|
||||
uint32_t coeff[ML_DSA_NUM_POLY_COEFFICIENTS];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Polynomial addition.
|
||||
*
|
||||
* @param lhs A polynomial with coefficients in the range (0..q-1)
|
||||
* @param rhs A polynomial with coefficients in the range (0..q-1) to add
|
||||
* to the 'lhs'.
|
||||
* @param out The returned addition result with the coefficients all in the
|
||||
* range 0..q-1
|
||||
*/
|
||||
static ossl_inline ossl_unused void
|
||||
poly_add(const POLY *lhs, const POLY *rhs, POLY *out)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ML_DSA_NUM_POLY_COEFFICIENTS; i++)
|
||||
out->coeff[i] = reduce_once(lhs->coeff[i] + rhs->coeff[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Polynomial subtraction.
|
||||
*
|
||||
* @param lhs A polynomial with coefficients in the range (0..q-1)
|
||||
* @param rhs A polynomial with coefficients in the range (0..q-1) to subtract
|
||||
* from the 'lhs'.
|
||||
* @param out The returned subtraction result with the coefficients all in the
|
||||
* range 0..q-1
|
||||
*/
|
||||
static ossl_inline ossl_unused void
|
||||
poly_sub(const POLY *lhs, const POLY *rhs, POLY *out)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ML_DSA_NUM_POLY_COEFFICIENTS; i++)
|
||||
out->coeff[i] = mod_sub(lhs->coeff[i], rhs->coeff[i]);
|
||||
}
|
||||
|
||||
/* @returns 1 if the polynomials are equal, or 0 otherwise */
|
||||
static ossl_inline ossl_unused int
|
||||
poly_equal(const POLY *a, const POLY *b)
|
||||
{
|
||||
return CRYPTO_memcmp(a, b, sizeof(*a)) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decompose the coefficients of a polynomial into (r1, r0) such that
|
||||
* coeff[i] == t1[i] * 2^13 + t0[i] mod q
|
||||
* See FIPS 204, Algorithm 35, Power2Round()
|
||||
*
|
||||
* @param t A polynomial containing coefficients in the range 0..q-1
|
||||
* @param t1 The returned polynomial containing coefficients that represent
|
||||
* the top 10 MSB of each coefficient in t (i.e each ranging from 0..1023)
|
||||
* @param t0 The remainder coefficients of t in the range (0..4096 or q-4095..q-1)
|
||||
* Each t0 coefficient has an effective range of 8192 (i.e. 13 bits).
|
||||
*/
|
||||
static ossl_inline ossl_unused void
|
||||
poly_power2_round(const POLY *t, POLY *t1, POLY *t0)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ML_DSA_NUM_POLY_COEFFICIENTS; i++)
|
||||
ossl_ml_dsa_key_compress_power2_round(t->coeff[i],
|
||||
&t1->coeff[i], &t0->coeff[i]);
|
||||
}
|
262
crypto/ml_dsa/ml_dsa_sample.c
Normal file
262
crypto/ml_dsa/ml_dsa_sample.c
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include "ml_dsa_local.h"
|
||||
#include "ml_dsa_vector.h"
|
||||
#include "ml_dsa_matrix.h"
|
||||
|
||||
#define SHAKE128_BLOCKSIZE 168
|
||||
#define SHAKE256_BLOCKSIZE 136
|
||||
|
||||
typedef int (COEFF_FROM_NIBBLE_FUNC)(uint32_t nibble, uint32_t *out);
|
||||
|
||||
static COEFF_FROM_NIBBLE_FUNC coeff_from_nibble_4;
|
||||
static COEFF_FROM_NIBBLE_FUNC coeff_from_nibble_2;
|
||||
|
||||
/**
|
||||
* @brief Combine 3 bytes to form an coefficient.
|
||||
* See FIPS 204, Algorithm 14, CoeffFromThreeBytes()
|
||||
*
|
||||
* This is not constant time as it is used to generate the matrix A which is public.
|
||||
*
|
||||
* @param s A byte array of 3 uniformly distributed bytes.
|
||||
* @param out The returned coefficient in the range 0..q-1.
|
||||
* @returns 1 if the value is less than q or 0 otherwise.
|
||||
* This is used for rejection sampling.
|
||||
*/
|
||||
static ossl_inline int coeff_from_three_bytes(const uint8_t *s, uint32_t *out)
|
||||
{
|
||||
/* Zero out the top bit of the 3rd byte to get a value in the range 0..2^23-1) */
|
||||
*out = (uint32_t)s[0] | ((uint32_t)s[1] << 8) | (((uint32_t)s[2] & 0x7f) << 16);
|
||||
return *out < ML_DSA_Q;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a value in the range (q-4..0..4)
|
||||
* See FIPS 204, Algorithm 15, CoeffFromHalfByte() where eta = 4
|
||||
* Note the FIPS 204 code uses the range -4..4 (whereas this code adds q to the
|
||||
* negative numbers).
|
||||
*
|
||||
* @param nibble A value in the range 0..15
|
||||
* @param out The returned value if the range (q-4)..0..4 if nibble is < 9
|
||||
* @returns 1 nibble was in range, or 0 if the nibble was rejected.
|
||||
*/
|
||||
static ossl_inline int coeff_from_nibble_4(uint32_t nibble, uint32_t *out)
|
||||
{
|
||||
/*
|
||||
* This is not constant time but will not leak any important info since
|
||||
* the value is either chosen or thrown away.
|
||||
*/
|
||||
if (value_barrier_32(nibble < 9)) {
|
||||
*out = mod_sub(4, nibble);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a value in the range (q-2..0..2)
|
||||
* See FIPS 204, Algorithm 15, CoeffFromHalfByte() where eta = 2
|
||||
* Note the FIPS 204 code uses the range -2..2 (whereas this code adds q to the
|
||||
* negative numbers).
|
||||
*
|
||||
* @param nibble A value in the range 0..15
|
||||
* @param out The returned value if the range (q-2)..0..2 if nibble is < 15
|
||||
* @returns 1 nibble was in range, or 0 if the nibble was rejected.
|
||||
*/
|
||||
static ossl_inline int coeff_from_nibble_2(uint32_t nibble, uint32_t *out)
|
||||
{
|
||||
if (value_barrier_32(nibble < 15)) {
|
||||
*out = mod_sub(2, nibble % 5);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Use a seed value to generate a polynomial with coefficients in the
|
||||
* range of 0..q-1 using rejection sampling.
|
||||
* SHAKE128 is used to absorb the seed, and then sequences of 3 sample bytes are
|
||||
* squeezed to try to produce coefficients.
|
||||
* The SHAKE128 stream is used to get uniformly distributed elements.
|
||||
* This algorithm is used for matrix expansion and only operates on public inputs.
|
||||
*
|
||||
* See FIPS 204, Algorithm 30, RejNTTPoly()
|
||||
*
|
||||
* @param g_ctx A pre-fetched SHAKE128 context used for sampling the seed.
|
||||
* @param seed The seed to use for sampling.
|
||||
* @param seed_len The size of |seed|
|
||||
* @param out The returned polynomial with coefficients in the range of
|
||||
* 0..q-1. This range is required for NTT.
|
||||
* @returns 1 if the polynomial was successfully generated, or 0 if any of the
|
||||
* digest operations failed.
|
||||
*/
|
||||
static int rej_ntt_poly(EVP_MD_CTX *g_ctx,
|
||||
const uint8_t *seed, size_t seed_len, POLY *out)
|
||||
{
|
||||
int j = 0;
|
||||
uint8_t blocks[SHAKE128_BLOCKSIZE], *b, *end = blocks + sizeof(blocks);
|
||||
|
||||
if (EVP_DigestInit_ex2(g_ctx, NULL, NULL) != 1
|
||||
|| EVP_DigestUpdate(g_ctx, seed, seed_len) != 1)
|
||||
return 0;
|
||||
|
||||
while (1) {
|
||||
/*
|
||||
* Instead of just squeezing 3 bytes at a time, we grab a whole block
|
||||
* Note that the shake128 blocksize of 168 is divisible by 3.
|
||||
*/
|
||||
if (!EVP_DigestSqueeze(g_ctx, blocks, sizeof(blocks)))
|
||||
return 0;
|
||||
for (b = blocks; b < end; b += 3) {
|
||||
if (coeff_from_three_bytes(b, &(out->coeff[j]))) {
|
||||
if (++j >= ML_DSA_NUM_POLY_COEFFICIENTS)
|
||||
return 1; /* finished */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Use a seed value to generate a polynomial with coefficients in the
|
||||
* range of ((q-eta)..0..eta) using rejection sampling. eta is either 2 or 4.
|
||||
* SHAKE256 is used to absorb the seed, and then samples are squeezed.
|
||||
* See FIPS 204, Algorithm 31, RejBoundedPoly()
|
||||
*
|
||||
* @param h_ctx A pre-fetched SHAKE256 context used for sampling the seed.
|
||||
* @param coef_from_nibble A function that is dependent on eta, which takes a
|
||||
* nibble and tries to see if it is in the correct range.
|
||||
* @param seed The seed to use for sampling.
|
||||
* @param seed_len The size of |seed|
|
||||
* @param out The returned polynomial with coefficients in the range of
|
||||
* ((q-eta)..0..eta)
|
||||
* @returns 1 if the polynomial was successfully generated, or 0 if any of the
|
||||
* digest operations failed.
|
||||
*/
|
||||
static int rej_bounded_poly(EVP_MD_CTX *h_ctx,
|
||||
COEFF_FROM_NIBBLE_FUNC *coef_from_nibble,
|
||||
const uint8_t *seed, size_t seed_len, POLY *out)
|
||||
{
|
||||
int j = 0;
|
||||
uint32_t z0, z1;
|
||||
uint8_t blocks[SHAKE256_BLOCKSIZE], *b, *end = blocks + sizeof(blocks);
|
||||
|
||||
if (EVP_DigestInit_ex2(h_ctx, NULL, NULL) != 1
|
||||
|| EVP_DigestUpdate(h_ctx, seed, seed_len) != 1)
|
||||
return 0;
|
||||
|
||||
while (1) {
|
||||
/* Instead of just squeezing 1 byte at a time, we grab a whole block */
|
||||
if (!EVP_DigestSqueeze(h_ctx, blocks, sizeof(blocks)))
|
||||
return 0;
|
||||
for (b = blocks; b < end; b++) {
|
||||
z0 = *b & 0x0F; /* lower nibble of byte */
|
||||
z1 = *b >> 4; /* high nibble of byte */
|
||||
|
||||
if (coef_from_nibble(z0, &out->coeff[j])
|
||||
&& ++j >= ML_DSA_NUM_POLY_COEFFICIENTS)
|
||||
return 1;
|
||||
if (coef_from_nibble(z1, &out->coeff[j])
|
||||
&& ++j >= ML_DSA_NUM_POLY_COEFFICIENTS)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generate a k * l matrix that has uniformly distributed polynomial
|
||||
* elements using rejection sampling.
|
||||
* See FIPS 204, Algorithm 32, ExpandA()
|
||||
*
|
||||
* @param g_ctx A pre-fetched SHAKE128 context used for rejection sampling
|
||||
* seed values generated from the seed rho.
|
||||
* @param rho A 32 byte seed to generated the matrix from.
|
||||
* @param out The generated k * l matrix of polynomials with coefficients
|
||||
* in the range of 0..q-1.
|
||||
* @returns 1 if the matrix was generated, or 0 on error.
|
||||
*/
|
||||
int ossl_ml_dsa_sample_expandA(EVP_MD_CTX *g_ctx, const uint8_t *rho,
|
||||
MATRIX *out)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t i, j;
|
||||
uint8_t derived_seed[ML_DSA_RHO_BYTES + 2];
|
||||
|
||||
/* The seed used for each matrix element is rho + column_index + row_index */
|
||||
memcpy(derived_seed, rho, ML_DSA_RHO_BYTES);
|
||||
|
||||
for (i = 0; i < out->k; i++) {
|
||||
for (j = 0; j < out->l; j++) {
|
||||
derived_seed[ML_DSA_RHO_BYTES + 1] = (uint8_t)i;
|
||||
derived_seed[ML_DSA_RHO_BYTES] = (uint8_t)j;
|
||||
/* Generate the polynomial for each matrix element using a unique seed */
|
||||
if (!rej_ntt_poly(g_ctx, derived_seed, sizeof(derived_seed),
|
||||
&out->m_poly[i][j]))
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates 2 vectors using rejection sampling whose polynomial
|
||||
* coefficients are in the interval [q-eta..0..eta]
|
||||
*
|
||||
* See FIPS 204, Algorithm 33, ExpandS().
|
||||
* Note that in FIPS 204 the range -eta..eta is used.
|
||||
*
|
||||
* @param h_ctx A pre-fetched SHAKE256 context used for sampling the seed.
|
||||
* @param eta Is either 2 or 4, and determines the range of the coefficients for
|
||||
* s1 and s2.
|
||||
* @param seed A 64 byte seed to use for sampling.
|
||||
* @param s1 A 1 * l column vector containing polynomials with coefficients in
|
||||
* the range (q-eta)..0..eta
|
||||
* @param s2 A 1 * k column vector containing polynomials with coefficients in
|
||||
* the range (q-eta)..0..eta
|
||||
* @returns 1 if s1 and s2 were successfully generated, or 0 otherwise.
|
||||
*/
|
||||
int ossl_ml_dsa_sample_expandS(EVP_MD_CTX *h_ctx, int eta, const uint8_t *seed,
|
||||
VECTOR *s1, VECTOR *s2)
|
||||
{
|
||||
int ret = 0;
|
||||
size_t i;
|
||||
size_t l = s1->num_poly;
|
||||
size_t k = s2->num_poly;
|
||||
uint8_t derived_seed[ML_DSA_PRIV_SEED_BYTES + 2];
|
||||
COEFF_FROM_NIBBLE_FUNC *coef_from_nibble_fn;
|
||||
|
||||
coef_from_nibble_fn = (eta == 4) ? coeff_from_nibble_4 : coeff_from_nibble_2;
|
||||
|
||||
/*
|
||||
* Each polynomial generated uses a unique seed that consists of
|
||||
* seed + counter (where the counter is 2 bytes starting at 0)
|
||||
*/
|
||||
memcpy(derived_seed, seed, ML_DSA_PRIV_SEED_BYTES);
|
||||
derived_seed[ML_DSA_PRIV_SEED_BYTES] = 0;
|
||||
derived_seed[ML_DSA_PRIV_SEED_BYTES + 1] = 0;
|
||||
|
||||
for (i = 0; i < l; i++) {
|
||||
if (!rej_bounded_poly(h_ctx, coef_from_nibble_fn,
|
||||
derived_seed, sizeof(derived_seed), &s1->poly[i]))
|
||||
goto err;
|
||||
++derived_seed[ML_DSA_PRIV_SEED_BYTES];
|
||||
}
|
||||
for (i = 0; i < k; i++) {
|
||||
if (!rej_bounded_poly(h_ctx, coef_from_nibble_fn,
|
||||
derived_seed, sizeof(derived_seed), &s2->poly[i]))
|
||||
goto err;
|
||||
++derived_seed[ML_DSA_PRIV_SEED_BYTES];
|
||||
}
|
||||
ret = 1;
|
||||
err:
|
||||
return ret;
|
||||
}
|
117
crypto/ml_dsa/ml_dsa_vector.h
Normal file
117
crypto/ml_dsa/ml_dsa_vector.h
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include "ml_dsa_poly.h"
|
||||
|
||||
/* Either a 1 * l column vector or a k * 1 row vector of polynomial entries */
|
||||
struct vector_st {
|
||||
POLY poly[ML_DSA_K_MAX];
|
||||
size_t num_poly; /* Either k or l */
|
||||
};
|
||||
|
||||
/* @brief Set the number of polynomial elements that will be present in the vector */
|
||||
static ossl_inline ossl_unused
|
||||
void vector_init(VECTOR *v, size_t num_polys)
|
||||
{
|
||||
v->num_poly = num_polys;
|
||||
}
|
||||
|
||||
/* @brief zeroize a vectors polynomial coefficients */
|
||||
static ossl_inline ossl_unused
|
||||
void vector_zero(VECTOR *va)
|
||||
{
|
||||
memset(va->poly, 0, va->num_poly * sizeof(va->poly[0]));
|
||||
}
|
||||
|
||||
/* @brief add 2 vectors */
|
||||
static ossl_inline ossl_unused void
|
||||
vector_add(const VECTOR *lhs, const VECTOR *rhs, VECTOR *out)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < lhs->num_poly; i++)
|
||||
poly_add(&lhs->poly[i], &rhs->poly[i], &out->poly[i]);
|
||||
}
|
||||
|
||||
/* @brief subtract 2 vectors */
|
||||
static ossl_inline ossl_unused void
|
||||
vector_sub(const VECTOR *lhs, const VECTOR *rhs, VECTOR *out)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < lhs->num_poly; i++)
|
||||
poly_sub(&lhs->poly[i], &rhs->poly[i], &out->poly[i]);
|
||||
}
|
||||
|
||||
/* @brief multiply a vector by a polynomial */
|
||||
static ossl_inline ossl_unused void
|
||||
vector_ntt_mult_poly(const VECTOR *lhs, const POLY *rhs, VECTOR *out)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < lhs->num_poly; i++)
|
||||
ossl_ml_dsa_poly_ntt_mult(&lhs->poly[i], rhs, &out->poly[i]);
|
||||
}
|
||||
|
||||
/* @brief copy a vector */
|
||||
static ossl_inline ossl_unused void
|
||||
vector_copy(VECTOR *dst, const VECTOR *src)
|
||||
{
|
||||
dst->num_poly = src->num_poly;
|
||||
memcpy(dst->poly, src->poly, sizeof(src->poly));
|
||||
}
|
||||
|
||||
/* @brief return 1 if 2 vectors are equal, or 0 otherwise */
|
||||
static ossl_inline ossl_unused int
|
||||
vector_equal(const VECTOR *a, const VECTOR *b)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (a->num_poly != b->num_poly)
|
||||
return 0;
|
||||
for (i = 0; i < a->num_poly; ++i) {
|
||||
if (!poly_equal(a->poly + i, b->poly + i))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* @brief convert a vector in place into NTT form */
|
||||
static ossl_inline ossl_unused void
|
||||
vector_ntt(VECTOR *va)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < va->num_poly; i++)
|
||||
ossl_ml_dsa_poly_ntt(&va->poly[i]);
|
||||
}
|
||||
|
||||
/* @brief convert a vector in place into inverse NTT form */
|
||||
static ossl_inline ossl_unused void
|
||||
vector_ntt_inverse(VECTOR *va)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < va->num_poly; i++)
|
||||
ossl_ml_dsa_poly_ntt_inverse(&va->poly[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief Decompose all polynomial coefficients of a vector into (t1, t0) such
|
||||
* that coeff[i] == t1[i] * 2^13 + t0[i] mod q.
|
||||
* See FIPS 204, Algorithm 35, Power2Round()
|
||||
*/
|
||||
static ossl_inline ossl_unused void
|
||||
vector_power2_round(const VECTOR *t, VECTOR *t1, VECTOR *t0)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < t->num_poly; i++)
|
||||
poly_power2_round(&t->poly[i], &t1->poly[i], &t0->poly[i]);
|
||||
}
|
55
include/crypto/ml_dsa.h
Normal file
55
include/crypto/ml_dsa.h
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
/* Internal ML_DSA functions for other submodules, not for application use */
|
||||
|
||||
#ifndef OSSL_CRYPTO_ML_DSA_H
|
||||
# define OSSL_CRYPTO_ML_DSA_H
|
||||
|
||||
# pragma once
|
||||
# include <openssl/e_os2.h>
|
||||
# include <openssl/types.h>
|
||||
# include "crypto/types.h"
|
||||
|
||||
# define ML_DSA_MAX_CONTEXT_STRING_LEN 255
|
||||
|
||||
typedef struct ml_dsa_ctx_st ML_DSA_CTX;
|
||||
|
||||
__owur ML_DSA_KEY *ossl_ml_dsa_key_new(OSSL_LIB_CTX *libctx, const char *alg);
|
||||
void ossl_ml_dsa_key_free(ML_DSA_KEY *key);
|
||||
__owur int ossl_ml_dsa_key_up_ref(ML_DSA_KEY *key);
|
||||
__owur int ossl_ml_dsa_key_equal(const ML_DSA_KEY *key1, const ML_DSA_KEY *key2,
|
||||
int selection);
|
||||
__owur int ossl_ml_dsa_key_has(const ML_DSA_KEY *key, int selection);
|
||||
__owur int ossl_ml_dsa_key_pairwise_check(const ML_DSA_KEY *key);
|
||||
__owur int ossl_ml_dsa_key_fromdata(ML_DSA_KEY *key, const OSSL_PARAM *params,
|
||||
int include_private);
|
||||
__owur int ossl_ml_dsa_generate_key(ML_DSA_CTX *ctx, OSSL_LIB_CTX *libctx,
|
||||
const uint8_t *entropy, size_t entropy_len,
|
||||
ML_DSA_KEY *out);
|
||||
__owur const uint8_t *ossl_ml_dsa_key_get_pub(const ML_DSA_KEY *key);
|
||||
__owur const uint8_t *ossl_ml_dsa_key_get_priv(const ML_DSA_KEY *key);
|
||||
__owur size_t ossl_ml_dsa_key_get_pub_len(const ML_DSA_KEY *key);
|
||||
__owur size_t ossl_ml_dsa_key_get_collision_strength_bits(const ML_DSA_KEY *key);
|
||||
__owur int ossl_ml_dsa_set_priv(ML_DSA_KEY *key, const uint8_t *priv,
|
||||
size_t priv_len);
|
||||
__owur int ossl_ml_dsa_set_pub(ML_DSA_KEY *key, const uint8_t *pub,
|
||||
size_t pub_len);
|
||||
__owur size_t ossl_ml_dsa_key_get_priv_len(const ML_DSA_KEY *key);
|
||||
__owur size_t ossl_ml_dsa_key_get_sig_len(const ML_DSA_KEY *key);
|
||||
__owur const char *ossl_ml_dsa_key_get_name(const ML_DSA_KEY *key);
|
||||
__owur int ossl_ml_dsa_key_type_matches(ML_DSA_CTX *ctx, const ML_DSA_KEY *key);
|
||||
__owur int ossl_ml_dsa_key_to_text(BIO *out, const ML_DSA_KEY *key, int selection);
|
||||
void ossl_ml_dsa_key_set0_libctx(ML_DSA_KEY *key, OSSL_LIB_CTX *lib_ctx);
|
||||
|
||||
__owur ML_DSA_CTX *ossl_ml_dsa_ctx_new(const char *alg,
|
||||
OSSL_LIB_CTX *lib_ctx, const char *propq);
|
||||
void ossl_ml_dsa_ctx_free(ML_DSA_CTX *ctx);
|
||||
|
||||
#endif /* OSSL_CRYPTO_SLH_DSA_H */
|
@ -28,5 +28,8 @@ typedef struct dsa_st DSA;
|
||||
# ifndef OPENSSL_NO_EC
|
||||
typedef struct ecx_key_st ECX_KEY;
|
||||
# endif
|
||||
# ifndef OPENSSL_NO_ML_DSA
|
||||
typedef struct ml_dsa_key_st ML_DSA_KEY;
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
@ -509,6 +509,10 @@ static const OSSL_ALGORITHM deflt_keymgmt[] = {
|
||||
PROV_DESCS_ED448 },
|
||||
# endif
|
||||
#endif
|
||||
#ifndef OPENSSL_NO_ML_DSA
|
||||
{ PROV_NAMES_ML_DSA_65, "provider=default", ossl_ml_dsa_65_keymgmt_functions,
|
||||
PROV_DESCS_ML_DSA_65 },
|
||||
#endif /* OPENSSL_NO_ML_DSA */
|
||||
{ PROV_NAMES_TLS1_PRF, "provider=default", ossl_kdf_keymgmt_functions,
|
||||
PROV_DESCS_TLS1_PRF_SIGN },
|
||||
{ PROV_NAMES_HKDF, "provider=default", ossl_kdf_keymgmt_functions,
|
||||
|
@ -321,6 +321,7 @@ extern const OSSL_DISPATCH ossl_cmac_legacy_keymgmt_functions[];
|
||||
#ifndef OPENSSL_NO_SM2
|
||||
extern const OSSL_DISPATCH ossl_sm2_keymgmt_functions[];
|
||||
#endif
|
||||
extern const OSSL_DISPATCH ossl_ml_dsa_65_keymgmt_functions[];
|
||||
|
||||
/* Key Exchange */
|
||||
extern const OSSL_DISPATCH ossl_dh_keyexch_functions[];
|
||||
|
@ -384,3 +384,5 @@
|
||||
#define PROV_DESCS_RSA_PSS "OpenSSL RSA-PSS implementation"
|
||||
#define PROV_NAMES_SM2 "SM2:1.2.156.10197.1.301"
|
||||
#define PROV_DESCS_SM2 "OpenSSL SM2 implementation"
|
||||
#define PROV_NAMES_ML_DSA_65 "ML-DSA-65:2.16.840.1.101.3.4.3.18"
|
||||
#define PROV_DESCS_ML_DSA_65 "OpenSSL ML-DSA-65 implementation"
|
||||
|
@ -9,6 +9,7 @@ $KDF_GOAL=../../libdefault.a ../../libfips.a
|
||||
$MAC_GOAL=../../libdefault.a ../../libfips.a
|
||||
$RSA_GOAL=../../libdefault.a ../../libfips.a
|
||||
$TEMPLATE_GOAL=../../libtemplate.a
|
||||
$ML_DSA_GOAL=../../libdefault.a
|
||||
|
||||
IF[{- !$disabled{dh} -}]
|
||||
SOURCE[$DH_GOAL]=dh_kmgmt.c
|
||||
@ -44,3 +45,7 @@ SOURCE[$KDF_GOAL]=kdf_legacy_kmgmt.c
|
||||
SOURCE[$MAC_GOAL]=mac_legacy_kmgmt.c
|
||||
|
||||
SOURCE[$TEMPLATE_GOAL]=template_kmgmt.c
|
||||
|
||||
IF[{- !$disabled{ml-dsa} -}]
|
||||
SOURCE[$ML_DSA_GOAL]=ml_dsa_kmgmt.c
|
||||
ENDIF
|
||||
|
375
providers/implementations/keymgmt/ml_dsa_kmgmt.c
Normal file
375
providers/implementations/keymgmt/ml_dsa_kmgmt.c
Normal file
@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/core_dispatch.h>
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/param_build.h>
|
||||
#include <openssl/self_test.h>
|
||||
#include "crypto/ml_dsa.h"
|
||||
#include "internal/param_build_set.h"
|
||||
#include "prov/implementations.h"
|
||||
#include "prov/providercommon.h"
|
||||
#include "prov/provider_ctx.h"
|
||||
|
||||
static OSSL_FUNC_keymgmt_free_fn ml_dsa_free_key;
|
||||
static OSSL_FUNC_keymgmt_has_fn ml_dsa_has;
|
||||
static OSSL_FUNC_keymgmt_match_fn ml_dsa_match;
|
||||
static OSSL_FUNC_keymgmt_import_fn ml_dsa_import;
|
||||
static OSSL_FUNC_keymgmt_export_fn ml_dsa_export;
|
||||
static OSSL_FUNC_keymgmt_import_types_fn ml_dsa_imexport_types;
|
||||
static OSSL_FUNC_keymgmt_export_types_fn ml_dsa_imexport_types;
|
||||
static OSSL_FUNC_keymgmt_load_fn ml_dsa_load;
|
||||
static OSSL_FUNC_keymgmt_get_params_fn ml_dsa_get_params;
|
||||
static OSSL_FUNC_keymgmt_gettable_params_fn ml_dsa_gettable_params;
|
||||
static OSSL_FUNC_keymgmt_validate_fn ml_dsa_validate;
|
||||
static OSSL_FUNC_keymgmt_gen_init_fn ml_dsa_gen_init;
|
||||
static OSSL_FUNC_keymgmt_gen_cleanup_fn ml_dsa_gen_cleanup;
|
||||
static OSSL_FUNC_keymgmt_gen_set_params_fn ml_dsa_gen_set_params;
|
||||
static OSSL_FUNC_keymgmt_gen_settable_params_fn ml_dsa_gen_settable_params;
|
||||
|
||||
#define ML_DSA_POSSIBLE_SELECTIONS (OSSL_KEYMGMT_SELECT_KEYPAIR)
|
||||
|
||||
struct ml_dsa_gen_ctx {
|
||||
ML_DSA_CTX *ctx;
|
||||
OSSL_LIB_CTX *libctx;
|
||||
char *propq;
|
||||
uint8_t entropy[32];
|
||||
size_t entropy_len;
|
||||
};
|
||||
|
||||
static void *ml_dsa_new_key(void *provctx, const char *alg)
|
||||
{
|
||||
if (!ossl_prov_is_running())
|
||||
return 0;
|
||||
|
||||
return ossl_ml_dsa_key_new(PROV_LIBCTX_OF(provctx), alg);
|
||||
}
|
||||
|
||||
static void ml_dsa_free_key(void *keydata)
|
||||
{
|
||||
ossl_ml_dsa_key_free((ML_DSA_KEY *)keydata);
|
||||
}
|
||||
|
||||
static int ml_dsa_has(const void *keydata, int selection)
|
||||
{
|
||||
const ML_DSA_KEY *key = keydata;
|
||||
|
||||
if (!ossl_prov_is_running() || key == NULL)
|
||||
return 0;
|
||||
if ((selection & ML_DSA_POSSIBLE_SELECTIONS) == 0)
|
||||
return 1; /* the selection is not missing */
|
||||
|
||||
return ossl_ml_dsa_key_has(key, selection);
|
||||
}
|
||||
|
||||
static int ml_dsa_match(const void *keydata1, const void *keydata2, int selection)
|
||||
{
|
||||
const ML_DSA_KEY *key1 = keydata1;
|
||||
const ML_DSA_KEY *key2 = keydata2;
|
||||
|
||||
if (!ossl_prov_is_running())
|
||||
return 0;
|
||||
if (key1 == NULL || key2 == NULL)
|
||||
return 0;
|
||||
return ossl_ml_dsa_key_equal(key1, key2, selection);
|
||||
}
|
||||
|
||||
static int ml_dsa_validate(const void *key_data, int selection, int check_type)
|
||||
{
|
||||
const ML_DSA_KEY *key = key_data;
|
||||
|
||||
if (!ml_dsa_has(key, selection))
|
||||
return 0;
|
||||
|
||||
if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == OSSL_KEYMGMT_SELECT_KEYPAIR)
|
||||
return ossl_ml_dsa_key_pairwise_check(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ml_dsa_import(void *keydata, int selection, const OSSL_PARAM params[])
|
||||
{
|
||||
ML_DSA_KEY *key = keydata;
|
||||
int include_priv;
|
||||
|
||||
if (!ossl_prov_is_running() || key == NULL)
|
||||
return 0;
|
||||
|
||||
if ((selection & ML_DSA_POSSIBLE_SELECTIONS) == 0)
|
||||
return 0;
|
||||
|
||||
include_priv = ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0);
|
||||
return ossl_ml_dsa_key_fromdata(key, params, include_priv);
|
||||
}
|
||||
|
||||
#define ML_DSA_IMEXPORTABLE_PARAMETERS \
|
||||
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, NULL, 0), \
|
||||
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, NULL, 0)
|
||||
|
||||
static const OSSL_PARAM ml_dsa_key_types[] = {
|
||||
ML_DSA_IMEXPORTABLE_PARAMETERS,
|
||||
OSSL_PARAM_END
|
||||
};
|
||||
static const OSSL_PARAM *ml_dsa_imexport_types(int selection)
|
||||
{
|
||||
if ((selection & ML_DSA_POSSIBLE_SELECTIONS) == 0)
|
||||
return NULL;
|
||||
return ml_dsa_key_types;
|
||||
}
|
||||
|
||||
static const OSSL_PARAM ml_dsa_params[] = {
|
||||
OSSL_PARAM_int(OSSL_PKEY_PARAM_BITS, NULL),
|
||||
OSSL_PARAM_int(OSSL_PKEY_PARAM_SECURITY_BITS, NULL),
|
||||
OSSL_PARAM_int(OSSL_PKEY_PARAM_MAX_SIZE, NULL),
|
||||
ML_DSA_IMEXPORTABLE_PARAMETERS,
|
||||
OSSL_PARAM_END
|
||||
};
|
||||
static const OSSL_PARAM *ml_dsa_gettable_params(void *provctx)
|
||||
{
|
||||
return ml_dsa_params;
|
||||
}
|
||||
|
||||
static int key_to_params(ML_DSA_KEY *key, OSSL_PARAM_BLD *tmpl,
|
||||
int include_private)
|
||||
{
|
||||
/* Error if there is no key or public key */
|
||||
if (key == NULL || ossl_ml_dsa_key_get_pub(key) == NULL)
|
||||
return 0;
|
||||
/*
|
||||
* Note that the private key always contains the public key elements so we
|
||||
* just save the one blob and return.
|
||||
*/
|
||||
if (include_private && ossl_ml_dsa_key_get_priv(key) != NULL)
|
||||
return ossl_param_build_set_octet_string(tmpl, NULL,
|
||||
OSSL_PKEY_PARAM_PRIV_KEY,
|
||||
ossl_ml_dsa_key_get_priv(key),
|
||||
ossl_ml_dsa_key_get_priv_len(key));
|
||||
/* Otherwise write out the public key element */
|
||||
return ossl_param_build_set_octet_string(tmpl, NULL,
|
||||
OSSL_PKEY_PARAM_PUB_KEY,
|
||||
ossl_ml_dsa_key_get_pub(key),
|
||||
ossl_ml_dsa_key_get_pub_len(key));
|
||||
}
|
||||
|
||||
static int ml_dsa_get_params(void *keydata, OSSL_PARAM params[])
|
||||
{
|
||||
ML_DSA_KEY *key = keydata;
|
||||
OSSL_PARAM *p;
|
||||
const uint8_t *pub, *priv;
|
||||
|
||||
if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_BITS)) != NULL
|
||||
&& !OSSL_PARAM_set_int(p, 8 * ossl_ml_dsa_key_get_pub_len(key)))
|
||||
return 0;
|
||||
if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_SECURITY_BITS)) != NULL
|
||||
&& !OSSL_PARAM_set_int(p, 8 * ossl_ml_dsa_key_get_collision_strength_bits(key)))
|
||||
return 0;
|
||||
if ((p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_MAX_SIZE)) != NULL
|
||||
&& !OSSL_PARAM_set_int(p, ossl_ml_dsa_key_get_sig_len(key)))
|
||||
return 0;
|
||||
|
||||
pub = ossl_ml_dsa_key_get_pub(key);
|
||||
priv = ossl_ml_dsa_key_get_priv(key);
|
||||
|
||||
/* This just gets the private elements */
|
||||
p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY);
|
||||
if (p != NULL) {
|
||||
if (priv == NULL
|
||||
|| !OSSL_PARAM_set_octet_string(p, priv,
|
||||
ossl_ml_dsa_key_get_priv_len(key)))
|
||||
return 0;
|
||||
}
|
||||
p = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY);
|
||||
if (p != NULL) {
|
||||
if (pub == NULL
|
||||
|| !OSSL_PARAM_set_octet_string(p, pub,
|
||||
ossl_ml_dsa_key_get_pub_len(key)))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ml_dsa_export(void *keydata, int selection,
|
||||
OSSL_CALLBACK *param_cb, void *cbarg)
|
||||
{
|
||||
ML_DSA_KEY *key = keydata;
|
||||
OSSL_PARAM_BLD *tmpl;
|
||||
OSSL_PARAM *params = NULL;
|
||||
int ret = 0, include_private;
|
||||
|
||||
if (!ossl_prov_is_running() || key == NULL)
|
||||
return 0;
|
||||
|
||||
if ((selection & OSSL_KEYMGMT_SELECT_KEYPAIR) == 0)
|
||||
return 0;
|
||||
/* The public key is required for private keys */
|
||||
if ((selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) == 0)
|
||||
return 0;
|
||||
|
||||
tmpl = OSSL_PARAM_BLD_new();
|
||||
if (tmpl == NULL)
|
||||
return 0;
|
||||
|
||||
include_private = ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) != 0);
|
||||
if (!key_to_params(key, tmpl, include_private))
|
||||
goto err;
|
||||
|
||||
params = OSSL_PARAM_BLD_to_param(tmpl);
|
||||
if (params == NULL)
|
||||
goto err;
|
||||
|
||||
ret = param_cb(params, cbarg);
|
||||
OSSL_PARAM_free(params);
|
||||
err:
|
||||
OSSL_PARAM_BLD_free(tmpl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *ml_dsa_load(const void *reference, size_t reference_sz)
|
||||
{
|
||||
ML_DSA_KEY *key = NULL;
|
||||
|
||||
if (ossl_prov_is_running() && reference_sz == sizeof(key)) {
|
||||
/* The contents of the reference is the address to our object */
|
||||
key = *(ML_DSA_KEY **)reference;
|
||||
/* We grabbed, so we detach it */
|
||||
*(ML_DSA_KEY **)reference = NULL;
|
||||
return key;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *ml_dsa_gen_init(void *provctx, int selection,
|
||||
const OSSL_PARAM params[])
|
||||
{
|
||||
OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(provctx);
|
||||
struct ml_dsa_gen_ctx *gctx = NULL;
|
||||
|
||||
if (!ossl_prov_is_running())
|
||||
return NULL;
|
||||
|
||||
if ((gctx = OPENSSL_zalloc(sizeof(*gctx))) != NULL) {
|
||||
gctx->libctx = libctx;
|
||||
if (!ml_dsa_gen_set_params(gctx, params)) {
|
||||
OPENSSL_free(gctx);
|
||||
gctx = NULL;
|
||||
}
|
||||
}
|
||||
return gctx;
|
||||
}
|
||||
|
||||
static void *ml_dsa_gen(void *genctx, const char *alg)
|
||||
{
|
||||
struct ml_dsa_gen_ctx *gctx = genctx;
|
||||
ML_DSA_KEY *key = NULL;
|
||||
ML_DSA_CTX *ctx = NULL;
|
||||
|
||||
if (!ossl_prov_is_running())
|
||||
return NULL;
|
||||
ctx = ossl_ml_dsa_ctx_new(alg, gctx->libctx, gctx->propq);
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
key = ossl_ml_dsa_key_new(gctx->libctx, alg);
|
||||
if (key == NULL)
|
||||
return NULL;
|
||||
if (!ossl_ml_dsa_generate_key(ctx, gctx->libctx,
|
||||
gctx->entropy, gctx->entropy_len, key))
|
||||
goto err;
|
||||
ossl_ml_dsa_ctx_free(ctx);
|
||||
return key;
|
||||
err:
|
||||
ossl_ml_dsa_ctx_free(ctx);
|
||||
ossl_ml_dsa_key_free(key);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ml_dsa_gen_set_params(void *genctx, const OSSL_PARAM params[])
|
||||
{
|
||||
struct ml_dsa_gen_ctx *gctx = genctx;
|
||||
const OSSL_PARAM *p;
|
||||
|
||||
if (gctx == NULL)
|
||||
return 0;
|
||||
|
||||
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_ML_DSA_SEED);
|
||||
if (p != NULL) {
|
||||
void *vp = gctx->entropy;
|
||||
size_t len = sizeof(gctx->entropy);
|
||||
|
||||
if (!OSSL_PARAM_get_octet_string(p, &vp, len, &(gctx->entropy_len))) {
|
||||
gctx->entropy_len = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
p = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_PROPERTIES);
|
||||
if (p != NULL) {
|
||||
if (p->data_type != OSSL_PARAM_UTF8_STRING)
|
||||
return 0;
|
||||
OPENSSL_free(gctx->propq);
|
||||
gctx->propq = OPENSSL_strdup(p->data);
|
||||
if (gctx->propq == NULL)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const OSSL_PARAM *ml_dsa_gen_settable_params(ossl_unused void *genctx,
|
||||
ossl_unused void *provctx)
|
||||
{
|
||||
static OSSL_PARAM settable[] = {
|
||||
OSSL_PARAM_utf8_string(OSSL_PKEY_PARAM_PROPERTIES, NULL, 0),
|
||||
OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED, NULL, 0),
|
||||
OSSL_PARAM_END
|
||||
};
|
||||
return settable;
|
||||
}
|
||||
|
||||
static void ml_dsa_gen_cleanup(void *genctx)
|
||||
{
|
||||
struct ml_dsa_gen_ctx *gctx = genctx;
|
||||
|
||||
OPENSSL_cleanse(gctx->entropy, gctx->entropy_len);
|
||||
OPENSSL_free(gctx->propq);
|
||||
OPENSSL_free(gctx);
|
||||
}
|
||||
|
||||
#define MAKE_KEYMGMT_FUNCTIONS(alg, fn) \
|
||||
static OSSL_FUNC_keymgmt_new_fn ml_dsa_##fn##_new_key; \
|
||||
static OSSL_FUNC_keymgmt_gen_fn ml_dsa_##fn##_gen; \
|
||||
static void *ml_dsa_##fn##_new_key(void *provctx) \
|
||||
{ \
|
||||
return ml_dsa_new_key(provctx, alg); \
|
||||
} \
|
||||
static void *ml_dsa_##fn##_gen(void *genctx, OSSL_CALLBACK *osslcb, void *cbarg)\
|
||||
{ \
|
||||
return ml_dsa_gen(genctx, alg); \
|
||||
} \
|
||||
const OSSL_DISPATCH ossl_ml_dsa_##fn##_keymgmt_functions[] = { \
|
||||
{ OSSL_FUNC_KEYMGMT_NEW, (void (*)(void))ml_dsa_##fn##_new_key }, \
|
||||
{ OSSL_FUNC_KEYMGMT_FREE, (void (*)(void))ml_dsa_free_key }, \
|
||||
{ OSSL_FUNC_KEYMGMT_HAS, (void (*)(void))ml_dsa_has }, \
|
||||
{ OSSL_FUNC_KEYMGMT_MATCH, (void (*)(void))ml_dsa_match }, \
|
||||
{ OSSL_FUNC_KEYMGMT_IMPORT, (void (*)(void))ml_dsa_import }, \
|
||||
{ OSSL_FUNC_KEYMGMT_IMPORT_TYPES, (void (*)(void))ml_dsa_imexport_types },\
|
||||
{ OSSL_FUNC_KEYMGMT_EXPORT, (void (*)(void))ml_dsa_export }, \
|
||||
{ OSSL_FUNC_KEYMGMT_EXPORT_TYPES, (void (*)(void))ml_dsa_imexport_types },\
|
||||
{ OSSL_FUNC_KEYMGMT_LOAD, (void (*)(void))ml_dsa_load }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GET_PARAMS, (void (*) (void))ml_dsa_get_params }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, (void (*) (void))ml_dsa_gettable_params },\
|
||||
{ OSSL_FUNC_KEYMGMT_VALIDATE, (void (*)(void))ml_dsa_validate }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GEN_INIT, (void (*)(void))ml_dsa_gen_init }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GEN, (void (*)(void))ml_dsa_##fn##_gen }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GEN_CLEANUP, (void (*)(void))ml_dsa_gen_cleanup }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GEN_SET_PARAMS, \
|
||||
(void (*)(void))ml_dsa_gen_set_params }, \
|
||||
{ OSSL_FUNC_KEYMGMT_GEN_SETTABLE_PARAMS, \
|
||||
(void (*)(void))ml_dsa_gen_settable_params }, \
|
||||
OSSL_DISPATCH_END \
|
||||
}
|
||||
|
||||
MAKE_KEYMGMT_FUNCTIONS("ML-DSA-65", 65);
|
@ -284,6 +284,13 @@ IF[{- !$disabled{tests} -}]
|
||||
DEPEND[casttest]=../libcrypto libtestutil.a
|
||||
ENDIF
|
||||
|
||||
IF[{- !$disabled{'ml-dsa'} -}]
|
||||
PROGRAMS{noinst}=ml_dsa_test
|
||||
SOURCE[ml_dsa_test]=ml_dsa_test.c
|
||||
INCLUDE[ml_dsa_test]=../include ../apps/include
|
||||
DEPEND[ml_dsa_test]=../libcrypto.a libtestutil.a
|
||||
ENDIF
|
||||
|
||||
SOURCE[v3nametest]=v3nametest.c
|
||||
INCLUDE[v3nametest]=../include ../apps/include
|
||||
DEPEND[v3nametest]=../libcrypto libtestutil.a
|
||||
|
2183
test/ml_dsa.inc
Normal file
2183
test/ml_dsa.inc
Normal file
File diff suppressed because it is too large
Load Diff
115
test/ml_dsa_test.c
Normal file
115
test/ml_dsa_test.c
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
* this file except in compliance with the License. You can obtain a copy
|
||||
* in the file LICENSE in the source distribution or at
|
||||
* https://www.openssl.org/source/license.html
|
||||
*/
|
||||
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/evp.h>
|
||||
#include "internal/nelem.h"
|
||||
#include "testutil.h"
|
||||
#include "ml_dsa.inc"
|
||||
|
||||
typedef enum OPTION_choice {
|
||||
OPT_ERR = -1,
|
||||
OPT_EOF = 0,
|
||||
OPT_CONFIG_FILE,
|
||||
OPT_TEST_ENUM
|
||||
} OPTION_CHOICE;
|
||||
|
||||
static OSSL_LIB_CTX *lib_ctx = NULL;
|
||||
static OSSL_PROVIDER *null_prov = NULL;
|
||||
static OSSL_PROVIDER *lib_prov = NULL;
|
||||
|
||||
static EVP_PKEY *do_gen_key(const char *alg,
|
||||
const uint8_t *seed, size_t seed_len)
|
||||
{
|
||||
EVP_PKEY *pkey = NULL;
|
||||
EVP_PKEY_CTX *ctx = NULL;
|
||||
OSSL_PARAM params[2], *p = params;
|
||||
|
||||
if (seed_len != 0)
|
||||
*p++ = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_ML_DSA_SEED,
|
||||
(char *)seed, seed_len);
|
||||
*p = OSSL_PARAM_construct_end();
|
||||
|
||||
if (!TEST_ptr(ctx = EVP_PKEY_CTX_new_from_name(lib_ctx, alg, NULL))
|
||||
|| !TEST_int_eq(EVP_PKEY_keygen_init(ctx), 1)
|
||||
|| !TEST_int_eq(EVP_PKEY_CTX_set_params(ctx, params), 1)
|
||||
|| !TEST_int_eq(EVP_PKEY_generate(ctx, &pkey), 1))
|
||||
goto err;
|
||||
err:
|
||||
EVP_PKEY_CTX_free(ctx);
|
||||
return pkey;
|
||||
}
|
||||
|
||||
static int ml_dsa_keygen_test(int tst_id)
|
||||
{
|
||||
int ret = 0;
|
||||
const ML_DSA_KEYGEN_TEST_DATA *tst = &ml_dsa_keygen_testdata[tst_id];
|
||||
EVP_PKEY *pkey = NULL;
|
||||
uint8_t priv[5 * 1024], pub[3 * 1024];
|
||||
size_t priv_len, pub_len;
|
||||
|
||||
if (!TEST_ptr(pkey = do_gen_key(tst->name, tst->seed, tst->seed_len)))
|
||||
goto err;
|
||||
if (!TEST_true(EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY,
|
||||
priv, sizeof(priv), &priv_len)))
|
||||
goto err;
|
||||
if (!TEST_true(EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY,
|
||||
pub, sizeof(pub), &pub_len)))
|
||||
goto err;
|
||||
if (!TEST_mem_eq(pub, pub_len, tst->pub, tst->pub_len))
|
||||
goto err;
|
||||
if (!TEST_mem_eq(priv, priv_len, tst->priv, tst->priv_len))
|
||||
goto err;
|
||||
ret = 1;
|
||||
err:
|
||||
EVP_PKEY_free(pkey);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const OPTIONS *test_get_options(void)
|
||||
{
|
||||
static const OPTIONS options[] = {
|
||||
OPT_TEST_OPTIONS_DEFAULT_USAGE,
|
||||
{ "config", OPT_CONFIG_FILE, '<',
|
||||
"The configuration file to use for the libctx" },
|
||||
{ NULL }
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
||||
int setup_tests(void)
|
||||
{
|
||||
OPTION_CHOICE o;
|
||||
char *config_file = NULL;
|
||||
|
||||
while ((o = opt_next()) != OPT_EOF) {
|
||||
switch (o) {
|
||||
case OPT_CONFIG_FILE:
|
||||
config_file = opt_arg();
|
||||
break;
|
||||
case OPT_TEST_CASES:
|
||||
break;
|
||||
default:
|
||||
case OPT_ERR:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!test_get_libctx(&lib_ctx, &null_prov, config_file, &lib_prov, NULL))
|
||||
return 0;
|
||||
|
||||
ADD_ALL_TESTS(ml_dsa_keygen_test, OSSL_NELEM(ml_dsa_keygen_testdata));
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cleanup_tests(void)
|
||||
{
|
||||
OSSL_PROVIDER_unload(null_prov);
|
||||
OSSL_PROVIDER_unload(lib_prov);
|
||||
OSSL_LIB_CTX_free(lib_ctx);
|
||||
}
|
43
test/recipes/30-test_ml_dsa.t
Normal file
43
test/recipes/30-test_ml_dsa.t
Normal file
@ -0,0 +1,43 @@
|
||||
#! /usr/bin/env perl
|
||||
# Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use OpenSSL::Test qw(:DEFAULT srctop_dir bldtop_dir srctop_file);
|
||||
use OpenSSL::Test::Utils;
|
||||
|
||||
BEGIN {
|
||||
setup("test_ml_dsa");
|
||||
}
|
||||
|
||||
my $provconf = srctop_file("test", "fips-and-base.cnf");
|
||||
# fips will be added later
|
||||
my $no_fips = 1;
|
||||
|
||||
use lib srctop_dir('Configurations');
|
||||
use lib bldtop_dir('.');
|
||||
|
||||
plan skip_all => 'ML-DSA is not supported in this build' if disabled('ml-dsa');
|
||||
plan tests => 2;
|
||||
|
||||
ok(run(test(["ml_dsa_test"])), "running ml_dsa_test");
|
||||
|
||||
SKIP: {
|
||||
skip "Skipping FIPS tests", 1
|
||||
if $no_fips;
|
||||
|
||||
# ML-DSA is only present after OpenSSL 3.5
|
||||
run(test(["fips_version_test", "-config", $provconf, ">=3.5.0"]),
|
||||
capture => 1, statusvar => \my $exit);
|
||||
skip "FIPS provider version is too old for ML-DSA test", 1
|
||||
if !$exit;
|
||||
|
||||
ok(run(test(["ml_dsa_test", "-config", $provconf])),
|
||||
"running ml_dsa_test with FIPS");
|
||||
}
|
@ -431,6 +431,9 @@ my %params = (
|
||||
'PKEY_PARAM_FIPS_SIGN_CHECK' => "sign-check",
|
||||
'PKEY_PARAM_FIPS_APPROVED_INDICATOR' => '*ALG_PARAM_FIPS_APPROVED_INDICATOR',
|
||||
|
||||
# ML_DSA Key generation parameter
|
||||
'PKEY_PARAM_ML_DSA_SEED' => "seed",
|
||||
|
||||
# Key Exchange parameters
|
||||
'EXCHANGE_PARAM_PAD' => "pad",# uint
|
||||
'EXCHANGE_PARAM_EC_ECDH_COFACTOR_MODE' => "ecdh-cofactor-mode",# int
|
||||
|
Loading…
x
Reference in New Issue
Block a user