Files
nexus/rust/vendor/sspi/src/negotiate/generators.rs
T
Andy Dixon 50c0104d57 feat(core): RDP client on IronRDP
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.
2026-10-12 10:50:24 +01:00

102 lines
3.8 KiB
Rust

use oid::ObjectIdentifier;
use picky::oids;
use picky_asn1::wrapper::{
Asn1SequenceOf, ExplicitContextTag0, ExplicitContextTag1, ExplicitContextTag2, ExplicitContextTag3,
ObjectIdentifierAsn1, OctetStringAsn1, Optional,
};
use picky_asn1_der::Asn1RawDer;
use picky_krb::constants::gss_api::ACCEPT_INCOMPLETE;
use picky_krb::gss_api::{
ApplicationTag0, GssApiNegInit, MechType, MechTypeList, NegTokenInit, NegTokenTarg, NegTokenTarg1,
};
use crate::{Error, ErrorKind, Result};
/// Generates supported mechanism type list.
pub(super) fn generate_mech_type_list(kerberos: bool, ntlm: bool) -> Result<MechTypeList> {
if !ntlm && !kerberos {
return Err(Error::new(
ErrorKind::InvalidParameter,
"NTLM and Kerberos are not enabled. no security packages available",
));
}
let mut mech_types = Vec::new();
if kerberos {
mech_types.push(MechType::from(oids::ms_krb5()));
mech_types.push(MechType::from(oids::krb5()));
// NEGOEX is not supported.
// mech_types.push(MechType::from(oids::negoex()));
}
if ntlm {
mech_types.push(MechType::from(oids::ntlm_ssp()));
}
Ok(MechTypeList::from(Asn1SequenceOf::from(mech_types)))
}
/// Generates the initial SPNEGO token.
///
/// The `sname` parameter is optional. If it is present, then the Kerberos U2U is in use, and `TgtReq` will be generated
/// for the input `sname` and placed in the `mech_token` field.
pub(super) fn generate_neg_token_init(
mech_list: MechTypeList,
mech_token: Option<Vec<u8>>,
) -> Result<ApplicationTag0<GssApiNegInit>> {
let mech_token = mech_token.map(|token| ExplicitContextTag2::from(OctetStringAsn1::from(token)));
Ok(ApplicationTag0(GssApiNegInit {
oid: ObjectIdentifierAsn1::from(oids::spnego()),
neg_token_init: ExplicitContextTag0::from(NegTokenInit {
mech_types: Optional::from(Some(ExplicitContextTag0::from(mech_list))),
req_flags: Optional::from(None),
mech_token: Optional::from(mech_token),
mech_list_mic: Optional::from(None),
}),
}))
}
pub(super) fn generate_neg_token_targ_1(response_token: Option<Vec<u8>>) -> NegTokenTarg1 {
NegTokenTarg1::from(NegTokenTarg {
neg_result: Optional::from(Some(ExplicitContextTag0::from(Asn1RawDer(ACCEPT_INCOMPLETE.to_vec())))),
supported_mech: Optional::from(None),
response_token: Optional::from(
response_token.map(|token| ExplicitContextTag2::from(OctetStringAsn1::from(token))),
),
mech_list_mic: Optional::from(None),
})
}
pub(super) fn generate_final_neg_token_targ(
neg_result: Vec<u8>,
response_token: Option<Vec<u8>>,
mech_list_mic: Option<Vec<u8>>,
) -> NegTokenTarg1 {
NegTokenTarg1::from(NegTokenTarg {
neg_result: Optional::from(Some(ExplicitContextTag0::from(Asn1RawDer(neg_result)))),
supported_mech: Optional::from(None),
response_token: Optional::from(
response_token.map(|token| ExplicitContextTag2::from(OctetStringAsn1::from(token))),
),
mech_list_mic: Optional::from(mech_list_mic.map(|v| ExplicitContextTag3::from(OctetStringAsn1::from(v)))),
})
}
pub(super) fn generate_neg_token_targ(
neg_result: Vec<u8>,
mech_type: ObjectIdentifier,
response_token: Option<Vec<u8>>,
) -> Result<NegTokenTarg1> {
let response_token =
response_token.map(|response_token| ExplicitContextTag2::from(OctetStringAsn1::from(response_token)));
Ok(NegTokenTarg1::from(NegTokenTarg {
neg_result: Optional::from(Some(ExplicitContextTag0::from(Asn1RawDer(neg_result)))),
supported_mech: Optional::from(Some(ExplicitContextTag1::from(MechType::from(mech_type)))),
response_token: Optional::from(response_token),
mech_list_mic: Optional::from(None),
}))
}