update KEM trait

This commit is contained in:
Jan Winkelmann (keks)
2025-02-26 13:01:29 +01:00
parent a1f41953b7
commit d61b137761
16 changed files with 256 additions and 69 deletions

1
Cargo.lock generated
View File

@@ -1828,6 +1828,7 @@ dependencies = [
"anyhow",
"rosenpass-oqs",
"rosenpass-secret-memory",
"thiserror 2.0.11",
]
[[package]]

View File

@@ -10,6 +10,7 @@ repository = "https://github.com/rosenpass/rosenpass"
readme = "readme.md"
[dependencies]
thiserror = "2"
[dev-dependencies]
rosenpass-oqs = { workspace = true }

View File

@@ -1,5 +1,2 @@
mod primitives;
pub use primitives::*;
mod kem;
pub use kem::Kem;

View File

@@ -1 +1,2 @@
pub mod kem;
pub mod keyed_hash;

View File

@@ -0,0 +1,171 @@
//! Traits and implementations for Key Encapsulation Mechanisms (KEMs)
//!
//! KEMs are the interface provided by almost all post-quantum
//! secure key exchange mechanisms.
//!
//! Conceptually KEMs are akin to public-key encryption, but instead of encrypting
//! arbitrary data, KEMs are limited to the transmission of keys, randomly chosen during
//! encapsulation.
//!
//! The [Kem] Trait describes the basic API offered by a Key Encapsulation
//! Mechanism. Two implementations for it are provided:
//! [Kyber512](../../rosenpass_oqs/kyber_512/enum.Kyber512.html) and
//! [ClassicMceliece460896](../../rosenpass_oqs/classic_mceliece_460896/enum.ClassicMceliece460896.html).
//!
//! An example where Alice generates a keypair and gives her public key to Bob, for Bob to
//! encapsulate a symmetric key and Alice to decapsulate it would look as follows.
//! In the example, we are using Kyber512, but any KEM that correctly implements the [Kem]
//! trait could be used as well.
//!```rust
//! use rosenpass_cipher_traits::Kem;
//! use rosenpass_oqs::Kyber512;
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
//!
//! type MyKem = Kyber512;
//! secret_policy_use_only_malloc_secrets();
//! let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
//! let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
//! MyKem::keygen(alice_sk.secret_mut(), &mut alice_pk)?;
//!
//! let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
//! let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
//! MyKem::encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
//!
//! let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
//! MyKem::decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
//!
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
//! # Ok::<(), anyhow::Error>(())
//!```
//!
//! Implementing the [Kem]-trait for a KEM is easy. Mostly, you must format the KEM's
//! keys, and ciphertext as `u8` slices. Below, we provide an example for how the trait can
//! be implemented using a **HORRIBLY INSECURE** DummyKem that only uses static values for keys
//! and ciphertexts as an example.
//!```rust
//!# use rosenpass_cipher_traits::Kem;
//!
//! struct DummyKem {}
//! impl Kem for DummyKem {
//!
//! // For this DummyKem, using String for errors is sufficient.
//! type Error = String;
//!
//! // For this DummyKem, we will use a single `u8` for everything
//! const SK_LEN: usize = 1;
//! const PK_LEN: usize = 1;
//! const CT_LEN: usize = 1;
//! const SHK_LEN: usize = 1;
//!
//! fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Result<(), Self::Error> {
//! if sk.len() != Self::SK_LEN {
//! return Err("sk does not have the correct length!".to_string());
//! }
//! if pk.len() != Self::PK_LEN {
//! return Err("pk does not have the correct length!".to_string());
//! }
//! sk[0] = 42;
//! pk[0] = 21;
//! Ok(())
//! }
//!
//! fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Result<(), Self::Error> {
//! if pk.len() != Self::PK_LEN {
//! return Err("pk does not have the correct length!".to_string());
//! }
//! if ct.len() != Self::CT_LEN {
//! return Err("ct does not have the correct length!".to_string());
//! }
//! if shk.len() != Self::SHK_LEN {
//! return Err("shk does not have the correct length!".to_string());
//! }
//! if pk[0] != 21 {
//! return Err("Invalid public key!".to_string());
//! }
//! ct[0] = 7;
//! shk[0] = 17;
//! Ok(())
//! }
//!
//! fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Result<(), Self::Error> {
//! if sk.len() != Self::SK_LEN {
//! return Err("sk does not have the correct length!".to_string());
//! }
//! if ct.len() != Self::CT_LEN {
//! return Err("ct does not have the correct length!".to_string());
//! }
//! if shk.len() != Self::SHK_LEN {
//! return Err("shk does not have the correct length!".to_string());
//! }
//! if sk[0] != 42 {
//! return Err("Invalid public key!".to_string());
//! }
//! if ct[0] != 7 {
//! return Err("Invalid ciphertext!".to_string());
//! }
//! shk[0] = 17;
//! Ok(())
//! }
//! }
//! # use rosenpass_secret_memory::{secret_policy_use_only_malloc_secrets, Secret};
//! #
//! # type MyKem = DummyKem;
//! # secret_policy_use_only_malloc_secrets();
//! # let mut alice_sk: Secret<{ MyKem::SK_LEN }> = Secret::zero();
//! # let mut alice_pk: [u8; MyKem::PK_LEN] = [0; MyKem::PK_LEN];
//! # MyKem::keygen(alice_sk.secret_mut(), &mut alice_pk)?;
//!
//! # let mut bob_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
//! # let mut bob_ct: [u8; MyKem::CT_LEN] = [0; MyKem::CT_LEN];
//! # MyKem::encaps(bob_shk.secret_mut(), &mut bob_ct, &mut alice_pk)?;
//! #
//! # let mut alice_shk: Secret<{ MyKem::SHK_LEN }> = Secret::zero();
//! # MyKem::decaps(alice_shk.secret_mut(), alice_sk.secret_mut(), &mut bob_ct)?;
//! #
//! # assert_eq!(alice_shk.secret(), bob_shk.secret());
//! #
//! # Ok::<(), String>(())
//!```
//!
use thiserror::Error;
/// Key Encapsulation Mechanism
///
/// The KEM interface defines three operations: Key generation, key encapsulation and key
/// decapsulation.
pub trait Kem<const SK_LEN: usize, const PK_LEN: usize, const CT_LEN: usize, const SHK_LEN: usize> {
const SK_LEN: usize = SK_LEN;
const PK_LEN: usize = PK_LEN;
const CT_LEN: usize = CT_LEN;
const SHK_LEN: usize = SHK_LEN;
/// Generate a keypair consisting of secret key (`sk`) and public key (`pk`)
///
/// `keygen() -> sk, pk`
fn keygen(sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), Error>;
/// From a public key (`pk`), generate a shared key (`shk`, for local use)
/// and a cipher text (`ct`, to be sent to the owner of the `pk`).
///
/// `encaps(pk) -> shk, ct`
fn encaps(
shk: &mut [u8; SHK_LEN],
ct: &mut [u8; CT_LEN],
pk: &[u8; PK_LEN],
) -> Result<(), Error>;
/// From a secret key (`sk`) and a cipher text (`ct`) derive a shared key
/// (`shk`)
///
/// `decaps(sk, ct) -> shk`
fn decaps(shk: &mut [u8; SHK_LEN], sk: &[u8; SK_LEN], ct: &[u8; CT_LEN]) -> Result<(), Error>;
}
#[derive(Debug, Error)]
pub enum Error {
#[error("invalid argument")]
InvalidArgument,
#[error("internal error")]
InternalError,
}

View File

@@ -4,7 +4,7 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass::protocol::CryptoServer;
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::policy::*;
use rosenpass_secret_memory::{PublicBox, Secret};

View File

@@ -4,7 +4,7 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::EphemeralKem;
#[derive(arbitrary::Arbitrary, Debug)]

View File

@@ -3,7 +3,7 @@ extern crate rosenpass;
use libfuzzer_sys::fuzz_target;
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
fuzz_target!(|input: [u8; StaticKem::PK_LEN]| {

View File

@@ -5,8 +5,7 @@ macro_rules! oqs_kem {
($name:ident) => { ::paste::paste!{
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
mod [< $name:snake >] {
use rosenpass_cipher_traits::Kem;
use rosenpass_util::result::Guaranteed;
use rosenpass_cipher_traits::kem;
#[doc = "Bindings for ::oqs_sys::kem::" [<"OQS_KEM" _ $name:snake>] "_*"]
#[doc = ""]
@@ -39,6 +38,11 @@ macro_rules! oqs_kem {
#[doc = "```"]
pub enum [< $name:camel >] {}
pub const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
pub const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
pub const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
pub const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
/// # Panic & Safety
///
/// This Trait impl calls unsafe [oqs_sys] functions, that write to byte
@@ -51,17 +55,8 @@ macro_rules! oqs_kem {
/// to only check that the buffers are big enough, allowing them to be even
/// bigger. However, from a correctness point of view it does not make sense to
/// allow bigger buffers.
impl Kem for [< $name:camel >] {
type Error = ::std::convert::Infallible;
const SK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_secret_key >] as usize;
const PK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_public_key >] as usize;
const CT_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_ciphertext >] as usize;
const SHK_LEN: usize = ::oqs_sys::kem::[<OQS_KEM _ $name:snake _ length_shared_secret >] as usize;
fn keygen(sk: &mut [u8], pk: &mut [u8]) -> Guaranteed<()> {
assert_eq!(sk.len(), Self::SK_LEN);
assert_eq!(pk.len(), Self::PK_LEN);
impl kem::Kem<SK_LEN, PK_LEN, CT_LEN, SHK_LEN> for [< $name:camel >] {
fn keygen(sk: &mut [u8; SK_LEN], pk: &mut [u8; PK_LEN]) -> Result<(), kem::Error> {
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ keypair >],
@@ -73,10 +68,7 @@ macro_rules! oqs_kem {
Ok(())
}
fn encaps(shk: &mut [u8], ct: &mut [u8], pk: &[u8]) -> Guaranteed<()> {
assert_eq!(shk.len(), Self::SHK_LEN);
assert_eq!(ct.len(), Self::CT_LEN);
assert_eq!(pk.len(), Self::PK_LEN);
fn encaps(shk: &mut [u8; SHK_LEN], ct: &mut [u8; CT_LEN], pk: &[u8; PK_LEN]) -> Result<(), kem::Error> {
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ encaps >],
@@ -89,10 +81,7 @@ macro_rules! oqs_kem {
Ok(())
}
fn decaps(shk: &mut [u8], sk: &[u8], ct: &[u8]) -> Guaranteed<()> {
assert_eq!(shk.len(), Self::SHK_LEN);
assert_eq!(sk.len(), Self::SK_LEN);
assert_eq!(ct.len(), Self::CT_LEN);
fn decaps(shk: &mut [u8; SHK_LEN], sk: &[u8; SK_LEN], ct: &[u8; CT_LEN]) -> Result<(), kem::Error> {
unsafe {
oqs_call!(
::oqs_sys::kem::[< OQS_KEM _ $name:snake _ decaps >],

View File

@@ -4,7 +4,7 @@ use rosenpass::protocol::{
};
use std::ops::DerefMut;
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
use criterion::{black_box, criterion_group, criterion_main, Criterion};

View File

@@ -5,7 +5,7 @@
use anyhow::{bail, ensure, Context};
use clap::{Parser, Subcommand};
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::file::StoreSecret;
use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue};

View File

@@ -13,7 +13,7 @@ use std::u8;
use zerocopy::{AsBytes, FromBytes, FromZeroes};
use super::RosenpassError;
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::{aead, xaead, KEY_LEN};

View File

@@ -4,7 +4,6 @@
//! files.
use std::borrow::Borrow;
use std::convert::Infallible;
use std::fmt::Debug;
use std::mem::size_of;
use std::ops::Deref;
@@ -21,8 +20,8 @@ use rand::Fill as Randomize;
use crate::{hash_domains, msgs::*, RosenpassError};
use memoffset::span_of;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_cipher_traits::keyed_hash::KeyedHashInstance;
use rosenpass_cipher_traits::Kem;
use rosenpass_ciphers::hash_domain::{SecretHashDomain, SecretHashDomainNamespace};
use rosenpass_ciphers::kem::{EphemeralKem, StaticKem};
use rosenpass_ciphers::subtle::keyed_hash::KeyedHash;
@@ -3334,31 +3333,77 @@ impl HandshakeState {
///
/// This is used to include asymmetric cryptography in the rosenpass protocol
// I loathe "error: constant expression depends on a generic parameter"
pub fn encaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
pub fn encaps_and_mix<
const KEM_SK_LEN: usize,
const KEM_PK_LEN: usize,
const KEM_CT_LEN: usize,
const KEM_SHK_LEN: usize,
T: Kem<KEM_SK_LEN, KEM_PK_LEN, KEM_CT_LEN, KEM_SHK_LEN>,
>(
&mut self,
ct: &mut [u8],
pk: &[u8],
ct: &mut [u8; KEM_CT_LEN],
pk: &[u8; KEM_PK_LEN],
) -> Result<&mut Self> {
let mut shk = Secret::<SHK_LEN>::zero();
let mut shk = Secret::<KEM_SHK_LEN>::zero();
T::encaps(shk.secret_mut(), ct, pk)?;
self.mix(pk)?.mix(shk.secret())?.mix(ct)
}
pub fn encaps_and_mix_static(
&mut self,
ct: &mut [u8; StaticKem::CT_LEN],
pk: &[u8; StaticKem::PK_LEN],
) -> Result<&mut Self> {
self.encaps_and_mix::<{StaticKem::SK_LEN},{ StaticKem::PK_LEN}, {StaticKem::CT_LEN}, {StaticKem::SHK_LEN}, StaticKem>(ct, pk)
}
pub fn encaps_and_mix_ephemeral(
&mut self,
ct: &mut [u8; EphemeralKem::CT_LEN],
pk: &[u8; EphemeralKem::PK_LEN],
) -> Result<&mut Self> {
self.encaps_and_mix::<{EphemeralKem::SK_LEN},{ EphemeralKem::PK_LEN}, {EphemeralKem::CT_LEN}, {EphemeralKem::SHK_LEN}, EphemeralKem>(ct, pk)
}
/// Decapsulation (decryption) counterpart to [Self::encaps_and_mix].
///
/// Makes sure that the same values are mixed into the chaining that where mixed in on the
/// sender side.
pub fn decaps_and_mix<T: Kem<Error = Infallible>, const SHK_LEN: usize>(
pub fn decaps_and_mix<
const KEM_SK_LEN: usize,
const KEM_PK_LEN: usize,
const KEM_CT_LEN: usize,
const KEM_SHK_LEN: usize,
T: Kem<KEM_SK_LEN, KEM_PK_LEN, KEM_CT_LEN, KEM_SHK_LEN>,
>(
&mut self,
sk: &[u8],
pk: &[u8],
ct: &[u8],
sk: &[u8; KEM_SK_LEN],
pk: &[u8; KEM_PK_LEN],
ct: &[u8; KEM_CT_LEN],
) -> Result<&mut Self> {
let mut shk = Secret::<SHK_LEN>::zero();
let mut shk = Secret::<KEM_SHK_LEN>::zero();
T::decaps(shk.secret_mut(), sk, ct)?;
self.mix(pk)?.mix(shk.secret())?.mix(ct)
}
pub fn decaps_and_mix_static(
&mut self,
sk: &[u8; StaticKem::SK_LEN],
pk: &[u8; StaticKem::PK_LEN],
ct: &[u8; StaticKem::CT_LEN],
) -> Result<&mut Self> {
self.decaps_and_mix::<{StaticKem::SK_LEN},{ StaticKem::PK_LEN}, {StaticKem::CT_LEN}, {StaticKem::SHK_LEN}, StaticKem>(sk, pk, ct)
}
pub fn decaps_and_mix_ephemeral(
&mut self,
sk: &[u8; EphemeralKem::SK_LEN],
pk: &[u8; EphemeralKem::PK_LEN],
ct: &[u8; EphemeralKem::CT_LEN],
) -> Result<&mut Self> {
self.decaps_and_mix::<{EphemeralKem::SK_LEN},{ EphemeralKem::PK_LEN}, {EphemeralKem::CT_LEN}, {EphemeralKem::SHK_LEN}, EphemeralKem>(sk, pk, ct)
}
/// Store the chaining key inside a cookie value called a "biscuit".
///
/// This biscuit can be transmitted to the other party and must be returned
@@ -3547,10 +3592,7 @@ impl CryptoServer {
// IHI5
hs.core
.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
ih.sctr.as_mut_slice(),
peer.get(self).spkt.deref(),
)?;
.encaps_and_mix_static(&mut ih.sctr, peer.get(self).spkt.deref())?;
// IHI6
hs.core.encrypt_and_mix(
@@ -3593,11 +3635,7 @@ impl CryptoServer {
core.mix(&ih.sidi)?.mix(&ih.epki)?;
// IHR5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(),
self.spkm.deref(),
&ih.sctr,
)?;
core.decaps_and_mix_static(self.sskm.secret(), self.spkm.deref(), &ih.sctr)?;
// IHR6
let peer = {
@@ -3623,13 +3661,10 @@ impl CryptoServer {
core.mix(&rh.sidr)?.mix(&rh.sidi)?;
// RHR4
core.encaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(&mut rh.ecti, &ih.epki)?;
core.encaps_and_mix_ephemeral(&mut rh.ecti, &ih.epki)?;
// RHR5
core.encaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
&mut rh.scti,
peer.get(self).spkt.deref(),
)?;
core.encaps_and_mix_static(&mut rh.scti, peer.get(self).spkt.deref())?;
// RHR6
core.store_biscuit(self, peer, &mut rh.biscuit)?;
@@ -3689,18 +3724,10 @@ impl CryptoServer {
core.mix(&rh.sidr)?.mix(&rh.sidi)?;
// RHI4
core.decaps_and_mix::<EphemeralKem, { EphemeralKem::SHK_LEN }>(
hs!().eski.secret(),
hs!().epki.deref(),
&rh.ecti,
)?;
core.decaps_and_mix_ephemeral(hs!().eski.secret(), hs!().epki.deref(), &rh.ecti)?;
// RHI5
core.decaps_and_mix::<StaticKem, { StaticKem::SHK_LEN }>(
self.sskm.secret(),
self.spkm.deref(),
&rh.scti,
)?;
core.decaps_and_mix_static(self.sskm.secret(), self.spkm.deref(), &rh.scti)?;
// RHI6
core.mix(&rh.biscuit)?;

View File

@@ -12,7 +12,7 @@ use rosenpass::{
app_server::{AppServer, AppServerTest, MAX_B64_KEY_SIZE},
protocol::{SPk, SSk, SymKey},
};
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_util::{file::LoadValueB64, functional::run, mem::DiscardResultExt, result::OkExt};

View File

@@ -5,7 +5,7 @@ use std::{
ops::DerefMut,
};
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_util::result::OkExt;

View File

@@ -10,7 +10,7 @@ use rosenpass_util::file::{LoadValueB64, StoreValue, StoreValueB64};
use zeroize::Zeroize;
use rosenpass::protocol::{SPk, SSk};
use rosenpass_cipher_traits::Kem;
use rosenpass_cipher_traits::kem::Kem;
use rosenpass_ciphers::kem::StaticKem;
use rosenpass_secret_memory::{file::StoreSecret as _, Public, Secret};