From 7cd9c611e2a1e0028081863a3678c47bc8af7b55 Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Thu, 11 Oct 2001 13:13:06 +0000 Subject: added a ASN.1 parser, so now I can properly parse the negTokenInit packet which means I can extract the service and realm, so we should now work with realms other than the local realm. it also means we now check the list of OIDs given by the server just in case it says that it doesn't support kerberos. In that case we should fall back to NTLMSSP but that isn't written yet. (This used to be commit 395cfeea94febb5280ea57027e8a8a3c7c3f9291) --- source3/libsmb/asn1.c | 186 +++++++++++++++++++++++++++++++++++++++----- source3/libsmb/cliconnect.c | 33 +++++++- source3/libsmb/clikrb5.c | 179 ++++++++++++++++++++++++++++-------------- 3 files changed, 320 insertions(+), 78 deletions(-) (limited to 'source3/libsmb') diff --git a/source3/libsmb/asn1.c b/source3/libsmb/asn1.c index e4c2d3af80..17b1ee1089 100644 --- a/source3/libsmb/asn1.c +++ b/source3/libsmb/asn1.c @@ -30,9 +30,13 @@ void asn1_free(ASN1_DATA *data) /* write to the ASN1 buffer, advancing the buffer pointer */ BOOL asn1_write(ASN1_DATA *data, const void *p, int len) { + if (data->has_error) return False; if (data->length < data->ofs+len) { data->data = Realloc(data->data, data->ofs+len); - if (!data->data) return False; + if (!data->data) { + data->has_error = True; + return False; + } data->length = data->ofs+len; } memcpy(data->data + data->ofs, p, len); @@ -53,13 +57,15 @@ BOOL asn1_push_tag(ASN1_DATA *data, uint8 tag) asn1_write_uint8(data, tag); nesting = (struct nesting *)malloc(sizeof(struct nesting)); - if (!nesting) return False; + if (!nesting) { + data->has_error = True; + return False; + } nesting->start = data->ofs; nesting->next = data->nesting; data->nesting = nesting; - asn1_write_uint8(data, 0xff); - return True; + return asn1_write_uint8(data, 0xff); } /* pop a tag */ @@ -71,6 +77,7 @@ BOOL asn1_pop_tag(ASN1_DATA *data) nesting = data->nesting; if (!nesting) { + data->has_error = True; return False; } len = data->ofs - (nesting->start+1); @@ -79,8 +86,8 @@ BOOL asn1_pop_tag(ASN1_DATA *data) need to correct our mistake */ if (len > 127) { data->data[nesting->start] = 0x82; - asn1_write_uint8(data, 0); - asn1_write_uint8(data, 0); + if (!asn1_write_uint8(data, 0)) return False; + if (!asn1_write_uint8(data, 0)) return False; memmove(data->data+nesting->start+3, data->data+nesting->start+1, len); data->data[nesting->start+1] = len>>8; data->data[nesting->start+2] = len&0xff; @@ -99,10 +106,10 @@ BOOL asn1_write_OID(ASN1_DATA *data, const char *OID) unsigned v, v2; char *p = (char *)OID; - asn1_push_tag(data, ASN1_OID); + if (!asn1_push_tag(data, ASN1_OID)) return False; v = strtol(p, &p, 10); v2 = strtol(p, &p, 10); - asn1_write_uint8(data, 40*v + v2); + if (!asn1_write_uint8(data, 40*v + v2)) return False; while (*p) { v = strtol(p, &p, 10); @@ -110,10 +117,9 @@ BOOL asn1_write_OID(ASN1_DATA *data, const char *OID) if (v >= (1<<21)) asn1_write_uint8(data, 0x80 | ((v>>21)&0xff)); if (v >= (1<<14)) asn1_write_uint8(data, 0x80 | ((v>>14)&0xff)); if (v >= (1<<7)) asn1_write_uint8(data, 0x80 | ((v>>7)&0xff)); - asn1_write_uint8(data, v&0x7f); + if (!asn1_write_uint8(data, v&0x7f)) return False; } - asn1_pop_tag(data); - return True; + return asn1_pop_tag(data); } /* write an octet string */ @@ -122,7 +128,7 @@ BOOL asn1_write_OctetString(ASN1_DATA *data, const void *p, size_t length) asn1_push_tag(data, ASN1_OCTET_STRING); asn1_write(data, p, length); asn1_pop_tag(data); - return True; + return !data->has_error; } /* write a general string */ @@ -131,7 +137,7 @@ BOOL asn1_write_GeneralString(ASN1_DATA *data, const char *s) asn1_push_tag(data, ASN1_GENERAL_STRING); asn1_write(data, s, strlen(s)); asn1_pop_tag(data); - return True; + return !data->has_error; } /* write a BOOLEAN */ @@ -139,17 +145,20 @@ BOOL asn1_write_BOOLEAN(ASN1_DATA *data, BOOL v) { asn1_write_uint8(data, ASN1_BOOLEAN); asn1_write_uint8(data, v); - return True; + return !data->has_error; } /* load a ASN1_DATA structure with a lump of data, ready to be parsed */ -BOOL asn1_load(ASN1_DATA *data, void *p, size_t length) +BOOL asn1_load(ASN1_DATA *data, DATA_BLOB blob) { ZERO_STRUCTP(data); - data->data = memdup(p, length); - if (!data->data) return False; - data->length = length; + data->data = memdup(blob.data, blob.length); + if (!data->data) { + data->has_error = True; + return False; + } + data->length = blob.length; return True; } @@ -157,6 +166,7 @@ BOOL asn1_load(ASN1_DATA *data, void *p, size_t length) BOOL asn1_read(ASN1_DATA *data, void *p, int len) { if (data->ofs + len > data->length) { + data->has_error = True; return False; } memcpy(p, data->data + data->ofs, len); @@ -164,4 +174,144 @@ BOOL asn1_read(ASN1_DATA *data, void *p, int len) return True; } +/* read a uint8 from a ASN1 buffer */ +BOOL asn1_read_uint8(ASN1_DATA *data, uint8 *v) +{ + return asn1_read(data, v, 1); +} +/* start reading a nested asn1 structure */ +BOOL asn1_start_tag(ASN1_DATA *data, uint8 tag) +{ + uint8 b; + struct nesting *nesting; + + asn1_read_uint8(data, &b); + if (b != tag) { + data->has_error = True; + return False; + } + nesting = (struct nesting *)malloc(sizeof(struct nesting)); + if (!nesting) { + data->has_error = True; + return False; + } + + asn1_read_uint8(data, &b); + if (b & 0x80) { + int n = b & 0x7f; + if (n != 2) { + data->has_error = True; + return False; + } + asn1_read_uint8(data, &b); + nesting->taglen = b<<8; + asn1_read_uint8(data, &b); + nesting->taglen |= b; + } else { + nesting->taglen = b; + } + nesting->start = data->ofs; + nesting->next = data->nesting; + data->nesting = nesting; + return !data->has_error; +} + + +/* stop reading a tag */ +BOOL asn1_end_tag(ASN1_DATA *data) +{ + struct nesting *nesting; + + /* make sure we read it all */ + if (asn1_tag_remaining(data) != 0) { + data->has_error = True; + return False; + } + + nesting = data->nesting; + + if (!nesting) { + data->has_error = True; + return False; + } + + data->nesting = nesting->next; + free(nesting); + return True; +} + +/* work out how many bytes are left in this nested tag */ +int asn1_tag_remaining(ASN1_DATA *data) +{ + if (!data->nesting) { + data->has_error = True; + return -1; + } + return data->nesting->taglen - (data->ofs - data->nesting->start); +} + +/* read an object ID from a ASN1 buffer */ +BOOL asn1_read_OID(ASN1_DATA *data, char **OID) +{ + uint8 b; + pstring oid; + fstring el; + + if (!asn1_start_tag(data, ASN1_OID)) return False; + asn1_read_uint8(data, &b); + + oid[0] = 0; + snprintf(el, sizeof(el), "%u", b/40); + pstrcat(oid, el); + snprintf(el, sizeof(el), " %u", b%40); + pstrcat(oid, el); + + while (asn1_tag_remaining(data) > 0) { + unsigned v = 0; + do { + asn1_read_uint8(data, &b); + v = (v<<7) | (b&0x7f); + } while (!data->has_error && b & 0x80); + snprintf(el, sizeof(el), " %u", v); + pstrcat(oid, el); + } + + asn1_end_tag(data); + + *OID = strdup(oid); + + return !data->has_error; +} + +/* check that the next object ID is correct */ +BOOL asn1_check_OID(ASN1_DATA *data, char *OID) +{ + char *id; + + if (!asn1_read_OID(data, &id)) return False; + + if (strcmp(id, OID) != 0) { + data->has_error = True; + return False; + } + free(id); + return True; +} + +/* read a GeneralString from a ASN1 buffer */ +BOOL asn1_read_GeneralString(ASN1_DATA *data, char **s) +{ + int len; + if (!asn1_start_tag(data, ASN1_GENERAL_STRING)) return False; + len = asn1_tag_remaining(data); + *s = malloc(len+1); + if (! *s) { + data->has_error = True; + return False; + } + asn1_read(data, *s, len); + (*s)[len] = 0; + asn1_end_tag(data); + return !data->has_error; +} diff --git a/source3/libsmb/cliconnect.c b/source3/libsmb/cliconnect.c index 77a8232ed5..36aedf2d59 100644 --- a/source3/libsmb/cliconnect.c +++ b/source3/libsmb/cliconnect.c @@ -325,8 +325,39 @@ static BOOL cli_session_setup_spnego(struct cli_state *cli, char *user, uint32 capabilities = cli_session_setup_capabilities(cli); char *p; DATA_BLOB blob2, negTokenTarg; + char *principle; + char *OIDs[ASN1_MAX_OIDS]; + uint8 guid[16]; + int i; + BOOL got_kerberos_mechanism = False; + + /* the server sent us the first part of the SPNEGO exchange in the negprot + reply */ + if (!spnego_parse_negTokenInit(cli->secblob, guid, OIDs, &principle)) { + return False; + } + + /* make sure the server understands kerberos */ + for (i=0;OIDs[i];i++) { + DEBUG(3,("got OID=%s\n", OIDs[i])); + if (strcmp(OIDs[i], "1 2 840 48018 1 2 2") == 0) { + got_kerberos_mechanism = True; + } + free(OIDs[i]); + } + DEBUG(3,("got principle=%s\n", principle)); + + if (!got_kerberos_mechanism) { + DEBUG(1,("Server didn't offer kerberos5 mechanism!?\n")); + return False; + } + + /* generate the encapsulated kerberos5 ticket */ + negTokenTarg = spnego_gen_negTokenTarg(cli, principle); + + free(principle); - negTokenTarg = spnego_gen_negTokenTarg(cli); + if (!negTokenTarg.data) return False; capabilities |= CAP_EXTENDED_SECURITY; diff --git a/source3/libsmb/clikrb5.c b/source3/libsmb/clikrb5.c index 0e049c14a1..fb442f7f09 100644 --- a/source3/libsmb/clikrb5.c +++ b/source3/libsmb/clikrb5.c @@ -27,6 +27,8 @@ #define OID_SPNEGO "1 3 6 1 5 5 2" #define OID_KERBEROS5 "1 2 840 113554 1 2 2" +#define CHECK_CALL(x) if (! x) goto failed + /* we can't use krb5_mk_req because w2k wants the service to be in a particular format */ @@ -34,60 +36,62 @@ static krb5_error_code krb5_mk_req2(krb5_context context, krb5_auth_context *auth_context, const krb5_flags ap_req_options, const char *service, - krb5_data *in_data, + const char *realm, krb5_ccache ccache, krb5_data *outbuf) { - krb5_error_code retval; - krb5_principal server; - krb5_creds * credsp; - krb5_creds creds; - char *realm; - - /* we should really get the realm from the negTargInit packet, - but this will do until I've done the asn1 decoder for that */ - if ((retval = krb5_get_default_realm(context, &realm))) { - return retval; - } - - retval = krb5_build_principal(context, &server, strlen(realm), - realm, service, NULL); - if (retval) - return retval; - - /* obtain ticket & session key */ - memset((char *)&creds, 0, sizeof(creds)); - if ((retval = krb5_copy_principal(context, server, &creds.server))) - goto cleanup_princ; - - if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) - goto cleanup_creds; - - if ((retval = krb5_get_credentials(context, 0, - ccache, &creds, &credsp))) - goto cleanup_creds; - - retval = krb5_mk_req_extended(context, auth_context, ap_req_options, - in_data, credsp, outbuf); + krb5_error_code retval; + krb5_principal server; + krb5_creds * credsp; + krb5_creds creds; + krb5_data in_data; + + retval = krb5_build_principal(context, &server, strlen(realm), + realm, service, NULL); + if (retval) { + DEBUG(1,("Failed to build principle for %s@%s\n", service, realm)); + return retval; + } + + /* obtain ticket & session key */ + memset((char *)&creds, 0, sizeof(creds)); + if ((retval = krb5_copy_principal(context, server, &creds.server))) + goto cleanup_princ; + + if ((retval = krb5_cc_get_principal(context, ccache, &creds.client))) + goto cleanup_creds; + + if ((retval = krb5_get_credentials(context, 0, + ccache, &creds, &credsp))) { + DEBUG(1,("krb5_get_credentials failed (%d)\n", retval)); + goto cleanup_creds; + } - krb5_free_creds(context, credsp); + in_data.length = 0; + retval = krb5_mk_req_extended(context, auth_context, ap_req_options, + &in_data, credsp, outbuf); + if (retval) { + DEBUG(1,("krb5_mk_req_extended failed (%d)\n", retval)); + } + + krb5_free_creds(context, credsp); cleanup_creds: - krb5_free_cred_contents(context, &creds); + krb5_free_cred_contents(context, &creds); cleanup_princ: - krb5_free_principal(context, server); + krb5_free_principal(context, server); - return retval; + return retval; } /* get a kerberos5 ticket for the given service */ -static DATA_BLOB krb5_get_ticket(char *service) +static DATA_BLOB krb5_get_ticket(char *service, char *realm) { krb5_error_code retval; - krb5_data packet, inbuf; + krb5_data packet; krb5_ccache ccdef; krb5_context context; krb5_auth_context auth_context = NULL; @@ -99,8 +103,6 @@ static DATA_BLOB krb5_get_ticket(char *service) goto failed; } - inbuf.length = 0; - if ((retval = krb5_cc_default(context, &ccdef))) { DEBUG(1,("krb5_cc_default failed\n")); goto failed; @@ -109,8 +111,8 @@ static DATA_BLOB krb5_get_ticket(char *service) if ((retval = krb5_mk_req2(context, &auth_context, AP_OPTS_MUTUAL_REQUIRED, - service, - &inbuf, ccdef, &packet))) { + service, realm, + ccdef, &packet))) { DEBUG(1,("krb5_mk_req2 failed\n")); goto failed; } @@ -139,16 +141,16 @@ ASN1_DATA spnego_gen_negTokenInit(uint8 guid[16], memset(&data, 0, sizeof(data)); - asn1_write(&data, guid, 16); - asn1_push_tag(&data,ASN1_APPLICATION(0)); - asn1_write_OID(&data,OID_SPNEGO); - asn1_push_tag(&data,ASN1_CONTEXT(0)); - asn1_push_tag(&data,ASN1_SEQUENCE(0)); + CHECK_CALL(asn1_write(&data, guid, 16)); + CHECK_CALL(asn1_push_tag(&data,ASN1_APPLICATION(0))); + CHECK_CALL(asn1_write_OID(&data,OID_SPNEGO)); + CHECK_CALL(asn1_push_tag(&data,ASN1_CONTEXT(0))); + CHECK_CALL(asn1_push_tag(&data,ASN1_SEQUENCE(0))); - asn1_push_tag(&data,ASN1_CONTEXT(0)); - asn1_push_tag(&data,ASN1_SEQUENCE(0)); + CHECK_CALL(asn1_push_tag(&data,ASN1_CONTEXT(0))); + CHECK_CALL(asn1_push_tag(&data,ASN1_SEQUENCE(0))); for (i=0; OIDs[i]; i++) { - asn1_write_OID(&data,OIDs[i]); + CHECK_CALL(asn1_write_OID(&data,OIDs[i])); } asn1_pop_tag(&data); asn1_pop_tag(&data); @@ -167,6 +169,62 @@ ASN1_DATA spnego_gen_negTokenInit(uint8 guid[16], asn1_pop_tag(&data); return data; + +failed: + DEBUG(1,("Failed to build negTokenInit at offset %d\n", (int)data.ofs)); + asn1_free(&data); + return data; +} + + +/* + parse a negTokenInit packet giving a GUID, a list of supported + OIDs (the mechanisms) and a principle name string +*/ +BOOL spnego_parse_negTokenInit(DATA_BLOB blob, + uint8 guid[16], + char *OIDs[ASN1_MAX_OIDS], + char **principle) +{ + int i; + BOOL ret; + ASN1_DATA data; + + asn1_load(&data, blob); + + asn1_read(&data, guid, 16); + asn1_start_tag(&data,ASN1_APPLICATION(0)); + asn1_check_OID(&data,OID_SPNEGO); + asn1_start_tag(&data,ASN1_CONTEXT(0)); + asn1_start_tag(&data,ASN1_SEQUENCE(0)); + + asn1_start_tag(&data,ASN1_CONTEXT(0)); + asn1_start_tag(&data,ASN1_SEQUENCE(0)); + for (i=0; asn1_tag_remaining(&data) > 0 && i < ASN1_MAX_OIDS; i++) { + char *oid = NULL; + asn1_read_OID(&data,&oid); + OIDs[i] = oid; + } + OIDs[i] = NULL; + asn1_end_tag(&data); + asn1_end_tag(&data); + + asn1_start_tag(&data, ASN1_CONTEXT(3)); + asn1_start_tag(&data, ASN1_SEQUENCE(0)); + asn1_start_tag(&data, ASN1_CONTEXT(0)); + asn1_read_GeneralString(&data,principle); + asn1_end_tag(&data); + asn1_end_tag(&data); + asn1_end_tag(&data); + + asn1_end_tag(&data); + asn1_end_tag(&data); + + asn1_end_tag(&data); + + ret = !data.has_error; + asn1_free(&data); + return ret; } @@ -229,25 +287,28 @@ static ASN1_DATA spnego_gen_krb5_wrap(DATA_BLOB ticket) generate a SPNEGO negTokenTarg packet, ready for a EXTENDED_SECURITY kerberos session setup */ -DATA_BLOB spnego_gen_negTokenTarg(struct cli_state *cli) +DATA_BLOB spnego_gen_negTokenTarg(struct cli_state *cli, char *principle) { char *p; fstring service; + char *realm; DATA_BLOB tkt, ret; ASN1_DATA tkt_wrapped, targ; const char *krb_mechs[] = {"1 2 840 48018 1 2 2", "1 3 6 1 4 1 311 2 2 10", NULL}; - /* the service name is the WINS name of the server in lowercase with - a $ on the end */ - fstrcpy(service, cli->desthost); - p = strchr_m(service, '.'); - if (p) *p = 0; - fstrcat(service, "$"); - strlower(service); + fstrcpy(service, principle); + p = strchr_m(service, '@'); + if (!p) { + DEBUG(1,("Malformed principle [%s] in spnego_gen_negTokenTarg\n", + principle)); + return data_blob(NULL, 0); + } + *p = 0; + realm = p+1; /* get a kerberos ticket for the service */ - tkt = krb5_get_ticket(service); + tkt = krb5_get_ticket(service, realm); /* wrap that up in a nice GSS-API wrapping */ tkt_wrapped = spnego_gen_krb5_wrap(tkt); -- cgit