From a3c0e1737d921de757c957bc9d831d85d446c4a1 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Thu, 20 Jan 2022 15:42:30 +0100 Subject: openpgp: Add key derivation function. - Nettle, OpenSSL, Botan, and RustCrypto implement this natively, for CNG we use the RustCrypto implementation. --- Cargo.lock | 2 ++ openpgp/Cargo.toml | 7 +++++-- openpgp/src/crypto/backend/botan.rs | 5 ++++- openpgp/src/crypto/backend/botan/kdf.rs | 31 +++++++++++++++++++++++++++++++ openpgp/src/crypto/backend/cng.rs | 1 + openpgp/src/crypto/backend/cng/kdf.rs | 20 ++++++++++++++++++++ openpgp/src/crypto/backend/fuzzing.rs | 2 ++ openpgp/src/crypto/backend/fuzzing/kdf.rs | 18 ++++++++++++++++++ openpgp/src/crypto/backend/interface.rs | 23 ++++++++++++++++++++++- openpgp/src/crypto/backend/nettle.rs | 1 + openpgp/src/crypto/backend/nettle/kdf.rs | 25 +++++++++++++++++++++++++ openpgp/src/crypto/backend/openssl.rs | 1 + openpgp/src/crypto/backend/openssl/kdf.rs | 31 +++++++++++++++++++++++++++++++ openpgp/src/crypto/backend/rust.rs | 1 + openpgp/src/crypto/backend/rust/kdf.rs | 20 ++++++++++++++++++++ 15 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 openpgp/src/crypto/backend/botan/kdf.rs create mode 100644 openpgp/src/crypto/backend/cng/kdf.rs create mode 100644 openpgp/src/crypto/backend/fuzzing/kdf.rs create mode 100644 openpgp/src/crypto/backend/nettle/kdf.rs create mode 100644 openpgp/src/crypto/backend/openssl/kdf.rs create mode 100644 openpgp/src/crypto/backend/rust/kdf.rs diff --git a/Cargo.lock b/Cargo.lock index da83576e..63fb5e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2667,7 +2667,9 @@ dependencies = [ "ed25519", "ed25519-dalek", "flate2", + "generic-array", "getrandom", + "hkdf", "idea", "idna 0.5.0", "lalrpop", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index dc5547de..f4ede36c 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -81,6 +81,8 @@ ecdsa = { version = "0.16", optional = true, features = ["hazmat", "arithmetic"] # std::error::Error. ed25519 = { version = "2", default-features = false, features = ["std"], optional = true } ed25519-dalek = { version = "2", features = ["rand_core", "zeroize"], optional = true } +generic-array = { version = "0.14.4", optional = true } +hkdf = { version = "0.12", optional = true } idea = { version = "0.5", optional = true, features = ["zeroize"] } md-5 = { version = "0.10", features = ["oid"], optional = true } num-bigint-dig = { version = "0.8", default-features = false, optional = true } @@ -125,12 +127,13 @@ crypto-rust = [ "sha1collisiondetection/digest-trait", "sha1collisiondetection/oid", "dep:twofish", "dep:typenum", "dep:x25519-dalek", "dep:p256", "dep:rand", "rand?/getrandom", "dep:rand_core", "rand_core?/getrandom", - "dep:ecdsa", "dep:aes-gcm", "dep:dsa" + "dep:ecdsa", "dep:aes-gcm", "dep:dsa", "dep:hkdf", ] crypto-cng = [ "dep:cipher", "dep:eax", "dep:winapi", "dep:win-crypto-ng", "dep:ed25519", "dep:ed25519-dalek", - "dep:num-bigint-dig", "dep:aes-gcm", "dep:rand_core" + "dep:num-bigint-dig", "dep:aes-gcm", "dep:rand_core", + "dep:hkdf", "dep:sha2", ] crypto-openssl = ["dep:openssl", "dep:openssl-sys"] crypto-botan = ["dep:botan", "botan?/botan3"] diff --git a/openpgp/src/crypto/backend/botan.rs b/openpgp/src/crypto/backend/botan.rs index 3f1ab906..568802de 100644 --- a/openpgp/src/crypto/backend/botan.rs +++ b/openpgp/src/crypto/backend/botan.rs @@ -1,11 +1,14 @@ //! Implementation of Sequoia crypto API using the Botan cryptographic library. -use crate::types::*; +use crate::{ + types::*, +}; pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; +pub mod kdf; pub mod symmetric; pub struct Backend(()); diff --git a/openpgp/src/crypto/backend/botan/kdf.rs b/openpgp/src/crypto/backend/botan/kdf.rs new file mode 100644 index 00000000..942165e2 --- /dev/null +++ b/openpgp/src/crypto/backend/botan/kdf.rs @@ -0,0 +1,31 @@ +use crate::{ + Result, + crypto::{ + SessionKey, + backend::interface::Kdf, + }, +}; + +impl Kdf for super::Backend { + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) + -> Result<()> + { + assert!(okm.len() <= 255 * 32); + + const NO_SALT: [u8; 32] = [0; 32]; + let salt = salt.unwrap_or(&NO_SALT); + + // XXX: It'd be nice to write that directly to `okm`, but botan-rs + // does not have such an interface. + let okm_heap: SessionKey = + botan::kdf("HKDF(SHA-256)", okm.len(), &*ikm, salt, info)? + .into(); + + // XXX: Now copy the secret. + let l = okm.len().min(okm_heap.len()); + okm[..l].copy_from_slice(&okm_heap[..l]); + + Ok(()) + } +} diff --git a/openpgp/src/crypto/backend/cng.rs b/openpgp/src/crypto/backend/cng.rs index 9f43958f..557a34c3 100644 --- a/openpgp/src/crypto/backend/cng.rs +++ b/openpgp/src/crypto/backend/cng.rs @@ -8,6 +8,7 @@ pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; +pub mod kdf; pub mod symmetric; pub struct Backend(()); diff --git a/openpgp/src/crypto/backend/cng/kdf.rs b/openpgp/src/crypto/backend/cng/kdf.rs new file mode 100644 index 00000000..ac98a496 --- /dev/null +++ b/openpgp/src/crypto/backend/cng/kdf.rs @@ -0,0 +1,20 @@ +use hkdf::Hkdf; +use sha2::Sha256; + +use crate::{ + Result, + crypto::{ + SessionKey, + backend::interface::Kdf, + }, +}; + +impl Kdf for super::Backend { + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) + -> Result<()> + { + Ok(Hkdf::::new(salt, &ikm).expand(info, okm) + .map_err(|e| crate::Error::InvalidOperation(e.to_string()))?) + } +} diff --git a/openpgp/src/crypto/backend/fuzzing.rs b/openpgp/src/crypto/backend/fuzzing.rs index e36ee34e..a10e5002 100644 --- a/openpgp/src/crypto/backend/fuzzing.rs +++ b/openpgp/src/crypto/backend/fuzzing.rs @@ -12,6 +12,8 @@ pub mod ecdh; #[allow(unused_variables)] pub mod hash; #[allow(unused_variables)] +pub mod kdf; +#[allow(unused_variables)] pub mod symmetric; pub struct Backend(()); diff --git a/openpgp/src/crypto/backend/fuzzing/kdf.rs b/openpgp/src/crypto/backend/fuzzing/kdf.rs new file mode 100644 index 00000000..f29f2b25 --- /dev/null +++ b/openpgp/src/crypto/backend/fuzzing/kdf.rs @@ -0,0 +1,18 @@ +use crate::{ + Result, + crypto::{ + SessionKey, + backend::interface::Kdf, + }, +}; + +impl Kdf for super::Backend { + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) + -> Result<()> + { + assert!(okm.len() <= 255 * 32); + okm.iter_mut().for_each(|p| *p = 4); + Ok(()) + } +} diff --git a/openpgp/src/crypto/backend/interface.rs b/openpgp/src/crypto/backend/interface.rs index 864b2714..05e56a8c 100644 --- a/openpgp/src/crypto/backend/interface.rs +++ b/openpgp/src/crypto/backend/interface.rs @@ -4,6 +4,7 @@ use crate::{ Error, Result, crypto::{ + SessionKey, mem::Protected, mpi::{MPI, ProtectedMPI}, }, @@ -11,7 +12,7 @@ use crate::{ }; /// Abstracts over the cryptographic backends. -pub trait Backend: Asymmetric { +pub trait Backend: Asymmetric + Kdf { /// Returns a short, human-readable description of the backend. /// /// This starts with the name of the backend, possibly a version, @@ -121,3 +122,23 @@ mod tests { assert_ne!(secret.as_ref(), public); } } + +/// Key-Derivation-Functions. +pub trait Kdf { + /// HKDF instantiated with SHA256. + /// + /// Used to derive message keys from session keys, and key + /// encapsulating keys from S2K mechanisms. In both cases, using + /// a KDF that includes algorithm information in the given `info` + /// provides key space separation between cipher algorithms and + /// modes. + /// + /// `salt`, if given, SHOULD be 32 bytes of salt matching the + /// digest size of the hash function. If it is not give, 32 zeros + /// are used instead. + /// + /// `okm` must not be larger than 255 * 32 (the size of the hash + /// digest). + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) -> Result<()>; +} diff --git a/openpgp/src/crypto/backend/nettle.rs b/openpgp/src/crypto/backend/nettle.rs index c0cb767f..04fef2eb 100644 --- a/openpgp/src/crypto/backend/nettle.rs +++ b/openpgp/src/crypto/backend/nettle.rs @@ -8,6 +8,7 @@ pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; +pub mod kdf; pub mod symmetric; pub struct Backend(()); diff --git a/openpgp/src/crypto/backend/nettle/kdf.rs b/openpgp/src/crypto/backend/nettle/kdf.rs new file mode 100644 index 00000000..a016a3a3 --- /dev/null +++ b/openpgp/src/crypto/backend/nettle/kdf.rs @@ -0,0 +1,25 @@ +use nettle::{ + kdf::hkdf, + hash::Sha256, +}; + +use crate::{ + Result, + crypto::{ + SessionKey, + backend::interface::Kdf, + }, +}; + +impl Kdf for super::Backend { + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) + -> Result<()> + { + assert!(okm.len() <= 255 * 32); + const NO_SALT: [u8; 32] = [0; 32]; + let salt = salt.unwrap_or(&NO_SALT); + hkdf::(&ikm[..], salt, info, okm); + Ok(()) + } +} diff --git a/openpgp/src/crypto/backend/openssl.rs b/openpgp/src/crypto/backend/openssl.rs index aabf94cf..2bea302b 100644 --- a/openpgp/src/crypto/backend/openssl.rs +++ b/openpgp/src/crypto/backend/openssl.rs @@ -6,6 +6,7 @@ pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; +pub mod kdf; pub mod symmetric; pub struct Backend(()); diff --git a/openpgp/src/crypto/backend/openssl/kdf.rs b/openpgp/src/crypto/backend/openssl/kdf.rs new file mode 100644 index 00000000..11c48667 --- /dev/null +++ b/openpgp/src/crypto/backend/openssl/kdf.rs @@ -0,0 +1,31 @@ +use openssl::{ + md::Md, + pkey::Id, + pkey_ctx::PkeyCtx, +}; + +use crate::{ + Result, + crypto::{ + SessionKey, + backend::interface::Kdf, + }, +}; + +impl Kdf for super::Backend { + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) + -> Result<()> + { + let mut pkey = PkeyCtx::new_id(Id::HKDF)?; + pkey.derive_init()?; + pkey.set_hkdf_md(Md::sha256())?; + pkey.set_hkdf_key(&ikm)?; + if let Some(salt) = salt { + pkey.set_hkdf_salt(salt)?; + } + pkey.add_hkdf_info(info)?; + pkey.derive(Some(okm))?; + Ok(()) + } +} diff --git a/openpgp/src/crypto/backend/rust.rs b/openpgp/src/crypto/backend/rust.rs index e373dba3..27370e82 100644 --- a/openpgp/src/crypto/backend/rust.rs +++ b/openpgp/src/crypto/backend/rust.rs @@ -10,6 +10,7 @@ pub mod aead; pub mod asymmetric; pub mod ecdh; pub mod hash; +pub mod kdf; pub mod symmetric; pub struct Backend(()); diff --git a/openpgp/src/crypto/backend/rust/kdf.rs b/openpgp/src/crypto/backend/rust/kdf.rs new file mode 100644 index 00000000..ac98a496 --- /dev/null +++ b/openpgp/src/crypto/backend/rust/kdf.rs @@ -0,0 +1,20 @@ +use hkdf::Hkdf; +use sha2::Sha256; + +use crate::{ + Result, + crypto::{ + SessionKey, + backend::interface::Kdf, + }, +}; + +impl Kdf for super::Backend { + fn hkdf_sha256(ikm: &SessionKey, salt: Option<&[u8]>, info: &[u8], + okm: &mut SessionKey) + -> Result<()> + { + Ok(Hkdf::::new(salt, &ikm).expand(info, okm) + .map_err(|e| crate::Error::InvalidOperation(e.to_string()))?) + } +} -- cgit v1.2.3