/* Do not modify this file. Changes will be overwritten. */ /* Generated automatically by the ASN.1 to Wireshark dissector compiler */ /* packet-spnego.c */ /* asn2wrs.py -b -L -p spnego -c ./spnego.cnf -s ./packet-spnego-template -D . -O ../.. spnego.asn */ /* packet-spnego-template.c * Routines for the simple and protected GSS-API negotiation mechanism * as described in RFC 2478. * Copyright 2002, Tim Potter * Copyright 2002, Richard Sharpe * Copyright 2003, Richard Sharpe * Copyright 2005, Ronnie Sahlberg (krb decryption) * Copyright 2005, Anders Broman (converted to asn2wrs generated dissector) * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ /* The heimdal code for decryption of GSSAPI wrappers using heimdal comes from Heimdal 1.6 and has been modified for wireshark's requirements. */ #include "config.h" #include #include #include #include #include #include #include "packet-gssapi.h" #include "packet-kerberos.h" #include "packet-ber.h" #define PNAME "Simple Protected Negotiation" #define PSNAME "SPNEGO" #define PFNAME "spnego" void proto_register_spnego(void); void proto_reg_handoff_spnego(void); static dissector_handle_t spnego_wrap_handle; /* Initialize the protocol and registered fields */ static int proto_spnego = -1; static int proto_spnego_krb5 = -1; static int hf_spnego_wraptoken = -1; static int hf_spnego_krb5_oid; static int hf_spnego_krb5 = -1; static int hf_spnego_krb5_tok_id = -1; static int hf_spnego_krb5_sgn_alg = -1; static int hf_spnego_krb5_seal_alg = -1; static int hf_spnego_krb5_snd_seq = -1; static int hf_spnego_krb5_sgn_cksum = -1; static int hf_spnego_krb5_confounder = -1; static int hf_spnego_krb5_filler = -1; static int hf_spnego_krb5_cfx_flags = -1; static int hf_spnego_krb5_cfx_flags_01 = -1; static int hf_spnego_krb5_cfx_flags_02 = -1; static int hf_spnego_krb5_cfx_flags_04 = -1; static int hf_spnego_krb5_cfx_ec = -1; static int hf_spnego_krb5_cfx_rrc = -1; static int hf_spnego_krb5_cfx_seq = -1; static int hf_spnego_negTokenInit = -1; /* T_negTokenInit */ static int hf_spnego_negTokenTarg = -1; /* NegTokenTarg */ static int hf_spnego_MechTypeList_item = -1; /* MechType */ static int hf_spnego_mechTypes = -1; /* MechTypeList */ static int hf_spnego_reqFlags = -1; /* ContextFlags */ static int hf_spnego_mechToken = -1; /* T_mechToken */ static int hf_spnego_mechListMIC = -1; /* OCTET_STRING */ static int hf_spnego_hintName = -1; /* GeneralString */ static int hf_spnego_hintAddress = -1; /* OCTET_STRING */ static int hf_spnego_mechToken_01 = -1; /* OCTET_STRING */ static int hf_spnego_negHints = -1; /* NegHints */ static int hf_spnego_negResult = -1; /* T_negResult */ static int hf_spnego_supportedMech = -1; /* T_supportedMech */ static int hf_spnego_responseToken = -1; /* T_responseToken */ static int hf_spnego_mechListMIC_01 = -1; /* T_mechListMIC */ static int hf_spnego_thisMech = -1; /* MechType */ static int hf_spnego_innerContextToken = -1; /* InnerContextToken */ /* named bits */ static int hf_spnego_ContextFlags_delegFlag = -1; static int hf_spnego_ContextFlags_mutualFlag = -1; static int hf_spnego_ContextFlags_replayFlag = -1; static int hf_spnego_ContextFlags_sequenceFlag = -1; static int hf_spnego_ContextFlags_anonFlag = -1; static int hf_spnego_ContextFlags_confFlag = -1; static int hf_spnego_ContextFlags_integFlag = -1; /* Global variables */ static const char *MechType_oid; gssapi_oid_value *next_level_value; gboolean saw_mechanism = FALSE; /* Initialize the subtree pointers */ static gint ett_spnego = -1; static gint ett_spnego_wraptoken = -1; static gint ett_spnego_krb5 = -1; static gint ett_spnego_krb5_cfx_flags = -1; static gint ett_spnego_NegotiationToken = -1; static gint ett_spnego_MechTypeList = -1; static gint ett_spnego_NegTokenInit = -1; static gint ett_spnego_NegHints = -1; static gint ett_spnego_NegTokenInit2 = -1; static gint ett_spnego_ContextFlags = -1; static gint ett_spnego_NegTokenTarg = -1; static gint ett_spnego_InitialContextToken_U = -1; static expert_field ei_spnego_decrypted_keytype = EI_INIT; static expert_field ei_spnego_unknown_header = EI_INIT; static dissector_handle_t spnego_handle; static dissector_handle_t spnego_krb5_handle; static dissector_handle_t spnego_krb5_wrap_handle; /* * Unfortunately, we have to have forward declarations of these, * as the code generated by asn2wrs includes a call before the * definition. */ static int dissect_spnego_NegTokenInit(bool implicit_tag, tvbuff_t *tvb, int offset, asn1_ctx_t *actx _U_, proto_tree *tree, int hf_index); static int dissect_spnego_NegTokenInit2(bool implicit_tag, tvbuff_t *tvb, int offset, asn1_ctx_t *actx _U_, proto_tree *tree, int hf_index); static int dissect_spnego_MechType(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { gssapi_oid_value *value; offset = dissect_ber_object_identifier_str(implicit_tag, actx, tree, tvb, offset, hf_index, &MechType_oid); value = gssapi_lookup_oid_str(MechType_oid); /* * Tell our caller the first mechanism we see, so that if * this is a negTokenInit with a mechToken, it can interpret * the mechToken according to the first mechType. (There * might not have been any indication of the mechType * in prior frames, so we can't necessarily use the * mechanism from the conversation; i.e., a negTokenInit * can contain the initial security token for the desired * mechanism of the initiator - that's the first mechanism * in the list.) */ if (!saw_mechanism) { if (value) next_level_value = value; saw_mechanism = TRUE; } return offset; } static const ber_sequence_t MechTypeList_sequence_of[1] = { { &hf_spnego_MechTypeList_item, BER_CLASS_UNI, BER_UNI_TAG_OID, BER_FLAGS_NOOWNTAG, dissect_spnego_MechType }, }; static int dissect_spnego_MechTypeList(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { conversation_t *conversation; saw_mechanism = FALSE; offset = dissect_ber_sequence_of(implicit_tag, actx, tree, tvb, offset, MechTypeList_sequence_of, hf_index, ett_spnego_MechTypeList); /* * If we saw a mechType we need to store it in case the negTokenTarg * does not provide a supportedMech. */ if(saw_mechanism){ conversation = find_or_create_conversation(actx->pinfo); conversation_add_proto_data(conversation, proto_spnego, next_level_value); } return offset; } static int * const ContextFlags_bits[] = { &hf_spnego_ContextFlags_delegFlag, &hf_spnego_ContextFlags_mutualFlag, &hf_spnego_ContextFlags_replayFlag, &hf_spnego_ContextFlags_sequenceFlag, &hf_spnego_ContextFlags_anonFlag, &hf_spnego_ContextFlags_confFlag, &hf_spnego_ContextFlags_integFlag, NULL }; static int dissect_spnego_ContextFlags(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_bitstring(implicit_tag, actx, tree, tvb, offset, ContextFlags_bits, 7, hf_index, ett_spnego_ContextFlags, NULL); return offset; } static int dissect_spnego_T_mechToken(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { tvbuff_t *mechToken_tvb = NULL; offset = dissect_ber_octet_string(implicit_tag, actx, tree, tvb, offset, hf_index, &mechToken_tvb); /* * Now, we should be able to dispatch, if we've gotten a tvbuff for * the token and we have information on how to dissect its contents. */ if (mechToken_tvb && next_level_value) call_dissector(next_level_value->handle, mechToken_tvb, actx->pinfo, tree); return offset; } static int dissect_spnego_OCTET_STRING(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_octet_string(implicit_tag, actx, tree, tvb, offset, hf_index, NULL); return offset; } static const ber_sequence_t NegTokenInit_sequence[] = { { &hf_spnego_mechTypes , BER_CLASS_CON, 0, BER_FLAGS_OPTIONAL, dissect_spnego_MechTypeList }, { &hf_spnego_reqFlags , BER_CLASS_CON, 1, BER_FLAGS_OPTIONAL, dissect_spnego_ContextFlags }, { &hf_spnego_mechToken , BER_CLASS_CON, 2, BER_FLAGS_OPTIONAL, dissect_spnego_T_mechToken }, { &hf_spnego_mechListMIC , BER_CLASS_CON, 3, BER_FLAGS_OPTIONAL, dissect_spnego_OCTET_STRING }, { NULL, 0, 0, 0, NULL } }; static int dissect_spnego_NegTokenInit(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_sequence(implicit_tag, actx, tree, tvb, offset, NegTokenInit_sequence, hf_index, ett_spnego_NegTokenInit); return offset; } static int dissect_spnego_T_negTokenInit(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { bool is_response = actx->pinfo->ptype == PT_TCP && actx->pinfo->srcport < 1024; /* * We decode as negTokenInit2 or negTokenInit depending on whether or not * we are in a response or a request. That is essentially what MS-SPNG * says. */ if (is_response) { return dissect_spnego_NegTokenInit2(implicit_tag, tvb, offset, actx, tree, hf_index); } else { return dissect_spnego_NegTokenInit(implicit_tag, tvb, offset, actx, tree, hf_index); } return offset; } static const value_string spnego_T_negResult_vals[] = { { 0, "accept-completed" }, { 1, "accept-incomplete" }, { 2, "reject" }, { 0, NULL } }; static int dissect_spnego_T_negResult(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_integer(implicit_tag, actx, tree, tvb, offset, hf_index, NULL); return offset; } static int dissect_spnego_T_supportedMech(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { conversation_t *conversation; saw_mechanism = FALSE; offset = dissect_spnego_MechType(implicit_tag, tvb, offset, actx, tree, hf_index); /* * If we saw an explicit mechType we store this in the conversation so that * it will override any mechType we might have picked up from the * negTokenInit. */ if(saw_mechanism){ conversation = find_or_create_conversation(actx->pinfo); conversation_add_proto_data(conversation, proto_spnego, next_level_value); } return offset; } static int dissect_spnego_T_responseToken(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { tvbuff_t *responseToken_tvb; offset = dissect_ber_octet_string(implicit_tag, actx, tree, tvb, offset, hf_index, &responseToken_tvb); /* * Now, we should be able to dispatch, if we've gotten a tvbuff for * the token and we have information on how to dissect its contents. * However, we should make sure that there is something in the * response token ... */ if (responseToken_tvb && (tvb_reported_length(responseToken_tvb) > 0) ){ gssapi_oid_value *value=next_level_value; if(value){ call_dissector(value->handle, responseToken_tvb, actx->pinfo, tree); } } return offset; } static int dissect_spnego_T_mechListMIC(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { tvbuff_t *mechListMIC_tvb; offset = dissect_ber_octet_string(implicit_tag, actx, tree, tvb, offset, hf_index, &mechListMIC_tvb); /* * Now, we should be able to dispatch, if we've gotten a tvbuff for * the token and we have information on how to dissect its contents. * However, we should make sure that there is something in the * response token ... */ if (mechListMIC_tvb && (tvb_reported_length(mechListMIC_tvb) > 0) ){ gssapi_oid_value *value=next_level_value; if(value){ call_dissector(value->handle, mechListMIC_tvb, actx->pinfo, tree); } } return offset; } static const ber_sequence_t NegTokenTarg_sequence[] = { { &hf_spnego_negResult , BER_CLASS_CON, 0, BER_FLAGS_OPTIONAL, dissect_spnego_T_negResult }, { &hf_spnego_supportedMech, BER_CLASS_CON, 1, BER_FLAGS_OPTIONAL, dissect_spnego_T_supportedMech }, { &hf_spnego_responseToken, BER_CLASS_CON, 2, BER_FLAGS_OPTIONAL, dissect_spnego_T_responseToken }, { &hf_spnego_mechListMIC_01, BER_CLASS_CON, 3, BER_FLAGS_OPTIONAL, dissect_spnego_T_mechListMIC }, { NULL, 0, 0, 0, NULL } }; static int dissect_spnego_NegTokenTarg(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_sequence(implicit_tag, actx, tree, tvb, offset, NegTokenTarg_sequence, hf_index, ett_spnego_NegTokenTarg); return offset; } static const ber_choice_t NegotiationToken_choice[] = { { 0, &hf_spnego_negTokenInit , BER_CLASS_CON, 0, 0, dissect_spnego_T_negTokenInit }, { 1, &hf_spnego_negTokenTarg , BER_CLASS_CON, 1, 0, dissect_spnego_NegTokenTarg }, { 0, NULL, 0, 0, 0, NULL } }; static int dissect_spnego_NegotiationToken(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_choice(actx, tree, tvb, offset, NegotiationToken_choice, hf_index, ett_spnego_NegotiationToken, NULL); return offset; } static int dissect_spnego_GeneralString(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_restricted_string(implicit_tag, BER_UNI_TAG_GeneralString, actx, tree, tvb, offset, hf_index, NULL); return offset; } static const ber_sequence_t NegHints_sequence[] = { { &hf_spnego_hintName , BER_CLASS_CON, 0, BER_FLAGS_OPTIONAL, dissect_spnego_GeneralString }, { &hf_spnego_hintAddress , BER_CLASS_CON, 1, BER_FLAGS_OPTIONAL, dissect_spnego_OCTET_STRING }, { NULL, 0, 0, 0, NULL } }; static int dissect_spnego_NegHints(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_sequence(implicit_tag, actx, tree, tvb, offset, NegHints_sequence, hf_index, ett_spnego_NegHints); return offset; } static const ber_sequence_t NegTokenInit2_sequence[] = { { &hf_spnego_mechTypes , BER_CLASS_CON, 0, BER_FLAGS_OPTIONAL, dissect_spnego_MechTypeList }, { &hf_spnego_reqFlags , BER_CLASS_CON, 1, BER_FLAGS_OPTIONAL, dissect_spnego_ContextFlags }, { &hf_spnego_mechToken_01 , BER_CLASS_CON, 2, BER_FLAGS_OPTIONAL, dissect_spnego_OCTET_STRING }, { &hf_spnego_negHints , BER_CLASS_CON, 3, BER_FLAGS_OPTIONAL, dissect_spnego_NegHints }, { &hf_spnego_mechListMIC , BER_CLASS_CON, 4, BER_FLAGS_OPTIONAL, dissect_spnego_OCTET_STRING }, { NULL, 0, 0, 0, NULL } }; static int dissect_spnego_NegTokenInit2(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_sequence(implicit_tag, actx, tree, tvb, offset, NegTokenInit2_sequence, hf_index, ett_spnego_NegTokenInit2); return offset; } static int dissect_spnego_InnerContextToken(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { gssapi_oid_value *next_level_value_lcl; proto_item *item; proto_tree *subtree; tvbuff_t *token_tvb; int len; /* * XXX - what should we do if this OID doesn't match the value * attached to the frame or conversation? (That would be * bogus, but that's not impossible - some broken implementation * might negotiate some security mechanism but put the OID * for some other security mechanism in GSS_Wrap tokens.) * Does it matter? */ next_level_value_lcl = gssapi_lookup_oid_str(MechType_oid); /* * Now dissect the GSS_Wrap token; it's assumed to be in the * rest of the tvbuff. */ item = proto_tree_add_item(tree, hf_spnego_wraptoken, tvb, offset, -1, ENC_NA); subtree = proto_item_add_subtree(item, ett_spnego_wraptoken); /* * Now, we should be able to dispatch after creating a new TVB. * The subdissector must return the length of the part of the * token it dissected, so we can return the length of the part * we (and it) dissected. */ token_tvb = tvb_new_subset_remaining(tvb, offset); if (next_level_value_lcl && next_level_value_lcl->wrap_handle) { len = call_dissector(next_level_value_lcl->wrap_handle, token_tvb, actx->pinfo, subtree); if (len == 0) offset = tvb_reported_length(tvb); else offset = offset + len; } else offset = tvb_reported_length(tvb); return offset; } static const ber_sequence_t InitialContextToken_U_sequence[] = { { &hf_spnego_thisMech , BER_CLASS_UNI, BER_UNI_TAG_OID, BER_FLAGS_NOOWNTAG, dissect_spnego_MechType }, { &hf_spnego_innerContextToken, BER_CLASS_ANY, 0, BER_FLAGS_NOOWNTAG, dissect_spnego_InnerContextToken }, { NULL, 0, 0, 0, NULL } }; static int dissect_spnego_InitialContextToken_U(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_sequence(implicit_tag, actx, tree, tvb, offset, InitialContextToken_U_sequence, hf_index, ett_spnego_InitialContextToken_U); return offset; } static int dissect_spnego_InitialContextToken(bool implicit_tag _U_, tvbuff_t *tvb _U_, int offset _U_, asn1_ctx_t *actx _U_, proto_tree *tree _U_, int hf_index _U_) { offset = dissect_ber_tagged_type(implicit_tag, actx, tree, tvb, offset, hf_index, BER_CLASS_APP, 0, TRUE, dissect_spnego_InitialContextToken_U); return offset; } /* * This is the SPNEGO KRB5 dissector. It is not true KRB5, but some ASN.1 * wrapped blob with an OID, USHORT token ID, and a Ticket, that is also * ASN.1 wrapped by the looks of it. It conforms to RFC1964. */ #define KRB_TOKEN_AP_REQ 0x0001 #define KRB_TOKEN_AP_REP 0x0002 #define KRB_TOKEN_AP_ERR 0x0003 #define KRB_TOKEN_GETMIC 0x0101 #define KRB_TOKEN_WRAP 0x0102 #define KRB_TOKEN_DELETE_SEC_CONTEXT 0x0201 #define KRB_TOKEN_TGT_REQ 0x0004 #define KRB_TOKEN_TGT_REP 0x0104 #define KRB_TOKEN_CFX_GETMIC 0x0404 #define KRB_TOKEN_CFX_WRAP 0x0405 static const value_string spnego_krb5_tok_id_vals[] = { { KRB_TOKEN_AP_REQ, "KRB5_AP_REQ"}, { KRB_TOKEN_AP_REP, "KRB5_AP_REP"}, { KRB_TOKEN_AP_ERR, "KRB5_ERROR"}, { KRB_TOKEN_GETMIC, "KRB5_GSS_GetMIC" }, { KRB_TOKEN_WRAP, "KRB5_GSS_Wrap" }, { KRB_TOKEN_DELETE_SEC_CONTEXT, "KRB5_GSS_Delete_sec_context" }, { KRB_TOKEN_TGT_REQ, "KERB_TGT_REQUEST" }, { KRB_TOKEN_TGT_REP, "KERB_TGT_REPLY" }, { KRB_TOKEN_CFX_GETMIC, "KRB_TOKEN_CFX_GetMic" }, { KRB_TOKEN_CFX_WRAP, "KRB_TOKEN_CFX_WRAP" }, { 0, NULL} }; #define KRB_SGN_ALG_DES_MAC_MD5 0x0000 #define KRB_SGN_ALG_MD2_5 0x0001 #define KRB_SGN_ALG_DES_MAC 0x0002 #define KRB_SGN_ALG_HMAC 0x0011 static const value_string spnego_krb5_sgn_alg_vals[] = { { KRB_SGN_ALG_DES_MAC_MD5, "DES MAC MD5"}, { KRB_SGN_ALG_MD2_5, "MD2.5"}, { KRB_SGN_ALG_DES_MAC, "DES MAC"}, { KRB_SGN_ALG_HMAC, "HMAC"}, { 0, NULL} }; #define KRB_SEAL_ALG_DES_CBC 0x0000 #define KRB_SEAL_ALG_RC4 0x0010 #define KRB_SEAL_ALG_NONE 0xffff static const value_string spnego_krb5_seal_alg_vals[] = { { KRB_SEAL_ALG_DES_CBC, "DES CBC"}, { KRB_SEAL_ALG_RC4, "RC4"}, { KRB_SEAL_ALG_NONE, "None"}, { 0, NULL} }; /* * XXX - is this for SPNEGO or just GSS-API? * RFC 1964 is "The Kerberos Version 5 GSS-API Mechanism"; presumably one * can directly designate Kerberos V5 as a mechanism in GSS-API, rather * than designating SPNEGO as the mechanism, offering Kerberos V5, and * getting it accepted. */ static int dissect_spnego_krb5_getmic_base(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree); static int dissect_spnego_krb5_wrap_base(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, guint16 token_id, gssapi_encrypt_info_t* gssapi_encrypt); static int dissect_spnego_krb5_cfx_getmic_base(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree); static int dissect_spnego_krb5_cfx_wrap_base(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, guint16 token_id, gssapi_encrypt_info_t* gssapi_encrypt); static int dissect_spnego_krb5(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) { proto_item *item; proto_tree *subtree; int offset = 0; guint16 token_id; const char *oid; tvbuff_t *krb5_tvb; gint8 ber_class; bool pc, ind = 0; gint32 tag; guint32 len; gssapi_encrypt_info_t* encrypt_info = (gssapi_encrypt_info_t*)data; asn1_ctx_t asn1_ctx; asn1_ctx_init(&asn1_ctx, ASN1_ENC_BER, TRUE, pinfo); item = proto_tree_add_item(tree, hf_spnego_krb5, tvb, offset, -1, ENC_NA); subtree = proto_item_add_subtree(item, ett_spnego_krb5); /* * The KRB5 blob conforms to RFC1964: * [APPLICATION 0] { * OID, * USHORT (0x0001 == AP-REQ, 0x0002 == AP-REP, 0x0003 == ERROR), * OCTET STRING } * * However, for some protocols, the KRB5 blob starts at the SHORT * and has no DER encoded header etc. * * It appears that for some other protocols the KRB5 blob is just * a Kerberos message, with no [APPLICATION 0] header, no OID, * and no USHORT. * * So: * * If we see an [APPLICATION 0] HEADER, we show the OID and * the USHORT, and then dissect the rest as a Kerberos message. * * If we see an [APPLICATION 14] or [APPLICATION 15] header, * we assume it's an AP-REQ or AP-REP message, and dissect * it all as a Kerberos message. * * Otherwise, we show the USHORT, and then dissect the rest * as a Kerberos message. */ /* * Get the first header ... */ get_ber_identifier(tvb, offset, &ber_class, &pc, &tag); if (ber_class == BER_CLASS_APP && pc) { /* * [APPLICATION ] */ offset = dissect_ber_identifier(pinfo, subtree, tvb, offset, &ber_class, &pc, &tag); offset = dissect_ber_length(pinfo, subtree, tvb, offset, &len, &ind); switch (tag) { case 0: /* * [APPLICATION 0] */ /* Next, the OID */ offset=dissect_ber_object_identifier_str(FALSE, &asn1_ctx, subtree, tvb, offset, hf_spnego_krb5_oid, &oid); token_id = tvb_get_letohs(tvb, offset); proto_tree_add_uint(subtree, hf_spnego_krb5_tok_id, tvb, offset, 2, token_id); offset += 2; break; case 14: /* [APPLICATION 14] */ case 15: /* [APPLICATION 15] */ /* * No token ID - just dissect as a Kerberos message and * return. */ dissect_kerberos_main(tvb, pinfo, subtree, FALSE, NULL); return tvb_captured_length(tvb); default: proto_tree_add_expert_format(subtree, pinfo, &ei_spnego_unknown_header, tvb, offset, 0, "Unknown header (class=%d, pc=%d, tag=%d)", ber_class, pc, tag); goto done; } } else { /* Next, the token ID ... */ token_id = tvb_get_letohs(tvb, offset); proto_tree_add_uint(subtree, hf_spnego_krb5_tok_id, tvb, offset, 2, token_id); offset += 2; } switch (token_id) { case KRB_TOKEN_TGT_REQ: offset = dissect_kerberos_TGT_REQ(FALSE, tvb, offset, &asn1_ctx, subtree, -1); break; case KRB_TOKEN_TGT_REP: offset = dissect_kerberos_TGT_REP(FALSE, tvb, offset, &asn1_ctx, subtree, -1); break; case KRB_TOKEN_AP_REQ: case KRB_TOKEN_AP_REP: case KRB_TOKEN_AP_ERR: krb5_tvb = tvb_new_subset_remaining(tvb, offset); offset += dissect_kerberos_main(krb5_tvb, pinfo, subtree, FALSE, NULL); break; case KRB_TOKEN_GETMIC: offset = dissect_spnego_krb5_getmic_base(tvb, offset, pinfo, subtree); break; case KRB_TOKEN_WRAP: offset = dissect_spnego_krb5_wrap_base(tvb, offset, pinfo, subtree, token_id, encrypt_info); break; case KRB_TOKEN_DELETE_SEC_CONTEXT: break; case KRB_TOKEN_CFX_GETMIC: offset = dissect_spnego_krb5_cfx_getmic_base(tvb, offset, pinfo, subtree); break; case KRB_TOKEN_CFX_WRAP: offset = dissect_spnego_krb5_cfx_wrap_base(tvb, offset, pinfo, subtree, token_id, encrypt_info); break; default: break; } done: proto_item_set_len(item, offset); return tvb_captured_length(tvb); } #ifdef HAVE_KERBEROS #ifndef KEYTYPE_ARCFOUR_56 # define KEYTYPE_ARCFOUR_56 24 #endif #ifndef KEYTYPE_ARCFOUR_HMAC # define KEYTYPE_ARCFOUR_HMAC 23 #endif /* XXX - We should probably do a configure-time check for this instead */ #ifndef KRB5_KU_USAGE_SEAL # define KRB5_KU_USAGE_SEAL 22 #endif static int arcfour_mic_key(const guint8 *key_data, size_t key_size, int key_type, const guint8 *cksum_data, size_t cksum_size, guint8 *key6_data) { guint8 k5_data[HASH_MD5_LENGTH]; guint8 T[4] = { 0 }; if (key_type == KEYTYPE_ARCFOUR_56) { guint8 L40[14] = "fortybits"; memcpy(L40 + 10, T, sizeof(T)); if (ws_hmac_buffer(GCRY_MD_MD5, k5_data, L40, 14, key_data, key_size)) { return 0; } memset(&k5_data[7], 0xAB, 9); } else { if (ws_hmac_buffer(GCRY_MD_MD5, k5_data, T, 4, key_data, key_size)) { return 0; } } if (ws_hmac_buffer(GCRY_MD_MD5, key6_data, cksum_data, cksum_size, k5_data, HASH_MD5_LENGTH)) { return 0; } return 0; } static int usage2arcfour(int usage) { switch (usage) { case 3: /*KRB5_KU_AS_REP_ENC_PART 3 */ case 9: /*KRB5_KU_TGS_REP_ENC_PART_SUB_KEY 9 */ return 8; case 22: /*KRB5_KU_USAGE_SEAL 22 */ return 13; case 23: /*KRB5_KU_USAGE_SIGN 23 */ return 15; case 24: /*KRB5_KU_USAGE_SEQ 24 */ return 0; default : return 0; } } static int arcfour_mic_cksum(guint8 *key_data, int key_length, unsigned int usage, guint8 sgn_cksum[8], const guint8 *v1, size_t l1, const guint8 *v2, size_t l2, const guint8 *v3, size_t l3) { static const guint8 signature[] = "signaturekey"; guint8 ksign_c[HASH_MD5_LENGTH]; guint8 t[4]; guint8 digest[HASH_MD5_LENGTH]; int rc4_usage; guint8 cksum[HASH_MD5_LENGTH]; gcry_md_hd_t md5_handle; rc4_usage=usage2arcfour(usage); if (ws_hmac_buffer(GCRY_MD_MD5, ksign_c, signature, sizeof(signature), key_data, key_length)) { return 0; } if (gcry_md_open(&md5_handle, GCRY_MD_MD5, 0)) { return 0; } t[0] = (rc4_usage >> 0) & 0xFF; t[1] = (rc4_usage >> 8) & 0xFF; t[2] = (rc4_usage >> 16) & 0xFF; t[3] = (rc4_usage >> 24) & 0xFF; gcry_md_write(md5_handle, t, 4); gcry_md_write(md5_handle, v1, l1); gcry_md_write(md5_handle, v2, l2); gcry_md_write(md5_handle, v3, l3); memcpy(digest, gcry_md_read(md5_handle, 0), HASH_MD5_LENGTH); gcry_md_close(md5_handle); if (ws_hmac_buffer(GCRY_MD_MD5, cksum, digest, HASH_MD5_LENGTH, ksign_c, HASH_MD5_LENGTH)) { return 0; } memcpy(sgn_cksum, cksum, 8); return 0; } /* * Verify padding of a gss wrapped message and return its length. */ static int gssapi_verify_pad(guint8 *wrapped_data, int wrapped_length, int datalen, int *padlen) { guint8 *pad; int padlength; int i; pad = wrapped_data + wrapped_length - 1; padlength = *pad; if (padlength > datalen) return 1; for (i = padlength; i > 0 && *pad == padlength; i--, pad--); if (i != 0) return 2; *padlen = padlength; return 0; } static int decrypt_arcfour(gssapi_encrypt_info_t* gssapi_encrypt, guint8 *input_message_buffer, guint8 *output_message_buffer, guint8 *key_value, int key_size, int key_type) { guint8 Klocaldata[16]; int ret; int datalen; guint8 k6_data[16]; guint32 SND_SEQ[2]; guint8 Confounder[8]; guint8 cksum_data[8]; int cmp; int conf_flag; int padlen = 0; gcry_cipher_hd_t rc4_handle; int i; datalen = tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb); if(tvb_get_ntohs(gssapi_encrypt->gssapi_wrap_tvb, 4)==0x1000){ conf_flag=1; } else if (tvb_get_ntohs(gssapi_encrypt->gssapi_wrap_tvb, 4)==0xffff){ conf_flag=0; } else { return -3; } if(tvb_get_ntohs(gssapi_encrypt->gssapi_wrap_tvb, 6)!=0xffff){ return -4; } ret = arcfour_mic_key(key_value, key_size, key_type, tvb_get_ptr(gssapi_encrypt->gssapi_wrap_tvb, 16, 8), 8, /* SGN_CKSUM */ k6_data); if (ret) { return -5; } tvb_memcpy(gssapi_encrypt->gssapi_wrap_tvb, SND_SEQ, 8, 8); if (gcry_cipher_open (&rc4_handle, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0)) { return -12; } if (gcry_cipher_setkey(rc4_handle, k6_data, sizeof(k6_data))) { gcry_cipher_close(rc4_handle); return -13; } gcry_cipher_decrypt(rc4_handle, (guint8 *)SND_SEQ, 8, NULL, 0); gcry_cipher_close(rc4_handle); memset(k6_data, 0, sizeof(k6_data)); if (SND_SEQ[1] != 0xFFFFFFFF && SND_SEQ[1] != 0x00000000) { return -6; } for (i = 0; i < 16; i++) Klocaldata[i] = ((guint8 *)key_value)[i] ^ 0xF0; ret = arcfour_mic_key(Klocaldata,sizeof(Klocaldata),key_type, (const guint8 *)SND_SEQ, 4, k6_data); memset(Klocaldata, 0, sizeof(Klocaldata)); if (ret) { return -7; } if(conf_flag) { tvb_memcpy(gssapi_encrypt->gssapi_wrap_tvb, Confounder, 24, 8); if (gcry_cipher_open (&rc4_handle, GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0)) { return -14; } if (gcry_cipher_setkey(rc4_handle, k6_data, sizeof(k6_data))) { gcry_cipher_close(rc4_handle); return -15; } gcry_cipher_decrypt(rc4_handle, Confounder, 8, NULL, 0); gcry_cipher_decrypt(rc4_handle, output_message_buffer, datalen, input_message_buffer, datalen); gcry_cipher_close(rc4_handle); } else { tvb_memcpy(gssapi_encrypt->gssapi_wrap_tvb, Confounder, 24, 8); memcpy(output_message_buffer, input_message_buffer, datalen); } memset(k6_data, 0, sizeof(k6_data)); /* only normal (i.e. non DCE style wrapping use padding ? */ if(gssapi_encrypt->decrypt_gssapi_tvb==DECRYPT_GSSAPI_NORMAL){ ret = gssapi_verify_pad(output_message_buffer,datalen,datalen, &padlen); if (ret) { return -9; } datalen -= padlen; } /* don't know what the checksum looks like for dce style gssapi */ if(gssapi_encrypt->decrypt_gssapi_tvb==DECRYPT_GSSAPI_NORMAL){ ret = arcfour_mic_cksum(key_value, key_size, KRB5_KU_USAGE_SEAL, cksum_data, tvb_get_ptr(gssapi_encrypt->gssapi_wrap_tvb, 0, 8), 8, Confounder, sizeof(Confounder), output_message_buffer, datalen + padlen); if (ret) { return -10; } cmp = tvb_memeql(gssapi_encrypt->gssapi_wrap_tvb, 16, cksum_data, 8); /* SGN_CKSUM */ if (cmp) { return -11; } } return datalen; } #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) static void decrypt_gssapi_krb_arcfour_wrap(proto_tree *tree _U_, packet_info *pinfo, tvbuff_t *tvb, int keytype, gssapi_encrypt_info_t* gssapi_encrypt) { int ret; enc_key_t *ek; int length; const guint8 *original_data; guint8 *cryptocopy=NULL; /* workaround for pre-0.6.1 heimdal bug */ guint8 *output_message_buffer; length=tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb); original_data=tvb_get_ptr(gssapi_encrypt->gssapi_encrypted_tvb, 0, length); /* don't do anything if we are not attempting to decrypt data */ /* if(!krb_decrypt){ return; } */ /* XXX we should only do this for first time, then store somewhere */ /* XXX We also need to re-read the keytab when the preference changes */ cryptocopy=(guint8 *)wmem_alloc(pinfo->pool, length); output_message_buffer=(guint8 *)wmem_alloc(pinfo->pool, length); for(ek=enc_key_list;ek;ek=ek->next){ /* shortcircuit and bail out if enctypes are not matching */ if(ek->keytype!=keytype){ continue; } /* pre-0.6.1 versions of Heimdal would sometimes change the cryptotext data even when the decryption failed. This would obviously not work since we iterate over the keys. So just give it a copy of the crypto data instead. This has been seen for RC4-HMAC blobs. */ memcpy(cryptocopy, original_data, length); ret=decrypt_arcfour(gssapi_encrypt, cryptocopy, output_message_buffer, ek->keyvalue, ek->keylength, ek->keytype); if (ret >= 0) { expert_add_info_format(pinfo, NULL, &ei_spnego_decrypted_keytype, "Decrypted keytype %d in frame %u using %s", ek->keytype, pinfo->num, ek->key_origin); gssapi_encrypt->gssapi_decrypted_tvb=tvb_new_child_real_data(tvb, output_message_buffer, ret, ret); add_new_data_source(pinfo, gssapi_encrypt->gssapi_decrypted_tvb, "Decrypted GSS-Krb5"); return; } } } /* borrowed from heimdal */ static int rrc_rotate(guint8 *data, int len, guint16 rrc, int unrotate) { guint8 *tmp, buf[256]; size_t left; if (len == 0) return 0; rrc %= len; if (rrc == 0) return 0; left = len - rrc; if (rrc <= sizeof(buf)) { tmp = buf; } else { tmp = (guint8 *)g_malloc(rrc); if (tmp == NULL) return -1; } if (unrotate) { memcpy(tmp, data, rrc); memmove(data, data + rrc, left); memcpy(data + left, tmp, rrc); } else { memcpy(tmp, data + left, rrc); memmove(data + rrc, data, left); memcpy(data, tmp, rrc); } if (rrc > sizeof(buf)) g_free(tmp); return 0; } static void decrypt_gssapi_krb_cfx_wrap(proto_tree *tree, packet_info *pinfo, tvbuff_t *checksum_tvb, gssapi_encrypt_info_t* gssapi_encrypt, guint16 ec _U_, guint16 rrc, int keytype, unsigned int usage) { guint8 *rotated; guint8 *output; int datalen; tvbuff_t *next_tvb; /* don't do anything if we are not attempting to decrypt data */ if(!krb_decrypt){ return; } if (gssapi_encrypt->decrypt_gssapi_tvb==DECRYPT_GSSAPI_DCE) { tvbuff_t *out_tvb = NULL; out_tvb = decrypt_krb5_krb_cfx_dce(tree, pinfo, usage, keytype, gssapi_encrypt->gssapi_header_tvb, gssapi_encrypt->gssapi_encrypted_tvb, gssapi_encrypt->gssapi_trailer_tvb, checksum_tvb); if (out_tvb) { gssapi_encrypt->gssapi_decrypted_tvb = out_tvb; add_new_data_source(pinfo, gssapi_encrypt->gssapi_decrypted_tvb, "Decrypted GSS-Krb5 CFX DCE"); } return; } datalen = tvb_captured_length(checksum_tvb) + tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb); rotated = (guint8 *)wmem_alloc(pinfo->pool, datalen); tvb_memcpy(checksum_tvb, rotated, 0, tvb_captured_length(checksum_tvb)); tvb_memcpy(gssapi_encrypt->gssapi_encrypted_tvb, rotated + tvb_captured_length(checksum_tvb), 0, tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb)); rrc_rotate(rotated, datalen, rrc, TRUE); next_tvb=tvb_new_child_real_data(gssapi_encrypt->gssapi_encrypted_tvb, rotated, datalen, datalen); add_new_data_source(pinfo, next_tvb, "GSSAPI CFX"); output = decrypt_krb5_data(tree, pinfo, usage, next_tvb, keytype, &datalen); if (output) { guint8 *outdata; outdata = (guint8 *)wmem_memdup(pinfo->pool, output, tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb)); gssapi_encrypt->gssapi_decrypted_tvb=tvb_new_child_real_data(gssapi_encrypt->gssapi_encrypted_tvb, outdata, tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb), tvb_captured_length(gssapi_encrypt->gssapi_encrypted_tvb)); add_new_data_source(pinfo, gssapi_encrypt->gssapi_decrypted_tvb, "Decrypted GSS-Krb5"); } } #endif /* HAVE_HEIMDAL_KERBEROS || HAVE_MIT_KERBEROS */ #endif /* * This is for GSSAPI Wrap tokens ... */ static int dissect_spnego_krb5_wrap_base(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, guint16 token_id, gssapi_encrypt_info_t* gssapi_encrypt) { guint16 sgn_alg, seal_alg; #ifdef HAVE_KERBEROS int start_offset=offset; #else (void) pinfo; (void) token_id; #endif /* * The KRB5 blob conforms to RFC1964: * USHORT (0x0102 == GSS_Wrap) * and so on } */ /* Now, the sign and seal algorithms ... */ sgn_alg = tvb_get_letohs(tvb, offset); proto_tree_add_uint(tree, hf_spnego_krb5_sgn_alg, tvb, offset, 2, sgn_alg); offset += 2; seal_alg = tvb_get_letohs(tvb, offset); proto_tree_add_uint(tree, hf_spnego_krb5_seal_alg, tvb, offset, 2, seal_alg); offset += 2; /* Skip the filler */ offset += 2; /* Encrypted sequence number */ proto_tree_add_item(tree, hf_spnego_krb5_snd_seq, tvb, offset, 8, ENC_NA); offset += 8; /* Checksum of plaintext padded data */ proto_tree_add_item(tree, hf_spnego_krb5_sgn_cksum, tvb, offset, 8, ENC_NA); offset += 8; /* * At least according to draft-brezak-win2k-krb-rc4-hmac-04, * if the signing algorithm is KRB_SGN_ALG_HMAC, there's an * extra 8 bytes of "Random confounder" after the checksum. * It certainly confounds code expecting all Kerberos 5 * GSS_Wrap() tokens to look the same.... */ if ((sgn_alg == KRB_SGN_ALG_HMAC) || /* there also seems to be a confounder for DES MAC MD5 - certainly seen when using with SASL with LDAP between a Java client and Active Directory. If this breaks other things we may need to make this an option. gal 17/2/06 */ (sgn_alg == KRB_SGN_ALG_DES_MAC_MD5)) { proto_tree_add_item(tree, hf_spnego_krb5_confounder, tvb, offset, 8, ENC_NA); offset += 8; } /* Is the data encrypted? */ if (gssapi_encrypt != NULL) gssapi_encrypt->gssapi_data_encrypted=(seal_alg!=KRB_SEAL_ALG_NONE); #ifdef HAVE_KERBEROS #define GSS_ARCFOUR_WRAP_TOKEN_SIZE 32 if(gssapi_encrypt && gssapi_encrypt->decrypt_gssapi_tvb){ /* if the caller did not provide a tvb, then we just use whatever is left of our current tvb. */ if(!gssapi_encrypt->gssapi_encrypted_tvb){ int len; len=tvb_reported_length_remaining(tvb,offset); if(len>tvb_captured_length_remaining(tvb, offset)){ /* no point in trying to decrypt, we don't have the full pdu. */ return offset; } gssapi_encrypt->gssapi_encrypted_tvb = tvb_new_subset_length( tvb, offset, len); } /* if this is KRB5 wrapped rc4-hmac */ if((token_id==KRB_TOKEN_WRAP) &&(sgn_alg==KRB_SGN_ALG_HMAC) &&(seal_alg==KRB_SEAL_ALG_RC4)){ /* do we need to create a tvb for the wrapper as well ? */ if(!gssapi_encrypt->gssapi_wrap_tvb){ gssapi_encrypt->gssapi_wrap_tvb = tvb_new_subset_length( tvb, start_offset-2, GSS_ARCFOUR_WRAP_TOKEN_SIZE); } #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) decrypt_gssapi_krb_arcfour_wrap(tree, pinfo, tvb, KEYTYPE_ARCFOUR_HMAC, gssapi_encrypt); #endif /* HAVE_HEIMDAL_KERBEROS || HAVE_MIT_KERBEROS */ } } #endif /* * Return the offset past the checksum, so that we know where * the data we're wrapped around starts. Also, set the length * of our top-level item to that offset, so it doesn't cover * the data we're wrapped around. * * Note that for DCERPC the GSSAPI blobs comes after the data it wraps, * not before. */ return offset; } /* * XXX - This is for GSSAPI GetMIC tokens ... */ static int dissect_spnego_krb5_getmic_base(tvbuff_t *tvb, int offset, packet_info *pinfo _U_, proto_tree *tree) { guint16 sgn_alg; /* * The KRB5 blob conforms to RFC1964: * USHORT (0x0101 == GSS_GetMIC) * and so on } */ /* Now, the sign algorithm ... */ sgn_alg = tvb_get_letohs(tvb, offset); proto_tree_add_uint(tree, hf_spnego_krb5_sgn_alg, tvb, offset, 2, sgn_alg); offset += 2; /* Skip the filler */ offset += 4; /* Encrypted sequence number */ proto_tree_add_item(tree, hf_spnego_krb5_snd_seq, tvb, offset, 8, ENC_NA); offset += 8; /* Checksum of plaintext padded data */ proto_tree_add_item(tree, hf_spnego_krb5_sgn_cksum, tvb, offset, 8, ENC_NA); offset += 8; /* * At least according to draft-brezak-win2k-krb-rc4-hmac-04, * if the signing algorithm is KRB_SGN_ALG_HMAC, there's an * extra 8 bytes of "Random confounder" after the checksum. * It certainly confounds code expecting all Kerberos 5 * GSS_Wrap() tokens to look the same.... * * The exception is DNS/TSIG where there is no such confounder * so we need to test here if there are more bytes in our tvb or not. * -- ronnie */ if (tvb_reported_length_remaining(tvb, offset)) { if (sgn_alg == KRB_SGN_ALG_HMAC) { proto_tree_add_item(tree, hf_spnego_krb5_confounder, tvb, offset, 8, ENC_NA); offset += 8; } } /* * Return the offset past the checksum, so that we know where * the data we're wrapped around starts. Also, set the length * of our top-level item to that offset, so it doesn't cover * the data we're wrapped around. */ return offset; } static int dissect_spnego_krb5_cfx_flags(tvbuff_t *tvb, int offset, proto_tree *spnego_krb5_tree, guint8 cfx_flags _U_) { static int * const flags[] = { &hf_spnego_krb5_cfx_flags_04, &hf_spnego_krb5_cfx_flags_02, &hf_spnego_krb5_cfx_flags_01, NULL }; proto_tree_add_bitmask(spnego_krb5_tree, tvb, offset, hf_spnego_krb5_cfx_flags, ett_spnego_krb5_cfx_flags, flags, ENC_NA); return (offset + 1); } /* * This is for GSSAPI CFX Wrap tokens ... */ static int dissect_spnego_krb5_cfx_wrap_base(tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, guint16 token_id _U_, gssapi_encrypt_info_t* gssapi_encrypt) { guint8 flags; guint16 ec; #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) guint16 rrc; #else (void) pinfo; #endif int checksum_size; int start_offset=offset; /* * The KRB5 blob conforms to RFC4121: * USHORT (0x0504) * and so on } */ /* Now, the sign and seal algorithms ... */ flags = tvb_get_guint8(tvb, offset); offset = dissect_spnego_krb5_cfx_flags(tvb, offset, tree, flags); if (gssapi_encrypt != NULL) gssapi_encrypt->gssapi_data_encrypted=(flags & 2); /* Skip the filler */ proto_tree_add_item(tree, hf_spnego_krb5_filler, tvb, offset, 1, ENC_NA); offset += 1; /* EC */ ec = tvb_get_ntohs(tvb, offset); proto_tree_add_item(tree, hf_spnego_krb5_cfx_ec, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; /* RRC */ #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) rrc = tvb_get_ntohs(tvb, offset); #endif proto_tree_add_item(tree, hf_spnego_krb5_cfx_rrc, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; /* sequence number */ proto_tree_add_item(tree, hf_spnego_krb5_cfx_seq, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; if (gssapi_encrypt == NULL) /* Probably shoudn't happen, but just protect ourselves */ return offset; /* Checksum of plaintext padded data */ if (gssapi_encrypt->gssapi_data_encrypted) { checksum_size = 44 + ec; proto_tree_add_item(tree, hf_spnego_krb5_sgn_cksum, tvb, offset, checksum_size, ENC_NA); offset += checksum_size; } else { int returned_offset; int inner_token_len = 0; /* * We know we have a wrap token, but we have to let the proto * above us decode that, so hand it back in gssapi_wrap_tvb * and put the checksum in the tree. */ checksum_size = ec; inner_token_len = tvb_reported_length_remaining(tvb, offset); if (inner_token_len > ec) { inner_token_len -= ec; } /* * We handle only the two common cases for now * (rrc == 0 and rrc == ec) */ #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) if (rrc == ec) { proto_tree_add_item(tree, hf_spnego_krb5_sgn_cksum, tvb, offset, checksum_size, ENC_NA); offset += checksum_size; } #endif returned_offset = offset; gssapi_encrypt->gssapi_wrap_tvb = tvb_new_subset_length(tvb, offset, inner_token_len); offset += inner_token_len; #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) if (rrc == 0) #endif { proto_tree_add_item(tree, hf_spnego_krb5_sgn_cksum, tvb, offset, checksum_size, ENC_NA); } /* * Return an offset that puts our caller before the inner * token. This is better than before, but we still see the * checksum included in the LDAP query at times. */ return returned_offset; } if(gssapi_encrypt->decrypt_gssapi_tvb){ /* if the caller did not provide a tvb, then we just use whatever is left of our current tvb. */ if(!gssapi_encrypt->gssapi_encrypted_tvb){ int len; len=tvb_reported_length_remaining(tvb,offset); if(len>tvb_captured_length_remaining(tvb, offset)){ /* no point in trying to decrypt, we don't have the full pdu. */ return offset; } gssapi_encrypt->gssapi_encrypted_tvb = tvb_new_subset_length_caplen( tvb, offset, len, len); } if (gssapi_encrypt->gssapi_data_encrypted) { /* do we need to create a tvb for the wrapper as well ? */ if(!gssapi_encrypt->gssapi_wrap_tvb){ gssapi_encrypt->gssapi_wrap_tvb = tvb_new_subset_length( tvb, start_offset-2, offset - (start_offset-2)); } } } #if defined(HAVE_HEIMDAL_KERBEROS) || defined(HAVE_MIT_KERBEROS) { tvbuff_t *checksum_tvb = tvb_new_subset_length(tvb, 16, checksum_size); if (gssapi_encrypt->gssapi_data_encrypted) { if(gssapi_encrypt->gssapi_encrypted_tvb){ decrypt_gssapi_krb_cfx_wrap(tree, pinfo, checksum_tvb, gssapi_encrypt, ec, rrc, -1, (flags & 0x0001)? KRB5_KU_USAGE_ACCEPTOR_SEAL: KRB5_KU_USAGE_INITIATOR_SEAL); } } } #endif /* HAVE_HEIMDAL_KERBEROS || HAVE_MIT_KERBEROS */ /* * Return the offset past the checksum, so that we know where * the data we're wrapped around starts. Also, set the length * of our top-level item to that offset, so it doesn't cover * the data we're wrapped around. * * Note that for DCERPC the GSSAPI blobs comes after the data it wraps, * not before. */ return offset; } /* * XXX - This is for GSSAPI CFX GetMIC tokens ... */ static int dissect_spnego_krb5_cfx_getmic_base(tvbuff_t *tvb, int offset, packet_info *pinfo _U_, proto_tree *tree) { guint8 flags; int checksum_size; /* * The KRB5 blob conforms to RFC4121: * USHORT (0x0404 == GSS_GetMIC) * and so on } */ flags = tvb_get_guint8(tvb, offset); offset = dissect_spnego_krb5_cfx_flags(tvb, offset, tree, flags); /* Skip the filler */ proto_tree_add_item(tree, hf_spnego_krb5_filler, tvb, offset, 5, ENC_NA); offset += 5; /* sequence number */ proto_tree_add_item(tree, hf_spnego_krb5_cfx_seq, tvb, offset, 8, ENC_BIG_ENDIAN); offset += 8; /* Checksum of plaintext padded data */ checksum_size = tvb_captured_length_remaining(tvb, offset); proto_tree_add_item(tree, hf_spnego_krb5_sgn_cksum, tvb, offset, checksum_size, ENC_NA); offset += checksum_size; /* * Return the offset past the checksum, so that we know where * the data we're wrapped around starts. Also, set the length * of our top-level item to that offset, so it doesn't cover * the data we're wrapped around. */ return offset; } /* * XXX - is this for SPNEGO or just GSS-API? * RFC 1964 is "The Kerberos Version 5 GSS-API Mechanism"; presumably one * can directly designate Kerberos V5 as a mechanism in GSS-API, rather * than designating SPNEGO as the mechanism, offering Kerberos V5, and * getting it accepted. */ static int dissect_spnego_krb5_wrap(tvbuff_t *tvb, packet_info *pinfo _U_, proto_tree *tree, void *data) { proto_item *item; proto_tree *subtree; int offset = 0; guint16 token_id; gssapi_encrypt_info_t* encrypt_info = (gssapi_encrypt_info_t*)data; item = proto_tree_add_item(tree, hf_spnego_krb5, tvb, 0, -1, ENC_NA); subtree = proto_item_add_subtree(item, ett_spnego_krb5); /* * The KRB5 blob conforms to RFC1964: * USHORT (0x0102 == GSS_Wrap) * and so on } */ /* First, the token ID ... */ token_id = tvb_get_letohs(tvb, offset); proto_tree_add_uint(subtree, hf_spnego_krb5_tok_id, tvb, offset, 2, token_id); offset += 2; switch (token_id) { case KRB_TOKEN_GETMIC: offset = dissect_spnego_krb5_getmic_base(tvb, offset, pinfo, subtree); break; case KRB_TOKEN_WRAP: offset = dissect_spnego_krb5_wrap_base(tvb, offset, pinfo, subtree, token_id, encrypt_info); break; case KRB_TOKEN_CFX_GETMIC: offset = dissect_spnego_krb5_cfx_getmic_base(tvb, offset, pinfo, subtree); break; case KRB_TOKEN_CFX_WRAP: offset = dissect_spnego_krb5_cfx_wrap_base(tvb, offset, pinfo, subtree, token_id, encrypt_info); break; default: break; } /* * Return the offset past the checksum, so that we know where * the data we're wrapped around starts. Also, set the length * of our top-level item to that offset, so it doesn't cover * the data we're wrapped around. */ proto_item_set_len(item, offset); return offset; } /* Spnego stuff from here */ static int dissect_spnego_wrap(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { proto_item *item; proto_tree *subtree; int offset = 0; asn1_ctx_t asn1_ctx; asn1_ctx_init(&asn1_ctx, ASN1_ENC_BER, TRUE, pinfo); MechType_oid = NULL; /* * We need this later, so lets get it now ... * It has to be per-frame as there can be more than one GSS-API * negotiation in a conversation. */ item = proto_tree_add_item(tree, proto_spnego, tvb, offset, -1, ENC_NA); subtree = proto_item_add_subtree(item, ett_spnego); /* * The TVB contains a [0] header and a sequence that consists of an * object ID and a blob containing the data ... * XXX - is this RFC 2743's "Mechanism-Independent Token Format", * with the "optional" "use in non-initial tokens" being chosen. * ASN1 code addet to spnego.asn to handle this. */ offset = dissect_spnego_InitialContextToken(FALSE, tvb, offset, &asn1_ctx , subtree, -1); return offset; } static int dissect_spnego(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree, void* data _U_) { proto_item *item; proto_tree *subtree; int offset = 0; conversation_t *conversation; asn1_ctx_t asn1_ctx; asn1_ctx_init(&asn1_ctx, ASN1_ENC_BER, TRUE, pinfo); /* * We need this later, so lets get it now ... * It has to be per-frame as there can be more than one GSS-API * negotiation in a conversation. */ next_level_value = (gssapi_oid_value *)p_get_proto_data(wmem_file_scope(), pinfo, proto_spnego, 0); if (!next_level_value && !pinfo->fd->visited) { /* * No handle attached to this frame, but it's the first * pass, so it'd be attached to the conversation. * If we have a conversation, try to get the handle, * and if we get one, attach it to the frame. */ conversation = find_conversation_pinfo(pinfo, 0); if (conversation) { next_level_value = (gssapi_oid_value *)conversation_get_proto_data(conversation, proto_spnego); if (next_level_value) p_add_proto_data(wmem_file_scope(), pinfo, proto_spnego, 0, next_level_value); } } item = proto_tree_add_item(parent_tree, proto_spnego, tvb, offset, -1, ENC_NA); subtree = proto_item_add_subtree(item, ett_spnego); /* * The TVB contains a [0] header and a sequence that consists of an * object ID and a blob containing the data ... * Actually, it contains, according to RFC2478: * NegotiationToken ::= CHOICE { * negTokenInit [0] NegTokenInit, * negTokenTarg [1] NegTokenTarg } * NegTokenInit ::= SEQUENCE { * mechTypes [0] MechTypeList OPTIONAL, * reqFlags [1] ContextFlags OPTIONAL, * mechToken [2] OCTET STRING OPTIONAL, * mechListMIC [3] OCTET STRING OPTIONAL } * NegTokenTarg ::= SEQUENCE { * negResult [0] ENUMERATED { * accept_completed (0), * accept_incomplete (1), * reject (2) } OPTIONAL, * supportedMech [1] MechType OPTIONAL, * responseToken [2] OCTET STRING OPTIONAL, * mechListMIC [3] OCTET STRING OPTIONAL } * * Windows typically includes mechTypes and mechListMic ('NONE' * in the case of NTLMSSP only). * It seems to duplicate the responseToken into the mechListMic field * as well. Naughty, naughty. * */ dissect_spnego_NegotiationToken(FALSE, tvb, offset, &asn1_ctx, subtree, -1); return tvb_captured_length(tvb); } /*--- proto_register_spnego -------------------------------------------*/ void proto_register_spnego(void) { /* List of fields */ static hf_register_info hf[] = { { &hf_spnego_wraptoken, { "wrapToken", "spnego.wraptoken", FT_NONE, BASE_NONE, NULL, 0x0, "SPNEGO wrapToken", HFILL}}, { &hf_spnego_krb5, { "krb5_blob", "spnego.krb5.blob", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_krb5_oid, { "KRB5 OID", "spnego.krb5_oid", FT_STRING, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_krb5_tok_id, { "krb5_tok_id", "spnego.krb5.tok_id", FT_UINT16, BASE_HEX, VALS(spnego_krb5_tok_id_vals), 0, "KRB5 Token Id", HFILL}}, { &hf_spnego_krb5_sgn_alg, { "krb5_sgn_alg", "spnego.krb5.sgn_alg", FT_UINT16, BASE_HEX, VALS(spnego_krb5_sgn_alg_vals), 0, "KRB5 Signing Algorithm", HFILL}}, { &hf_spnego_krb5_seal_alg, { "krb5_seal_alg", "spnego.krb5.seal_alg", FT_UINT16, BASE_HEX, VALS(spnego_krb5_seal_alg_vals), 0, "KRB5 Sealing Algorithm", HFILL}}, { &hf_spnego_krb5_snd_seq, { "krb5_snd_seq", "spnego.krb5.snd_seq", FT_BYTES, BASE_NONE, NULL, 0, "KRB5 Encrypted Sequence Number", HFILL}}, { &hf_spnego_krb5_sgn_cksum, { "krb5_sgn_cksum", "spnego.krb5.sgn_cksum", FT_BYTES, BASE_NONE, NULL, 0, "KRB5 Data Checksum", HFILL}}, { &hf_spnego_krb5_confounder, { "krb5_confounder", "spnego.krb5.confounder", FT_BYTES, BASE_NONE, NULL, 0, "KRB5 Confounder", HFILL}}, { &hf_spnego_krb5_filler, { "krb5_filler", "spnego.krb5.filler", FT_BYTES, BASE_NONE, NULL, 0, "KRB5 Filler", HFILL}}, { &hf_spnego_krb5_cfx_flags, { "krb5_cfx_flags", "spnego.krb5.cfx_flags", FT_UINT8, BASE_HEX, NULL, 0, "KRB5 CFX Flags", HFILL}}, { &hf_spnego_krb5_cfx_flags_01, { "SendByAcceptor", "spnego.krb5.send_by_acceptor", FT_BOOLEAN, 8, TFS (&tfs_set_notset), 0x01, NULL, HFILL}}, { &hf_spnego_krb5_cfx_flags_02, { "Sealed", "spnego.krb5.sealed", FT_BOOLEAN, 8, TFS (&tfs_set_notset), 0x02, NULL, HFILL}}, { &hf_spnego_krb5_cfx_flags_04, { "AcceptorSubkey", "spnego.krb5.acceptor_subkey", FT_BOOLEAN, 8, TFS (&tfs_set_notset), 0x04, NULL, HFILL}}, { &hf_spnego_krb5_cfx_ec, { "krb5_cfx_ec", "spnego.krb5.cfx_ec", FT_UINT16, BASE_DEC, NULL, 0, "KRB5 CFX Extra Count", HFILL}}, { &hf_spnego_krb5_cfx_rrc, { "krb5_cfx_rrc", "spnego.krb5.cfx_rrc", FT_UINT16, BASE_DEC, NULL, 0, "KRB5 CFX Right Rotation Count", HFILL}}, { &hf_spnego_krb5_cfx_seq, { "krb5_cfx_seq", "spnego.krb5.cfx_seq", FT_UINT64, BASE_DEC, NULL, 0, "KRB5 Sequence Number", HFILL}}, { &hf_spnego_negTokenInit, { "negTokenInit", "spnego.negTokenInit_element", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_negTokenTarg, { "negTokenTarg", "spnego.negTokenTarg_element", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_MechTypeList_item, { "MechType", "spnego.MechType", FT_OID, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_mechTypes, { "mechTypes", "spnego.mechTypes", FT_UINT32, BASE_DEC, NULL, 0, "MechTypeList", HFILL }}, { &hf_spnego_reqFlags, { "reqFlags", "spnego.reqFlags", FT_BYTES, BASE_NONE, NULL, 0, "ContextFlags", HFILL }}, { &hf_spnego_mechToken, { "mechToken", "spnego.mechToken", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_mechListMIC, { "mechListMIC", "spnego.mechListMIC", FT_BYTES, BASE_NONE, NULL, 0, "OCTET_STRING", HFILL }}, { &hf_spnego_hintName, { "hintName", "spnego.hintName", FT_STRING, BASE_NONE, NULL, 0, "GeneralString", HFILL }}, { &hf_spnego_hintAddress, { "hintAddress", "spnego.hintAddress", FT_BYTES, BASE_NONE, NULL, 0, "OCTET_STRING", HFILL }}, { &hf_spnego_mechToken_01, { "mechToken", "spnego.mechToken", FT_BYTES, BASE_NONE, NULL, 0, "OCTET_STRING", HFILL }}, { &hf_spnego_negHints, { "negHints", "spnego.negHints_element", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_negResult, { "negResult", "spnego.negResult", FT_UINT32, BASE_DEC, VALS(spnego_T_negResult_vals), 0, NULL, HFILL }}, { &hf_spnego_supportedMech, { "supportedMech", "spnego.supportedMech", FT_OID, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_responseToken, { "responseToken", "spnego.responseToken", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_mechListMIC_01, { "mechListMIC", "spnego.mechListMIC", FT_BYTES, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_thisMech, { "thisMech", "spnego.thisMech", FT_OID, BASE_NONE, NULL, 0, "MechType", HFILL }}, { &hf_spnego_innerContextToken, { "innerContextToken", "spnego.innerContextToken_element", FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL }}, { &hf_spnego_ContextFlags_delegFlag, { "delegFlag", "spnego.ContextFlags.delegFlag", FT_BOOLEAN, 8, NULL, 0x80, NULL, HFILL }}, { &hf_spnego_ContextFlags_mutualFlag, { "mutualFlag", "spnego.ContextFlags.mutualFlag", FT_BOOLEAN, 8, NULL, 0x40, NULL, HFILL }}, { &hf_spnego_ContextFlags_replayFlag, { "replayFlag", "spnego.ContextFlags.replayFlag", FT_BOOLEAN, 8, NULL, 0x20, NULL, HFILL }}, { &hf_spnego_ContextFlags_sequenceFlag, { "sequenceFlag", "spnego.ContextFlags.sequenceFlag", FT_BOOLEAN, 8, NULL, 0x10, NULL, HFILL }}, { &hf_spnego_ContextFlags_anonFlag, { "anonFlag", "spnego.ContextFlags.anonFlag", FT_BOOLEAN, 8, NULL, 0x08, NULL, HFILL }}, { &hf_spnego_ContextFlags_confFlag, { "confFlag", "spnego.ContextFlags.confFlag", FT_BOOLEAN, 8, NULL, 0x04, NULL, HFILL }}, { &hf_spnego_ContextFlags_integFlag, { "integFlag", "spnego.ContextFlags.integFlag", FT_BOOLEAN, 8, NULL, 0x02, NULL, HFILL }}, }; /* List of subtrees */ static gint *ett[] = { &ett_spnego, &ett_spnego_wraptoken, &ett_spnego_krb5, &ett_spnego_krb5_cfx_flags, &ett_spnego_NegotiationToken, &ett_spnego_MechTypeList, &ett_spnego_NegTokenInit, &ett_spnego_NegHints, &ett_spnego_NegTokenInit2, &ett_spnego_ContextFlags, &ett_spnego_NegTokenTarg, &ett_spnego_InitialContextToken_U, }; static ei_register_info ei[] = { { &ei_spnego_decrypted_keytype, { "spnego.decrypted_keytype", PI_SECURITY, PI_CHAT, "Decrypted keytype", EXPFILL }}, { &ei_spnego_unknown_header, { "spnego.unknown_header", PI_PROTOCOL, PI_WARN, "Unknown header", EXPFILL }}, }; expert_module_t* expert_spnego; /* Register protocol */ proto_spnego = proto_register_protocol(PNAME, PSNAME, PFNAME); spnego_handle = register_dissector("spnego", dissect_spnego, proto_spnego); spnego_wrap_handle = register_dissector("spnego-wrap", dissect_spnego_wrap, proto_spnego); proto_spnego_krb5 = proto_register_protocol("SPNEGO-KRB5", "SPNEGO-KRB5", "spnego-krb5"); spnego_krb5_handle = register_dissector("spnego-krb5", dissect_spnego_krb5, proto_spnego_krb5); spnego_krb5_wrap_handle = register_dissector("spnego-krb5-wrap", dissect_spnego_krb5_wrap, proto_spnego_krb5); /* Register fields and subtrees */ proto_register_field_array(proto_spnego, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); expert_spnego = expert_register_protocol(proto_spnego); expert_register_field_array(expert_spnego, ei, array_length(ei)); } /*--- proto_reg_handoff_spnego ---------------------------------------*/ void proto_reg_handoff_spnego(void) { /* Register protocol with GSS-API module */ gssapi_init_oid("1.3.6.1.5.5.2", proto_spnego, ett_spnego, spnego_handle, spnego_wrap_handle, "SPNEGO - Simple Protected Negotiation"); /* Register both the one MS created and the real one */ /* * Thanks to Jean-Baptiste Marchand and Richard B Ward, the * mystery of the MS KRB5 OID is cleared up. It was due to a library * that did not handle OID components greater than 16 bits, and was * fixed in Win2K SP2 as well as WinXP. * See the archive of for the thread topic * SPNEGO implementation issues. 3-Dec-2002. */ gssapi_init_oid("1.2.840.48018.1.2.2", proto_spnego_krb5, ett_spnego_krb5, spnego_krb5_handle, spnego_krb5_wrap_handle, "MS KRB5 - Microsoft Kerberos 5"); gssapi_init_oid("1.2.840.113554.1.2.2", proto_spnego_krb5, ett_spnego_krb5, spnego_krb5_handle, spnego_krb5_wrap_handle, "KRB5 - Kerberos 5"); gssapi_init_oid("1.2.840.113554.1.2.2.3", proto_spnego_krb5, ett_spnego_krb5, spnego_krb5_handle, spnego_krb5_wrap_handle, "KRB5 - Kerberos 5 - User to User"); } /* * Editor modelines * * Local Variables: * c-basic-offset: 2 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=2 tabstop=8 expandtab: * :indentSize=2:tabSize=8:noTabs=true: */