Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c3d640c
Add support for ML-KEM
bifurcation Nov 12, 2025
fa8a9ca
clang-format
bifurcation Nov 12, 2025
40d25ba
Add testing of ML-KEM
bifurcation Nov 12, 2025
adef760
Specuatively enable OQS for OpenSSL 3 and BoringSSL
bifurcation Nov 12, 2025
c051122
Update build configuration to work for OpenSSL 3 and BoringSSL
bifurcation Nov 14, 2025
d75dcc9
Add implementations using OpenSSL3 and BoringSSL
bifurcation Nov 14, 2025
1cdaea8
Remove BoringSSL support because of missing SHAKE256
bifurcation Nov 14, 2025
55786a5
clang-format
bifurcation Nov 14, 2025
0a2df86
CI fixes
bifurcation Nov 14, 2025
0264c11
Test against the HPKE PQ test vectors
bifurcation Nov 14, 2025
5a6ceec
Pass test vectors
bifurcation Nov 14, 2025
fc5f3ef
clang-format
bifurcation Nov 14, 2025
cc758bd
Change config order to avoid imposing flags on libOQS
bifurcation Nov 16, 2025
6f16348
Use liboqs from environment instead of vendored version
bifurcation Nov 18, 2025
a676f21
Use vcpkg for libOQS
bifurcation Nov 19, 2025
d87cdc6
Revert hack changes
bifurcation Nov 19, 2025
7665075
Build interop tests with OpenSSL 3
bifurcation Nov 19, 2025
17ae3e2
More hybrid KEM implementation
bifurcation Nov 14, 2025
bec7601
Add SHA3_256
bifurcation Nov 14, 2025
f9ca669
Factor out SHAKE256
bifurcation Nov 14, 2025
cba9229
More hybrid KEM implementation
bifurcation Nov 15, 2025
7d65a8c
Add support for hybrid KEMs
bifurcation Nov 15, 2025
9aee1c6
Add PQ test vectors
bifurcation Nov 16, 2025
f9d4140
Merge branch 'main' into hybrid-kem
bifurcation Nov 20, 2025
ad09eed
clang-format
bifurcation Nov 20, 2025
bdf1d21
Get rid of strlen
bifurcation Nov 20, 2025
7d86d73
Add a flag to disable PQ and use it consistently
bifurcation Nov 21, 2025
cd7884d
clang-format
bifurcation Nov 21, 2025
3a1afee
Add missing comma
bifurcation Nov 21, 2025
06b6bcc
Fix CI issue
bifurcation Nov 21, 2025
fecaec4
More integer conversions
bifurcation Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ option(SANITIZERS "Enable sanitizers" OFF)
option(MLS_NAMESPACE_SUFFIX "Namespace Suffix for CXX and CMake Export")
option(DISABLE_GREASE "Disables the inclusion of MLS protocol recommended GREASE values" OFF)
option(REQUIRE_BORINGSSL "Require BoringSSL instead of OpenSSL" OFF)
option(DISABLE_PQ "Disables support for PQ algorithms even when they would otherwise be enabled" OFF)

if(MLS_NAMESPACE_SUFFIX)
set(MLS_CXX_NAMESPACE "mls_${MLS_NAMESPACE_SUFFIX}" CACHE STRING "Top-level Namespace for CXX")
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ devB: ${TOOLCHAIN_FILE}
-DVCPKG_MANIFEST_DIR=${BORINGSSL_MANIFEST} \
-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}

# Like `dev`, but using OpenSSL 3 with PQ disabled
dev-no-pq:
cmake -B${BUILD_DIR} -DTESTING=ON -DCMAKE_BUILD_TYPE=Debug \
-DDISABLE_PQ=ON \
-DVCPKG_MANIFEST_DIR=${OPENSSL3_MANIFEST} \
-DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE}

test: ${BUILD_DIR} test/*
cmake --build ${BUILD_DIR} --target mlspp_test

Expand Down
11 changes: 10 additions & 1 deletion lib/hpke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,19 @@ if ( OPENSSL_FOUND )

elseif (REQUIRE_BORINGSSL)
message(FATAL_ERROR "BoringSSL required but not found")
elseif (${OPENSSL_VERSION} VERSION_GREATER_EQUAL 3.5)
target_compile_definitions(${CURRENT_LIB_NAME} PUBLIC WITH_OPENSSL3)

if(NOT DISABLE_PQ)
target_compile_definitions(${CURRENT_LIB_NAME} PUBLIC WITH_PQ)
endif()
elseif (${OPENSSL_VERSION} VERSION_GREATER_EQUAL 3)
target_compile_definitions(${CURRENT_LIB_NAME} PUBLIC WITH_OPENSSL3)
elseif (${OPENSSL_VERSION} VERSION_GREATER_EQUAL 1.1.1)
set(USING_LIBOQS ON)
if(NOT DISABLE_PQ)
set(USING_LIBOQS ON)
target_compile_definitions(${CURRENT_LIB_NAME} PUBLIC WITH_PQ)
endif()
else()
message(FATAL_ERROR "OpenSSL 1.1.1 or greater is required")
endif()
Expand Down
16 changes: 16 additions & 0 deletions lib/hpke/include/hpke/digest.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <memory>

#include <bytes/bytes.h>
#include <hpke/hpke.h>
#include <namespace.h>

using namespace MLS_NAMESPACE::bytes_ns;
Expand All @@ -16,6 +17,9 @@ struct Digest
SHA256,
SHA384,
SHA512,
#if !defined(WITH_BORINGSSL)
SHA3_256,
#endif
};

template<ID id>
Expand All @@ -35,4 +39,16 @@ struct Digest
friend struct HKDF;
};

#if !defined(WITH_BORINGSSL)
struct SHAKE256
{
static bytes derive(const bytes& ikm, size_t length);
static bytes labeled_derive(KEM::ID kem_id,
const bytes& ikm,
const std::string& label,
const bytes& context,
size_t length);
};
#endif

} // namespace MLS_NAMESPACE::hpke
7 changes: 7 additions & 0 deletions lib/hpke/include/hpke/hpke.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ struct KEM
DHKEM_X25519_SHA256 = 0x0020,
#if !defined(WITH_BORINGSSL)
DHKEM_X448_SHA512 = 0x0021,
#endif
#if defined(WITH_PQ)
MLKEM512 = 0x0040,
MLKEM768 = 0x0041,
MLKEM1024 = 0x0042,
MLKEM768_P256 = 0x0050,
MLKEM1024_P384 = 0x0051,
MLKEM768_X25519 = 0x647a,
#endif
};

Expand All @@ -42,6 +47,7 @@ struct KEM
};

const ID id;
const size_t seed_size;
const size_t secret_size;
const size_t enc_size;
const size_t pk_size;
Expand Down Expand Up @@ -71,6 +77,7 @@ struct KEM

protected:
KEM(ID id_in,
size_t seed_size_in,
size_t secret_size_in,
size_t enc_size_in,
size_t pk_size_in,
Expand Down
1,380 changes: 1,380 additions & 0 deletions lib/hpke/scripts/test-vectors-pq.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/hpke/src/dhkem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ DHKEM::get<KEM::ID::DHKEM_X448_SHA512>()

DHKEM::DHKEM(KEM::ID kem_id_in, const Group& group_in, const KDF& kdf_in)
: KEM(kem_id_in,
group_in.seed_size,
kdf_in.hash_size,
group_in.pk_size,
group_in.pk_size,
Expand Down
65 changes: 65 additions & 0 deletions lib/hpke/src/digest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <openssl/core_names.h>
#endif

#include "common.h"
#include "openssl_common.h"

namespace MLS_NAMESPACE::hpke {
Expand All @@ -24,6 +25,11 @@ openssl_digest_type(Digest::ID digest)
case Digest::ID::SHA512:
return EVP_sha512();

#if !defined(WITH_BORINGSSL)
case Digest::ID::SHA3_256:
return EVP_sha3_256();
#endif

default:
throw std::runtime_error("Unsupported ciphersuite");
}
Expand All @@ -43,6 +49,9 @@ openssl_digest_name(Digest::ID digest)
case Digest::ID::SHA512:
return OSSL_DIGEST_NAME_SHA2_512;

case Digest::ID::SHA3_256:
return OSSL_DIGEST_NAME_SHA3_256;

default:
throw std::runtime_error("Unsupported digest algorithm");
}
Expand Down Expand Up @@ -73,6 +82,16 @@ Digest::get<Digest::ID::SHA512>()
return instance;
}

#if !defined(WITH_BORINGSSL)
template<>
const Digest&
Digest::get<Digest::ID::SHA3_256>()
{
static const Digest instance(Digest::ID::SHA3_256);
return instance;
}
#endif

Digest::Digest(Digest::ID id_in)
: id(id_in)
, hash_size(EVP_MD_size(openssl_digest_type(id_in)))
Expand Down Expand Up @@ -185,4 +204,50 @@ Digest::hmac_for_hkdf_extract(const bytes& key, const bytes& data) const
return md;
}

#if !defined(WITH_BORINGSSL)
bytes
SHAKE256::derive(const bytes& ikm, size_t length)
{
auto ctx = make_typed_unique(EVP_MD_CTX_new());
if (!ctx) {
throw openssl_error();
}

if (EVP_DigestInit_ex(ctx.get(), EVP_shake256(), nullptr) != 1) {
throw openssl_error();
}

if (EVP_DigestUpdate(ctx.get(), ikm.data(), ikm.size()) != 1) {
throw openssl_error();
}

auto out = bytes(length);
if (EVP_DigestFinalXOF(ctx.get(), out.data(), out.size()) != 1) {
throw openssl_error();
}

return out;
}

bytes
SHAKE256::labeled_derive(KEM::ID kem_id,
const bytes& ikm,
const std::string& label,
const bytes& context,
size_t length)
{
const auto hpke_version = from_ascii("HPKE-v1");
const auto label_kem = from_ascii("KEM");
const auto suite_id = label_kem + i2osp(uint16_t(kem_id), 2);
const auto label_bytes = from_ascii(label);
const auto label_len = i2osp(uint16_t(label_bytes.size()), 2);
const auto length_bytes = i2osp(uint16_t(length), 2);

return derive(ikm + hpke_version + suite_id + label_len + label_bytes +
length_bytes + context,
length);
}

#endif // !defined(WITH_BORINGSSL)

} // namespace MLS_NAMESPACE::hpke
88 changes: 88 additions & 0 deletions lib/hpke/src/group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,57 @@ struct ECKeyGroup : public EVPGroup
}
}

#if defined(WITH_OPENSSL3)
auto key = keypair_evp_key(sk);
return std::make_unique<EVPGroup::PrivateKey>(key.release());
#else
auto pt = make_typed_unique(EC_POINT_new(group));
EC_POINT_mul(group, pt.get(), sk.get(), nullptr, nullptr, nullptr);

EC_KEY_set_private_key(eckey.get(), sk.get());
EC_KEY_set_public_key(eckey.get(), pt.get());

auto pkey = to_pkey(eckey.release());
return std::make_unique<PrivateKey>(pkey.release());
#endif
}

std::unique_ptr<Group::PrivateKey> random_scalar(
const bytes& seed) const override
{
#if defined(WITH_OPENSSL3)
auto* group = EC_GROUP_new_by_curve_name_ex(nullptr, nullptr, curve_nid);
auto group_ptr = make_typed_unique(group);
#else
auto eckey = new_ec_key();
const auto* group = EC_KEY_get0_group(eckey.get());
#endif

auto order = make_typed_unique(BN_new());
if (1 != EC_GROUP_get_order(group, order.get(), nullptr)) {
throw openssl_error();
}

auto sk = make_typed_unique(BN_new());
BN_zero(sk.get());

auto start = size_t(0);
auto end = sk_size;
auto candidate = seed.slice(start, end);
auto candidate_size = static_cast<int>(candidate.size());
sk.reset(BN_bin2bn(candidate.data(), candidate_size, nullptr));

while (BN_is_zero(sk.get()) != 0 || BN_cmp(sk.get(), order.get()) != -1) {
start = end;
end = end + sk_size;
if (end > seed.size()) {
throw std::runtime_error("Rejection sampling failed");
}

candidate = seed.slice(start, end);
sk.reset(BN_bin2bn(candidate.data(), candidate_size, nullptr));
}

#if defined(WITH_OPENSSL3)
auto key = keypair_evp_key(sk);
return std::make_unique<EVPGroup::PrivateKey>(key.release());
Expand Down Expand Up @@ -785,6 +836,16 @@ struct RawKeyGroup : public EVPGroup
return deserialize_private(skm);
}

std::unique_ptr<Group::PrivateKey> random_scalar(
const bytes& seed) const override
{
if (seed.size() != sk_size) {
throw std::runtime_error("Invalid seed");
}

return deserialize_private(seed);
}

bytes serialize(const Group::PublicKey& pk) const override
{
const auto& rpk = dynamic_cast<const PublicKey&>(pk);
Expand Down Expand Up @@ -952,6 +1013,32 @@ Group::get<Group::ID::Ed448>()
return instance;
}

static inline size_t
group_seed_size(Group::ID group_id)
{
switch (group_id) {
case Group::ID::P256:
return 128;
case Group::ID::P384:
return 48;
case Group::ID::P521:
// XXX(RLB): This may be wrong, but we're never going to use it
return 66;
case Group::ID::X25519:
return 32;
case Group::ID::X448:
return 56;

// Non-DH groups
case Group::ID::Ed25519:
case Group::ID::Ed448:
return 0;

default:
throw std::runtime_error("Unknown group");
}
}

static inline size_t
group_dh_size(Group::ID group_id)
{
Expand Down Expand Up @@ -1066,6 +1153,7 @@ group_jwk_key_type(Group::ID group_id)

Group::Group(ID group_id_in, const KDF& kdf_in)
: id(group_id_in)
, seed_size(group_seed_size(group_id_in))
, dh_size(group_dh_size(group_id_in))
, pk_size(group_pk_size(group_id_in))
, sk_size(group_sk_size(group_id_in))
Expand Down
3 changes: 3 additions & 0 deletions lib/hpke/src/group.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct Group
virtual ~Group() = default;

const ID id;
const size_t seed_size;
const size_t dh_size;
const size_t pk_size;
const size_t sk_size;
Expand All @@ -51,6 +52,8 @@ struct Group
virtual std::unique_ptr<PrivateKey> derive_key_pair(
const bytes& suite_id,
const bytes& ikm) const = 0;
virtual std::unique_ptr<PrivateKey> random_scalar(
const bytes& seed) const = 0;

virtual bytes serialize(const PublicKey& pk) const = 0;
virtual std::unique_ptr<PublicKey> deserialize(const bytes& enc) const = 0;
Expand Down
4 changes: 2 additions & 2 deletions lib/hpke/src/hkdf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ digest_to_kdf(Digest::ID digest_id)
return KDF::ID::HKDF_SHA384;
case Digest::ID::SHA512:
return KDF::ID::HKDF_SHA512;
default:
throw std::runtime_error("Unsupported algorithm");
}

throw std::runtime_error("Unsupported algorithm");
}

HKDF::HKDF(const Digest& digest_in)
Expand Down
Loading
Loading