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