From: Tobias Brunner Date: Mon, 21 Oct 2024 08:38:14 +0000 (+0200) Subject: ml: Add software implementation of ML-KEM X-Git-Tag: 6.0.0rc1~11 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=89f4b345e33618729c26faaf1d605896346c28bc;p=thirdparty%2Fstrongswan.git ml: Add software implementation of ML-KEM This follows FIPS 203 relatively closely but takes some ideas from the reference implementation. In particular, how to avoid potential side-channels via direct C division/modulo operations. However, it just uses Barrett reduction (no Montgomery reduction) and no negative coefficients to avoid number format conversions and keep the implementation clearer. --- diff --git a/configure.ac b/configure.ac index 4b3aa0ad4d..81906acfc5 100644 --- a/configure.ac +++ b/configure.ac @@ -152,6 +152,7 @@ ARG_DISBL_SET([kdf], [disable KDF (prf+) implementation plugin.]) ARG_ENABL_SET([md4], [enable MD4 software implementation plugin.]) ARG_DISBL_SET([md5], [disable MD5 software implementation plugin.]) ARG_ENABL_SET([mgf1], [enable the MGF1 software implementation plugin.]) +ARG_ENABL_SET([ml], [enable Module-Lattice-based crypto (ML-KEM) plugin.]) ARG_DISBL_SET([nonce], [disable nonce generation plugin.]) ARG_ENABL_SET([openssl], [enables the OpenSSL crypto plugin.]) ARG_ENABL_SET([wolfssl], [enables the wolfSSL crypto plugin.]) @@ -1587,6 +1588,7 @@ ADD_PLUGIN([kdf], [s charon pki scripts nm cmd]) ADD_PLUGIN([ctr], [s charon scripts nm cmd]) ADD_PLUGIN([ccm], [s charon scripts nm cmd]) ADD_PLUGIN([gcm], [s charon scripts nm cmd]) +ADD_PLUGIN([ml], [s charon scripts nm cmd]) ADD_PLUGIN([drbg], [s charon pki scripts nm cmd]) ADD_PLUGIN([curl], [s charon pki scripts nm cmd]) ADD_PLUGIN([files], [s charon pki scripts nm cmd]) @@ -1755,6 +1757,7 @@ AM_CONDITIONAL(USE_CCM, test x$ccm = xtrue) AM_CONDITIONAL(USE_GCM, test x$gcm = xtrue) AM_CONDITIONAL(USE_AF_ALG, test x$af_alg = xtrue) AM_CONDITIONAL(USE_DRBG, test x$drbg = xtrue) +AM_CONDITIONAL(USE_ML, test x$ml = xtrue) # charon plugins # ---------------- @@ -2034,6 +2037,7 @@ AC_CONFIG_FILES([ src/libstrongswan/plugins/gcm/Makefile src/libstrongswan/plugins/af_alg/Makefile src/libstrongswan/plugins/drbg/Makefile + src/libstrongswan/plugins/ml/Makefile src/libstrongswan/plugins/test_vectors/Makefile src/libstrongswan/tests/Makefile src/libipsec/Makefile diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index c130032be1..8d3c869bd9 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -683,6 +683,13 @@ if MONOLITHIC endif endif +if USE_ML + SUBDIRS += plugins/ml +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/ml/libstrongswan-ml.la +endif +endif + if USE_TEST_VECTORS SUBDIRS += plugins/test_vectors if MONOLITHIC diff --git a/src/libstrongswan/plugins/ml/Makefile.am b/src/libstrongswan/plugins/ml/Makefile.am new file mode 100644 index 0000000000..e0d8d2bac9 --- /dev/null +++ b/src/libstrongswan/plugins/ml/Makefile.am @@ -0,0 +1,19 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-ml.la +else +plugin_LTLIBRARIES = libstrongswan-ml.la +endif + +libstrongswan_ml_la_SOURCES = \ + ml_bitpacker.c ml_bitpacker.h \ + ml_kem.c ml_kem.h \ + ml_params.c ml_params.h \ + ml_plugin.h ml_plugin.c \ + ml_poly.c ml_poly.h \ + ml_utils.c ml_utils.h diff --git a/src/libstrongswan/plugins/ml/ml_bitpacker.c b/src/libstrongswan/plugins/ml/ml_bitpacker.c new file mode 100644 index 0000000000..d208bbb510 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_bitpacker.c @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * Copyright (C) 2014 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ml_bitpacker.h" +#include "ml_utils.h" + +typedef struct private_ml_bitpacker_t private_ml_bitpacker_t; + +/** + * Private data. + */ +struct private_ml_bitpacker_t { + + /** + * Public interface. + */ + ml_bitpacker_t public; + + /** + * Bit buffer for up to 32 bits. + */ + uint32_t bits_buf; + + /** + * Bits left in the bit buffer. + */ + size_t bits_left; + + /** + * Target buffer. + */ + chunk_t buf; + + /** + * Read/Write pointer into buffer. + */ + chunk_t pos; +}; + +/** + * Write the bytes in the bit buffer to the output buffer. + */ +static void flush_buffer(private_ml_bitpacker_t *this) +{ + size_t to_write = min(4, this->pos.len); + + ml_write_bytes_le(this->pos.ptr, to_write, this->bits_buf); + this->pos = chunk_skip(this->pos, to_write); +} + +METHOD(ml_bitpacker_t, write_bits, bool, + private_ml_bitpacker_t *this, uint32_t value, size_t bits) +{ + if (!bits) + { + return TRUE; + } + if (bits > 32) + { + return FALSE; + } + if (bits < 32) + { + value &= (1 << bits) - 1; + } + + while (TRUE) + { + if (!this->pos.len) + { + return FALSE; + } + + this->bits_buf |= value << (32 - this->bits_left); + + if (bits < this->bits_left) + { + this->bits_left -= bits; + return TRUE; + } + value >>= this->bits_left; + bits -= this->bits_left; + + flush_buffer(this); + this->bits_buf = 0; + this->bits_left = 32; + } +} + +METHOD(ml_bitpacker_t, read_bits, bool, + private_ml_bitpacker_t *this, uint32_t *value, size_t bits) +{ + size_t to_read, written = 0; + + if (bits > 32) + { + return FALSE; + } + *value = 0; + + while (TRUE) + { + if (!this->bits_left) + { + if (!this->pos.len) + { + return FALSE; + } + to_read = min(4, this->pos.len); + this->bits_buf = ml_read_bytes_le(this->pos.ptr, to_read); + this->pos = chunk_skip(this->pos, to_read); + this->bits_left = 8 * to_read; + } + if (bits <= this->bits_left) + { + *value |= (this->bits_buf & ((1 << bits) - 1)) << written; + this->bits_buf >>= bits; + this->bits_left -= bits; + return TRUE; + } + *value |= this->bits_buf; + written = this->bits_left; + bits -= this->bits_left; + this->bits_left = 0; + } +} + +METHOD(ml_bitpacker_t, destroy, void, + private_ml_bitpacker_t *this) +{ + if (this->public.write_bits == _write_bits && + this->bits_left < 32) + { + flush_buffer(this); + } + free(this); +} + +/* + * Described in header + */ +ml_bitpacker_t *ml_bitpacker_create(chunk_t dst) +{ + private_ml_bitpacker_t *this; + + INIT(this, + .public = { + .write_bits = _write_bits, + .read_bits = (void*)return_false, + .destroy = _destroy, + }, + .bits_left = 32, + .buf = dst, + ); + + this->pos = this->buf; + + return &this->public; +} + +/* + * Described in header + */ +ml_bitpacker_t *ml_bitpacker_create_from_data(chunk_t data) +{ + private_ml_bitpacker_t *this; + + INIT(this, + .public = { + .write_bits = (void*)return_false, + .read_bits = _read_bits, + .destroy = _destroy, + }, + .buf = data, + ); + + this->pos = this->buf; + + return &this->public; +} diff --git a/src/libstrongswan/plugins/ml/ml_bitpacker.h b/src/libstrongswan/plugins/ml/ml_bitpacker.h new file mode 100644 index 0000000000..7918b6bf4c --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_bitpacker.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * Copyright (C) 2014 Andreas Steffen + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ml_bitpacker ml_bitpacker + * @{ @ingroup ml_p + */ + +#ifndef ML_BITPACKER_H_ +#define ML_BITPACKER_H_ + +#include + +typedef struct ml_bitpacker_t ml_bitpacker_t; + +/** + * Reads and writes a variable number of bits in packed format + * from and to an octet buffer. + */ +struct ml_bitpacker_t { + + /** + * Write a number of bits of the given value to the buffer. + * + * @param value value to be written + * @param bits number of bits to be written + * @result TRUE if value could be written into buffer + */ + bool (*write_bits)(ml_bitpacker_t *this, uint32_t value, size_t bits); + + /** + * Get a number of bits from the buffer. + * + * @param value read value + * @param bits number of bits to be read + * @result TRUE if value could be read from buffer + */ + bool (*read_bits)(ml_bitpacker_t *this, uint32_t *value, size_t bits); + + /** + * Destroy the object. + * + * Note that when writing, this flushes the internal buffer and writes the + * remaining bits if there is still room in the destination buffer. + */ + void (*destroy)(ml_bitpacker_t *this); +}; + +/** + * Create a ml_bitpacker_t object for writing to a buffer. + * + * @param dst existing buffer to write bits to + */ +ml_bitpacker_t *ml_bitpacker_create(chunk_t dst); + +/** + * Create a ml_bitpacker_t object for reading. + * + * @param data packed array of bits + */ +ml_bitpacker_t *ml_bitpacker_create_from_data(chunk_t data); + +#endif /** ML_BITPACKER_H_ @}*/ diff --git a/src/libstrongswan/plugins/ml/ml_kem.c b/src/libstrongswan/plugins/ml/ml_kem.c new file mode 100644 index 0000000000..94bf4daa64 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_kem.c @@ -0,0 +1,987 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ml_bitpacker.h" +#include "ml_kem.h" +#include "ml_params.h" +#include "ml_poly.h" +#include "ml_utils.h" + +typedef struct private_key_exchange_t private_key_exchange_t; + +/** + * Private data. + */ +struct private_key_exchange_t { + + /** + * Public interface. + */ + key_exchange_t public; + + /** + * Key exchange method. + */ + key_exchange_method_t method; + + /** + * Parameter set. + */ + const ml_kem_params_t *params; + + /** + * Decryption/private key as initiator. + */ + chunk_t private_key; + + /** + * Ciphertext as responder. + */ + chunk_t ciphertext; + + /** + * Shared secret. + */ + chunk_t shared_secret; + + /** + * SHAKE-128 instance used as XOF when generating matrix A. + */ + xof_t *shake128; + + /** + * SHAKE-256 instance used as PRF and hash function J. + */ + xof_t *shake256; + + /** + * Hash function G (SHA3-512) used throughout the algorithms. + */ + hasher_t *G; + + /** + * Hash function H (SHA3-256) used throughout the algorithms. + */ + hasher_t *H; + + /** + * DRBG used during testing. + */ + drbg_t *drbg; +}; + +/** + * Get random bytes either from a DRBG during testing or from an RNG. + */ +static bool get_random(private_key_exchange_t *this, size_t len, uint8_t *out) +{ + rng_t *rng; + + if (this->drbg) + { + return this->drbg->generate(this->drbg, len, out); + } + rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG); + if (!rng || !rng->get_bytes(rng, len, out)) + { + DESTROY_IF(rng); + return FALSE; + } + rng->destroy(rng); + return TRUE; +} + +/** + * Generate a pseudorandom element of T_q using the given XOF. + * + * Algorithm 7 in FIPS 203. + */ +static bool sample_ntt(private_key_exchange_t *this, ml_poly_t *ahat) +{ + uint8_t C[3]; + uint16_t d1, d2; + int j = 0; + + while (j < ML_KEM_N) + { + if (!this->shake128->get_bytes(this->shake128, sizeof(C), C)) + { + return FALSE; + } + d1 = C[0] + ((C[1] & 0xf) << 8); + d2 = (C[1] >> 4) + (C[2] << 4); + if (d1 < ML_KEM_Q) + { + ahat->f[j++] = d1; + } + if (d2 < ML_KEM_Q && j < ML_KEM_N) + { + ahat->f[j++] = d2; + } + } + return TRUE; +} + +/** + * Generate pseudorandom matrix A or its transposed version A^T. + */ +static bool generate_a(private_key_exchange_t *this, ml_poly_t *a, + uint8_t *rho) +{ + const uint8_t k = this->params->k; + + chunk_t B = chunk_alloca(ML_KEM_SEED_LEN + 2); + int i, j; + + memcpy(B.ptr, rho, ML_KEM_SEED_LEN); + for (i = 0; i < k; i++) + { + for (j = 0; j < k; j++) + { + B.ptr[ML_KEM_SEED_LEN] = j; + B.ptr[ML_KEM_SEED_LEN + 1] = i; + if (!this->shake128->set_seed(this->shake128, B) || + !sample_ntt(this, &a[i*k + j])) + { + return FALSE; + } + } + } + return TRUE; +} + +/** + * Derives a seed via PRF_eta(s, N) = SHAKE256(s||N, 64 * eta) from the given + * random 32-byte seed and nonce N and outputs a pseudorandom polynomial sampled + * from the distribution D_eta(R_q) of polynomials in R_q with small + * coefficients, with each coefficient sampled independently from a certain + * centered binomial distribution (CBD) on Z_q. + * + * Algorithm 8 in FIPS 203. + */ +static void sample_poly_cbd(private_key_exchange_t *this, uint8_t eta, + uint8_t *s, uint8_t N, ml_poly_t *p) +{ + /* this uses an optimization from the reference implementation. since eta + * can only take two values for the current parameter sets (it's actually 2 + * for all but ML-KEM-512 that uses 3 for eta1), we can optimize this and + * either add together 16*2 or 8*3 bits concurrently and then split the + * result into 8 or 4 coefficients, respectively */ + const int coeffs = (eta == 2) ? 8 : 4; + const int fetch = 2 * coeffs * eta / 8; + /* every second or third bit is set in these masks */ + const uint32_t add_mask = (eta == 2) ? 0x55555555 : 0x00249249; + const uint32_t mask = (eta == 2) ? 0x3 : 0x7; + const int len = 64 * eta; + + chunk_t seed = chunk_alloca(ML_KEM_SEED_LEN+1); + uint8_t sample_seed[len] = {}; + uint32_t t, b; + uint16_t x, y; + int i, j; + + memcpy(seed.ptr, s, ML_KEM_SEED_LEN); + seed.ptr[ML_KEM_SEED_LEN] = N; + + ignore_result(this->shake256->set_seed(this->shake256, seed) && + this->shake256->get_bytes(this->shake256, len, sample_seed)); + + for (i = 0; i < ML_KEM_N/coeffs; i++) + { + /* get a bit stream from the seed */ + t = ml_read_bytes_le(sample_seed + fetch*i, fetch); + + /* add together eta consecutive bits */ + b = t & add_mask; + b += (t >> 1) & add_mask; + if (eta == 3) + { + b += (t >> 2) & add_mask; + } + + for (j = 0; j < coeffs; j++) + { + x = (b >> (2*j*eta)) & mask; + y = (b >> (2*j*eta + eta)) & mask; + p->f[coeffs*i + j] = ml_reduce_modq(x - y + ML_KEM_Q); + } + } + + memwipe(seed.ptr, seed.len); + memwipe(sample_seed, sizeof(sample_seed)); +} + +/** + * Multiply the given two (reduced) numbers, followed by a Barrett reduction to + * get a*b mod q so i.e. a*b - [a*b*m/R]*q (where m is [R/q], R is 2^40 and + * rounded against zero). Note that m/2^40 is close enough to 1/q that no + * fixup is necessary after the final subtraction. + */ +static uint16_t mul_modq(uint16_t a, uint16_t b) +{ + const uint64_t m = (1ULL << 40) / ML_KEM_Q; + const uint32_t prod = (uint32_t)a * b; + + uint32_t t; + + t = (prod * m) >> 40; + return prod - t * ML_KEM_Q; +} + +/** + * Computes the number-theoretic transform (NTT) representation of the given + * polynomial p in place. + * + * Algorithm 9 in FIPS 203. + */ +static void ntt(private_key_exchange_t *this, ml_poly_t *p) +{ + int len, start, i = 1, j; + uint16_t zeta, t; + + for (len = ML_KEM_N / 2; len >= 2; len /= 2) + { + for (start = 0; start < ML_KEM_N; start += 2 * len) + { + zeta = ml_kem_zetas[i++]; + for (j = start; j < start + len; j++) + { + t = mul_modq(zeta, p->f[j + len]); + p->f[j + len] = ml_reduce_modq(p->f[j] - t + ML_KEM_Q); + p->f[j] = ml_reduce_modq(p->f[j] + t); + } + } + } +} + +/** + * Computes the polynomial p that corresponds to the given number-theoretic + * transform (NTT) representation of it in place. + * + * Algorithm 10 in FIPS 203. + */ +static void ntt_inv(private_key_exchange_t *this, ml_poly_t *p) +{ + const uint16_t f = 3303; + + int len, start, i = 127, j; + uint16_t zeta, t; + + for (len = 2; len <= 128; len *= 2) + { + for (start = 0; start < ML_KEM_N; start += 2 * len) + { + zeta = ml_kem_zetas[i--]; + for (j = start; j < start + len; j++) + { + t = p->f[j]; + p->f[j] = ml_reduce_modq(t + p->f[j + len]); + p->f[j + len] = mul_modq(zeta, p->f[j + len] - t + ML_KEM_Q); + } + } + } + for (i = 0; i < ML_KEM_N; i++) + { + p->f[i] = mul_modq(p->f[i], f); + } +} + +/** + * Multiply the two degree-one polynomials a (= a[0] + a[1]*X) and b with + * respect to a quadratic modulus (X^2-gamma) and put the result in c. + * + * Algorithm 12 in FIPS 203. + */ +static void base_case_multiply(uint16_t *a, uint16_t *b, uint16_t *c, + uint16_t gamma) +{ + c[0] = mul_modq(a[1], b[1]); + c[0] = ml_reduce_modq(mul_modq(c[0], gamma) + mul_modq(a[0], b[0])); + c[1] = ml_reduce_modq(mul_modq(a[0], b[1]) + mul_modq(a[1], b[0])); +} + +/** + * Multiply two polynomials a and b in NTT domain and put them into c. + * + * Algorithm 11 in FIPS 203. + */ +static void multiply_poly(ml_poly_t *a, ml_poly_t *b, ml_poly_t *c) +{ + int i; + + /* since the Zeta^(2BitRev_7(i)+1) mod q values required here can be found + * in the second half of the table for Zeta^BitRev_7(i) mod q, we reuse them + * and directly handle four coefficients per iteration instead of two */ + for (i = 0; i < ML_KEM_N/4; i++) + { + base_case_multiply(&a->f[4*i], &b->f[4*i], &c->f[4*i], + ml_kem_zetas[i+64]); + base_case_multiply(&a->f[4*i+2], &b->f[4*i+2], &c->f[4*i+2], + ML_KEM_Q - ml_kem_zetas[i+64]); + } +} + +/** + * Multiply the k polynomials in a with those in b and accumulate them into c. + * + * If transposed is TRUE, a is assumed to be a k*k matrix where polynomials from + * a column are used (not a sequential row). + * + * See the note regarding the result of multiply_poly(). + */ +static void multiply_poly_arr(uint8_t k, ml_poly_t *a, ml_poly_t *b, + ml_poly_t *c, bool transposed) +{ + ml_poly_t t; + int i, f = transposed ? k : 1; + + multiply_poly(&a[0], &b[0], c); + for (i = 1; i < k; i++) + { + multiply_poly(&a[i*f], &b[i], &t); + ml_poly_add(c, &t, c); + } +} + +/** + * Encode k polynomials to a byte array (12-bit version that packs 2 + * coefficients into 3 bytes, not using ml_bitpacker_t for performance reasons). + * + * Algorithm 5 in FIPS 203. + */ +static void encode_poly_arr(uint8_t k, ml_poly_t *a, uint8_t *out) +{ + uint16_t f0, f1; + int i, j; + + for (i = 0; i < k; i++) + { + for (j = 0; j < ML_KEM_N / 2; j++) + { + f0 = a[i].f[2*j]; + f1 = a[i].f[2*j + 1]; + out[3*j] = (uint8_t)f0; + out[3*j+1] = (uint8_t)((f0 >> 8) | (f1 << 4)); + out[3*j+2] = (uint8_t)(f1 >> 4); + } + out += ML_KEM_POLY_LEN; + } +} + +/** + * Decode k polynomials from a byte array (12-bit version that unpacks 2 + * coefficients from 3 bytes, not using ml_bitpacker_t for performance reasons). + * + * Algorithm 6 in FIPS 203. + */ +static void decode_poly_arr(uint8_t k, uint8_t *in, ml_poly_t *a) +{ + int i, j; + + for (i = 0; i < k; i++) + { + for (j = 0; j < ML_KEM_N / 2; j++) + { + a[i].f[2*j] = (in[3*j] | ((uint16_t)in[3*j+1] << 8)) & 0xfff; + a[i].f[2*j+1] = ((in[3*j+1] >> 4) | ((uint16_t)in[3*j+2] << 4)) & 0xfff; + } + in += ML_KEM_POLY_LEN; + } +} + +/** + * Compress the k 12-bit polynomials in a to d bits and encode the result as + * bytes in out. + */ +static void compress_polys_arr(uint8_t k, uint8_t d, ml_poly_t *a, uint8_t *out) +{ + /* avoid division by replacing 2^d/q with [m/2^p] where m is [2^(p+d)/q] */ + const int p = 63 - d; + const uint64_t m = ((1ULL << (p+d)) + ML_KEM_Q/2) / ML_KEM_Q; + const uint64_t mask = (1 << d) - 1; + + ml_bitpacker_t *packer; + uint64_t f; + int i, j; + + packer = ml_bitpacker_create(chunk_create(out, k * d * ML_KEM_N / 8)); + for (i = 0; i < k; i++) + { + for (j = 0; j < ML_KEM_N; j++) + { + f = a[i].f[j]; + /* calculate the compression [f * 2^d/q mod 2^d] without division */ + f = ((f * m + (1ULL << (p - 1))) >> p) & mask; + packer->write_bits(packer, f, d); + } + } + packer->destroy(packer); +} + +/** + * Decompress the k 12-bit polynomials in a from a stream of d-bit chunks. + */ +static void decompress_poly_arr(uint8_t k, uint8_t d, uint8_t *in, ml_poly_t *a) +{ + const uint16_t rounding = 1 << (d - 1); + + ml_bitpacker_t *packer; + uint32_t f; + int i, j; + + packer = ml_bitpacker_create_from_data(chunk_create(in, k * d * ML_KEM_N / 8)); + for (i = 0; i < k; i++) + { + for (j = 0; j < ML_KEM_N; j++) + { + /* calculate the decompression [f * q / 2^d] */ + packer->read_bits(packer, &f, d); + a[i].f[j] = (f * ML_KEM_Q + rounding) >> d; + } + } + packer->destroy(packer); +} + +/** + * Calculates Decompress_1(ByteDecode_1()) for the given message m of length + * ML_KEM_SEED_LEN and puts the result into p. + */ +static void message_to_poly(uint8_t *m, ml_poly_t *p) +{ + int i, j; + + for (i = 0; i < ML_KEM_SEED_LEN; i++) + { + for (j = 0; j < 8; j++) + { + /* can't use + * p->f[8 * i + j] = (ML_KEM_Q + 1) / 2 * (m[i] >> j & 0x1); + * or some manual masking here because after recognizing that this + * is either 0 or a constant, some versions of clang apparently + * optimize this with a branching instruction to just skip over the + * assignment, creating a possible side-channel */ + p->f[8 * i + j] = 0; + ml_assign_cond_int16(&p->f[8 * i + j], (ML_KEM_Q + 1) / 2, + (m[i] >> j) & 0x1); + } + } +} + +/** + * Calculates ByteEncode_1(Compress_1()) for the given polynomial p to decode + * message m of length ML_KEM_SEED_LEN. + */ +static void poly_to_message(ml_poly_t *p, uint8_t *m) +{ + /* avoid division by replacing 2/q with [n/2^k] where n is [2^(k+1)/q] */ + const int k = 30; + const uint32_t n = ((1 << (k+1)) + ML_KEM_Q/2) / ML_KEM_Q; + + uint32_t f; + int i, j; + + for (i = 0; i < ML_KEM_SEED_LEN; i++) + { + m[i] = 0; + for (j = 0; j < 8; j++) + { + f = p->f[8 * i + j]; + /* calculate the compression [f * 2/q mod 2] without division */ + f = ((f * n + (1 << (k-1))) >> k) & 0x1; + m[i] |= f << j; + } + } +} + +/** + * Generate a key pair from the given random seed d. + * + * Algorithm 13 in FIPS 203. + */ +static bool pke_keygen(private_key_exchange_t *this, chunk_t d, chunk_t *ek, + chunk_t *dk) +{ + const uint8_t k = this->params->k; + const uint8_t eta1 = this->params->eta1; + + uint8_t seeds[2 * ML_KEM_SEED_LEN]; + uint8_t *rho = seeds; + uint8_t *sigma = seeds + ML_KEM_SEED_LEN; + uint8_t N = 0; + ml_poly_t a[k*k], s[k], e[k], t[k]; + int i; + bool success = FALSE; + + /* derive seeds for private and public key from randomness d and domain + * parameter k */ + if (!this->G->get_hash(this->G, d, NULL) || + !this->G->get_hash(this->G, chunk_from_thing(k), seeds)) + { + goto err; + } + + /* generate matrix A */ + if (!generate_a(this, a, rho)) + { + goto err; + } + + /* sample s from CBD using noise seed sigma and nonce N as input */ + for (i = 0; i < k; i++) + { + sample_poly_cbd(this, eta1, sigma, N++, &s[i]); + } + + /* sample e from CBD using noise seed sigma and nonce N as input */ + for (i = 0; i < k; i++) + { + sample_poly_cbd(this, eta1, sigma, N++, &e[i]); + } + + /* calculate s = NTT(s) */ + for (i = 0; i < k; i++) + { + ntt(this, &s[i]); + } + + /* calculate e = NTT(e) */ + for (i = 0; i < k; i++) + { + ntt(this, &e[i]); + } + + /* calculate t = A * s + e to get the public key */ + for (i = 0; i < k; i++) + { + multiply_poly_arr(k, &a[i*k], s, &t[i], FALSE); + } + ml_poly_add_arr(k, t, e, t); + + /* pack public key and rho */ + *ek = chunk_alloc(k * ML_KEM_POLY_LEN + ML_KEM_SEED_LEN); + encode_poly_arr(k, t, ek->ptr); + memcpy(ek->ptr + k * ML_KEM_POLY_LEN, rho, ML_KEM_SEED_LEN); + + /* pack private key */ + *dk = chunk_alloc(k * ML_KEM_POLY_LEN); + encode_poly_arr(k, s, dk->ptr); + + success = TRUE; + +err: + memwipe(seeds, sizeof(seeds)); + memwipe(sigma, ML_KEM_SEED_LEN); + memwipe(s, sizeof(s)); + memwipe(e, sizeof(e)); + return success; +} + +/** + * Encrypt randomness m using the given public key and randomness r that's + * derived from both. + * + * Algorithm 14 in FIPS 203. + */ +static bool pke_encrypt(private_key_exchange_t *this, chunk_t ek, uint8_t *m, + uint8_t *r, chunk_t ciphertext) +{ + const uint8_t k = this->params->k; + const uint8_t eta1 = this->params->eta1; + const uint8_t eta2 = this->params->eta2; + const uint8_t du = this->params->du; + const uint8_t dv = this->params->dv; + + uint8_t rho[ML_KEM_SEED_LEN]; + uint8_t N = 0; + ml_poly_t a[k*k], t[k], y[k], e1[k], e2, u[k], mu, v; + int i; + bool success = FALSE; + + /* decode polynomial t and extract seed rho from the public key */ + decode_poly_arr(k, ek.ptr, t); + memcpy(rho, ek.ptr + k * ML_KEM_POLY_LEN, ML_KEM_SEED_LEN); + + /* generate matrix A */ + if (!generate_a(this, a, rho)) + { + goto err; + } + + /* sample y from CBD using noise seed r and nonce N as input */ + for (i = 0; i < k; i++) + { + sample_poly_cbd(this, eta1, r, N++, &y[i]); + } + + /* sample e_1 from CBD using noise seed r and nonce N as input */ + for (i = 0; i < k; i++) + { + sample_poly_cbd(this, eta2, r, N++, &e1[i]); + } + + /* sample e_2 from CBD using noise seed r and nonce N as input */ + sample_poly_cbd(this, eta2, r, N++, &e2); + + /* calculate y = NTT(y) */ + for (i = 0; i < k; i++) + { + ntt(this, &y[i]); + } + + /* calculate u = NTT^-1(A^T * y) + e_1 */ + for (i = 0; i < k; i++) + { + multiply_poly_arr(k, &a[i], y, &u[i], TRUE); + } + for (i = 0; i < k; i++) + { + ntt_inv(this, &u[i]); + } + ml_poly_add_arr(k, u, e1, u); + + /* prepare plaintext message m */ + message_to_poly(m, &mu); + + /* calculate v = NTT^-1(t^T * y) + e_2 + mu to encrypt the plaintext */ + multiply_poly_arr(k, t, y, &v, FALSE); + ntt_inv(this, &v); + ml_poly_add(&v, &e2, &v); + ml_poly_add(&v, &mu, &v); + + /* encode u as c1 and v as c2, the two parts of the ciphertext */ + compress_polys_arr(k, du, u, ciphertext.ptr); + compress_polys_arr(1, dv, &v, ciphertext.ptr + k * du * ML_KEM_N / 8); + success = TRUE; + +err: + memwipe(y, sizeof(y)); + memwipe(e1, sizeof(e1)); + memwipe(&e2, sizeof(e2)); + memwipe(&mu, sizeof(mu)); + return success; +} + +/** + * Decrypt message m using the given private key and ciphertext. + * + * Algorithm 14 in FIPS 203. + */ +static bool pke_decrypt(private_key_exchange_t *this, chunk_t dk, + chunk_t ciphertext, uint8_t *m) +{ + const uint8_t k = this->params->k; + const uint8_t du = this->params->du; + const uint8_t dv = this->params->dv; + + ml_poly_t s[k], u[k], v, w; + int i; + + /* decode u and v from c1 and c2, the two parts of the ciphertext */ + decompress_poly_arr(k, du, ciphertext.ptr, u); + decompress_poly_arr(1, dv, ciphertext.ptr + k * du * ML_KEM_N / 8, &v); + + /* decode polynomial s from private key */ + decode_poly_arr(k, dk.ptr, s); + + /* calculate w = v - NTT^-1(s * NTT(u)) */ + for (i = 0; i < k; i++) + { + ntt(this, &u[i]); + } + multiply_poly_arr(k, s, u, &w, FALSE); + ntt_inv(this, &w); + ml_poly_sub(&v, &w, &w); + + /* decode plaintext message m from polynomial w */ + poly_to_message(&w, m); + + memwipe(s, sizeof(s)); + memwipe(&w, sizeof(w)); + return TRUE; +} + +/** + * Get random seeds and generate a key pair. + * + * Algorithm 16/19 in FIPS 203. + */ +static bool generate_keypair(private_key_exchange_t *this, chunk_t *ek) +{ + uint8_t dz[2*ML_KEM_SEED_LEN]; + chunk_t d = chunk_create(dz, ML_KEM_SEED_LEN); + chunk_t z = chunk_create(dz + ML_KEM_SEED_LEN, ML_KEM_SEED_LEN); + chunk_t dk = chunk_empty, Hek; + bool success = FALSE; + + /* get random seeds d and z */ + if (!get_random(this, sizeof(dz), dz)) + { + return FALSE; + } + + /* generate a key pair and store the private key, the public key, a hash + * of the latter and seed z as our secret key */ + if (pke_keygen(this, d, ek, &dk) && + this->H->allocate_hash(this->H, *ek, &Hek)) + { + this->private_key = chunk_cat("ccmc", dk, *ek, Hek, z); + success = TRUE; + } + + memwipe(dz, sizeof(dz)); + chunk_clear(&dk); + return success; +} + +METHOD(key_exchange_t, get_public_key, bool, + private_key_exchange_t *this, chunk_t *value) +{ + /* as responder, this method is called after set_public_key(), which + * encapsulated the secret to produce this ciphertext */ + if (this->ciphertext.ptr) + { + *value = chunk_clone(this->ciphertext); + return TRUE; + } + + /* as initiator, we generate a key pair and return the public key */ + return generate_keypair(this, value); +} + +/** + * Decapsulate a generated shared secret from the given ciphertext using our + * private key. + * + * Algorithm 18 in FIPS 203. + */ +static bool decaps_shared_secret(private_key_exchange_t *this, chunk_t ciphertext) +{ + const uint8_t k = this->params->k; + + chunk_t dk, ek, Hek, z, zc, c = chunk_empty; + chunk_t m = chunk_alloca(ML_KEM_SEED_LEN); + uint8_t Kr[2*ML_KEM_SEED_LEN]; + uint8_t *r = Kr + ML_KEM_SEED_LEN; + bool success = FALSE; + + /* get the private and public keys, a hash of the latter and seed z */ + chunk_split(this->private_key, "mmmm", + k * ML_KEM_POLY_LEN, &dk, + k * ML_KEM_POLY_LEN + ML_KEM_SEED_LEN, &ek, + ML_KEM_SEED_LEN, &Hek, + ML_KEM_SEED_LEN, &z); + /* prepare the seed to derive the implicit rejection secret */ + zc = chunk_cat("cc", z, ciphertext); + + /* decrypt message m */ + if (!pke_decrypt(this, dk, ciphertext, m.ptr)) + { + goto err; + } + + /* calculate (K, r) = G(m||H(ek)) */ + if (!this->G->get_hash(this->G, m, NULL) || + !this->G->get_hash(this->G, Hek, Kr)) + { + goto err; + } + + /* encrypt the decrypted message again using the derived r */ + c = chunk_alloc(this->params->ct_len); + if (!pke_encrypt(this, ek, m.ptr, r, c)) + { + goto err; + } + + this->shared_secret = chunk_alloc(ML_KEM_SEED_LEN); + + /* calculate the rejection value K_rej = J(z||c) as fallback */ + if (!this->shake256->set_seed(this->shake256, zc) || + !this->shake256->get_bytes(this->shake256, this->shared_secret.len, + this->shared_secret.ptr)) + { + goto err; + } + /* replace the shared secret with K based on whether our own ciphertext + * matches what we received (in constant time) */ + memcpy_cond(this->shared_secret.ptr, Kr, this->shared_secret.len, + chunk_equals_const(ciphertext, c)); + + success = TRUE; + +err: + memwipe(m.ptr, m.len); + memwipe(Kr, sizeof(Kr)); + chunk_clear(&zc); + chunk_free(&c); + return success; +} + +/** + * Encapsulate a generated shared secret using the given public key. + * + * Algorithm 17 in FIPS 203. + */ +static bool encaps_shared_secret(private_key_exchange_t *this, chunk_t public) +{ + chunk_t mH = chunk_alloca(2*ML_KEM_SEED_LEN); + uint8_t Kr[2*ML_KEM_SEED_LEN]; + uint8_t *r = Kr + ML_KEM_SEED_LEN; + bool success = FALSE; + + /* get a random message and calculate (K, r) = G(m||H(ek)) */ + if (!get_random(this, ML_KEM_SEED_LEN, mH.ptr) || + !this->H->get_hash(this->H, public, mH.ptr + ML_KEM_SEED_LEN) || + !this->G->get_hash(this->G, mH, Kr)) + { + goto err; + } + + /* encrypt the message using the derived r */ + this->ciphertext = chunk_alloc(this->params->ct_len); + if (pke_encrypt(this, public, mH.ptr, r, this->ciphertext)) + { + this->shared_secret = chunk_clone(chunk_create(Kr, ML_KEM_SEED_LEN)); + success = TRUE; + } + +err: + memwipe(mH.ptr, ML_KEM_SEED_LEN); + memwipe(Kr, sizeof(Kr)); + return success; +} + +/** + * Perform a modulus check as required by section 7.2 of FIPS 203. + */ +static bool validate_public_key(private_key_exchange_t *this, chunk_t public) +{ + const uint8_t k = this->params->k; + + ml_poly_t p[k]; + uint8_t ek[k * ML_KEM_POLY_LEN]; + + decode_poly_arr(k, public.ptr, p); + encode_poly_arr(k, p, ek); + return memeq_const(public.ptr, ek, sizeof(ek)); +} + +METHOD(key_exchange_t, set_public_key, bool, + private_key_exchange_t *this, chunk_t value) +{ + /* as initiator, we decapsulate the secret from the given ciphertext */ + if (this->private_key.ptr) + { + if (value.len != this->params->ct_len) + { + DBG1(DBG_LIB, "wrong %N ciphertext size of %u bytes, %u bytes expected", + key_exchange_method_names, this->method, value.len, + this->params->ct_len); + return FALSE; + } + return decaps_shared_secret(this, value); + } + + /* as responder, we generate a secret and encapsulate it */ + if (value.len != this->params->pk_len) + { + DBG1(DBG_LIB, "wrong %N public key size of %u bytes, %u bytes expected", + key_exchange_method_names, this->method, value.len, + this->params->pk_len); + return FALSE; + } + else if (!validate_public_key(this, value)) + { + DBG1(DBG_LIB, "%N public key encoding invalid", + key_exchange_method_names, this->method); + return FALSE; + } + return encaps_shared_secret(this, value); +} + +METHOD(key_exchange_t, get_method, key_exchange_method_t, + private_key_exchange_t *this) +{ + return this->method; +} + +METHOD(key_exchange_t, get_shared_secret, bool, + private_key_exchange_t *this, chunk_t *secret) +{ + *secret = chunk_clone(this->shared_secret); + return TRUE; +} + +METHOD(key_exchange_t, set_seed, bool, + private_key_exchange_t *this, chunk_t value, drbg_t *drbg) +{ + DESTROY_IF(this->drbg); + this->drbg = drbg->get_ref(drbg); + return TRUE; +} + +METHOD(key_exchange_t, destroy, void, + private_key_exchange_t *this) +{ + chunk_clear(&this->shared_secret); + chunk_clear(&this->private_key); + chunk_free(&this->ciphertext); + DESTROY_IF(this->drbg); + DESTROY_IF(this->shake128); + DESTROY_IF(this->shake256); + DESTROY_IF(this->G); + DESTROY_IF(this->H); + free(this); +} + +/* + * Described in header + */ +key_exchange_t *ml_kem_create(key_exchange_method_t method) +{ + private_key_exchange_t *this; + const ml_kem_params_t *params; + + params = ml_kem_params_get(method); + if (!params) + { + return NULL; + } + + INIT(this, + .public = { + .get_method = _get_method, + .get_public_key = _get_public_key, + .set_public_key = _set_public_key, + .get_shared_secret = _get_shared_secret, + .set_seed = _set_seed, + .destroy = _destroy, + }, + .method = method, + .params = params, + .shake128 = lib->crypto->create_xof(lib->crypto, XOF_SHAKE_128), + .shake256 = lib->crypto->create_xof(lib->crypto, XOF_SHAKE_256), + .G = lib->crypto->create_hasher(lib->crypto, HASH_SHA3_512), + .H = lib->crypto->create_hasher(lib->crypto, HASH_SHA3_256), + ); + + if (!this->shake128 || !this->shake256 || !this->G || !this->H) + { + destroy(this); + return NULL; + } + return &this->public; +} diff --git a/src/libstrongswan/plugins/ml/ml_kem.h b/src/libstrongswan/plugins/ml/ml_kem.h new file mode 100644 index 0000000000..323ab47d45 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_kem.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * Quantum-safe key encapsulation implementation using ML-KEM. + * + * @defgroup ml_kem ml_kem + * @{ @ingroup ml_p + */ + +#ifndef ML_KEM_H_ +#define ML_KEM_H_ + +#include + +/** + * Creates a new key_exchange_t object. + * + * @param method key exchange method + * @return key_exchange_t object, NULL if not supported + */ +key_exchange_t *ml_kem_create(key_exchange_method_t method); + +#endif /** ML_KEM_H_ @}*/ diff --git a/src/libstrongswan/plugins/ml/ml_params.c b/src/libstrongswan/plugins/ml/ml_params.c new file mode 100644 index 0000000000..89b1e6dbe5 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_params.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ml_params.h" + +/* + * Described in header + */ +const uint16_t ml_kem_zetas[128] = { + 1, 1729, 2580, 3289, 2642, 630, 1897, 848, + 1062, 1919, 193, 797, 2786, 3260, 569, 1746, + 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, + 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, + 289, 331, 3253, 1756, 1197, 2304, 2277, 2055, + 650, 1977, 2513, 632, 2865, 33, 1320, 1915, + 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, + 2647, 2617, 1481, 648, 2474, 3110, 1227, 910, + 17, 2761, 583, 2649, 1637, 723, 2288, 1100, + 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, + 1703, 1651, 2789, 1789, 1847, 952, 1461, 2687, + 939, 2308, 2437, 2388, 733, 2337, 268, 641, + 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, + 1063, 319, 2773, 757, 2099, 561, 2466, 2594, + 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, + 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, +}; + +/** + * Parameter sets for ML-KEM. + */ +static const ml_kem_params_t ml_kem_params[] = { + { + .method = ML_KEM_512, + .k = 2, + .eta1 = 3, + .eta2 = 2, + .du = 10, + .dv = 4, + .pk_len = 800, + .ct_len = 768, + }, + { + .method = ML_KEM_768, + .k = 3, + .eta1 = 2, + .eta2 = 2, + .du = 10, + .dv = 4, + .pk_len = 1184, + .ct_len = 1088, + }, + { + .method = ML_KEM_1024, + .k = 4, + .eta1 = 2, + .eta2 = 2, + .du = 11, + .dv = 5, + .pk_len = 1568, + .ct_len = 1568, + }, +}; + +/* + * Described in header + */ +const ml_kem_params_t *ml_kem_params_get(key_exchange_method_t method) +{ + int i; + + for (i = 0; i < countof(ml_kem_params); i++) + { + if (ml_kem_params[i].method == method) + { + return &ml_kem_params[i]; + } + } + return NULL; +} diff --git a/src/libstrongswan/plugins/ml/ml_params.h b/src/libstrongswan/plugins/ml/ml_params.h new file mode 100644 index 0000000000..585f57b736 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_params.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ml_params ml_params + * @{ @ingroup ml_p + */ + +#ifndef ML_PARAMS_H_ +#define ML_PARAMS_H_ + +#include + +typedef struct ml_kem_params_t ml_kem_params_t; + +/** + * Constant n used throughout the algorithms. + */ +#define ML_KEM_N 256 + +/** + * The prime q = 2^8 * 13 + 1. + */ +#define ML_KEM_Q 3329 + +/** + * Length of the seeds and hashes as well as the shared secret. + */ +#define ML_KEM_SEED_LEN 32 + +/** + * Length of an enoded polynomial (used for the public key). + */ +#define ML_KEM_POLY_LEN 384 + +/** + * Parameters for ML-KEM. + */ +struct ml_kem_params_t { + + /** + * Key exchange method. + */ + const key_exchange_method_t method; + + /** + * Module dimension k. + */ + const uint8_t k; + + /** + * Factor eta_1 for generating s, e and y. + */ + const uint8_t eta1; + + /** + * Factor eta_2 for generating e1 and e2. + */ + const uint8_t eta2; + + /** + * Parameter du for compression/encoding. + */ + const uint8_t du; + + /** + * Parameter dv for compression/encoding. + */ + const uint8_t dv; + + /** + * Length of the public key (k * ML_KEM_POLY_LEN + ML_KEM_SEED_LEN). + */ + const uint16_t pk_len; + + /** + * Length of the ciphertext ((ML_KEM_N * (k * du + kv)) / 8). + */ + const uint16_t ct_len; +}; + +/** + * Precalculated Zeta^BitRev_7(i) mod q values for NTT (see Appendix A in + * FIPS 203). The second half is also used as Zeta^(2*BitRev_7(i)+1) mod q + * values. + */ +extern const uint16_t ml_kem_zetas[128]; + +/** + * Get parameters fro a specific ML-KEM method. + * + * @param method key exchange method + * @return parameters, NULL if not supported + */ +const ml_kem_params_t *ml_kem_params_get(key_exchange_method_t method); + +#endif /** ML_PARAMS_H_ @}*/ diff --git a/src/libstrongswan/plugins/ml/ml_plugin.c b/src/libstrongswan/plugins/ml/ml_plugin.c new file mode 100644 index 0000000000..752c20da7c --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ml_plugin.h" + +#include + +#include "ml_kem.h" + +typedef struct private_plugin_t private_plugin_t; + +/** + * Private data. + */ +struct private_plugin_t { + + /** + * Public interface. + */ + plugin_t public; +}; + +METHOD(plugin_t, get_name, char*, + private_plugin_t *this) +{ + return "ml"; +} + +METHOD(plugin_t, get_features, int, + private_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_REGISTER(KE, ml_kem_create), + PLUGIN_PROVIDE(KE, ML_KEM_512), + PLUGIN_DEPENDS(HASHER, HASH_SHA3_256), + PLUGIN_DEPENDS(HASHER, HASH_SHA3_512), + PLUGIN_DEPENDS(XOF, XOF_SHAKE_128), + PLUGIN_DEPENDS(XOF, XOF_SHAKE_256), + PLUGIN_DEPENDS(RNG, RNG_STRONG), + PLUGIN_PROVIDE(KE, ML_KEM_768), + PLUGIN_DEPENDS(HASHER, HASH_SHA3_256), + PLUGIN_DEPENDS(HASHER, HASH_SHA3_512), + PLUGIN_DEPENDS(XOF, XOF_SHAKE_128), + PLUGIN_DEPENDS(XOF, XOF_SHAKE_256), + PLUGIN_DEPENDS(RNG, RNG_STRONG), + PLUGIN_PROVIDE(KE, ML_KEM_1024), + PLUGIN_DEPENDS(HASHER, HASH_SHA3_256), + PLUGIN_DEPENDS(HASHER, HASH_SHA3_512), + PLUGIN_DEPENDS(XOF, XOF_SHAKE_128), + PLUGIN_DEPENDS(XOF, XOF_SHAKE_256), + PLUGIN_DEPENDS(RNG, RNG_STRONG), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_plugin_t *this) +{ + free(this); +} + +/* + * Described in header + */ +plugin_t *ml_plugin_create() +{ + private_plugin_t *this; + + INIT(this, + .public = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + ); + + return &this->public; +} diff --git a/src/libstrongswan/plugins/ml/ml_plugin.h b/src/libstrongswan/plugins/ml/ml_plugin.h new file mode 100644 index 0000000000..19eb37d7a8 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_plugin.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * Plugin implementing Module-Lattice-based algorithms like ML-KEM in software. + * + * @defgroup ml_p ml + * @ingroup plugins + * + * @defgroup ml_plugin ml_plugin + * @{ @ingroup ml_p + */ + +#ifndef ML_PLUGIN_H_ +#define ML_PLUGIN_H_ + +#endif /** ML_PLUGIN_H_ @}*/ diff --git a/src/libstrongswan/plugins/ml/ml_poly.c b/src/libstrongswan/plugins/ml/ml_poly.c new file mode 100644 index 0000000000..cb45a12718 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_poly.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ml_poly.h" +#include "ml_utils.h" + +/* + * Described in header + */ +void ml_poly_add(ml_poly_t *a, ml_poly_t *b, ml_poly_t *res) +{ + int i; + + for (i = 0; i < ML_KEM_N; i++) + { + res->f[i] = ml_reduce_modq(a->f[i] + b->f[i]); + } +} + +/* + * Described in header + */ +void ml_poly_add_arr(u_int k, ml_poly_t *a, ml_poly_t *b, ml_poly_t *res) +{ + while (k--) + { + ml_poly_add(&a[k], &b[k], &res[k]); + } +} + +/* + * Described in header + */ +void ml_poly_sub(ml_poly_t *a, ml_poly_t *b, ml_poly_t *res) +{ + int i; + + for (i = 0; i < ML_KEM_N; i++) + { + res->f[i] = ml_reduce_modq(a->f[i] - b->f[i] + ML_KEM_Q); + } +} diff --git a/src/libstrongswan/plugins/ml/ml_poly.h b/src/libstrongswan/plugins/ml/ml_poly.h new file mode 100644 index 0000000000..3863598edb --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_poly.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ml_poly ml_poly + * @{ @ingroup ml_p + */ + +#ifndef ML_POLY_H_ +#define ML_POLY_H_ + +#include "ml_params.h" + +typedef struct ml_poly_t ml_poly_t; + +/** + * Represents an element in R_q = Z_q[X]/(X^n + 1) i.e. a polynomial of the + * form f[0] + f[1]*X + ... + f[n-1]*X^n-1. + */ +struct ml_poly_t { + + /** + * Coefficients of the polynomial. + */ + uint16_t f[ML_KEM_N]; +}; + +/** + * Add two polynomials (a + b mod q). + * + * @param a polynomial a + * @param b polynomial b + * @param res result of adding a and b (can be one of the others) + */ +void ml_poly_add(ml_poly_t *a, ml_poly_t *b, ml_poly_t *res); + +/** + * Add polynomials in array a and b (a[i] + b[i] mod q for i in 0 to k-1). + * + * @param k number of polynomials in each array + * @param a array of polynomials a + * @param b array of polynomials b + * @param res array of resulting polynomials (can be one of the others) + */ +void ml_poly_add_arr(u_int k, ml_poly_t *a, ml_poly_t *b, ml_poly_t *res); + +/** + * Subtract a polynomial from another (a - b mod q). + * + * @param a polynomial a + * @param b polynomial b + * @param res result of subtracting b from a (can be one of the others) + */ +void ml_poly_sub(ml_poly_t *a, ml_poly_t *b, ml_poly_t *res); + +#endif /** ML_POLY_H_ @}*/ diff --git a/src/libstrongswan/plugins/ml/ml_utils.c b/src/libstrongswan/plugins/ml/ml_utils.c new file mode 100644 index 0000000000..7b92f53aef --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_utils.c @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ml_utils.h" + +/* + * Described in header + */ +void ml_assign_cond_int16(int16_t *dst, int16_t val, uint16_t cond) +{ + cond = -cond; + *dst ^= cond & (val ^ *dst); +} + +/* + * Described in header + */ +uint32_t ml_read_bytes_le(uint8_t *buf, size_t len) +{ + uint32_t x = 0; + int i; + + for (i = 0; i < len; i++) + { + x |= (uint32_t)buf[i] << (8 * i); + } + return x; +} + +/* + * Described in header + */ +void ml_write_bytes_le(uint8_t *buf, size_t len, uint32_t val) +{ + int i; + + for (i = 0; i < len; i++) + { + buf[i] = val; + val >>= 8; + } +} diff --git a/src/libstrongswan/plugins/ml/ml_utils.h b/src/libstrongswan/plugins/ml/ml_utils.h new file mode 100644 index 0000000000..0258132108 --- /dev/null +++ b/src/libstrongswan/plugins/ml/ml_utils.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 Tobias Brunner + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ml_utils ml_utils + * @{ @ingroup ml_p + */ + +#ifndef ML_UTILS_H_ +#define ML_UTILS_H_ + +#include "ml_params.h" + +/** + * Returns a mod q for a in [0,2*q) in constant time. + * + * @param a value to reduce mod q + * @return reduced value + */ +static inline uint16_t ml_reduce_modq(uint16_t a) +{ + const uint16_t diff = a - ML_KEM_Q; + uint16_t mask = 0 - (diff >> 15); + + return (mask & a) | (~mask & diff); +} + +/** + * Used to assign the given value based on a condition in constant time and + * without branching. + * + * @param dst the value to set + * @param val value to set if condition is 1 + * @param cond 1 to set the value, 0 to leave as is + */ +void ml_assign_cond_int16(int16_t *dst, int16_t val, uint16_t cond); + +/** + * Read up to four bytes in little-endian order from the given buffer. + * + * @param buf byte buffer to read from + * @param len length betweeen 0 and 4 + * @return read value + */ +uint32_t ml_read_bytes_le(uint8_t *buf, size_t len); + +/** + * Write up to four bytes of the given value in little-endian order to a buffer. + * + * @param buf byte buffer to write to + * @param len length betweeen 0 and 4 + * @param val value to write + */ +void ml_write_bytes_le(uint8_t *buf, size_t len, uint32_t val); + +#endif /** ML_UTILS_H_ @}*/