X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=mech_eap%2Finit_sec_context.c;h=2a1d3e029fea549bc43a52974d6a6cd455104b79;hb=cd961180d47d76e55de1c09e3ec6d7448f4e3359;hp=8a877fdbcb8c246e07d9c626675543304bd3be71;hpb=a771578767d77a45ab0bddb814aac565dae7a3e0;p=mech_eap.git diff --git a/mech_eap/init_sec_context.c b/mech_eap/init_sec_context.c index 8a877fd..2a1d3e0 100644 --- a/mech_eap/init_sec_context.c +++ b/mech_eap/init_sec_context.c @@ -36,6 +36,16 @@ */ #include "gssapiP_eap.h" +#include "radius/radius.h" +#include "util_radius.h" +#include "utils/radius_utils.h" +#include "openssl/err.h" +#include "libmoonshot.h" + +/* methods allowed for phase1 authentication*/ +static const struct eap_method_type allowed_eap_method_types[] = { + {EAP_VENDOR_IETF, EAP_TYPE_TTLS}, + {EAP_VENDOR_IETF, EAP_TYPE_NONE}}; static OM_uint32 policyVariableToFlag(enum eapol_bool_var variable) @@ -70,6 +80,9 @@ policyVariableToFlag(enum eapol_bool_var variable) case EAPOL_altReject: flag = CTX_FLAG_EAP_ALT_REJECT; break; + case EAPOL_eapTriggerStart: + flag = CTX_FLAG_EAP_TRIGGER_START; + break; } return flag; @@ -167,10 +180,22 @@ peerSetConfigBlob(void *ctx GSSEAP_UNUSED, } static const struct wpa_config_blob * -peerGetConfigBlob(void *ctx GSSEAP_UNUSED, - const char *name GSSEAP_UNUSED) +peerGetConfigBlob(void *ctx, + const char *name) { - return NULL; + gss_ctx_id_t gssCtx = (gss_ctx_id_t)ctx; + size_t index; + + if (strcmp(name, "client-cert") == 0) + index = CONFIG_BLOB_CLIENT_CERT; + else if (strcmp(name, "private-key") == 0) + index = CONFIG_BLOB_PRIVATE_KEY; + else if (strcmp(name, "ca-cert") == 0) + index = CONFIG_BLOB_CA_CERT; + else + return NULL; + + return &gssCtx->initiatorCtx.configBlobs[index]; } static void @@ -178,6 +203,18 @@ peerNotifyPending(void *ctx GSSEAP_UNUSED) { } +static void peerNotifyCert(void *ctx GSSEAP_UNUSED, + int depth , + const char *subject GSSEAP_UNUSED, + const char *altsubject[] GSSEAP_UNUSED, + int num_altsubject GSSEAP_UNUSED, + const char *cert_hash GSSEAP_UNUSED, + const struct wpabuf *cert GSSEAP_UNUSED) +{ + printf("peerNotifyCert: depth=%d; hash=%s (%p)\n", depth, cert_hash, cert_hash); +} + + static struct eapol_callbacks gssEapPolicyCallbacks = { peerGetConfig, peerGetBool, @@ -188,11 +225,238 @@ static struct eapol_callbacks gssEapPolicyCallbacks = { peerSetConfigBlob, peerGetConfigBlob, peerNotifyPending, + NULL, /* eap_param_needed */ + peerNotifyCert }; -#ifdef GSSEAP_DEBUG -extern int wpa_debug_level; -#endif + +#define CHBIND_SERVICE_NAME_FLAG 0x01 +#define CHBIND_HOST_NAME_FLAG 0x02 +#define CHBIND_SERVICE_SPECIFIC_FLAG 0x04 +#define CHBIND_REALM_NAME_FLAG 0x08 + +static OM_uint32 +peerInitEapChannelBinding(OM_uint32 *minor, gss_ctx_id_t ctx) +{ + struct wpabuf *buf = NULL; + unsigned int chbindReqFlags = 0; + krb5_principal princ = NULL; + gss_buffer_desc nameBuf = GSS_C_EMPTY_BUFFER; + OM_uint32 major = GSS_S_COMPLETE; + krb5_context krbContext = NULL; + + /* XXX is this check redundant? */ + if (ctx->acceptorName == GSS_C_NO_NAME) { + major = GSS_S_BAD_NAME; + *minor = GSSEAP_NO_ACCEPTOR_NAME; + goto cleanup; + } + + princ = ctx->acceptorName->krbPrincipal; + + krbPrincComponentToGssBuffer(princ, 0, &nameBuf); + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, PW_GSS_ACCEPTOR_SERVICE_NAME, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_SERVICE_NAME_FLAG; + } + + krbPrincComponentToGssBuffer(princ, 1, &nameBuf); + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, PW_GSS_ACCEPTOR_HOST_NAME, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_HOST_NAME_FLAG; + } + + GSSEAP_KRB_INIT(&krbContext); + + *minor = krbPrincUnparseServiceSpecifics(krbContext, princ, &nameBuf); + if (*minor != 0) + goto cleanup; + + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, + PW_GSS_ACCEPTOR_SERVICE_SPECIFICS, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_SERVICE_SPECIFIC_FLAG; + } + + krbFreeUnparsedName(krbContext, &nameBuf); + krbPrincRealmToGssBuffer(princ, &nameBuf); + + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, + PW_GSS_ACCEPTOR_REALM_NAME, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_REALM_NAME_FLAG; + } + + if (chbindReqFlags == 0) { + major = GSS_S_BAD_NAME; + *minor = GSSEAP_BAD_ACCEPTOR_NAME; + goto cleanup; + } + + ctx->initiatorCtx.chbindData = buf; + ctx->initiatorCtx.chbindReqFlags = chbindReqFlags; + + buf = NULL; + + major = GSS_S_COMPLETE; + *minor = 0; + +cleanup: + /*namebuf is freed when used and may be left with a unowned pointer*/ + wpabuf_free(buf); + + return major; +} + +static void +peerProcessChbindResponse(void *context, int code, int nsid, + u8 *data, size_t len) +{ + radius_parser msg; + gss_ctx_id_t ctx = (gss_ctx_id_t )context; + void *vsadata; + u8 type; + u32 vendor_id; + u32 chbindRetFlags = 0; + size_t vsadata_len; + + if (nsid != CHBIND_NSID_RADIUS) + return; + + if (data == NULL) + return; + msg = radius_parser_start(data, len); + if (msg == NULL) + return; + + while (radius_parser_parse_tlv(msg, &type, &vendor_id, &vsadata, + &vsadata_len) == 0) { + switch (type) { + case PW_GSS_ACCEPTOR_SERVICE_NAME: + chbindRetFlags |= CHBIND_SERVICE_NAME_FLAG; + break; + case PW_GSS_ACCEPTOR_HOST_NAME: + chbindRetFlags |= CHBIND_HOST_NAME_FLAG; + break; + case PW_GSS_ACCEPTOR_SERVICE_SPECIFICS: + chbindRetFlags |= CHBIND_SERVICE_SPECIFIC_FLAG; + break; + case PW_GSS_ACCEPTOR_REALM_NAME: + chbindRetFlags |= CHBIND_REALM_NAME_FLAG; + break; + } + } + + radius_parser_finish(msg); + + if (code == CHBIND_CODE_SUCCESS && + ((chbindRetFlags & ctx->initiatorCtx.chbindReqFlags) == ctx->initiatorCtx.chbindReqFlags)) { + ctx->flags |= CTX_FLAG_EAP_CHBIND_ACCEPT; + ctx->gssFlags |= GSS_C_MUTUAL_FLAG; + } /* else log failures? */ +} + +static int cert_to_byte_array(X509 *cert, unsigned char **bytes) +{ + unsigned char *buf; + unsigned char *p; + + int len = i2d_X509(cert, NULL); + if (len <= 0) { + return -1; + } + + p = buf = GSSEAP_MALLOC(len); + if (buf == NULL) { + return -1; + } + + i2d_X509(cert, &buf); + + *bytes = p; + return len; +} + +static int sha256(unsigned char *bytes, int len, unsigned char *hash) +{ + EVP_MD_CTX ctx; + unsigned int hash_len; + + EVP_MD_CTX_init(&ctx); + if (!EVP_DigestInit_ex(&ctx, EVP_sha256(), NULL)) { + printf("sha256(init_sec_context.c): EVP_DigestInit_ex failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + if (!EVP_DigestUpdate(&ctx, bytes, len)) { + printf("sha256(init_sec_context.c): EVP_DigestUpdate failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + if (!EVP_DigestFinal(&ctx, hash, &hash_len)) { + printf("sha256(init_sec_context.c): EVP_DigestFinal failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + return -1; + } + + return hash_len; +} + + +static int peerValidateServer(int ok_so_far, X509* cert, void *ca_ctx) +{ + const char *realm = NULL; + unsigned char *cert_bytes = NULL; + int cert_len; + unsigned char hash[32]; + int hash_len; + MoonshotError *error = NULL; + struct eap_peer_config *eap_config = (struct eap_peer_config *) ca_ctx; + char *identity = strdup((const char *) eap_config->identity); + + // Truncate the identity to just the username + char* at = strchr(identity, '@'); + if (at != NULL) { + *at = '\0'; + } + + cert_len = cert_to_byte_array(cert, &cert_bytes); + hash_len = sha256(cert_bytes, cert_len, hash); + GSSEAP_FREE(cert_bytes); + + if (hash_len != 32) { + printf("peerValidateServer: Error: hash_len=%d, not 32!\n", hash_len); + return FALSE; + } + + /* This is ugly, but it works -- anonymous_identity is '@' + realm + * (see peerConfigInit) + */ + realm = ((char *) eap_config->anonymous_identity) + 1; + + ok_so_far = moonshot_confirm_ca_certificate(identity, realm, hash, 32, &error); + free(identity); + + printf("peerValidateServer: Returning %d\n", ok_so_far); + return ok_so_far; +} + static OM_uint32 peerConfigInit(OM_uint32 *minor, gss_ctx_id_t ctx) @@ -200,6 +464,7 @@ peerConfigInit(OM_uint32 *minor, gss_ctx_id_t ctx) OM_uint32 major; krb5_context krbContext; struct eap_peer_config *eapPeerConfig = &ctx->initiatorCtx.eapPeerConfig; + struct wpa_config_blob *configBlobs = ctx->initiatorCtx.configBlobs; gss_buffer_desc identity = GSS_C_EMPTY_BUFFER; gss_buffer_desc realm = GSS_C_EMPTY_BUFFER; gss_cred_id_t cred = ctx->cred; @@ -210,16 +475,14 @@ peerConfigInit(OM_uint32 *minor, gss_ctx_id_t ctx) eapPeerConfig->anonymous_identity_len = 0; eapPeerConfig->password = NULL; eapPeerConfig->password_len = 0; + eapPeerConfig->eap_methods = (struct eap_method_type *) allowed_eap_method_types; GSSEAP_ASSERT(cred != GSS_C_NO_CREDENTIAL); GSSEAP_KRB_INIT(&krbContext); eapPeerConfig->fragment_size = 1024; -#ifdef GSSEAP_DEBUG - wpa_debug_level = 0; -#endif - + GSSEAP_ASSERT(cred->name != GSS_C_NO_NAME); if ((cred->name->flags & (NAME_FLAG_NAI | NAME_FLAG_SERVICE)) == 0) { @@ -259,13 +522,53 @@ peerConfigInit(OM_uint32 *minor, gss_ctx_id_t ctx) eapPeerConfig->ca_cert = (unsigned char *)cred->caCertificate.value; eapPeerConfig->subject_match = (unsigned char *)cred->subjectNameConstraint.value; eapPeerConfig->altsubject_match = (unsigned char *)cred->subjectAltNameConstraint.value; + configBlobs[CONFIG_BLOB_CA_CERT].data = cred->caCertificateBlob.value; + configBlobs[CONFIG_BLOB_CA_CERT].len = cred->caCertificateBlob.length; + + /* eap channel binding */ + if (ctx->initiatorCtx.chbindData != NULL) { + struct eap_peer_chbind_config *chbind_config = + (struct eap_peer_chbind_config *)GSSEAP_MALLOC(sizeof(struct eap_peer_chbind_config)); + if (chbind_config == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + chbind_config->req_data = wpabuf_mhead_u8(ctx->initiatorCtx.chbindData); + chbind_config->req_data_len = wpabuf_len(ctx->initiatorCtx.chbindData); + chbind_config->nsid = CHBIND_NSID_RADIUS; + chbind_config->response_cb = &peerProcessChbindResponse; + chbind_config->ctx = ctx; + eapPeerConfig->chbind_config = chbind_config; + eapPeerConfig->chbind_config_len = 1; + } else { + eapPeerConfig->chbind_config = NULL; + eapPeerConfig->chbind_config_len = 0; + } if (cred->flags & CRED_FLAG_CERTIFICATE) { - eapPeerConfig->client_cert = (unsigned char *)cred->clientCertificate.value; - eapPeerConfig->private_key = (unsigned char *)cred->privateKey.value; - eapPeerConfig->private_key_passwd = (unsigned char *)cred->password.value; + /* + * CRED_FLAG_CONFIG_BLOB is an internal flag which will be used in the + * future to directly pass certificate and private key data to the + * EAP implementation, rather than an indirected string pointer. + */ + if (cred->flags & CRED_FLAG_CONFIG_BLOB) { + eapPeerConfig->client_cert = (unsigned char *)"blob://client-cert"; + configBlobs[CONFIG_BLOB_CLIENT_CERT].data = cred->clientCertificate.value; + configBlobs[CONFIG_BLOB_CLIENT_CERT].len = cred->clientCertificate.length; + + eapPeerConfig->client_cert = (unsigned char *)"blob://private-key"; + configBlobs[CONFIG_BLOB_PRIVATE_KEY].data = cred->clientCertificate.value; + configBlobs[CONFIG_BLOB_PRIVATE_KEY].len = cred->privateKey.length; + } else { + eapPeerConfig->client_cert = (unsigned char *)cred->clientCertificate.value; + eapPeerConfig->private_key = (unsigned char *)cred->privateKey.value; + } + eapPeerConfig->private_key_passwd = (char *)cred->password.value; } + eapPeerConfig->server_cert_cb = peerValidateServer; + eapPeerConfig->server_cert_ctx = eapPeerConfig; + *minor = 0; return GSS_S_COMPLETE; } @@ -296,18 +599,12 @@ peerConfigFree(OM_uint32 *minor, * Mark an initiator context as ready for cryptographic operations */ static OM_uint32 -initReady(OM_uint32 *minor, gss_ctx_id_t ctx, OM_uint32 reqFlags) +initReady(OM_uint32 *minor, gss_ctx_id_t ctx) { OM_uint32 major; const unsigned char *key; size_t keyLength; -#if 1 - /* XXX actually check for mutual auth */ - if (reqFlags & GSS_C_MUTUAL_FLAG) - ctx->gssFlags |= GSS_C_MUTUAL_FLAG; -#endif - /* Cache encryption type derived from selected mechanism OID */ major = gssEapOidToEnctype(minor, ctx->mechanismUsed, &ctx->encryptionType); if (GSS_ERROR(major)) @@ -434,7 +731,10 @@ eapGssSmInitError(OM_uint32 *minor, p = (unsigned char *)inputToken->value; major = load_uint32_be(&p[0]); - *minor = ERROR_TABLE_BASE_eapg + load_uint32_be(&p[4]); + *minor = load_uint32_be(&p[4]); + if ((*minor >0) && (*minor < 128)) + * minor += ERROR_TABLE_BASE_eapg; + else *minor = 0; if (!GSS_ERROR(major) || !IS_WIRE_ERROR(*minor)) { major = GSS_S_FAILURE; @@ -569,17 +869,45 @@ eapGssSmInitAcceptorName(OM_uint32 *minor, outputToken, NULL); if (GSS_ERROR(major)) return major; - } else if (inputToken != GSS_C_NO_BUFFER && - ctx->acceptorName == GSS_C_NO_NAME) { - /* Accept target name hint from acceptor */ + } else if (inputToken != GSS_C_NO_BUFFER) { + OM_uint32 tmpMinor; + gss_name_t nameHint; + int equal; + + /* Accept target name hint from acceptor or verify acceptor */ major = gssEapImportName(minor, inputToken, GSS_C_NT_USER_NAME, ctx->mechanismUsed, - &ctx->acceptorName); + &nameHint); if (GSS_ERROR(major)) return major; + + if (ctx->acceptorName != GSS_C_NO_NAME) { + /* verify name hint matched asserted acceptor name */ + major = gssEapCompareName(minor, + nameHint, + ctx->acceptorName, + COMPARE_NAME_FLAG_IGNORE_EMPTY_REALMS, + &equal); + if (GSS_ERROR(major)) { + gssEapReleaseName(&tmpMinor, &nameHint); + return major; + } + + gssEapReleaseName(&tmpMinor, &nameHint); + + if (!equal) { + *minor = GSSEAP_WRONG_ACCEPTOR_NAME; + return GSS_S_DEFECTIVE_TOKEN; + } + } else { /* acceptor name is no_name */ + /* accept acceptor name hint */ + ctx->acceptorName = nameHint; + nameHint = GSS_C_NO_NAME; + } } + /* * Currently, other parts of the code assume that the acceptor name * is available, hence this check. @@ -589,6 +917,15 @@ eapGssSmInitAcceptorName(OM_uint32 *minor, return GSS_S_FAILURE; } + /* + * Generate channel binding data + */ + if (ctx->initiatorCtx.chbindData == NULL) { + major = peerInitEapChannelBinding(minor, ctx); + if (GSS_ERROR(major)) + return major; + } + return GSS_S_CONTINUE_NEEDED; } @@ -606,6 +943,8 @@ eapGssSmInitIdentity(OM_uint32 *minor, OM_uint32 *smFlags) { struct eap_config eapConfig; + memset(&eapConfig, 0, sizeof(eapConfig)); + eapConfig.cert_in_cb = 1; #ifdef GSSEAP_ENABLE_REAUTH if (GSSEAP_SM_STATE(ctx) == GSSEAP_STATE_REAUTHENTICATE) { @@ -622,11 +961,9 @@ eapGssSmInitIdentity(OM_uint32 *minor, GSSEAP_ASSERT((ctx->flags & CTX_FLAG_KRB_REAUTH) == 0); GSSEAP_ASSERT(inputToken == GSS_C_NO_BUFFER); - memset(&eapConfig, 0, sizeof(eapConfig)); - ctx->initiatorCtx.eap = eap_peer_sm_init(ctx, &gssEapPolicyCallbacks, - ctx, + NULL, /* ctx?? */ &eapConfig); if (ctx->initiatorCtx.eap == NULL) { *minor = GSSEAP_PEER_SM_INIT_FAILURE; @@ -689,7 +1026,7 @@ eapGssSmInitAuthenticate(OM_uint32 *minor, resp = eap_get_eapRespData(ctx->initiatorCtx.eap); } else if (ctx->flags & CTX_FLAG_EAP_SUCCESS) { - major = initReady(minor, ctx, reqFlags); + major = initReady(minor, ctx); if (GSS_ERROR(major)) goto cleanup; @@ -745,6 +1082,11 @@ eapGssSmInitGssFlags(OM_uint32 *minor, unsigned char wireFlags[4]; gss_buffer_desc flagsBuf; + /* + * As a temporary measure, force mutual authentication until channel binding is + * more widely deployed. + */ + ctx->gssFlags |= GSS_C_MUTUAL_FLAG; store_uint32_be(ctx->gssFlags & GSSEAP_WIRE_FLAGS_MASK, wireFlags); flagsBuf.length = sizeof(wireFlags); @@ -767,21 +1109,45 @@ eapGssSmInitGssChannelBindings(OM_uint32 *minor, OM_uint32 *smFlags) { OM_uint32 major; - gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + krb5_error_code code; + krb5_context krbContext; + krb5_data data; + krb5_checksum cksum; + gss_buffer_desc cksumBuffer; - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS) - buffer = chanBindings->application_data; + if (chanBindings == GSS_C_NO_CHANNEL_BINDINGS || + chanBindings->application_data.length == 0) + return GSS_S_CONTINUE_NEEDED; - major = gssEapWrap(minor, ctx, TRUE, GSS_C_QOP_DEFAULT, - &buffer, NULL, outputToken); - if (GSS_ERROR(major)) - return major; + GSSEAP_KRB_INIT(&krbContext); + + KRB_DATA_INIT(&data); + + gssBufferToKrbData(&chanBindings->application_data, &data); - GSSEAP_ASSERT(outputToken->value != NULL); + code = krb5_c_make_checksum(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + KEY_USAGE_GSSEAP_CHBIND_MIC, + &data, &cksum); + if (code != 0) { + *minor = code; + return GSS_S_FAILURE; + } + + cksumBuffer.length = KRB_CHECKSUM_LENGTH(&cksum); + cksumBuffer.value = KRB_CHECKSUM_DATA(&cksum); + + major = duplicateBuffer(minor, &cksumBuffer, outputToken); + if (GSS_ERROR(major)) { + krb5_free_checksum_contents(krbContext, &cksum); + return major; + } *minor = 0; *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; + krb5_free_checksum_contents(krbContext, &cksum); + return GSS_S_CONTINUE_NEEDED; } @@ -811,7 +1177,7 @@ eapGssSmInitInitiatorMIC(OM_uint32 *minor, return GSS_S_CONTINUE_NEEDED; } - + #ifdef GSSEAP_ENABLE_REAUTH static OM_uint32 eapGssSmInitReauthCreds(OM_uint32 *minor, @@ -876,7 +1242,8 @@ static struct gss_eap_sm eapGssInitiatorSm[] = { { ITOK_TYPE_ACCEPTOR_NAME_RESP, ITOK_TYPE_ACCEPTOR_NAME_REQ, - GSSEAP_STATE_INITIAL | GSSEAP_STATE_AUTHENTICATE, + GSSEAP_STATE_INITIAL | GSSEAP_STATE_AUTHENTICATE | + GSSEAP_STATE_ACCEPTOR_EXTS, 0, eapGssSmInitAcceptorName }, @@ -926,7 +1293,7 @@ static struct gss_eap_sm eapGssInitiatorSm[] = { ITOK_TYPE_NONE, ITOK_TYPE_GSS_CHANNEL_BINDINGS, GSSEAP_STATE_INITIATOR_EXTS, - SM_ITOK_FLAG_REQUIRED, + 0, eapGssSmInitGssChannelBindings }, { @@ -1026,8 +1393,10 @@ gssEapInitSecContext(OM_uint32 *minor, goto cleanup; } } + if (ret_flags != NULL) *ret_flags = ctx->gssFlags; + if (time_rec != NULL) gssEapContextTime(&tmpMinor, ctx, time_rec); @@ -1101,5 +1470,7 @@ gss_init_sec_context(OM_uint32 *minor, if (GSS_ERROR(major)) gssEapReleaseContext(&tmpMinor, context_handle); + gssEapTraceStatus( "gss_init_sec_context", major, *minor); return major; } +