* ./t_spnego host/test.host@REALM testhost.keytab
*/
+/* Replace *tok and *len with the concatenation of prefix and *tok. */
+static void
+prepend(const void *prefix, size_t plen, uint8_t **tok, size_t *len)
+{
+ uint8_t *newtok;
+
+ newtok = malloc(plen + *len);
+ assert(newtok != NULL);
+ memcpy(newtok, prefix, plen);
+ memcpy(newtok + plen, *tok, *len);
+ free(*tok);
+ *tok = newtok;
+ *len = plen + *len;
+}
+
+/* Replace *tok and *len with *tok wrapped in a DER tag with the given tag
+ * byte. *len must be less than 2^16. */
+static void
+der_wrap(uint8_t tag, uint8_t **tok, size_t *len)
+{
+ char lenbuf[3];
+ uint8_t *wrapped;
+ size_t llen;
+
+ if (*len < 128) {
+ lenbuf[0] = *len;
+ llen = 1;
+ } else if (*len < 256) {
+ lenbuf[0] = 0x81;
+ lenbuf[1] = *len;
+ llen = 2;
+ } else {
+ assert(*len >> 16 == 0);
+ lenbuf[0] = 0x82;
+ lenbuf[1] = *len >> 8;
+ lenbuf[2] = *len & 0xFF;
+ llen = 3;
+ }
+ wrapped = malloc(1 + llen + *len);
+ assert(wrapped != NULL);
+ *wrapped = tag;
+ memcpy(wrapped + 1, lenbuf, llen);
+ memcpy(wrapped + 1 + llen, *tok, *len);
+ free(*tok);
+ *tok = wrapped;
+ *len = 1 + llen + *len;
+}
+
+/*
+ * Create a SPNEGO initiator token for the erroneous Microsoft krb5 mech OID,
+ * wrapping a krb5 token ktok. The token should look like:
+ *
+ * 60 <len> (GSS framing sequence)
+ * 06 06 2B 06 01 05 05 02 (SPNEGO OID)
+ * A0 <len> (NegotiationToken choice 0, negTokenInit)
+ * 30 <len> (sequence)
+ * A0 0D (context tag 0, mechTypes)
+ * 30 0B (sequence of)
+ * 06 09 2A 86 48 82 F7 12 01 02 02 (wrong krb5 OID)
+ * A2 <len> (context tag 2, mechToken)
+ * 04 <len> (octet string)
+ * <mech token octets>
+ */
+static void
+create_mskrb5_spnego_token(gss_buffer_t ktok, gss_buffer_desc *tok_out)
+{
+ uint8_t *tok;
+ size_t len;
+
+ len = ktok->length;
+ tok = malloc(len);
+ assert(tok != NULL);
+ memcpy(tok, ktok->value, len);
+ /* Wrap the krb5 token in OCTET STRING and [2] tags. */
+ der_wrap(0x04, &tok, &len);
+ der_wrap(0xA2, &tok, &len);
+ /* Prepend the wrong krb5 OID inside OBJECT IDENTIFIER and [0] tags. */
+ prepend("\xA0\x0D\x30\x0B\x06\x09\x2A\x86\x48\x82\xF7\x12\x01\x02\x02", 15,
+ &tok, &len);
+ /* Wrap the previous two things in SEQUENCE and [0] tags. */
+ der_wrap(0x30, &tok, &len);
+ der_wrap(0xA0, &tok, &len);
+ /* Prepend the SPNEGO OID in an OBJECT IDENTIFIER tag. */
+ prepend("\x06\x06\x2B\x06\x01\x05\x05\x02", 8, &tok, &len);
+ /* Wrap the whole thing in an [APPLICATION 0] tag. */
+ der_wrap(0x60, &tok, &len);
+ tok_out->value = tok;
+ tok_out->length = len;
+}
+
int
main(int argc, char *argv[])
{
gss_cred_id_t verifier_cred_handle = GSS_C_NO_CREDENTIAL;
gss_cred_id_t initiator_cred_handle = GSS_C_NO_CREDENTIAL;
gss_OID_set actual_mechs = GSS_C_NO_OID_SET;
- gss_buffer_desc itok = GSS_C_EMPTY_BUFFER, atok = GSS_C_EMPTY_BUFFER;
+ gss_buffer_desc atok = GSS_C_EMPTY_BUFFER, ktok = GSS_C_EMPTY_BUFFER, stok;
gss_ctx_id_t initiator_context, acceptor_context;
gss_name_t target_name, source_name = GSS_C_NO_NAME;
gss_OID mech = GSS_C_NO_OID;
/*
* Test that the SPNEGO acceptor code properly reflects back the erroneous
* Microsoft mech OID in the supportedMech field of the NegTokenResp
- * message. Our initiator code doesn't care (it treats all variants of the
- * krb5 mech as equivalent when comparing the supportedMech response to its
- * first-choice mech), so we have to look directly at the DER encoding of
- * the response token. If we don't request mutual authentication, the
- * SPNEGO reply will contain no underlying mech token, so the encoding of
- * the correct NegotiationToken response is completely predictable:
+ * message. Our SPNEGO mech no longer acquires creds for the wrong mech
+ * OID, so we have to construct a SPNEGO token ourselves, and then look
+ * look directly at the DER encoding of the response token. If we don't
+ * request mutual authentication, the SPNEGO reply will contain no
+ * underlying mech token, so the encoding of the correct NegotiationToken
+ * response is completely predictable:
*
* A1 14 (choice 1, length 20, meaning negTokenResp)
* 30 12 (sequence, length 18)
* So we can just compare the length to 22 and the nine bytes at offset 13
* to the expected OID.
*/
- major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
- &mechset_spnego, GSS_C_INITIATE,
- &initiator_cred_handle, NULL, NULL);
- check_gsserr("gss_acquire_cred(2)", major, minor);
- major = gss_set_neg_mechs(&minor, initiator_cred_handle,
- &mechset_krb5_wrong);
- check_gsserr("gss_set_neg_mechs(2)", major, minor);
- major = gss_init_sec_context(&minor, initiator_cred_handle,
- &initiator_context, target_name, &mech_spnego,
- flags, GSS_C_INDEFINITE,
- GSS_C_NO_CHANNEL_BINDINGS, &atok, NULL, &itok,
- NULL, NULL);
+ major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL,
+ &initiator_context, target_name,
+ (gss_OID)gss_mech_krb5_wrong, flags,
+ GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS,
+ &atok, NULL, &ktok, NULL, NULL);
check_gsserr("gss_init_sec_context", major, minor);
- assert(major == GSS_S_CONTINUE_NEEDED);
+ assert(major == GSS_S_COMPLETE);
+ create_mskrb5_spnego_token(&ktok, &stok);
+
major = gss_accept_sec_context(&minor, &acceptor_context,
- GSS_C_NO_CREDENTIAL, &itok,
+ GSS_C_NO_CREDENTIAL, &stok,
GSS_C_NO_CHANNEL_BINDINGS, NULL,
NULL, &atok, NULL, NULL, NULL);
assert(atok.length == 22);
(void)gss_delete_sec_context(&minor, &acceptor_context, NULL);
(void)gss_release_cred(&minor, &initiator_cred_handle);
(void)gss_release_name(&minor, &target_name);
- (void)gss_release_buffer(&minor, &itok);
+ (void)gss_release_buffer(&minor, &ktok);
(void)gss_release_buffer(&minor, &atok);
+ free(stok.value);
return 0;
}