rdp/nla/
cssp.rs

1use nla::asn1::{ASN1, Sequence, ExplicitTag, SequenceOf, ASN1Type, OctetString, Integer, to_der};
2use model::error::{RdpError, RdpErrorKind, Error, RdpResult};
3use num_bigint::{BigUint};
4use yasna::Tag;
5use x509_parser::{parse_x509_der, X509Certificate};
6use nla::sspi::AuthenticationProtocol;
7use model::link::Link;
8use std::io::{Read, Write};
9
10/// Create a ts request as expected by the specification
11/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/6aac4dea-08ef-47a6-8747-22ea7f6d8685?redirectedfrom=MSDN
12///
13/// This is the first payload sent from client to server
14///
15/// # Example
16/// ```
17/// use rdp::nla::cssp::create_ts_request;
18/// let payload = create_ts_request(vec![0, 1, 2]);
19/// assert_eq!(payload, [48, 18, 160, 3, 2, 1, 2, 161, 11, 48, 9, 48, 7, 160, 5, 4, 3, 0, 1, 2])
20/// ```
21pub fn create_ts_request(nego: Vec<u8>) -> Vec<u8> {
22    let ts_request = sequence![
23        "version" => ExplicitTag::new(Tag::context(0), 2 as Integer),
24        "negoTokens" => ExplicitTag::new(Tag::context(1),
25            sequence_of![
26                sequence![
27                    "negoToken" => ExplicitTag::new(Tag::context(0), nego)
28                ]
29            ])
30    ];
31    to_der(&ts_request)
32}
33
34/// This is the second step in CSSP handshake
35/// this is the challenge message from server to client
36///
37/// This function will parse the request and extract the negoToken
38/// which use as payload field
39///
40/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/6aac4dea-08ef-47a6-8747-22ea7f6d8685?redirectedfrom=MSDN
41/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/9664994d-0784-4659-b85b-83b8d54c2336
42/// 
43/// # Example
44/// ```
45/// use rdp::nla::cssp::read_ts_server_challenge;
46/// let challenge = [48, 18, 160, 3, 2, 1, 2, 161, 11, 48, 9, 48, 7, 160, 5, 4, 3, 0, 1, 2];
47/// let payload = read_ts_server_challenge(&challenge).unwrap();
48/// assert_eq!(payload, [0, 1, 2])
49/// ```
50pub fn read_ts_server_challenge(stream: &[u8]) -> RdpResult<Vec<u8>> {
51    let mut ts_request = sequence![
52        "version" => ExplicitTag::new(Tag::context(0), 2 as Integer),
53        "negoTokens" => ExplicitTag::new(Tag::context(1),
54            SequenceOf::reader(|| {
55                Box::new(sequence![
56                    "negoToken" => ExplicitTag::new(Tag::context(0), OctetString::new())
57                ])
58            })
59         )
60    ];
61
62    yasna::parse_der(stream, |reader| {
63        if let Err(Error::ASN1Error(e)) = ts_request.read_asn1(reader) {
64            return Err(e)
65        }
66        Ok(())
67    })?;
68
69    let nego_tokens = cast!(ASN1Type::SequenceOf, ts_request["negoTokens"]).unwrap();
70    let first_nego_tokens = cast!(ASN1Type::Sequence, nego_tokens.inner[0]).unwrap();
71    let nego_token = cast!(ASN1Type::OctetString, first_nego_tokens["negoToken"]).unwrap();
72    Ok(nego_token.to_vec())
73}
74
75/// This the third step in CSSP Handshake
76/// Send the pubKey of server encoded with negotiated key
77/// to protect agains MITM attack
78///
79/// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/6aac4dea-08ef-47a6-8747-22ea7f6d8685?redirectedfrom=MSDN
80///
81/// # Example
82/// ```
83/// use rdp::nla::cssp::create_ts_authenticate;
84/// let payload = create_ts_authenticate(vec![0, 1, 2], vec![0, 1, 2]);
85/// assert_eq!(payload, [48, 25, 160, 3, 2, 1, 2, 161, 11, 48, 9, 48, 7, 160, 5, 4, 3, 0, 1, 2, 163, 5, 4, 3, 0, 1, 2])
86/// ```
87pub fn create_ts_authenticate(nego: Vec<u8>, pub_key_auth: Vec<u8>) -> Vec<u8> {
88    let ts_challenge = sequence![
89        "version" => ExplicitTag::new(Tag::context(0), 2 as Integer),
90        "negoTokens" => ExplicitTag::new(Tag::context(1),
91            sequence_of![
92                sequence![
93                    "negoToken" => ExplicitTag::new(Tag::context(0), nego as OctetString)
94                ]
95            ]),
96        "pubKeyAuth" => ExplicitTag::new(Tag::context(3), pub_key_auth as OctetString)
97    ];
98
99    to_der(&ts_challenge)
100}
101
102pub fn read_public_certificate(stream: &[u8]) -> RdpResult<X509Certificate> {
103    let res = parse_x509_der(stream).unwrap();
104    Ok(res.1)
105}
106
107/// read ts validate
108/// This is the last step in cssp handshake
109/// Server must send its public key incremented by one
110/// and cyphered with the authentication protocol
111/// Parse the ts message and extract the public key
112///
113/// # Example
114/// ```
115/// use rdp::nla::cssp::read_ts_validate;
116/// let pub_key = read_ts_validate(&[48, 12, 160, 3, 2, 1, 2, 163, 5, 4, 3, 0, 1, 2]).unwrap();
117/// assert_eq!(pub_key, [0, 1, 2])
118/// ```
119pub fn read_ts_validate(request: &[u8]) -> RdpResult<Vec<u8>> {
120    let mut ts_challenge = sequence![
121        "version" => ExplicitTag::new(Tag::context(0), 2 as Integer),
122        "pubKeyAuth" => ExplicitTag::new(Tag::context(3), OctetString::new())
123    ];
124
125    yasna::parse_der(request, |reader| {
126        if let Err(Error::ASN1Error(e)) = ts_challenge.read_asn1(reader) {
127            return Err(e)
128        }
129        Ok(())
130    })?;
131    let pubkey = cast!(ASN1Type::OctetString, ts_challenge["pubKeyAuth"])?;
132    Ok(pubkey.to_vec())
133}
134
135fn create_ts_credentials(domain: Vec<u8>, user: Vec<u8>, password: Vec<u8>) -> Vec<u8> {
136    let ts_password_creds = sequence![
137        "domainName" => ExplicitTag::new(Tag::context(0), domain as OctetString),
138        "userName" => ExplicitTag::new(Tag::context(1), user as OctetString),
139        "password" => ExplicitTag::new(Tag::context(2), password as OctetString)
140    ];
141
142    let ts_password_cred_encoded = yasna::construct_der(|writer| {
143        ts_password_creds.write_asn1(writer).unwrap();
144    });
145
146    let ts_credentials = sequence![
147        "credType" => ExplicitTag::new(Tag::context(0), 1 as Integer),
148        "credentials" => ExplicitTag::new(Tag::context(1), ts_password_cred_encoded as OctetString)
149    ];
150
151    to_der(&ts_credentials)
152}
153
154fn create_ts_authinfo(auth_info: Vec<u8>) -> Vec<u8> {
155    let ts_authinfo = sequence![
156        "version" => ExplicitTag::new(Tag::context(0), 2 as Integer),
157        "authInfo" => ExplicitTag::new(Tag::context(2), auth_info)
158    ];
159
160    to_der(&ts_authinfo)
161}
162
163/// This the main function for CSSP protocol
164/// It will use the raw link layer and the selected authenticate protocol
165/// to perform the NLA authenticate
166pub fn cssp_connect<S: Read + Write>(link: &mut Link<S>, authentication_protocol: &mut dyn AuthenticationProtocol, restricted_admin_mode: bool) -> RdpResult<()> {
167    // first step is to send the negotiate message from authentication protocol
168    let negotiate_message = create_ts_request(authentication_protocol.create_negotiate_message()?);
169    link.write(&negotiate_message)?;
170
171    // now receive server challenge
172    let server_challenge = read_ts_server_challenge(&(link.read(0)?))?;
173
174    // now ask for to authenticate protocol
175    let client_challenge = authentication_protocol.read_challenge_message(&server_challenge)?;
176
177    // now we need to build the security interface for auth protocol
178    let mut security_interface = authentication_protocol.build_security_interface();
179
180    // Get the peer public certificate
181    let certificate_der = try_option!(link.get_peer_certificate()?, "No public certificate available")?.to_der()?;
182    let certificate = read_public_certificate(&certificate_der)?;
183
184    // Now we can send back our challenge payload wit the public key encoded
185    let challenge = create_ts_authenticate(client_challenge, security_interface.gss_wrapex(certificate.tbs_certificate.subject_pki.subject_public_key.data)?);
186    link.write(&challenge)?;
187
188    // now server respond normally with the original public key incremented by one
189    let inc_pub_key = security_interface.gss_unwrapex(&(read_ts_validate(&(link.read(0)?))?))?;
190
191    // Check possible man in the middle using cssp
192    if BigUint::from_bytes_le(&inc_pub_key) != BigUint::from_bytes_le(certificate.tbs_certificate.subject_pki.subject_public_key.data) + BigUint::new(vec![1]) {
193        return Err(Error::RdpError(RdpError::new(RdpErrorKind::PossibleMITM, "Man in the middle detected")))
194    }
195
196    // compute the last message with encoded credentials
197
198    let domain = if restricted_admin_mode { vec![] } else { authentication_protocol.get_domain_name()};
199    let user = if restricted_admin_mode { vec![] } else { authentication_protocol.get_user_name() };
200    let password = if restricted_admin_mode { vec![] } else { authentication_protocol.get_password() };
201
202    let credentials = create_ts_authinfo(security_interface.gss_wrapex(&create_ts_credentials(domain, user, password))?);
203    link.write(&credentials)?;
204
205    Ok(())
206}
207
208#[cfg(test)]
209mod test {
210    use super::*;
211
212    #[test]
213    fn test_create_ts_credentials() {
214        let credentials = create_ts_credentials(b"domain".to_vec(), b"user".to_vec(), b"password".to_vec());
215        let result =  [48, 41, 160, 3, 2, 1, 1, 161, 34, 4, 32, 48, 30, 160, 8, 4, 6, 100, 111, 109, 97, 105, 110, 161, 6, 4, 4, 117, 115, 101, 114, 162, 10, 4, 8, 112, 97, 115, 115, 119, 111, 114, 100];
216        assert_eq!(credentials[0..32], result[0..32]);
217        assert_eq!(credentials[33..43], result[33..43]);
218    }
219
220    #[test]
221    fn test_create_ts_authinfo() {
222        assert_eq!(create_ts_authinfo(b"foo".to_vec()), [48, 12, 160, 3, 2, 1, 2, 162, 5, 4, 3, 102, 111, 111])
223    }
224}