clients::rdp connects over any caller-dialled TCP stream (direct, proxy or SSH tunnel), upgrades to TLS and logs on with NLA (CredSSP/NTLM), optionally falling back to TLS-only logon for servers without NLA such as xrdp (credentials are passed so they log straight in). The server certificate is pinned through a caller check on its SHA256 fingerprint before any credentials are sent. The desktop decodes into the VNC client's RGBA framebuffer; input (IronRDP's scancode/Unicode/mouse operations), Display Control resizes and the deactivation-reactivation sequence run on the session thread. IronRDP's releases need three vendored patches (vendor/README.md): picky and sspi with their RustCrypto release-candidate pins relaxed so they share russh's stable versions (their own suites, 402 tests, pass against them), and ironrdp-session with two unreleased upstream bitmap fixes (#1486 stride, #1524 RDP6 orientation) without which xrdp desktops render sheared. Verified live against xrdp (scottyhardy/docker-remote-desktop): logon, full desktop rendering, keyboard and mouse input.
171 lines
5.9 KiB
Rust
171 lines
5.9 KiB
Rust
use crate::pkcs12::{Pkcs12CryptoContext, Pkcs12Encryption, Pkcs12Error, Pkcs12ParsingParams, SafeBag};
|
|
use picky_asn1::wrapper::OctetStringAsn1;
|
|
use picky_asn1_x509::pkcs12::{
|
|
EncryptedSafeContents as EncryptedSafeContentsAsn1, SafeContents as SafeContentsAsn1,
|
|
SafeContentsContentInfo as SafeContentsContentInfoAsn1,
|
|
};
|
|
|
|
/// Top-level PFX container object, which holds list of safe bags and could be encrypted or not.
|
|
#[derive(Debug, Clone)]
|
|
pub struct SafeContents {
|
|
kind: SafeContentsKind,
|
|
inner: SafeContentsContentInfoAsn1,
|
|
}
|
|
|
|
impl SafeContents {
|
|
pub(crate) fn from_asn1(
|
|
inner: SafeContentsContentInfoAsn1,
|
|
crypto_context: &Pkcs12CryptoContext,
|
|
parsing_params: &Pkcs12ParsingParams,
|
|
) -> Result<Self, Pkcs12Error> {
|
|
let to_unparsed = |inner| Self {
|
|
kind: SafeContentsKind::Unknown,
|
|
inner,
|
|
};
|
|
|
|
let kind = match &inner {
|
|
SafeContentsContentInfoAsn1::Data(data) => {
|
|
let safe_bags = data
|
|
.0
|
|
.iter()
|
|
.map(|sb| SafeBag::from_asn1(sb.clone(), crypto_context, parsing_params))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Self {
|
|
kind: SafeContentsKind::SafeBags(safe_bags),
|
|
inner,
|
|
}
|
|
}
|
|
SafeContentsContentInfoAsn1::EncryptedData(encrypted) => {
|
|
let encryption = match Pkcs12Encryption::from_asn1(encrypted.algorithm.clone()) {
|
|
Ok(encryption) => encryption,
|
|
Err(_) if parsing_params.skip_decryption_errors => {
|
|
return Ok(to_unparsed(inner));
|
|
}
|
|
Err(e) => {
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
let encrypted_content = match encrypted.encrypted_content.as_ref() {
|
|
Some(content) => content.0.as_slice(),
|
|
None => {
|
|
return Ok(Self {
|
|
// No content to decrypt
|
|
kind: SafeContentsKind::EncryptedSafeBags {
|
|
encryption,
|
|
safe_bags: vec![],
|
|
},
|
|
inner,
|
|
});
|
|
}
|
|
};
|
|
|
|
let decrypted = match encryption.decrypt(encrypted_content, crypto_context) {
|
|
Ok(decrypted) => decrypted,
|
|
Err(_) if parsing_params.skip_decryption_errors => {
|
|
return Ok(to_unparsed(inner));
|
|
}
|
|
Err(e) => {
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
let safe_bags_asn1 = match picky_asn1_der::from_bytes::<SafeContentsAsn1>(&decrypted) {
|
|
Ok(safe_contents) => safe_contents.0,
|
|
Err(_) if parsing_params.skip_decryption_errors => {
|
|
return Ok(to_unparsed(inner));
|
|
}
|
|
Err(e) => {
|
|
return Err(e.into());
|
|
}
|
|
};
|
|
|
|
let safe_bags = safe_bags_asn1
|
|
.into_iter()
|
|
.map(|sb| SafeBag::from_asn1(sb, crypto_context, parsing_params))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
Self {
|
|
kind: SafeContentsKind::EncryptedSafeBags { encryption, safe_bags },
|
|
inner,
|
|
}
|
|
}
|
|
SafeContentsContentInfoAsn1::Unknown { .. } => return Ok(to_unparsed(inner)),
|
|
};
|
|
|
|
Ok(kind)
|
|
}
|
|
|
|
pub fn new(safe_bags: Vec<SafeBag>) -> Self {
|
|
let safe_contents = SafeContentsAsn1(safe_bags.iter().map(|sb| sb.inner().clone()).collect());
|
|
Self {
|
|
kind: SafeContentsKind::SafeBags(safe_bags),
|
|
inner: SafeContentsContentInfoAsn1::Data(safe_contents),
|
|
}
|
|
}
|
|
|
|
pub fn new_encrypted(
|
|
safe_bags: Vec<SafeBag>,
|
|
encryption: Pkcs12Encryption,
|
|
crypto_context: &Pkcs12CryptoContext,
|
|
) -> Result<Self, Pkcs12Error> {
|
|
let safe_contents = SafeContentsAsn1(safe_bags.iter().map(|sb| sb.inner().clone()).collect());
|
|
let der_data = picky_asn1_der::to_vec(&safe_contents)?;
|
|
let encrypted = encryption.encrypt(&der_data, crypto_context)?;
|
|
|
|
let inner = SafeContentsContentInfoAsn1::EncryptedData(EncryptedSafeContentsAsn1 {
|
|
algorithm: encryption.inner().clone(),
|
|
encrypted_content: Some(OctetStringAsn1(encrypted)),
|
|
});
|
|
|
|
Ok(Self {
|
|
kind: SafeContentsKind::EncryptedSafeBags { encryption, safe_bags },
|
|
inner,
|
|
})
|
|
}
|
|
|
|
pub fn kind(&self) -> &SafeContentsKind {
|
|
&self.kind
|
|
}
|
|
|
|
pub fn into_kind(self) -> SafeContentsKind {
|
|
self.kind
|
|
}
|
|
|
|
pub fn inner(&self) -> &SafeContentsContentInfoAsn1 {
|
|
&self.inner
|
|
}
|
|
|
|
pub fn into_inner(self) -> SafeContentsContentInfoAsn1 {
|
|
self.inner
|
|
}
|
|
}
|
|
|
|
// Clippy triggers lint because of relatively big `Pkcs12Encryption` (~200 bytes) in comparison with
|
|
// `SafeContentsKind::Unknown` (0 bytes), but just to keep it consistent with other enums, we do allow
|
|
// such difference in size.
|
|
#[allow(clippy::large_enum_variant)]
|
|
#[derive(Debug)]
|
|
pub enum SafeContentsKind {
|
|
SafeBags(Vec<SafeBag>),
|
|
EncryptedSafeBags {
|
|
encryption: Pkcs12Encryption,
|
|
safe_bags: Vec<SafeBag>,
|
|
},
|
|
Unknown,
|
|
}
|
|
|
|
impl Clone for SafeContentsKind {
|
|
fn clone(&self) -> Self {
|
|
match self {
|
|
Self::SafeBags(bags) => Self::SafeBags(bags.clone()),
|
|
Self::EncryptedSafeBags { encryption, safe_bags } => Self::EncryptedSafeBags {
|
|
encryption: encryption.duplicate(),
|
|
safe_bags: safe_bags.clone(),
|
|
},
|
|
Self::Unknown => Self::Unknown,
|
|
}
|
|
}
|
|
}
|