Skip to content

Commit f77e9ac

Browse files
PavloMyroniukCBenoit
authored andcommitted
feat(ffi): implement DPAPI FFI functions (#380)
1 parent ab347e3 commit f77e9ac

File tree

10 files changed

+439
-2
lines changed

10 files changed

+439
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ picky-krb = "0.9"
4646
tokio = "1.43"
4747
ffi-types = { path = "crates/ffi-types" }
4848
winscard = { version = "0.2", path = "crates/winscard" }
49+
dpapi = { version = "0.1.0", path = "crates/dpapi" }
4950
rsa = { version = "0.9.7", default-features = false }
5051
windows-sys = "0.59"
5152
base64 = "0.22"

crates/dpapi/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ description = "A Rust implementation of Windows DPAPI"
1212
[lib]
1313
name = "dpapi"
1414

15+
[features]
16+
tsssp = ["sspi/tsssp"]
17+
1518
[dependencies]
1619
bitflags.workspace = true
1720
byteorder.workspace = true

crates/dpapi/src/rpc/auth.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ impl AuthProvider {
4444
SspiContext::Kerberos(_) => SecurityProvider::GssKerberos,
4545
SspiContext::Negotiate(_) => SecurityProvider::GssNegotiate,
4646
SspiContext::Pku2u(_) => Err(AuthError::SecurityProviderNotSupported("PKU2U"))?,
47+
#[cfg(feature = "tsssp")]
48+
SspiContext::CredSsp(_) => Err(AuthError::SecurityProviderNotSupported("CredSSP"))?,
4749
};
4850

4951
let builder = AcquireCredentialsHandle::<'_, _, _, WithoutCredentialUse>::new();

crates/ffi-types/src/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub type Bool = i32;
2424
/// unsigned char Data4[8];
2525
/// } GUID;
2626
/// ```
27+
#[derive(Clone, Copy)]
2728
#[repr(C)]
2829
pub struct Guid {
2930
pub data1: u32,

ffi/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ name = "sspi"
1313
crate-type = ["cdylib"]
1414

1515
[features]
16-
default = ["aws-lc-rs", "scard"]
17-
tsssp = ["sspi/tsssp"]
16+
default = ["aws-lc-rs", "scard", "dpapi"]
17+
tsssp = ["sspi/tsssp", "dpapi/tsssp"]
1818
scard = ["sspi/scard", "dep:ffi-types", "dep:winscard", "dep:bitflags", "dep:picky-asn1-x509", "dep:picky"]
1919
aws-lc-rs = ["sspi/aws-lc-rs"]
2020
ring = ["sspi/ring"]
21+
dpapi = ["dep:dpapi", "dep:ffi-types"]
2122

2223
[dependencies]
2324
cfg-if.workspace = true
@@ -27,6 +28,7 @@ ffi-types = { workspace = true, features = ["winscard"], optional = true }
2728
picky-asn1-der.workspace = true
2829
uuid.workspace = true
2930
winscard = { workspace = true, features = ["std"], optional = true }
31+
dpapi = { workspace = true, optional = true }
3032
# logging
3133
tracing = { workspace = true, default-features = true }
3234
tracing-subscriber = { workspace = true, features = ["std", "fmt", "local-time", "env-filter"] }

ffi/src/dpapi/api.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#[cfg(not(any(test, miri)))]
2+
mod inner {
3+
pub use dpapi::{n_crypt_protect_secret, n_crypt_unprotect_secret};
4+
}
5+
6+
#[cfg(any(test, miri))]
7+
mod inner {
8+
//! We have FFI wrappers for DPAPI functions from the [dpapi] crate and we want to test them.
9+
//! The DPAPI implementation is complex and makes calls to the RPC and KDC servers.
10+
//! Implementing a mock for KDF and RPC servers is too hard and unreasonable. So, we wrote a simple
11+
//! high-level mock of [n_crypt_protect_secret] and [n_crypt_unprotect_secret] functions.
12+
//!
13+
//! **Note**: The goal is to test FFI functions, not the DPAPI implementation correctness.
14+
//! The FFI tests should not care about returned data correctness but rather check
15+
//! for memory corruptions and memory leaks.
16+
17+
use sspi::Secret;
18+
use uuid::Uuid;
19+
20+
pub fn n_crypt_unprotect_secret(
21+
_blob: &[u8],
22+
_server: &str,
23+
_username: &str,
24+
_password: Secret<String>,
25+
_client_computer_name: Option<String>,
26+
) -> dpapi::Result<Secret<Vec<u8>>> {
27+
Ok(b"secret-to-encrypt".to_vec().into())
28+
}
29+
30+
pub fn n_crypt_protect_secret(
31+
_data: Secret<Vec<u8>>,
32+
_sid: String,
33+
_root_key_id: Option<Uuid>,
34+
_server: &str,
35+
_username: &str,
36+
_password: Secret<String>,
37+
_client_computer_name: Option<String>,
38+
) -> dpapi::Result<Vec<u8>> {
39+
Ok(b"DPAPI_blob".to_vec())
40+
}
41+
}
42+
43+
pub use inner::*;

ffi/src/dpapi/macros.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
macro_rules! check_null {
2+
($x:expr) => {{
3+
if $x.is_null() {
4+
// https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret#return-value
5+
return crate::dpapi::NTE_INVALID_PARAMETER;
6+
}
7+
}};
8+
}
9+
10+
macro_rules! try_execute {
11+
($x:expr, $err_value:expr) => {{
12+
match $x {
13+
Ok(val) => val,
14+
Err(err) => {
15+
error!(%err, "an error occurred");
16+
return $err_value;
17+
}
18+
}
19+
}};
20+
}
21+
22+
macro_rules! catch_panic {
23+
($($tokens:tt)*) => {{
24+
match std::panic::catch_unwind(move || { $($tokens)* }) {
25+
Ok(val) => val,
26+
Err(_) => {
27+
return crate::dpapi::NTE_INTERNAL_ERROR;
28+
}
29+
}
30+
}};
31+
}

0 commit comments

Comments
 (0)