gss_api/negotiation.rs
1//! Negotiation-related types
2use der::{
3 asn1::{BitString, OctetStringRef},
4 AnyRef, Choice, Enumerated, Sequence,
5};
6
7use crate::MechType;
8
9/// The `MechTypeList` type is defined in [RFC 4178 Section 4.1].
10///
11/// ```text
12/// MechTypeList ::= SEQUENCE OF MechType
13/// ```
14///
15/// [RFC 4178 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc4178#section-4.1
16pub type MechTypeList = alloc::vec::Vec<MechType>;
17
18/// `NegotiationToken` as defined in [RFC 4178 Section 4.2].
19///
20/// ```text
21/// NegotiationToken ::= CHOICE {
22/// negTokenInit [0] NegTokenInit,
23/// negTokenResp [1] NegTokenResp
24/// }
25/// ```
26///
27/// [RFC 4178 Section 4.2]: https://datatracker.ietf.org/doc/html/rfc4178#section-4.2
28#[derive(Clone, Debug, PartialEq, Eq, Choice)]
29pub enum NegotiationToken<'a> {
30 /// This is the inner token of the initial negotiation message.
31 #[cfg(feature = "rfc2478")]
32 #[asn1(context_specific = "0", constructed = "true", tag_mode = "EXPLICIT")]
33 NegTokenInit(NegTokenInit<'a>),
34 /// The NegTokenInit2 message extends NegTokenInit with a negotiation hints (negHints) field.
35 #[cfg(not(feature = "rfc2478"))]
36 #[asn1(context_specific = "0", constructed = "true", tag_mode = "EXPLICIT")]
37 NegTokenInit2(NegTokenInit2<'a>),
38 /// Negotiation token returned by the target to the initiator which
39 /// contains, for the first token returned, a global negotiation result
40 /// and the security mechanism selected (if any).
41 #[cfg(feature = "rfc2478")]
42 #[asn1(context_specific = "1", constructed = "true", tag_mode = "EXPLICIT")]
43 NegTokenTarg(NegTokenTarg<'a>),
44 /// This is the token for all subsequent negotiation messages.
45 #[cfg(not(feature = "rfc2478"))]
46 #[asn1(context_specific = "1", constructed = "true", tag_mode = "EXPLICIT")]
47 NegTokenResp(NegTokenResp<'a>),
48}
49
50/// `NegTokenInit` as defined in [RFC 4178 Section 4.2.1].
51///
52/// ```text
53/// NegTokenInit ::= SEQUENCE {
54/// mechTypes [0] MechTypeList,
55/// reqFlags [1] ContextFlags OPTIONAL,
56/// -- inherited from RFC 2478 for backward compatibility,
57/// -- RECOMMENDED to be left out
58/// mechToken [2] OCTET STRING OPTIONAL,
59/// mechListMIC [3] OCTET STRING OPTIONAL,
60/// ...
61/// }
62/// ```
63///
64/// [RFC 4178 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.1
65#[cfg(feature = "rfc2478")]
66#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
67pub struct NegTokenInit<'a> {
68 /// This field contains one or more security mechanisms available for
69 /// the initiator, in decreasing preference order (favorite choice
70 /// first).
71 #[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")]
72 pub mech_types: Option<MechTypeList>,
73
74 /// This field, if present, contains the service options that are
75 /// requested to establish the context (the req_flags parameter of
76 /// GSS_Init_sec_context()). This field is inherited from RFC 2478
77 /// and is not integrity protected. For implementations of this
78 /// specification, the initiator SHOULD omit this reqFlags field and
79 /// the acceptor MUST ignore this reqFlags field.
80 ///
81 /// The size constraint on the ContextFlags ASN.1 type only applies to
82 /// the abstract type. The ASN.1 DER requires that all trailing zero
83 /// bits be truncated from the encoding of a bit string type whose
84 /// abstract definition includes named bits. Implementations should
85 /// not expect to receive exactly 32 bits in an encoding of
86 /// ContextFlags.
87 #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
88 pub req_flags: Option<ContextFlags>,
89
90 /// This field, if present, contains the optimistic mechanism token.
91 #[asn1(context_specific = "2", optional = "true", tag_mode = "IMPLICIT")]
92 pub mech_token: Option<OctetStringRef<'a>>,
93
94 /// This field, if present, contains an MIC token for the mechanism
95 /// list in the initial negotiation message. This MIC token is
96 /// computed according to Section 5.
97 #[asn1(context_specific = "3", optional = "true", tag_mode = "IMPLICIT")]
98 pub mech_list_mic: Option<OctetStringRef<'a>>,
99}
100
101/// `ContextFlags` as defined in [RFC 4178 Section 4.2.1].
102///
103/// ```text
104/// ContextFlags ::= BIT STRING {
105/// delegFlag (0),
106/// mutualFlag (1),
107/// replayFlag (2),
108/// sequenceFlag (3),
109/// anonFlag (4),
110/// confFlag (5),
111/// integFlag (6)
112/// } (SIZE (32))
113/// ```
114///
115/// [RFC 4178 Section 4.2.1]: https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.1
116pub type ContextFlags = BitString;
117
118/// `NegTokenTarg` as defined in [RFC 2479 Section 3.2.1].
119///
120/// ```text
121/// NegTokenTarg ::= SEQUENCE {
122/// negResult [0] ENUMERATED {
123/// accept_completed (0),
124/// accept_incomplete (1),
125/// reject (2) } OPTIONAL,
126/// supportedMech [1] MechType OPTIONAL,
127/// responseToken [2] OCTET STRING OPTIONAL,
128/// mechListMIC [3] OCTET STRING OPTIONAL
129/// }
130/// ```
131///
132/// [RFC 2479 Section 3.2.1]: https://datatracker.ietf.org/doc/html/rfc2478#section-3.2.1
133#[cfg(feature = "rfc2478")]
134#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)]
135pub struct NegTokenTarg<'a> {
136 /// The result accept_completed indicates that a context has been
137 /// successfully established, while the result accept_incomplete
138 /// indicates that additional token exchanges are needed.
139 ///
140 /// Note:: For the case where (a) a single-token context setup is
141 /// used and (b) the preferred mechanism does not support the
142 /// integrity facility which would cause a mechListMIC to be
143 /// generated and enclosed, this feature allows to make a
144 /// difference between a mechToken sent by the initiator but not
145 /// processed by the target (accept_incomplete) and a mechToken
146 /// sent by the initiator and processed by the target
147 /// (accept_completed).
148 ///
149 // For those targets that support piggybacking the initial mechToken,
150 // an optimistic negotiation response is possible and includes in that
151 // case a responseToken which may continue the authentication exchange
152 // (e.g. when mutual authentication has been requested or when
153 // unilateral authentication requires several round trips). Otherwise
154 // the responseToken is used to carry the tokens specific to the
155 // mechanism selected. For subsequent tokens (if any) returned by the
156 // target, negResult, and supportedMech are not present.
157
158 // For the last token returned by the target, the mechListMIC, when
159 // present, is a MIC computed over the MechTypes using the selected
160 // mechanism.
161 #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
162 pub neg_result: Option<NegResult>,
163
164 /// This field has to be present when negResult is "accept_completed"
165 /// or "accept_incomplete". It is a choice from the mechanisms offered
166 /// by the initiator.
167 #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")]
168 pub supported_mech: Option<MechType>,
169
170 /// This field may be used either to transmit the response to the
171 /// mechToken when sent by the initiator and when the first mechanism
172 /// from the list has been selected by the target or to carry the
173 /// tokens specific to the selected security mechanism.
174 #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")]
175 pub response_token: Option<OctetStringRef<'a>>,
176
177 /// If the selected mechanism is capable of integrity protection, this
178 /// field must be present in the last message of the negotiation,
179 /// (i.e., when the underlying mechanism returns a non-empty token and
180 /// a major status of GSS_S_COMPLETE); it contains the result of a
181 /// GetMIC of the MechTypes field in the initial NegTokenInit. It
182 /// allows to verify that the list initially sent by the initiator has
183 /// been received unmodified by the target.
184 #[asn1(context_specific = "3", optional = "true", tag_mode = "EXPLICIT")]
185 pub mech_list_mic: Option<OctetStringRef<'a>>,
186}
187
188/// `NegResult` as defined in [RFC 2479 Section 3.2.1].
189///
190/// ```text
191/// NegTokenTarg ::= SEQUENCE {
192/// negResult [0] ENUMERATED {
193/// accept_completed (0),
194/// accept_incomplete (1),
195/// reject (2) } OPTIONAL,
196/// supportedMech [1] MechType OPTIONAL,
197/// responseToken [2] OCTET STRING OPTIONAL,
198/// mechListMIC [3] OCTET STRING OPTIONAL
199/// }
200/// ```
201///
202/// [RFC 2479 Section 3.2.1]: https://datatracker.ietf.org/doc/html/rfc2478#section-3.2.1
203#[cfg(feature = "rfc2478")]
204#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Enumerated)]
205#[asn1(type = "ENUMERATED")]
206#[repr(u8)]
207#[allow(missing_docs)]
208pub enum NegResult {
209 /// The target accepts the preferred security mechanism, and the context is established for the target.
210 AcceptCompleted = 0,
211 /// The target accepts one of the proposed security mechanisms and further exchanges are necessary.
212 AcceptIncomplete = 1,
213 /// The target rejects all the proposed security mechanisms.
214 Reject = 2,
215}
216
217/// `NegTokenResp` as defined in [RFC 4178 Section 4.2.2].
218///
219/// ```text
220/// NegTokenResp ::= SEQUENCE {
221/// negState [0] ENUMERATED {
222/// accept-completed (0),
223/// accept-incomplete (1),
224/// reject (2),
225/// request-mic (3)
226/// } OPTIONAL,
227/// -- REQUIRED in the first reply from the target
228/// supportedMech [1] MechType OPTIONAL,
229/// -- present only in the first reply from the target
230/// responseToken [2] OCTET STRING OPTIONAL,
231/// mechListMIC [3] OCTET STRING OPTIONAL,
232/// ...
233/// }
234/// ```
235///
236/// [RFC 4178 Section 4.2.2]: https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.2
237#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
238pub struct NegTokenResp<'a> {
239 /// This field is REQUIRED in the first reply from the target, and is
240 /// OPTIONAL thereafter. When negState is absent, the actual state
241 /// should be inferred from the state of the negotiated mechanism
242 /// context.
243 #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
244 pub neg_state: Option<NegState>,
245
246 /// This field SHALL only be present in the first reply from the
247 /// target. It MUST be one of the mechanism(s) offered by the
248 /// initiator.
249 #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")]
250 pub supported_mech: Option<MechType>,
251
252 /// This field, if present, contains tokens specific to the mechanism
253 /// selected.
254 #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")]
255 pub response_token: Option<OctetStringRef<'a>>,
256
257 /// This field, if present, contains an MIC token for the mechanism
258 /// list in the initial negotiation message. This MIC token is
259 /// computed according to Section 5.
260 #[asn1(context_specific = "3", optional = "true", tag_mode = "EXPLICIT")]
261 pub mech_list_mic: Option<OctetStringRef<'a>>,
262}
263
264#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Enumerated)]
265#[asn1(type = "ENUMERATED")]
266#[repr(u8)]
267#[allow(missing_docs)]
268pub enum NegState {
269 /// No further negotiation message from the peer is expected, and
270 /// the security context is established for the sender.
271 AcceptCompleted = 0,
272 /// At least one additional negotiation message from the peer is
273 /// needed to establish the security context.
274 AcceptIncomplete = 1,
275 /// The sender terminates the negotiation.
276 Reject = 2,
277 /// The sender indicates that the exchange of MIC tokens, as
278 /// described in Section 5, will be REQUIRED if per-message
279 /// integrity services are available on the mechanism context to be
280 /// established. This value SHALL only be present in the first
281 /// reply from the target.
282 RequestMic = 3,
283}
284
285/// `NegHints` as defined in [MS-SPNG Section 2.2.1].
286///
287/// ```text
288/// NegHints ::= SEQUENCE {
289/// hintName[0] GeneralString OPTIONAL,
290/// hintAddress[1] OCTET STRING OPTIONAL
291/// }
292/// ```
293///
294/// [MS-SPNG Section 2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/8e71cf53-e867-4b79-b5b5-38c92be3d472
295#[derive(Clone, Copy, Debug, Eq, PartialEq, Sequence)]
296pub struct NegHints<'a> {
297 /// SHOULD<5> contain the string "not_defined_in_RFC4178@please_ignore".
298 /// This is currently `AnyRef` as `GeneralString` is not part of the `der` crate
299 #[asn1(
300 context_specific = "0",
301 optional = "true",
302 tag_mode = "IMPLICIT",
303 constructed = "true"
304 )]
305 pub hint_name: Option<AnyRef<'a>>, // TODO: GeneralString
306
307 /// Never present. MUST be omitted by the sender. Note that the encoding rules, as specified in [X690], require that this structure not be present at all, not just be zero.
308 ///
309 /// [X690]: https://www.itu.int/rec/T-REC-X.690/
310 #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
311 pub hint_address: Option<OctetStringRef<'a>>,
312}
313
314/// `NegTokenInit2` as defined in [MS-SPNG Section 2.2.1].
315///
316/// ```text
317/// NegTokenInit2 ::= SEQUENCE {
318/// mechTypes[0] MechTypeList OPTIONAL,
319/// reqFlags [1] ContextFlags OPTIONAL,
320/// mechToken [2] OCTET STRING OPTIONAL,
321/// negHints [3] NegHints OPTIONAL,
322/// mechListMIC [4] OCTET STRING OPTIONAL,
323/// ...
324/// }
325/// ```
326///
327/// [MS-SPNG Section 2.2.1]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-spng/8e71cf53-e867-4b79-b5b5-38c92be3d472
328#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
329pub struct NegTokenInit2<'a> {
330 /// The list of authentication mechanisms that are available, by OID, as specified in [RFC4178] section 4.1.
331 ///
332 /// [RFC4178]: https://datatracker.ietf.org/doc/html/rfc4178
333 #[asn1(context_specific = "0", optional = "true", tag_mode = "EXPLICIT")]
334 pub mech_types: Option<MechTypeList>,
335
336 /// As specified in [RFC4178] section 4.2.1 This field SHOULD be omitted by the sender.
337 ///
338 /// [RFC4178]: https://datatracker.ietf.org/doc/html/rfc4178
339 #[asn1(context_specific = "1", optional = "true", tag_mode = "EXPLICIT")]
340 pub req_flags: Option<ContextFlags>,
341
342 /// The optimistic mechanism token ([RFC4178] section 4.2.1).
343 ///
344 /// [RFC4178]: https://datatracker.ietf.org/doc/html/rfc4178
345 #[asn1(context_specific = "2", optional = "true", tag_mode = "EXPLICIT")]
346 pub mech_token: Option<OctetStringRef<'a>>,
347
348 /// The server supplies the negotiation hints using a NegHints structure.
349 #[asn1(context_specific = "3", optional = "true", tag_mode = "EXPLICIT")]
350 pub neg_hints: Option<NegHints<'a>>,
351
352 /// The message integrity code (MIC) token ([RFC4178] section 4.2.1).
353 ///
354 /// [RFC4178]: https://datatracker.ietf.org/doc/html/rfc4178
355 #[asn1(context_specific = "4", optional = "true", tag_mode = "EXPLICIT")]
356 pub mech_list_mic: Option<OctetStringRef<'a>>,
357}
358
359#[cfg(test)]
360mod tests {
361 use hex_literal::hex;
362 use spki::ObjectIdentifier;
363
364 use super::*;
365
366 use der::Decode;
367
368 #[test]
369 fn mech_type() {
370 let mech_type_bytes = hex!("060a2b06010401823702020a");
371 let mech_type1 = MechType::from_der(&mech_type_bytes).unwrap();
372 assert_eq!(
373 ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.2.2.10"),
374 mech_type1
375 );
376 }
377
378 #[test]
379 fn token_init() {
380 let neg_token_init_bytes = hex!("303ca00e300c060a2b06010401823702020aa32a3028a0261b246e6f745f646566696e65645f696e5f5246433431373840706c656173655f69676e6f7265");
381 let neg_token = NegTokenInit2::from_der(&neg_token_init_bytes).unwrap();
382 assert_eq!(
383 1,
384 neg_token.mech_types.unwrap().len(),
385 "NegTokenInit2 mech_types len correct"
386 );
387 assert_eq!(
388 b"not_defined_in_RFC4178@please_ignore",
389 &neg_token.neg_hints.unwrap().hint_name.unwrap().value()[2..]
390 );
391 }
392
393 #[test]
394 fn token_response() {
395 let neg_token_resp_bytes = hex!("308199a0030a0101a10c060a2b06010401823702020aa281830481804e544c4d53535000020000000a000a003800000005028a6234805409a0e0e1f900000000000000003e003e0042000000060100000000000f530041004d004200410002000a00530041004d004200410001000a00530041004d00420041000400000003000a00730061006d00620061000700080036739dbd327fd90100000000");
396 let neg_token_resp = NegTokenResp::from_der(&neg_token_resp_bytes).unwrap();
397 assert_eq!(
398 ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.2.2.10"),
399 neg_token_resp.supported_mech.unwrap()
400 );
401 }
402
403 #[cfg(feature = "rfc2478")]
404 #[test]
405 fn decode_rfc2478() {
406 let neg_token_targ_bytes = hex!("308199a0030a0101a10c060a2b06010401823702020aa281830481804e544c4d53535000020000000a000a003800000005028a6234805409a0e0e1f900000000000000003e003e0042000000060100000000000f530041004d004200410002000a00530041004d004200410001000a00530041004d00420041000400000003000a00730061006d00620061000700080036739dbd327fd90100000000");
407 let neg_token_targ = NegTokenTarg::from_der(&neg_token_targ_bytes).unwrap();
408 assert_eq!(
409 NegResult::AcceptIncomplete,
410 neg_token_targ.neg_result.unwrap()
411 );
412 assert_eq!(
413 ObjectIdentifier::new_unwrap("1.3.6.1.4.1.311.2.2.10"),
414 neg_token_targ.supported_mech.unwrap()
415 );
416 }
417}