diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 09:26:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-19 09:26:03 +0000 |
commit | 9918693037dce8aa4bb6f08741b6812923486c18 (patch) | |
tree | 21d2b40bec7e6a7ea664acee056eb3d08e15a1cf /vendor/orion/src/hazardous/kem | |
parent | Releasing progress-linux version 1.75.0+dfsg1-5~progress7.99u1. (diff) | |
download | rustc-9918693037dce8aa4bb6f08741b6812923486c18.tar.xz rustc-9918693037dce8aa4bb6f08741b6812923486c18.zip |
Merging upstream version 1.76.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/orion/src/hazardous/kem')
-rw-r--r-- | vendor/orion/src/hazardous/kem/mod.rs | 25 | ||||
-rw-r--r-- | vendor/orion/src/hazardous/kem/x25519_hkdf_sha256.rs | 289 |
2 files changed, 314 insertions, 0 deletions
diff --git a/vendor/orion/src/hazardous/kem/mod.rs b/vendor/orion/src/hazardous/kem/mod.rs new file mode 100644 index 000000000..767b65337 --- /dev/null +++ b/vendor/orion/src/hazardous/kem/mod.rs @@ -0,0 +1,25 @@ +// MIT License + +// Copyright (c) 2023 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#[cfg(feature = "safe_api")] +/// DHKEM(X25519, HKDF-SHA256) as specified in HPKE [RFC 9180](https://www.rfc-editor.org/rfc/rfc9180.html). +pub mod x25519_hkdf_sha256; diff --git a/vendor/orion/src/hazardous/kem/x25519_hkdf_sha256.rs b/vendor/orion/src/hazardous/kem/x25519_hkdf_sha256.rs new file mode 100644 index 000000000..0ea9609f2 --- /dev/null +++ b/vendor/orion/src/hazardous/kem/x25519_hkdf_sha256.rs @@ -0,0 +1,289 @@ +// MIT License + +// Copyright (c) 2023 The orion Developers + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! # Parameters: +//! - `public_recipient`: The public X25519 key of the recipient. +//! - `public_ephemeral`: The ephemeral X25519 key fro this KEM operation. +//! - `secret_recipient`: The private X25519 of the recipient. +//! - `secret_sender`: The private X25519 of the sender. +//! +//! # Errors: +//! An error will be returned if: +//! - If a shared X25519 secret is all-zero. +//! - If `ikm.len() < 32` when calling [`derive_keypair()`]. +//! +//! # Panics: +//! A panic will occur if: +//! - [`generate()`] panics during [`encap()`], [`auth_encap()`], [`decap()`] or [`auth_decap()`]. +//! +//! # Security: +//! - The `ikm` used as input for [`derive_keypair()`] must never be reused. +//! - This KEM is vulnerable to key-compromise impersonation attacks (KCI), meaning +//! that if the recipients private key `secret_recipient` is leaked at any point, sender authentication +//! no longer holds. See [KCI section](https://www.rfc-editor.org/rfc/rfc9180.html#section-9.1.1) of the RFC +//! on recommendations on how to mitigate this. +//! - Please refer to the RFC for a detailed description of all security properties provided: <https://www.rfc-editor.org/rfc/rfc9180.html#section-9>. +//! +//! # Example: +//! ```rust +//! # #[cfg(feature = "safe_api")] { +//! use orion::hazardous::kem::x25519_hkdf_sha256::DhKem; +//! +//! let (sender_secret, sender_public) = DhKem::generate_keypair()?; +//! let (recipient_secret, recipient_public) = DhKem::generate_keypair()?; +//! +//! let (sender_shared_secret, public_eph) = +//! DhKem::auth_encap(&recipient_public, &sender_secret)?; +//! let recipient_shared_secret = DhKem::auth_decap(&public_eph, &recipient_secret, &sender_public)?; +//! +//! assert_eq!(sender_shared_secret, recipient_shared_secret); +//! # } +//! # Ok::<(), orion::errors::UnknownCryptoError>(()) +//! ``` +//! [`encap()`]: x25519_hkdf_sha256::DhKem::encap +//! [`decap()`]: x25519_hkdf_sha256::DhKem::decap +//! [`auth_encap()`]: x25519_hkdf_sha256::DhKem::auth_encap +//! [`auth_decap()`]: x25519_hkdf_sha256::DhKem::auth_decap +//! [`derive_keypair()`]: x25519_hkdf_sha256::DhKem::derive_keypair +//! [`generate()`]: crate::hazardous::ecc::x25519::PrivateKey::generate + +#![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))] + +use crate::errors::UnknownCryptoError; +use crate::hazardous::ecc::x25519; +use crate::hazardous::kdf::hkdf::sha256; +use zeroize::Zeroize; + +pub use crate::hazardous::ecc::x25519::PrivateKey; +pub use crate::hazardous::ecc::x25519::PublicKey; + +construct_secret_key! { + /// A type to represent the `SharedSecret` that DH-KEM(X25519, HKDF-SHA256) produces. + /// + /// This type simply holds bytes. Creating an instance from slices or similar, + /// performs no checks whatsoever. + /// + /// # Errors: + /// An error will be returned if: + /// - `slice` is not 32 bytes. + (SharedSecret, test_shared_key, 32, 32) +} + +/// DHKEM(X25519, HKDF-SHA256) as specified in HPKE [RFC 9180](https://www.rfc-editor.org/rfc/rfc9180.html). +pub struct DhKem {} + +impl DhKem { + /// ID for this DH-KEM. See <https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1> + pub const KEM_ID: u16 = 0x0020; + + /// Version of HPKE implemented. See <https://www.rfc-editor.org/rfc/rfc9180.html#section-4-10>. + pub const HPKE_VERSION_ID: &'static str = "HPKE-v1"; + + /// Length of bytes of a shared secret produced by this KEM. See <https://www.rfc-editor.org/rfc/rfc9180.html#section-4-1>. + const N_SECRET: u16 = 32; + + fn labeled_extract( + salt: &[u8], + label: &[u8; 7], + ikm: &[u8], + ) -> Result<sha256::Tag, UnknownCryptoError> { + let mut labeled_ikm = Vec::<u8>::new(); + labeled_ikm.extend_from_slice(Self::HPKE_VERSION_ID.as_bytes()); + labeled_ikm.extend_from_slice(b"KEM"); + labeled_ikm.extend_from_slice(&Self::KEM_ID.to_be_bytes()); + labeled_ikm.extend_from_slice(label); + labeled_ikm.extend_from_slice(ikm); + + sha256::extract(salt, &labeled_ikm) + } + + fn labeled_expand<const L: u16>( + prk: &sha256::Tag, + label: &[u8], + info: &[u8], + ) -> Result<Vec<u8>, UnknownCryptoError> { + let mut labeled_info = Vec::<u8>::new(); + labeled_info.extend_from_slice(&L.to_be_bytes()); + labeled_info.extend_from_slice(Self::HPKE_VERSION_ID.as_bytes()); + labeled_info.extend_from_slice(b"KEM"); + labeled_info.extend_from_slice(&Self::KEM_ID.to_be_bytes()); + labeled_info.extend_from_slice(label); + labeled_info.extend_from_slice(info); + + let mut out = vec![0u8; L as usize]; + sha256::expand(prk, Some(&labeled_info), &mut out)?; + + Ok(out) + } + + fn extract_and_expand(dh: &[u8], kem_context: &[u8]) -> Result<Vec<u8>, UnknownCryptoError> { + let eae_prk = Self::labeled_extract(b"", b"eae_prk", dh)?; + let shared_secret = + Self::labeled_expand::<{ Self::N_SECRET }>(&eae_prk, b"shared_secret", kem_context)?; + + Ok(shared_secret) + } + + /// Generate random X25519 keypair. + pub fn generate_keypair() -> Result<(PrivateKey, PublicKey), UnknownCryptoError> { + let sk = PrivateKey::generate(); + let pk = PublicKey::try_from(&sk)?; + + Ok((sk, pk)) + } + + /// Deterministically derive a X25519 keyapir from `ikm`. + pub fn derive_keypair(ikm: &[u8]) -> Result<(PrivateKey, PublicKey), UnknownCryptoError> { + if ikm.len() < 32 { + return Err(UnknownCryptoError); + } + + let dkp_prk = Self::labeled_extract(b"", b"dkp_prk", ikm)?; + let sk = PrivateKey::from_slice(&Self::labeled_expand::<32>(&dkp_prk, b"sk", b"")?)?; + let pk = PublicKey::try_from(&sk)?; + + Ok((sk, pk)) + } + + /// Derive ephemeral shared secret and encapsulation thereof, which can be + /// decapsulated by the holder of `public_recipient`. + pub fn encap( + public_recipient: &PublicKey, + ) -> Result<(SharedSecret, PublicKey), UnknownCryptoError> { + let secret_ephemeral = PrivateKey::generate(); + let public_ephemeral = PublicKey::try_from(&secret_ephemeral)?; + + let dh = x25519::key_agreement(&secret_ephemeral, public_recipient)?; + let mut kem_context = [0u8; 32 + 32]; + kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); + kem_context[32..64].copy_from_slice(&public_recipient.to_bytes()); + + let shared_secret = Self::extract_and_expand(dh.unprotected_as_bytes(), &kem_context)?; + + Ok((SharedSecret::from_slice(&shared_secret)?, public_ephemeral)) + } + + /// Decapsulate `public_ephemeral` and return the shared ephemeral secrety, + /// using `secret_recipient` private key. + pub fn decap( + public_ephemeral: &PublicKey, + secret_recipient: &PrivateKey, + ) -> Result<SharedSecret, UnknownCryptoError> { + let dh = x25519::key_agreement(secret_recipient, public_ephemeral)?; + + let mut kem_context = [0u8; 32 + 32]; + kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); + kem_context[32..64].copy_from_slice(&PublicKey::try_from(secret_recipient)?.to_bytes()); + + let shared_secret = Self::extract_and_expand(dh.unprotected_as_bytes(), &kem_context)?; + + SharedSecret::from_slice(&shared_secret) + } + + /// Equivalent to [`Self::encap()`], additionally ensuring the holder of `secret_sender` was + /// the one to generate the shared secret. + pub fn auth_encap( + public_recipient: &PublicKey, + secret_sender: &PrivateKey, + ) -> Result<(SharedSecret, PublicKey), UnknownCryptoError> { + let secret_ehemeral = PrivateKey::generate(); + let public_ephemeral = PublicKey::try_from(&secret_ehemeral)?; + + let mut dh = [0u8; 64]; + dh[..32].copy_from_slice( + x25519::key_agreement(&secret_ehemeral, public_recipient)?.unprotected_as_bytes(), + ); + dh[32..64].copy_from_slice( + x25519::key_agreement(secret_sender, public_recipient)?.unprotected_as_bytes(), + ); + + let mut kem_context = [0u8; 32 * 3]; + kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); + kem_context[32..64].copy_from_slice(&public_recipient.to_bytes()); + kem_context[64..96].copy_from_slice(&PublicKey::try_from(secret_sender)?.to_bytes()); + + let shared_secret = Self::extract_and_expand(&dh, &kem_context)?; + dh.iter_mut().zeroize(); + + Ok((SharedSecret::from_slice(&shared_secret)?, public_ephemeral)) + } + + /// Equivalent to [`Self::decap()`], additionally ensuring the holder of `secret_sender` was + /// the one to generate the shared secret. + pub fn auth_decap( + public_ephemeral: &PublicKey, + secret_recipient: &PrivateKey, + public_sender: &PublicKey, + ) -> Result<SharedSecret, UnknownCryptoError> { + let mut dh = [0u8; 64]; + dh[..32].copy_from_slice( + x25519::key_agreement(secret_recipient, public_ephemeral)?.unprotected_as_bytes(), + ); + dh[32..64].copy_from_slice( + x25519::key_agreement(secret_recipient, public_sender)?.unprotected_as_bytes(), + ); + + let mut kem_context = [0u8; 32 * 3]; + kem_context[..32].copy_from_slice(&public_ephemeral.to_bytes()); + kem_context[32..64].copy_from_slice(&PublicKey::try_from(secret_recipient)?.to_bytes()); + kem_context[64..96].copy_from_slice(&public_sender.to_bytes()); + + let shared_secret = Self::extract_and_expand(&dh, &kem_context)?; + dh.iter_mut().zeroize(); + + SharedSecret::from_slice(&shared_secret) + } +} + +#[cfg(test)] +#[cfg(feature = "safe_api")] +mod public { + use crate::hazardous::ecc::x25519::{PrivateKey, PublicKey}; + use crate::hazardous::kem::x25519_hkdf_sha256::*; + + #[test] + fn encap_decap_roundtrip() { + let recipient_secret = PrivateKey::generate(); + let recipient_public = PublicKey::try_from(&recipient_secret).unwrap(); + + let (shared_secret_1, public_eph) = DhKem::encap(&recipient_public).unwrap(); + let shared_secret_2 = DhKem::decap(&public_eph, &recipient_secret).unwrap(); + + assert_eq!(shared_secret_1, shared_secret_2); + } + + #[test] + fn auth_encap_decap_roundtrip() { + let sender_secret = PrivateKey::generate(); + let sender_public = PublicKey::try_from(&sender_secret).unwrap(); + + let recipient_secret = PrivateKey::generate(); + let recipient_public = PublicKey::try_from(&recipient_secret).unwrap(); + + let (shared_secret_1, public_eph) = + DhKem::auth_encap(&recipient_public, &sender_secret).unwrap(); + let shared_secret_2 = + DhKem::auth_decap(&public_eph, &recipient_secret, &sender_public).unwrap(); + + assert_eq!(shared_secret_1, shared_secret_2); + } +} |