]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
ml: Add software implementation of ML-KEM
authorTobias Brunner <tobias@strongswan.org>
Mon, 21 Oct 2024 08:38:14 +0000 (10:38 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 22 Nov 2024 13:14:08 +0000 (14:14 +0100)
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.

15 files changed:
configure.ac
src/libstrongswan/Makefile.am
src/libstrongswan/plugins/ml/Makefile.am [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_bitpacker.c [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_bitpacker.h [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_kem.c [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_kem.h [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_params.c [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_params.h [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_plugin.c [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_plugin.h [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_poly.c [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_poly.h [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_utils.c [new file with mode: 0644]
src/libstrongswan/plugins/ml/ml_utils.h [new file with mode: 0644]

index 4b3aa0ad4d269a2a571a7a1e2af92f43f15c0f2d..81906acfc5afca7bae9d0ac907f1cd7bc5cd7551 100644 (file)
@@ -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
index c130032be15dc592625829a3085c0653ea68dcd7..8d3c869bd986bf96c1abf4c2275ab1292de9143f 100644 (file)
@@ -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 (file)
index 0000000..e0d8d2b
--- /dev/null
@@ -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 (file)
index 0000000..d208bbb
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..7918b6b
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <library.h>
+
+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 (file)
index 0000000..94bf4da
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..323ab47
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <library.h>
+
+/**
+ * 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 (file)
index 0000000..89b1e6d
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..585f57b
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <crypto/key_exchange.h>
+
+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 (file)
index 0000000..752c20d
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 <plugins/plugin.h>
+
+#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 (file)
index 0000000..19eb37d
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..cb45a12
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..3863598
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..7b92f53
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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 (file)
index 0000000..0258132
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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_ @}*/