From 4034e2497a7c2d1f7bd25dcf4b1900fcfce0ff1f Mon Sep 17 00:00:00 2001 From: Ken Raeburn Date: Sat, 13 Dec 2003 07:07:23 +0000 Subject: [PATCH] Add 64-bit sequence number support. Do sequence number ordering tests relative to the initial value rather than absolute. Support tokens without pseudo-ASN.1 wrappers. Don't restrict enctype lists. Implement CFX token support. With CFX_EXERCISE defined, use random padding, random rotates, and bogus initial tokens, to exercise the associated code paths. ticket: 2040 status: open git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@15911 dc483132-0cff-0310-8789-dd5450dbe970 --- src/lib/gssapi/generic/ChangeLog | 17 + src/lib/gssapi/generic/gssapiP_generic.h | 14 +- src/lib/gssapi/generic/util_ordering.c | 56 ++- src/lib/gssapi/generic/util_token.c | 25 +- src/lib/gssapi/krb5/ChangeLog | 37 ++ src/lib/gssapi/krb5/Makefile.in | 3 + src/lib/gssapi/krb5/accept_sec_context.c | 128 ++++-- src/lib/gssapi/krb5/delete_sec_context.c | 5 +- src/lib/gssapi/krb5/gssapiP_krb5.h | 47 ++- src/lib/gssapi/krb5/init_sec_context.c | 236 +++++------ src/lib/gssapi/krb5/k5seal.c | 75 ++-- src/lib/gssapi/krb5/k5sealv3.c | 476 +++++++++++++++++++++++ src/lib/gssapi/krb5/k5unseal.c | 41 +- src/lib/gssapi/krb5/ser_sctx.c | 36 +- src/lib/gssapi/krb5/wrap_size_limit.c | 26 ++ 15 files changed, 932 insertions(+), 290 deletions(-) create mode 100644 src/lib/gssapi/krb5/k5sealv3.c diff --git a/src/lib/gssapi/generic/ChangeLog b/src/lib/gssapi/generic/ChangeLog index 5444c99d6..a6e77399e 100644 --- a/src/lib/gssapi/generic/ChangeLog +++ b/src/lib/gssapi/generic/ChangeLog @@ -1,3 +1,20 @@ +2003-12-13 Ken Raeburn + + * gssapiP_generic.h: Include k5-platform.h. + (gssint_uint64): New typedef. + (g_order_init, g_order_check): Update decls. + * util_ordering.c (struct _queue): Change sequence number fields + to gssint_uint64. Add mask field. + (queue_insert): Change sequence number to gssint_uint64. + (g_order_init): Change sequence numbers to gssint_uint64. Add + "wide_nums" argument; initialize the queue mask field based on + it; all callers changed. Store a -1 as the first element. + (g_order_check): Store and check elements as offsets from + firstnum. Mask to 32 bits if desired. + * util_token.c (g_verify_token_header): Add new argument + indicating whether the pseudo-ASN.1 wrapper is required; all + callers changed. + 2003-07-17 Ken Raeburn * Makefile.in (LIBNAME) [##WIN16##]: Don't define. diff --git a/src/lib/gssapi/generic/gssapiP_generic.h b/src/lib/gssapi/generic/gssapiP_generic.h index 102ba699e..24d51d0d5 100644 --- a/src/lib/gssapi/generic/gssapiP_generic.h +++ b/src/lib/gssapi/generic/gssapiP_generic.h @@ -40,6 +40,9 @@ #include "gssapi_err_generic.h" #include +#include "k5-platform.h" +typedef UINT64_TYPE gssint_uint64; + /** helper macros **/ #define g_OID_equal(o1,o2) \ @@ -159,8 +162,9 @@ void g_make_token_header (gss_OID mech, unsigned int body_size, unsigned char **buf, int tok_type); gss_int32 g_verify_token_header (gss_OID mech, unsigned int *body_size, - unsigned char **buf, int tok_type, - unsigned int toksize_in); + unsigned char **buf, int tok_type, + unsigned int toksize_in, + int wrapper_required); OM_uint32 g_display_major_status (OM_uint32 *minor_status, OM_uint32 status_value, @@ -171,10 +175,10 @@ OM_uint32 g_display_com_err_status (OM_uint32 *minor_status, OM_uint32 status_value, gss_buffer_t status_string); -gss_int32 g_order_init (void **queue, OM_uint32 seqnum, - int do_replay, int do_sequence); +gss_int32 g_order_init (void **queue, gssint_uint64 seqnum, + int do_replay, int do_sequence, int wide); -gss_int32 g_order_check (void **queue, OM_uint32 seqnum); +gss_int32 g_order_check (void **queue, gssint_uint64 seqnum); void g_order_free (void **queue); diff --git a/src/lib/gssapi/generic/util_ordering.c b/src/lib/gssapi/generic/util_ordering.c index 21a8b0641..fe2eaafc2 100644 --- a/src/lib/gssapi/generic/util_ordering.c +++ b/src/lib/gssapi/generic/util_ordering.c @@ -38,8 +38,14 @@ typedef struct _queue { int do_sequence; int start; int length; - unsigned int firstnum; - unsigned int elem[QUEUE_LENGTH]; + gssint_uint64 firstnum; + /* Stored as deltas from firstnum. This way, the high bit won't + overflow unless we've actually gone through 2**n messages, or + gotten something *way* out of sequence. */ + gssint_uint64 elem[QUEUE_LENGTH]; + /* All ones for 64-bit sequence numbers; 32 ones for 32-bit + sequence numbers. */ + gssint_uint64 mask; } queue; /* rep invariant: @@ -51,7 +57,7 @@ typedef struct _queue { #define QELEM(q,i) ((q)->elem[(i)%QSIZE(q)]) static void -queue_insert(queue *q, int after, unsigned int seqnum) +queue_insert(queue *q, int after, gssint_uint64 seqnum) { /* insert. this is not the fastest way, but it's easy, and it's optimized for insert at end, which is the common case */ @@ -80,10 +86,10 @@ queue_insert(queue *q, int after, unsigned int seqnum) q->length++; } } - + gss_int32 -g_order_init(void **vqueue, OM_uint32 seqnum, - int do_replay, int do_sequence) +g_order_init(void **vqueue, gssint_uint64 seqnum, + int do_replay, int do_sequence, int wide_nums) { queue *q; @@ -92,38 +98,49 @@ g_order_init(void **vqueue, OM_uint32 seqnum, q->do_replay = do_replay; q->do_sequence = do_sequence; + q->mask = wide_nums ? ~(gssint_uint64)0 : 0xffffffffUL; q->start = 0; q->length = 1; q->firstnum = seqnum; - q->elem[q->start] = seqnum-1; + q->elem[q->start] = ((gssint_uint64)0 - 1) & q->mask; *vqueue = (void *) q; return(0); } gss_int32 -g_order_check(void **vqueue, OM_uint32 seqnum) +g_order_check(void **vqueue, gssint_uint64 seqnum) { queue *q; int i; - + gssint_uint64 expected; + q = (queue *) (*vqueue); if (!q->do_replay && !q->do_sequence) return(GSS_S_COMPLETE); + /* All checks are done relative to the initial sequence number, to + avoid (or at least put off) the pain of wrapping. */ + seqnum -= q->firstnum; + /* If we're only doing 32-bit values, adjust for that again. + + Note that this will probably be the wrong thing to if we get + 2**32 messages sent with 32-bit sequence numbers. */ + seqnum &= q->mask; + /* rule 1: expected sequence number */ - if (seqnum == QELEM(q,q->start+q->length-1)+1) { + expected = (QELEM(q,q->start+q->length-1)+1) & q->mask; + if (seqnum == expected) { queue_insert(q, q->start+q->length-1, seqnum); return(GSS_S_COMPLETE); } /* rule 2: > expected sequence number */ - if ((seqnum > QELEM(q,q->start+q->length-1)+1) || - (seqnum < q->firstnum)) { + if ((seqnum > expected)) { queue_insert(q, q->start+q->length-1, seqnum); if (q->do_replay && !q->do_sequence) return(GSS_S_COMPLETE); @@ -134,7 +151,20 @@ g_order_check(void **vqueue, OM_uint32 seqnum) /* rule 3: seqnum < seqnum(first) */ if ((seqnum < QELEM(q,q->start)) && - (seqnum >= q->firstnum)) { + /* Is top bit of whatever width we're using set? + + We used to check for greater than or equal to firstnum, but + (1) we've since switched to compute values relative to + firstnum, so the lowest we can have is 0, and (2) the effect + of the original scheme was highly dependent on whether + firstnum was close to either side of 0. (Consider + firstnum==0xFFFFFFFE and we miss three packets; the next + packet is *new* but would look old.) + + This check should give us 2**31 or 2**63 messages "new", and + just as many "old". That's not quite right either. */ + (seqnum & (1 + (q->mask >> 1))) + ) { if (q->do_replay && !q->do_sequence) return(GSS_S_OLD_TOKEN); else diff --git a/src/lib/gssapi/generic/util_token.c b/src/lib/gssapi/generic/util_token.c index 30ae0698c..97a788c09 100644 --- a/src/lib/gssapi/generic/util_token.c +++ b/src/lib/gssapi/generic/util_token.c @@ -168,12 +168,15 @@ void g_make_token_header(mech, body_size, buf, tok_type) * mechanism in the token does not match the mech argument. buf and * *body_size are left unmodified on error. */ -gss_int32 g_verify_token_header(mech, body_size, buf_in, tok_type, toksize_in) + +gss_int32 g_verify_token_header(mech, body_size, buf_in, tok_type, toksize_in, + wrapper_required) gss_OID mech; unsigned int *body_size; unsigned char **buf_in; int tok_type; unsigned int toksize_in; + int wrapper_required; { unsigned char *buf = *buf_in; int seqsize; @@ -182,8 +185,13 @@ gss_int32 g_verify_token_header(mech, body_size, buf_in, tok_type, toksize_in) if ((toksize-=1) < 0) return(G_BAD_TOK_HEADER); - if (*buf++ != 0x60) - return(G_BAD_TOK_HEADER); + if (*buf++ != 0x60) { + if (wrapper_required) + return(G_BAD_TOK_HEADER); + buf--; + toksize++; + goto skip_wrapper; + } if ((seqsize = der_read_length(&buf, &toksize)) < 0) return(G_BAD_TOK_HEADER); @@ -207,16 +215,17 @@ gss_int32 g_verify_token_header(mech, body_size, buf_in, tok_type, toksize_in) if (! g_OID_equal(&toid, mech)) return G_WRONG_MECH; +skip_wrapper: if (tok_type != -1) { if ((toksize-=2) < 0) return(G_BAD_TOK_HEADER); if ((*buf++ != ((tok_type>>8)&0xff)) || - (*buf++ != (tok_type&0xff))) + (*buf++ != (tok_type&0xff))) return(G_WRONG_TOKID); } - *buf_in = buf; - *body_size = toksize; + *buf_in = buf; + *body_size = toksize; - return 0; - } + return 0; +} diff --git a/src/lib/gssapi/krb5/ChangeLog b/src/lib/gssapi/krb5/ChangeLog index d02374809..66dcc1efd 100644 --- a/src/lib/gssapi/krb5/ChangeLog +++ b/src/lib/gssapi/krb5/ChangeLog @@ -1,3 +1,40 @@ +2003-12-13 Ken Raeburn + Sam Hartman + + * k5sealv3.c: New file, implements Wrap and MIC tokens for CFX + extensions. + * gssapiP_krb5.h (struct _krb5_gss_ctx_id_rec): Added acceptor + subkey, 64-bit sequence numbers, checksum type, and hooks for + sending a bogus initial token for CFX testing. Changed some flags + into bitfields. + (gss_krb5int_make_seal_token_v3): Declare. + * Makefile.in (SRCS, OBJS, STLIBOBJS): Build it. + * accept_sec_context.c (krb5_gss_accept_sec_context): Add CFX + support. For G_WRONG_TOKID, send back an error token with + AP_ERR_MSG_TYPE code and return a CONTINUE_NEEDED indication. + Initialize new fields in context. + * delete_sec_context.c (krb5_gss_delete_sec_context): Free + acceptor subkey field. + * init_sec_context.c (get_credentials): Drop enctypes argument; + callers changed. + (get_requested_enctypes): Deleted. + (setup_enc): Combine some common sections. Do CFX initialization + for newer enctypes. + (new_connection) [CFX_EXERCISE]: If doing CFX, send a bogus + token. Delete the enctype list manipulation. + (mutual_auth): If CFX, save acceptor's subkey. + * k5seal.c (make_seal_token_v1): Sequence number is now 64 bits. + (kg_seal): Call out to _v3 code for CFX. + * k5unseal.c (kg_unseal): For CFX, adjust token id numbers and + call out to _v3 code. + * wrap_size_limit.c (krb5_gss_wrap_size_limit): Implement CFX + support. + + * gssapiP_krb5.h (struct _krb5_gss_ctx_id_rec): Deleted fields + ctypes and nctypes. + * delete_sec_context.c, init_sec_context.c, ser_sctx.c: Removed + references. + 2003-12-11 Alexandra Ellwood * acquire_cred.c, gssapi_krb5.c, gssapiP_krb5.h, set_ccache.c: diff --git a/src/lib/gssapi/krb5/Makefile.in b/src/lib/gssapi/krb5/Makefile.in index 9a18c50a8..759eedd94 100644 --- a/src/lib/gssapi/krb5/Makefile.in +++ b/src/lib/gssapi/krb5/Makefile.in @@ -42,6 +42,7 @@ SRCS = \ $(srcdir)/inq_cred.c \ $(srcdir)/inq_names.c \ $(srcdir)/k5seal.c \ + $(srcdir)/k5sealv3.c \ $(srcdir)/k5unseal.c \ $(srcdir)/krb5_gss_glue.c \ $(srcdir)/process_context_token.c \ @@ -89,6 +90,7 @@ OBJS = \ $(OUTPRE)inq_cred.$(OBJEXT) \ $(OUTPRE)inq_names.$(OBJEXT) \ $(OUTPRE)k5seal.$(OBJEXT) \ + $(OUTPRE)k5sealv3.$(OBJEXT) \ $(OUTPRE)k5unseal.$(OBJEXT) \ $(OUTPRE)krb5_gss_glue.$(OBJEXT) \ $(OUTPRE)process_context_token.$(OBJEXT) \ @@ -136,6 +138,7 @@ STLIBOBJS = \ inq_cred.o \ inq_names.o \ k5seal.o \ + k5sealv3.o \ k5unseal.o \ krb5_gss_glue.o \ process_context_token.o \ diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c index 3367c1da7..d507909f3 100644 --- a/src/lib/gssapi/krb5/accept_sec_context.c +++ b/src/lib/gssapi/krb5/accept_sec_context.c @@ -77,6 +77,12 @@ #endif #include +#ifdef CFX_EXERCISE +#define CFX_ACCEPTOR_SUBKEY (time(0) & 1) +#else +#define CFX_ACCEPTOR_SUBKEY 1 +#endif + /* Decode, decrypt and store the forwarded creds in the local ccache. */ static krb5_error_code rd_and_store_for_creds(context, auth_context, inbuf, out_cred) @@ -122,7 +128,8 @@ rd_and_store_for_creds(context, auth_context, inbuf, out_cred) if ((retval = krb5_auth_con_init(context, &new_auth_ctx))) goto cleanup; krb5_auth_con_setflags(context, new_auth_ctx, 0); - if ((retval = krb5_rd_cred(context, new_auth_ctx, inbuf, &creds, NULL))) + if ((retval = krb5_rd_cred(context, new_auth_ctx, inbuf, + &creds, NULL))) goto cleanup; } @@ -312,13 +319,13 @@ krb5_gss_accept_sec_context(minor_status, context_handle, if (!(code = g_verify_token_header((gss_OID) gss_mech_krb5, &(ap_req.length), &ptr, KG_TOK_CTX_AP_REQ, - input_token->length))) { + input_token->length, 1))) { mech_used = gss_mech_krb5; } else if ((code == G_WRONG_MECH) && !(code = g_verify_token_header((gss_OID) gss_mech_krb5_old, &(ap_req.length), &ptr, KG_TOK_CTX_AP_REQ, - input_token->length))) { + input_token->length, 1))) { /* * Previous versions of this library used the old mech_id * and some broken behavior (wrong IV on checksum @@ -327,6 +334,11 @@ krb5_gss_accept_sec_context(minor_status, context_handle, * old behavior. */ mech_used = gss_mech_krb5_old; + } else if (code == G_WRONG_TOKID) { + major_status = GSS_S_CONTINUE_NEEDED; + code = KRB5KRB_AP_ERR_MSG_TYPE; + mech_used = gss_mech_krb5; + goto fail; } else { major_status = GSS_S_DEFECTIVE_TOKEN; goto fail; @@ -616,6 +628,7 @@ krb5_gss_accept_sec_context(minor_status, context_handle, goto fail; } + ctx->proto = 0; switch(ctx->subkey->enctype) { case ENCTYPE_DES_CBC_MD5: case ENCTYPE_DES_CBC_CRC: @@ -635,12 +648,7 @@ krb5_gss_accept_sec_context(minor_status, context_handle, /*SUPPRESS 113*/ ctx->enc->contents[i] ^= 0xf0; - if ((code = krb5_copy_keyblock(context, ctx->subkey, &ctx->seq))) { - major_status = GSS_S_FAILURE; - goto fail; - } - - break; + goto copy_subkey_to_seq; case ENCTYPE_DES3_CBC_SHA1: ctx->subkey->enctype = ENCTYPE_DES3_CBC_RAW; @@ -649,36 +657,38 @@ krb5_gss_accept_sec_context(minor_status, context_handle, ctx->sealalg = SEAL_ALG_DES3KD; /* fill in the encryption descriptors */ - + copy_subkey: if ((code = krb5_copy_keyblock(context, ctx->subkey, &ctx->enc))) { major_status = GSS_S_FAILURE; goto fail; } - + copy_subkey_to_seq: if ((code = krb5_copy_keyblock(context, ctx->subkey, &ctx->seq))) { major_status = GSS_S_FAILURE; goto fail; } - break; - case ENCTYPE_ARCFOUR_HMAC: - ctx->signalg = SGN_ALG_HMAC_MD5 ; - ctx->cksum_size = 8; - ctx->sealalg = SEAL_ALG_MICROSOFT_RC4 ; - - code = krb5_copy_keyblock (context, ctx->subkey, &ctx->enc); - if (code) - goto fail; - code = krb5_copy_keyblock (context, ctx->subkey, &ctx->seq); - if (code) { - krb5_free_keyblock (context, ctx->enc); - goto fail; - } - break; + + case ENCTYPE_ARCFOUR_HMAC: + ctx->signalg = SGN_ALG_HMAC_MD5 ; + ctx->cksum_size = 8; + ctx->sealalg = SEAL_ALG_MICROSOFT_RC4 ; + goto copy_subkey; default: - code = KRB5_BAD_ENCTYPE; - goto fail; + ctx->signalg = -1; + ctx->sealalg = -1; + ctx->proto = 1; + code = krb5int_c_mandatory_cksumtype(context, ctx->subkey->enctype, + &ctx->cksumtype); + if (code) + goto fail; + code = krb5_c_checksum_length(context, ctx->cksumtype, + &ctx->cksum_size); + if (code) + goto fail; + ctx->have_acceptor_subkey = 0; + goto copy_subkey; } ctx->endtime = ticket->enc_part2->times.endtime; @@ -686,7 +696,11 @@ krb5_gss_accept_sec_context(minor_status, context_handle, krb5_free_ticket(context, ticket); /* Done with ticket */ - krb5_auth_con_getremoteseqnumber(context, auth_context, &ctx->seq_recv); + { + krb5_ui_4 seq_temp; + krb5_auth_con_getremoteseqnumber(context, auth_context, &seq_temp); + ctx->seq_recv = seq_temp; + } if ((code = krb5_timeofday(context, &now))) { major_status = GSS_S_FAILURE; @@ -701,7 +715,7 @@ krb5_gss_accept_sec_context(minor_status, context_handle, g_order_init(&(ctx->seqstate), ctx->seq_recv, (ctx->gss_flags & GSS_C_REPLAY_FLAG) != 0, - (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) != 0); + (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) != 0, ctx->proto); /* at this point, the entire context structure is filled in, so it can be released. */ @@ -710,13 +724,53 @@ krb5_gss_accept_sec_context(minor_status, context_handle, if (ctx->gss_flags & GSS_C_MUTUAL_FLAG) { unsigned char * ptr3; + krb5_ui_4 seq_temp; + int cfx_generate_subkey; + + if (ctx->proto == 1) + cfx_generate_subkey = CFX_ACCEPTOR_SUBKEY; + else + cfx_generate_subkey = 0; + + if (cfx_generate_subkey) { + krb5_int32 acflags; + code = krb5_auth_con_getflags(context, auth_context, &acflags); + if (code == 0) { + acflags |= KRB5_AUTH_CONTEXT_USE_SUBKEY; + code = krb5_auth_con_setflags(context, auth_context, acflags); + } + if (code) { + major_status = GSS_S_FAILURE; + goto fail; + } + } + if ((code = krb5_mk_rep(context, auth_context, &ap_rep))) { major_status = GSS_S_FAILURE; goto fail; } - krb5_auth_con_getlocalseqnumber(context, auth_context, - &ctx->seq_send); + krb5_auth_con_getlocalseqnumber(context, auth_context, &seq_temp); + ctx->seq_send = seq_temp & 0xffffffffL; + + if (cfx_generate_subkey) { + /* Get the new acceptor subkey. With the code above, there + should always be one if we make it to this point. */ + code = krb5_auth_con_getsendsubkey(context, auth_context, + &ctx->acceptor_subkey); + if (code != 0) { + major_status = GSS_S_FAILURE; + goto fail; + } + code = krb5int_c_mandatory_cksumtype(context, + ctx->acceptor_subkey->enctype, + &ctx->acceptor_subkey_cksumtype); + if (code) { + major_status = GSS_S_FAILURE; + goto fail; + } + ctx->have_acceptor_subkey = 1; + } /* the reply token hasn't been sent yet, but that's ok. */ ctx->gss_flags |= GSS_C_PROT_READY_FLAG; @@ -804,7 +858,7 @@ krb5_gss_accept_sec_context(minor_status, context_handle, if (ap_rep.data) krb5_free_data_contents(context, &ap_rep); - if (!GSS_ERROR(major_status)) + if (!GSS_ERROR(major_status) && major_status != GSS_S_CONTINUE_NEEDED) return(major_status); /* from here on is the real "fail" code */ @@ -844,7 +898,9 @@ krb5_gss_accept_sec_context(minor_status, context_handle, krb5_free_ap_req(context, request); } - if (cred && (gss_flags & GSS_C_MUTUAL_FLAG)) { + if (cred + && ((gss_flags & GSS_C_MUTUAL_FLAG) + || (major_status == GSS_S_CONTINUE_NEEDED))) { unsigned int tmsglen; int toktype; @@ -854,7 +910,7 @@ krb5_gss_accept_sec_context(minor_status, context_handle, */ memset(&krb_error_data, 0, sizeof(krb_error_data)); - code -= ERROR_TABLE_BASE_krb5; + code -= ERROR_TABLE_BASE_krb5; if (code < 0 || code > 128) code = 60 /* KRB_ERR_GENERIC */; @@ -862,7 +918,7 @@ krb5_gss_accept_sec_context(minor_status, context_handle, (void) krb5_us_timeofday(context, &krb_error_data.stime, &krb_error_data.susec); krb_error_data.server = cred->princ; - + code = krb5_mk_error(context, &krb_error_data, &scratch); if (code) return (major_status); diff --git a/src/lib/gssapi/krb5/delete_sec_context.c b/src/lib/gssapi/krb5/delete_sec_context.c index 28c235890..94702b862 100644 --- a/src/lib/gssapi/krb5/delete_sec_context.c +++ b/src/lib/gssapi/krb5/delete_sec_context.c @@ -92,6 +92,8 @@ krb5_gss_delete_sec_context(minor_status, context_handle, output_token) krb5_free_principal(context, ctx->there); if (ctx->subkey) krb5_free_keyblock(context, ctx->subkey); + if (ctx->acceptor_subkey) + krb5_free_keyblock(context, ctx->acceptor_subkey); if (ctx->auth_context) { (void)krb5_auth_con_setrcache(context, ctx->auth_context, NULL); @@ -101,9 +103,6 @@ krb5_gss_delete_sec_context(minor_status, context_handle, output_token) if (ctx->mech_used) gss_release_oid(minor_status, &ctx->mech_used); - if (ctx->ctypes) - xfree(ctx->ctypes); - /* Zero out context */ memset(ctx, 0, sizeof(*ctx)); xfree(ctx); diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index fc4cf2fcd..cee702bcd 100644 --- a/src/lib/gssapi/krb5/gssapiP_krb5.h +++ b/src/lib/gssapi/krb5/gssapiP_krb5.h @@ -69,6 +69,9 @@ #include "gssapi_krb5.h" #include "gssapi_err_krb5.h" +/* for debugging */ +#undef CFX_EXERCISE + /** constants **/ #define CKSUMTYPE_KG_CB 0x8003 @@ -116,10 +119,17 @@ enum seal_alg { SEAL_ALG_DES3KD = 0x0002 }; +/* for 3DES */ #define KG_USAGE_SEAL 22 #define KG_USAGE_SIGN 23 #define KG_USAGE_SEQ 24 +/* for draft-ietf-krb-wg-gssapi-cfx-01 */ +#define KG_USAGE_ACCEPTOR_SEAL 22 +#define KG_USAGE_ACCEPTOR_SIGN 23 +#define KG_USAGE_INITIATOR_SEAL 24 +#define KG_USAGE_INITIATOR_SIGN 25 + enum qop { GSS_KRB5_INTEG_C_QOP_MD5 = 0x0001, /* *partial* MD5 = "MD2.5" */ GSS_KRB5_INTEG_C_QOP_DES_MD5 = 0x0002, @@ -152,15 +162,21 @@ typedef struct _krb5_gss_cred_id_rec { } krb5_gss_cred_id_rec, *krb5_gss_cred_id_t; typedef struct _krb5_gss_ctx_id_rec { - int initiate; /* nonzero if initiating, zero if accepting */ + unsigned int initiate : 1; /* nonzero if initiating, zero if accepting */ + unsigned int established : 1; + unsigned int big_endian : 1; + unsigned int have_acceptor_subkey : 1; + unsigned int seed_init : 1; /* XXX tested but never actually set */ +#ifdef CFX_EXERCISE + unsigned int testing_unknown_tokid : 1; /* for testing only */ +#endif OM_uint32 gss_flags; - int seed_init; unsigned char seed[16]; krb5_principal here; krb5_principal there; krb5_keyblock *subkey; int signalg; - int cksum_size; + size_t cksum_size; int sealalg; krb5_keyblock *enc; krb5_keyblock *seq; @@ -169,15 +185,22 @@ typedef struct _krb5_gss_ctx_id_rec { /* XXX these used to be signed. the old spec is inspecific, and the new spec specifies unsigned. I don't believe that the change affects the wire encoding. */ - krb5_ui_4 seq_send; - krb5_ui_4 seq_recv; + gssint_uint64 seq_send; + gssint_uint64 seq_recv; void *seqstate; - int established; - int big_endian; krb5_auth_context auth_context; gss_OID_desc *mech_used; - int nctypes; - krb5_cksumtype *ctypes; + /* Protocol spec revision + 0 => RFC 1964 with 3DES and RC4 enhancements + 1 => draft-ietf-krb-wg-gssapi-cfx-01 + No others defined so far. */ + int proto; + krb5_cksumtype cksumtype; /* for "main" subkey */ + krb5_keyblock *acceptor_subkey; /* CFX only */ + krb5_cksumtype acceptor_subkey_cksumtype; +#ifdef CFX_EXERCISE + gss_buffer_desc init_token; +#endif } krb5_gss_ctx_id_rec, *krb5_gss_ctx_id_t; extern void *kg_vdb; @@ -595,4 +618,10 @@ gss_OID krb5_gss_convert_static_mech_oid (gss_OID oid ); +krb5_error_code gss_krb5int_make_seal_token_v3(krb5_context, + krb5_gss_ctx_id_rec *, + const gss_buffer_desc *, + gss_buffer_t, + int, int); + #endif /* _GSSAPIP_KRB5_H_ */ diff --git a/src/lib/gssapi/krb5/init_sec_context.c b/src/lib/gssapi/krb5/init_sec_context.c index 98102ce87..102ffdadd 100644 --- a/src/lib/gssapi/krb5/init_sec_context.c +++ b/src/lib/gssapi/krb5/init_sec_context.c @@ -90,13 +90,12 @@ int krb5_gss_dbg_client_expcreds = 0; * ccache. */ static krb5_error_code get_credentials(context, cred, server, now, - endtime, enctypes, out_creds) + endtime, out_creds) krb5_context context; krb5_gss_cred_id_t cred; krb5_principal server; krb5_timestamp now; krb5_timestamp endtime; - const krb5_enctype *enctypes; krb5_creds **out_creds; { krb5_error_code code; @@ -112,10 +111,7 @@ static krb5_error_code get_credentials(context, cred, server, now, in_creds.keyblock.enctype = 0; - code = krb5_set_default_tgs_enctypes (context, enctypes); - if (code) - goto cleanup; - code = krb5_get_credentials(context, 0, cred->ccache, + code = krb5_get_credentials(context, 0, cred->ccache, &in_creds, out_creds); if (code) goto cleanup; @@ -324,86 +320,6 @@ make_ap_req_v1(context, ctx, cred, k_cred, chan_bindings, mech_type, token) return (code); } -/* - * get_requested_enctypes - * - * Filter the krb5 library's default enctype list with the set of - * enctypes we support for GSSAPI. - */ -static krb5_error_code -get_requested_enctypes( - krb5_context context, - krb5_enctype **ret_enctypes) -{ - krb5_error_code code; - int i, j, k; - int is_duplicate_enctype; - int is_wanted_enctype; - static const krb5_enctype wanted_enctypes[] = { -#if 1 - ENCTYPE_DES3_CBC_SHA1, -#endif - ENCTYPE_ARCFOUR_HMAC, - ENCTYPE_DES_CBC_CRC, - ENCTYPE_DES_CBC_MD5, ENCTYPE_DES_CBC_MD4, - }; -#define N_WANTED_ENCTYPES (sizeof(wanted_enctypes)/sizeof(wanted_enctypes[0])) - krb5_enctype *default_enctypes = 0; - krb5_enctype *requested_enctypes; - - *ret_enctypes = malloc((N_WANTED_ENCTYPES + 1) * sizeof(krb5_enctype)); - if (*ret_enctypes == NULL) - return ENOMEM; - - code = krb5_get_tgs_ktypes (context, 0, &default_enctypes); - if (code) { - free(*ret_enctypes); - *ret_enctypes = NULL; - return code; - } - requested_enctypes = *ret_enctypes; - - /* "i" denotes *next* slot to fill. Don't forget to save room - for a trailing zero. */ - i = 0; - for (j = 0; - (default_enctypes[j] != 0 - /* This part should be redundant, but let's be paranoid. */ - && i < N_WANTED_ENCTYPES); - j++) { - - krb5_enctype e = default_enctypes[j]; - - /* Is this enctype one of the ones we want for GSSAPI? */ - is_wanted_enctype = 0; - for (k = 0; k < N_WANTED_ENCTYPES; k++) { - if (wanted_enctypes[k] == e) { - is_wanted_enctype = 1; - break; - } - } - /* If unwanted, go to the next one. */ - if (!is_wanted_enctype) - continue; - - /* Is this enctype already in the list of enctypes to - request? (Is it a duplicate?) */ - is_duplicate_enctype = 0; - for (k = 0; k < i; k++) { - if (requested_enctypes[k] == e) { - is_duplicate_enctype = 1; - break; - } - } - /* If it is not a duplicate, add it. */ - if (!is_duplicate_enctype) - requested_enctypes[i++] = e; - } - krb5_free_ktypes(context, default_enctypes); - requested_enctypes[i++] = 0; - return GSS_S_COMPLETE; -} - /* * setup_enc * @@ -418,6 +334,9 @@ setup_enc( krb5_error_code code; int i; + ctx->have_acceptor_subkey = 0; + ctx->proto = 0; + ctx->cksumtype = 0; switch(ctx->subkey->enctype) { case ENCTYPE_DES_CBC_MD5: case ENCTYPE_DES_CBC_MD4: @@ -433,54 +352,53 @@ setup_enc( goto fail; for (i=0; ienc->length; i++) - /*SUPPRESS 113*/ ctx->enc->contents[i] ^= 0xf0; - if ((code = krb5_copy_keyblock(context, ctx->subkey, &ctx->seq))) - goto fail; - - break; + goto copy_subkey_to_seq; case ENCTYPE_DES3_CBC_SHA1: + /* MIT extension */ ctx->subkey->enctype = ENCTYPE_DES3_CBC_RAW; ctx->signalg = SGN_ALG_HMAC_SHA1_DES3_KD; ctx->cksum_size = 20; ctx->sealalg = SEAL_ALG_DES3KD; + copy_subkey: code = krb5_copy_keyblock (context, ctx->subkey, &ctx->enc); if (code) goto fail; + copy_subkey_to_seq: code = krb5_copy_keyblock (context, ctx->subkey, &ctx->seq); if (code) { krb5_free_keyblock (context, ctx->enc); goto fail; } break; + case ENCTYPE_ARCFOUR_HMAC: + /* Microsoft extension */ ctx->signalg = SGN_ALG_HMAC_MD5 ; ctx->cksum_size = 8; ctx->sealalg = SEAL_ALG_MICROSOFT_RC4 ; - code = krb5_copy_keyblock (context, ctx->subkey, &ctx->enc); - if (code) - goto fail; - code = krb5_copy_keyblock (context, ctx->subkey, &ctx->seq); - if (code) { - krb5_free_keyblock (context, ctx->enc); - goto fail; - } - break; -#if 0 - case ENCTYPE_DES3_CBC_MD5: - enctype = ENCTYPE_DES3_CBC_RAW; - ctx->signalg = 3; - ctx->cksum_size = 16; - ctx->sealalg = 1; - break; -#endif + goto copy_subkey; + default: - *minor_status = KRB5_BAD_ENCTYPE; - return GSS_S_FAILURE; + /* Fill some fields we shouldn't be using on this path + with garbage. */ + ctx->signalg = -10; + ctx->sealalg = -10; + + ctx->proto = 1; + code = krb5int_c_mandatory_cksumtype(context, ctx->subkey->enctype, + &ctx->cksumtype); + if (code) + goto fail; + code = krb5_c_checksum_length(context, ctx->cksumtype, + &ctx->cksum_size); + if (code) + goto fail; + goto copy_subkey; } fail: *minor_status = code; @@ -533,8 +451,17 @@ new_connection( /* complain if the input token is non-null */ if (input_token != GSS_C_NO_BUFFER && input_token->length != 0) { - *minor_status = 0; - return(GSS_S_DEFECTIVE_TOKEN); +#ifdef CFX_EXERCISE + if (*context_handle != GSS_C_NO_CONTEXT + && ((krb5_gss_ctx_id_t)*context_handle)->testing_unknown_tokid) { + /* XXX Should check for a KRB_ERROR message that we can + parse, and which contains the expected error code. */ + ctx = (krb5_gss_ctx_id_t)*context_handle; + goto resume_after_testing; + } +#endif + *minor_status = 0; + return(GSS_S_DEFECTIVE_TOKEN); } /* create the ctx */ @@ -557,8 +484,6 @@ new_connection( ctx->seed_init = 0; ctx->big_endian = 0; /* all initiators do little-endian, as per spec */ ctx->seqstate = 0; - ctx->nctypes = 0; - ctx->ctypes = 0; if ((code = krb5_timeofday(context, &now))) goto fail; @@ -576,13 +501,8 @@ new_connection( &ctx->there))) goto fail; - code = get_requested_enctypes(context, &requested_enctypes); - if (code) - goto fail; - code = get_credentials(context, cred, ctx->there, now, - ctx->endtime, requested_enctypes, &k_cred); - free(requested_enctypes); + ctx->endtime, &k_cred); if (code) goto fail; @@ -602,6 +522,7 @@ new_connection( { /* gsskrb5 v1 */ + krb5_ui_4 seq_temp; if ((code = make_ap_req_v1(context, ctx, cred, k_cred, input_chan_bindings, mech_type, &token))) { @@ -613,8 +534,8 @@ new_connection( goto fail; } - krb5_auth_con_getlocalseqnumber(context, ctx->auth_context, - &ctx->seq_send); + krb5_auth_con_getlocalseqnumber(context, ctx->auth_context, &seq_temp); + ctx->seq_send = seq_temp; krb5_auth_con_getsendsubkey(context, ctx->auth_context, &ctx->subkey); } @@ -638,6 +559,47 @@ new_connection( *context_handle = (gss_ctx_id_t) ctx; ctx_free = 0; +#ifdef CFX_EXERCISE + if (ctx->proto == 1 + /* I think the RPC code may be broken. Don't mess around + if we're authenticating to "kadmin/whatever". */ + && ctx->there->data[0].data[0] != 'k' + /* I *know* the FTP server code is broken. */ + && ctx->there->data[0].data[0] != 'f' + ) { + /* Create a bogus token and return it, with status + GSS_S_CONTINUE_NEEDED. Save enough data that we can resume + on the next call. */ + static const unsigned char hack_token[20] = { + 0x60, 0x12, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x12, 0x01, 0x02, 0x02, 0x12, 0x34, 0x68, + 0x65, 0x6c, 0x6c, 0x6f + }; + ctx->testing_unknown_tokid = 1; + ctx->init_token = token; + token.value = malloc(20); + token.length = 20; + if (token.value == NULL) { + /* Skip testing. We'll probably die soon enough, but let's + not do it because we couldn't exercise this code + path. */ + goto resume_after_testing; + } + memcpy(token.value, hack_token, sizeof(hack_token)); + /* Can just fall through into the normal return path, because + it'll always return GSS_S_CONTINUE_NEEDED because we're + doing mutual authentication. */ + } + if (0) { + resume_after_testing: + token = ctx->init_token; + ctx->init_token.value = 0; + ctx->init_token.length = 0; + ctx->testing_unknown_tokid = 0; + ctx_free = 0; + } +#endif + /* compute time_rec */ if (time_rec) { if ((code = krb5_timeofday(context, &now))) @@ -664,7 +626,7 @@ new_connection( ctx->seq_recv = ctx->seq_send; g_order_init(&(ctx->seqstate), ctx->seq_recv, (ctx->gss_flags & GSS_C_REPLAY_FLAG) != 0, - (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) != 0); + (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) != 0, ctx->proto); ctx->gss_flags |= GSS_C_PROT_READY_FLAG; ctx->established = 1; return(GSS_S_COMPLETE); @@ -680,8 +642,6 @@ fail: krb5_free_principal(context, ctx_free->there); if (ctx_free->subkey) krb5_free_keyblock(context, ctx_free->subkey); - if (ctx_free->ctypes) - krb5_free_cksumtypes(context, ctx_free->ctypes); xfree(ctx_free); } else (void)krb5_gss_delete_sec_context(minor_status, context_handle, NULL); @@ -766,11 +726,11 @@ mutual_auth( if (g_verify_token_header((gss_OID) ctx->mech_used, &(ap_rep.length), &ptr, KG_TOK_CTX_AP_REP, - input_token->length)) { + input_token->length, 1)) { if (g_verify_token_header((gss_OID) ctx->mech_used, &(ap_rep.length), &ptr, KG_TOK_CTX_ERROR, - input_token->length) == 0) { + input_token->length, 1) == 0) { /* Handle a KRB_ERROR message from the server */ @@ -813,7 +773,21 @@ mutual_auth( ctx->seq_recv = ap_rep_data->seq_number; g_order_init(&(ctx->seqstate), ctx->seq_recv, (ctx->gss_flags & GSS_C_REPLAY_FLAG) != 0, - (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) !=0); + (ctx->gss_flags & GSS_C_SEQUENCE_FLAG) !=0, ctx->proto); + + if (ctx->proto == 1 && ap_rep_data->subkey) { + /* Keep acceptor's subkey. */ + ctx->have_acceptor_subkey = 1; + code = krb5_copy_keyblock(context, ap_rep_data->subkey, + &ctx->acceptor_subkey); + if (code) + goto fail; + code = krb5int_c_mandatory_cksumtype(context, + ctx->acceptor_subkey->enctype, + &ctx->acceptor_subkey_cksumtype); + if (code) + goto fail; + } /* free the ap_rep_data */ krb5_free_ap_rep_enc_part(context, ap_rep_data); @@ -938,7 +912,11 @@ krb5_gss_init_sec_context(minor_status, claimant_cred_handle, /* is this a new connection or not? */ /*SUPPRESS 29*/ - if (*context_handle == GSS_C_NO_CONTEXT) { + if (*context_handle == GSS_C_NO_CONTEXT +#ifdef CFX_EXERCISE + || ((krb5_gss_ctx_id_t)*context_handle)->testing_unknown_tokid +#endif + ) { major_status = new_connection(minor_status, cred, context_handle, target_name, mech_type, req_flags, time_req, input_chan_bindings, diff --git a/src/lib/gssapi/krb5/k5seal.c b/src/lib/gssapi/krb5/k5seal.c index 3c8702a55..7999a3e15 100644 --- a/src/lib/gssapi/krb5/k5seal.c +++ b/src/lib/gssapi/krb5/k5seal.c @@ -54,7 +54,7 @@ static krb5_error_code make_seal_token_v1 (krb5_context context, krb5_keyblock *enc, krb5_keyblock *seq, - krb5_ui_4 *seqnum, + gssint_uint64 *seqnum, int direction, gss_buffer_t text, gss_buffer_t token, @@ -304,6 +304,7 @@ make_seal_token_v1 (krb5_context context, /* that's it. return the token */ (*seqnum)++; + *seqnum &= 0xffffffffL; token->length = tlen; token->value = (void *) t; @@ -334,50 +335,16 @@ kg_seal(context, minor_status, context_handle, conf_req_flag, qop_req, output_message_buffer->length = 0; output_message_buffer->value = NULL; - /* only default qop or matching established cryptosystem is allowed */ - -#if 0 - switch (qop_req & GSS_KRB5_CONF_C_QOP_MASK) { - case GSS_C_QOP_DEFAULT: - break; - default: - unknown_qop: - *minor_status = (OM_uint32) G_UNKNOWN_QOP; - return GSS_S_FAILURE; - case GSS_KRB5_CONF_C_QOP_DES: - if (ctx->sealalg != SEAL_ALG_DES) { - bad_qop: - *minor_status = (OM_uint32) G_BAD_QOP; - return GSS_S_FAILURE; - } - break; - case GSS_KRB5_CONF_C_QOP_DES3: - if (ctx->sealalg != SEAL_ALG_DES3) - goto bad_qop; - break; - } - switch (qop_req & GSS_KRB5_INTEG_C_QOP_MASK) { - case GSS_C_QOP_DEFAULT: - break; - default: - goto unknown_qop; - case GSS_KRB5_INTEG_C_QOP_MD5: - case GSS_KRB5_INTEG_C_QOP_DES_MD5: - case GSS_KRB5_INTEG_C_QOP_DES_MAC: - if (ctx->sealalg != SEAL_ALG_DES) - goto bad_qop; - break; - case GSS_KRB5_INTEG_C_QOP_HMAC_SHA1: - if (ctx->sealalg != SEAL_ALG_DES3KD) - goto bad_qop; - break; - } -#else + /* Only default qop or matching established cryptosystem is allowed. + + There are NO EXTENSIONS to this set for AES and friends! The + new spec says "just use 0". The old spec plus extensions would + actually allow for certain non-zero values. Fix this to handle + them later. */ if (qop_req != 0) { *minor_status = (OM_uint32) G_UNKNOWN_QOP; return GSS_S_FAILURE; } -#endif /* validate the context handle */ if (! kg_validate_ctx_id(context_handle)) { @@ -397,12 +364,26 @@ kg_seal(context, minor_status, context_handle, conf_req_flag, qop_req, return(GSS_S_FAILURE); } - code = make_seal_token_v1(context, ctx->enc, ctx->seq, - &ctx->seq_send, ctx->initiate, - input_message_buffer, output_message_buffer, - ctx->signalg, ctx->cksum_size, ctx->sealalg, - conf_req_flag, toktype, ctx->big_endian, - ctx->mech_used); + switch (ctx->proto) + { + case 0: + code = make_seal_token_v1(context, ctx->enc, ctx->seq, + &ctx->seq_send, ctx->initiate, + input_message_buffer, output_message_buffer, + ctx->signalg, ctx->cksum_size, ctx->sealalg, + conf_req_flag, toktype, ctx->big_endian, + ctx->mech_used); + break; + case 1: + code = gss_krb5int_make_seal_token_v3(context, ctx, + input_message_buffer, + output_message_buffer, + conf_req_flag, toktype); + break; + default: + code = G_UNKNOWN_QOP; /* XXX */ + break; + } if (code) { *minor_status = code; diff --git a/src/lib/gssapi/krb5/k5sealv3.c b/src/lib/gssapi/krb5/k5sealv3.c new file mode 100644 index 000000000..73b993b35 --- /dev/null +++ b/src/lib/gssapi/krb5/k5sealv3.c @@ -0,0 +1,476 @@ +/* Copyright 2003 MIT. All rights reserved. */ +/* draft-ietf-krb-wg-gssapi-cfx-02 plus discussed changes */ + +#include +#include "k5-platform.h" /* for 64-bit support */ +#include "k5-int.h" /* for zap() */ +#include "gssapiP_krb5.h" +#include + +static int +rotate_left (void *ptr, size_t bufsiz, size_t rc) +{ + /* Optimize for receiving. After some debugging is done, the MIT + implementation won't do any rotates on sending, and while + debugging, they'll be randomly chosen. + + Return 1 for success, 0 for failure (ENOMEM). */ + void *tbuf; + + if (bufsiz == 0) + return 1; + rc = rc % bufsiz; + if (rc == 0) + return 1; + + tbuf = malloc(rc); + if (tbuf == 0) + return 0; + memcpy(tbuf, ptr, rc); + memmove(ptr, (char *)ptr + rc, bufsiz - rc); + memcpy((char *)ptr + bufsiz - rc, tbuf, rc); + free(tbuf); + return 1; +} + +static const gss_buffer_desc empty_message = { 0, 0 }; + +#define FLAG_SENDER_IS_ACCEPTOR 0x01 +#define FLAG_WRAP_CONFIDENTIAL 0x02 +#define FLAG_ACCEPTOR_SUBKEY 0x04 + +krb5_error_code +gss_krb5int_make_seal_token_v3 (krb5_context context, + krb5_gss_ctx_id_rec *ctx, + const gss_buffer_desc * message, + gss_buffer_t token, + int conf_req_flag, int toktype) +{ + size_t bufsize = 16; + unsigned char *outbuf = 0; + krb5_error_code err; + int key_usage; + unsigned char acceptor_flag; + const gss_buffer_desc *message2 = message; + size_t rrc, ec; + unsigned short tok_id; + krb5_checksum sum; + krb5_keyblock *key; + + assert(toktype != KG_TOK_SEAL_MSG || ctx->enc != 0); + assert(ctx->big_endian == 0); + + acceptor_flag = ctx->initiate ? 0 : FLAG_SENDER_IS_ACCEPTOR; + key_usage = (toktype == KG_TOK_WRAP_MSG + ? (ctx->initiate + ? KG_USAGE_INITIATOR_SEAL + : KG_USAGE_ACCEPTOR_SEAL) + : (ctx->initiate + ? KG_USAGE_INITIATOR_SIGN + : KG_USAGE_ACCEPTOR_SIGN)); + if (ctx->have_acceptor_subkey) { + key = ctx->acceptor_subkey; + } else { + key = ctx->enc; + } + +#ifdef CFX_EXERCISE + { + static int initialized = 0; + if (!initialized) { + srand(time(0)); + initialized = 1; + } + } +#endif + + if (toktype == KG_TOK_WRAP_MSG && conf_req_flag) { + krb5_data plain; + krb5_enc_data cipher; + size_t ec_max; + + /* 300: Adds some slop. */ + if (SIZE_MAX - 300 < message->length) + return ENOMEM; + ec_max = SIZE_MAX - message->length - 300; + if (ec_max > 0xffff) + ec_max = 0xffff; +#ifdef CFX_EXERCISE + /* For testing only. For performance, always set ec = 0. */ + ec = ec_max & rand(); +#else + ec = 0; +#endif + plain.length = message->length + 16 + ec; + plain.data = malloc(message->length + 16 + ec); + if (plain.data == NULL) + return ENOMEM; + + /* Get size of ciphertext. */ + bufsize = 16 + krb5_encrypt_size (plain.length, ctx->enc->enctype); + /* Allocate space for header plus encrypted data. */ + outbuf = malloc(bufsize); + if (outbuf == NULL) { + free(plain.data); + return ENOMEM; + } + + /* TOK_ID */ + store_16_be(0x0504, outbuf); + /* flags */ + outbuf[2] = (acceptor_flag + | (conf_req_flag ? FLAG_WRAP_CONFIDENTIAL : 0) + | (ctx->have_acceptor_subkey ? FLAG_ACCEPTOR_SUBKEY : 0)); + /* filler */ + outbuf[3] = 0xff; + /* EC */ + store_16_be(ec, outbuf+4); + /* RRC */ + store_16_be(0, outbuf+6); + store_64_be(ctx->seq_send, outbuf+8); + + memcpy(plain.data, message->value, message->length); + memset(plain.data + message->length, 'x', ec); + memcpy(plain.data + message->length + ec, outbuf, 16); + + cipher.ciphertext.data = outbuf + 16; + cipher.ciphertext.length = bufsize - 16; + cipher.enctype = key->enctype; + err = krb5_c_encrypt(context, key, key_usage, 0, &plain, &cipher); + zap(plain.data, plain.length); + free(plain.data); + plain.data = 0; + if (err) + goto error; + + /* Now that we know we're returning a valid token.... */ + ctx->seq_send++; + +#ifdef CFX_EXERCISE + rrc = rand() & 0xffff; + if (rotate_left(outbuf+16, bufsize-16, + (bufsize-16) - (rrc % (bufsize - 16)))) + store_16_be(rrc, outbuf+6); + /* If the rotate fails, don't worry about it. */ +#endif + } else if (toktype == KG_TOK_WRAP_MSG && !conf_req_flag) { + krb5_data plain; + + /* Here, message is the application-supplied data; message2 is + what goes into the output token. They may be the same, or + message2 may be empty (for MIC). */ + + tok_id = 0x0504; + + wrap_with_checksum: + plain.length = message->length + 16; + plain.data = malloc(message->length + 16); + if (plain.data == NULL) + return ENOMEM; + + if (ctx->cksum_size > 0xffff) + abort(); + + bufsize = 16 + message2->length + ctx->cksum_size; + outbuf = malloc(bufsize); + if (outbuf == NULL) { + free(plain.data); + plain.data = 0; + err = ENOMEM; + goto error; + } + + /* TOK_ID */ + store_16_be(tok_id, outbuf); + /* flags */ + outbuf[2] = (acceptor_flag + | (ctx->have_acceptor_subkey ? FLAG_ACCEPTOR_SUBKEY : 0)); + /* filler */ + outbuf[3] = 0xff; + if (toktype == KG_TOK_WRAP_MSG) { + /* Use 0 for checksum calculation, substitute + checksum length later. */ + /* EC */ + store_16_be(0, outbuf+4); + /* RRC */ + store_16_be(0, outbuf+6); + } else { + /* MIC and DEL store 0xFF in EC and RRC. */ + store_16_be(0xffff, outbuf+4); + store_16_be(0xffff, outbuf+6); + } + store_64_be(ctx->seq_send, outbuf+8); + + memcpy(plain.data, message->value, message->length); + memcpy(plain.data + message->length, outbuf, 16); + + /* Fill in the output token -- data contents, if any, and + space for the checksum. */ + if (message2->length) + memcpy(outbuf + 16, message2->value, message2->length); + + sum.contents = outbuf + 16 + message2->length; + sum.length = ctx->cksum_size; + + err = krb5_c_make_checksum(context, ctx->cksumtype, key, + key_usage, &plain, &sum); + zap(plain.data, plain.length); + free(plain.data); + plain.data = 0; + if (err) { + zap(outbuf,bufsize); + free(outbuf); + goto error; + } + if (sum.length != ctx->cksum_size) + abort(); + memcpy(outbuf + 16 + message2->length, sum.contents, ctx->cksum_size); + krb5_free_checksum_contents(context, &sum); + sum.contents = 0; + /* Now that we know we're actually generating the token... */ + ctx->seq_send++; + + if (toktype == KG_TOK_WRAP_MSG) { +#ifdef CFX_EXERCISE + rrc = rand() & 0xffff; + /* If the rotate fails, don't worry about it. */ + if (rotate_left(outbuf+16, bufsize-16, + (bufsize-16) - (rrc % (bufsize - 16)))) + store_16_be(rrc, outbuf+6); +#endif + /* Fix up EC field. */ + store_16_be(ctx->cksum_size, outbuf+4); + } else { + store_16_be(0xffff, outbuf+6); + } + } else if (toktype == KG_TOK_MIC_MSG) { + tok_id = 0x0404; + message2 = &empty_message; + goto wrap_with_checksum; + } else if (toktype == KG_TOK_DEL_CTX) { + tok_id = 0x0405; + message = message2 = &empty_message; + goto wrap_with_checksum; + } else + abort(); + + token->value = outbuf; + token->length = bufsize; + return 0; + +error: + free(outbuf); + token->value = NULL; + token->length = 0; + return err; +} + +/* message_buffer is an input if SIGN, output if SEAL, and ignored if DEL_CTX + conf_state is only valid if SEAL. */ + +OM_uint32 +gss_krb5int_unseal_token_v3(krb5_context *contextptr, + OM_uint32 *minor_status, + krb5_gss_ctx_id_rec *ctx, + unsigned char *ptr, int bodysize, + gss_buffer_t message_buffer, + int *conf_state, int *qop_state, int toktype) +{ + krb5_context context = *contextptr; + krb5_data plain; + gssint_uint64 seqnum; + size_t ec, rrc; + int key_usage; + unsigned char acceptor_flag; + krb5_checksum sum; + krb5_error_code err; + krb5_boolean valid; + krb5_keyblock *key; + + assert(toktype != KG_TOK_SEAL_MSG || ctx->enc != 0); + assert(ctx->big_endian == 0); + assert(ctx->proto == 1); + + if (qop_state) + *qop_state = GSS_C_QOP_DEFAULT; + + acceptor_flag = ctx->initiate ? FLAG_SENDER_IS_ACCEPTOR : 0; + key_usage = (toktype == KG_TOK_WRAP_MSG + ? (!ctx->initiate + ? KG_USAGE_INITIATOR_SEAL + : KG_USAGE_ACCEPTOR_SEAL) + : (!ctx->initiate + ? KG_USAGE_INITIATOR_SIGN + : KG_USAGE_ACCEPTOR_SIGN)); + + /* Oops. I wrote this code assuming ptr would be at the start of + the token header. */ + ptr -= 2; + bodysize += 2; + + if (bodysize < 16) { + defective: + *minor_status = 0; + return GSS_S_DEFECTIVE_TOKEN; + } + if ((ptr[2] & FLAG_SENDER_IS_ACCEPTOR) != acceptor_flag) { + *minor_status = G_BAD_DIRECTION; + return GSS_S_BAD_SIG; + } + + /* Two things to note here. + + First, we can't really enforce the use of the acceptor's subkey, + if we're the acceptor; the initiator may have sent messages + before getting the subkey. We could probably enforce it if + we're the initiator. + + Second, if someone tweaks the code to not set the flag telling + the krb5 library to generate a new subkey in the AP-REP + message, the MIT library may include a subkey anyways -- + namely, a copy of the AP-REQ subkey, if it was provided. So + the initiator may think we wanted a subkey, and set the flag, + even though we weren't trying to set the subkey. The "other" + key, the one not asserted by the acceptor, will have the same + value in that case, though, so we can just ignore the flag. */ + if (ctx->have_acceptor_subkey && (ptr[2] & FLAG_ACCEPTOR_SUBKEY)) { + key = ctx->acceptor_subkey; + } else { + key = ctx->enc; + } + + if (toktype == KG_TOK_WRAP_MSG) { + if (load_16_be(ptr) != 0x0504) + goto defective; + if (ptr[3] != 0xff) + goto defective; + ec = load_16_be(ptr+4); + rrc = load_16_be(ptr+6); + seqnum = load_64_be(ptr+8); + if (!rotate_left(ptr+16, bodysize-16, rrc)) { + no_mem: + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + if (ptr[2] & FLAG_WRAP_CONFIDENTIAL) { + /* confidentiality */ + krb5_enc_data cipher; + unsigned char *althdr; + + if (conf_state) + *conf_state = 1; + /* Do we have no decrypt_size function? + + For all current cryptosystems, the ciphertext size will + be larger than the plaintext size. */ + cipher.enctype = key->enctype; + cipher.ciphertext.length = bodysize - 16; + cipher.ciphertext.data = ptr + 16; + plain.length = bodysize - 16; + plain.data = malloc(plain.length); + if (plain.data == NULL) + goto no_mem; + err = krb5_c_decrypt(context, key, key_usage, 0, + &cipher, &plain); + if (err) { + free(plain.data); + goto error; + } + /* Don't use bodysize here! Use the fact that + cipher.ciphertext.length has been adjusted to the + correct length. */ + althdr = plain.data + plain.length - 16; + if (load_16_be(althdr) != 0x0504 + || althdr[2] != ptr[2] + || althdr[3] != ptr[3] + || memcmp(althdr+8, ptr+8, 8)) + goto defective; + message_buffer->value = plain.data; + message_buffer->length = plain.length - ec - 16; + } else { + /* no confidentiality */ + if (conf_state) + *conf_state = 0; + if (ec + 16 < ec) + /* overflow check */ + goto defective; + if (ec + 16 > bodysize) + goto defective; + /* We have: header | msg | cksum. + We need cksum(msg | header). + Rotate the first two. */ + store_16_be(0, ptr+4); + store_16_be(0, ptr+6); + plain.length = bodysize-ec; + plain.data = ptr; + if (!rotate_left(ptr, bodysize-ec, 16)) + goto no_mem; + sum.length = ec; + if (sum.length != ctx->cksum_size) { + *minor_status = 0; + return GSS_S_BAD_SIG; + } + sum.contents = ptr+bodysize-ec; + sum.checksum_type = ctx->cksumtype; + err = krb5_c_verify_checksum(context, key, key_usage, + &plain, &sum, &valid); + if (err) + goto error; + if (!valid) { + *minor_status = 0; + return GSS_S_BAD_SIG; + } + message_buffer->length = plain.length - 16; + message_buffer->value = malloc(message_buffer->length); + if (message_buffer->value == NULL) + goto no_mem; + memcpy(message_buffer->value, plain.data, message_buffer->length); + } + err = g_order_check(&ctx->seqstate, seqnum); + *minor_status = 0; + return err; + } else if (toktype == KG_TOK_MIC_MSG) { + /* wrap token, no confidentiality */ + if (load_16_be(ptr) != 0x0404) + goto defective; + verify_mic_1: + if (ptr[3] != 0xff) + goto defective; + if (load_32_be(ptr+4) != 0xffffffffL) + goto defective; + seqnum = load_64_be(ptr+8); + plain.length = message_buffer->length + 16; + plain.data = malloc(plain.length); + if (plain.data == NULL) + goto no_mem; + if (message_buffer->length) + memcpy(plain.data, message_buffer->value, message_buffer->length); + memcpy(plain.data + message_buffer->length, ptr, 16); + sum.length = bodysize - 16; + sum.contents = ptr + 16; + sum.checksum_type = ctx->cksumtype; + err = krb5_c_verify_checksum(context, key, key_usage, + &plain, &sum, &valid); + if (err) { + error: + free(plain.data); + *minor_status = err; + return GSS_S_BAD_SIG; /* XXX */ + } + if (!valid) { + free(plain.data); + *minor_status = 0; + return GSS_S_BAD_SIG; + } + err = g_order_check(&ctx->seqstate, seqnum); + *minor_status = 0; + return err; + } else if (toktype == KG_TOK_DEL_CTX) { + if (load_16_be(ptr) != 0x0405) + goto defective; + message_buffer = &empty_message; + goto verify_mic_1; + } else { + goto defective; + } +} diff --git a/src/lib/gssapi/krb5/k5unseal.c b/src/lib/gssapi/krb5/k5unseal.c index e678311f9..6851352ee 100644 --- a/src/lib/gssapi/krb5/k5unseal.c +++ b/src/lib/gssapi/krb5/k5unseal.c @@ -490,6 +490,7 @@ kg_unseal(context, minor_status, context_handle, input_token_buffer, unsigned char *ptr; unsigned int bodysize; int err; + int toktype2; /* validate the context handle */ if (! kg_validate_ctx_id(context_handle)) { @@ -510,14 +511,38 @@ kg_unseal(context, minor_status, context_handle, input_token_buffer, ptr = (unsigned char *) input_token_buffer->value; - if (!(err = g_verify_token_header((gss_OID) ctx->mech_used, - &bodysize, &ptr, toktype, - input_token_buffer->length))) { - return(kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, - message_buffer, conf_state, qop_state, - toktype)); + if (ctx->proto) + switch (toktype) { + case KG_TOK_SIGN_MSG: + toktype2 = 0x0404; + break; + case KG_TOK_SEAL_MSG: + toktype2 = 0x0504; + break; + case KG_TOK_DEL_CTX: + toktype2 = 0x0405; + break; + default: + toktype2 = toktype; + break; + } + else + toktype2 = toktype; + err = g_verify_token_header((gss_OID) ctx->mech_used, + &bodysize, &ptr, toktype2, + input_token_buffer->length, + !ctx->proto); + if (err) { + *minor_status = err; + return GSS_S_DEFECTIVE_TOKEN; } - *minor_status = err; - return(GSS_S_DEFECTIVE_TOKEN); + if (ctx->proto == 0) + return kg_unseal_v1(context, minor_status, ctx, ptr, bodysize, + message_buffer, conf_state, qop_state, + toktype); + else + return gss_krb5int_unseal_token_v3(context, minor_status, ctx, + ptr, bodysize, message_buffer, + conf_state, qop_state, toktype); } diff --git a/src/lib/gssapi/krb5/ser_sctx.c b/src/lib/gssapi/krb5/ser_sctx.c index 8ab9401c3..f6314928e 100644 --- a/src/lib/gssapi/krb5/ser_sctx.c +++ b/src/lib/gssapi/krb5/ser_sctx.c @@ -237,7 +237,6 @@ kg_ctx_size(kcontext, arg, sizep) if ((ctx = (krb5_gss_ctx_id_rec *) arg)) { required = 16*sizeof(krb5_int32); required += sizeof(ctx->seed); - required += ctx->nctypes*sizeof(krb5_int32); kret = 0; if (!kret && ctx->here) @@ -337,16 +336,14 @@ kg_ctx_externalize(kcontext, arg, buffer, lenremain) &bp, &remain); (void) krb5_ser_pack_int32((krb5_int32) ctx->krb_flags, &bp, &remain); - (void) krb5_ser_pack_int32((krb5_int32) ctx->seq_send, + (void) krb5_ser_pack_int64((krb5_int64) ctx->seq_send, &bp, &remain); - (void) krb5_ser_pack_int32((krb5_int32) ctx->seq_recv, + (void) krb5_ser_pack_int64((krb5_int64) ctx->seq_recv, &bp, &remain); (void) krb5_ser_pack_int32((krb5_int32) ctx->established, &bp, &remain); (void) krb5_ser_pack_int32((krb5_int32) ctx->big_endian, &bp, &remain); - (void) krb5_ser_pack_int32((krb5_int32) ctx->nctypes, - &bp, &remain); /* Now dynamic data */ kret = 0; @@ -395,13 +392,6 @@ kg_ctx_externalize(kcontext, arg, buffer, lenremain) (krb5_pointer) ctx->auth_context, &bp, &remain); - for (i=0; inctypes; i++) { - if (!kret) { - kret = krb5_ser_pack_int32((krb5_int32) ctx->ctypes[i], - &bp, &remain); - } - } - if (!kret) { (void) krb5_ser_pack_int32(KG_CONTEXT, &bp, &remain); *buffer = bp; @@ -464,14 +454,12 @@ kg_ctx_internalize(kcontext, argp, buffer, lenremain) ctx->endtime = (krb5_timestamp) ibuf; (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain); ctx->krb_flags = (krb5_flags) ibuf; - (void) krb5_ser_unpack_int32(&ctx->seq_send, &bp, &remain); - (void) krb5_ser_unpack_int32(&ctx->seq_recv, &bp, &remain); + (void) krb5_ser_unpack_int64(&ctx->seq_send, &bp, &remain); + (void) krb5_ser_unpack_int64(&ctx->seq_recv, &bp, &remain); (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain); ctx->established = (int) ibuf; (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain); ctx->big_endian = (int) ibuf; - (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain); - ctx->nctypes = (int) ibuf; if ((kret = kg_oid_internalize(kcontext, &ctx->mech_used, &bp, &remain))) { @@ -531,22 +519,6 @@ kg_ctx_internalize(kcontext, argp, buffer, lenremain) KV5M_AUTH_CONTEXT, (krb5_pointer *) &ctx->auth_context, &bp, &remain); - - if (!kret) { - if (ctx->nctypes) { - if ((ctx->ctypes = (krb5_cksumtype *) - malloc(ctx->nctypes*sizeof(krb5_cksumtype))) == NULL){ - kret = ENOMEM; - } - - for (i=0; inctypes; i++) { - if (!kret) { - kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain); - ctx->ctypes[i] = (krb5_cksumtype) ibuf; - } - } - } - } /* Get trailer */ if (!kret && diff --git a/src/lib/gssapi/krb5/wrap_size_limit.c b/src/lib/gssapi/krb5/wrap_size_limit.c index 43ccc6415..59bf30e4c 100644 --- a/src/lib/gssapi/krb5/wrap_size_limit.c +++ b/src/lib/gssapi/krb5/wrap_size_limit.c @@ -110,6 +110,32 @@ krb5_gss_wrap_size_limit(minor_status, context_handle, conf_req_flag, return(GSS_S_NO_CONTEXT); } + if (ctx->proto == 1) { + /* No pseudo-ASN.1 wrapper overhead, so no sequence length and + OID. */ + OM_uint32 sz = req_output_size; + /* Token header: 16 octets. */ + if (conf_req_flag) { + while (sz > 0 && krb5_encrypt_size(sz, ctx->enc->enctype) + 16 > req_output_size) + sz--; + } else { + if (sz < 16 + ctx->cksum_size) + sz = 0; + else + sz -= (16 + ctx->cksum_size); + } + + /* While testing only! */ + if (sz < 65536) + sz = 0; + else + sz -= 65535; + + *max_input_size = sz; + *minor_status = 0; + return GSS_S_COMPLETE; + } + /* Calculate the token size and subtract that from the output size */ overhead = 7 + ctx->mech_used->length; data_size = req_output_size; -- 2.26.2