xref: /freebsd/crypto/krb5/src/tests/gssapi/t_spnego.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  * Copyright 2010  by the Massachusetts Institute of Technology.
4  * All Rights Reserved.
5  *
6  * Export of this software from the United States of America may
7  *   require a specific license from the United States Government.
8  *   It is the responsibility of any person or organization contemplating
9  *   export to obtain such a license before exporting.
10  *
11  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
12  * distribute this software and its documentation for any purpose and
13  * without fee is hereby granted, provided that the above copyright
14  * notice appear in all copies and that both that copyright notice and
15  * this permission notice appear in supporting documentation, and that
16  * the name of M.I.T. not be used in advertising or publicity pertaining
17  * to distribution of the software without specific, written prior
18  * permission.  Furthermore if you modify this software you must label
19  * your software as modified software and not distribute it in such a
20  * fashion that it might be confused with the original M.I.T. software.
21  * M.I.T. makes no representations about the suitability of
22  * this software for any purpose.  It is provided "as is" without express
23  * or implied warranty.
24  *
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <assert.h>
31 
32 #include "common.h"
33 
34 static gss_OID_desc mech_krb5_wrong = {
35     9, "\052\206\110\202\367\022\001\002\002"
36 };
37 gss_OID_set_desc mechset_krb5_wrong = { 1, &mech_krb5_wrong };
38 
39 /*
40  * Test program for SPNEGO and gss_set_neg_mechs
41  *
42  * Example usage:
43  *
44  * kinit testuser
45  * ./t_spnego host/test.host@REALM testhost.keytab
46  */
47 
48 /* Replace *tok and *len with the concatenation of prefix and *tok. */
49 static void
prepend(const void * prefix,size_t plen,uint8_t ** tok,size_t * len)50 prepend(const void *prefix, size_t plen, uint8_t **tok, size_t *len)
51 {
52     uint8_t *newtok;
53 
54     newtok = malloc(plen + *len);
55     assert(newtok != NULL);
56     memcpy(newtok, prefix, plen);
57     memcpy(newtok + plen, *tok, *len);
58     free(*tok);
59     *tok = newtok;
60     *len = plen + *len;
61 }
62 
63 /* Replace *tok and *len with *tok wrapped in a DER tag with the given tag
64  * byte.  *len must be less than 2^16. */
65 static void
der_wrap(uint8_t tag,uint8_t ** tok,size_t * len)66 der_wrap(uint8_t tag, uint8_t **tok, size_t *len)
67 {
68     char lenbuf[3];
69     uint8_t *wrapped;
70     size_t llen;
71 
72     if (*len < 128) {
73         lenbuf[0] = *len;
74         llen = 1;
75     } else if (*len < 256) {
76         lenbuf[0] = 0x81;
77         lenbuf[1] = *len;
78         llen = 2;
79     } else {
80         assert(*len >> 16 == 0);
81         lenbuf[0] = 0x82;
82         lenbuf[1] = *len >> 8;
83         lenbuf[2] = *len & 0xFF;
84         llen = 3;
85     }
86     wrapped = malloc(1 + llen + *len);
87     assert(wrapped != NULL);
88     *wrapped = tag;
89     memcpy(wrapped + 1, lenbuf, llen);
90     memcpy(wrapped + 1 + llen, *tok, *len);
91     free(*tok);
92     *tok = wrapped;
93     *len = 1 + llen + *len;
94 }
95 
96 /*
97  * Create a SPNEGO initiator token for the erroneous Microsoft krb5 mech OID,
98  * wrapping a krb5 token ktok.  The token should look like:
99  *
100  * 60 <len> (GSS framing sequence)
101  *   06 06 2B 06 01 05 05 02 (SPNEGO OID)
102  *   A0 <len> (NegotiationToken choice 0, negTokenInit)
103  *     30 <len> (sequence)
104  *       A0 0D (context tag 0, mechTypes)
105  *         30 0B (sequence of)
106  *           06 09 2A 86 48 82 F7 12 01 02 02 (wrong krb5 OID)
107  *       A2 <len> (context tag 2, mechToken)
108  *         04 <len> (octet string)
109  *           <mech token octets>
110  */
111 static void
create_mskrb5_spnego_token(gss_buffer_t ktok,gss_buffer_desc * tok_out)112 create_mskrb5_spnego_token(gss_buffer_t ktok, gss_buffer_desc *tok_out)
113 {
114     uint8_t *tok;
115     size_t len;
116 
117     len = ktok->length;
118     tok = malloc(len);
119     assert(tok != NULL);
120     memcpy(tok, ktok->value, len);
121     /* Wrap the krb5 token in OCTET STRING and [2] tags. */
122     der_wrap(0x04, &tok, &len);
123     der_wrap(0xA2, &tok, &len);
124     /* Prepend the wrong krb5 OID inside OBJECT IDENTIFIER and [0] tags. */
125     prepend("\xA0\x0D\x30\x0B\x06\x09\x2A\x86\x48\x82\xF7\x12\x01\x02\x02", 15,
126             &tok, &len);
127     /* Wrap the previous two things in SEQUENCE and [0] tags. */
128     der_wrap(0x30, &tok, &len);
129     der_wrap(0xA0, &tok, &len);
130     /* Prepend the SPNEGO OID in an OBJECT IDENTIFIER tag. */
131     prepend("\x06\x06\x2B\x06\x01\x05\x05\x02", 8, &tok, &len);
132     /* Wrap the whole thing in an [APPLICATION 0] tag. */
133     der_wrap(0x60, &tok, &len);
134     tok_out->value = tok;
135     tok_out->length = len;
136 }
137 
138 /*
139  * Test that the SPNEGO acceptor code accepts and properly reflects back the
140  * erroneous Microsoft mech OID in the supportedMech field of the NegTokenResp
141  * message.  Use acred as the verifier cred handle.
142  */
143 static void
test_mskrb_oid(gss_name_t tname,gss_cred_id_t acred)144 test_mskrb_oid(gss_name_t tname, gss_cred_id_t acred)
145 {
146     OM_uint32 major, minor;
147     gss_ctx_id_t ictx = GSS_C_NO_CONTEXT, actx = GSS_C_NO_CONTEXT;
148     gss_buffer_desc atok = GSS_C_EMPTY_BUFFER, ktok = GSS_C_EMPTY_BUFFER, stok;
149     const unsigned char *atok_oid;
150 
151     /*
152      * Our SPNEGO mech no longer acquires creds for the wrong mech OID, so we
153      * have to construct a SPNEGO token ourselves.
154      */
155     major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &ictx, tname,
156                                  &mech_krb5, 0, GSS_C_INDEFINITE,
157                                  GSS_C_NO_CHANNEL_BINDINGS, &atok, NULL, &ktok,
158                                  NULL, NULL);
159     check_gsserr("gss_init_sec_context(mskrb)", major, minor);
160     assert(major == GSS_S_COMPLETE);
161     create_mskrb5_spnego_token(&ktok, &stok);
162 
163     /*
164      * Look directly at the DER encoding of the response token.  Since we
165      * didn't request mutual authentication, the SPNEGO reply will contain no
166      * underlying mech token; therefore, the encoding of the correct
167      * NegotiationToken response is completely predictable:
168      *
169      *   A1 14 (choice 1, length 20, meaning negTokenResp)
170      *     30 12 (sequence, length 18)
171      *       A0 03 (context tag 0, length 3)
172      *         0A 01 00 (enumerated value 0, meaning accept-completed)
173      *       A1 0B (context tag 1, length 11)
174      *         06 09 (object identifier, length 9)
175      *           2A 86 48 82 F7 12 01 02 02 (the erroneous krb5 OID)
176      *
177      * So we can just compare the length to 22 and the nine bytes at offset 13
178      * to the expected OID.
179      */
180     major = gss_accept_sec_context(&minor, &actx, acred, &stok,
181                                    GSS_C_NO_CHANNEL_BINDINGS, NULL,
182                                    NULL, &atok, NULL, NULL, NULL);
183     check_gsserr("gss_accept_sec_context(mskrb)", major, minor);
184     assert(atok.length == 22);
185     atok_oid = (unsigned char *)atok.value + 13;
186     assert(memcmp(atok_oid, mech_krb5_wrong.elements, 9) == 0);
187 
188     (void)gss_delete_sec_context(&minor, &ictx, NULL);
189     (void)gss_delete_sec_context(&minor, &actx, NULL);
190     (void)gss_release_buffer(&minor, &ktok);
191     (void)gss_release_buffer(&minor, &atok);
192     free(stok.value);
193 }
194 
195 /* Check that we return a compatibility NegTokenInit2 message containing
196  * NegHints for an empty initiator token. */
197 static void
test_neghints()198 test_neghints()
199 {
200     OM_uint32 major, minor;
201     gss_buffer_desc itok = GSS_C_EMPTY_BUFFER, atok;
202     gss_ctx_id_t actx = GSS_C_NO_CONTEXT;
203     const char *expected =
204         /* RFC 2743 token framing: [APPLICATION 0] IMPLICIT SEQUENCE followed
205          * by OBJECT IDENTIFIER and the SPNEGO OID */
206         "\x60\x47\x06\x06" "\x2B\x06\x01\x05\x05\x02"
207         /* [0] SEQUENCE for the NegotiationToken negtokenInit choice */
208         "\xA0\x3D\x30\x3B"
209         /* [0] MechTypeList containing the krb5 OID */
210         "\xA0\x0D\x30\x0B\x06\x09" "\x2A\x86\x48\x86\xF7\x12\x01\x02\x02"
211         /* [3] NegHints containing [0] GeneralString containing the dummy
212          * hintName string defined in [MS-SPNG] */
213         "\xA3\x2A\x30\x28\xA0\x26\x1B\x24"
214         "not_defined_in_RFC4178@please_ignore";
215 
216     /* Produce a hint token. */
217     major = gss_accept_sec_context(&minor, &actx, GSS_C_NO_CREDENTIAL, &itok,
218                                    GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL,
219                                    &atok, NULL, NULL, NULL);
220     check_gsserr("gss_accept_sec_context(neghints)", major, minor);
221 
222     /* Verify it against the expected contents, which are fixed as long as we
223      * only list the krb5 mech in the token. */
224     assert(atok.length == strlen(expected));
225     assert(memcmp(atok.value, expected, atok.length) == 0);
226 
227     (void)gss_release_buffer(&minor, &atok);
228     (void)gss_delete_sec_context(&minor, &actx, NULL);
229 }
230 
231 int
main(int argc,char * argv[])232 main(int argc, char *argv[])
233 {
234     OM_uint32 minor, major, flags;
235     gss_cred_id_t verifier_cred_handle = GSS_C_NO_CREDENTIAL;
236     gss_cred_id_t initiator_cred_handle = GSS_C_NO_CREDENTIAL;
237     gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
238     gss_ctx_id_t initiator_context, acceptor_context;
239     gss_name_t target_name, source_name = GSS_C_NO_NAME;
240     gss_OID mech = GSS_C_NO_OID;
241     gss_OID_desc pref_oids[2];
242     gss_OID_set_desc pref_mechs;
243 
244     if (argc < 2 || argc > 3) {
245         fprintf(stderr, "Usage: %s target_name [keytab]\n", argv[0]);
246         exit(1);
247     }
248 
249     target_name = import_name(argv[1]);
250 
251     if (argc >= 3) {
252         major = krb5_gss_register_acceptor_identity(argv[2]);
253         check_gsserr("krb5_gss_register_acceptor_identity", major, 0);
254     }
255 
256     /* Get default initiator cred. */
257     major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
258                              &mechset_spnego, GSS_C_INITIATE,
259                              &initiator_cred_handle, NULL, NULL);
260     check_gsserr("gss_acquire_cred(initiator)", major, minor);
261 
262     /*
263      * The following test is designed to exercise SPNEGO reselection on the
264      * client and server.  Unfortunately, it no longer does so after tickets
265      * #8217 and #8021, since SPNEGO now only acquires a single krb5 cred and
266      * there is no way to expand the underlying creds with gss_set_neg_mechs().
267      * To fix this we need gss_acquire_cred_with_cred() or some other way to
268      * turn a cred with a specifically requested mech set into a SPNEGO cred.
269      */
270 
271     /* Make the initiator prefer IAKERB and offer krb5 as an alternative. */
272     pref_oids[0] = mech_iakerb;
273     pref_oids[1] = mech_krb5;
274     pref_mechs.count = 2;
275     pref_mechs.elements = pref_oids;
276     major = gss_set_neg_mechs(&minor, initiator_cred_handle, &pref_mechs);
277     check_gsserr("gss_set_neg_mechs(initiator)", major, minor);
278 
279     /* Get default acceptor cred. */
280     major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
281                              &mechset_spnego, GSS_C_ACCEPT,
282                              &verifier_cred_handle, &actual_mechs, NULL);
283     check_gsserr("gss_acquire_cred(acceptor)", major, minor);
284 
285     /* Restrict the acceptor to krb5 (which will force a reselection). */
286     major = gss_set_neg_mechs(&minor, verifier_cred_handle, &mechset_krb5);
287     check_gsserr("gss_set_neg_mechs(acceptor)", major, minor);
288 
289     flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG;
290     establish_contexts(&mech_spnego, initiator_cred_handle,
291                        verifier_cred_handle, target_name, flags,
292                        &initiator_context, &acceptor_context, &source_name,
293                        &mech, NULL);
294 
295     display_canon_name("Source name", source_name, &mech_krb5);
296     display_oid("Source mech", mech);
297 
298     /* Test acceptance of the erroneous Microsoft krb5 OID, with and without an
299      * acceptor cred. */
300     test_mskrb_oid(target_name, verifier_cred_handle);
301     test_mskrb_oid(target_name, GSS_C_NO_CREDENTIAL);
302 
303     test_neghints();
304 
305     (void)gss_delete_sec_context(&minor, &initiator_context, NULL);
306     (void)gss_delete_sec_context(&minor, &acceptor_context, NULL);
307     (void)gss_release_name(&minor, &source_name);
308     (void)gss_release_name(&minor, &target_name);
309     (void)gss_release_cred(&minor, &initiator_cred_handle);
310     (void)gss_release_cred(&minor, &verifier_cred_handle);
311     (void)gss_release_oid_set(&minor, &actual_mechs);
312 
313     return 0;
314 }
315