From baf10853e29fdfa15077229946b4b52930d83cda Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Tue, 31 Aug 2010 15:08:31 -0400 Subject: [PATCH] s3-dcerpc: add spnego server helpers squashed: add michlistMIC signature checks --- source3/Makefile.in | 3 +- source3/include/proto.h | 10 + source3/librpc/crypto/cli_spnego.c | 87 +++++++- source3/librpc/crypto/spnego.h | 23 +++ source3/libsmb/clispnego.c | 79 +++++++- source3/rpc_server/dcesrv_spnego.c | 309 +++++++++++++++++++++++++++++ source3/rpc_server/dcesrv_spnego.h | 37 ++++ 7 files changed, 542 insertions(+), 6 deletions(-) create mode 100644 source3/rpc_server/dcesrv_spnego.c create mode 100644 source3/rpc_server/dcesrv_spnego.h diff --git a/source3/Makefile.in b/source3/Makefile.in index 831c7379a4e..f85f16e7dc1 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -712,7 +712,8 @@ RPC_NCACN_NP = rpc_server/srv_pipe_register.o rpc_server/rpc_ncacn_np.o \ RPC_SERVICE = rpc_server/rpc_server.o RPC_CRYPTO = rpc_server/dcesrv_ntlmssp.o \ - rpc_server/dcesrv_gssapi.o + rpc_server/dcesrv_gssapi.o \ + rpc_server/dcesrv_spnego.o RPC_PIPE_OBJ = rpc_server/srv_pipe.o rpc_server/srv_pipe_hnd.o \ $(RPC_NCACN_NP) $(RPC_SERVICE) $(RPC_CRYPTO) diff --git a/source3/include/proto.h b/source3/include/proto.h index 7796e055b9d..16fef58c045 100644 --- a/source3/include/proto.h +++ b/source3/include/proto.h @@ -2605,6 +2605,16 @@ bool spnego_parse_auth_response(TALLOC_CTX *ctx, const char *mechOID, DATA_BLOB *auth); +bool spnego_parse_auth_and_mic(TALLOC_CTX *ctx, DATA_BLOB blob, + DATA_BLOB *auth, DATA_BLOB *signature); +DATA_BLOB spnego_gen_auth_response_and_mic(TALLOC_CTX *ctx, + NTSTATUS nt_status, + const char *mechOID, + DATA_BLOB *reply, + DATA_BLOB *mechlistMIC); +bool spnego_mech_list_blob(TALLOC_CTX *mem_ctx, + char **oid_list, DATA_BLOB *data); + /* The following definitions come from libsmb/clistr.c */ size_t clistr_push_fn(const char *function, diff --git a/source3/librpc/crypto/cli_spnego.c b/source3/librpc/crypto/cli_spnego.c index 07301c9dc99..bf58e25d9a8 100644 --- a/source3/librpc/crypto/cli_spnego.c +++ b/source3/librpc/crypto/cli_spnego.c @@ -1,6 +1,6 @@ /* * SPNEGO Encapsulation - * RPC Pipe client routines + * Client functions * Copyright (C) Simo Sorce 2010. * * This program is free software; you can redistribute it and/or modify @@ -349,3 +349,88 @@ DATA_BLOB spnego_get_session_key(TALLOC_CTX *mem_ctx, } } +NTSTATUS spnego_sign(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature) +{ + switch(sp_ctx->mech) { + case SPNEGO_KRB5: + return gse_sign(mem_ctx, + sp_ctx->mech_ctx.gssapi_state, + data, signature); + case SPNEGO_NTLMSSP: + return auth_ntlmssp_sign_packet( + sp_ctx->mech_ctx.ntlmssp_state, + mem_ctx, + data->data, data->length, + full_data->data, full_data->length, + signature); + default: + return NT_STATUS_INVALID_PARAMETER; + } +} + +NTSTATUS spnego_sigcheck(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature) +{ + switch(sp_ctx->mech) { + case SPNEGO_KRB5: + return gse_sigcheck(mem_ctx, + sp_ctx->mech_ctx.gssapi_state, + data, signature); + case SPNEGO_NTLMSSP: + return auth_ntlmssp_check_packet( + sp_ctx->mech_ctx.ntlmssp_state, + data->data, data->length, + full_data->data, full_data->length, + signature); + default: + return NT_STATUS_INVALID_PARAMETER; + } +} + +NTSTATUS spnego_seal(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature) +{ + switch(sp_ctx->mech) { + case SPNEGO_KRB5: + return gse_seal(mem_ctx, + sp_ctx->mech_ctx.gssapi_state, + data, signature); + case SPNEGO_NTLMSSP: + return auth_ntlmssp_seal_packet( + sp_ctx->mech_ctx.ntlmssp_state, + mem_ctx, + data->data, data->length, + full_data->data, full_data->length, + signature); + default: + return NT_STATUS_INVALID_PARAMETER; + } +} + +NTSTATUS spnego_unseal(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature) +{ + switch(sp_ctx->mech) { + case SPNEGO_KRB5: + return gse_unseal(mem_ctx, + sp_ctx->mech_ctx.gssapi_state, + data, signature); + case SPNEGO_NTLMSSP: + return auth_ntlmssp_unseal_packet( + sp_ctx->mech_ctx.ntlmssp_state, + data->data, data->length, + full_data->data, full_data->length, + signature); + default: + return NT_STATUS_INVALID_PARAMETER; + } +} diff --git a/source3/librpc/crypto/spnego.h b/source3/librpc/crypto/spnego.h index 9512ed63241..68d9243bb05 100644 --- a/source3/librpc/crypto/spnego.h +++ b/source3/librpc/crypto/spnego.h @@ -34,8 +34,12 @@ struct spnego_context { struct gse_context *gssapi_state; } mech_ctx; + char *oid_list[ASN1_MAX_OIDS]; + char *mech_oid; + enum { SPNEGO_CONV_INIT = 0, + SPNEGO_CONV_NEGO, SPNEGO_CONV_AUTH_MORE, SPNEGO_CONV_AUTH_CONFIRM, SPNEGO_CONV_AUTH_DONE @@ -43,6 +47,7 @@ struct spnego_context { bool do_sign; bool do_seal; + bool is_dcerpc; }; NTSTATUS spnego_gssapi_init_client(TALLOC_CTX *mem_ctx, @@ -75,4 +80,22 @@ NTSTATUS spnego_get_negotiated_mech(struct spnego_context *sp_ctx, DATA_BLOB spnego_get_session_key(TALLOC_CTX *mem_ctx, struct spnego_context *sp_ctx); + +NTSTATUS spnego_sign(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature); +NTSTATUS spnego_sigcheck(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature); +NTSTATUS spnego_seal(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature); +NTSTATUS spnego_unseal(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *data, DATA_BLOB *full_data, + DATA_BLOB *signature); + #endif /* _CLI_SPENGO_H_ */ diff --git a/source3/libsmb/clispnego.c b/source3/libsmb/clispnego.c index 539b4110569..9ef848b59ce 100644 --- a/source3/libsmb/clispnego.c +++ b/source3/libsmb/clispnego.c @@ -408,7 +408,8 @@ DATA_BLOB spnego_gen_auth(TALLOC_CTX *ctx, DATA_BLOB blob) /* parse a SPNEGO auth packet. This contains the encrypted passwords */ -bool spnego_parse_auth(TALLOC_CTX *ctx, DATA_BLOB blob, DATA_BLOB *auth) +bool spnego_parse_auth_and_mic(TALLOC_CTX *ctx, DATA_BLOB blob, + DATA_BLOB *auth, DATA_BLOB *signature) { ssize_t len; struct spnego_data token; @@ -429,17 +430,34 @@ bool spnego_parse_auth(TALLOC_CTX *ctx, DATA_BLOB blob, DATA_BLOB *auth) *auth = data_blob_talloc(ctx, token.negTokenTarg.responseToken.data, token.negTokenTarg.responseToken.length); + + if (!signature) { + goto done; + } + + *signature = data_blob_talloc(ctx, + token.negTokenTarg.mechListMIC.data, + token.negTokenTarg.mechListMIC.length); + +done: spnego_free_data(&token); return true; } +bool spnego_parse_auth(TALLOC_CTX *ctx, DATA_BLOB blob, DATA_BLOB *auth) +{ + return spnego_parse_auth_and_mic(ctx, blob, auth, NULL); +} + /* generate a minimal SPNEGO response packet. Doesn't contain much. */ -DATA_BLOB spnego_gen_auth_response(TALLOC_CTX *ctx, - DATA_BLOB *reply, NTSTATUS nt_status, - const char *mechOID) +DATA_BLOB spnego_gen_auth_response_and_mic(TALLOC_CTX *ctx, + NTSTATUS nt_status, + const char *mechOID, + DATA_BLOB *reply, + DATA_BLOB *mechlistMIC) { ASN1_DATA *data; DATA_BLOB ret; @@ -476,6 +494,14 @@ DATA_BLOB spnego_gen_auth_response(TALLOC_CTX *ctx, asn1_pop_tag(data); } + if (mechlistMIC && mechlistMIC->data != NULL) { + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_write_OctetString(data, + mechlistMIC->data, + mechlistMIC->length); + asn1_pop_tag(data); + } + asn1_pop_tag(data); asn1_pop_tag(data); @@ -484,6 +510,13 @@ DATA_BLOB spnego_gen_auth_response(TALLOC_CTX *ctx, return ret; } +DATA_BLOB spnego_gen_auth_response(TALLOC_CTX *ctx, DATA_BLOB *reply, + NTSTATUS nt_status, const char *mechOID) +{ + return spnego_gen_auth_response_and_mic(ctx, nt_status, + mechOID, reply, NULL); +} + /* parse a SPNEGO auth packet. This contains the encrypted passwords */ @@ -558,3 +591,41 @@ bool spnego_parse_auth_response(TALLOC_CTX *ctx, asn1_free(data); return True; } + +bool spnego_mech_list_blob(TALLOC_CTX *mem_ctx, + char **oid_list, DATA_BLOB *raw_data) +{ + ASN1_DATA *data; + unsigned int idx; + + if (!oid_list || !oid_list[0] || !raw_data) { + return false; + } + + data = asn1_init(talloc_tos()); + if (data == NULL) { + return false; + } + + asn1_push_tag(data, ASN1_SEQUENCE(0)); + for (idx = 0; oid_list[idx]; idx++) { + asn1_write_OID(data, oid_list[idx]); + } + asn1_pop_tag(data); + + if (data->has_error) { + DEBUG(3, (__location__ " failed at %d\n", (int)data->ofs)); + asn1_free(data); + return false; + } + + *raw_data = data_blob_talloc(mem_ctx, data->data, data->length); + if (!raw_data->data) { + DEBUG(3, (__location__": data_blob_talloc() failed!\n")); + asn1_free(data); + return false; + } + + asn1_free(data); + return true; +} diff --git a/source3/rpc_server/dcesrv_spnego.c b/source3/rpc_server/dcesrv_spnego.c new file mode 100644 index 00000000000..f5a2fbbb281 --- /dev/null +++ b/source3/rpc_server/dcesrv_spnego.c @@ -0,0 +1,309 @@ +/* + * SPNEGO Encapsulation + * DCERPC Server functions + * Copyright (C) Simo Sorce 2010. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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, see . + */ + +#include "includes.h" +#include "../libcli/auth/spnego.h" +#include "dcesrv_ntlmssp.h" +#include "dcesrv_gssapi.h" +#include "dcesrv_spnego.h" + +static NTSTATUS spnego_init_server(TALLOC_CTX *mem_ctx, + bool do_sign, bool do_seal, + bool is_dcerpc, + struct spnego_context **spnego_ctx) +{ + struct spnego_context *sp_ctx = NULL; + + sp_ctx = talloc_zero(mem_ctx, struct spnego_context); + if (!sp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + sp_ctx->do_sign = do_sign; + sp_ctx->do_seal = do_seal; + sp_ctx->is_dcerpc = is_dcerpc; + + *spnego_ctx = sp_ctx; + return NT_STATUS_OK; +} + +static NTSTATUS spnego_server_mech_init(struct spnego_context *sp_ctx, + DATA_BLOB *token_in, + DATA_BLOB *token_out) +{ + struct auth_ntlmssp_state *ntlmssp_ctx; + struct gse_context *gse_ctx; + NTSTATUS status; + + switch (sp_ctx->mech) { + case SPNEGO_KRB5: + status = gssapi_server_auth_start(sp_ctx, + sp_ctx->do_sign, + sp_ctx->do_seal, + sp_ctx->is_dcerpc, + token_in, + token_out, + &gse_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to init gssapi server " + "(%s)\n", nt_errstr(status))); + return status; + } + + sp_ctx->mech_ctx.gssapi_state = gse_ctx; + break; + + case SPNEGO_NTLMSSP: + status = ntlmssp_server_auth_start(sp_ctx, + sp_ctx->do_sign, + sp_ctx->do_seal, + sp_ctx->is_dcerpc, + token_in, + token_out, + &ntlmssp_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to init ntlmssp server " + "(%s)\n", nt_errstr(status))); + return status; + } + + sp_ctx->mech_ctx.ntlmssp_state = ntlmssp_ctx; + break; + + default: + DEBUG(3, ("No known mechanisms available\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +NTSTATUS spnego_server_step(struct spnego_context *sp_ctx, + TALLOC_CTX *mem_ctx, + DATA_BLOB *spnego_in, + DATA_BLOB *spnego_out) +{ + DATA_BLOB token_in = data_blob_null; + DATA_BLOB token_out = data_blob_null; + DATA_BLOB signature = data_blob_null; + DATA_BLOB MICblob = data_blob_null; + struct spnego_data sp_in; + ssize_t len_in = 0; + NTSTATUS status; + bool ret; + + len_in = spnego_read_data(mem_ctx, *spnego_in, &sp_in); + if (len_in == -1) { + DEBUG(1, (__location__ ": invalid SPNEGO blob.\n")); + dump_data(10, spnego_in->data, spnego_in->length); + status = NT_STATUS_INVALID_PARAMETER; + sp_ctx->state = SPNEGO_CONV_AUTH_DONE; + goto done; + } + if (sp_in.type != SPNEGO_NEG_TOKEN_TARG) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + token_in = sp_in.negTokenTarg.responseToken; + signature = sp_in.negTokenTarg.mechListMIC; + + switch (sp_ctx->state) { + case SPNEGO_CONV_NEGO: + /* still to initialize */ + status = spnego_server_mech_init(sp_ctx, + &token_in, + &token_out); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + /* server always need at least one reply from client */ + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + sp_ctx->state = SPNEGO_CONV_AUTH_MORE; + break; + + case SPNEGO_CONV_AUTH_MORE: + + switch(sp_ctx->mech) { + case SPNEGO_KRB5: + status = gssapi_server_step( + sp_ctx->mech_ctx.gssapi_state, + mem_ctx, &token_in, &token_out); + break; + case SPNEGO_NTLMSSP: + status = ntlmssp_server_step( + sp_ctx->mech_ctx.ntlmssp_state, + mem_ctx, &token_in, &token_out); + break; + default: + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + break; + + case SPNEGO_CONV_AUTH_DONE: + /* we are already done, can't step further */ + /* fall thorugh and return error */ + default: + /* wrong case */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (NT_STATUS_IS_OK(status) && signature.length != 0) { + /* last packet and requires signature check */ + ret = spnego_mech_list_blob(talloc_tos(), + sp_ctx->oid_list, &MICblob); + if (ret) { + status = spnego_sigcheck(talloc_tos(), sp_ctx, + &MICblob, &MICblob, + &signature); + } else { + status = NT_STATUS_UNSUCCESSFUL; + } + } + if (NT_STATUS_IS_OK(status) && signature.length != 0) { + /* if signature was good, sign our own packet too */ + status = spnego_sign(talloc_tos(), sp_ctx, + &MICblob, &MICblob, &signature); + } + +done: + *spnego_out = spnego_gen_auth_response_and_mic(mem_ctx, status, + sp_ctx->mech_oid, + &token_out, + &signature); + if (!spnego_out->data) { + DEBUG(1, ("SPNEGO wrapping failed!\n")); + status = NT_STATUS_UNSUCCESSFUL; + } + + if (NT_STATUS_IS_OK(status) || + !NT_STATUS_EQUAL(status, + NT_STATUS_MORE_PROCESSING_REQUIRED)) { + sp_ctx->state = SPNEGO_CONV_AUTH_DONE; + } + + data_blob_free(&token_in); + data_blob_free(&token_out); + return status; +} + +NTSTATUS spnego_server_auth_start(TALLOC_CTX *mem_ctx, + bool do_sign, + bool do_seal, + bool is_dcerpc, + DATA_BLOB *spnego_in, + DATA_BLOB *spnego_out, + struct spnego_context **spnego_ctx) +{ + struct spnego_context *sp_ctx; + DATA_BLOB token_in = data_blob_null; + DATA_BLOB token_out = data_blob_null; + unsigned int i; + NTSTATUS status; + bool ret; + + if (!spnego_in->length) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = spnego_init_server(mem_ctx, do_sign, do_seal, is_dcerpc, &sp_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ret = spnego_parse_negTokenInit(sp_ctx, *spnego_in, + sp_ctx->oid_list, NULL, &token_in); + if (!ret) { + DEBUG(3, ("Invalid SPNEGO message\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* try for krb auth first */ + for (i = 0; sp_ctx->oid_list[i] && sp_ctx->mech == SPNEGO_NONE; i++) { + if (strcmp(OID_KERBEROS5, sp_ctx->oid_list[i]) == 0 || + strcmp(OID_KERBEROS5_OLD, sp_ctx->oid_list[i]) == 0) { + + if (lp_security() == SEC_ADS || USE_KERBEROS_KEYTAB) { + sp_ctx->mech = SPNEGO_KRB5; + sp_ctx->mech_oid = sp_ctx->oid_list[i]; + } + } + } + + /* if auth type still undetermined, try for NTLMSSP */ + for (i = 0; sp_ctx->oid_list[i] && sp_ctx->mech == SPNEGO_NONE; i++) { + if (strcmp(OID_NTLMSSP, sp_ctx->oid_list[i]) == 0) { + sp_ctx->mech = SPNEGO_NTLMSSP; + sp_ctx->mech_oid = sp_ctx->oid_list[i]; + } + } + + if (!sp_ctx->mech_oid) { + DEBUG(3, ("No known mechanisms available\n")); + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if (DEBUGLEVEL >= 10) { + DEBUG(10, ("Client Provided OIDs:\n")); + for (i = 0; sp_ctx->oid_list[i]; i++) { + DEBUG(10, (" %d: %s\n", i, sp_ctx->oid_list[i])); + } + DEBUG(10, ("Chosen OID: %s\n", sp_ctx->mech_oid)); + } + + /* If it is not the first OID, then token_in is not valid for the + * choosen mech */ + if (sp_ctx->mech_oid != sp_ctx->oid_list[0]) { + /* request more and send back empty token */ + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + sp_ctx->state = SPNEGO_CONV_NEGO; + goto done; + } + + status = spnego_server_mech_init(sp_ctx, &token_in, &token_out); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + DEBUG(10, ("SPNEGO(%d) auth started\n", sp_ctx->mech)); + + /* server always need at least one reply from client */ + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + sp_ctx->state = SPNEGO_CONV_AUTH_MORE; + +done: + *spnego_out = spnego_gen_auth_response(mem_ctx, &token_out, + status, sp_ctx->mech_oid); + if (!spnego_out->data) { + status = NT_STATUS_INVALID_PARAMETER; + TALLOC_FREE(sp_ctx); + } else { + status = NT_STATUS_OK; + *spnego_ctx = sp_ctx; + } + + data_blob_free(&token_in); + data_blob_free(&token_out); + + return status; +} + diff --git a/source3/rpc_server/dcesrv_spnego.h b/source3/rpc_server/dcesrv_spnego.h new file mode 100644 index 00000000000..eeb865d8052 --- /dev/null +++ b/source3/rpc_server/dcesrv_spnego.h @@ -0,0 +1,37 @@ +/* + * SPNEGO Encapsulation + * Server routines + * Copyright (C) Simo Sorce 2010. + * + * 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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, see . + */ + +#ifndef _DCESRV_SPNEGO_H_ +#define _DCESRV_SPENGO_H_ + +#include "librpc/crypto/spnego.h" + +NTSTATUS spnego_server_auth_start(TALLOC_CTX *mem_ctx, + bool do_sign, + bool do_seal, + bool is_dcerpc, + DATA_BLOB *spnego_in, + DATA_BLOB *spnego_out, + struct spnego_context **spnego_ctx); +NTSTATUS spnego_server_step(struct spnego_context *sp_ctx, + TALLOC_CTX *mem_ctx, + DATA_BLOB *spnego_in, + DATA_BLOB *spnego_out); + +#endif /* _DCESRV_SPENGO_H_ */ -- 2.34.1