Skip to content
Draft
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
62 changes: 47 additions & 15 deletions mscrypto-bcrypt/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ use windows_sys::Win32::Security::Cryptography::*;

use crate::BcryptProvider;

const SHA256_LEN: usize = 32;
const SHA384_LEN: usize = 48;
const SHA512_LEN: usize = 64;

// windows-sys exposes the SHA-2 algorithm pseudo-handles but not the SHA-3 ones, so
// define them the same way it defines its own (a BCRYPT_ALG_HANDLE built from the value
// in the bcrypt pseudo-handle table). SHA-3 pseudo-handles exist on Windows 11 24H2 and later.
Expand Down Expand Up @@ -71,6 +67,15 @@ impl BcryptHasher {
output_len,
}
}

/// Builds a keyed hasher (HMAC) from an algorithm pseudo-handle. Shares the
/// streaming machinery with the bare hash path; only construction differs.
pub(crate) fn new_keyed(alg: BCRYPT_ALG_HANDLE, output_len: usize, key: &[u8]) -> Self {
BcryptHasher {
hash: create_keyed_hash_handle(alg, key),
output_len,
}
}
}

/// Creates a hash object from an algorithm pseudo-handle. Passing NULL/0 for the object
Expand All @@ -84,6 +89,30 @@ fn create_hash_handle(alg: BCRYPT_ALG_HANDLE) -> HashHandle {
HashHandle(handle)
}

/// Creates a keyed hash object (HMAC) from an algorithm pseudo-handle, passing the
/// key as the secret. Passing NULL/0 for the object buffer lets bcrypt allocate it,
/// freed via BCryptDestroyHash. Panics on failure (the HMAC path is infallible, like
/// SHA-2). An HMAC key is never large enough to overflow the u32 length.
fn create_keyed_hash_handle(alg: BCRYPT_ALG_HANDLE, key: &[u8]) -> HashHandle {
let mut handle: BCRYPT_HASH_HANDLE = ptr::null_mut();
// SAFETY: alg is a valid HMAC algorithm pseudo-handle; key is valid for its length;
// the hash object is bcrypt owned.
let status = unsafe {
BCryptCreateHash(
alg,
&mut handle,
ptr::null_mut(),
0,
key.as_ptr(),
// TODO: `key.len() as u32` truncates keys longer than u32::MAX.
key.len() as u32,
0,
)
};
expect_success("BCryptCreateHash", status);
HashHandle(handle)
}

impl HashOps for BcryptHasher {
fn update(&mut self, data: &[u8]) {
// BCryptHashData's length is a u32 (ULONG). On 64-bit a &[u8] can exceed
Expand Down Expand Up @@ -114,12 +143,12 @@ impl Hash for BcryptProvider {
type Hasher = BcryptHasher;

fn hash(&self, algorithm: BaseHashAlgorithm) -> BcryptHasher {
let (alg, len) = match algorithm {
BaseHashAlgorithm::Sha256 => (BCRYPT_SHA256_ALG_HANDLE, SHA256_LEN),
BaseHashAlgorithm::Sha384 => (BCRYPT_SHA384_ALG_HANDLE, SHA384_LEN),
BaseHashAlgorithm::Sha512 => (BCRYPT_SHA512_ALG_HANDLE, SHA512_LEN),
let alg = match algorithm {
BaseHashAlgorithm::Sha256 => BCRYPT_SHA256_ALG_HANDLE,
BaseHashAlgorithm::Sha384 => BCRYPT_SHA384_ALG_HANDLE,
BaseHashAlgorithm::Sha512 => BCRYPT_SHA512_ALG_HANDLE,
};
BcryptHasher::new(alg, len)
BcryptHasher::new(alg, algorithm.output_len())
}

fn digest(&self, algorithm: BaseHashAlgorithm, data: &[u8]) -> Digest {
Expand Down Expand Up @@ -167,7 +196,7 @@ pub(crate) fn sha3_available(algorithm: Sha3Algorithm) -> bool {

#[cfg(feature = "sha3")]
mod sha3_impl {
use super::{sha3_alg_handle, BcryptHasher, SHA256_LEN, SHA384_LEN, SHA512_LEN};
use super::{sha3_alg_handle, BcryptHasher};
use crate::BcryptProvider;
use mscrypto::algorithm::Sha3Algorithm;
use mscrypto::error::Error;
Expand All @@ -178,15 +207,18 @@ mod sha3_impl {
type Sha3Hasher = BcryptHasher;

fn sha3(&self, algorithm: Sha3Algorithm) -> Result<BcryptHasher, Error> {
let (len, available) = match algorithm {
Sha3Algorithm::Sha3_256 => (SHA256_LEN, self.sha3_256),
Sha3Algorithm::Sha3_384 => (SHA384_LEN, self.sha3_384),
Sha3Algorithm::Sha3_512 => (SHA512_LEN, self.sha3_512),
let available = match algorithm {
Sha3Algorithm::Sha3_256 => self.sha3_256,
Sha3Algorithm::Sha3_384 => self.sha3_384,
Sha3Algorithm::Sha3_512 => self.sha3_512,
};
if !available {
return Err(Error::Unavailable);
}
Ok(BcryptHasher::new(sha3_alg_handle(algorithm), len))
Ok(BcryptHasher::new(
sha3_alg_handle(algorithm),
algorithm.output_len(),
))
}

fn sha3_digest(&self, algorithm: Sha3Algorithm, data: &[u8]) -> Result<Digest, Error> {
Expand Down
16 changes: 11 additions & 5 deletions mscrypto-bcrypt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
//! BCrypt backend for the `mscrypto` contract.
//!
//! Provides [`BcryptProvider`], a concrete [`CryptoProvider`] backed by Windows
//! (`bcryptprimitives.dll`).
//! (`bcryptprimitives.dll`).

#![cfg(windows)]

mod hash;
mod mac;

pub use hash::BcryptHasher;
pub use mac::BcryptMac;

/// Everything needed to use this provider in one glob import:
/// `use mscrypto_bcrypt::prelude::*;`. It re-exports the provider and traits
/// `use mscrypto_bcrypt::prelude::*;`. It re-exports the provider and traits
/// (whose methods are otherwise not in scope), and the shared
/// algorithm, error, and metadata types from the contract, so a consumer
/// does not need a separate dependency on `mscrypto` for the common path.
pub mod prelude {
pub use crate::{BcryptHasher, BcryptProvider, BcryptProviderBuilder};
pub use crate::{BcryptHasher, BcryptMac, BcryptProvider, BcryptProviderBuilder};

pub use mscrypto::algorithm::{Algorithm, BaseHashAlgorithm};
pub use mscrypto::algorithm::{Algorithm, BaseHashAlgorithm, MacAlgorithm};
pub use mscrypto::error::{Error, ProviderBuildError};
pub use mscrypto::hash::{Digest, Hash, HashOps};
pub use mscrypto::mac::{Mac, MacOps};
pub use mscrypto::provider::{BackendInfo, BackendVersion, CryptoProvider, LinkMode};

#[cfg(feature = "sha3")]
Expand All @@ -28,7 +31,7 @@ pub mod prelude {
pub use mscrypto::sha3::Sha3;
}

use mscrypto::algorithm::{Algorithm, BaseHashAlgorithm};
use mscrypto::algorithm::{Algorithm, BaseHashAlgorithm, MacAlgorithm};
use mscrypto::error::ProviderBuildError;
use mscrypto::provider::{BackendInfo, BackendVersion, CryptoProvider, LinkMode};

Expand Down Expand Up @@ -112,6 +115,9 @@ impl CryptoProvider for BcryptProvider {
Algorithm::Hash(
BaseHashAlgorithm::Sha256 | BaseHashAlgorithm::Sha384 | BaseHashAlgorithm::Sha512,
) => true,
Algorithm::Mac(MacAlgorithm::Hmac(
BaseHashAlgorithm::Sha256 | BaseHashAlgorithm::Sha384 | BaseHashAlgorithm::Sha512,
)) => true,
#[cfg(feature = "sha3")]
Algorithm::Sha3(variant) => match variant {
Sha3Algorithm::Sha3_256 => self.sha3_256,
Expand Down
196 changes: 196 additions & 0 deletions mscrypto-bcrypt/src/mac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
//! Keyed hashing (HMAC) for the BCrypt provider.

use mscrypto::algorithm::{BaseHashAlgorithm, MacAlgorithm};
use mscrypto::hash::{Digest, HashOps};
use mscrypto::mac::{Mac, MacOps};

use windows_sys::Win32::Security::Cryptography::{
BCRYPT_ALG_HANDLE, BCRYPT_HMAC_SHA256_ALG_HANDLE, BCRYPT_HMAC_SHA384_ALG_HANDLE,
BCRYPT_HMAC_SHA512_ALG_HANDLE,
};

use crate::hash::BcryptHasher;
use crate::BcryptProvider;

/// Streaming HMAC state. HMAC runs on the same BCrypt hash-object machinery as a
/// bare hash, so this wraps a keyed hasher; only construction differs.
pub struct BcryptMac(BcryptHasher);

impl MacOps for BcryptMac {
fn update(&mut self, data: &[u8]) {
self.0.update(data);
}

fn finalize(self) -> Digest {
self.0.finalize()
}
}

/// Maps an HMAC mechanism to its algorithm pseudo-handle and tag length.
fn hmac_alg(algorithm: MacAlgorithm) -> (BCRYPT_ALG_HANDLE, usize) {
match algorithm {
MacAlgorithm::Hmac(base) => {
let handle = match base {
BaseHashAlgorithm::Sha256 => BCRYPT_HMAC_SHA256_ALG_HANDLE,
BaseHashAlgorithm::Sha384 => BCRYPT_HMAC_SHA384_ALG_HANDLE,
BaseHashAlgorithm::Sha512 => BCRYPT_HMAC_SHA512_ALG_HANDLE,
};
(handle, base.output_len())
}
}
}

impl Mac for BcryptProvider {
type MacState = BcryptMac;

fn mac(&self, algorithm: MacAlgorithm, key: &[u8]) -> BcryptMac {
let (alg, len) = hmac_alg(algorithm);
BcryptMac(BcryptHasher::new_keyed(alg, len, key))
}

fn mac_digest(&self, algorithm: MacAlgorithm, key: &[u8], data: &[u8]) -> Digest {
let mut mac = self.mac(algorithm, key);
mac.update(data);
mac.finalize()
}
}

#[cfg(test)]
mod test {
use crate::BcryptProvider;
use mscrypto::algorithm::{BaseHashAlgorithm, MacAlgorithm};
use mscrypto::hash::Digest;
use mscrypto::mac::{Mac, MacOps};

struct MacVector {
algorithm: MacAlgorithm,
key: &'static str,
msg: &'static str,
tag: &'static str,
}

// RFC 4231 HMAC test cases 1 and 2.
const HMAC_VECTORS: &[MacVector] = &[
MacVector {
algorithm: MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256),
key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
msg: "4869205468657265",
tag: "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7",
},
MacVector {
algorithm: MacAlgorithm::Hmac(BaseHashAlgorithm::Sha384),
key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
msg: "4869205468657265",
tag: "afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6",
},
MacVector {
algorithm: MacAlgorithm::Hmac(BaseHashAlgorithm::Sha512),
key: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
msg: "4869205468657265",
tag: "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854",
},
MacVector {
algorithm: MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256),
key: "4a656665",
msg: "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
tag: "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843",
},
MacVector {
algorithm: MacAlgorithm::Hmac(BaseHashAlgorithm::Sha384),
key: "4a656665",
msg: "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
tag: "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649",
},
MacVector {
algorithm: MacAlgorithm::Hmac(BaseHashAlgorithm::Sha512),
key: "4a656665",
msg: "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
tag: "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737",
},
];

fn hex_of(digest: &Digest) -> String {
hex::encode(digest.as_bytes())
}

#[test]
fn hmac_matches_kat() {
let provider = BcryptProvider::new().expect("SHA-2 providers open");
for vector in HMAC_VECTORS {
let key = hex::decode(vector.key).expect("valid hex key");
let msg = hex::decode(vector.msg).expect("valid hex message");
let tag = provider.mac_digest(vector.algorithm, &key, &msg);
assert_eq!(hex_of(&tag), vector.tag, "{:?}", vector.algorithm);
}
}

#[test]
fn streaming_matches_kat() {
let provider = BcryptProvider::new().expect("SHA-2 providers open");
let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").expect("valid hex key");
let mut mac = provider.mac(MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256), &key);
mac.update(&hex::decode("4869").expect("valid hex"));
mac.update(&hex::decode("205468657265").expect("valid hex"));
assert_eq!(
hex_of(&mac.finalize()),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
}

#[test]
fn verify_accepts_valid_tag() {
let provider = BcryptProvider::new().expect("SHA-2 providers open");
let algorithm = MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256);
let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").expect("valid hex key");
let msg = hex::decode("4869205468657265").expect("valid hex message");
let tag = provider.mac_digest(algorithm, &key, &msg);
provider
.verify(algorithm, &key, &msg, tag.as_bytes())
.expect("valid tag verifies");
}

#[test]
fn verify_rejects_forged_tag() {
let provider = BcryptProvider::new().expect("SHA-2 providers open");
let algorithm = MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256);
let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").expect("valid hex key");
let msg = hex::decode("4869205468657265").expect("valid hex message");
let mut tag = provider
.mac_digest(algorithm, &key, &msg)
.as_bytes()
.to_vec();
tag[0] ^= 0x01;
assert!(provider.verify(algorithm, &key, &msg, &tag).is_err());
}

#[test]
fn verify_rejects_wrong_length_tag() {
let provider = BcryptProvider::new().expect("SHA-2 providers open");
let algorithm = MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256);
let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").expect("valid hex key");
let msg = hex::decode("4869205468657265").expect("valid hex message");
let tag = provider.mac_digest(algorithm, &key, &msg);
assert!(provider
.verify(algorithm, &key, &msg, &tag.as_bytes()[..16])
.is_err());
}

#[test]
fn streaming_verify_accepts_and_rejects() {
let provider = BcryptProvider::new().expect("SHA-2 providers open");
let algorithm = MacAlgorithm::Hmac(BaseHashAlgorithm::Sha256);
let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").expect("valid hex key");
let msg = hex::decode("4869205468657265").expect("valid hex message");
let tag = provider.mac_digest(algorithm, &key, &msg);

let mut mac = provider.mac(algorithm, &key);
mac.update(&msg);
mac.verify(tag.as_bytes()).expect("valid tag verifies");

let mut forged = tag.as_bytes().to_vec();
forged[0] ^= 0x01;
let mut mac = provider.mac(algorithm, &key);
mac.update(&msg);
assert!(mac.verify(&forged).is_err());
}
}
6 changes: 5 additions & 1 deletion mscrypto-example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,9 @@ fn main() {
}

fn to_hex(digest: &Digest) -> String {
digest.as_bytes().iter().map(|byte| format!("{byte:02x}")).collect()
digest
.as_bytes()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
3 changes: 2 additions & 1 deletion mscrypto-symcrypt/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fn main() {
println!("cargo::rerun-if-env-changed=SYMCRYPT_STATIC");
println!("cargo::rerun-if-env-changed={prefix}_SYMCRYPT_STATIC");

let read = |name: &str| std::env::var(format!("{prefix}_{name}")).or_else(|_| std::env::var(name));
let read =
|name: &str| std::env::var(format!("{prefix}_{name}")).or_else(|_| std::env::var(name));
let is_static = read("SYMCRYPT_STATIC").map(|v| v != "0").unwrap_or(false);

// v1 distinguishes dynamic vs prebuilt-static only. "from_source" is reserved
Expand Down
2 changes: 1 addition & 1 deletion mscrypto-symcrypt/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl HashOps for SymCryptHasher {
/// Copies a fixed-size SymCrypt result into a `Digest`. SymCrypt's safe API hands
/// back an owned array, so the digest bytes are moved through a single stack copy
/// of at most `Digest::MAX_LEN` bytes.
fn digest_from<const N: usize>(out: [u8; N]) -> Digest {
pub(crate) fn digest_from<const N: usize>(out: [u8; N]) -> Digest {
Digest::from_fn(N, |buf| buf.copy_from_slice(&out))
}

Expand Down
Loading
Loading