diff options
Diffstat (limited to 'source4/libcli/auth/spnego.c')
-rw-r--r-- | source4/libcli/auth/spnego.c | 522 |
1 files changed, 250 insertions, 272 deletions
diff --git a/source4/libcli/auth/spnego.c b/source4/libcli/auth/spnego.c index ddc98f883b..ae7a3b4042 100644 --- a/source4/libcli/auth/spnego.c +++ b/source4/libcli/auth/spnego.c @@ -2,8 +2,9 @@ Unix SMB/CIFS implementation. RFC2478 Compliant SPNEGO implementation - - Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 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 @@ -26,318 +27,295 @@ #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH -static BOOL read_negTokenInit(ASN1_DATA *asn1, struct spnego_negTokenInit *token) +NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security) { - ZERO_STRUCTP(token); - - asn1_start_tag(asn1, ASN1_CONTEXT(0)); - asn1_start_tag(asn1, ASN1_SEQUENCE(0)); - - while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) { - int i; - - switch (asn1->data[asn1->ofs]) { - /* Read mechTypes */ - case ASN1_CONTEXT(0): - asn1_start_tag(asn1, ASN1_CONTEXT(0)); - asn1_start_tag(asn1, ASN1_SEQUENCE(0)); - - token->mechTypes = malloc(sizeof(*token->mechTypes)); - for (i = 0; !asn1->has_error && - 0 < asn1_tag_remaining(asn1); i++) { - token->mechTypes = - realloc(token->mechTypes, (i + 2) * - sizeof(*token->mechTypes)); - asn1_read_OID(asn1, token->mechTypes + i); - } - token->mechTypes[i] = NULL; - - asn1_end_tag(asn1); - asn1_end_tag(asn1); - break; - /* Read reqFlags */ - case ASN1_CONTEXT(1): - asn1_start_tag(asn1, ASN1_CONTEXT(1)); - asn1_read_Integer(asn1, &token->reqFlags); - token->reqFlags |= SPNEGO_REQ_FLAG; - asn1_end_tag(asn1); - break; - /* Read mechToken */ - case ASN1_CONTEXT(2): - asn1_start_tag(asn1, ASN1_CONTEXT(2)); - asn1_read_OctetString(asn1, &token->mechToken); - asn1_end_tag(asn1); - break; - /* Read mecListMIC */ - case ASN1_CONTEXT(3): - asn1_start_tag(asn1, ASN1_CONTEXT(3)); - if (asn1->data[asn1->ofs] == ASN1_OCTET_STRING) { - asn1_read_OctetString(asn1, - &token->mechListMIC); - } else { - /* RFC 2478 says we have an Octet String here, - but W2k sends something different... */ - char *mechListMIC; - asn1_push_tag(asn1, ASN1_SEQUENCE(0)); - asn1_push_tag(asn1, ASN1_CONTEXT(0)); - asn1_read_GeneralString(asn1, &mechListMIC); - asn1_pop_tag(asn1); - asn1_pop_tag(asn1); - - token->mechListMIC = - data_blob(mechListMIC, strlen(mechListMIC)); - SAFE_FREE(mechListMIC); - } - asn1_end_tag(asn1); - break; - default: - asn1->has_error = True; - break; - } + struct spnego_state *spnego_state; + TALLOC_CTX *mem_ctx = talloc_init("gensec_spengo_client_start"); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + spnego_state = talloc_p(mem_ctx, struct spnego_state); + + if (!spnego_state) { + return NT_STATUS_NO_MEMORY; } - asn1_end_tag(asn1); - asn1_end_tag(asn1); + spnego_state->role = SPNEGO_CLIENT; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + spnego_state->state_position = SPNEGO_CLIENT_GET_MECHS; + spnego_state->result = SPNEGO_ACCEPT_INCOMPLETE; + spnego_state->mem_ctx = mem_ctx; - return !asn1->has_error; + gensec_security->private_data = spnego_state; + return NT_STATUS_OK; } -static BOOL write_negTokenInit(ASN1_DATA *asn1, struct spnego_negTokenInit *token) +/* + wrappers for the spnego_*() functions +*/ +NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, DATA_BLOB *sig) { - asn1_push_tag(asn1, ASN1_CONTEXT(0)); - asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + struct spnego_state *spnego_state = gensec_security->private_data; - /* Write mechTypes */ - if (token->mechTypes && *token->mechTypes) { - int i; - - asn1_push_tag(asn1, ASN1_CONTEXT(0)); - asn1_push_tag(asn1, ASN1_SEQUENCE(0)); - for (i = 0; token->mechTypes[i]; i++) { - asn1_write_OID(asn1, token->mechTypes[i]); - } - asn1_pop_tag(asn1); - asn1_pop_tag(asn1); + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return NT_STATUS_INVALID_PARAMETER; } + + return spnego_state->sub_sec_security.ops->unseal(&spnego_state->sub_sec_security, + mem_ctx, data, length, sig); +} - /* write reqFlags */ - if (token->reqFlags & SPNEGO_REQ_FLAG) { - int flags = token->reqFlags & ~SPNEGO_REQ_FLAG; +NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const DATA_BLOB *sig) +{ + struct spnego_state *spnego_state = gensec_security->private_data; - asn1_push_tag(asn1, ASN1_CONTEXT(1)); - asn1_write_Integer(asn1, flags); - asn1_pop_tag(asn1); + return NT_STATUS_NOT_IMPLEMENTED; + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return NT_STATUS_INVALID_PARAMETER; } + + return spnego_state->sub_sec_security.ops->check_sig(&spnego_state->sub_sec_security, + mem_ctx, data, length, sig); +} - /* write mechToken */ - if (token->mechToken.data) { - asn1_push_tag(asn1, ASN1_CONTEXT(2)); - asn1_write_OctetString(asn1, token->mechToken.data, - token->mechToken.length); - asn1_pop_tag(asn1); - } +NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + DATA_BLOB *sig) +{ + struct spnego_state *spnego_state = gensec_security->private_data; - /* write mechListMIC */ - if (token->mechListMIC.data) { - asn1_push_tag(asn1, ASN1_CONTEXT(3)); -#if 0 - /* This is what RFC 2478 says ... */ - asn1_write_OctetString(asn1, token->mechListMIC.data, - token->mechListMIC.length); -#else - /* ... but unfortunately this is what Windows - sends/expects */ - asn1_push_tag(asn1, ASN1_SEQUENCE(0)); - asn1_push_tag(asn1, ASN1_CONTEXT(0)); - asn1_push_tag(asn1, ASN1_GENERAL_STRING); - asn1_write(asn1, token->mechListMIC.data, - token->mechListMIC.length); - asn1_pop_tag(asn1); - asn1_pop_tag(asn1); - asn1_pop_tag(asn1); -#endif - asn1_pop_tag(asn1); + return NT_STATUS_NOT_IMPLEMENTED; + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return NT_STATUS_INVALID_PARAMETER; } + + return spnego_state->sub_sec_security.ops->seal(&spnego_state->sub_sec_security, + mem_ctx, data, length, sig); +} - asn1_pop_tag(asn1); - asn1_pop_tag(asn1); +NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + DATA_BLOB *sig) +{ + struct spnego_state *spnego_state = gensec_security->private_data; - return !asn1->has_error; + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return NT_STATUS_INVALID_PARAMETER; + } + + return spnego_state->sub_sec_security.ops->sign(&spnego_state->sub_sec_security, + mem_ctx, data, length, sig); } -static BOOL read_negTokenTarg(ASN1_DATA *asn1, struct spnego_negTokenTarg *token) +NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) { - ZERO_STRUCTP(token); - - asn1_start_tag(asn1, ASN1_CONTEXT(1)); - asn1_start_tag(asn1, ASN1_SEQUENCE(0)); - - while (!asn1->has_error && 0 < asn1_tag_remaining(asn1)) { - switch (asn1->data[asn1->ofs]) { - case ASN1_CONTEXT(0): - asn1_start_tag(asn1, ASN1_CONTEXT(0)); - asn1_start_tag(asn1, ASN1_ENUMERATED); - asn1_read_uint8(asn1, &token->negResult); - asn1_end_tag(asn1); - asn1_end_tag(asn1); - break; - case ASN1_CONTEXT(1): - asn1_start_tag(asn1, ASN1_CONTEXT(1)); - asn1_read_OID(asn1, &token->supportedMech); - asn1_end_tag(asn1); - break; - case ASN1_CONTEXT(2): - asn1_start_tag(asn1, ASN1_CONTEXT(2)); - asn1_read_OctetString(asn1, &token->responseToken); - asn1_end_tag(asn1); - break; - case ASN1_CONTEXT(3): - asn1_start_tag(asn1, ASN1_CONTEXT(3)); - asn1_read_OctetString(asn1, &token->mechListMIC); - asn1_end_tag(asn1); - break; - default: - asn1->has_error = True; - break; - } + struct spnego_state *spnego_state = gensec_security->private_data; + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return NT_STATUS_INVALID_PARAMETER; } - - asn1_end_tag(asn1); - asn1_end_tag(asn1); - - return !asn1->has_error; + + return spnego_state->sub_sec_security.ops->session_key(&spnego_state->sub_sec_security, + session_key); } -static BOOL write_negTokenTarg(ASN1_DATA *asn1, struct spnego_negTokenTarg *token) +NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) { - asn1_push_tag(asn1, ASN1_CONTEXT(1)); - asn1_push_tag(asn1, ASN1_SEQUENCE(0)); - - asn1_push_tag(asn1, ASN1_CONTEXT(0)); - asn1_write_enumerated(asn1, token->negResult); - asn1_pop_tag(asn1); + struct spnego_state *spnego_state = gensec_security->private_data; + DATA_BLOB null_data_blob = data_blob(NULL, 0); + DATA_BLOB unwrapped_out; + struct spnego_data spnego_out; + struct spnego_data spnego; + const struct gensec_security_ops *op; - if (token->supportedMech) { - asn1_push_tag(asn1, ASN1_CONTEXT(1)); - asn1_write_OID(asn1, token->supportedMech); - asn1_pop_tag(asn1); - } + ssize_t len; - if (token->responseToken.data) { - asn1_push_tag(asn1, ASN1_CONTEXT(2)); - asn1_write_OctetString(asn1, token->responseToken.data, - token->responseToken.length); - asn1_pop_tag(asn1); + if (!out_mem_ctx) { + out_mem_ctx = spnego_state->mem_ctx; } - if (token->mechListMIC.data) { - asn1_push_tag(asn1, ASN1_CONTEXT(3)); - asn1_write_OctetString(asn1, token->mechListMIC.data, - token->mechListMIC.length); - asn1_pop_tag(asn1); + if (spnego_state->state_position == SPNEGO_FALLBACK) { + return spnego_state->sub_sec_security.ops->update(&spnego_state->sub_sec_security, + out_mem_ctx, in, out); } - asn1_pop_tag(asn1); - asn1_pop_tag(asn1); - - return !asn1->has_error; -} + len = read_spnego_data(in, &spnego); -ssize_t read_spnego_data(DATA_BLOB data, struct spnego_data *token) -{ - ASN1_DATA asn1; - ssize_t ret = -1; - - ZERO_STRUCTP(token); - ZERO_STRUCT(asn1); - asn1_load(&asn1, data); - - switch (asn1.data[asn1.ofs]) { - case ASN1_APPLICATION(0): - asn1_start_tag(&asn1, ASN1_APPLICATION(0)); - asn1_check_OID(&asn1, OID_SPNEGO); - if (read_negTokenInit(&asn1, &token->negTokenInit)) { - token->type = SPNEGO_NEG_TOKEN_INIT; + if (len == -1 && spnego_state->state_position == SPNEGO_SERVER_START) { + int i; + const struct gensec_security_ops **all_ops = gensec_security_all(); + for (i=0; all_ops[i]; i++) { + NTSTATUS nt_status; + op = all_ops[i]; + if (!op->oid) { + continue; + } + nt_status = op->server_start(&spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + continue; + } + nt_status = op->update(&spnego_state->sub_sec_security, + out_mem_ctx, in, out); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + spnego_state->state_position = SPNEGO_FALLBACK; + return nt_status; + } + op->end(&spnego_state->sub_sec_security); } - asn1_end_tag(&asn1); - break; - case ASN1_CONTEXT(1): - if (read_negTokenTarg(&asn1, &token->negTokenTarg)) { - token->type = SPNEGO_NEG_TOKEN_TARG; + DEBUG(1, ("Failed to parse SPENGO request\n")); + return NT_STATUS_INVALID_PARAMETER; + } else { + + if (spnego.type != spnego_state->expected_packet) { + free_spnego_data(&spnego); + DEBUG(1, ("Invalid SPENGO request: %d, expected %d\n", spnego.type, + spnego_state->expected_packet)); + return NT_STATUS_INVALID_PARAMETER; } - break; - default: - break; - } - if (!asn1.has_error) ret = asn1.ofs; - asn1_free(&asn1); - - return ret; -} + if (spnego_state->state_position == SPNEGO_CLIENT_GET_MECHS) { -ssize_t write_spnego_data(DATA_BLOB *blob, struct spnego_data *spnego) -{ - ASN1_DATA asn1; - ssize_t ret = -1; - - ZERO_STRUCT(asn1); - - switch (spnego->type) { - case SPNEGO_NEG_TOKEN_INIT: - asn1_push_tag(&asn1, ASN1_APPLICATION(0)); - asn1_write_OID(&asn1, OID_SPNEGO); - write_negTokenInit(&asn1, &spnego->negTokenInit); - asn1_pop_tag(&asn1); - break; - case SPNEGO_NEG_TOKEN_TARG: - write_negTokenTarg(&asn1, &spnego->negTokenTarg); - break; - default: - asn1.has_error = True; - break; - } + /* The server offers a list of mechanisms */ + + char **mechType = spnego.negTokenInit.mechTypes; + char *my_mechs[] = {NULL, NULL}; + int i; + NTSTATUS nt_status; + + for (i=0; mechType[i]; i++) { + op = gensec_security_by_oid(mechType[i]); + if (!op) { + continue; + } + spnego_state->sub_sec_security.ops = op; + spnego_state->sub_sec_security.user = gensec_security->user; + + nt_status = op->client_start(&spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + op->end(&spnego_state->sub_sec_security); + continue; + } + if (i == 0) { + nt_status = op->update(&spnego_state->sub_sec_security, + out_mem_ctx, + spnego.negTokenInit.mechToken, + &unwrapped_out); + } else { + /* only get the helping start blob for the first OID */ + nt_status = op->update(&spnego_state->sub_sec_security, + out_mem_ctx, + null_data_blob, + &unwrapped_out); + } + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + DEBUG(1, ("SPENGO(%s) NEG_TOKEN_INIT failed: %s\n", op->name, nt_errstr(nt_status))); + op->end(&spnego_state->sub_sec_security); + } else { + break; + } + } + if (!mechType[i]) { + DEBUG(1, ("SPENGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); + } - if (!asn1.has_error) { - *blob = data_blob(asn1.data, asn1.length); - ret = asn1.ofs; - } - asn1_free(&asn1); + free_spnego_data(&spnego); + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + return nt_status; + } + + /* compose reply */ + my_mechs[0] = op->oid; + + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + spnego_out.negTokenInit.mechTypes = my_mechs; + spnego_out.negTokenInit.reqFlags = 0; + spnego_out.negTokenInit.mechListMIC = null_data_blob; + spnego_out.negTokenInit.mechToken = unwrapped_out; + + if (write_spnego_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPENGO reply to NEG_TOKEN_INIT\n")); + return NT_STATUS_INVALID_PARAMETER; + } - return ret; -} + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_TARG; -BOOL free_spnego_data(struct spnego_data *spnego) -{ - BOOL ret = True; + return nt_status; + } else if (spnego_state->state_position == SPNEGO_TARG) { + NTSTATUS nt_status; + if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) { + return NT_STATUS_ACCESS_DENIED; + } - if (!spnego) goto out; + op = spnego_state->sub_sec_security.ops; + if (spnego.negTokenTarg.responseToken.length) { + nt_status = op->update(&spnego_state->sub_sec_security, + out_mem_ctx, + spnego.negTokenTarg.responseToken, + &unwrapped_out); + } else { + unwrapped_out = data_blob(NULL, 0); + nt_status = NT_STATUS_OK; + } + + if (NT_STATUS_IS_OK(nt_status) + && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) { + nt_status = NT_STATUS_INVALID_PARAMETER; + } - switch(spnego->type) { - case SPNEGO_NEG_TOKEN_INIT: - if (spnego->negTokenInit.mechTypes) { - int i; - for (i = 0; spnego->negTokenInit.mechTypes[i]; i++) { - free(spnego->negTokenInit.mechTypes[i]); + spnego_state->result = spnego.negTokenTarg.negResult; + free_spnego_data(&spnego); + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; + spnego_out.negTokenTarg.supportedMech = op->oid; + spnego_out.negTokenTarg.responseToken = unwrapped_out; + spnego_out.negTokenTarg.mechListMIC = null_data_blob; + + if (write_spnego_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPENGO reply to NEG_TOKEN_TARG\n")); + return NT_STATUS_INVALID_PARAMETER; } - free(spnego->negTokenInit.mechTypes); - } - data_blob_free(&spnego->negTokenInit.mechToken); - data_blob_free(&spnego->negTokenInit.mechListMIC); - break; - case SPNEGO_NEG_TOKEN_TARG: - if (spnego->negTokenTarg.supportedMech) { - free(spnego->negTokenTarg.supportedMech); + spnego_state->state_position = SPNEGO_TARG; + } else if (NT_STATUS_IS_OK(nt_status)) { + spnego_state->state_position = SPNEGO_DONE; + } else { + DEBUG(1, ("SPENGO(%s) login failed: %s\n", op->name, nt_errstr(nt_status))); + return nt_status; + } + + return nt_status; + } else { + free_spnego_data(&spnego); + DEBUG(1, ("Invalid SPENGO request: %d\n", spnego.type)); + return NT_STATUS_INVALID_PARAMETER; } - data_blob_free(&spnego->negTokenTarg.responseToken); - data_blob_free(&spnego->negTokenTarg.mechListMIC); - break; - default: - ret = False; - break; } - ZERO_STRUCTP(spnego); -out: - return ret; } +void gensec_spnego_end(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + + spnego_state->sub_sec_security.ops->end(&spnego_state->sub_sec_security); + + talloc_destroy(spnego_state->mem_ctx); + + gensec_security->private_data = NULL; +} |