-
-
Notifications
You must be signed in to change notification settings - Fork 779
RFC: Add bindings for ML-KEM and ML-DSA. #2405
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
Open
teythoon
wants to merge
27
commits into
sfackler:master
Choose a base branch
from
teythoon:justus/pqc
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
0307d7c
Add internal module to simplify working with OSSL_PARAM structure
Jakuje 88e9baa
kdf: Use the OsslParam API to simplify building OSSL_PARAM
Jakuje 7ceae78
Use the new OpenSSL 3.* API for managing EVP_PKEY
Jakuje d26c91b
Expose encapsulation and decapsulation.
teythoon a3984e7
Add support for ML-KEM.
teythoon f60f7b7
Expose message signing and verification.
teythoon 2864758
Add support for ML-DSA.
teythoon f87f6ff
fixup! Add support for ML-DSA.
teythoon 176a99d
fixup! Expose encapsulation and decapsulation.
teythoon c0c1dc2
fixup! Expose message signing and verification.
teythoon d617f8b
fixup! Expose message signing and verification.
teythoon 804620c
fixup! Expose encapsulation and decapsulation.
teythoon fd63113
fixup! Add support for ML-KEM.
teythoon 9525529
fixup! Expose message signing and verification.
teythoon c72455b
fixup! Add support for ML-DSA.
teythoon 7fb4d0b
Appease warnings about unused imports and functions.
teythoon efcdbf3
fixup! Appease warnings about unused imports and functions.
teythoon 5fa6d4e
fixup! Appease warnings about unused imports and functions.
teythoon 2c3e2d4
fixup! Add support for ML-DSA.
teythoon 6b3b15c
Add support for SLH-DSA.
teythoon 25b5f99
fixup! Add support for SLH-DSA.
teythoon 816b788
fixup! Add support for SLH-DSA.
teythoon 28a442d
fixup! Add support for ML-KEM.
teythoon 37768f2
fixup! Add support for ML-DSA.
teythoon d85a999
fixup! Add support for ML-DSA.
teythoon 13f396e
fixup! Add support for ML-KEM.
teythoon 4ada1c1
fixup! Add support for SLH-DSA.
teythoon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
//! Module-Lattice-Based Digital Signatures. | ||
//! | ||
//! ML-DSA is a signature algorithm that is believed to be secure | ||
//! against adversaries with quantum computers. It has been | ||
//! standardized by NIST as [FIPS 204]. | ||
//! | ||
//! [FIPS 204]: https://csrc.nist.gov/pubs/fips/204/final | ||
|
||
use std::ffi::CStr; | ||
|
||
use foreign_types::ForeignType; | ||
use libc::c_int; | ||
use std::ptr; | ||
|
||
use crate::error::ErrorStack; | ||
use crate::ossl_param::{OsslParam, OsslParamBuilder}; | ||
use crate::pkey::{PKey, Private, Public}; | ||
use crate::pkey_ctx::PkeyCtx; | ||
use crate::{cvt, cvt_p}; | ||
use openssl_macros::corresponds; | ||
|
||
const OSSL_PKEY_PARAM_SEED: &[u8; 5] = b"seed\0"; | ||
const OSSL_PKEY_PARAM_PUB_KEY: &[u8; 4] = b"pub\0"; | ||
const OSSL_PKEY_PARAM_PRIV_KEY: &[u8; 5] = b"priv\0"; | ||
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] | ||
pub enum Variant { | ||
MlDsa44, | ||
MlDsa65, | ||
MlDsa87, | ||
} | ||
|
||
impl Variant { | ||
pub(crate) fn as_str(&self) -> &'static str { | ||
match self { | ||
Variant::MlDsa44 => "ML-DSA-44", | ||
Variant::MlDsa65 => "ML-DSA-65", | ||
Variant::MlDsa87 => "ML-DSA-87", | ||
} | ||
} | ||
|
||
pub(crate) fn as_cstr(&self) -> &'static CStr { | ||
match self { | ||
Variant::MlDsa44 => CStr::from_bytes_with_nul(b"ML-DSA-44\0"), | ||
Variant::MlDsa65 => CStr::from_bytes_with_nul(b"ML-DSA-65\0"), | ||
Variant::MlDsa87 => CStr::from_bytes_with_nul(b"ML-DSA-87\0"), | ||
}.unwrap() | ||
} | ||
} | ||
|
||
pub struct PKeyMlDsaBuilder<T> { | ||
bld: OsslParamBuilder, | ||
variant: Variant, | ||
_m: ::std::marker::PhantomData<T>, | ||
} | ||
|
||
impl<T> PKeyMlDsaBuilder<T> { | ||
/// Creates a new `PKeyMlDsaBuilder` to build ML-DSA private or | ||
/// public keys. | ||
pub fn new( | ||
variant: Variant, | ||
public: &[u8], | ||
private: Option<&[u8]>, | ||
) -> Result<PKeyMlDsaBuilder<T>, ErrorStack> { | ||
let bld = OsslParamBuilder::new()?; | ||
bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, public)?; | ||
if let Some(private) = private { | ||
bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)? | ||
}; | ||
Ok(PKeyMlDsaBuilder::<T> { | ||
bld, | ||
variant, | ||
_m: ::std::marker::PhantomData, | ||
}) | ||
} | ||
|
||
/// Creates a new `PKeyMlDsaBuilder` to build ML-DSA private keys | ||
/// from a seed. | ||
pub fn from_seed( | ||
variant: Variant, | ||
seed: &[u8], | ||
) -> Result<PKeyMlDsaBuilder<T>, ErrorStack> { | ||
let bld = OsslParamBuilder::new()?; | ||
bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; | ||
Ok(PKeyMlDsaBuilder::<T> { | ||
bld, | ||
variant, | ||
_m: ::std::marker::PhantomData, | ||
}) | ||
} | ||
|
||
/// Build PKey. Internal. | ||
#[corresponds(EVP_PKEY_fromdata)] | ||
fn build_internal(self, selection: c_int) | ||
-> Result<PKey<T>, ErrorStack> | ||
{ | ||
let mut ctx = PkeyCtx::new_from_name( | ||
None, self.variant.as_str(), None)?; | ||
ctx.fromdata_init()?; | ||
let params = self.bld.to_param()?; | ||
unsafe { | ||
let evp = cvt_p(ffi::EVP_PKEY_new())?; | ||
let pkey = PKey::from_ptr(evp); | ||
cvt(ffi::EVP_PKEY_fromdata( | ||
ctx.as_ptr(), | ||
&mut pkey.as_ptr(), | ||
selection, | ||
params.as_ptr(), | ||
))?; | ||
Ok(pkey) | ||
} | ||
} | ||
} | ||
|
||
impl PKeyMlDsaBuilder<Private> { | ||
/// Returns the Private ML-DSA PKey from the provided parameters. | ||
#[corresponds(EVP_PKEY_fromdata)] | ||
pub fn build(self) -> Result<PKey<Private>, ErrorStack> { | ||
self.build_internal(ffi::EVP_PKEY_KEYPAIR) | ||
} | ||
|
||
/// Creates a new `PKeyRsaBuilder` to generate a new ML-DSA key | ||
/// pair. | ||
pub fn new_generate(variant: Variant) | ||
-> Result<PKeyMlDsaBuilder<Private>, ErrorStack> | ||
{ | ||
let bld = OsslParamBuilder::new()?; | ||
Ok(PKeyMlDsaBuilder::<Private> { | ||
bld, | ||
variant, | ||
_m: ::std::marker::PhantomData, | ||
}) | ||
} | ||
|
||
/// Generate an ML-DSA PKey. | ||
pub fn generate(self) -> Result<PKey<Private>, ErrorStack> { | ||
let mut ctx = PkeyCtx::new_from_name( | ||
None, self.variant.as_str(), None)?; | ||
ctx.keygen_init()?; | ||
let params = self.bld.to_param()?; | ||
unsafe { | ||
cvt(ffi::EVP_PKEY_CTX_set_params(ctx.as_ptr(), params.as_ptr()))?; | ||
} | ||
ctx.generate() | ||
} | ||
} | ||
|
||
impl PKeyMlDsaBuilder<Public> { | ||
/// Returns the Public ML-DSA PKey from the provided parameters. | ||
#[corresponds(EVP_PKEY_fromdata)] | ||
pub fn build(self) -> Result<PKey<Public>, ErrorStack> { | ||
self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) | ||
} | ||
} | ||
|
||
pub struct PKeyMlDsaParams<T> { | ||
params: OsslParam, | ||
_m: ::std::marker::PhantomData<T>, | ||
} | ||
|
||
impl<T> PKeyMlDsaParams<T> { | ||
/// Creates a new `PKeyMlDsaParams` from existing ECDSA PKey. Internal. | ||
teythoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[corresponds(EVP_PKEY_todata)] | ||
fn _new_from_pkey<S>(pkey: &PKey<S>, selection: c_int) | ||
-> Result<PKeyMlDsaParams<T>, ErrorStack> | ||
{ | ||
unsafe { | ||
let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); | ||
cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; | ||
Ok(PKeyMlDsaParams::<T> { | ||
params: OsslParam::from_ptr(params), | ||
_m: ::std::marker::PhantomData, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
impl PKeyMlDsaParams<Public> { | ||
/// Creates a new `PKeyMlDsaParams` from existing Public ECDSA PKey. | ||
teythoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[corresponds(EVP_PKEY_todata)] | ||
pub fn from_pkey<S>(pkey: &PKey<S>) -> Result<PKeyMlDsaParams<Public>, ErrorStack> { | ||
Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) | ||
} | ||
|
||
/// Returns a reference to the public key. | ||
pub fn public_key(&self) -> Result<&[u8], ErrorStack> { | ||
self.params | ||
.locate(OSSL_PKEY_PARAM_PUB_KEY).unwrap() | ||
.get_octet_string() | ||
} | ||
} | ||
|
||
impl PKeyMlDsaParams<Private> { | ||
/// Creates a new `PKeyMlDsaParams` from existing Private ECDSA PKey. | ||
teythoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#[corresponds(EVP_PKEY_todata)] | ||
pub fn from_pkey(pkey: &PKey<Private>) -> Result<PKeyMlDsaParams<Private>, ErrorStack> { | ||
Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) | ||
} | ||
|
||
/// Returns the private key seed. | ||
pub fn private_key_seed(&self) -> Result<&[u8], ErrorStack> { | ||
self.params.locate(OSSL_PKEY_PARAM_SEED)?.get_octet_string() | ||
} | ||
|
||
/// Returns the private key. | ||
pub fn private_key(&self) -> Result<&[u8], ErrorStack> { | ||
self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_octet_string() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
|
||
use super::*; | ||
use crate::signature::Signature; | ||
|
||
#[test] | ||
fn test_generate_ml_dsa_44() { | ||
test_generate(Variant::MlDsa44); | ||
} | ||
|
||
#[test] | ||
fn test_generate_ml_dsa_65() { | ||
test_generate(Variant::MlDsa65); | ||
} | ||
|
||
#[test] | ||
fn test_generate_ml_dsa_87() { | ||
test_generate(Variant::MlDsa87); | ||
} | ||
|
||
fn test_generate(variant: Variant) { | ||
let bld = PKeyMlDsaBuilder::<Private>::new_generate(variant).unwrap(); | ||
let key = bld.generate().unwrap(); | ||
|
||
let mut algo = Signature::for_ml_dsa(variant).unwrap(); | ||
|
||
let data = b"Some Crypto Text"; | ||
let bad_data = b"Some Crypto text"; | ||
|
||
let mut signature = vec![]; | ||
let mut ctx = PkeyCtx::new(&key).unwrap(); | ||
ctx.sign_message_init(&mut algo).unwrap(); | ||
ctx.sign_to_vec(&data[..], &mut signature).unwrap(); | ||
|
||
// Verify good version with the original PKEY. | ||
ctx.verify_message_init(&mut algo).unwrap(); | ||
let valid = ctx.verify(&data[..], &signature); | ||
assert!(matches!(valid, Ok(true))); | ||
assert!(ErrorStack::get().errors().is_empty()); | ||
|
||
// Verify bad version with the original PKEY. | ||
ctx.verify_message_init(&mut algo).unwrap(); | ||
let valid = ctx.verify(&bad_data[..], &signature); | ||
assert!(matches!(valid, Ok(false) | Err(_))); | ||
assert!(ErrorStack::get().errors().is_empty()); | ||
|
||
// Derive a new PKEY with only the public bits. | ||
let public_params = | ||
PKeyMlDsaParams::<Public>::from_pkey(&key).unwrap(); | ||
let key_pub = PKeyMlDsaBuilder::<Public>::new( | ||
variant, public_params.public_key().unwrap(), None).unwrap() | ||
.build().unwrap(); | ||
let mut ctx = PkeyCtx::new(&key_pub).unwrap(); | ||
let mut algo = Signature::for_ml_dsa(variant).unwrap(); | ||
|
||
// Verify good version with the public PKEY. | ||
ctx.verify_message_init(&mut algo).unwrap(); | ||
let valid = ctx.verify(&data[..], &signature); | ||
assert!(matches!(valid, Ok(true))); | ||
assert!(ErrorStack::get().errors().is_empty()); | ||
|
||
// Verify bad version with the public PKEY. | ||
ctx.verify_message_init(&mut algo).unwrap(); | ||
let valid = ctx.verify(&bad_data[..], &signature); | ||
assert!(matches!(valid, Ok(false) | Err(_))); | ||
assert!(ErrorStack::get().errors().is_empty()); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.