Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import mlkem-native #2176

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

hanno-becker
Copy link
Contributor

@hanno-becker hanno-becker commented Feb 7, 2025

License

mlkem-native (everything under crypto/fipsmodule/ml_kem/mlkem/**) is imported under the Apache 2.0 license (only). The LICENSE file is updated accordingly.

Integration-specific code (everything with direct parent crypto/fipsmodule/ml_kem/*) is made under the terms of the Apache 2.0 license and the ISC license.

Import mlkem-native

This imports mlkem-native (https://github.com/pq-code-package/mlkem-native, maintained by myself and @mkannwischer) into AWS-LC, replacing the reference implementation.

This PR focuses on the minimal configuration of mlkem-native: No assembly and no FIPS-202 code are imported.

mlkem-native is a high-performance, high-assurance C90 implementation of ML-KEM developed under the Post-Quantum Cryptography Alliance (PQCA) and the Linux Foundation. It is a fork of the reference implementation that AWS-LC previously relied on, and remains close to it. mlkem-native is the default ML-KEM implementation in libOQS.

Import Mechanism

The mlkem-native source code is unmodified and imported using the importer script crypto/fipsmodule/ml_kem/importer.sh; the details of the import are in META.yml.

A custom config is provided for mlkem-native which in particular includes a small 'compatibility layer' between AWS-LC/OpenSSL and mlkem-native -- see below.

Future imports (C-only)

Future updates of the C-only mlkem-native source tree should happen through a re-import of mlkem-native: That is, (a) delete crypto/fipsmodule/ml_kem/mlkem and (b) re-run import.sh. This will re-import mlkem-native/main, though you can set the GITHUB_SHA and GITHUB_REPOSITORY environment variables to point to any other mlkem-native repository/fork.
Temporary ad-hoc changes of crypto/fipsmodule/ml_kem/mlkem/* with accompanying change-log are conceivable as well, similar to how the changes from the reference implementation were documented so far -- but those should be used sparingly and ideally be upstreamed.

Once this PR is merged, we will also merge pq-code-package/mlkem-native#654 into mlkem-native, which adds an integration test doing (a)+(b) above on any PR and checking that the basic AWS-LC build (FIPS and non-FIPS) still works. This way, we will ensure that are no large surprises when AWS-LC wants to re-import a newer version of mlkem-native.

Future imports (native code)

Once we have verified meaningful parts of the mlkem-native assembly backends, PRs will be filed to integrate those. The details for this integration are TBD and not necessary to finalize for this PR. The options I see are (a) extending import.sh to import larger parts of the mlkem-native upstream source tree, including native backends, (b) writing custom backends, backed by sources living in the s2n-bignum source tree. Both is possible and compatible with this PR, and we can discuss nearer the time.

Import Scope

mlkem-native has a C-only version as well as native 'backends' in AVX2 and Neon for high performance. This commit only imports the C-only version. Integration of native backends will be done separately.

mlkem-native offers its own FIPS-202 implementation, including fast versions of batched FIPS-202. However, this commit does not import those, but instead provides glue-code around AWS-LC's own FIPS-202 implementation. The path to leveraging the FIPS-202 performance improvements in mlkem-native would be to integrate them directly
into crypto/fipsmodule/sha.

Impact on build

None. No build-files are modified.

Compatibility layer

The configuration file mlkem_native_config.h includes a compatibility layer between AWS-LC/OpenSSL and mlkem-native, covering:

  • FIPS/PCT: If AWSLC_FIPS is set, MLK_KEYGEN_PCT is enabled to include a PCT.
  • FIPS/PCT: If BORINGSSL_FIPS_BREAK_TESTS is set, MLK_KEYGEN_PCT_BREAKAGE_TEST is set and mlk_break_pct defined via boringssl_fips_break_test("MLKEM_PWCT"), to include runtime-breakage of the PCT for testing purposes.
  • CT: If BORINGSSL_CONSTANT_TIME_VALIDATION is set, then MLK_CT_TESTING_ENABLED is set to enable valgrind testing.
  • Zeroization: MLK_CUSTOM_ZEROIZE is set and mlk_zeroize mapped to OPENSSL_cleanse to use OpenSSL's zeroization function.
  • Randombytes: MLK_CUSTOM_RANDOMBYTES is set and mlk_randombytes mapped to RAND_bytes to use OpenSSL's zeroization function.

Side-channels

mlkem-native's CI uses a patched version of valgrind to check for various compilers and compile flags that there are no secret-dependent memory accesses, branches, or divisions. The relevant assertions have been kept but are unused unless MLK_CT_TESTING_ENABLED is set, which is the case if and only if BORINGSSL_CONSTANT_TIME_VALIDATION is set.

Similar to AWS-LC, mlkem-native uses value barriers to block potentially harmful compiler reasoning and optimization. Where standard gcc/clang inline assembly is not available, mlkem-native falls back to a slower 'opt blocker' based on a volatile global (an idea we picked up from DjB) -- both is described in verify.h. It will be interesting to see if the opt-blocker variant works on all platforms that AWS-LC cares about.

Formal Verification

All C-code imported in this commit is formally verified using the C Bounded Model Checker (CBMC) to be free of various classes of undefined behaviour, including out-of-bounds memory accesses and arithmetic overflow; the latter is of particular interest for ML-KEM because of the use of lazy modular reduction for improved performance.

The heart of the CBMC proofs are function contract and loop annotations to the C-code. Function contracts are denoted __contract__(...) clauses and occur at the time of declaration, while loop contracts are denoted __loop__ and follow the for statement.

The function contract and loop statements are kept in the source, but removed by the preprocessor so long as the CBMC macro is undefined. Keeping them simplifies the import, and care has been taken to make them readable to the non-expert, and thereby serve as precise documentation of assumptions and guarantees upheld by the code.

The CBMC proofs are automatic and don't require further proofs scripts; yet, they come with their own build system and toolchain dependencies, which this commit does not attempt to import. See proofs/cbmc in the mlkem-native repository. Mid-term, however, CI infrastructure should be setup that allows to import and check the CBMC proofs as part of the AWS-LC CI.

FIPS Compliance

The current reference implementation in AWS-LC accommodates FIPS (IG) requirements via:

  • Adding explicit stack buffer via OPENSSL_cleanse
  • Adding a Pairwise Consistency Test (PCT) after key generation (only for the FIPS-build)

mlkem-native unconditionally includes stack zeroization. mlkem-native's default secure memset is replaced by OPENSSL_cleanse.

mlkem-native conditionally includes a PCT, guarded by MLK_KEYGEN_PCT. This is set in the config if and only if AWSLC_FIPS is set.

Formatting

Code in crypto/fipsmodule/ml_kem/mlkem is directly imported from mlkem-native and comes with its own crypto/fipsmodule/ml_kem/mlkem/.clang-format.

Prefix build

The prefix build should not be affected by the import, since no definitions of external linkage are imported (everything is tagged either static directly, or MLK_EXTERNAL_API or MLK_INTERNAL_API, both of which are set to static in the context of the import, too).

Performance

It is expected -- but should be checked! -- that the ML-KEM performance with this PR is comparable to that of the reference implementation. This is because the mlkem-native's fast backends are not yet imported, the FIPS-202 code remains that of AWS-LC, and mlkem-native is otherwise close to the reference implementation.

Multilevel build

At the core, mlkem-native is currently a 'single-level' implementation of ML-KEM: A build of the main source tree provides an implementation of exactly one of ML-KEM-512/768/1024, depending on the MLKEM_K parameter. This property is inherited from the ML-KEM reference implementation, while AWS-LC's fork of the reference implementation has changed this behaviour and passes the security level as a runtime parameter.

To build all security levels, level-specific sources are built 3 times, once per security level, and linked with a single build of the level-independent code. The single-compilation-unit approach pursued by AWS-LC makes this process fairly simple since one merely needs to include the single-compilation-unit file provided by mlkem-native three times, and configure it so that the level-independent code is included only once. The final include moreover #undef'ines all macros defined by mlkem-native, reducing the risk of name clashes with other parts of crypto/fipsmodule/bcm.c.

Note that this process is entirely internal to ml_kem.c, and does not affect the AWS-LC build.

Main differences from reference implementation

mlkem-native is a fork of the ML-KEM reference implementation.

The following gives an overview of the major changes:

  • CBMC and debug annotations, and minor code restructurings or signature changes to facilitate the CBMC proofs. For example, poly_add(x,a) only comes in a destructive variant to avoid specifying aliasing constraints; poly_rej_uniform has an additional offset parameter indicating the position in the sampling buffer, to avoid passing shifted pointers).
  • Introduction of 4x-batched versions of some functions from the reference implementation. This is to leverage 4x-batched Keccak-f1600 implementations if present. The batching happens at the C level even if no native backend for FIPS 202 is present.
  • FIPS 203 compliance: Introduced PK (FIPS 203, Section 7.2, 'modulus check') and SK (FIPS 203, Section 7.3, 'hash check') check, as well as optional PCT (FIPS 203, Section 7.1, Pairwise Consistency). Also, introduced zeroization of stack buffers as required by (FIPS 203, Section 3.3, Destruction of intermediate values).
  • Introduction of native backend implementations. With the exception of the native backend for poly_rej_uniform(), which may fail and fall back to the C implementation, those are drop-in replacements for the corresponding C functions and dispatched at compile-time.
  • Restructuring of files to separate level-specific from level-generic functionality. This is needed to enable a multi-level build of mlkem-native where level-generic code is shared between levels.
  • More pervasive use of value barriers to harden constant-time primitives, even when Link-Time-Optimization (LTO) is enabled. The use of LTO can lead to insecure compilation in case of the reference implementation.
  • Use of a multiplication cache ('mulcache') structure to simplify and speedup the base multiplication.
  • Different placement of modular reductions: We reduce to unsigned canonical representatives in poly_reduce(), and assume such in all polynomial compression functions. The reference implementation works with a signed poly_reduce(), and embeds various signed->unsigned conversions in the compression functions.
  • More inlining: Modular multiplication and primitives are in a header rather than a separate compilation unit.

@codecov-commenter
Copy link

codecov-commenter commented Feb 7, 2025

Codecov Report

Attention: Patch coverage is 99.85915% with 1 line in your changes missing coverage. Please review.

Project coverage is 79.04%. Comparing base (85b5a95) to head (40c60e5).

Files with missing lines Patch % Lines
crypto/fipsmodule/ml_kem/mlkem/kem.c 98.43% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2176      +/-   ##
==========================================
+ Coverage   79.01%   79.04%   +0.02%     
==========================================
  Files         614      617       +3     
  Lines      106934   107043     +109     
  Branches    15143    15141       -2     
==========================================
+ Hits        84493    84611     +118     
+ Misses      21787    21778       -9     
  Partials      654      654              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hanno-becker hanno-becker marked this pull request as ready for review February 7, 2025 16:37
@hanno-becker hanno-becker requested a review from a team as a code owner February 7, 2025 16:37
@torben-hansen torben-hansen self-requested a review February 7, 2025 18:21
Copy link
Contributor

@jakemas jakemas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will also review (perhaps in collaboration with Nevine as an official reviewer)

@hanno-becker
Copy link
Contributor Author

hanno-becker commented Feb 19, 2025

CI broken since #2192; see #2192 (comment). Adjusted to #2192.

@hanno-becker hanno-becker force-pushed the mlkem_native_pr branch 3 times, most recently from abf6a67 to f81cb51 Compare February 20, 2025 06:24
@hanno-becker hanno-becker requested a review from nebeid February 20, 2025 14:45
@hanno-becker hanno-becker force-pushed the mlkem_native_pr branch 2 times, most recently from 7f0f3f8 to 3a3b631 Compare February 24, 2025 20:39
@hanno-becker hanno-becker force-pushed the mlkem_native_pr branch 4 times, most recently from 1c43e38 to 09ad19e Compare February 27, 2025 06:31
torben-hansen
torben-hansen previously approved these changes Mar 3, 2025
@hanno-becker
Copy link
Contributor Author

Gotcha, it is not obvious whether this import is actually a release or not.

@torben-hansen I think it makes sense to have a tag for the final import (we also did one for the libOQS import), but for now it's just any commit, yes.

This commit removes the reference implementation of ML-KEM from
the source tree, in preparation for the integration of mlkem-native.

Signed-off-by: Hanno Becker <[email protected]>
This imports mlkem-native (https://github.com/pq-code-package/mlkem-native,
maintained by myself and @mkannwischer) into AWS-LC, replacing the
reference implementation.

This PR focuses on the minimal configuration of mlkem-native: No assembly
and no FIPS-202 code are imported.

mlkem-native is a high-performance, high-assurance C90 implementation of
ML-KEM developed under the Post-Quantum Cryptography Alliance (PQCA) and
the Linux Foundation. It is a fork of the reference implementation that
AWS-LC previously relied on, and remains close to it. mlkem-native is the
default ML-KEM implementation in
[libOQS](https://github.com/open-quantum-safe/liboqs).

**Import Mechanism**

The mlkem-native source code is unmodified and imported using the importer
script `crypto/fipsmodule/ml_kem/importer.sh`; the details of the import
are in META.yml.

A custom config is provided for mlkem-native which in particular includes a
small 'compatibility layer' between AWS-LC/OpenSSL and mlkem-native -- see
below.

**Import Scope**

mlkem-native has a C-only version as well as native 'backends' in AVX2 and
Neon for high performance. This commit only imports the C-only
version. Integration of native backends will be done separately.

mlkem-native offers its own FIPS-202 implementation, including fast
versions of batched FIPS-202. However, this commit does not import those,
but instead provides glue-code around AWS-LC's own FIPS-202
implementation. The path to leveraging the FIPS-202 performance
improvements in mlkem-native would be to integrate them directly into
[crypto/fipsmodule/sha](crytpo/fipsmodule/sha).

**Impact on build**

None. No build-files are modified.

**Compatibility layer**

The configuration file `mlkem_native_config.h` includes a compatibility
layer between AWS-LC/OpenSSL and mlkem-native, covering:
* FIPS/PCT: If `AWSLC_FIPS` is set, `MLK_KEYGEN_PCT` is enabled to include
a PCT.
* FIPS/PCT: If `BORINGSSL_FIPS_BREAK_TESTS` is set,
`MLK_KEYGEN_PCT_BREAKAGE_TEST` is set and `mlk_break_pct` defined via
`boringssl_fips_break_test("MLKEM_PWCT")`, to include runtime-breakage of
the PCT for testing purposes.
* CT: If `BORINGSSL_CONSTANT_TIME_VALIDATION` is set, then
`MLK_CT_TESTING_ENABLED` is set to enable valgrind testing.
* Zeroization: `MLK_CUSTOM_ZEROIZE` is set and `mlk_zeroize`
mapped to `OPENSSL_cleanse` to use OpenSSL's zeroization function.
* Randombytes: `MLK_CUSTOM_RANDOMBYTES` is set and `mlk_randombytes`
mapped to `RAND_bytes` to use AWS-LC's randombytes function.

**Side-channels**

mlkem-native's CI uses a patched version of valgrind to check for various
compilers and compile flags that there are no secret-dependent memory
accesses, branches, or divisions. The relevant assertions have been kept
but are unused unless `MLK_CT_TESTING_ENABLED` is set, which is the case if
and only if `BORINGSSL_CONSTANT_TIME_VALIDATION` is set.

Similar to AWS-LC, mlkem-native uses value barriers to block potentially
harmful compiler reasoning and optimization. Where standard gcc/clang
inline assembly is not available, mlkem-native falls back to a slower 'opt
blocker' based on a volatile global (an idea we picked up from DjB) -- both
is described in
[verify.h](https://github.com/aws/aws-lc/blob/df5b09029e27d54b2b117eeddb6abd983528ae15/crypto/fipsmodule/ml_kem/mlkem/verify.h). It
will be interesting to see if the opt-blocker variant works on all
platforms that AWS-LC cares about.

**Formal Verification**

All C-code imported in this commit is formally verified using the C Bounded
Model Checker ([CBMC](https://github.com/diffblue/cbmc/)) to be free of
various classes of undefined behaviour, including out-of-bounds memory
accesses and arithmetic overflow; the latter is of particular interest for
ML-KEM because of the use of lazy modular reduction for improved
performance.

The heart of the CBMC proofs are function contract and loop annotations to
the C-code. Function contracts are denoted `__contract__(...)` clauses and
occur at the time of declaration, while loop contracts are denoted
`__loop__` and follow the `for` statement.

The function contract and loop statements are kept in the source, but
removed by the preprocessor so long as the CBMC macro is undefined. Keeping
them simplifies the import, and care has been taken to make them readable
to the non-expert, and thereby serve as precise documentation of
assumptions and guarantees upheld by the code.

The CBMC proofs are automatic and don't require further proofs scripts;
yet, they come with their own build system and toolchain dependencies,
which this commit does not attempt to import. See
[proofs/cbmc](https://github.com/pq-code-package/mlkem-native/tree/main/proofs/cbmc)
in the mlkem-native repository. Mid-term, however, CI infrastructure should
be setup that allows to import and check the CBMC proofs as part of the
AWS-LC CI.

**FIPS Compliance**

The current reference implementation in AWS-LC accommodates FIPS (IG)
requirements via:
* Adding explicit stack buffer via `OPENSSL_cleanse`
* Adding a Pairwise Consistency Test (PCT) after key generation (only for
the FIPS-build)

mlkem-native unconditionally includes stack zeroization. mlkem-native's
default secure `memset` is replaced by `OPENSSL_cleanse`.

mlkem-native conditionally includes a PCT, guarded by
`MLK_KEYGEN_PCT`. This is set in the config if and only if `AWSLC_FIPS` is
set.

**Formatting**

Code in `crypto/fipsmodule/ml_kem/mlkem` is directly imported from
mlkem-native and comes with its own
`crypto/fipsmodule/ml_kem/mlkem/.clang-format`.

**Prefix build**

The prefix build should not be affected by the import, since no definitions
of external linkage are imported (everything is tagged either `static`
directly, or `MLK_EXTERNAL_API` or `MLK_INTERNAL_API`, both of which are
set to `static` in the context of the import, too).

**Performance**

It is expected -- but should be checked! -- that the ML-KEM performance
with this PR is comparable to that of the reference implementation. This is
because the mlkem-native's fast backends are not yet imported, the FIPS-202
code remains that of AWS-LC, and mlkem-native is otherwise close to the
reference implementation.

**Multilevel build**

At the core, mlkem-native is currently a 'single-level' implementation of
ML-KEM: A build of the main source tree provides an implementation of
exactly one of ML-KEM-512/768/1024, depending on the MLKEM_K
parameter. This property is inherited from the ML-KEM reference
implementation, while AWS-LC's fork of the reference implementation has
changed this behaviour and passes the security level as a runtime
parameter.

To build all security levels, level-specific sources are built 3 times,
once per security level, and linked with a single build of the
level-independent code. The single-compilation-unit approach pursued by
AWS-LC makes this process fairly simple since one merely needs to include
the single-compilation-unit file provided by mlkem-native three times, and
configure it so that the level-independent code is included only once. The
final include moreover `#undef`'ines all macros defined by mlkem-native,
reducing the risk of name clashes with other parts of
crypto/fipsmodule/bcm.c.

Note that this process is entirely internal to `ml_kem.c`, and does not
affect the AWS-LC build.

**Main differences from reference implementation**

mlkem-native is a fork of the ML-KEM [reference
implementation](https://github.com/pq-crystals/kyber).

The following gives an overview of the major changes:

- CBMC and debug annotations, and minor code restructurings or signature
  changes to facilitate the CBMC proofs. For example, `poly_add(x,a)` only
  comes in a destructive variant to avoid specifying aliasing constraints;
  `poly_rej_uniform` has an additional `offset` parameter indicating the
  position in the sampling buffer, to avoid passing shifted pointers).
- Introduction of 4x-batched versions of some functions from the reference
  implementation. This is to leverage 4x-batched Keccak-f1600 implementations
  if present. The batching happens at the C level even if no native backend
  for FIPS 202 is present.
- FIPS 203 compliance: Introduced PK (FIPS 203, Section 7.2, 'modulus
  check') and SK (FIPS 203, Section 7.3, 'hash check') check, as well as
  optional PCT (FIPS 203, Section 7.1, Pairwise Consistency). Also,
  introduced zeroization of stack buffers as required by (FIPS 203, Section
  3.3, Destruction of intermediate values).
- Introduction of native backend implementations. With the exception of the
  native backend for `poly_rej_uniform()`, which may fail and fall back to
  the C implementation, those are drop-in replacements for the corresponding
  C functions and dispatched at compile-time.
- Restructuring of files to separate level-specific from level-generic
  functionality. This is needed to enable a multi-level build of mlkem-native
  where level-generic code is shared between levels.
- More pervasive use of value barriers to harden constant-time primitives,
  even when Link-Time-Optimization (LTO) is enabled. The use of LTO can lead
  to insecure compilation in case of the reference implementation.
- Use of a multiplication cache ('mulcache') structure to simplify and
  speedup the base multiplication.
- Different placement of modular reductions: We reduce to _unsigned_
  canonical representatives in `poly_reduce()`, and _assume_ such in all
  polynomial compression functions. The reference implementation works with a
  _signed_ `poly_reduce()`, and embeds various signed->unsigned conversions
  in the compression functions.
- More inlining: Modular multiplication and primitives are in a header
  rather than a separate compilation unit.

Signed-off-by: Hanno Becker <[email protected]>
This commit updates the LICENSE file to indicate that
mlkem-native is distributed under Apache 2.0.

Signed-off-by: Hanno Becker <[email protected]>
@hanno-becker
Copy link
Contributor Author

hanno-becker commented Mar 24, 2025

Rebased to update to latest mlkem-native main, and to use @manastasova's changes from #2247.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants