diff --git a/Cargo.lock b/Cargo.lock index c7da7a40..85a4fb0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,9 +128,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "aws-lc-rs" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f409eb70b561706bf8abba8ca9c112729c481595893fd06a2dd9af8ed8441148" +checksum = "1ea835662a0af02443aa1396d39be523bbf8f11ee6fad20329607c480bea48c3" dependencies = [ "aws-lc-sys", "paste", @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923ded50f602b3007e5e63e3f094c479d9c8a9b42d7f4034e4afe456aa48bfd2" +checksum = "71b2ddd3ada61a305e1d8bb6c005d1eaa7d14d903681edfc400406d523a9b491" dependencies = [ "bindgen", "cc", @@ -341,6 +341,15 @@ dependencies = [ "cc", ] +[[package]] +name = "concat-kdf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d72c1252426a83be2092dd5884a5f6e3b8e7180f6891b6263d2c21b92ec8816" +dependencies = [ + "digest", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -536,17 +545,30 @@ dependencies = [ name = "dpapi" version = "0.1.0" dependencies = [ + "aes-gcm", + "aes-kw", "bitflags 2.8.0", "byteorder", + "concat-kdf", + "elliptic-curve", + "hmac", "num-bigint-dig", "num-derive", "num-traits", + "p256", + "p384", + "p521", "paste", "picky-asn1", "picky-asn1-der", "picky-asn1-x509", + "rand", "regex", + "rust-kbkdf", + "sha1", + "sha2", "thiserror 2.0.11", + "typenum", "uuid", ] @@ -1684,9 +1706,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "picky" -version = "7.0.0-rc.11" +version = "7.0.0-rc.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f62f11977ee3ab76e48f7465f035a607e61b7421b154384b71607cb85a26d5dd" +checksum = "0b11f32016338b5bdb81179ec286a7d466041c27c5d45810da090c08ae451925" dependencies = [ "aes", "aes-gcm", @@ -1724,9 +1746,9 @@ dependencies = [ [[package]] name = "picky-asn1" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d061c9f67e256511d8d69b86730a506bed100db520c8812e789cf91d9c6a16cc" +checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" dependencies = [ "oid", "serde", @@ -1737,9 +1759,9 @@ dependencies = [ [[package]] name = "picky-asn1-der" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e15b90fb132c46ded79c39277afa93151691d9df6e7ff369c071890b36478392" +checksum = "9dccb53c26f70c082e008818f524bd45d057069517b047bd0c0ee062d6d7d7f2" dependencies = [ "picky-asn1", "serde", @@ -1748,9 +1770,9 @@ dependencies = [ [[package]] name = "picky-asn1-x509" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dc93bbf01e4b1c9f426c3fb315bc31dc431a32c5ba7c7cf242a967869e9c0cb" +checksum = "511c46b93e7f08571a375882879d3a468dfe8793d73249907b2e3332950cb33e" dependencies = [ "base64", "num-bigint-dig", @@ -2178,6 +2200,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-kbkdf" +version = "1.1.0" +source = "git+https://gitlab.com/TheBestTvarynka/rust-kbkdf.git?branch=fix-key-generation#d1c113f1446be6415569e339cfcf8a0f4873ce32" +dependencies = [ + "generic-array", + "typenum", + "zeroize", +] + [[package]] name = "rustc-demangle" version = "0.1.24" diff --git a/Cargo.toml b/Cargo.toml index 39848b04..4aff314e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ rand = "0.8" cfg-if = "1" time = { version = "0.3", default-features = false } sha1 = { version = "0.10", default-features = false } +sha2 = "0.10" num-derive = "0.4" num-traits = { version = "0.2", default-features = false } picky = "7.0.0-rc.11" @@ -53,6 +54,7 @@ proptest = "1.6" serde = "1" byteorder = "1.5" num-bigint-dig = "0.8" +hmac = "0.12" [features] default = ["aws-lc-rs"] @@ -83,6 +85,7 @@ cfg-if.workspace = true time = { workspace = true, features = ["std"] } picky.workspace = true sha1.workspace = true +sha2.workspace = true num-derive.workspace = true num-traits = { workspace = true, default-features = true } picky-asn1-der.workspace = true @@ -98,11 +101,10 @@ picky-krb.workspace = true picky-asn1 = { workspace = true, features = ["time_conversion"] } byteorder.workspace = true num-bigint-dig.workspace = true +hmac.workspace = true md-5 = "0.10" md4 = "0.10" -sha2 = "0.10" -hmac = "0.12" crypto-mac = "0.11" lazy_static = "1.5" serde_derive = "1" diff --git a/crates/dpapi/Cargo.toml b/crates/dpapi/Cargo.toml index de748afe..44fb1681 100644 --- a/crates/dpapi/Cargo.toml +++ b/crates/dpapi/Cargo.toml @@ -22,6 +22,20 @@ picky-asn1.workspace = true picky-asn1-der.workspace = true picky-asn1-x509 = { workspace = true, features = ["pkcs7"] } num-bigint-dig.workspace = true +sha1.workspace = true +sha2.workspace = true +rand.workspace = true +hmac.workspace = true + +rust-kbkdf = { version = "1.1", git = "https://gitlab.com/TheBestTvarynka/rust-kbkdf.git", branch = "fix-key-generation" } +elliptic-curve = { version = "0.13", features = ["sec1", "std"] } +p521 = { version = "0.13", features = ["ecdh"] } +p256 = { version = "0.13", features = ["ecdh"] } +p384 = { version = "0.13", features = ["ecdh"] } +concat-kdf = { version = "0.1", features = ["std"] } +typenum = "1.17" +aes-kw = { version = "0.2", features = ["std"] } +aes-gcm = { version = "0.10", features = ["std"] } thiserror = "2.0" regex = "1.11" diff --git a/crates/dpapi/src/blob.rs b/crates/dpapi/src/blob.rs index c3106aeb..94e567ec 100644 --- a/crates/dpapi/src/blob.rs +++ b/crates/dpapi/src/blob.rs @@ -60,11 +60,11 @@ pub struct KeyIdentifier { pub flags: u32, /// The L0 index of the key. - pub l0: u32, + pub l0: i32, /// The L1 index of the key. - pub l1: u32, + pub l1: i32, /// The L2 index of the key. - pub l2: u32, + pub l2: i32, /// A GUID that identifies a root key. pub root_key_identifier: Uuid, @@ -79,6 +79,10 @@ pub struct KeyIdentifier { impl KeyIdentifier { // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/192c061c-e740-4aa0-ab1d-6954fb3e58f7 const MAGIC: [u8; 4] = [0x4b, 0x44, 0x53, 0x4b]; + + pub fn is_public_key(&self) -> bool { + self.flags & 1 != 0 + } } impl Encode for KeyIdentifier { @@ -90,9 +94,9 @@ impl Encode for KeyIdentifier { write_buf(&KeyIdentifier::MAGIC, &mut writer)?; writer.write_u32::(self.flags)?; - writer.write_u32::(self.l0)?; - writer.write_u32::(self.l1)?; - writer.write_u32::(self.l2)?; + writer.write_i32::(self.l0)?; + writer.write_i32::(self.l1)?; + writer.write_i32::(self.l2)?; self.root_key_identifier.encode(&mut writer)?; @@ -125,9 +129,9 @@ impl Decode for KeyIdentifier { let flags = reader.read_u32::()?; - let l0 = reader.read_u32::()?; - let l1 = reader.read_u32::()?; - let l2 = reader.read_u32::()?; + let l0 = reader.read_i32::()?; + let l1 = reader.read_i32::()?; + let l2 = reader.read_i32::()?; let root_key_identifier = Uuid::decode(&mut reader)?; let key_info_len = reader.read_u32::()?; diff --git a/crates/dpapi/src/crypto/hmac_sha_prf.rs b/crates/dpapi/src/crypto/hmac_sha_prf.rs new file mode 100644 index 00000000..4fd9a047 --- /dev/null +++ b/crates/dpapi/src/crypto/hmac_sha_prf.rs @@ -0,0 +1,87 @@ +use hmac::{Hmac, Mac}; +use rust_kbkdf::{PseudoRandomFunction, PseudoRandomFunctionKey}; + +use super::{CryptoError, CryptoResult}; + +pub struct HmacShaPrfKey<'key>(&'key [u8]); + +impl<'key> HmacShaPrfKey<'key> { + pub fn new(key: &'key [u8]) -> Self { + Self(key) + } + + pub fn key(&self) -> &[u8] { + self.0 + } +} + +impl<'key> PseudoRandomFunctionKey for HmacShaPrfKey<'key> { + type KeyHandle = HmacShaPrfKey<'key>; + + fn key_handle(&self) -> &Self::KeyHandle { + self + } +} + +macro_rules! define_hmac_sha_prf { + ($name:ident, $sha:ty, $out_size:ty) => { + pub struct $name { + hmac: Option>, + } + + impl $name { + pub fn new() -> Self { + Self { hmac: None } + } + } + + impl<'a> PseudoRandomFunction<'a> for $name { + type KeyHandle = HmacShaPrfKey<'a>; + type PrfOutputSize = $out_size; + type Error = CryptoError; + + fn init( + &mut self, + key: &'a dyn PseudoRandomFunctionKey>, + ) -> CryptoResult<()> { + self.hmac = Some(Hmac::<$sha>::new_from_slice(key.key_handle().key()).map_err(|_| { + use hmac::digest::crypto_common::KeySizeUser; + + CryptoError::InvalidKeyLength { + expected: Hmac::<$sha>::key_size(), + actual: key.key_handle().key().len(), + } + })?); + + Ok(()) + } + + fn update(&mut self, msg: &[u8]) -> CryptoResult<()> { + if let Some(hmac) = self.hmac.as_mut() { + hmac.update(msg); + + Ok(()) + } else { + Err(CryptoError::Uninitialized("HMAC hasher")) + } + } + + fn finish(&mut self, out: &mut [u8]) -> CryptoResult { + if let Some(hmac) = self.hmac.as_mut() { + let hmac = hmac.clone().finalize().into_bytes(); + + out.copy_from_slice(hmac.as_slice()); + + Ok(hmac.as_slice().len()) + } else { + Err(CryptoError::Uninitialized("HMAC hasher")) + } + } + } + }; +} + +define_hmac_sha_prf!(HmacSha1Prf, sha1::Sha1, typenum::U20); +define_hmac_sha_prf!(HmacSha256Prf, sha2::Sha256, typenum::U32); +define_hmac_sha_prf!(HmacSha384Prf, sha2::Sha384, typenum::U48); +define_hmac_sha_prf!(HmacSha512Prf, sha2::Sha512, typenum::U64); diff --git a/crates/dpapi/src/crypto/mod.rs b/crates/dpapi/src/crypto/mod.rs new file mode 100644 index 00000000..5578a713 --- /dev/null +++ b/crates/dpapi/src/crypto/mod.rs @@ -0,0 +1,526 @@ +mod hmac_sha_prf; + +use aes_gcm::aead::{Aead, KeyInit, OsRng}; +use aes_gcm::{Aes256Gcm, Key}; +use aes_kw::KekAes256; +use num_bigint_dig::BigUint; +use picky_asn1_x509::enveloped_data::{ContentEncryptionAlgorithmIdentifier, KeyEncryptionAlgorithmIdentifier}; +use picky_asn1_x509::{oids, AesParameters, AlgorithmIdentifierParameters}; +use rand::Rng; +use thiserror::Error; +use uuid::Uuid; + +use self::hmac_sha_prf::{HmacSha1Prf, HmacSha256Prf, HmacSha384Prf, HmacSha512Prf, HmacShaPrfKey}; +use crate::gkdi::{EcdhKey, EllipticCurve, FfcdhKey, GroupKeyEnvelope, HashAlg}; +use crate::rpc::{Decode, EncodeExt}; +use crate::str::encode_utf16_le; +use crate::DpapiResult; + +#[derive(Debug, Error)] +pub enum CryptoError { + #[error("invalid {name} algorithm id: expected {expected} but got {actual}")] + InvalidAlgorithm { + name: &'static str, + expected: String, + actual: String, + }, + + #[error("invalid AES parameters: {reason}")] + InvalidAesParams { + reason: &'static str, + parameters: AlgorithmIdentifierParameters, + }, + + #[error("invalid elliptic curve point")] + InvalidEllipticCurvePoint, + + #[error("invalid or unsupported secret algorithm: {0}")] + InvalidSecretAlg(String), + + #[error("missing elliptic curve point {0} coordinate")] + MissingPointCoordinate(&'static str), + + #[error(transparent)] + EllipticCurve(#[from] elliptic_curve::Error), + + #[error(transparent)] + ConcatKdf(#[from] concat_kdf::Error), + + #[error(transparent)] + IntConversion(#[from] std::num::TryFromIntError), + + #[error(transparent)] + AesGcm(#[from] aes_gcm::Error), + + #[error(transparent)] + AesKw(#[from] aes_kw::Error), + + #[error("{0} is uninitialized")] + Uninitialized(&'static str), + + #[error("invalid key length: expected {expected} bytes but got {actual}")] + InvalidKeyLength { expected: usize, actual: usize }, +} + +pub type CryptoResult = Result; + +// "KDS service\0" encoded in UTF16 le. +pub const KDS_SERVICE_LABEL: &[u8] = &[ + 75, 0, 68, 0, 83, 0, 32, 0, 115, 0, 101, 0, 114, 0, 118, 0, 105, 0, 99, 0, 101, 0, 0, 0, +]; + +pub fn cek_decrypt( + algorithm: &KeyEncryptionAlgorithmIdentifier, + kek: &[u8], + wrapped_key: &[u8], +) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_wrap() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-wrap", + expected: oids::aes256_wrap().into(), + actual: algorithm.oid().into(), + }); + } + + let kek = KekAes256::new(kek.into()); + + Ok(kek.unwrap_vec(wrapped_key)?) +} + +pub fn cek_encrypt(algorithm: &KeyEncryptionAlgorithmIdentifier, kek: &[u8], key: &[u8]) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_wrap() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-wrap", + expected: oids::aes256_wrap().into(), + actual: algorithm.oid().into(), + }); + } + + let kek = KekAes256::new(kek.into()); + + Ok(kek.wrap_vec(key)?) +} + +pub fn cek_generate(algorithm: &KeyEncryptionAlgorithmIdentifier) -> CryptoResult<(Vec, Vec)> { + if algorithm.oid() != &oids::aes256_wrap() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-wrap", + expected: oids::aes256_wrap().into(), + actual: algorithm.oid().into(), + }); + } + + let mut rng = OsRng; + let cek = Aes256Gcm::generate_key(&mut rng); + let iv = rng.gen::<[u8; 12]>(); + + Ok((cek.to_vec(), iv.to_vec())) +} + +fn extract_iv(parameters: &AlgorithmIdentifierParameters) -> CryptoResult<&[u8]> { + if let AlgorithmIdentifierParameters::Aes(aes_parameters) = parameters { + if let AesParameters::InitializationVector(iv) = aes_parameters { + Ok(iv.0.as_slice()) + } else { + Err(CryptoError::InvalidAesParams { + reason: "expected AES initialization vector", + parameters: parameters.clone(), + }) + } + } else { + Err(CryptoError::InvalidAesParams { + reason: "provided ones are not AES parameters", + parameters: parameters.clone(), + }) + } +} + +pub fn content_decrypt( + algorithm: &ContentEncryptionAlgorithmIdentifier, + cek: &[u8], + data: &[u8], +) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_gcm() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-gcm", + expected: oids::aes256_gcm().into(), + actual: algorithm.oid().into(), + }); + } + + let iv = extract_iv(algorithm.parameters())?; + + let cipher = Aes256Gcm::new(Key::::from_slice(cek)); + Ok(cipher.decrypt(iv.into(), data)?) +} + +pub fn content_encrypt( + algorithm: &ContentEncryptionAlgorithmIdentifier, + cek: &[u8], + plaintext: &[u8], +) -> CryptoResult> { + if algorithm.oid() != &oids::aes256_gcm() { + return Err(CryptoError::InvalidAlgorithm { + name: "aes256-gcm", + expected: oids::aes256_gcm().into(), + actual: algorithm.oid().into(), + }); + } + + let iv = extract_iv(algorithm.parameters())?; + + let cipher = Aes256Gcm::new(Key::::from_slice(cek)); + Ok(cipher.encrypt(iv.into(), plaintext)?) +} + +pub fn kdf(algorithm: HashAlg, secret: &[u8], label: &[u8], context: &[u8], length: usize) -> CryptoResult> { + use rust_kbkdf::{kbkdf, CounterMode, InputType, KDFMode, SpecifiedInput}; + + let mut derived_key = vec![0; length]; + + macro_rules! kdf { + ($sha:ident) => {{ + let key = HmacShaPrfKey::new(secret); + let mut hmac = $sha::new(); + + // KDF(HashAlg, KI, Label, Context, L) + // where KDF is SP800-108 in counter mode. + kbkdf( + &KDFMode::CounterMode(CounterMode { counter_length: 32 }), + &InputType::SpecifiedInput(SpecifiedInput { label, context }), + &key, + &mut hmac, + &mut derived_key, + )?; + }}; + } + + match algorithm { + HashAlg::Sha1 => kdf!(HmacSha1Prf), + HashAlg::Sha256 => kdf!(HmacSha256Prf), + HashAlg::Sha384 => kdf!(HmacSha384Prf), + HashAlg::Sha512 => kdf!(HmacSha512Prf), + } + + Ok(derived_key) +} + +fn kdf_concat( + algorithm: HashAlg, + shared_secret: &[u8], + algorithm_id: &[u8], + party_uinfo: &[u8], + party_vinfo: &[u8], +) -> CryptoResult> { + let mut other_info = algorithm_id.to_vec(); + other_info.extend_from_slice(party_uinfo); + other_info.extend_from_slice(party_vinfo); + + Ok(match algorithm { + HashAlg::Sha1 => concat_kdf::derive_key::(shared_secret, &other_info, 20)?, + HashAlg::Sha256 => concat_kdf::derive_key::(shared_secret, &other_info, 32)?, + HashAlg::Sha384 => concat_kdf::derive_key::(shared_secret, &other_info, 48)?, + HashAlg::Sha512 => concat_kdf::derive_key::(shared_secret, &other_info, 64)?, + }) +} + +fn compute_kdf_context(key_guid: Uuid, l0: i32, l1: i32, l2: i32) -> Vec { + let mut buf = vec![0; 28]; + + buf[0..16].copy_from_slice(&key_guid.to_bytes_le()); + buf[16..20].copy_from_slice(&l0.to_le_bytes()); + buf[20..24].copy_from_slice(&l1.to_le_bytes()); + buf[24..28].copy_from_slice(&l2.to_le_bytes()); + + buf +} + +pub fn compute_l1_key( + target_sd: &[u8], + root_key_id: Uuid, + l0: i32, + root_key: &[u8], + algorithm: HashAlg, +) -> CryptoResult> { + // Note: 512 is number of bits, we use byte length here + // Key(SD, RK, L0, -1, -1) = KDF( + // HashAlg, + // RK.msKds-RootKeyData, + // "KDS service", + // RKID || L0 || 0xffffffff || 0xffffffff, + // 512 + // ) + let l0_seed = kdf( + algorithm, + root_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(root_key_id, l0, -1, -1), + 64, + )?; + + // Key(SD, RK, L0, 31, -1) = KDF( + // HashAlg, + // Key(SD, RK, L0, -1, -1), + // "KDS service", + // RKID || L0 || 31 || 0xffffffff || SD, + // 512 + // ) + let mut kdf_context = compute_kdf_context(root_key_id, l0, 31, -1); + kdf_context.extend_from_slice(target_sd); + + kdf(algorithm, &l0_seed, KDS_SERVICE_LABEL, &kdf_context, 64) +} + +pub fn compute_l2_key( + algorithm: HashAlg, + request_l1: i32, + request_l2: i32, + rk: &GroupKeyEnvelope, +) -> CryptoResult> { + let mut l1 = rk.l1; + let mut l1_key = rk.l1_key.clone(); + let mut l2 = rk.l2; + let mut l2_key = rk.l2_key.clone(); + let mut reseed_l2 = l2 == 31 || rk.l1 != request_l1; + + // MS-GKDI 2.2.4 Group key Envelope + // If the value in the L2 index field is equal to 31, this contains the + // L1 key with group key identifier (L0 index, L1 index, -1). In all + // other cases, this field contains the L1 key with group key identifier + // (L0 index, L1 index - 1, -1). If this field is present, its length + // MUST be equal to 64 bytes. + if l2 != 31 && l1 != request_l1 { + l1 -= 1; + } + + while l1 != request_l1 { + reseed_l2 = true; + l1 -= 1; + + l1_key = kdf( + algorithm, + &l1_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(rk.root_key_identifier, rk.l0, l1, -1), + 64, + )?; + } + + if reseed_l2 { + l2 = 31; + l2_key = kdf( + algorithm, + &l1_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(rk.root_key_identifier, rk.l0, l1, l2), + 64, + )?; + } + + while l2 != request_l2 { + l2 -= 1; + + l2_key = kdf( + algorithm, + &l2_key, + KDS_SERVICE_LABEL, + &compute_kdf_context(rk.root_key_identifier, rk.l0, l1, l2), + 64, + )?; + } + + Ok(l2_key) +} + +pub fn compute_kek_from_public_key( + algorithm: HashAlg, + seed: &[u8], + secret_algorithm: &str, + public_key: &[u8], + private_key_length: usize, +) -> DpapiResult> { + let encoded_secret_algorithm = encode_utf16_le(secret_algorithm); + + let private_key = kdf( + algorithm, + seed, + KDS_SERVICE_LABEL, + &encoded_secret_algorithm, + private_key_length, + )?; + + compute_kek(algorithm, secret_algorithm, &private_key, public_key) +} + +pub fn compute_kek( + algorithm: HashAlg, + secret_algorithm: &str, + private_key: &[u8], + public_key: &[u8], +) -> DpapiResult> { + let (shared_secret, secret_hash_algorithm) = if secret_algorithm == "DH" { + let dh_pub_key = FfcdhKey::decode(public_key)?; + let shared_secret = dh_pub_key + .public_key + .modpow(&BigUint::from_bytes_be(private_key), &dh_pub_key.field_order); + let mut shared_secret = shared_secret.to_bytes_be(); + + while shared_secret.len() < dh_pub_key.key_length.try_into()? { + shared_secret.insert(0, 0); + } + + (shared_secret, HashAlg::Sha256) + } else if secret_algorithm.starts_with("ECDH_P") { + use elliptic_curve::scalar::ScalarPrimitive; + use elliptic_curve::sec1::FromEncodedPoint; + use elliptic_curve::{PublicKey, SecretKey}; + + let ecdh_pub_key_info = EcdhKey::decode(public_key)?; + + match ecdh_pub_key_info.curve { + EllipticCurve::P256 => { + let public_key: p256::PublicKey = Option::from(PublicKey::from_encoded_point( + &p256::EncodedPoint::from_affine_coordinates( + ecdh_pub_key_info.x.to_bytes_be().as_slice().into(), + ecdh_pub_key_info.y.to_bytes_be().as_slice().into(), + false, + ), + )) + .ok_or(CryptoError::InvalidEllipticCurvePoint)?; + let secret_key = SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let shared_secret: p256::ecdh::SharedSecret = + p256::ecdh::diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine()); + + (shared_secret.raw_secret_bytes().as_slice().to_vec(), HashAlg::Sha256) + } + EllipticCurve::P384 => { + let public_key: p384::PublicKey = Option::from(PublicKey::from_encoded_point( + &p384::EncodedPoint::from_affine_coordinates( + ecdh_pub_key_info.x.to_bytes_be().as_slice().into(), + ecdh_pub_key_info.y.to_bytes_be().as_slice().into(), + false, + ), + )) + .ok_or(CryptoError::InvalidEllipticCurvePoint)?; + let secret_key = SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let shared_secret: p384::ecdh::SharedSecret = + p384::ecdh::diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine()); + + (shared_secret.raw_secret_bytes().as_slice().to_vec(), HashAlg::Sha384) + } + EllipticCurve::P521 => { + let public_key: p521::PublicKey = Option::from(PublicKey::from_encoded_point( + &p521::EncodedPoint::from_affine_coordinates( + ecdh_pub_key_info.x.to_bytes_be().as_slice().into(), + ecdh_pub_key_info.y.to_bytes_be().as_slice().into(), + false, + ), + )) + .ok_or(CryptoError::InvalidEllipticCurvePoint)?; + let secret_key = SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let shared_secret: p521::ecdh::SharedSecret = + p384::ecdh::diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine()); + + (shared_secret.raw_secret_bytes().as_slice().to_vec(), HashAlg::Sha512) + } + } + } else { + Err(CryptoError::InvalidSecretAlg(secret_algorithm.to_owned()))? + }; + + // "KDS public key\0" encoded in UTF16 le. + let kek_context = &[ + 75, 0, 68, 0, 83, 0, 32, 0, 112, 0, 117, 0, 98, 0, 108, 0, 105, 0, 99, 0, 32, 0, 107, 0, 101, 0, 121, 0, 0, 0, + ]; + + // This part isn't documented but we use the key derivation algorithm + // SP 800-56A to derive the kek secret input value. On Windows this uses + // BCryptDeriveKey with the following parameters. + // KDF_ALGORITHMID - SHA512 + // KDF_PARTYUINFO - KDS public key + // KDF_PARTYVINFO - KDS service + // Each of these is just appended to the otherinfo value used in + // cryptography as the UTF-16-LE NULL terminated strings. + let secret = kdf_concat( + secret_hash_algorithm, + &shared_secret, + // "SHA512\0" encoded in UTF16 le. + &[83, 0, 72, 0, 65, 0, 53, 0, 49, 0, 50, 0, 0, 0], + kek_context, + KDS_SERVICE_LABEL, + )?; + + Ok(kdf(algorithm, &secret, KDS_SERVICE_LABEL, kek_context, 32)?) +} + +pub fn compute_public_key(secret_algorithm: &str, private_key: &[u8], peer_public_key: &[u8]) -> DpapiResult> { + if secret_algorithm == "DH" { + let FfcdhKey { + key_length, + field_order, + generator, + public_key: _, + } = FfcdhKey::decode(peer_public_key)?; + + let my_pub_key = generator.modpow(&BigUint::from_bytes_be(private_key), &field_order); + + FfcdhKey { + key_length, + field_order, + generator, + public_key: my_pub_key, + } + .encode_to_vec() + } else if secret_algorithm.starts_with("ECDH_P") { + use elliptic_curve::scalar::ScalarPrimitive; + use elliptic_curve::sec1::ToEncodedPoint; + + let ecdh_pub_key_info = EcdhKey::decode(peer_public_key)?; + + let (x, y) = match ecdh_pub_key_info.curve { + EllipticCurve::P256 => { + let secret_key = + p256::SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let public_key = secret_key.public_key(); + let point = public_key.to_encoded_point(false); + + ( + BigUint::from_bytes_be(point.x().ok_or(CryptoError::MissingPointCoordinate("x"))?), + BigUint::from_bytes_be(point.y().ok_or(CryptoError::MissingPointCoordinate("y"))?), + ) + } + EllipticCurve::P384 => { + let secret_key = + p384::SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let public_key = secret_key.public_key(); + let point = public_key.to_encoded_point(false); + + ( + BigUint::from_bytes_be(point.x().ok_or(CryptoError::MissingPointCoordinate("x"))?), + BigUint::from_bytes_be(point.y().ok_or(CryptoError::MissingPointCoordinate("y"))?), + ) + } + EllipticCurve::P521 => { + let secret_key = + p521::SecretKey::new(ScalarPrimitive::from_slice(private_key).map_err(CryptoError::from)?); + let public_key = secret_key.public_key(); + let point = public_key.to_encoded_point(false); + + ( + BigUint::from_bytes_be(point.x().ok_or(CryptoError::MissingPointCoordinate("x"))?), + BigUint::from_bytes_be(point.y().ok_or(CryptoError::MissingPointCoordinate("y"))?), + ) + } + }; + + EcdhKey { + curve: ecdh_pub_key_info.curve, + key_length: ecdh_pub_key_info.key_length, + x, + y, + } + .encode_to_vec() + } else { + Ok(Err(CryptoError::InvalidSecretAlg(secret_algorithm.to_owned()))?) + } +} diff --git a/crates/dpapi/src/error.rs b/crates/dpapi/src/error.rs index 72d6193c..958341db 100644 --- a/crates/dpapi/src/error.rs +++ b/crates/dpapi/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { #[error(transparent)] Sid(#[from] crate::sid::SidError), + #[error(transparent)] + Crypto(#[from] crate::crypto::CryptoError), + #[error("IO error")] Io(#[from] std::io::Error), diff --git a/crates/dpapi/src/gkdi.rs b/crates/dpapi/src/gkdi.rs index 6ed120e9..f79e4c33 100644 --- a/crates/dpapi/src/gkdi.rs +++ b/crates/dpapi/src/gkdi.rs @@ -3,9 +3,15 @@ use std::io::{Read, Write}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use num_bigint_dig::BigUint; +use rand::rngs::OsRng; +use rand::Rng; use thiserror::Error; use uuid::Uuid; +use crate::blob::KeyIdentifier; +use crate::crypto::{ + compute_kek, compute_kek_from_public_key, compute_l2_key, compute_public_key, kdf, KDS_SERVICE_LABEL, +}; use crate::rpc::{read_buf, read_c_str_utf16_le, read_padding, read_vec, write_buf, write_padding, Decode, Encode}; use crate::str::{encode_utf16_le, from_utf16_le}; use crate::{DpapiResult, Error}; @@ -24,8 +30,19 @@ pub enum GkdiError { #[error("invalid elliptic curve id")] InvalidEllipticCurveId(Vec), + + #[error("invalid kdf algorithm name: expected {expected} but got {actual}")] + InvalidKdfAlgName { expected: &'static str, actual: String }, + + #[error("current user is not authorized to retrieve the KEK information")] + IsNotAuthorized, + + #[error("l0 index does not match requested l0 index")] + InvalidL0Index, } +const KDF_ALGORITHM_NAME: &str = "SP800_108_CTR_HMAC"; + /// GetKey RPC Request /// /// This can be used to build the stub data for the GetKey RPC request. @@ -107,7 +124,7 @@ impl Decode for GetKey { /// /// It contains hash algorithms that are listed in the documentation: /// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/9946aeff-a914-45e9-b9e5-6cb5b4059187 -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HashAlg { Sha1, Sha256, @@ -470,15 +487,14 @@ pub struct GroupKeyEnvelope { /// by this structure might be used for encryption and decryption, otherwise it should only be used for decryption. /// This field is encoded using little-endian format. pub flags: u32, - /// A 32-bit unsigned integer. This field MUST be the L0 index of the key being enveloped. + /// This field MUST be the L0 index of the key being enveloped. This field is encoded using little-endian format. + pub l0: i32, + /// This field MUST be the L1 index of the key being enveloped, and therefore MUST be a number between 0 and 31, inclusive. + /// This field is encoded using little-endian format. + pub l1: i32, + /// This field MUST be the L2 index of the key being enveloped, and therefore MUST be a number between 0 and 31, inclusive. /// This field is encoded using little-endian format. - pub l0: u32, - /// A 32-bit unsigned integer. This field MUST be the L1 index of the key being enveloped, - /// and therefore MUST be a number between 0 and 31, inclusive. This field is encoded using little-endian format. - pub l1: u32, - /// A 32-bit unsigned integer. This field MUST be the L2 index of the key being enveloped, - /// and therefore MUST be a number between 0 and 31, inclusive. This field is encoded using little-endian format. - pub l2: u32, + pub l2: i32, /// A GUID containing the root key identifier of the key being enveloped. pub root_key_identifier: Uuid, /// This field MUST be the ADM element KDF algorithm name associated with the ADM element root key, @@ -514,6 +530,91 @@ impl GroupKeyEnvelope { // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/192c061c-e740-4aa0-ab1d-6954fb3e58f7 const MAGIC: &[u8] = &[0x4B, 0x44, 0x53, 0x4B]; const VERSION: u32 = 1; + + pub fn is_public_key(&self) -> bool { + self.flags & 1 != 0 + } + + pub fn new_kek(&self) -> DpapiResult<(Vec, KeyIdentifier)> { + if self.kdf_alg != KDF_ALGORITHM_NAME { + Err(GkdiError::InvalidKdfAlgName { + expected: KDF_ALGORITHM_NAME, + actual: self.kdf_alg.clone(), + })?; + } + + let kdf_parameters = KdfParameters::decode(self.kdf_parameters.as_slice())?; + let hash_alg = kdf_parameters.hash_alg; + + let mut rand = OsRng; + + let (kek, key_info) = if self.is_public_key() { + // the L2 key is the peer's public key + + let mut private_key = vec![self.private_key_length.div_ceil(8).try_into()?]; + rand.fill(private_key.as_mut_slice()); + + let kek = compute_kek(hash_alg, &self.secret_algorithm, &private_key, &self.l2_key)?; + let key_info = compute_public_key(&self.secret_algorithm, &private_key, &self.l2_key)?; + + (kek, key_info) + } else { + let key_info = rand.gen::<[u8; 32]>(); + let kek = kdf(hash_alg, &self.l2_key, KDS_SERVICE_LABEL, &key_info, 32)?; + + (kek, key_info.to_vec()) + }; + + Ok(( + kek, + KeyIdentifier { + version: 1, + flags: self.flags, + + l0: self.l0, + l1: self.l1, + l2: self.l2, + root_key_identifier: self.root_key_identifier, + + key_info, + domain_name: self.domain_name.clone(), + forest_name: self.forest_name.clone(), + }, + )) + } + + pub fn get_kek(&self, key_identifier: &KeyIdentifier) -> DpapiResult> { + if self.is_public_key() { + Err(GkdiError::IsNotAuthorized)?; + } + + if self.l0 != key_identifier.l0 { + Err(GkdiError::InvalidL0Index)?; + } + + if self.kdf_alg != KDF_ALGORITHM_NAME { + Err(GkdiError::InvalidKdfAlgName { + expected: KDF_ALGORITHM_NAME, + actual: self.kdf_alg.clone(), + })?; + } + + let kdf_parameters = KdfParameters::decode(self.kdf_parameters.as_slice())?; + let hash_alg = kdf_parameters.hash_alg; + let l2_key = compute_l2_key(hash_alg, key_identifier.l1, key_identifier.l2, self)?; + + if key_identifier.is_public_key() { + Ok(compute_kek_from_public_key( + hash_alg, + &l2_key, + &self.secret_algorithm, + &key_identifier.key_info, + self.private_key_length.div_ceil(8).try_into()?, + )?) + } else { + Ok(kdf(hash_alg, &l2_key, KDS_SERVICE_LABEL, &key_identifier.key_info, 32)?) + } + } } impl Encode for GroupKeyEnvelope { @@ -522,9 +623,9 @@ impl Encode for GroupKeyEnvelope { write_buf(GroupKeyEnvelope::MAGIC, &mut writer)?; writer.write_u32::(self.flags)?; - writer.write_u32::(self.l0)?; - writer.write_u32::(self.l1)?; - writer.write_u32::(self.l2)?; + writer.write_i32::(self.l0)?; + writer.write_i32::(self.l1)?; + writer.write_i32::(self.l2)?; self.root_key_identifier.encode(&mut writer)?; let encoded_kdf_alg = encode_utf16_le(&self.kdf_alg); @@ -580,9 +681,9 @@ impl Decode for GroupKeyEnvelope { } let flags = reader.read_u32::()?; - let l0 = reader.read_u32::()?; - let l1 = reader.read_u32::()?; - let l2 = reader.read_u32::()?; + let l0 = reader.read_i32::()?; + let l1 = reader.read_i32::()?; + let l2 = reader.read_i32::()?; let root_key_identifier = Uuid::decode(&mut reader)?; let kdf_alg_len = reader.read_u32::()?; diff --git a/crates/dpapi/src/lib.rs b/crates/dpapi/src/lib.rs index 692f2fec..c2e72fdc 100644 --- a/crates/dpapi/src/lib.rs +++ b/crates/dpapi/src/lib.rs @@ -2,6 +2,7 @@ #![allow(dead_code)] pub mod blob; +pub mod crypto; pub mod error; pub mod gkdi; pub mod rpc; diff --git a/crates/dpapi/tests/dpapi/crypto.rs b/crates/dpapi/tests/dpapi/crypto.rs new file mode 100644 index 00000000..a92aa314 --- /dev/null +++ b/crates/dpapi/tests/dpapi/crypto.rs @@ -0,0 +1,247 @@ +use dpapi::crypto::{ + cek_decrypt, cek_encrypt, compute_kek, compute_public_key, content_decrypt, content_encrypt, kdf, KDS_SERVICE_LABEL, +}; +use dpapi::gkdi::HashAlg; +use picky_asn1::wrapper::OctetStringAsn1; +use picky_asn1_x509::enveloped_data::{ContentEncryptionAlgorithmIdentifier, KeyEncryptionAlgorithmIdentifier}; +use picky_asn1_x509::{AesMode, AesParameters}; + +const SECRET_KEY: &[u8] = &[ + 213, 85, 238, 100, 120, 222, 109, 53, 48, 101, 43, 187, 152, 206, 110, 105, 123, 251, 227, 253, 232, 85, 197, 24, + 217, 190, 118, 74, 54, 226, 8, 188, 163, 141, 155, 170, 208, 164, 97, 125, 32, 172, 65, 183, 251, 135, 229, 224, + 214, 22, 98, 18, 170, 254, 220, 105, 217, 11, 142, 135, 141, 104, 82, 189, +]; +const CONTEXT: &[u8] = &[ + 228, 137, 183, 195, 107, 83, 44, 167, 62, 235, 215, 116, 108, 38, 108, 149, 107, 206, 154, 191, 189, 219, 105, 175, + 72, 213, 172, 131, 94, 207, 58, 208, +]; +const PRIVATE_KEY_DH: &[u8] = &[ + 139, 93, 50, 184, 90, 214, 77, 2, 57, 23, 5, 0, 155, 2, 202, 140, 58, 27, 111, 51, 97, 204, 165, 167, 18, 41, 158, + 25, 48, 44, 42, 198, 74, 238, 245, 201, 107, 49, 243, 27, 164, 205, 223, 112, 31, 100, 146, 48, 90, 81, 126, 112, + 38, 0, 194, 4, 195, 140, 122, 134, 104, 123, 211, 100, +]; +const PUBLIC_KEY_DH: &[u8] = &[ + 68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, 238, + 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, 223, 69, + 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, 100, 151, + 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, 177, 239, 66, + 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, 57, 160, 191, 18, + 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, 82, 82, 103, 53, 72, + 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, 161, 103, 181, 164, 24, 37, + 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, 202, 63, 121, 113, 80, 96, 38, + 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, 163, 150, 10, 84, 231, 16, 195, 117, + 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, 210, 39, 110, 17, 113, 95, 105, 56, 119, + 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, + 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, + 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, + 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, + 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, + 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, + 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, + 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, + 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, + 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, + 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, 130, 102, 75, 76, 15, 108, 196, 22, 89, 63, 246, 158, 197, 238, 228, + 177, 87, 255, 7, 170, 179, 251, 65, 155, 170, 131, 138, 187, 46, 97, 142, 5, 165, 60, 250, 49, 231, 45, 194, 253, + 138, 19, 51, 17, 14, 58, 138, 220, 159, 243, 234, 232, 20, 213, 21, 252, 63, 24, 156, 7, 240, 21, 148, 36, 254, + 147, 3, 29, 43, 52, 13, 13, 153, 78, 16, 128, 153, 153, 114, 253, 211, 219, 59, 84, 206, 244, 233, 243, 222, 144, + 228, 133, 135, 176, 87, 48, 253, 16, 188, 170, 171, 53, 228, 102, 234, 1, 38, 120, 251, 65, 104, 155, 189, 160, 74, + 150, 128, 13, 242, 122, 148, 52, 206, 158, 237, 95, 106, 96, 236, 190, 43, 173, 141, 144, 42, 198, 40, 92, 242, + 100, 42, 54, 249, 250, 249, 97, 19, 168, 10, 109, 182, 35, 138, 248, 158, 153, 45, 181, 175, 160, 65, 6, 210, 191, + 73, 164, 230, 167, 12, 140, 148, 222, 156, 10, 62, 149, 64, 13, 150, 200, 169, 109, 49, 149, 52, 227, 37, 250, 250, + 208, 109, 50, 187, 91, 242, 40, 102, 200, 248, 182, 96, 18, 87, 71, 238, 88, 8, 220, 157, 201, 237, 178, 250, 211, + 77, 95, 21, 98, 248, 205, 24, 172, 212, 96, 169, 101, 35, 47, 40, 187, 132, 216, 243, 39, 131, 24, 135, 88, 17, + 245, 208, 83, 162, 58, 194, 84, 192, 227, 105, 145, 134, 13, 44, 216, 9, 102, 61, 99, 224, 239, 153, 66, 232, 191, + 24, +]; + +#[test] +fn test_kdf_sha1() { + let expected_key = [ + 117, 25, 15, 198, 42, 170, 180, 156, 140, 156, 188, 164, 163, 245, 40, 18, 53, 43, 149, 141, 135, 152, 11, 248, + 80, 84, 1, 195, 212, 7, 149, 35, + ]; + + let key = kdf(HashAlg::Sha1, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 32).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_kdf_sha256() { + let expected_key = [ + 95, 246, 71, 210, 202, 186, 163, 251, 24, 175, 54, 107, 191, 107, 87, 35, 241, 202, 64, 106, 34, 201, 185, 5, + 213, 175, 222, 111, 249, 145, 238, 162, + ]; + + let key = kdf(HashAlg::Sha256, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 32).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_kdf_sha384() { + let expected_key = [ + 91, 218, 125, 86, 51, 207, 96, 224, 6, 253, 16, 137, 142, 10, 95, 156, 163, 217, 31, 186, 206, 88, 81, 141, + 231, 62, 224, 200, 168, 156, 189, 71, 60, 220, 166, 65, 141, 47, 92, 145, 241, 112, 91, 39, 27, 237, 88, 122, + 103, 38, 115, 222, 26, 214, 185, 78, 34, 7, 170, 54, 74, 18, 206, 75, + ]; + + let key = kdf(HashAlg::Sha384, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 64).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_kdf_sha512() { + let expected_key = [ + 56, 219, 230, 175, 76, 173, 241, 49, 216, 97, 145, 27, 74, 153, 173, 79, 201, 145, 64, 135, 166, 0, 111, 19, + 164, 112, 171, 230, 130, 28, 71, 240, 122, 88, 46, 26, 192, 243, 50, 182, 242, 217, 179, 190, 12, 13, 85, 1, + 202, 211, 212, 169, 83, 208, 162, 227, 217, 30, 33, 226, 101, 230, 8, 109, + ]; + + let key = kdf(HashAlg::Sha512, SECRET_KEY, KDS_SERVICE_LABEL, CONTEXT, 64).unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +#[test] +fn test_compute_public_key_dh() { + let expected_key = [ + 68, 72, 80, 66, 0, 1, 0, 0, 135, 168, 230, 29, 180, 182, 102, 60, 255, 187, 209, 156, 101, 25, 89, 153, 140, + 238, 246, 8, 102, 13, 208, 242, 93, 44, 238, 212, 67, 94, 59, 0, 224, 13, 248, 241, 214, 25, 87, 212, 250, 247, + 223, 69, 97, 178, 170, 48, 22, 195, 217, 17, 52, 9, 111, 170, 59, 244, 41, 109, 131, 14, 154, 124, 32, 158, 12, + 100, 151, 81, 122, 189, 90, 138, 157, 48, 107, 207, 103, 237, 145, 249, 230, 114, 91, 71, 88, 192, 34, 224, + 177, 239, 66, 117, 191, 123, 108, 91, 252, 17, 212, 95, 144, 136, 185, 65, 245, 78, 177, 229, 155, 184, 188, + 57, 160, 191, 18, 48, 127, 92, 79, 219, 112, 197, 129, 178, 63, 118, 182, 58, 202, 225, 202, 166, 183, 144, 45, + 82, 82, 103, 53, 72, 138, 14, 241, 60, 109, 154, 81, 191, 164, 171, 58, 216, 52, 119, 150, 82, 77, 142, 246, + 161, 103, 181, 164, 24, 37, 217, 103, 225, 68, 229, 20, 5, 100, 37, 28, 202, 203, 131, 230, 180, 134, 246, 179, + 202, 63, 121, 113, 80, 96, 38, 192, 184, 87, 246, 137, 150, 40, 86, 222, 212, 1, 10, 189, 11, 230, 33, 195, + 163, 150, 10, 84, 231, 16, 195, 117, 242, 99, 117, 215, 1, 65, 3, 164, 181, 67, 48, 193, 152, 175, 18, 97, 22, + 210, 39, 110, 17, 113, 95, 105, 56, 119, 250, 215, 239, 9, 202, 219, 9, 74, 233, 30, 26, 21, 151, 63, 179, 44, + 155, 115, 19, 77, 11, 46, 119, 80, 102, 96, 237, 189, 72, 76, 167, 177, 143, 33, 239, 32, 84, 7, 244, 121, 58, + 26, 11, 161, 37, 16, 219, 193, 80, 119, 190, 70, 63, 255, 79, 237, 74, 172, 11, 181, 85, 190, 58, 108, 27, 12, + 107, 71, 177, 188, 55, 115, 191, 126, 140, 111, 98, 144, 18, 40, 248, 194, 140, 187, 24, 165, 90, 227, 19, 65, + 0, 10, 101, 1, 150, 249, 49, 199, 122, 87, 242, 221, 244, 99, 229, 233, 236, 20, 75, 119, 125, 230, 42, 170, + 184, 168, 98, 138, 195, 118, 210, 130, 214, 237, 56, 100, 230, 121, 130, 66, 142, 188, 131, 29, 20, 52, 143, + 111, 47, 145, 147, 181, 4, 90, 242, 118, 113, 100, 225, 223, 201, 103, 193, 251, 63, 46, 85, 164, 189, 27, 255, + 232, 59, 156, 128, 208, 82, 185, 133, 209, 130, 234, 10, 219, 42, 59, 115, 19, 211, 254, 20, 200, 72, 75, 30, + 5, 37, 136, 185, 183, 210, 187, 210, 223, 1, 97, 153, 236, 208, 110, 21, 87, 205, 9, 21, 179, 53, 59, 187, 100, + 224, 236, 55, 127, 208, 40, 55, 13, 249, 43, 82, 199, 137, 20, 40, 205, 198, 126, 182, 24, 75, 82, 61, 29, 178, + 70, 195, 47, 99, 7, 132, 144, 240, 14, 248, 214, 71, 209, 72, 212, 121, 84, 81, 94, 35, 39, 207, 239, 152, 197, + 130, 102, 75, 76, 15, 108, 196, 22, 89, 112, 124, 225, 37, 170, 121, 200, 204, 39, 82, 73, 239, 179, 79, 50, + 51, 207, 130, 16, 9, 49, 150, 137, 59, 156, 72, 231, 118, 10, 79, 87, 132, 54, 160, 121, 120, 82, 45, 130, 61, + 11, 207, 93, 176, 13, 49, 155, 223, 213, 26, 171, 188, 84, 184, 62, 16, 149, 16, 26, 35, 72, 12, 173, 68, 176, + 48, 84, 175, 37, 188, 209, 38, 57, 183, 57, 184, 123, 249, 56, 131, 229, 224, 39, 66, 9, 178, 36, 254, 21, 73, + 60, 212, 212, 119, 130, 245, 84, 33, 111, 156, 95, 19, 172, 13, 82, 37, 38, 109, 52, 223, 45, 162, 130, 115, + 64, 186, 53, 50, 42, 119, 173, 13, 128, 224, 12, 40, 93, 71, 136, 205, 137, 185, 138, 201, 202, 158, 184, 18, + 77, 242, 208, 18, 49, 124, 69, 105, 20, 3, 114, 204, 98, 30, 64, 153, 254, 165, 198, 129, 124, 53, 251, 168, + 187, 150, 176, 245, 34, 42, 159, 27, 186, 65, 126, 35, 175, 148, 173, 231, 57, 68, 198, 175, 117, 130, 17, 248, + 234, 224, 220, 238, 197, 226, 200, 190, 121, 5, 81, 66, 133, 13, 41, 74, 89, 29, 106, 14, 56, 186, 246, 156, + 51, 204, 84, 247, 202, 39, 58, 62, 38, 170, 170, 191, 8, 18, 15, 65, 53, 239, 223, 98, 245, 69, 40, 70, 147, + 81, 9, 177, 119, 78, 158, 68, 179, 94, 183, 150, 34, 134, 172, 28, 86, 63, 192, 65, 4, 216, + ]; + + let public_key = compute_public_key("DH", PRIVATE_KEY_DH, PUBLIC_KEY_DH).unwrap(); + + assert_eq!(expected_key[..], public_key[..]); +} + +#[test] +fn test_compute_kek_dh() { + let expected_key = [ + 9, 171, 213, 100, 174, 219, 112, 33, 135, 63, 151, 51, 231, 55, 121, 167, 132, 216, 251, 190, 174, 207, 209, + 164, 141, 125, 85, 196, 84, 60, 232, 36, + ]; + + let kek = compute_kek(HashAlg::Sha512, "DH", PRIVATE_KEY_DH, PUBLIC_KEY_DH).unwrap(); + + assert_eq!(expected_key[..], kek[..]); +} + +#[test] +fn test_cek_encrypt() { + let expected_key = [ + 177, 34, 69, 51, 190, 164, 94, 127, 38, 205, 148, 208, 11, 108, 215, 29, 178, 61, 153, 114, 42, 203, 15, 82, + 30, 72, 228, 118, 78, 34, 29, 117, 181, 56, 147, 124, 62, 48, 255, 39, + ]; + + let wrapped_key = cek_encrypt( + &KeyEncryptionAlgorithmIdentifier::new_aes256_empty(AesMode::Wrap), + &[ + 9, 171, 213, 100, 174, 219, 112, 33, 135, 63, 151, 51, 231, 55, 121, 167, 132, 216, 251, 190, 174, 207, + 209, 164, 141, 125, 85, 196, 84, 60, 232, 36, + ], + &[ + 206, 232, 113, 60, 84, 106, 53, 122, 24, 150, 171, 198, 170, 126, 87, 228, 7, 22, 212, 151, 162, 93, 220, + 211, 115, 74, 24, 231, 235, 112, 110, 133, + ], + ) + .unwrap(); + + assert_eq!(expected_key[..], wrapped_key[..]); +} + +#[test] +fn test_cek_decrypt() { + let expected_key = [ + 237, 217, 97, 116, 100, 107, 229, 54, 97, 127, 233, 172, 141, 83, 124, 250, 21, 115, 218, 160, 137, 22, 103, + 96, 167, 25, 59, 35, 65, 126, 69, 192, + ]; + + let key = cek_decrypt( + &KeyEncryptionAlgorithmIdentifier::new_aes256_empty(AesMode::Wrap), + &[ + 166, 59, 66, 26, 83, 122, 242, 219, 236, 155, 114, 107, 185, 13, 252, 191, 239, 219, 244, 91, 42, 197, 34, + 82, 11, 8, 251, 120, 137, 197, 250, 110, + ], + &[ + 79, 59, 241, 186, 249, 240, 229, 63, 50, 183, 56, 137, 17, 64, 57, 136, 49, 12, 176, 219, 163, 106, 132, + 25, 1, 87, 85, 16, 179, 52, 21, 138, 173, 143, 110, 15, 16, 0, 99, 244, + ], + ) + .unwrap(); + + assert_eq!(expected_key[..], key[..]); +} + +const PLAINTEXT: &[u8] = &[84, 104, 101, 66, 101, 115, 116, 84, 118, 97, 114, 121, 110, 107, 97]; +const CIPHER_TEXT: &[u8] = &[ + 141, 73, 82, 191, 110, 35, 212, 200, 182, 19, 135, 174, 143, 253, 167, 179, 170, 9, 181, 213, 130, 114, 20, 4, 145, + 63, 224, 92, 231, 37, 18, +]; +const AES256_GCM_IV: &[u8] = &[127, 98, 187, 173, 250, 133, 155, 4, 74, 60, 109, 245]; +const AES256_GCM_KEY: &[u8] = &[ + 237, 217, 97, 116, 100, 107, 229, 54, 97, 127, 233, 172, 141, 83, 124, 250, 21, 115, 218, 160, 137, 22, 103, 96, + 167, 25, 59, 35, 65, 126, 69, 192, +]; + +#[test] +fn test_content_decrypt() { + let plaintext = content_decrypt( + &ContentEncryptionAlgorithmIdentifier::new_aes256( + AesMode::Gcm, + AesParameters::InitializationVector(OctetStringAsn1::from(AES256_GCM_IV.to_vec())), + ), + AES256_GCM_KEY, + CIPHER_TEXT, + ) + .unwrap(); + + assert_eq!(PLAINTEXT[..], plaintext[..]); +} + +#[test] +fn test_content_encrypt() { + let cipher_text = content_encrypt( + &ContentEncryptionAlgorithmIdentifier::new_aes256( + AesMode::Gcm, + AesParameters::InitializationVector(OctetStringAsn1::from(AES256_GCM_IV.to_vec())), + ), + AES256_GCM_KEY, + PLAINTEXT, + ) + .unwrap(); + + assert_eq!(CIPHER_TEXT[..], cipher_text[..]); +} diff --git a/crates/dpapi/tests/dpapi/macros.rs b/crates/dpapi/tests/dpapi/macros.rs index 671ad94a..7877810b 100644 --- a/crates/dpapi/tests/dpapi/macros.rs +++ b/crates/dpapi/tests/dpapi/macros.rs @@ -11,7 +11,7 @@ macro_rules! test_encoding_decoding { let encoded = parsed.encode_to_vec().unwrap(); assert_eq!($expected, parsed); - assert_eq!(data.as_ref(), &encoded); + assert_eq!(data[..], encoded[..]); } } }; diff --git a/crates/dpapi/tests/dpapi/main.rs b/crates/dpapi/tests/dpapi/main.rs index 10366eab..e9002a62 100644 --- a/crates/dpapi/tests/dpapi/main.rs +++ b/crates/dpapi/tests/dpapi/main.rs @@ -1,6 +1,6 @@ #[macro_use] mod macros; - mod blob; +mod crypto; mod gkdi; mod rpc;