/*
- * Copyright (c) 2010, JANET(UK)
+ * Copyright (c) 2011, JANET(UK)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* SUCH DAMAGE.
*/
/*
- * lib/gssapi/krb5/k5sealv3iov.c
- *
* Copyright 2008 by the Massachusetts Institute of Technology.
* All Rights Reserved.
*
* M.I.T. makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
- *
- *
+ */
+
+/*
+ * Message protection services: unwrap with scatter-gather API.
*/
#include "gssapiP_eap.h"
+/*
+ * Caller must provide TOKEN | DATA | PADDING | TRAILER, except
+ * for DCE in which case it can just provide TOKEN | DATA (must
+ * guarantee that DATA is padded)
+ */
OM_uint32
-gssEapUnwrapOrVerifyMIC(OM_uint32 *minor_status,
- gss_ctx_id_t ctx,
- int *conf_state,
- gss_qop_t *qop_state,
- gss_iov_buffer_desc *iov,
- int iov_count,
- enum gss_eap_token_type toktype)
+unwrapToken(OM_uint32 *minor,
+ gss_ctx_id_t ctx,
+#ifdef HAVE_HEIMDAL_VERSION
+ krb5_crypto krbCrypto,
+#else
+ krb5_keyblock *unused,
+#endif
+ int *conf_state,
+ gss_qop_t *qop_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count,
+ enum gss_eap_token_type toktype)
{
- OM_uint32 code;
+ OM_uint32 major = GSS_S_FAILURE, code;
gss_iov_buffer_t header;
gss_iov_buffer_t padding;
gss_iov_buffer_t trailer;
- unsigned char acceptor_flag;
+ unsigned char flags;
unsigned char *ptr = NULL;
- int key_usage;
+ int keyUsage;
size_t rrc, ec;
- size_t data_length, assoc_data_length;
+ size_t dataLen, assocDataLen;
uint64_t seqnum;
- krb5_boolean valid;
- krb5_cksumtype cksumtype;
+ int valid = 0;
int conf_flag = 0;
+ krb5_context krbContext;
+#ifdef HAVE_HEIMDAL_VERSION
+ int freeCrypto = (krbCrypto == NULL);
+#endif
+
+ GSSEAP_KRB_INIT(&krbContext);
- *minor_status = 0;
+ *minor = 0;
if (qop_state != NULL)
*qop_state = GSS_C_QOP_DEFAULT;
- if (!CTX_IS_ESTABLISHED(ctx))
- return GSS_S_NO_CONTEXT;
-
header = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_HEADER);
assert(header != NULL);
padding = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_PADDING);
- if (padding != NULL && padding->buffer.length != 0)
- return GSS_S_DEFECTIVE_TOKEN;
+ if (padding != NULL && padding->buffer.length != 0) {
+ code = GSSEAP_BAD_PADDING_IOV;
+ major = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
+ }
trailer = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_TRAILER);
- acceptor_flag = CTX_IS_INITIATOR(ctx) ? TOK_FLAG_SENDER_IS_ACCEPTOR : 0;
- key_usage = (toktype == TOK_TYPE_WRAP
- ? (!CTX_IS_INITIATOR(ctx)
- ? KRB_USAGE_INITIATOR_SEAL
- : KRB_USAGE_ACCEPTOR_SEAL)
- : (!CTX_IS_INITIATOR(ctx)
- ? KRB_USAGE_INITIATOR_SIGN
- : KRB_USAGE_ACCEPTOR_SIGN));
+ flags = rfc4121Flags(ctx, TRUE);
+
+ if (toktype == TOK_TYPE_WRAP) {
+ keyUsage = !CTX_IS_INITIATOR(ctx)
+ ? KEY_USAGE_INITIATOR_SEAL
+ : KEY_USAGE_ACCEPTOR_SEAL;
+ } else {
+ keyUsage = !CTX_IS_INITIATOR(ctx)
+ ? KEY_USAGE_INITIATOR_SIGN
+ : KEY_USAGE_ACCEPTOR_SIGN;
+ }
- gssEapIovMessageLength(iov, iov_count, &data_length, &assoc_data_length);
+ gssEapIovMessageLength(iov, iov_count, &dataLen, &assocDataLen);
ptr = (unsigned char *)header->buffer.value;
if (header->buffer.length < 16) {
- *minor_status = 0;
- return GSS_S_DEFECTIVE_TOKEN;
+ code = GSSEAP_TOK_TRUNC;
+ major = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
}
- if ((ptr[2] & TOK_FLAG_SENDER_IS_ACCEPTOR) != acceptor_flag) {
- return GSS_S_BAD_SIG;
+ if ((ptr[2] & flags) != flags) {
+ code = GSSEAP_BAD_DIRECTION;
+ major = GSS_S_BAD_SIG;
+ goto cleanup;
}
- if (ptr[2] & TOK_FLAG_ACCEPTOR_SUBKEY) {
- return GSS_S_BAD_SIG;
+#ifdef HAVE_HEIMDAL_VERSION
+ if (krbCrypto == NULL) {
+ code = krb5_crypto_init(krbContext, &ctx->rfc3961Key,
+ ETYPE_NULL, &krbCrypto);
+ if (code != 0)
+ goto cleanup;
}
+#endif
if (toktype == TOK_TYPE_WRAP) {
- unsigned int k5_trailerlen;
+ size_t krbTrailerLen;
- if (load_16_be(ptr) != TOK_TYPE_WRAP)
+ if (load_uint16_be(ptr) != TOK_TYPE_WRAP)
goto defective;
conf_flag = ((ptr[2] & TOK_FLAG_WRAP_CONFIDENTIAL) != 0);
if (ptr[3] != 0xFF)
goto defective;
- ec = load_16_be(ptr + 4);
- rrc = load_16_be(ptr + 6);
- seqnum = load_64_be(ptr + 8);
-
- code = krb5_c_crypto_length(ctx->kerberosCtx,
- KRB_KEYTYPE(ctx->encryptionKey),
- conf_flag ? KRB5_CRYPTO_TYPE_TRAILER :
- KRB5_CRYPTO_TYPE_CHECKSUM,
- &k5_trailerlen);
- if (code != 0) {
- *minor_status = code;
- return GSS_S_FAILURE;
- }
+ ec = load_uint16_be(ptr + 4);
+ rrc = load_uint16_be(ptr + 6);
+ seqnum = load_uint64_be(ptr + 8);
+
+ code = krbCryptoLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
+ conf_flag ? KRB5_CRYPTO_TYPE_TRAILER :
+ KRB5_CRYPTO_TYPE_CHECKSUM,
+ &krbTrailerLen);
+ if (code != 0)
+ goto cleanup;
/* Deal with RRC */
if (trailer == NULL) {
- size_t desired_rrc = k5_trailerlen;
+ size_t desired_rrc = krbTrailerLen;
if (conf_flag) {
desired_rrc += 16; /* E(Header) */
unsigned char *althdr;
/* Decrypt */
- code = gssEapDecrypt(ctx->kerberosCtx,
+ code = gssEapDecrypt(krbContext,
((ctx->gssFlags & GSS_C_DCE_STYLE) != 0),
- ec, rrc, ctx->encryptionKey,
- key_usage, 0, iov, iov_count);
+ ec, rrc, KRB_CRYPTO_CONTEXT(ctx), keyUsage,
+ iov, iov_count);
if (code != 0) {
- *minor_status = code;
- return GSS_S_BAD_SIG;
+ major = GSS_S_BAD_SIG;
+ goto cleanup;
}
/* Validate header integrity */
else
althdr = (unsigned char *)trailer->buffer.value + ec;
- if (load_16_be(althdr) != TOK_TYPE_WRAP
+ if (load_uint16_be(althdr) != TOK_TYPE_WRAP
|| althdr[2] != ptr[2]
|| althdr[3] != ptr[3]
|| memcmp(althdr + 8, ptr + 8, 8) != 0) {
- *minor_status = 0;
- return GSS_S_BAD_SIG;
+ code = GSSEAP_BAD_WRAP_TOKEN;
+ major = GSS_S_BAD_SIG;
+ goto cleanup;
}
} else {
/* Verify checksum: note EC is checksum size here, not padding */
- if (ec != k5_trailerlen)
+ if (ec != krbTrailerLen)
goto defective;
/* Zero EC, RRC before computing checksum */
- store_16_be(0, ptr + 4);
- store_16_be(0, ptr + 6);
+ store_uint16_be(0, ptr + 4);
+ store_uint16_be(0, ptr + 6);
- code = gssEapVerify(ctx->kerberosCtx, cksumtype, rrc,
- ctx->encryptionKey, key_usage,
+ code = gssEapVerify(krbContext, ctx->checksumType, rrc,
+ KRB_CRYPTO_CONTEXT(ctx), keyUsage,
iov, iov_count, &valid);
if (code != 0 || valid == FALSE) {
- *minor_status = code;
- return GSS_S_BAD_SIG;
+ major = GSS_S_BAD_SIG;
+ goto cleanup;
}
}
- code = g_order_check(&ctx->seqState, seqnum);
+ code = sequenceCheck(minor, &ctx->seqState, seqnum);
} else if (toktype == TOK_TYPE_MIC) {
- if (load_16_be(ptr) != TOK_TYPE_MIC)
+ if (load_uint16_be(ptr) != toktype)
goto defective;
verify_mic_1:
if (ptr[3] != 0xFF)
goto defective;
- seqnum = load_64_be(ptr + 8);
+ seqnum = load_uint64_be(ptr + 8);
- code = gssEapVerify(ctx->kerberosCtx, cksumtype, 0,
- ctx->encryptionKey, key_usage,
+ code = gssEapVerify(krbContext, ctx->checksumType, 0,
+ KRB_CRYPTO_CONTEXT(ctx), keyUsage,
iov, iov_count, &valid);
if (code != 0 || valid == FALSE) {
- *minor_status = code;
- return GSS_S_BAD_SIG;
+ major = GSS_S_BAD_SIG;
+ goto cleanup;
}
- code = g_order_check(&ctx->seqState, seqnum);
- } else if (toktype == TOK_TYPE_DELETE) {
- if (load_16_be(ptr) != TOK_TYPE_DELETE)
+ code = sequenceCheck(minor, &ctx->seqState, seqnum);
+ } else if (toktype == TOK_TYPE_DELETE_CONTEXT) {
+ if (load_uint16_be(ptr) != TOK_TYPE_DELETE_CONTEXT)
goto defective;
goto verify_mic_1;
} else {
goto defective;
}
- *minor_status = 0;
-
if (conf_state != NULL)
*conf_state = conf_flag;
- return code;
+ code = 0;
+ major = GSS_S_COMPLETE;
+ goto cleanup;
defective:
- *minor_status = 0;
+ code = GSSEAP_BAD_WRAP_TOKEN;
+ major = GSS_S_DEFECTIVE_TOKEN;
+
+cleanup:
+ *minor = code;
+#ifdef HAVE_HEIMDAL_VERSION
+ if (freeCrypto && krbCrypto != NULL)
+ krb5_crypto_destroy(krbContext, krbCrypto);
+#endif
+
+ return major;
+}
+
+int
+rotateLeft(void *ptr, size_t bufsiz, size_t rc)
+{
+ void *tbuf;
+
+ if (bufsiz == 0)
+ return 0;
+ rc = rc % bufsiz;
+ if (rc == 0)
+ return 0;
+
+ tbuf = GSSEAP_MALLOC(rc);
+ if (tbuf == NULL)
+ return ENOMEM;
+
+ memcpy(tbuf, ptr, rc);
+ memmove(ptr, (char *)ptr + rc, bufsiz - rc);
+ memcpy((char *)ptr + bufsiz - rc, tbuf, rc);
+ GSSEAP_FREE(tbuf);
+
+ return 0;
+}
+
+/*
+ * Split a STREAM | SIGN_DATA | DATA into
+ * HEADER | SIGN_DATA | DATA | PADDING | TRAILER
+ */
+static OM_uint32
+unwrapStream(OM_uint32 *minor,
+ gss_ctx_id_t ctx,
+ int *conf_state,
+ gss_qop_t *qop_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count,
+ enum gss_eap_token_type toktype)
+{
+ unsigned char *ptr;
+ OM_uint32 code = 0, major = GSS_S_FAILURE;
+ krb5_context krbContext;
+ int conf_req_flag, toktype2;
+ int i = 0, j;
+ gss_iov_buffer_desc *tiov = NULL;
+ gss_iov_buffer_t stream, data = NULL;
+ gss_iov_buffer_t theader, tdata = NULL, tpadding, ttrailer;
+#ifdef HAVE_HEIMDAL_VERSION
+ krb5_crypto krbCrypto = NULL;
+#endif
+
+ GSSEAP_KRB_INIT(&krbContext);
+
+ assert(toktype == TOK_TYPE_WRAP);
+
+ if (toktype != TOK_TYPE_WRAP) {
+ code = GSSEAP_WRONG_TOK_ID;
+ goto cleanup;
+ }
+
+ stream = gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM);
+ assert(stream != NULL);
+
+ if (stream->buffer.length < 16) {
+ major = GSS_S_DEFECTIVE_TOKEN;
+ goto cleanup;
+ }
+
+ ptr = (unsigned char *)stream->buffer.value;
+ toktype2 = load_uint16_be(ptr);
+ ptr += 2;
+
+ tiov = (gss_iov_buffer_desc *)GSSEAP_CALLOC((size_t)iov_count + 2,
+ sizeof(gss_iov_buffer_desc));
+ if (tiov == NULL) {
+ code = ENOMEM;
+ goto cleanup;
+ }
+
+ /* HEADER */
+ theader = &tiov[i++];
+ theader->type = GSS_IOV_BUFFER_TYPE_HEADER;
+ theader->buffer.value = stream->buffer.value;
+ theader->buffer.length = 16;
+
+ /* n[SIGN_DATA] | DATA | m[SIGN_DATA] */
+ for (j = 0; j < iov_count; j++) {
+ OM_uint32 type = GSS_IOV_BUFFER_TYPE(iov[j].type);
+
+ if (type == GSS_IOV_BUFFER_TYPE_DATA) {
+ if (data != NULL) {
+ /* only a single DATA buffer can appear */
+ code = GSSEAP_BAD_STREAM_IOV;
+ goto cleanup;
+ }
+
+ data = &iov[j];
+ tdata = &tiov[i];
+ }
+ if (type == GSS_IOV_BUFFER_TYPE_DATA ||
+ type == GSS_IOV_BUFFER_TYPE_SIGN_ONLY)
+ tiov[i++] = iov[j];
+ }
+
+ if (data == NULL) {
+ /* a single DATA buffer must be present */
+ code = GSSEAP_BAD_STREAM_IOV;
+ goto cleanup;
+ }
+
+ /* PADDING | TRAILER */
+ tpadding = &tiov[i++];
+ tpadding->type = GSS_IOV_BUFFER_TYPE_PADDING;
+ tpadding->buffer.length = 0;
+ tpadding->buffer.value = NULL;
+
+ ttrailer = &tiov[i++];
+ ttrailer->type = GSS_IOV_BUFFER_TYPE_TRAILER;
+
+#ifdef HAVE_HEIMDAL_VERSION
+ code = krb5_crypto_init(krbContext, &ctx->rfc3961Key, ETYPE_NULL, &krbCrypto);
+ if (code != 0)
+ goto cleanup;
+#endif
+
+ {
+ size_t ec, rrc;
+ size_t krbHeaderLen = 0;
+ size_t krbTrailerLen = 0;
+
+ conf_req_flag = ((ptr[0] & TOK_FLAG_WRAP_CONFIDENTIAL) != 0);
+ ec = conf_req_flag ? load_uint16_be(ptr + 2) : 0;
+ rrc = load_uint16_be(ptr + 4);
+
+ if (rrc != 0) {
+ code = rotateLeft((unsigned char *)stream->buffer.value + 16,
+ stream->buffer.length - 16, rrc);
+ if (code != 0)
+ goto cleanup;
+ store_uint16_be(0, ptr + 4); /* set RRC to zero */
+ }
+
+ if (conf_req_flag) {
+ code = krbCryptoLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
+ KRB5_CRYPTO_TYPE_HEADER, &krbHeaderLen);
+ if (code != 0)
+ goto cleanup;
+ theader->buffer.length += krbHeaderLen; /* length validated later */
+ }
+
+ /* no PADDING for CFX, EC is used instead */
+ code = krbCryptoLength(krbContext, KRB_CRYPTO_CONTEXT(ctx),
+ conf_req_flag
+ ? KRB5_CRYPTO_TYPE_TRAILER
+ : KRB5_CRYPTO_TYPE_CHECKSUM,
+ &krbTrailerLen);
+ if (code != 0)
+ goto cleanup;
+
+ ttrailer->buffer.length = ec + (conf_req_flag ? 16 : 0 /* E(Header) */) +
+ krbTrailerLen;
+ ttrailer->buffer.value = (unsigned char *)stream->buffer.value +
+ stream->buffer.length - ttrailer->buffer.length;
+ }
+
+ /* IOV: -----------0-------------+---1---+--2--+----------------3--------------*/
+ /* CFX: GSS-Header | Kerb-Header | Data | | EC | E(Header) | Kerb-Trailer */
+ /* GSS: -------GSS-HEADER--------+-DATA--+-PAD-+----------GSS-TRAILER----------*/
+
+ /* validate lengths */
+ if (stream->buffer.length < theader->buffer.length +
+ tpadding->buffer.length +
+ ttrailer->buffer.length) {
+ major = GSS_S_DEFECTIVE_TOKEN;
+ code = GSSEAP_TOK_TRUNC;
+ goto cleanup;
+ }
+
+ /* setup data */
+ tdata->buffer.length = stream->buffer.length - ttrailer->buffer.length -
+ tpadding->buffer.length - theader->buffer.length;
+
+ assert(data != NULL);
+
+ if (data->type & GSS_IOV_BUFFER_FLAG_ALLOCATE) {
+ code = gssEapAllocIov(tdata, tdata->buffer.length);
+ if (code != 0)
+ goto cleanup;
+
+ memcpy(tdata->buffer.value,
+ (unsigned char *)stream->buffer.value + theader->buffer.length,
+ tdata->buffer.length);
+ } else {
+ tdata->buffer.value = (unsigned char *)stream->buffer.value +
+ theader->buffer.length;
+ }
+
+ assert(i <= iov_count + 2);
+
+ major = unwrapToken(&code, ctx, KRB_CRYPTO_CONTEXT(ctx),
+ conf_state, qop_state, tiov, i, toktype);
+ if (major == GSS_S_COMPLETE) {
+ *data = *tdata;
+ } else if (tdata->type & GSS_IOV_BUFFER_FLAG_ALLOCATED) {
+ OM_uint32 tmp;
+
+ gss_release_buffer(&tmp, &tdata->buffer);
+ tdata->type &= ~(GSS_IOV_BUFFER_FLAG_ALLOCATED);
+ }
+
+cleanup:
+ if (tiov != NULL)
+ GSSEAP_FREE(tiov);
+#ifdef HAVE_HEIMDAL_VERSION
+ if (krbCrypto != NULL)
+ krb5_crypto_destroy(krbContext, krbCrypto);
+#endif
+
+ *minor = code;
- return GSS_S_DEFECTIVE_TOKEN;
+ return major;
+}
+
+OM_uint32
+gssEapUnwrapOrVerifyMIC(OM_uint32 *minor,
+ gss_ctx_id_t ctx,
+ int *conf_state,
+ gss_qop_t *qop_state,
+ gss_iov_buffer_desc *iov,
+ int iov_count,
+ enum gss_eap_token_type toktype)
+{
+ OM_uint32 major;
+
+ if (ctx->encryptionType == ENCTYPE_NULL) {
+ *minor = GSSEAP_KEY_UNAVAILABLE;
+ return GSS_S_UNAVAILABLE;
+ }
+
+ if (gssEapLocateIov(iov, iov_count, GSS_IOV_BUFFER_TYPE_STREAM) != NULL) {
+ major = unwrapStream(minor, ctx, conf_state, qop_state,
+ iov, iov_count, toktype);
+ } else {
+ major = unwrapToken(minor, ctx,
+ NULL, /* krbCrypto */
+ conf_state, qop_state,
+ iov, iov_count, toktype);
+ }
+
+ return major;
}
OM_uint32
gss_iov_buffer_desc *iov,
int iov_count)
{
- return gssEapUnwrapOrVerifyMIC(minor, ctx,
- iov, iov_count, conf_state,
- qop_state, TOK_TYPE_WRAP);
+ OM_uint32 major;
+
+ if (ctx == GSS_C_NO_CONTEXT) {
+ *minor = EINVAL;
+ return GSS_S_CALL_INACCESSIBLE_READ | GSS_S_NO_CONTEXT;
+ }
+
+ *minor = 0;
+
+ GSSEAP_MUTEX_LOCK(&ctx->mutex);
+
+ if (!CTX_IS_ESTABLISHED(ctx)) {
+ major = GSS_S_NO_CONTEXT;
+ *minor = GSSEAP_CONTEXT_INCOMPLETE;
+ goto cleanup;
+ }
+
+ major = gssEapUnwrapOrVerifyMIC(minor, ctx, conf_state, qop_state,
+ iov, iov_count, TOK_TYPE_WRAP);
+ if (GSS_ERROR(major))
+ goto cleanup;
+
+cleanup:
+ GSSEAP_MUTEX_UNLOCK(&ctx->mutex);
+ return major;
}