Skip to content

Commit c2122e6

Browse files
feat(dpapi): implement encryption and key derivation functions (#350)
The `crypto` module contains many _magic_ numbers. I took them from the Python DPAPI implementation: https://github.com/jborean93/dpapi-ng/blob/main/src/dpapi_ng/_crypto.py and https://github.com/jborean93/dpapi-ng/blob/main/src/dpapi_ng/_gkdi.py. Docs & references: * [[MS-GKDI]: Group Key Distribution Protocol](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/943dd4f6-6b80-4a66-8594-80df6d2aad0a). * [jborean93/dpapi-ng](https://github.com/jborean93/dpapi-ng/tree/main).
1 parent 2711686 commit c2122e6

File tree

12 files changed

+1057
-40
lines changed

12 files changed

+1057
-40
lines changed

Cargo.lock

+44-12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ rand = "0.8"
3434
cfg-if = "1"
3535
time = { version = "0.3", default-features = false }
3636
sha1 = { version = "0.10", default-features = false }
37+
sha2 = "0.10"
3738
num-derive = "0.4"
3839
num-traits = { version = "0.2", default-features = false }
3940
picky = "7.0.0-rc.11"
@@ -53,6 +54,7 @@ proptest = "1.6"
5354
serde = "1"
5455
byteorder = "1.5"
5556
num-bigint-dig = "0.8"
57+
hmac = "0.12"
5658

5759
[features]
5860
default = ["aws-lc-rs"]
@@ -83,6 +85,7 @@ cfg-if.workspace = true
8385
time = { workspace = true, features = ["std"] }
8486
picky.workspace = true
8587
sha1.workspace = true
88+
sha2.workspace = true
8689
num-derive.workspace = true
8790
num-traits = { workspace = true, default-features = true }
8891
picky-asn1-der.workspace = true
@@ -98,11 +101,10 @@ picky-krb.workspace = true
98101
picky-asn1 = { workspace = true, features = ["time_conversion"] }
99102
byteorder.workspace = true
100103
num-bigint-dig.workspace = true
104+
hmac.workspace = true
101105

102106
md-5 = "0.10"
103107
md4 = "0.10"
104-
sha2 = "0.10"
105-
hmac = "0.12"
106108
crypto-mac = "0.11"
107109
lazy_static = "1.5"
108110
serde_derive = "1"

crates/dpapi/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ picky-asn1.workspace = true
2222
picky-asn1-der.workspace = true
2323
picky-asn1-x509 = { workspace = true, features = ["pkcs7"] }
2424
num-bigint-dig.workspace = true
25+
sha1.workspace = true
26+
sha2.workspace = true
27+
rand.workspace = true
28+
hmac.workspace = true
29+
30+
rust-kbkdf = { version = "1.1", git = "https://gitlab.com/TheBestTvarynka/rust-kbkdf.git", branch = "fix-key-generation" }
31+
elliptic-curve = { version = "0.13", features = ["sec1", "std"] }
32+
p521 = { version = "0.13", features = ["ecdh"] }
33+
p256 = { version = "0.13", features = ["ecdh"] }
34+
p384 = { version = "0.13", features = ["ecdh"] }
35+
concat-kdf = { version = "0.1", features = ["std"] }
36+
typenum = "1.17"
37+
aes-kw = { version = "0.2", features = ["std"] }
38+
aes-gcm = { version = "0.10", features = ["std"] }
2539

2640
thiserror = "2.0"
2741
regex = "1.11"

crates/dpapi/src/blob.rs

+13-9
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ pub struct KeyIdentifier {
6060
pub flags: u32,
6161

6262
/// The L0 index of the key.
63-
pub l0: u32,
63+
pub l0: i32,
6464
/// The L1 index of the key.
65-
pub l1: u32,
65+
pub l1: i32,
6666
/// The L2 index of the key.
67-
pub l2: u32,
67+
pub l2: i32,
6868
/// A GUID that identifies a root key.
6969
pub root_key_identifier: Uuid,
7070

@@ -79,6 +79,10 @@ pub struct KeyIdentifier {
7979
impl KeyIdentifier {
8080
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gkdi/192c061c-e740-4aa0-ab1d-6954fb3e58f7
8181
const MAGIC: [u8; 4] = [0x4b, 0x44, 0x53, 0x4b];
82+
83+
pub fn is_public_key(&self) -> bool {
84+
self.flags & 1 != 0
85+
}
8286
}
8387

8488
impl Encode for KeyIdentifier {
@@ -90,9 +94,9 @@ impl Encode for KeyIdentifier {
9094
write_buf(&KeyIdentifier::MAGIC, &mut writer)?;
9195
writer.write_u32::<LittleEndian>(self.flags)?;
9296

93-
writer.write_u32::<LittleEndian>(self.l0)?;
94-
writer.write_u32::<LittleEndian>(self.l1)?;
95-
writer.write_u32::<LittleEndian>(self.l2)?;
97+
writer.write_i32::<LittleEndian>(self.l0)?;
98+
writer.write_i32::<LittleEndian>(self.l1)?;
99+
writer.write_i32::<LittleEndian>(self.l2)?;
96100

97101
self.root_key_identifier.encode(&mut writer)?;
98102

@@ -125,9 +129,9 @@ impl Decode for KeyIdentifier {
125129

126130
let flags = reader.read_u32::<LittleEndian>()?;
127131

128-
let l0 = reader.read_u32::<LittleEndian>()?;
129-
let l1 = reader.read_u32::<LittleEndian>()?;
130-
let l2 = reader.read_u32::<LittleEndian>()?;
132+
let l0 = reader.read_i32::<LittleEndian>()?;
133+
let l1 = reader.read_i32::<LittleEndian>()?;
134+
let l2 = reader.read_i32::<LittleEndian>()?;
131135
let root_key_identifier = Uuid::decode(&mut reader)?;
132136

133137
let key_info_len = reader.read_u32::<LittleEndian>()?;
+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use hmac::{Hmac, Mac};
2+
use rust_kbkdf::{PseudoRandomFunction, PseudoRandomFunctionKey};
3+
4+
use super::{CryptoError, CryptoResult};
5+
6+
pub struct HmacShaPrfKey<'key>(&'key [u8]);
7+
8+
impl<'key> HmacShaPrfKey<'key> {
9+
pub fn new(key: &'key [u8]) -> Self {
10+
Self(key)
11+
}
12+
13+
pub fn key(&self) -> &[u8] {
14+
self.0
15+
}
16+
}
17+
18+
impl<'key> PseudoRandomFunctionKey for HmacShaPrfKey<'key> {
19+
type KeyHandle = HmacShaPrfKey<'key>;
20+
21+
fn key_handle(&self) -> &Self::KeyHandle {
22+
self
23+
}
24+
}
25+
26+
macro_rules! define_hmac_sha_prf {
27+
($name:ident, $sha:ty, $out_size:ty) => {
28+
pub struct $name {
29+
hmac: Option<Hmac<$sha>>,
30+
}
31+
32+
impl $name {
33+
pub fn new() -> Self {
34+
Self { hmac: None }
35+
}
36+
}
37+
38+
impl<'a> PseudoRandomFunction<'a> for $name {
39+
type KeyHandle = HmacShaPrfKey<'a>;
40+
type PrfOutputSize = $out_size;
41+
type Error = CryptoError;
42+
43+
fn init(
44+
&mut self,
45+
key: &'a dyn PseudoRandomFunctionKey<KeyHandle = HmacShaPrfKey<'a>>,
46+
) -> CryptoResult<()> {
47+
self.hmac = Some(Hmac::<$sha>::new_from_slice(key.key_handle().key()).map_err(|_| {
48+
use hmac::digest::crypto_common::KeySizeUser;
49+
50+
CryptoError::InvalidKeyLength {
51+
expected: Hmac::<$sha>::key_size(),
52+
actual: key.key_handle().key().len(),
53+
}
54+
})?);
55+
56+
Ok(())
57+
}
58+
59+
fn update(&mut self, msg: &[u8]) -> CryptoResult<()> {
60+
if let Some(hmac) = self.hmac.as_mut() {
61+
hmac.update(msg);
62+
63+
Ok(())
64+
} else {
65+
Err(CryptoError::Uninitialized("HMAC hasher"))
66+
}
67+
}
68+
69+
fn finish(&mut self, out: &mut [u8]) -> CryptoResult<usize> {
70+
if let Some(hmac) = self.hmac.as_mut() {
71+
let hmac = hmac.clone().finalize().into_bytes();
72+
73+
out.copy_from_slice(hmac.as_slice());
74+
75+
Ok(hmac.as_slice().len())
76+
} else {
77+
Err(CryptoError::Uninitialized("HMAC hasher"))
78+
}
79+
}
80+
}
81+
};
82+
}
83+
84+
define_hmac_sha_prf!(HmacSha1Prf, sha1::Sha1, typenum::U20);
85+
define_hmac_sha_prf!(HmacSha256Prf, sha2::Sha256, typenum::U32);
86+
define_hmac_sha_prf!(HmacSha384Prf, sha2::Sha384, typenum::U48);
87+
define_hmac_sha_prf!(HmacSha512Prf, sha2::Sha512, typenum::U64);

0 commit comments

Comments
 (0)