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}