X-Git-Url: http://git.samba.org/?a=blobdiff_plain;f=source3%2Flibsmb%2Fclispnego.c;h=49b484b56c2405fc69198c2644da46412f38d497;hb=1b85990b833fe4ef2007e82ffe26ee18f87cb464;hp=78cae3315a856aad33760017e13bdae9b3c59a78;hpb=b728042334f67738fd1a6fdd03e619bdb78fe06a;p=samba.git diff --git a/source3/libsmb/clispnego.c b/source3/libsmb/clispnego.c index 78cae3315a8..49b484b56c2 100644 --- a/source3/libsmb/clispnego.c +++ b/source3/libsmb/clispnego.c @@ -1,12 +1,13 @@ /* - Unix SMB/Netbios implementation. - Version 3.0 + Unix SMB/CIFS implementation. simple kerberos5/SPNEGO routines Copyright (C) Andrew Tridgell 2001 - + Copyright (C) Jim McDonough 2002 + Copyright (C) Luke Howard 2003 + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -15,228 +16,407 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + along with this program. If not, see . */ #include "includes.h" +#include "../libcli/auth/spnego.h" +#include "smb_krb5.h" /* generate a negTokenInit packet given a GUID, a list of supported - OIDs (the mechanisms) and a principle name string + OIDs (the mechanisms) and a principal name string */ -DATA_BLOB spnego_gen_negTokenInit(uint8 guid[16], +DATA_BLOB spnego_gen_negTokenInit(char guid[16], const char *OIDs[], - const char *principle) + const char *principal) { int i; - ASN1_DATA data; + ASN1_DATA *data; DATA_BLOB ret; - memset(&data, 0, sizeof(data)); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return data_blob_null; + } - 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)); + 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)); - asn1_push_tag(&data,ASN1_CONTEXT(0)); - asn1_push_tag(&data,ASN1_SEQUENCE(0)); + asn1_push_tag(data,ASN1_CONTEXT(0)); + asn1_push_tag(data,ASN1_SEQUENCE(0)); for (i=0; OIDs[i]; i++) { - asn1_write_OID(&data,OIDs[i]); + asn1_write_OID(data,OIDs[i]); } - asn1_pop_tag(&data); - asn1_pop_tag(&data); + asn1_pop_tag(data); + asn1_pop_tag(data); - asn1_push_tag(&data, ASN1_CONTEXT(3)); - asn1_push_tag(&data, ASN1_SEQUENCE(0)); - asn1_push_tag(&data, ASN1_CONTEXT(0)); - asn1_write_GeneralString(&data,principle); - asn1_pop_tag(&data); - asn1_pop_tag(&data); - asn1_pop_tag(&data); + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_push_tag(data, ASN1_CONTEXT(0)); + asn1_write_GeneralString(data,principal); + asn1_pop_tag(data); + asn1_pop_tag(data); + asn1_pop_tag(data); - asn1_pop_tag(&data); - asn1_pop_tag(&data); + asn1_pop_tag(data); + asn1_pop_tag(data); - asn1_pop_tag(&data); + asn1_pop_tag(data); - if (data.has_error) { - DEBUG(1,("Failed to build negTokenInit at offset %d\n", (int)data.ofs)); - asn1_free(&data); + if (data->has_error) { + DEBUG(1,("Failed to build negTokenInit at offset %d\n", (int)data->ofs)); } - ret = data_blob(data.data, data.length); - asn1_free(&data); + ret = data_blob(data->data, data->length); + asn1_free(data); return ret; } +/* + Generate a negTokenInit as used by the client side ... It has a mechType + (OID), and a mechToken (a security blob) ... + + Really, we need to break out the NTLMSSP stuff as well, because it could be + raw in the packets! +*/ +DATA_BLOB gen_negTokenInit(const char *OID, DATA_BLOB blob) +{ + ASN1_DATA *data; + DATA_BLOB ret; + + data = asn1_init(talloc_tos()); + if (data == NULL) { + return data_blob_null; + } + + 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)); + + asn1_push_tag(data, ASN1_CONTEXT(0)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_OID(data, OID); + asn1_pop_tag(data); + asn1_pop_tag(data); + + asn1_push_tag(data, ASN1_CONTEXT(2)); + asn1_write_OctetString(data,blob.data,blob.length); + asn1_pop_tag(data); + + asn1_pop_tag(data); + asn1_pop_tag(data); + + asn1_pop_tag(data); + + if (data->has_error) { + DEBUG(1,("Failed to build negTokenInit at offset %d\n", (int)data->ofs)); + } + + ret = data_blob(data->data, data->length); + asn1_free(data); + + return ret; +} /* parse a negTokenInit packet giving a GUID, a list of supported - OIDs (the mechanisms) and a principle name string + OIDs (the mechanisms) and a principal name string */ -BOOL spnego_parse_negTokenInit(DATA_BLOB blob, - uint8 guid[16], - char *OIDs[ASN1_MAX_OIDS], - char **principle) +bool spnego_parse_negTokenInit(DATA_BLOB blob, + char *OIDs[ASN1_MAX_OIDS], + char **principal) { int i; - BOOL ret; - ASN1_DATA data; + bool ret; + ASN1_DATA *data; + + for (i = 0; i < ASN1_MAX_OIDS; i++) { + OIDs[i] = NULL; + } - asn1_load(&data, blob); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return false; + } - 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_load(data, blob); - 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; + asn1_start_tag(data,ASN1_APPLICATION(0)); + + asn1_check_OID(data,OID_SPNEGO); + + /* negTokenInit [0] NegTokenInit */ + asn1_start_tag(data,ASN1_CONTEXT(0)); + asn1_start_tag(data,ASN1_SEQUENCE(0)); + + /* mechTypes [0] MechTypeList OPTIONAL */ + + /* Not really optional, we depend on this to decide + * what mechanisms we have to work with. */ + + 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-1; i++) { + const char *oid_str = NULL; + asn1_read_OID(data,talloc_autofree_context(),&oid_str); + if (data->has_error) { + break; + } + OIDs[i] = CONST_DISCARD(char *, oid_str); } OIDs[i] = NULL; - asn1_end_tag(&data); - asn1_end_tag(&data); + asn1_end_tag(data); + asn1_end_tag(data); + + *principal = NULL; + + /* + Win7 + Live Sign-in Assistant attaches a mechToken + ASN1_CONTEXT(2) to the negTokenInit packet + which breaks our negotiation if we just assume + the next tag is ASN1_CONTEXT(3). + */ + + if (asn1_peek_tag(data, ASN1_CONTEXT(1))) { + uint8 flags; + + /* reqFlags [1] ContextFlags OPTIONAL */ + asn1_start_tag(data, ASN1_CONTEXT(1)); + asn1_start_tag(data, ASN1_BIT_STRING); + while (asn1_tag_remaining(data) > 0) { + asn1_read_uint8(data, &flags); + } + asn1_end_tag(data); + asn1_end_tag(data); + } + + if (asn1_peek_tag(data, ASN1_CONTEXT(2))) { + /* mechToken [2] OCTET STRING OPTIONAL */ + DATA_BLOB token; + asn1_start_tag(data, ASN1_CONTEXT(2)); + asn1_read_OctetString(data, talloc_autofree_context(), + &token); + asn1_end_tag(data); + /* Throw away the token - not used. */ + data_blob_free(&token); + } - 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); + if (asn1_peek_tag(data, ASN1_CONTEXT(3))) { + /* mechListMIC [3] OCTET STRING OPTIONAL */ + 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,talloc_autofree_context(), + principal); + asn1_end_tag(data); + asn1_end_tag(data); + asn1_end_tag(data); + } + + 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; + if (data->has_error) { + int j; + TALLOC_FREE(*principal); + for(j = 0; j < i && j < ASN1_MAX_OIDS-1; j++) { + TALLOC_FREE(OIDs[j]); + } + } - ret = !data.has_error; - asn1_free(&data); + asn1_free(data); return ret; } - /* generate a negTokenTarg packet given a list of OIDs and a security blob */ DATA_BLOB gen_negTokenTarg(const char *OIDs[], DATA_BLOB blob) { int i; - ASN1_DATA data; + ASN1_DATA *data; DATA_BLOB ret; - memset(&data, 0, sizeof(data)); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return data_blob_null; + } - 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)); + 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)); - asn1_push_tag(&data, ASN1_CONTEXT(0)); - asn1_push_tag(&data, ASN1_SEQUENCE(0)); + asn1_push_tag(data, ASN1_CONTEXT(0)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); for (i=0; OIDs[i]; i++) { - asn1_write_OID(&data,OIDs[i]); + asn1_write_OID(data,OIDs[i]); } - asn1_pop_tag(&data); - asn1_pop_tag(&data); + asn1_pop_tag(data); + asn1_pop_tag(data); - asn1_push_tag(&data, ASN1_CONTEXT(2)); - asn1_write_OctetString(&data,blob.data,blob.length); - asn1_pop_tag(&data); + asn1_push_tag(data, ASN1_CONTEXT(2)); + asn1_write_OctetString(data,blob.data,blob.length); + asn1_pop_tag(data); - asn1_pop_tag(&data); - asn1_pop_tag(&data); + asn1_pop_tag(data); + asn1_pop_tag(data); - asn1_pop_tag(&data); + asn1_pop_tag(data); - if (data.has_error) { - DEBUG(1,("Failed to build negTokenTarg at offset %d\n", (int)data.ofs)); - asn1_free(&data); + if (data->has_error) { + DEBUG(1,("Failed to build negTokenTarg at offset %d\n", (int)data->ofs)); } - ret = data_blob(data.data, data.length); - asn1_free(&data); + ret = data_blob(data->data, data->length); + asn1_free(data); return ret; } - /* parse a negTokenTarg packet giving a list of OIDs and a security blob */ -BOOL parse_negTokenTarg(DATA_BLOB blob, char *OIDs[ASN1_MAX_OIDS], DATA_BLOB *secblob) +bool parse_negTokenTarg(DATA_BLOB blob, char *OIDs[ASN1_MAX_OIDS], DATA_BLOB *secblob) { int i; - ASN1_DATA data; + ASN1_DATA *data; - asn1_load(&data, blob); - 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)); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return false; + } - 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; + asn1_load(data, blob); + 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-1; i++) { + const char *oid_str = NULL; + asn1_read_OID(data,talloc_autofree_context(),&oid_str); + OIDs[i] = CONST_DISCARD(char *, oid_str); } OIDs[i] = NULL; - asn1_end_tag(&data); - asn1_end_tag(&data); + asn1_end_tag(data); + asn1_end_tag(data); + + /* Skip any optional req_flags that are sent per RFC 4178 */ + if (asn1_peek_tag(data, ASN1_CONTEXT(1))) { + uint8 flags; + + asn1_start_tag(data, ASN1_CONTEXT(1)); + asn1_start_tag(data, ASN1_BIT_STRING); + while (asn1_tag_remaining(data) > 0) + asn1_read_uint8(data, &flags); + asn1_end_tag(data); + asn1_end_tag(data); + } - asn1_start_tag(&data, ASN1_CONTEXT(2)); - asn1_read_OctetString(&data,secblob); - asn1_end_tag(&data); + asn1_start_tag(data, ASN1_CONTEXT(2)); + asn1_read_OctetString(data,talloc_autofree_context(),secblob); + asn1_end_tag(data); - asn1_end_tag(&data); - asn1_end_tag(&data); + asn1_end_tag(data); + asn1_end_tag(data); - asn1_end_tag(&data); + asn1_end_tag(data); - if (data.has_error) { - DEBUG(1,("Failed to parse negTokenTarg at offset %d\n", (int)data.ofs)); - asn1_free(&data); + if (data->has_error) { + int j; + data_blob_free(secblob); + for(j = 0; j < i && j < ASN1_MAX_OIDS-1; j++) { + TALLOC_FREE(OIDs[j]); + } + DEBUG(1,("Failed to parse negTokenTarg at offset %d\n", (int)data->ofs)); + asn1_free(data); return False; } - asn1_free(&data); + asn1_free(data); return True; } /* generate a krb5 GSS-API wrapper packet given a ticket */ -static DATA_BLOB spnego_gen_krb5_wrap(DATA_BLOB ticket) +DATA_BLOB spnego_gen_krb5_wrap(const DATA_BLOB ticket, const uint8 tok_id[2]) { - ASN1_DATA data; + ASN1_DATA *data; DATA_BLOB ret; - memset(&data, 0, sizeof(data)); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return data_blob_null; + } + + asn1_push_tag(data, ASN1_APPLICATION(0)); + asn1_write_OID(data, OID_KERBEROS5); + + asn1_write(data, tok_id, 2); + asn1_write(data, ticket.data, ticket.length); + asn1_pop_tag(data); + + if (data->has_error) { + DEBUG(1,("Failed to build krb5 wrapper at offset %d\n", (int)data->ofs)); + } + + ret = data_blob(data->data, data->length); + asn1_free(data); + + return ret; +} + +/* + parse a krb5 GSS-API wrapper packet giving a ticket +*/ +bool spnego_parse_krb5_wrap(DATA_BLOB blob, DATA_BLOB *ticket, uint8 tok_id[2]) +{ + bool ret; + ASN1_DATA *data; + int data_remaining; + + data = asn1_init(talloc_tos()); + if (data == NULL) { + return false; + } + + asn1_load(data, blob); + asn1_start_tag(data, ASN1_APPLICATION(0)); + asn1_check_OID(data, OID_KERBEROS5); + + data_remaining = asn1_tag_remaining(data); - asn1_push_tag(&data, ASN1_APPLICATION(0)); - asn1_write_OID(&data, OID_KERBEROS5); - asn1_write_BOOLEAN(&data, 0); - asn1_write(&data, ticket.data, ticket.length); - asn1_pop_tag(&data); + if (data_remaining < 3) { + data->has_error = True; + } else { + asn1_read(data, tok_id, 2); + data_remaining -= 2; + *ticket = data_blob(NULL, data_remaining); + asn1_read(data, ticket->data, ticket->length); + } + + asn1_end_tag(data); - if (data.has_error) { - DEBUG(1,("Failed to build krb5 wrapper at offset %d\n", (int)data.ofs)); - asn1_free(&data); + ret = !data->has_error; + + if (data->has_error) { + data_blob_free(ticket); } - ret = data_blob(data.data, data.length); - asn1_free(&data); + asn1_free(data); return ret; } @@ -246,360 +426,267 @@ static DATA_BLOB 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, char *principle) +int spnego_gen_negTokenTarg(const char *principal, int time_offset, + DATA_BLOB *targ, + DATA_BLOB *session_key_krb5, uint32 extra_ap_opts, + time_t *expire_time) { - char *p; - fstring service; - char *realm; - DATA_BLOB tkt, tkt_wrapped, targ; - const char *krb_mechs[] = {OID_KERBEROS5_OLD, OID_NTLMSSP, NULL}; + int retval; + DATA_BLOB tkt, tkt_wrapped; + const char *krb_mechs[] = {OID_KERBEROS5_OLD, OID_KERBEROS5, OID_NTLMSSP, NULL}; - 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 and extract the session key */ + retval = cli_krb5_get_ticket(principal, time_offset, + &tkt, session_key_krb5, extra_ap_opts, NULL, + expire_time, NULL); - /* get a kerberos ticket for the service */ - tkt = krb5_get_ticket(service, realm); + if (retval) + return retval; /* wrap that up in a nice GSS-API wrapping */ - tkt_wrapped = spnego_gen_krb5_wrap(tkt); + tkt_wrapped = spnego_gen_krb5_wrap(tkt, TOK_ID_KRB_AP_REQ); /* and wrap that in a shiny SPNEGO wrapper */ - targ = gen_negTokenTarg(krb_mechs, tkt_wrapped); + *targ = gen_negTokenTarg(krb_mechs, tkt_wrapped); data_blob_free(&tkt_wrapped); data_blob_free(&tkt); - return targ; + return retval; } /* parse a spnego NTLMSSP challenge packet giving two security blobs */ -BOOL spnego_parse_challenge(DATA_BLOB blob, +bool spnego_parse_challenge(const DATA_BLOB blob, DATA_BLOB *chal1, DATA_BLOB *chal2) { - BOOL ret; - ASN1_DATA data; + bool ret; + ASN1_DATA *data; ZERO_STRUCTP(chal1); ZERO_STRUCTP(chal2); - asn1_load(&data, blob); - asn1_start_tag(&data,ASN1_CONTEXT(1)); - asn1_start_tag(&data,ASN1_SEQUENCE(0)); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return false; + } + + asn1_load(data, blob); + asn1_start_tag(data,ASN1_CONTEXT(1)); + asn1_start_tag(data,ASN1_SEQUENCE(0)); - asn1_start_tag(&data,ASN1_CONTEXT(0)); - asn1_check_enumerated(&data,1); - asn1_end_tag(&data); + asn1_start_tag(data,ASN1_CONTEXT(0)); + asn1_check_enumerated(data,1); + asn1_end_tag(data); - asn1_start_tag(&data,ASN1_CONTEXT(1)); - asn1_check_OID(&data, OID_NTLMSSP); - asn1_end_tag(&data); + asn1_start_tag(data,ASN1_CONTEXT(1)); + asn1_check_OID(data, OID_NTLMSSP); + asn1_end_tag(data); - asn1_start_tag(&data,ASN1_CONTEXT(2)); - asn1_read_OctetString(&data, chal1); - asn1_end_tag(&data); + asn1_start_tag(data,ASN1_CONTEXT(2)); + asn1_read_OctetString(data, talloc_autofree_context(), chal1); + asn1_end_tag(data); /* the second challenge is optional (XP doesn't send it) */ - if (asn1_tag_remaining(&data)) { - asn1_start_tag(&data,ASN1_CONTEXT(3)); - asn1_read_OctetString(&data, chal2); - asn1_end_tag(&data); + if (asn1_tag_remaining(data)) { + asn1_start_tag(data,ASN1_CONTEXT(3)); + asn1_read_OctetString(data, talloc_autofree_context(), chal2); + asn1_end_tag(data); } - asn1_end_tag(&data); - asn1_end_tag(&data); + asn1_end_tag(data); + asn1_end_tag(data); + + ret = !data->has_error; + + if (data->has_error) { + data_blob_free(chal1); + data_blob_free(chal2); + } - ret = !data.has_error; - asn1_free(&data); + asn1_free(data); return ret; } /* - generate a spnego NTLMSSP challenge packet given two security blobs - The second challenge is optional + generate a SPNEGO auth packet. This will contain the encrypted passwords */ -BOOL spnego_gen_challenge(DATA_BLOB *blob, - DATA_BLOB *chal1, DATA_BLOB *chal2) +DATA_BLOB spnego_gen_auth(DATA_BLOB blob) { - ASN1_DATA data; + ASN1_DATA *data; + DATA_BLOB ret; - ZERO_STRUCT(data); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return data_blob_null; + } - asn1_push_tag(&data,ASN1_CONTEXT(1)); - asn1_push_tag(&data,ASN1_SEQUENCE(0)); + asn1_push_tag(data, ASN1_CONTEXT(1)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_push_tag(data, ASN1_CONTEXT(2)); + asn1_write_OctetString(data,blob.data,blob.length); + asn1_pop_tag(data); + asn1_pop_tag(data); + asn1_pop_tag(data); - asn1_push_tag(&data,ASN1_CONTEXT(0)); - asn1_write_enumerated(&data,1); - asn1_pop_tag(&data); + ret = data_blob(data->data, data->length); - asn1_push_tag(&data,ASN1_CONTEXT(1)); - asn1_write_OID(&data, OID_NTLMSSP); - asn1_pop_tag(&data); + asn1_free(data); - asn1_push_tag(&data,ASN1_CONTEXT(2)); - asn1_write_OctetString(&data, chal1->data, chal1->length); - asn1_pop_tag(&data); + return ret; +} - /* the second challenge is optional (XP doesn't send it) */ - if (chal2) { - asn1_push_tag(&data,ASN1_CONTEXT(3)); - asn1_write_OctetString(&data, chal2->data, chal2->length); - asn1_pop_tag(&data); - } +/* + parse a SPNEGO auth packet. This contains the encrypted passwords +*/ +bool spnego_parse_auth(DATA_BLOB blob, DATA_BLOB *auth) +{ + ssize_t len; + struct spnego_data token; - asn1_pop_tag(&data); - asn1_pop_tag(&data); + len = spnego_read_data(talloc_tos(), blob, &token); + if (len == -1) { + DEBUG(3,("spnego_parse_auth: spnego_read_data failed\n")); + return false; + } - if (data.has_error) { - return False; + if (token.type != SPNEGO_NEG_TOKEN_TARG) { + DEBUG(3,("spnego_parse_auth: wrong token type: %d\n", + token.type)); + spnego_free_data(&token); + return false; } - *blob = data_blob(data.data, data.length); - asn1_free(&data); - return True; + *auth = data_blob_talloc(talloc_tos(), + token.negTokenTarg.responseToken.data, + token.negTokenTarg.responseToken.length); + spnego_free_data(&token); + + return true; } /* - generate a SPNEGO NTLMSSP auth packet. This will contain the encrypted passwords + generate a minimal SPNEGO response packet. Doesn't contain much. */ -DATA_BLOB spnego_gen_auth(DATA_BLOB blob) +DATA_BLOB spnego_gen_auth_response(DATA_BLOB *reply, NTSTATUS nt_status, + const char *mechOID) { - ASN1_DATA data; + ASN1_DATA *data; DATA_BLOB ret; + uint8 negResult; + + if (NT_STATUS_IS_OK(nt_status)) { + negResult = SPNEGO_ACCEPT_COMPLETED; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + negResult = SPNEGO_ACCEPT_INCOMPLETE; + } else { + negResult = SPNEGO_REJECT; + } - memset(&data, 0, sizeof(data)); + data = asn1_init(talloc_tos()); + if (data == NULL) { + return data_blob_null; + } - asn1_push_tag(&data, ASN1_CONTEXT(1)); - asn1_push_tag(&data, ASN1_SEQUENCE(0)); - asn1_push_tag(&data, ASN1_CONTEXT(2)); - asn1_write_OctetString(&data,blob.data,blob.length); - asn1_pop_tag(&data); - asn1_pop_tag(&data); - asn1_pop_tag(&data); + asn1_push_tag(data, ASN1_CONTEXT(1)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_push_tag(data, ASN1_CONTEXT(0)); + asn1_write_enumerated(data, negResult); + asn1_pop_tag(data); - ret = data_blob(data.data, data.length); + if (mechOID) { + asn1_push_tag(data,ASN1_CONTEXT(1)); + asn1_write_OID(data, mechOID); + asn1_pop_tag(data); + } - asn1_free(&data); + if (reply && reply->data != NULL) { + asn1_push_tag(data,ASN1_CONTEXT(2)); + asn1_write_OctetString(data, reply->data, reply->length); + asn1_pop_tag(data); + } + asn1_pop_tag(data); + asn1_pop_tag(data); + + ret = data_blob(data->data, data->length); + asn1_free(data); return ret; } /* - parse a SPNEGO NTLMSSP auth packet. This contains the encrypted passwords + parse a SPNEGO auth packet. This contains the encrypted passwords */ -BOOL spnego_parse_auth(DATA_BLOB blob, DATA_BLOB *auth) +bool spnego_parse_auth_response(DATA_BLOB blob, NTSTATUS nt_status, + const char *mechOID, + DATA_BLOB *auth) { - ASN1_DATA data; - - asn1_load(&data, blob); - asn1_start_tag(&data, ASN1_CONTEXT(1)); - asn1_start_tag(&data, ASN1_SEQUENCE(0)); - asn1_start_tag(&data, ASN1_CONTEXT(2)); - asn1_read_OctetString(&data,auth); - asn1_end_tag(&data); - asn1_end_tag(&data); - asn1_end_tag(&data); - - if (data.has_error) { - DEBUG(3,("spnego_parse_auth failed at %d\n", (int)data.ofs)); - asn1_free(&data); - return False; + ASN1_DATA *data; + uint8 negResult; + + if (NT_STATUS_IS_OK(nt_status)) { + negResult = SPNEGO_ACCEPT_COMPLETED; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + negResult = SPNEGO_ACCEPT_INCOMPLETE; + } else { + negResult = SPNEGO_REJECT; } - asn1_free(&data); - return True; -} + data = asn1_init(talloc_tos()); + if (data == NULL) { + return false; + } + asn1_load(data, blob); + asn1_start_tag(data, ASN1_CONTEXT(1)); + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_start_tag(data, ASN1_CONTEXT(0)); + asn1_check_enumerated(data, negResult); + asn1_end_tag(data); -/* - this is a tiny msrpc packet generator. I am only using this to - avoid tying this code to a particular varient of our rpc code. This - generator is not general enough for all our rpc needs, its just - enough for the spnego/ntlmssp code - - format specifiers are: - - U = unicode string (input is unix string) - B = data blob (pointer + length) - b = data blob in header (pointer + length) - d = word (4 bytes) - C = constant ascii string - */ -BOOL msrpc_gen(DATA_BLOB *blob, - const char *format, ...) -{ - int i, n; - va_list ap; - char *s; - uint8 *b; - int head_size=0, data_size=0; - int head_ofs, data_ofs; - - /* first scan the format to work out the header and body size */ - va_start(ap, format); - for (i=0; format[i]; i++) { - switch (format[i]) { - case 'U': - s = va_arg(ap, char *); - head_size += 8; - data_size += str_charnum(s) * 2; - break; - case 'B': - b = va_arg(ap, uint8 *); - head_size += 8; - data_size += va_arg(ap, int); - break; - case 'b': - b = va_arg(ap, uint8 *); - head_size += va_arg(ap, int); - break; - case 'd': - n = va_arg(ap, int); - head_size += 4; - break; - case 'C': - s = va_arg(ap, char *); - head_size += str_charnum(s) + 1; - break; - } - } - va_end(ap); - - /* allocate the space, then scan the format again to fill in the values */ - blob->data = malloc(head_size + data_size); - blob->length = head_size + data_size; - if (!blob->data) return False; - - head_ofs = 0; - data_ofs = head_size; - - va_start(ap, format); - for (i=0; format[i]; i++) { - switch (format[i]) { - case 'U': - s = va_arg(ap, char *); - n = str_charnum(s); - SSVAL(blob->data, head_ofs, n*2); head_ofs += 2; - SSVAL(blob->data, head_ofs, n*2); head_ofs += 2; - SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4; - push_string(NULL, blob->data+data_ofs, s, n*2, STR_UNICODE|STR_NOALIGN); - data_ofs += n*2; - break; - case 'B': - b = va_arg(ap, uint8 *); - n = va_arg(ap, int); - SSVAL(blob->data, head_ofs, n); head_ofs += 2; - SSVAL(blob->data, head_ofs, n); head_ofs += 2; - SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4; - memcpy(blob->data+data_ofs, b, n); - data_ofs += n; - break; - case 'd': - n = va_arg(ap, int); - SIVAL(blob->data, head_ofs, n); head_ofs += 4; - break; - case 'b': - b = va_arg(ap, uint8 *); - n = va_arg(ap, int); - memcpy(blob->data + head_ofs, b, n); - head_ofs += n; - break; - case 'C': - s = va_arg(ap, char *); - head_ofs += push_string(NULL, blob->data+head_ofs, s, -1, - STR_ASCII|STR_TERMINATE); - break; + *auth = data_blob_null; + + if (asn1_tag_remaining(data)) { + asn1_start_tag(data,ASN1_CONTEXT(1)); + asn1_check_OID(data, mechOID); + asn1_end_tag(data); + + if (asn1_tag_remaining(data)) { + asn1_start_tag(data,ASN1_CONTEXT(2)); + asn1_read_OctetString(data, talloc_autofree_context(), auth); + asn1_end_tag(data); } + } else if (negResult == SPNEGO_ACCEPT_INCOMPLETE) { + data->has_error = 1; } - va_end(ap); - return True; -} + /* Binding against Win2K DC returns a duplicate of the responseToken in + * the optional mechListMIC field. This is a bug in Win2K. We ignore + * this field if it exists. Win2K8 may return a proper mechListMIC at + * which point we need to implement the integrity checking. */ + if (asn1_tag_remaining(data)) { + DATA_BLOB mechList = data_blob_null; + asn1_start_tag(data, ASN1_CONTEXT(3)); + asn1_read_OctetString(data, talloc_autofree_context(), &mechList); + asn1_end_tag(data); + data_blob_free(&mechList); + DEBUG(5,("spnego_parse_auth_response received mechListMIC, " + "ignoring.\n")); + } + asn1_end_tag(data); + asn1_end_tag(data); -/* - this is a tiny msrpc packet parser. This the the partner of msrpc_gen - - format specifiers are: - - U = unicode string (input is unix string) - B = data blob - b = data blob in header - d = word (4 bytes) - C = constant ascii string - */ -BOOL msrpc_parse(DATA_BLOB *blob, - const char *format, ...) -{ - int i; - va_list ap; - char **ps, *s; - DATA_BLOB *b; - int head_ofs = 0; - uint16 len1, len2; - uint32 ptr; - uint32 *v; - pstring p; - - va_start(ap, format); - for (i=0; format[i]; i++) { - switch (format[i]) { - case 'U': - len1 = SVAL(blob->data, head_ofs); head_ofs += 2; - len2 = SVAL(blob->data, head_ofs); head_ofs += 2; - ptr = IVAL(blob->data, head_ofs); head_ofs += 4; - /* make sure its in the right format - be strict */ - if (len1 != len2 || (len1&1) || ptr + len1 > blob->length) { - return False; - } - ps = va_arg(ap, char **); - pull_string(NULL, p, blob->data + ptr, -1, len1, - STR_UNICODE|STR_NOALIGN); - (*ps) = strdup(p); - break; - case 'B': - len1 = SVAL(blob->data, head_ofs); head_ofs += 2; - len2 = SVAL(blob->data, head_ofs); head_ofs += 2; - ptr = IVAL(blob->data, head_ofs); head_ofs += 4; - /* make sure its in the right format - be strict */ - if (len1 != len2 || ptr + len1 > blob->length) { - return False; - } - b = (DATA_BLOB *)va_arg(ap, void *); - *b = data_blob(blob->data + ptr, len1); - break; - case 'b': - b = (DATA_BLOB *)va_arg(ap, void *); - len1 = va_arg(ap, unsigned); - *b = data_blob(blob->data + head_ofs, len1); - head_ofs += len1; - break; - case 'd': - v = va_arg(ap, uint32 *); - *v = IVAL(blob->data, head_ofs); head_ofs += 4; - break; - case 'C': - s = va_arg(ap, char *); - head_ofs += pull_string(NULL, p, blob->data+head_ofs, -1, - blob->length - head_ofs, - STR_ASCII|STR_TERMINATE); - if (strcmp(s, p) != 0) { - return False; - } - break; - } + if (data->has_error) { + DEBUG(3,("spnego_parse_auth_response failed at %d\n", (int)data->ofs)); + asn1_free(data); + data_blob_free(auth); + return False; } - va_end(ap); + asn1_free(data); return True; }