use crate::ndr::{NdrDecoder, NdrEncoder};
use crate::transport::SmbPipe;
use crate::{Result, RpcError, Syntax};
use smb2_client::SmbClient;
pub fn icpr_syntax() -> Syntax {
Syntax::new("91ae6020-9e3c-11cf-8d7c-00aa00c091be", 0, 0)
}
const CERT_SERVER_REQUEST: u16 = 0;
pub struct EnrollResult {
pub disposition: u32,
pub cert_der: Vec<u8>,
pub message: String,
}
fn utf16z(s: &str) -> Vec<u8> {
let mut v: Vec<u8> = s.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
v.extend_from_slice(&[0, 0]);
v
}
pub fn utf16z_string(s: &str) -> Vec<u8> {
utf16z(s)
}
pub const CERT_SERVER_REQUEST_OPNUM: u16 = CERT_SERVER_REQUEST;
pub fn encode_cert_server_request(authority: &str, template: &str, csr_der: &[u8]) -> Vec<u8> {
let attribs = utf16z(&format!("CertificateTemplate:{template}"));
encode_request(authority, &attribs, csr_der)
}
pub fn decode_cert_server_response(stub: &[u8]) -> Result<EnrollResult> {
let mut d = NdrDecoder::new(stub);
let _request_id = d.u32()?;
let disposition = d.u32()?;
let _chain = read_blob(&mut d)?;
let cert_der = read_blob(&mut d)?;
let msg_raw = read_blob(&mut d)?;
let message = String::from_utf16_lossy(
&msg_raw
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect::<Vec<_>>(),
)
.trim_end_matches('\0')
.to_string();
Ok(EnrollResult {
disposition,
cert_der,
message,
})
}
fn encode_request(authority: &str, attribs: &[u8], request: &[u8]) -> Vec<u8> {
let mut e = NdrEncoder::new();
e.u32(0); e.referent(); e.conformant_varying_wstr(authority); e.align(4);
e.u32(0); e.u32(attribs.len() as u32);
e.referent();
e.u32(attribs.len() as u32); e.bytes(attribs);
e.align(4);
e.u32(request.len() as u32);
e.referent();
e.u32(request.len() as u32);
e.bytes(request);
e.align(4);
e.into_bytes()
}
fn read_blob(d: &mut NdrDecoder) -> Result<Vec<u8>> {
let cb = d.u32()? as usize;
let ptr = d.u32()?;
if ptr == 0 || cb == 0 {
return Ok(Vec::new());
}
let _max = d.u32()?;
let v = d.read_bytes(cb)?.to_vec();
d.align(4);
Ok(v)
}
#[allow(clippy::too_many_arguments)]
pub async fn request_cert(
client: &mut SmbClient,
authority: &str,
template: &str,
csr_der: &[u8],
domain: &str,
user: &str,
password: &str,
host: &str,
) -> Result<EnrollResult> {
let file_id = client
.open_pipe("cert")
.await
.map_err(|e| RpcError::Protocol(format!("open \\cert: {e}")))?;
let mut pipe = SmbPipe::new(client, file_id);
pipe.bind_sealed(icpr_syntax(), domain, user, password, host)
.await?;
let attribs = utf16z(&format!("CertificateTemplate:{template}"));
let stub = encode_request(authority, &attribs, csr_der);
let resp = pipe.call_sealed(CERT_SERVER_REQUEST, &stub).await?;
let mut d = NdrDecoder::new(&resp);
let _request_id = d.u32()?;
let disposition = d.u32()?;
let _chain = read_blob(&mut d)?; let cert_der = read_blob(&mut d)?; let msg_raw = read_blob(&mut d)?; let message = String::from_utf16_lossy(
&msg_raw
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect::<Vec<_>>(),
)
.trim_end_matches('\0')
.to_string();
Ok(EnrollResult {
disposition,
cert_der,
message,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_stub_matches_ms_icpr_layout() {
let s = encode_request(
"corp-CA-01",
&utf16z("CertificateTemplate:VulnUser"),
&(0..20).collect::<Vec<u8>>(),
);
assert_eq!(
s.len(),
152,
"stub length must match the MS-ICPR wire format"
);
assert_eq!(u32::from_le_bytes(s[0..4].try_into().unwrap()), 0); assert_ne!(u32::from_le_bytes(s[4..8].try_into().unwrap()), 0); assert_eq!(u32::from_le_bytes(s[8..12].try_into().unwrap()), 11); assert_eq!(u32::from_le_bytes(s[48..52].try_into().unwrap()), 58); }
#[test]
fn utf16z_has_terminator() {
let v = utf16z("A");
assert_eq!(v, vec![0x41, 0x00, 0x00, 0x00]);
}
}