Skip to content
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

feat(dpapi): implement encryption and key derivation functions #350

Merged
merged 11 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"]
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down
14 changes: 14 additions & 0 deletions crates/dpapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it awful? Absolutely yes.
Do we have any alternative? Not really. See this: RustCrypto/KDFs#75 (comment)

I think the best solution is to contribute to RustCrypto. Or we can fork the rust-kbkdf crate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best approach is typically to contribute to the upstream project, because this gives back to the open source community while removing maintenance burden from our shoulders. We definitely can’t release a crate with a git dependency. As you said, either we contribute back, either we go the fork route with a crate we maintain and push to crates.io. I’ll link this to Marc-André, and we’ll see how we prioritize this work.

Copy link
Member

@CBenoit CBenoit Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheBestTvarynka UPD: We agreed with Marc that the best course of action was to contribute KBKDF to RustCrypto.

  1. Take over the work in progress PR. Collaborate with the author and RustCrypto maintainers to implement the missing pieces so that PR can be merged. Check with them if you can open a new PR from your fork, or work from theirs.
  2. Let RustCrypto publish the crate and manage it.
  3. Remove the git dependency from dpdpi in favor of the proper crates.io dependency.
  4. Integrate and publish our dpdpi crate.

Your priority should be to move this forward so we’re not stuck with a git dependency when it’s time to integrate dpdpi into sspi.
It’s fine to merge work on dpdpi even with the git dependency, because dpdpi is not depended on by our other crates that we are publishing to crates.io yet.
Feel free to rotate back on the dpdpi work when you wait for their feedback.
RustCrypto maintainers are pretty responsive, so I believe things should move forward quickly 🙂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

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"
Expand Down
22 changes: 13 additions & 9 deletions crates/dpapi/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand All @@ -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 {
Expand All @@ -90,9 +94,9 @@ impl Encode for KeyIdentifier {
write_buf(&KeyIdentifier::MAGIC, &mut writer)?;
writer.write_u32::<LittleEndian>(self.flags)?;

writer.write_u32::<LittleEndian>(self.l0)?;
writer.write_u32::<LittleEndian>(self.l1)?;
writer.write_u32::<LittleEndian>(self.l2)?;
writer.write_i32::<LittleEndian>(self.l0)?;
writer.write_i32::<LittleEndian>(self.l1)?;
writer.write_i32::<LittleEndian>(self.l2)?;

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

Expand Down Expand Up @@ -125,9 +129,9 @@ impl Decode for KeyIdentifier {

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

let l0 = reader.read_u32::<LittleEndian>()?;
let l1 = reader.read_u32::<LittleEndian>()?;
let l2 = reader.read_u32::<LittleEndian>()?;
let l0 = reader.read_i32::<LittleEndian>()?;
let l1 = reader.read_i32::<LittleEndian>()?;
let l2 = reader.read_i32::<LittleEndian>()?;
let root_key_identifier = Uuid::decode(&mut reader)?;

let key_info_len = reader.read_u32::<LittleEndian>()?;
Expand Down
87 changes: 87 additions & 0 deletions crates/dpapi/src/crypto/hmac_sha_prf.rs
Original file line number Diff line number Diff line change
@@ -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<Hmac<$sha>>,
}

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<KeyHandle = HmacShaPrfKey<'a>>,
) -> 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<usize> {
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);
Loading
Loading