Skip to content

Commit ab347e3

Browse files
PavloMyroniukCBenoit
authored andcommitted
feat(dpapi-cli): implement simple DPAPI cli client; (#378)
1 parent 442dfc1 commit ab347e3

File tree

7 files changed

+177
-4
lines changed

7 files changed

+177
-4
lines changed

Cargo.lock

Lines changed: 24 additions & 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
@@ -18,6 +18,7 @@ members = [
1818
"crates/winscard",
1919
"crates/ffi-types",
2020
"crates/dpapi",
21+
"crates/dpapi-cli-client",
2122
]
2223
exclude = [
2324
"tools/wasm-testcompile",

crates/dpapi-cli-client/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "dpapi-cli-client"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
xflags = "0.3"
8+
dpapi = { path = "../dpapi" }
9+
tracing-subscriber = { workspace = true, features = ["std", "fmt", "local-time", "env-filter"] }

crates/dpapi-cli-client/src/cli.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use std::path::PathBuf;
2+
3+
xflags::xflags! {
4+
/// DPAPI cli client. This app is used to encrypt/decrypt secrets using the DPAPI.
5+
cmd dpapi {
6+
/// Target server hostname.
7+
/// For example, win-956cqossjtf.tbt.com.
8+
required --server server: String
9+
10+
/// The username to decrypt/encrypt the DPAPI blob.
11+
/// The username can be specified in FQDN (DOMAIN\username) or UPN (username@domain) format.
12+
required --username username: String
13+
14+
/// User's password.
15+
required --password password: String
16+
17+
/// Client's computer name. This parameter is optional.
18+
/// If not provided, the current computer name will be used.
19+
optional --computer-name computer_name: String
20+
21+
/// Encrypt secret.
22+
/// This command simulates the `NCryptProtectSecret` function. The encrypted secret (DPAPI blob) will be printed to stdout.
23+
cmd encrypt {
24+
/// User's SID.
25+
required --sid sid: String
26+
27+
/// Secret to encrypt.
28+
/// This parameter is optional. If not provided, the app will try to read the secret from stdin.
29+
optional --secret secret: String
30+
}
31+
32+
cmd decrypt {
33+
/// Path to file that contains DPAPI blob.
34+
/// This parameter is optional. If not provided, the app will try to read the DPAPI blob from stdin.
35+
optional --file file: PathBuf
36+
}
37+
}
38+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use std::fs::OpenOptions;
2+
3+
use tracing_subscriber::prelude::*;
4+
use tracing_subscriber::EnvFilter;
5+
6+
const DPAPI_LOG_PATH_ENV: &str = "DPAPI_LOG_PATH";
7+
8+
pub fn init_logging() {
9+
let path = if let Ok(path) = std::env::var(DPAPI_LOG_PATH_ENV) {
10+
path
11+
} else {
12+
eprintln!(
13+
"[DPAPI] {} environment variable is not set. Logging is disabled.",
14+
DPAPI_LOG_PATH_ENV
15+
);
16+
return;
17+
};
18+
19+
let file = match OpenOptions::new().create(true).append(true).open(&path) {
20+
Ok(f) => f,
21+
Err(e) => {
22+
eprintln!("[DPAPI] Couldn't open log file: {e}. File path: {}", path);
23+
return;
24+
}
25+
};
26+
27+
let fmt_layer = tracing_subscriber::fmt::layer()
28+
.pretty()
29+
.with_thread_names(true)
30+
.with_writer(file);
31+
32+
tracing_subscriber::registry()
33+
.with(fmt_layer)
34+
.with(EnvFilter::from_env("DPAPI_LOG_LEVEL"))
35+
.init();
36+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
mod cli;
2+
mod logging;
3+
4+
use std::fs;
5+
use std::io::{stdin, stdout, Read, Result, Write};
6+
7+
use crate::cli::{Decrypt, Dpapi, DpapiCmd, Encrypt};
8+
9+
fn run(data: Dpapi) -> Result<()> {
10+
logging::init_logging();
11+
12+
let Dpapi {
13+
server,
14+
username,
15+
password,
16+
computer_name,
17+
subcommand,
18+
} = data;
19+
20+
match subcommand {
21+
DpapiCmd::Encrypt(Encrypt { sid, secret }) => {
22+
let secret = if let Some(secret) = secret {
23+
secret.into_bytes()
24+
} else {
25+
stdin().bytes().collect::<Result<Vec<_>>>()?
26+
};
27+
28+
let blob = dpapi::n_crypt_protect_secret(
29+
secret.into(),
30+
sid,
31+
None,
32+
&server,
33+
&username,
34+
password.into(),
35+
computer_name,
36+
)
37+
.unwrap();
38+
39+
stdout().write_all(&blob)?;
40+
}
41+
DpapiCmd::Decrypt(Decrypt { file }) => {
42+
let blob = if let Some(file) = file {
43+
fs::read(file)?
44+
} else {
45+
stdin().bytes().collect::<Result<Vec<_>>>()?
46+
};
47+
48+
let secret =
49+
dpapi::n_crypt_unprotect_secret(&blob, &server, &username, password.into(), computer_name).unwrap();
50+
51+
stdout().write_all(secret.as_ref())?;
52+
}
53+
}
54+
55+
Ok(())
56+
}
57+
58+
fn main() -> Result<()> {
59+
match Dpapi::from_env() {
60+
Ok(flags) => run(flags),
61+
Err(err) => err.exit(),
62+
}
63+
}

crates/dpapi/src/client.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -306,13 +306,14 @@ fn try_get_kerberos_config(server: &str, client_computer_name: Option<String>) -
306306
///
307307
/// MSDN:
308308
/// * [NCryptUnprotectSecret function (ncryptprotect.h)](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptunprotectsecret).
309+
#[instrument(err)]
309310
pub fn n_crypt_unprotect_secret(
310311
blob: &[u8],
311312
server: &str,
312313
username: &str,
313314
password: Secret<String>,
314315
client_computer_name: Option<String>,
315-
) -> Result<Vec<u8>> {
316+
) -> Result<Secret<Vec<u8>>> {
316317
let dpapi_blob = DpapiBlob::decode(blob)?;
317318
let target_sd = dpapi_blob.protection_descriptor.get_target_sd()?;
318319

@@ -330,7 +331,7 @@ pub fn n_crypt_unprotect_secret(
330331

331332
info!("Successfully requested root key.");
332333

333-
decrypt_blob(&dpapi_blob, &root_key)
334+
Ok(decrypt_blob(&dpapi_blob, &root_key)?.into())
334335
}
335336

336337
/// Encrypts data to a specified protection descriptor.
@@ -341,8 +342,9 @@ pub fn n_crypt_unprotect_secret(
341342
///
342343
/// MSDN:
343344
/// * [NCryptProtectSecret function (`ncryptprotect.h`)](https://learn.microsoft.com/en-us/windows/win32/api/ncryptprotect/nf-ncryptprotect-ncryptprotectsecret).
345+
#[instrument(ret)]
344346
pub fn n_crypt_protect_secret(
345-
data: &[u8],
347+
data: Secret<Vec<u8>>,
346348
sid: String,
347349
root_key_id: Option<Uuid>,
348350
server: &str,
@@ -371,5 +373,5 @@ pub fn n_crypt_protect_secret(
371373

372374
info!("Successfully requested root key.");
373375

374-
encrypt_blob(data, &root_key, descriptor)
376+
encrypt_blob(data.as_ref(), &root_key, descriptor)
375377
}

0 commit comments

Comments
 (0)