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

Ounsworth KEM Combiner #4119

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

FAlbertDev
Copy link
Collaborator

@FAlbertDev FAlbertDev commented Jun 13, 2024

Ounsworth KEM Combiner

This KEM Combiner is based on this draft specification. Unlike X-Wing, the Ounsworth combiner (named after its main author) offers great flexibility by supporting any KEM combination. X-Wing, on the other hand, only specifies a single KEM combination (ML-KEM-768 with X25519). This flexibility is beneficial when using BSI-recommended algorithms such as FrodoKEM and brainpool curves.

Since the current draft is still in its early stages and lacks test vectors, the specification may change in the future. Additionally, I may have misinterpreted certain parts of the specification. Therefore, I have marked the implementation as experimental using the BOTAN_UNSTABLE_API macro.

Structure of the Implementation

The Ounsworth KEM Combiner is implemented as a regular KEM. It combines any number of sub-KEMs with a specified KDF. To load/create Ounsworth private and public keys users must define how to load or create the individual keys respectively. This is done using the Ounsworth::PrivateKeyImportInfo, Ounsworth::PublicKeyImportInfo, and Ounsworth::PublicKeyGenerationInfo classes.

To provide flexibility in defining sub-KEMs, I want to create an interface that allows users to define and use their own KEMs in the Ounsworth Combiner. This is useful in one of our research projects where experimental PKCS#11-based instances must be combined with the Ounsworth Combiner.

However, I also understand that most applications have more straightforward requirements. To accommodate this, I provide a straightforward constructor for the predefined algorithms such as Kyber, FrodoKEM for PQC, and X25519, X448, and ECDH for the classic choice. These algorithms can be selected using an enum value.
Example:

// Generate new sk
Ounsworth_PrivateKey private_key(rng, {Ounsworth::Sub_Algo_Type::Kyber768_R3,
                                       Ounsworth::Sub_Algo_Type::ECDH_Secp256R1},
                                      Ounsworth::Kdf::Option::KMAC128);

// Load sk from bytes
Ounsworth_PrivateKey loaded_sk(private_key.private_key_bits(),
                               {Ounsworth::Sub_Algo_Type::Kyber768_R3,
                                Ounsworth::Sub_Algo_Type::ECDH_Secp256R1},
                               Ounsworth::Kdf::Option::KMAC128);

// Load pk from bytes
Ounsworth_PublicKey loaded_pk(private_key.public_key_bits(),
                              {Ounsworth::Sub_Algo_Type::Kyber768_R3,
                               Ounsworth::Sub_Algo_Type::ECDH_Secp256R1},
                              Ounsworth::Kdf::Option::KMAC128);

Some limitations with the current public/private key interface make generalization a bit messy. For example, to concatenate public keys without any encoding, we need to know the length of the public key in advance. The same goes for private keys. To address this, we may want to add methods like pk_byte_length() and sk_byte_length() for the private and public key interfaces in the future. For now, I worked around this limitation using algorithm-dependent methods.

I decided to compose the Ounsworth key using the raw public and private key bytes. Although the encoding is not specified in the draft, I believe this is the best approach. However, in most cases, this prevents me from using Botan's generic interfaces, which adds further complexity to the implementation (see ounsworth_mode.cpp).

Testing

Since no test vectors are available yet, I used some tests covering most of the draft's requirements.

  • The underlying One-Step KDF of NIST.SP.800-56C is extended and tested in One-Step Key Derivation Method with KMAC #4121.

  • To ensure that the predefined KEMs are configured correctly, I conducted a roundtrip test using all predefined algorithms simultaneously.

  • The only potential source of error remaining is the creation of the KDF inputs, specifically the composition of various fields and lengths to form the KDF input. To test this, I created a handcrafted test vector that should represent other combinations.

  • I also tested the key interfaces using the generic keygen tests.

General Remarks

The draft is still in its early stages, so I had to make some assumptions. For instance, the structure of the private and public keys is not specified in the draft. To address this, I documented my decisions in the doxygen documentation of the private key class.

I've already defined some OIDs in Botan's private arc to make it easier to use in protocols. These OIDs use sensible combinations for different key strengths. I've even included combinations with Frodo and Brainpool curves, which the BSI recommends. I crafted these combinations myself, but feel free to suggest other options.

I welcome any feedback on the implementation.

Pull request dependencies

@randombit
Copy link
Owner

Initial comment is that the KDFs should be split out into their own PR, and should be actual KDFs

The hash based one in already implemented as SP800-56A, with the apparent extension required that 56A hash KDF doesn't support a salt (LOL LMAO WTAF) and 56C does.

The KMAC one should be implemented in a dedicated way - that is to say requiring and using only KMAC rather than being theoretically generic over the mechanism.

@FAlbertDev
Copy link
Collaborator Author

FAlbertDev commented Jun 13, 2024

Ops, you are right. I thought I'd looked into the existing KDFs and decided they didn't match. I'll create a separate PR where I address this 👍

@coveralls
Copy link

Coverage Status

coverage: 91.781% (+0.003%) from 91.778%
when pulling 7fc6fe4 on Rohde-Schwarz:ounsworth-kem-combiner
into f02c602 on randombit:master.

@coveralls
Copy link

Coverage Status

coverage: 91.778%. remained the same
when pulling 001cc5f on Rohde-Schwarz:ounsworth-kem-combiner
into f02c602 on randombit:master.

@coveralls
Copy link

Coverage Status

coverage: 91.779% (+0.001%) from 91.778%
when pulling b6d7b3b on Rohde-Schwarz:ounsworth-kem-combiner
into f02c602 on randombit:master.

@coveralls
Copy link

Coverage Status

coverage: 91.781% (+0.003%) from 91.778%
when pulling c3a9e8e on Rohde-Schwarz:ounsworth-kem-combiner
into f02c602 on randombit:master.

@reneme
Copy link
Collaborator

reneme commented Jun 15, 2024

With #4126 merged, please rebase to fix the macOS build.

@FAlbertDev
Copy link
Collaborator Author

Rebased: ounsworth_kem_combiner->kem-combiner-abstraction->master

@coveralls
Copy link

Coverage Status

coverage: 91.769% (-0.002%) from 91.771%
when pulling 541f3d3 on Rohde-Schwarz:ounsworth-kem-combiner
into a902bba on randombit:master.

@randombit randombit added this to the Botan 3.6.0 milestone Jun 30, 2024
@FAlbertDev
Copy link
Collaborator Author

I've rebased to master and did some major refactoring. Using a single Mode is too inflexible. Now, applications can either pass lists of public and private keys directly or define callbacks to generate/load private/public keys directly. These callbacks are no longer collected in a single _Ounsworth_Mode_but are used individually for the respective constructors. (I updated the PR description above)

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