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 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 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 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 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 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 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