diff options
Diffstat (limited to 'source4/auth/gensec')
-rw-r--r-- | source4/auth/gensec/gensec.c | 630 | ||||
-rw-r--r-- | source4/auth/gensec/gensec.h | 117 | ||||
-rw-r--r-- | source4/auth/gensec/gensec.m4 | 12 | ||||
-rw-r--r-- | source4/auth/gensec/gensec.mk | 86 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_gssapi.c | 376 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_gsskrb5.c | 584 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_krb5.c | 751 | ||||
-rw-r--r-- | source4/auth/gensec/gensec_ntlmssp.c | 514 | ||||
-rw-r--r-- | source4/auth/gensec/ntlmssp.c | 1322 | ||||
-rw-r--r-- | source4/auth/gensec/ntlmssp.h | 190 | ||||
-rw-r--r-- | source4/auth/gensec/ntlmssp_parse.c | 338 | ||||
-rw-r--r-- | source4/auth/gensec/ntlmssp_sign.c | 449 | ||||
-rw-r--r-- | source4/auth/gensec/schannel.c | 268 | ||||
-rw-r--r-- | source4/auth/gensec/schannel.h | 35 | ||||
-rw-r--r-- | source4/auth/gensec/schannel_sign.c | 283 | ||||
-rw-r--r-- | source4/auth/gensec/schannel_state.c | 229 | ||||
-rw-r--r-- | source4/auth/gensec/spnego.c | 884 | ||||
-rw-r--r-- | source4/auth/gensec/spnego.h | 69 | ||||
-rw-r--r-- | source4/auth/gensec/spnego_parse.c | 375 |
19 files changed, 7512 insertions, 0 deletions
diff --git a/source4/auth/gensec/gensec.c b/source4/auth/gensec/gensec.c new file mode 100644 index 0000000000..cc7327187c --- /dev/null +++ b/source4/auth/gensec/gensec.c @@ -0,0 +1,630 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "auth/auth.h" + +/* the list of currently registered GENSEC backends */ +const static struct gensec_security_ops **generic_security_ops; +static int gensec_num_backends; + +static const struct gensec_security_ops *gensec_security_by_authtype(uint8_t auth_type) +{ + int i; + for (i=0; i < gensec_num_backends; i++) { + if (generic_security_ops[i]->auth_type == auth_type) { + return generic_security_ops[i]; + } + } + + return NULL; +} + +static const struct gensec_security_ops *gensec_security_by_oid(const char *oid_string) +{ + int i; + for (i=0; i < gensec_num_backends; i++) { + if (generic_security_ops[i]->oid && + (strcmp(generic_security_ops[i]->oid, oid_string) == 0)) { + return generic_security_ops[i]; + } + } + + return NULL; +} + +static const struct gensec_security_ops *gensec_security_by_sasl_name(const char *sasl_name) +{ + int i; + for (i=0; i < gensec_num_backends; i++) { + if (generic_security_ops[i]->sasl_name + && (strcmp(generic_security_ops[i]->sasl_name, sasl_name) == 0)) { + return generic_security_ops[i]; + } + } + + return NULL; +} + +static const struct gensec_security_ops *gensec_security_by_name(const char *name) +{ + int i; + for (i=0; i < gensec_num_backends; i++) { + if (generic_security_ops[i]->name + && (strcmp(generic_security_ops[i]->name, name) == 0)) { + return generic_security_ops[i]; + } + } + + return NULL; +} + +const struct gensec_security_ops **gensec_security_all(int *num_backends_out) +{ + *num_backends_out = gensec_num_backends; + return generic_security_ops; +} + +const char **gensec_security_oids(TALLOC_CTX *mem_ctx, const char *skip) +{ + int i, j = 0; + const char **oid_list; + int num_backends; + const struct gensec_security_ops **ops = gensec_security_all(&num_backends); + if (!ops) { + return NULL; + } + oid_list = talloc_array(mem_ctx, const char *, num_backends + 1); + if (!oid_list) { + return NULL; + } + + for (i=0; i<num_backends; i++) { + if (!ops[i]->oid) { + continue; + } + + if (skip && strcmp(skip, ops[i]->oid)==0) { + continue; + } + + oid_list[j] = ops[i]->oid; + j++; + } + oid_list[j] = NULL; + return oid_list; +} + +/** + Start the GENSEC system, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +static NTSTATUS gensec_start(TALLOC_CTX *mem_ctx, struct gensec_security **gensec_security) +{ + (*gensec_security) = talloc(mem_ctx, struct gensec_security); + if (!(*gensec_security)) { + return NT_STATUS_NO_MEMORY; + } + + (*gensec_security)->ops = NULL; + + ZERO_STRUCT((*gensec_security)->target); + + (*gensec_security)->subcontext = False; + (*gensec_security)->want_features = 0; + return NT_STATUS_OK; +} + +/** + * Start a GENSEC subcontext, with a copy of the properties of the parent + * @param mem_ctx The parent TALLOC memory context. + * @param parent The parent GENSEC context + * @param gensec_security Returned GENSEC context pointer. + * @note Used by SPNEGO in particular, for the actual implementation mechanism + */ + +NTSTATUS gensec_subcontext_start(TALLOC_CTX *mem_ctx, + struct gensec_security *parent, + struct gensec_security **gensec_security) +{ + (*gensec_security) = talloc(mem_ctx, struct gensec_security); + if (!(*gensec_security)) { + return NT_STATUS_NO_MEMORY; + } + + (**gensec_security) = *parent; + (*gensec_security)->ops = NULL; + (*gensec_security)->private_data = NULL; + + (*gensec_security)->subcontext = True; + + return NT_STATUS_OK; +} + +/** + Start the GENSEC system, in client mode, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +NTSTATUS gensec_client_start(TALLOC_CTX *mem_ctx, struct gensec_security **gensec_security) +{ + NTSTATUS status; + status = gensec_start(mem_ctx, gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*gensec_security)->gensec_role = GENSEC_CLIENT; + (*gensec_security)->password_callback = NULL; + + return status; +} + +/** + Start the GENSEC system, in server mode, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +NTSTATUS gensec_server_start(TALLOC_CTX *mem_ctx, struct gensec_security **gensec_security) +{ + NTSTATUS status; + status = gensec_start(mem_ctx, gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*gensec_security)->gensec_role = GENSEC_SERVER; + + return status; +} + +static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security) +{ + NTSTATUS status; + DEBUG(5, ("Starting GENSEC %smechanism %s\n", + gensec_security->subcontext ? "sub" : "", + gensec_security->ops->name)); + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + if (gensec_security->ops->client_start) { + status = gensec_security->ops->client_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC client mech %s: %s\n", + gensec_security->ops->name, nt_errstr(status))); + } + return status; + } + case GENSEC_SERVER: + if (gensec_security->ops->server_start) { + status = gensec_security->ops->server_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC server mech %s: %s\n", + gensec_security->ops->name, nt_errstr(status))); + } + return status; + } + } + return NT_STATUS_INVALID_PARAMETER; +} + +/** + * Start a GENSEC sub-mechanism by DCERPC allocated 'auth type' number + * @param gensec_security GENSEC context pointer. + * @param auth_type DCERPC auth type + * @param auth_level DCERPC auth level + */ + +NTSTATUS gensec_start_mech_by_authtype(struct gensec_security *gensec_security, + uint8_t auth_type, uint8_t auth_level) +{ + gensec_security->ops = gensec_security_by_authtype(auth_type); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for auth_type=%d\n", (int)auth_type)); + return NT_STATUS_INVALID_PARAMETER; + } + gensec_want_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE); + if (auth_level == DCERPC_AUTH_LEVEL_INTEGRITY) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + } + if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL); + } + + return gensec_start_mech(gensec_security); +} + +const char *gensec_get_name_by_authtype(uint8_t authtype) +{ + const struct gensec_security_ops *ops; + ops = gensec_security_by_authtype(authtype); + if (ops) { + return ops->name; + } + return NULL; +} + + +const char *gensec_get_name_by_oid(const char *oid_string) +{ + const struct gensec_security_ops *ops; + ops = gensec_security_by_oid(oid_string); + if (ops) { + return ops->name; + } + return NULL; +} + + +/** + * Start a GENSEC sub-mechanism by OID, used in SPNEGO + * + * @note This should also be used when you wish to just start NLTMSSP (for example), as it uses a + * well-known #define to hook it in. + */ + +NTSTATUS gensec_start_mech_by_oid(struct gensec_security *gensec_security, + const char *mech_oid) +{ + gensec_security->ops = gensec_security_by_oid(mech_oid); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for oid=%s\n", mech_oid)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Start a GENSEC sub-mechanism by a well know SASL name + * + */ + +NTSTATUS gensec_start_mech_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name) +{ + gensec_security->ops = gensec_security_by_sasl_name(sasl_name); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for sasl_name=%s\n", sasl_name)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/* + wrappers for the gensec function pointers +*/ +NTSTATUS gensec_unseal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!gensec_security->ops->unseal_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return gensec_check_packet(gensec_security, mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); + } + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->unseal_packet(gensec_security, mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_check_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + if (!gensec_security->ops->check_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->check_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +NTSTATUS gensec_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!gensec_security->ops->seal_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return gensec_sign_packet(gensec_security, mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); + } + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->seal_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +NTSTATUS gensec_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!gensec_security->ops->sign_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->sign_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +size_t gensec_sig_size(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->sig_size) { + return 0; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return 0; + } + + return gensec_security->ops->sig_size(gensec_security); +} + +NTSTATUS gensec_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (!gensec_security->ops->wrap) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->wrap(gensec_security, mem_ctx, in, out); +} + +NTSTATUS gensec_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (!gensec_security->ops->unwrap) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->unwrap(gensec_security, mem_ctx, in, out); +} + +NTSTATUS gensec_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) +{ + if (!gensec_security->ops->session_key) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->session_key(gensec_security, session_key); +} + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ + +NTSTATUS gensec_session_info(struct gensec_security *gensec_security, + struct auth_session_info **session_info) +{ + if (!gensec_security->ops->session_info) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->session_info(gensec_security, session_info); +} + +/** + * Next state function for the GENSEC state machine + * + * @param gensec_security GENSEC State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ + +NTSTATUS gensec_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + return gensec_security->ops->update(gensec_security, out_mem_ctx, in, out); +} + +/** + * Set the requirement for a certain feature on the connection + * + */ + +void gensec_want_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + gensec_security->want_features |= feature; +} + +/** + * Check the requirement for a certain feature on the connection + * + */ + +BOOL gensec_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (!gensec_security->ops->have_feature) { + return False; + } + return gensec_security->ops->have_feature(gensec_security, feature); +} + +/** + * Associate a credentails structure with a GENSEC context - talloc_reference()s it to the context + * + */ + +NTSTATUS gensec_set_credentials(struct gensec_security *gensec_security, struct cli_credentials *credentials) +{ + gensec_security->credentials = talloc_reference(gensec_security, credentials); + return NT_STATUS_OK; +} + +/** + * Return the credentails structure associated with a GENSEC context + * + */ + +struct cli_credentials *gensec_get_credentials(struct gensec_security *gensec_security) +{ + return gensec_security->credentials; +} + +/** + * Set the target service (such as 'http' or 'host') on a GENSEC context - ensures it is talloc()ed + * + */ + +NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service) +{ + gensec_security->target.service = talloc_strdup(gensec_security, service); + if (!gensec_security->target.service) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Set the target hostname (suitable for kerberos resolutation) on a GENSEC context - ensures it is talloc()ed + * + */ + +NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname) +{ + gensec_security->target.hostname = talloc_strdup(gensec_security, hostname); + if (!gensec_security->target.hostname) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +const char *gensec_get_target_hostname(struct gensec_security *gensec_security) +{ + if (gensec_security->target.hostname) { + return gensec_security->target.hostname; + } + + /* TODO: Add a 'set sockaddr' call, and do a reverse lookup */ + return NULL; +} + +const char *gensec_get_target_service(struct gensec_security *gensec_security) +{ + if (gensec_security->target.service) { + return gensec_security->target.service; + } + + return "host"; +} + +/* + register a GENSEC backend. + + The 'name' can be later used by other backends to find the operations + structure for this backend. +*/ +NTSTATUS gensec_register(const void *_ops) +{ + const struct gensec_security_ops *ops = _ops; + + if (!lp_parm_bool(-1, "gensec", ops->name, ops->enabled)) { + DEBUG(2,("gensec subsystem %s is disabled\n", ops->name)); + return NT_STATUS_OK; + } + + if (gensec_security_by_name(ops->name) != NULL) { + /* its already registered! */ + DEBUG(0,("GENSEC backend '%s' already registered\n", + ops->name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + generic_security_ops = realloc_p(generic_security_ops, + const struct gensec_security_ops *, + gensec_num_backends+1); + if (!generic_security_ops) { + smb_panic("out of memory in gensec_register"); + } + + generic_security_ops[gensec_num_backends] = ops; + + gensec_num_backends++; + + DEBUG(3,("GENSEC backend '%s' registered\n", + ops->name)); + + return NT_STATUS_OK; +} + +/* + return the GENSEC interface version, and the size of some critical types + This can be used by backends to either detect compilation errors, or provide + multiple implementations for different smbd compilation options in one module +*/ +const struct gensec_critical_sizes *gensec_interface_version(void) +{ + static const struct gensec_critical_sizes critical_sizes = { + GENSEC_INTERFACE_VERSION, + sizeof(struct gensec_security_ops), + sizeof(struct gensec_security), + }; + + return &critical_sizes; +} + +/* + initialise the GENSEC subsystem +*/ +NTSTATUS gensec_init(void) +{ + return NT_STATUS_OK; +} diff --git a/source4/auth/gensec/gensec.h b/source4/auth/gensec/gensec.h new file mode 100644 index 0000000000..91c817d48a --- /dev/null +++ b/source4/auth/gensec/gensec.h @@ -0,0 +1,117 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#define GENSEC_OID_NTLMSSP "1 3 6 1 4 1 311 2 2 10" +#define GENSEC_OID_SPNEGO "1 3 6 1 5 5 2" +#define GENSEC_OID_KERBEROS5 "1 2 840 113554 1 2 2" +#define GENSEC_OID_KERBEROS5_OLD "1 2 840 48018 1 2 2" +#define GENSEC_OID_KERBEROS5_USER2USER "1 2 840 113554 1 2 2 3" + +struct gensec_security; +struct gensec_target { + const char *principal; + const char *hostname; + const struct sock_addr *addr; + const char *service; +}; + +#define GENSEC_FEATURE_SESSION_KEY 0x00000001 +#define GENSEC_FEATURE_SIGN 0x00000002 +#define GENSEC_FEATURE_SEAL 0x00000004 +#define GENSEC_FEATURE_DCE_STYLE 0x00000008 + +/* GENSEC mode */ +enum gensec_role +{ + GENSEC_SERVER, + GENSEC_CLIENT +}; + +struct auth_session_info; + +struct gensec_security_ops { + const char *name; + const char *sasl_name; + uint8_t auth_type; /* 0 if not offered on DCE-RPC */ + const char *oid; /* NULL if not offered by SPNEGO */ + NTSTATUS (*client_start)(struct gensec_security *gensec_security); + NTSTATUS (*server_start)(struct gensec_security *gensec_security); + NTSTATUS (*update)(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); + NTSTATUS (*seal_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + NTSTATUS (*sign_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + size_t (*sig_size)(struct gensec_security *gensec_security); + NTSTATUS (*check_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); + NTSTATUS (*unseal_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + NTSTATUS (*wrap)(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + NTSTATUS (*unwrap)(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + NTSTATUS (*session_key)(struct gensec_security *gensec_security, DATA_BLOB *session_key); + NTSTATUS (*session_info)(struct gensec_security *gensec_security, + struct auth_session_info **session_info); + BOOL (*have_feature)(struct gensec_security *gensec_security, + uint32_t feature); + BOOL enabled; +}; + +#define GENSEC_INTERFACE_VERSION 0 + +struct gensec_security { + gensec_password_callback password_callback; + void *password_callback_private; + const struct gensec_security_ops *ops; + void *private_data; + struct cli_credentials *credentials; + struct gensec_target target; + enum gensec_role gensec_role; + BOOL subcontext; + uint32_t want_features; +}; + +/* this structure is used by backends to determine the size of some critical types */ +struct gensec_critical_sizes { + int interface_version; + int sizeof_gensec_security_ops; + int sizeof_gensec_security; +}; + + +/* pre-declare schannel structure for schannel backend */ +struct schannel_state; diff --git a/source4/auth/gensec/gensec.m4 b/source4/auth/gensec/gensec.m4 new file mode 100644 index 0000000000..1af0a1d9c8 --- /dev/null +++ b/source4/auth/gensec/gensec.m4 @@ -0,0 +1,12 @@ +SMB_MODULE_DEFAULT(gensec_krb5, NOT) +SMB_MODULE_DEFAULT(gensec_gssapi, NOT) +SMB_MODULE_DEFAULT(gensec_gsskrb5, NOT) + +if test x"$SMB_EXT_LIB_ENABLE_KRB5" = x"YES"; then + # krb5 is now disabled at runtime, not build time + SMB_MODULE_DEFAULT(gensec_krb5, STATIC) + SMB_MODULE_DEFAULT(gensec_gssapi, STATIC) + if test x"$samba_cv_GSS_C_DCE_STYLE" = x"yes"; then + SMB_MODULE_DEFAULT(gensec_gsskrb5, STATIC) + fi +fi diff --git a/source4/auth/gensec/gensec.mk b/source4/auth/gensec/gensec.mk new file mode 100644 index 0000000000..8ed6f7c840 --- /dev/null +++ b/source4/auth/gensec/gensec.mk @@ -0,0 +1,86 @@ +################################# +# Start SUBSYSTEM GENSEC +[SUBSYSTEM::GENSEC] +INIT_FUNCTION = gensec_init +INIT_OBJ_FILES = auth/gensec/gensec.o +REQUIRED_SUBSYSTEMS = \ + SCHANNELDB +# End SUBSYSTEM GENSEC +################################# + +################################################ +# Start MODULE gensec_krb5 +[MODULE::gensec_krb5] +SUBSYSTEM = GENSEC +INIT_FUNCTION = gensec_krb5_init +INIT_OBJ_FILES = auth/gensec/gensec_krb5.o +REQUIRED_SUBSYSTEMS = NDR_KRB5PAC KERBEROS EXT_LIB_KRB5 +# End MODULE gensec_krb5 +################################################ + +################################################ +# Start MODULE gensec_gssapi +[MODULE::gensec_gssapi] +SUBSYSTEM = GENSEC +INIT_FUNCTION = gensec_gssapi_init +INIT_OBJ_FILES = auth/gensec/gensec_gssapi.o +REQUIRED_SUBSYSTEMS = EXT_LIB_KRB5 +# End MODULE gensec_gssapi +################################################ + +################################################ +# Start MODULE gensec_gsskrb5 +[MODULE::gensec_gsskrb5] +SUBSYSTEM = GENSEC +INIT_FUNCTION = gensec_gsskrb5_init +INIT_OBJ_FILES = auth/gensec/gensec_gsskrb5.o +REQUIRED_SUBSYSTEMS = EXT_LIB_KRB5 +# End MODULE gensec_gsskrb5 +################################################ + +################################################ +# Start MODULE gensec_spnego +[MODULE::gensec_spnego] +SUBSYSTEM = GENSEC +INIT_FUNCTION = gensec_spnego_init +INIT_OBJ_FILES = auth/gensec/spnego.o +ADD_OBJ_FILES = \ + auth/gensec/spnego_parse.o +# End MODULE gensec_spnego +################################################ + +################################################ +# Start MODULE gensec_ntlmssp +[MODULE::gensec_ntlmssp] +SUBSYSTEM = GENSEC +INIT_FUNCTION = gensec_ntlmssp_init +INIT_OBJ_FILES = auth/gensec/gensec_ntlmssp.o +ADD_OBJ_FILES = \ + auth/gensec/ntlmssp.o \ + auth/gensec/ntlmssp_parse.o \ + auth/gensec/ntlmssp_sign.o +REQUIRED_SUBSYSTEMS = AUTH +# End MODULE gensec_ntlmssp +################################################ + +################################################ +# Start MODULE gensec_schannel +[MODULE::gensec_schannel] +SUBSYSTEM = GENSEC +INIT_FUNCTION = gensec_schannel_init +INIT_OBJ_FILES = auth/gensec/schannel.o +ADD_OBJ_FILES = \ + auth/gensec/schannel_sign.o +REQUIRED_SUBSYSTEMS = AUTH SCHANNELDB +# End MODULE gensec_ntlmssp +################################################ + +################################################ +# Start SUBSYSTEM SCHANNELDB +[SUBSYSTEM::SCHANNELDB] +INIT_OBJ_FILES = \ + auth/gensec/schannel_state.o +# +# End SUBSYSTEM SCHANNELDB +################################################ + diff --git a/source4/auth/gensec/gensec_gssapi.c b/source4/auth/gensec/gensec_gssapi.c new file mode 100644 index 0000000000..c974b93952 --- /dev/null +++ b/source4/auth/gensec/gensec_gssapi.c @@ -0,0 +1,376 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + 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 + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "auth/auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +struct gensec_gssapi_state { + gss_ctx_id_t gssapi_context; + struct gss_channel_bindings_struct *input_chan_bindings; + gss_name_t server_name; + gss_name_t client_name; + OM_uint32 want_flags, got_flags; + const gss_OID_desc *gss_oid; +}; +static int gensec_gssapi_destory(void *ptr) +{ + struct gensec_gssapi_state *gensec_gssapi_state = ptr; + OM_uint32 maj_stat, min_stat; + + if (gensec_gssapi_state->gssapi_context != GSS_C_NO_CONTEXT) { + maj_stat = gss_delete_sec_context (&min_stat, + &gensec_gssapi_state->gssapi_context, + GSS_C_NO_BUFFER); + } + + if (gensec_gssapi_state->server_name != GSS_C_NO_NAME) { + maj_stat = gss_release_name(&min_stat, &gensec_gssapi_state->server_name); + } + if (gensec_gssapi_state->client_name != GSS_C_NO_NAME) { + maj_stat = gss_release_name(&min_stat, &gensec_gssapi_state->client_name); + } + return 0; +} + +static NTSTATUS gensec_gssapi_start(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state; + + gensec_gssapi_state = talloc(gensec_security, struct gensec_gssapi_state); + if (!gensec_gssapi_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_gssapi_state; + + gensec_gssapi_state->gssapi_context = GSS_C_NO_CONTEXT; + gensec_gssapi_state->server_name = GSS_C_NO_NAME; + gensec_gssapi_state->client_name = GSS_C_NO_NAME; + + talloc_set_destructor(gensec_gssapi_state, gensec_gssapi_destory); + + /* TODO: Fill in channel bindings */ + gensec_gssapi_state->input_chan_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + gensec_gssapi_state->want_flags = 0; + gensec_gssapi_state->got_flags = 0; + + if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) { + /* GSSAPI won't give us the session keys */ + return NT_STATUS_INVALID_PARAMETER; + } + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + gensec_gssapi_state->want_flags |= GSS_C_INTEG_FLAG; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + gensec_gssapi_state->want_flags |= GSS_C_CONF_FLAG; + } + + if (strcmp(gensec_security->ops->oid, GENSEC_OID_KERBEROS5) == 0) { + static const gss_OID_desc gensec_gss_krb5_mechanism_oid_desc = + {9, (void *)discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")}; + + gensec_gssapi_state->gss_oid = &gensec_gss_krb5_mechanism_oid_desc; + } else if (strcmp(gensec_security->ops->oid, GENSEC_OID_SPNEGO) == 0) { + static const gss_OID_desc gensec_gss_spnego_mechanism_oid_desc = + {6, (void *)discard_const_p(char, "\x2b\x06\x01\x05\x05\x02")}; + gensec_gssapi_state->gss_oid = &gensec_gss_spnego_mechanism_oid_desc; + } else { + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct gensec_gssapi_state *gensec_gssapi_state; + + nt_status = gensec_gssapi_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_gssapi_state = gensec_security->private_data; + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_client_start(struct gensec_security *gensec_security) +{ + struct gensec_gssapi_state *gensec_gssapi_state; + NTSTATUS nt_status; + gss_buffer_desc name_token; + OM_uint32 maj_stat, min_stat; + + gss_OID_desc hostbased = {10, + (void *)discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12" + "\x01\x02\x01\x04")}; + + nt_status = gensec_gssapi_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_gssapi_state = gensec_security->private_data; + + name_token.value = talloc_asprintf(gensec_gssapi_state, "%s@%s", gensec_get_target_service(gensec_security), + gensec_get_target_hostname(gensec_security)); + DEBUG(0, ("name: %s\n", (char *)name_token.value)); + name_token.length = strlen(name_token.value); + + maj_stat = gss_import_name (&min_stat, + &name_token, + &hostbased, + &gensec_gssapi_state->server_name); + + + if (maj_stat) { + return NT_STATUS_UNSUCCESSFUL; + } + return NT_STATUS_OK; +} + + + +/** + * Next state function for the GSSAPI GENSEC mechanism + * + * @param gensec_gssapi_state GSSAPI State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ + +static NTSTATUS gensec_gssapi_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_gssapi_state *gensec_gssapi_state = gensec_security->private_data; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + OM_uint32 maj_stat, min_stat; + OM_uint32 min_stat2; + gss_buffer_desc input_token, output_token; + gss_OID gss_oid_p; + input_token.length = in.length; + input_token.value = in.data; + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + { + maj_stat = gss_init_sec_context(&min_stat, + GSS_C_NO_CREDENTIAL, + &gensec_gssapi_state->gssapi_context, + gensec_gssapi_state->server_name, + discard_const_p(gss_OID_desc, gensec_gssapi_state->gss_oid), + gensec_gssapi_state->want_flags, + 0, + gensec_gssapi_state->input_chan_bindings, + &input_token, + NULL, + &output_token, + &gensec_gssapi_state->got_flags, /* ret flags */ + NULL); + break; + } + case GENSEC_SERVER: + { + maj_stat = gss_accept_sec_context(&min_stat, + &gensec_gssapi_state->gssapi_context, + GSS_C_NO_CREDENTIAL, + &input_token, + gensec_gssapi_state->input_chan_bindings, + &gensec_gssapi_state->client_name, + &gss_oid_p, + &output_token, + &gensec_gssapi_state->got_flags, + NULL, + NULL); + gensec_gssapi_state->gss_oid = gss_oid_p; + break; + } + default: + return NT_STATUS_INVALID_PARAMETER; + + } + + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + gss_release_buffer(&min_stat2, &output_token); + + if (maj_stat == GSS_S_COMPLETE) { + return NT_STATUS_OK; + } else if (maj_stat == GSS_S_CONTINUE_NEEDED) { + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + gss_buffer_desc msg1, msg2; + OM_uint32 msg_ctx = 0; + + msg1.value = NULL; + msg2.value = NULL; + gss_display_status(&min_stat2, maj_stat, GSS_C_GSS_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg1); + gss_display_status(&min_stat2, min_stat, GSS_C_MECH_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg2); + DEBUG(1, ("gensec_gssapi_update: %s : %s\n", (char *)msg1.value, (char *)msg2.value)); + gss_release_buffer(&min_stat2, &msg1); + gss_release_buffer(&min_stat2, &msg2); + + return nt_status; + } + +} + +static NTSTATUS gensec_gssapi_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_gssapi_state *gensec_gssapi_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + input_token.length = in->length; + input_token.value = in->data; + + maj_stat = gss_wrap(&min_stat, + gensec_gssapi_state->gssapi_context, + gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL), + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length); + + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gssapi_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_gssapi_state *gensec_gssapi_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + gss_qop_t qop_state; + input_token.length = in->length; + input_token.value = in->data; + + maj_stat = gss_unwrap(&min_stat, + gensec_gssapi_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length); + + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static BOOL gensec_gssapi_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_gssapi_state *gensec_gssapi_state = gensec_security->private_data; + if (feature & GENSEC_FEATURE_SIGN) { + return gensec_gssapi_state->got_flags & GSS_C_INTEG_FLAG; + } + if (feature & GENSEC_FEATURE_SEAL) { + return gensec_gssapi_state->got_flags & GSS_C_CONF_FLAG; + } + return False; +} + +/* As a server, this could in theory accept any GSSAPI mech */ +static const struct gensec_security_ops gensec_gssapi_krb5_security_ops = { + .name = "gssapi_krb5", + .sasl_name = "GSSAPI", + .oid = GENSEC_OID_KERBEROS5, + .client_start = gensec_gssapi_client_start, + .server_start = gensec_gssapi_server_start, + .update = gensec_gssapi_update, + .wrap = gensec_gssapi_wrap, + .unwrap = gensec_gssapi_unwrap, + .have_feature = gensec_gssapi_have_feature, + .enabled = False + +}; + +static const struct gensec_security_ops gensec_gssapi_spnego_security_ops = { + .name = "gssapi_spnego", + .sasl_name = "GSS-SPNEGO", + .oid = GENSEC_OID_SPNEGO, + .client_start = gensec_gssapi_client_start, + .server_start = gensec_gssapi_server_start, + .update = gensec_gssapi_update, + .wrap = gensec_gssapi_wrap, + .unwrap = gensec_gssapi_unwrap, + .have_feature = gensec_gssapi_have_feature, + .enabled = False +}; + +NTSTATUS gensec_gssapi_init(void) +{ + NTSTATUS ret; + + ret = gensec_register(&gensec_gssapi_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_gssapi_krb5_security_ops.name)); + return ret; + } + + ret = gensec_register(&gensec_gssapi_spnego_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_gssapi_spnego_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/gensec_gsskrb5.c b/source4/auth/gensec/gensec_gsskrb5.c new file mode 100644 index 0000000000..77e077276b --- /dev/null +++ b/source4/auth/gensec/gensec_gsskrb5.c @@ -0,0 +1,584 @@ +/* + Unix SMB/CIFS implementation. + + GSSAPI KRB5 backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "system/time.h" +#include "libcli/auth/kerberos.h" +#include "auth/auth.h" + +static const gss_OID_desc gensec_gss_krb5_mechanism_oid_desc = + {9, (void *)discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")}; + +enum GENSEC_GSSKRB5_STATE { + GENSEC_GSSKRB5_CLIENT_START, + GENSEC_GSSKRB5_CLIENT_MUTUAL_AUTH, + GENSEC_GSSKRB5_CLIENT_DCE_STYLE, + GENSEC_GSSKRB5_DONE +}; + +struct gensec_gsskrb5_state { + enum GENSEC_GSSKRB5_STATE state_position; + gss_ctx_id_t gssapi_context; + struct gss_channel_bindings_struct *input_chan_bindings; + gss_name_t server_name; + gss_name_t client_name; + OM_uint32 want_flags, got_flags; +}; + +static int gensec_gsskrb5_destory(void *ptr) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = ptr; + OM_uint32 maj_stat, min_stat; + + if (gensec_gsskrb5_state->gssapi_context != GSS_C_NO_CONTEXT) { + maj_stat = gss_delete_sec_context (&min_stat, + &gensec_gsskrb5_state->gssapi_context, + GSS_C_NO_BUFFER); + } + + if (gensec_gsskrb5_state->server_name != GSS_C_NO_NAME) { + maj_stat = gss_release_name(&min_stat, &gensec_gsskrb5_state->server_name); + } + if (gensec_gsskrb5_state->client_name != GSS_C_NO_NAME) { + maj_stat = gss_release_name(&min_stat, &gensec_gsskrb5_state->client_name); + } + return 0; +} + +static NTSTATUS gensec_gsskrb5_start(struct gensec_security *gensec_security) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state; + + gensec_gsskrb5_state = talloc(gensec_security, struct gensec_gsskrb5_state); + if (!gensec_gsskrb5_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_gsskrb5_state; + + gensec_gsskrb5_state->gssapi_context = GSS_C_NO_CONTEXT; + gensec_gsskrb5_state->server_name = GSS_C_NO_NAME; + gensec_gsskrb5_state->client_name = GSS_C_NO_NAME; + + talloc_set_destructor(gensec_gsskrb5_state, gensec_gsskrb5_destory); + + /* TODO: Fill in channel bindings */ + gensec_gsskrb5_state->input_chan_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + gensec_gsskrb5_state->want_flags = GSS_C_MUTUAL_FLAG; + gensec_gsskrb5_state->got_flags = 0; + + if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) { + /* GSSAPI won't give us the session keys */ + return NT_STATUS_INVALID_PARAMETER; + } + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + gensec_gsskrb5_state->want_flags |= GSS_C_INTEG_FLAG; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + gensec_gsskrb5_state->want_flags |= GSS_C_CONF_FLAG; + } + if (gensec_security->want_features & GENSEC_FEATURE_DCE_STYLE) { + gensec_gsskrb5_state->want_flags |= GSS_C_DCE_STYLE; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gsskrb5_client_start(struct gensec_security *gensec_security) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state; + NTSTATUS nt_status; + gss_buffer_desc name_token; + OM_uint32 maj_stat, min_stat; + + gss_OID_desc hostbased = {10, + (void *)discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12" + "\x01\x02\x01\x04")}; + + nt_status = gensec_gsskrb5_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_gsskrb5_state = gensec_security->private_data; + + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_CLIENT_START; + + name_token.value = talloc_asprintf(gensec_gsskrb5_state, "%s@%s", gensec_get_target_service(gensec_security), + gensec_get_target_hostname(gensec_security)); + DEBUG(0, ("name: %s\n", (char *)name_token.value)); + name_token.length = strlen(name_token.value); + + maj_stat = gss_import_name (&min_stat, + &name_token, + &hostbased, + &gensec_gsskrb5_state->server_name); + if (maj_stat) { + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +/** + * Next state function for the GSSKRB5 GENSEC mechanism + * + * @param gensec_gsskrb5_state GSSAPI State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ + +static NTSTATUS gensec_gsskrb5_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL; + OM_uint32 maj_stat, min_stat; + OM_uint32 min_stat2; + gss_buffer_desc input_token, output_token; + + *out = data_blob(NULL, 0); + + input_token.length = in.length; + input_token.value = in.data; + + switch (gensec_gsskrb5_state->state_position) { + case GENSEC_GSSKRB5_CLIENT_START: + { + maj_stat = gss_init_sec_context(&min_stat, + GSS_C_NO_CREDENTIAL, + &gensec_gsskrb5_state->gssapi_context, + gensec_gsskrb5_state->server_name, + discard_const_p(gss_OID_desc, &gensec_gss_krb5_mechanism_oid_desc), + gensec_gsskrb5_state->want_flags, + 0, + gensec_gsskrb5_state->input_chan_bindings, + &input_token, + NULL, + &output_token, + &gensec_gsskrb5_state->got_flags, /* ret flags */ + NULL); + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + if (out->length != output_token.length) { + gss_release_buffer(&min_stat2, &output_token); + return NT_STATUS_NO_MEMORY; + } + gss_release_buffer(&min_stat2, &output_token); + + if (maj_stat == GSS_S_COMPLETE) { + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_DONE; + return NT_STATUS_OK; + } else if (maj_stat == GSS_S_CONTINUE_NEEDED) { + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_CLIENT_MUTUAL_AUTH; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + gss_buffer_desc msg1, msg2; + OM_uint32 msg_ctx = 0; + + msg1.value = NULL; + msg2.value = NULL; + gss_display_status(&min_stat2, maj_stat, GSS_C_GSS_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg1); + gss_display_status(&min_stat2, min_stat, GSS_C_MECH_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg2); + DEBUG(1, ("gensec_gsskrb5_update: %s : %s\n", (char *)msg1.value, (char *)msg2.value)); + gss_release_buffer(&min_stat2, &msg1); + gss_release_buffer(&min_stat2, &msg2); + + return nt_status; + } + break; + } + case GENSEC_GSSKRB5_CLIENT_MUTUAL_AUTH: + { + maj_stat = gss_init_sec_context(&min_stat, + GSS_C_NO_CREDENTIAL, + &gensec_gsskrb5_state->gssapi_context, + gensec_gsskrb5_state->server_name, + discard_const_p(gss_OID_desc, &gensec_gss_krb5_mechanism_oid_desc), + gensec_gsskrb5_state->want_flags, + 0, + gensec_gsskrb5_state->input_chan_bindings, + &input_token, + NULL, + &output_token, + &gensec_gsskrb5_state->got_flags, /* ret flags */ + NULL); + *out = data_blob_talloc(out_mem_ctx, output_token.value, output_token.length); + if (out->length != output_token.length) { + gss_release_buffer(&min_stat2, &output_token); + return NT_STATUS_NO_MEMORY; + } + gss_release_buffer(&min_stat2, &output_token); + + if (maj_stat == GSS_S_COMPLETE) { + if (gensec_gsskrb5_state->got_flags & GSS_C_DCE_STYLE) { + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_CLIENT_DCE_STYLE; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_DONE; + return NT_STATUS_OK; + } else if (maj_stat == GSS_S_CONTINUE_NEEDED) { + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_CLIENT_DCE_STYLE; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + gss_buffer_desc msg1, msg2; + OM_uint32 msg_ctx = 0; + + msg1.value = NULL; + msg2.value = NULL; + gss_display_status(&min_stat2, maj_stat, GSS_C_GSS_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg1); + gss_display_status(&min_stat2, min_stat, GSS_C_MECH_CODE, + GSS_C_NULL_OID, &msg_ctx, &msg2); + DEBUG(1, ("gensec_gsskrb5_update: %s : %s\n", (char *)msg1.value, (char *)msg2.value)); + gss_release_buffer(&min_stat2, &msg1); + gss_release_buffer(&min_stat2, &msg2); + + return nt_status; + } + break; + } + case GENSEC_GSSKRB5_CLIENT_DCE_STYLE: + { + gensec_gsskrb5_state->state_position = GENSEC_GSSKRB5_DONE; + return NT_STATUS_OK; + } + case GENSEC_GSSKRB5_DONE: + { + return NT_STATUS_OK; + } + default: + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_gsskrb5_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + input_token.length = in->length; + input_token.value = in->data; + + maj_stat = gss_wrap(&min_stat, + gensec_gsskrb5_state->gssapi_context, + gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL), + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length); + + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gsskrb5_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + gss_qop_t qop_state; + input_token.length = in->length; + input_token.value = in->data; + + maj_stat = gss_unwrap(&min_stat, + gensec_gsskrb5_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length); + + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static size_t gensec_gsskrb5_sig_size(struct gensec_security *gensec_security) +{ + /* not const but work for DCERPC packets and arcfour */ + return 45; +} + +static NTSTATUS gensec_gsskrb5_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + ssize_t sig_length = 0; + + input_token.length = length; + input_token.value = data; + + maj_stat = gss_wrap(&min_stat, + gensec_gsskrb5_state->gssapi_context, + gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL), + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (output_token.length < length) { + return NT_STATUS_INTERNAL_ERROR; + } + + sig_length = 45; + + memcpy(data, ((uint8_t *)output_token.value) + sig_length, length); + *sig = data_blob_talloc(mem_ctx, (uint8_t *)output_token.value, sig_length); + +DEBUG(0,("gensec_gsskrb5_seal_packet: siglen: %d inlen: %d, wrap_len: %d\n", sig->length, length, output_token.length - sig_length)); +dump_data(0,sig->data, sig->length); +dump_data(0,data, length); +dump_data(0,((uint8_t *)output_token.value) + sig_length, output_token.length - sig_length); + + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gsskrb5_unseal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + gss_qop_t qop_state; + DATA_BLOB in; + +DEBUG(0,("gensec_gsskrb5_unseal_packet: siglen: %d\n", sig->length)); +dump_data(0,sig->data, sig->length); + + in = data_blob_talloc(mem_ctx, NULL, sig->length + length); + + memcpy(in.data, sig->data, sig->length); + memcpy(in.data + sig->length, data, length); + + input_token.length = in.length; + input_token.value = in.data; + + maj_stat = gss_unwrap(&min_stat, + gensec_gsskrb5_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (output_token.length != length) { + return NT_STATUS_INTERNAL_ERROR; + } + + memcpy(data, output_token.value, length); + + gss_release_buffer(&min_stat, &output_token); + + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) + && !conf_state) { + return NT_STATUS_ACCESS_DENIED; + } + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gsskrb5_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + ssize_t sig_length = 0; + + input_token.length = length; + input_token.value = data; + + maj_stat = gss_wrap(&min_stat, + gensec_gsskrb5_state->gssapi_context, + 0, + GSS_C_QOP_DEFAULT, + &input_token, + &conf_state, + &output_token); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (output_token.length < length) { + return NT_STATUS_INTERNAL_ERROR; + } + + sig_length = 45; + + /*memcpy(data, ((uint8_t *)output_token.value) + sig_length, length);*/ + *sig = data_blob_talloc(mem_ctx, (uint8_t *)output_token.value, sig_length); + +DEBUG(0,("gensec_gsskrb5_sign_packet: siglen: %d\n", sig->length)); +dump_data(0,sig->data, sig->length); + + gss_release_buffer(&min_stat, &output_token); + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_gsskrb5_check_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + int conf_state; + gss_qop_t qop_state; + DATA_BLOB in; + +DEBUG(0,("gensec_gsskrb5_check_packet: siglen: %d\n", sig->length)); +dump_data(0,sig->data, sig->length); + + in = data_blob_talloc(mem_ctx, NULL, sig->length + length); + + memcpy(in.data, sig->data, sig->length); + memcpy(in.data + sig->length, data, length); + + input_token.length = in.length; + input_token.value = in.data; + + maj_stat = gss_unwrap(&min_stat, + gensec_gsskrb5_state->gssapi_context, + &input_token, + &output_token, + &conf_state, + &qop_state); + if (GSS_ERROR(maj_stat)) { + return NT_STATUS_ACCESS_DENIED; + } + + if (output_token.length != length) { + return NT_STATUS_INTERNAL_ERROR; + } + + /*memcpy(data, output_token.value, length);*/ + + gss_release_buffer(&min_stat, &output_token); + + return NT_STATUS_OK; +} + +static BOOL gensec_gsskrb5_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_gsskrb5_state *gensec_gsskrb5_state = gensec_security->private_data; + if (feature & GENSEC_FEATURE_SIGN) { + return gensec_gsskrb5_state->got_flags & GSS_C_INTEG_FLAG; + } + if (feature & GENSEC_FEATURE_SEAL) { + return gensec_gsskrb5_state->got_flags & GSS_C_CONF_FLAG; + } + return False; +} + +static const struct gensec_security_ops gensec_gsskrb5_security_ops = { + .name = "gsskrb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = GENSEC_OID_KERBEROS5, + .client_start = gensec_gsskrb5_client_start, + .update = gensec_gsskrb5_update, + .sig_size = gensec_gsskrb5_sig_size, + .sign_packet = gensec_gsskrb5_sign_packet, + .check_packet = gensec_gsskrb5_check_packet, + .seal_packet = gensec_gsskrb5_seal_packet, + .unseal_packet = gensec_gsskrb5_unseal_packet, + .wrap = gensec_gsskrb5_wrap, + .unwrap = gensec_gsskrb5_unwrap, + .have_feature = gensec_gsskrb5_have_feature, + .enabled = False +}; + +NTSTATUS gensec_gsskrb5_init(void) +{ + NTSTATUS ret; + + ret = gensec_register(&gensec_gsskrb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_gsskrb5_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/gensec_krb5.c b/source4/auth/gensec/gensec_krb5.c new file mode 100644 index 0000000000..bad143f3c8 --- /dev/null +++ b/source4/auth/gensec/gensec_krb5.c @@ -0,0 +1,751 @@ +/* + Unix SMB/CIFS implementation. + + Kerberos backend for GENSEC + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/kerberos.h" +#include "system/time.h" +#include "auth/kerberos/kerberos.h" +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "auth/auth.h" + +enum GENSEC_KRB5_STATE { + GENSEC_KRB5_SERVER_START, + GENSEC_KRB5_CLIENT_START, + GENSEC_KRB5_CLIENT_MUTUAL_AUTH, + GENSEC_KRB5_DONE +}; + +struct gensec_krb5_state { + DATA_BLOB session_key; + DATA_BLOB pac; + enum GENSEC_KRB5_STATE state_position; + krb5_context context; + krb5_auth_context auth_context; + krb5_ccache ccache; + krb5_data ticket; + krb5_keyblock keyblock; + char *peer_principal; +}; + +#ifdef KRB5_DO_VERIFY_PAC +static NTSTATUS gensec_krb5_pac_checksum(DATA_BLOB pac_data, + struct PAC_SIGNATURE_DATA *sig, + struct gensec_krb5_state *gensec_krb5_state, + uint32 keyusage) +{ + krb5_error_code ret; + krb5_crypto crypto; + Checksum cksum; + int i; + + cksum.cksumtype = (CKSUMTYPE)sig->type; + cksum.checksum.length = sizeof(sig->signature); + cksum.checksum.data = sig->signature; + + + ret = krb5_crypto_init(gensec_krb5_state->context, + &gensec_krb5_state->keyblock, + 0, + &crypto); + if (ret) { + DEBUG(0,("krb5_crypto_init() failed\n")); + return NT_STATUS_FOOBAR; + } + for (i=0; i < 40; i++) { + keyusage = i; + ret = krb5_verify_checksum(gensec_krb5_state->context, + crypto, + keyusage, + pac_data.data, + pac_data.length, + &cksum); + if (!ret) { + DEBUG(0,("PAC Verified: keyusage: %d\n", keyusage)); + break; + } + } + krb5_crypto_destroy(gensec_krb5_state->context, crypto); + + if (ret) { + DEBUG(0,("NOT verifying PAC checksums yet!\n")); + //return NT_STATUS_LOGON_FAILURE; + } else { + DEBUG(0,("PAC checksums verified!\n")); + } + + return NT_STATUS_OK; +} +#endif + +static NTSTATUS gensec_krb5_decode_pac(TALLOC_CTX *mem_ctx, + struct PAC_LOGON_INFO **logon_info_out, + DATA_BLOB blob, + struct gensec_krb5_state *gensec_krb5_state) +{ + NTSTATUS status; + struct PAC_SIGNATURE_DATA srv_sig; + struct PAC_SIGNATURE_DATA *srv_sig_ptr; + struct PAC_SIGNATURE_DATA kdc_sig; + struct PAC_SIGNATURE_DATA *kdc_sig_ptr; + struct PAC_LOGON_INFO *logon_info = NULL; + struct PAC_DATA pac_data; +#ifdef KRB5_DO_VERIFY_PAC + DATA_BLOB tmp_blob = data_blob(NULL, 0); +#endif + int i; + + status = ndr_pull_struct_blob(&blob, mem_ctx, &pac_data, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("can't parse the PAC\n")); + return status; + } + NDR_PRINT_DEBUG(PAC_DATA, &pac_data); + + if (pac_data.num_buffers < 3) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 3 PAC buffers\n")); + return NT_STATUS_FOOBAR; + } + + for (i=0; i < pac_data.num_buffers; i++) { + switch (pac_data.buffers[i].type) { + case PAC_TYPE_LOGON_INFO: + if (!pac_data.buffers[i].info) { + break; + } + logon_info = &pac_data.buffers[i].info->logon_info; + break; + case PAC_TYPE_SRV_CHECKSUM: + if (!pac_data.buffers[i].info) { + break; + } + srv_sig_ptr = &pac_data.buffers[i].info->srv_cksum; + srv_sig = pac_data.buffers[i].info->srv_cksum; + break; + case PAC_TYPE_KDC_CHECKSUM: + if (!pac_data.buffers[i].info) { + break; + } + kdc_sig_ptr = &pac_data.buffers[i].info->kdc_cksum; + kdc_sig = pac_data.buffers[i].info->kdc_cksum; + break; + case PAC_TYPE_UNKNOWN_10: + break; + default: + break; + } + } + + if (!logon_info) { + DEBUG(0,("PAC no logon_info\n")); + return NT_STATUS_FOOBAR; + } + + if (!srv_sig_ptr) { + DEBUG(0,("PAC no srv_key\n")); + return NT_STATUS_FOOBAR; + } + + if (!kdc_sig_ptr) { + DEBUG(0,("PAC no kdc_key\n")); + return NT_STATUS_FOOBAR; + } +#ifdef KRB5_DO_VERIFY_PAC + /* clear the kdc_key */ +/* memset((void *)kdc_sig_ptr , '\0', sizeof(*kdc_sig_ptr));*/ + + status = ndr_push_struct_blob(&tmp_blob, mem_ctx, &pac_data, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = ndr_pull_struct_blob(&tmp_blob, mem_ctx, &pac_data, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("can't parse the PAC\n")); + return status; + } + /*NDR_PRINT_DEBUG(PAC_DATA, &pac_data);*/ + + /* verify by kdc_key */ + status = gensec_krb5_pac_checksum(tmp_blob, &kdc_sig, gensec_krb5_state, 0); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* clear the service_key */ +/* memset((void *)srv_sig_ptr , '\0', sizeof(*srv_sig_ptr));*/ + + status = ndr_push_struct_blob(&tmp_blob, mem_ctx, &pac_data, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + status = ndr_pull_struct_blob(&tmp_blob, mem_ctx, &pac_data, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("can't parse the PAC\n")); + return status; + } + NDR_PRINT_DEBUG(PAC_DATA, &pac_data); + + /* verify by servie_key */ + status = gensec_krb5_pac_checksum(tmp_blob, &srv_sig, gensec_krb5_state, 0); + + if (!NT_STATUS_IS_OK(status)) { + return status; + } +#endif + DEBUG(0,("account_name: %s [%s]\n",logon_info->info3.base.account_name.string, logon_info->info3.base.full_name.string)); + *logon_info_out = logon_info; + + return status; +} + +static int gensec_krb5_destory(void *ptr) +{ + struct gensec_krb5_state *gensec_krb5_state = ptr; + + if (gensec_krb5_state->ticket.length) { + kerberos_free_data_contents(gensec_krb5_state->context, &gensec_krb5_state->ticket); + } + if (gensec_krb5_state->ccache) { + /* current heimdal - 0.6.3, which we need anyway, fixes segfaults here */ + krb5_cc_close(gensec_krb5_state->context, gensec_krb5_state->ccache); + } + + krb5_free_keyblock_contents(gensec_krb5_state->context, + &gensec_krb5_state->keyblock); + + if (gensec_krb5_state->auth_context) { + krb5_auth_con_free(gensec_krb5_state->context, + gensec_krb5_state->auth_context); + } + + if (gensec_krb5_state->context) { + krb5_free_context(gensec_krb5_state->context); + } + return 0; +} + +static NTSTATUS gensec_krb5_start(struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state; + krb5_error_code ret = 0; + + gensec_krb5_state = talloc(gensec_security, struct gensec_krb5_state); + if (!gensec_krb5_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_krb5_state; + + initialize_krb5_error_table(); + gensec_krb5_state->context = NULL; + gensec_krb5_state->auth_context = NULL; + gensec_krb5_state->ccache = NULL; + ZERO_STRUCT(gensec_krb5_state->ticket); + ZERO_STRUCT(gensec_krb5_state->keyblock); + gensec_krb5_state->session_key = data_blob(NULL, 0); + gensec_krb5_state->pac = data_blob(NULL, 0); + + talloc_set_destructor(gensec_krb5_state, gensec_krb5_destory); + + ret = krb5_init_context(&gensec_krb5_state->context); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_init_context failed (%s)\n", error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + if (lp_realm() && *lp_realm()) { + ret = krb5_set_default_realm(gensec_krb5_state->context, lp_realm()); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_set_default_realm failed (%s)\n", error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + } + + ret = krb5_auth_con_init(gensec_krb5_state->context, &gensec_krb5_state->auth_context); + if (ret) { + DEBUG(1,("gensec_krb5_start: krb5_auth_con_init failed (%s)\n", error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct gensec_krb5_state *gensec_krb5_state; + + nt_status = gensec_krb5_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_krb5_state = gensec_security->private_data; + gensec_krb5_state->state_position = GENSEC_KRB5_SERVER_START; + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_krb5_client_start(struct gensec_security *gensec_security) +{ + struct gensec_krb5_state *gensec_krb5_state; + krb5_error_code ret; + NTSTATUS nt_status; + const char *hostname = gensec_get_target_hostname(gensec_security); + if (!hostname) { + DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n")); + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = gensec_krb5_start(gensec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + gensec_krb5_state = gensec_security->private_data; + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_START; + + /* TODO: This is effecivly a static/global variable... + + TODO: If the user set a username, we should use an in-memory CCACHE (see below) + */ + ret = krb5_cc_default(gensec_krb5_state->context, &gensec_krb5_state->ccache); + if (ret) { + DEBUG(1,("krb5_cc_default failed (%s)\n", + error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + while (1) { + { + krb5_data in_data; + in_data.length = 0; + + ret = krb5_mk_req(gensec_krb5_state->context, + &gensec_krb5_state->auth_context, + AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED, + gensec_get_target_service(gensec_security), + hostname, + &in_data, gensec_krb5_state->ccache, + &gensec_krb5_state->ticket); + + } + switch (ret) { + case 0: + return NT_STATUS_OK; + case KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN: + DEBUG(3, ("Server [%s] is not registered with our KDC: %s\n", + hostname, error_message(ret))); + return NT_STATUS_ACCESS_DENIED; + case KRB5KDC_ERR_PREAUTH_FAILED: + case KRB5KRB_AP_ERR_TKT_EXPIRED: + case KRB5_CC_END: + /* Too much clock skew - we will need to kinit to re-skew the clock */ + case KRB5KRB_AP_ERR_SKEW: + case KRB5_KDCREP_SKEW: + { + DEBUG(3, ("kerberos (mk_req) failed: %s\n", + error_message(ret))); + /* fall down to remaining code */ + } + + + /* just don't print a message for these really ordinary messages */ + case KRB5_FCC_NOFILE: + case KRB5_CC_NOTFOUND: + case ENOENT: + + { + const char *password; + char *ccache_string; + time_t kdc_time = 0; + password = cli_credentials_get_password(gensec_security->credentials); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + /* this string should be unique */ + ccache_string = talloc_asprintf(gensec_krb5_state, "MEMORY:%s:%s:%s", + cli_credentials_get_principal(gensec_security->credentials, gensec_krb5_state), + gensec_get_target_hostname(gensec_security), + generate_random_str(gensec_krb5_state, 16)); + + ret = krb5_cc_resolve(gensec_krb5_state->context, ccache_string, &gensec_krb5_state->ccache); + if (ret) { + DEBUG(1,("failed to generate a new krb5 keytab (%s): %s\n", + ccache_string, + error_message(ret))); + return NT_STATUS_INTERNAL_ERROR; + } + + ret = kerberos_kinit_password_cc(gensec_krb5_state->context, gensec_krb5_state->ccache, + cli_credentials_get_principal(gensec_security->credentials, gensec_krb5_state), + password, NULL, &kdc_time); + + /* cope with ticket being in the future due to clock skew */ + if ((unsigned)kdc_time > time(NULL)) { + time_t t = time(NULL); + int time_offset =(unsigned)kdc_time-t; + DEBUG(4,("Advancing clock by %d seconds to cope with clock skew\n", time_offset)); + krb5_set_real_time(gensec_krb5_state->context, t + time_offset + 1, 0); + break; + } + + if (ret == KRB5KRB_AP_ERR_SKEW || ret == KRB5_KDCREP_SKEW) { + DEBUG(1,("kinit for %s failed (%s)\n", + cli_credentials_get_principal(gensec_security->credentials, gensec_krb5_state), + error_message(ret))); + return NT_STATUS_TIME_DIFFERENCE_AT_DC; + } + if (ret) { + DEBUG(1,("kinit for %s failed (%s)\n", + cli_credentials_get_principal(gensec_security->credentials, gensec_krb5_state), + error_message(ret))); + return NT_STATUS_WRONG_PASSWORD; + } + break; + } + default: + DEBUG(0, ("kerberos: %s\n", + error_message(ret))); + return NT_STATUS_UNSUCCESSFUL; + } + } +} + + +/** + * Next state function for the Krb5 GENSEC mechanism + * + * @param gensec_krb5_state KRB5 State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ + +static NTSTATUS gensec_krb5_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + krb5_error_code ret = 0; + DATA_BLOB pac; + NTSTATUS nt_status; + + switch (gensec_krb5_state->state_position) { + case GENSEC_KRB5_CLIENT_START: + { + if (ret) { + DEBUG(1,("ads_krb5_mk_req (request ticket) failed (%s)\n", + error_message(ret))); + nt_status = NT_STATUS_LOGON_FAILURE; + } else { + DATA_BLOB unwrapped_out; + +#ifndef GENSEC_SEND_UNWRAPPED_KRB5 /* This should be a switch for the torture code to set */ + unwrapped_out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length); + + /* wrap that up in a nice GSS-API wrapping */ + *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REQ); +#else + *out = data_blob_talloc(out_mem_ctx, gensec_krb5_state->ticket.data, gensec_krb5_state->ticket.length); +#endif + gensec_krb5_state->state_position = GENSEC_KRB5_CLIENT_MUTUAL_AUTH; + nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + return nt_status; + } + + case GENSEC_KRB5_CLIENT_MUTUAL_AUTH: + { + krb5_data inbuf; + krb5_ap_rep_enc_part *repl = NULL; + uint8_t tok_id[2]; + DATA_BLOB unwrapped_in; + + if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) { + DEBUG(1,("gensec_gssapi_parse_krb5_wrap(mutual authentication) failed to parse\n")); + dump_data_pw("Mutual authentication message:\n", in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + /* TODO: check the tok_id */ + + inbuf.data = unwrapped_in.data; + inbuf.length = unwrapped_in.length; + ret = krb5_rd_rep(gensec_krb5_state->context, + gensec_krb5_state->auth_context, + &inbuf, &repl); + if (ret) { + DEBUG(1,("krb5_rd_rep (mutual authentication) failed (%s)\n", + error_message(ret))); + dump_data_pw("Mutual authentication message:\n", inbuf.data, inbuf.length); + nt_status = NT_STATUS_ACCESS_DENIED; + } else { + *out = data_blob(NULL, 0); + nt_status = NT_STATUS_OK; + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + } + if (repl) { + krb5_free_ap_rep_enc_part(gensec_krb5_state->context, repl); + } + return nt_status; + } + + case GENSEC_KRB5_SERVER_START: + { + char *principal; + DATA_BLOB unwrapped_in; + DATA_BLOB unwrapped_out = data_blob(NULL, 0); + uint8_t tok_id[2]; + + if (!in.data) { + *out = unwrapped_out; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + /* Parse the GSSAPI wrapping, if it's there... (win2k3 allows it to be omited) */ + if (!gensec_gssapi_parse_krb5_wrap(out_mem_ctx, &in, &unwrapped_in, tok_id)) { + nt_status = ads_verify_ticket(out_mem_ctx, + gensec_krb5_state->context, + gensec_krb5_state->auth_context, + lp_realm(), + gensec_get_target_service(gensec_security), &in, + &principal, &pac, &unwrapped_out, + &gensec_krb5_state->keyblock); + } else { + /* TODO: check the tok_id */ + nt_status = ads_verify_ticket(out_mem_ctx, + gensec_krb5_state->context, + gensec_krb5_state->auth_context, + lp_realm(), + gensec_get_target_service(gensec_security), + &unwrapped_in, + &principal, &pac, &unwrapped_out, + &gensec_krb5_state->keyblock); + } + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (pac.data) { + gensec_krb5_state->pac = data_blob_talloc_reference(gensec_krb5_state, &pac); + } + + if (NT_STATUS_IS_OK(nt_status)) { + gensec_krb5_state->state_position = GENSEC_KRB5_DONE; + /* wrap that up in a nice GSS-API wrapping */ +#ifndef GENSEC_SEND_UNWRAPPED_KRB5 + *out = gensec_gssapi_gen_krb5_wrap(out_mem_ctx, &unwrapped_out, TOK_ID_KRB_AP_REP); +#else + *out = unwrapped_out; +#endif + gensec_krb5_state->peer_principal = talloc_steal(gensec_krb5_state, principal); + } + return nt_status; + } + case GENSEC_KRB5_DONE: + return NT_STATUS_OK; + } + + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_krb5_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) +{ + struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + krb5_context context = gensec_krb5_state->context; + krb5_auth_context auth_context = gensec_krb5_state->auth_context; + krb5_keyblock *skey; + krb5_error_code err; + + if (gensec_krb5_state->session_key.data) { + *session_key = gensec_krb5_state->session_key; + return NT_STATUS_OK; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + err = krb5_auth_con_getlocalsubkey(context, auth_context, &skey); + break; + case GENSEC_SERVER: + err = krb5_auth_con_getremotesubkey(context, auth_context, &skey); + break; + } + if (err == 0 && skey != NULL) { + DEBUG(10, ("Got KRB5 session key of length %d\n", KRB5_KEY_LENGTH(skey))); + gensec_krb5_state->session_key = data_blob_talloc(gensec_krb5_state, + KRB5_KEY_DATA(skey), KRB5_KEY_LENGTH(skey)); + *session_key = gensec_krb5_state->session_key; + dump_data_pw("KRB5 Session Key:\n", session_key->data, session_key->length); + + krb5_free_keyblock(context, skey); + return NT_STATUS_OK; + } else { + DEBUG(10, ("KRB5 error getting session key %d\n", err)); + return NT_STATUS_NO_USER_SESSION_KEY; + } +} + +static NTSTATUS gensec_krb5_session_info(struct gensec_security *gensec_security, + struct auth_session_info **_session_info) +{ + NTSTATUS nt_status; + struct gensec_krb5_state *gensec_krb5_state = gensec_security->private_data; + struct auth_serversupplied_info *server_info = NULL; + struct auth_session_info *session_info = NULL; + struct PAC_LOGON_INFO *logon_info; + char *p; + char *principal; + const char *account_name; + const char *realm; + + principal = talloc_strdup(gensec_krb5_state, gensec_krb5_state->peer_principal); + NT_STATUS_HAVE_NO_MEMORY(principal); + + p = strchr(principal, '@'); + if (p) { + *p = '\0'; + p++; + realm = p; + } else { + realm = lp_realm(); + } + account_name = principal; + + /* decode and verify the pac */ + nt_status = gensec_krb5_decode_pac(gensec_krb5_state, &logon_info, gensec_krb5_state->pac, + gensec_krb5_state); + + /* IF we have the PAC - otherwise we need to get this + * data from elsewere - local ldb, or (TODO) lookup of some + * kind... + * + * when heimdal can generate the PAC, we should fail if there's + * no PAC present + */ + + if (NT_STATUS_IS_OK(nt_status)) { + union netr_Validation validation; + validation.sam3 = &logon_info->info3; + nt_status = make_server_info_netlogon_validation(gensec_krb5_state, + account_name, + 3, &validation, + &server_info); + talloc_free(principal); + NT_STATUS_NOT_OK_RETURN(nt_status); + } else { + DATA_BLOB user_sess_key = data_blob(NULL, 0); + DATA_BLOB lm_sess_key = data_blob(NULL, 0); + /* TODO: should we pass the krb5 session key in here? */ + nt_status = sam_get_server_info(gensec_krb5_state, account_name, realm, + user_sess_key, lm_sess_key, + &server_info); + talloc_free(principal); + NT_STATUS_NOT_OK_RETURN(nt_status); + } + + /* references the server_info into the session_info */ + nt_status = auth_generate_session_info(gensec_krb5_state, server_info, &session_info); + talloc_free(server_info); + NT_STATUS_NOT_OK_RETURN(nt_status); + + nt_status = gensec_krb5_session_key(gensec_security, &session_info->session_key); + NT_STATUS_NOT_OK_RETURN(nt_status); + + *_session_info = session_info; + + return NT_STATUS_OK; +} + +static BOOL gensec_krb5_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (feature & GENSEC_FEATURE_SESSION_KEY) { + return True; + } + + return False; +} + + +static const struct gensec_security_ops gensec_krb5_security_ops = { + .name = "krb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = GENSEC_OID_KERBEROS5, + .client_start = gensec_krb5_client_start, + .server_start = gensec_krb5_server_start, + .update = gensec_krb5_update, + .session_key = gensec_krb5_session_key, + .session_info = gensec_krb5_session_info, + .have_feature = gensec_krb5_have_feature, + .enabled = False +}; + +static const struct gensec_security_ops gensec_ms_krb5_security_ops = { + .name = "ms_krb5", + .auth_type = DCERPC_AUTH_TYPE_KRB5, + .oid = GENSEC_OID_KERBEROS5_OLD, + .client_start = gensec_krb5_client_start, + .server_start = gensec_krb5_server_start, + .update = gensec_krb5_update, + .session_key = gensec_krb5_session_key, + .session_info = gensec_krb5_session_info, + .have_feature = gensec_krb5_have_feature, + .enabled = False +}; + + +NTSTATUS gensec_krb5_init(void) +{ + NTSTATUS ret; + + ret = gensec_register(&gensec_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_krb5_security_ops.name)); + return ret; + } + + ret = gensec_register(&gensec_ms_krb5_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_krb5_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/gensec_ntlmssp.c b/source4/auth/gensec/gensec_ntlmssp.c new file mode 100644 index 0000000000..5955904886 --- /dev/null +++ b/source4/auth/gensec/gensec_ntlmssp.c @@ -0,0 +1,514 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc authentication operations + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Stefan Metzmacher 2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "auth/auth.h" + +struct gensec_ntlmssp_state { + struct auth_context *auth_context; + struct auth_serversupplied_info *server_info; + struct ntlmssp_state *ntlmssp_state; + uint32_t have_features; +}; + + +/** + * Return the challenge as determined by the authentication subsystem + * @return an 8 byte random challenge + */ + +static const uint8_t *auth_ntlmssp_get_challenge(const struct ntlmssp_state *ntlmssp_state) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = ntlmssp_state->auth_context; + NTSTATUS status; + const uint8_t *chal; + + status = auth_get_challenge(gensec_ntlmssp_state->auth_context, &chal); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + + return chal; +} + +/** + * Some authentication methods 'fix' the challenge, so we may not be able to set it + * + * @return If the effective challenge used by the auth subsystem may be modified + */ +static BOOL auth_ntlmssp_may_set_challenge(const struct ntlmssp_state *ntlmssp_state) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = ntlmssp_state->auth_context; + + return auth_challenge_may_be_modified(gensec_ntlmssp_state->auth_context); +} + +/** + * NTLM2 authentication modifies the effective challenge, + * @param challenge The new challenge value + */ +static NTSTATUS auth_ntlmssp_set_challenge(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *challenge) +{ + NTSTATUS nt_status; + struct gensec_ntlmssp_state *gensec_ntlmssp_state = ntlmssp_state->auth_context; + struct auth_context *auth_context = gensec_ntlmssp_state->auth_context; + const uint8_t *chal; + + if (challenge->length != 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + chal = challenge->data; + + nt_status = auth_context_set_challenge(auth_context, chal, "NTLMSSP callback (NTLM2)"); + + return nt_status; +} + +/** + * Check the password on an NTLMSSP login. + * + * Return the session keys used on the connection. + */ + +static NTSTATUS auth_ntlmssp_check_password(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *user_session_key, DATA_BLOB *lm_session_key) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = ntlmssp_state->auth_context; + struct auth_usersupplied_info *user_info = NULL; + NTSTATUS nt_status; + + nt_status = make_user_info_map(ntlmssp_state, + gensec_ntlmssp_state->ntlmssp_state->user, + gensec_ntlmssp_state->ntlmssp_state->domain, + gensec_ntlmssp_state->ntlmssp_state->workstation, + gensec_ntlmssp_state->ntlmssp_state->lm_resp.data ? &gensec_ntlmssp_state->ntlmssp_state->lm_resp : NULL, + gensec_ntlmssp_state->ntlmssp_state->nt_resp.data ? &gensec_ntlmssp_state->ntlmssp_state->nt_resp : NULL, + NULL, NULL, NULL, True, + &user_info); + NT_STATUS_NOT_OK_RETURN(nt_status); + + nt_status = auth_check_password(gensec_ntlmssp_state->auth_context, gensec_ntlmssp_state, + user_info, &gensec_ntlmssp_state->server_info); + talloc_free(user_info); + NT_STATUS_NOT_OK_RETURN(nt_status); + + if (gensec_ntlmssp_state->server_info->user_session_key.length) { + DEBUG(10, ("Got NT session key of length %u\n", gensec_ntlmssp_state->server_info->user_session_key.length)); + *user_session_key = data_blob_talloc(ntlmssp_state, + gensec_ntlmssp_state->server_info->user_session_key.data, + gensec_ntlmssp_state->server_info->user_session_key.length); + } + if (gensec_ntlmssp_state->server_info->lm_session_key.length) { + DEBUG(10, ("Got LM session key of length %u\n", gensec_ntlmssp_state->server_info->lm_session_key.length)); + *lm_session_key = data_blob_talloc(ntlmssp_state, + gensec_ntlmssp_state->server_info->lm_session_key.data, + gensec_ntlmssp_state->server_info->lm_session_key.length); + } + return nt_status; +} + +static int gensec_ntlmssp_destroy(void *ptr) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = ptr; + + if (gensec_ntlmssp_state->ntlmssp_state) { + ntlmssp_end(&gensec_ntlmssp_state->ntlmssp_state); + } + + return 0; +} + +static NTSTATUS gensec_ntlmssp_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state; + + gensec_ntlmssp_state = talloc(gensec_security, struct gensec_ntlmssp_state); + if (!gensec_ntlmssp_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_ntlmssp_state->ntlmssp_state = NULL; + gensec_ntlmssp_state->auth_context = NULL; + gensec_ntlmssp_state->server_info = NULL; + gensec_ntlmssp_state->have_features = 0; + + talloc_set_destructor(gensec_ntlmssp_state, gensec_ntlmssp_destroy); + + gensec_security->private_data = gensec_ntlmssp_state; + return NT_STATUS_OK; +} + +static NTSTATUS gensec_ntlmssp_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct ntlmssp_state *ntlmssp_state; + struct gensec_ntlmssp_state *gensec_ntlmssp_state; + + nt_status = gensec_ntlmssp_start(gensec_security); + NT_STATUS_NOT_OK_RETURN(nt_status); + + gensec_ntlmssp_state = gensec_security->private_data; + + nt_status = ntlmssp_server_start(gensec_ntlmssp_state, &gensec_ntlmssp_state->ntlmssp_state); + NT_STATUS_NOT_OK_RETURN(nt_status); + + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + gensec_ntlmssp_state->ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + gensec_ntlmssp_state->ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + + nt_status = auth_context_create(gensec_ntlmssp_state, lp_auth_methods(), &gensec_ntlmssp_state->auth_context); + NT_STATUS_NOT_OK_RETURN(nt_status); + + ntlmssp_state = gensec_ntlmssp_state->ntlmssp_state; + ntlmssp_state->auth_context = gensec_ntlmssp_state; + ntlmssp_state->get_challenge = auth_ntlmssp_get_challenge; + ntlmssp_state->may_set_challenge = auth_ntlmssp_may_set_challenge; + ntlmssp_state->set_challenge = auth_ntlmssp_set_challenge; + ntlmssp_state->check_password = auth_ntlmssp_check_password; + ntlmssp_state->server_role = lp_server_role(); + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_ntlmssp_client_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state; + const char *password = NULL; + NTSTATUS nt_status; + + nt_status = gensec_ntlmssp_start(gensec_security); + NT_STATUS_NOT_OK_RETURN(nt_status); + + gensec_ntlmssp_state = gensec_security->private_data; + nt_status = ntlmssp_client_start(gensec_ntlmssp_state, + &gensec_ntlmssp_state->ntlmssp_state); + NT_STATUS_NOT_OK_RETURN(nt_status); + + if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) { + /* + * We need to set this to allow a later SetPassword + * via the SAMR pipe to succeed. Strange.... We could + * also add NTLMSSP_NEGOTIATE_SEAL here. JRA. + * + * Without this, Windows will not create the master key + * that it thinks is only used for NTLMSSP signing and + * sealing. (It is actually pulled out and used directly) + */ + gensec_ntlmssp_state->ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + gensec_ntlmssp_state->ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + gensec_ntlmssp_state->ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + + nt_status = ntlmssp_set_domain(gensec_ntlmssp_state->ntlmssp_state, + cli_credentials_get_domain(gensec_security->credentials)); + NT_STATUS_NOT_OK_RETURN(nt_status); + + nt_status = ntlmssp_set_username(gensec_ntlmssp_state->ntlmssp_state, + cli_credentials_get_username(gensec_security->credentials)); + NT_STATUS_NOT_OK_RETURN(nt_status); + + password = cli_credentials_get_password(gensec_security->credentials); + + nt_status = ntlmssp_set_password(gensec_ntlmssp_state->ntlmssp_state, password); + NT_STATUS_NOT_OK_RETURN(nt_status); + + nt_status = ntlmssp_set_workstation(gensec_ntlmssp_state->ntlmssp_state, + cli_credentials_get_workstation(gensec_security->credentials)); + + gensec_security->private_data = gensec_ntlmssp_state; + + return NT_STATUS_OK; +} + +/* + wrappers for the ntlmssp_*() functions +*/ +static NTSTATUS gensec_ntlmssp_unseal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + + return ntlmssp_unseal_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +static NTSTATUS gensec_ntlmssp_check_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + + return ntlmssp_check_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +static NTSTATUS gensec_ntlmssp_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + + return ntlmssp_seal_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +static NTSTATUS gensec_ntlmssp_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + + return ntlmssp_sign_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +static size_t gensec_ntlmssp_sig_size(struct gensec_security *gensec_security) +{ + return NTLMSSP_SIG_SIZE; +} + +static NTSTATUS gensec_ntlmssp_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + DATA_BLOB sig; + NTSTATUS nt_status; + + if (gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + + *out = data_blob_talloc(mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE); + memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length); + + nt_status = ntlmssp_seal_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + &sig); + + if (NT_STATUS_IS_OK(nt_status)) { + memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE); + } + return nt_status; + + } else if ((gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) + || (gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)) { + + *out = data_blob_talloc(mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE); + memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length); + + nt_status = ntlmssp_sign_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + &sig); + + if (NT_STATUS_IS_OK(nt_status)) { + memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE); + } + return nt_status; + + } else { + *out = *in; + return NT_STATUS_OK; + } +} + + +static NTSTATUS gensec_ntlmssp_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + DATA_BLOB sig; + + if (gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + if (in->length < NTLMSSP_SIG_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + sig.data = in->data; + sig.length = NTLMSSP_SIG_SIZE; + + *out = data_blob_talloc(mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE); + + return ntlmssp_unseal_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, + out->data, out->length, + out->data, out->length, + &sig); + + } else if ((gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) + || (gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)) { + if (in->length < NTLMSSP_SIG_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + sig.data = in->data; + sig.length = NTLMSSP_SIG_SIZE; + + *out = data_blob_talloc(mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE); + + return ntlmssp_check_packet(gensec_ntlmssp_state->ntlmssp_state, mem_ctx, + out->data, out->length, + out->data, out->length, + &sig); + } else { + *out = *in; + return NT_STATUS_OK; + } +} + +static NTSTATUS gensec_ntlmssp_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + + return ntlmssp_session_key(gensec_ntlmssp_state->ntlmssp_state, session_key); +} + +/** + * Next state function for the wrapped NTLMSSP state machine + * + * @param gensec_security GENSEC state, initialised to NTLMSSP + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ + +static NTSTATUS gensec_ntlmssp_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + NTSTATUS status; + + status = ntlmssp_update(gensec_ntlmssp_state->ntlmssp_state, out_mem_ctx, in, out); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(status)) { + return status; + } + + gensec_ntlmssp_state->have_features = 0; + + if (gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + gensec_ntlmssp_state->have_features |= GENSEC_FEATURE_SIGN; + } + + if (gensec_ntlmssp_state->ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + gensec_ntlmssp_state->have_features |= GENSEC_FEATURE_SEAL; + } + + if (gensec_ntlmssp_state->ntlmssp_state->session_key.data) { + gensec_ntlmssp_state->have_features |= GENSEC_FEATURE_SESSION_KEY; + } + + return status; +} + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ + +static NTSTATUS gensec_ntlmssp_session_info(struct gensec_security *gensec_security, + struct auth_session_info **session_info) +{ + NTSTATUS nt_status; + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + + nt_status = auth_generate_session_info(gensec_ntlmssp_state, gensec_ntlmssp_state->server_info, session_info); + NT_STATUS_NOT_OK_RETURN(nt_status); + + (*session_info)->session_key = data_blob_talloc(*session_info, + gensec_ntlmssp_state->ntlmssp_state->session_key.data, + gensec_ntlmssp_state->ntlmssp_state->session_key.length); + + return NT_STATUS_OK; +} + +static BOOL gensec_ntlmssp_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_ntlmssp_state *gensec_ntlmssp_state = gensec_security->private_data; + if (gensec_ntlmssp_state->have_features & feature) { + return True; + } + + return False; +} + +static const struct gensec_security_ops gensec_ntlmssp_security_ops = { + .name = "ntlmssp", + .sasl_name = "NTLM", + .auth_type = DCERPC_AUTH_TYPE_NTLMSSP, + .oid = GENSEC_OID_NTLMSSP, + .client_start = gensec_ntlmssp_client_start, + .server_start = gensec_ntlmssp_server_start, + .update = gensec_ntlmssp_update, + .sig_size = gensec_ntlmssp_sig_size, + .sign_packet = gensec_ntlmssp_sign_packet, + .check_packet = gensec_ntlmssp_check_packet, + .seal_packet = gensec_ntlmssp_seal_packet, + .unseal_packet = gensec_ntlmssp_unseal_packet, + .wrap = gensec_ntlmssp_wrap, + .unwrap = gensec_ntlmssp_unwrap, + .session_key = gensec_ntlmssp_session_key, + .session_info = gensec_ntlmssp_session_info, + .have_feature = gensec_ntlmssp_have_feature, + .enabled = True +}; + + +NTSTATUS gensec_ntlmssp_init(void) +{ + NTSTATUS ret; + ret = gensec_register(&gensec_ntlmssp_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_ntlmssp_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/ntlmssp.c b/source4/auth/gensec/ntlmssp.c new file mode 100644 index 0000000000..37374d9d39 --- /dev/null +++ b/source4/auth/gensec/ntlmssp.c @@ -0,0 +1,1322 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "auth/auth.h" +#include "lib/crypto/crypto.h" +#include "pstring.h" + +static NTSTATUS ntlmssp_client_initial(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out); +static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); +static NTSTATUS ntlmssp_client_challenge(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); +static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); + +/** + * Callbacks for NTLMSSP - for both client and server operating modes + * + */ + +static const struct ntlmssp_callbacks { + enum ntlmssp_role role; + enum ntlmssp_message_type ntlmssp_command; + NTSTATUS (*fn)(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out); +} ntlmssp_callbacks[] = { + {NTLMSSP_CLIENT, NTLMSSP_INITIAL, ntlmssp_client_initial}, + {NTLMSSP_SERVER, NTLMSSP_NEGOTIATE, ntlmssp_server_negotiate}, + {NTLMSSP_CLIENT, NTLMSSP_CHALLENGE, ntlmssp_client_challenge}, + {NTLMSSP_SERVER, NTLMSSP_AUTH, ntlmssp_server_auth}, + {NTLMSSP_CLIENT, NTLMSSP_UNKNOWN, NULL}, + {NTLMSSP_SERVER, NTLMSSP_UNKNOWN, NULL} +}; + + +/** + * Print out the NTLMSSP flags for debugging + * @param neg_flags The flags from the packet + */ + +void debug_ntlmssp_flags(uint32_t neg_flags) +{ + DEBUG(3,("Got NTLMSSP neg_flags=0x%08x\n", neg_flags)); + + if (neg_flags & NTLMSSP_NEGOTIATE_UNICODE) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_UNICODE\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_OEM) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_OEM\n")); + if (neg_flags & NTLMSSP_REQUEST_TARGET) + DEBUGADD(4, (" NTLMSSP_REQUEST_TARGET\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_SIGN) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_SIGN\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_SEAL) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_SEAL\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_LM_KEY\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_NETWARE) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NETWARE\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_NTLM) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NTLM\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_ALWAYS_SIGN\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_NTLM2) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_NTLM2\n")); + if (neg_flags & NTLMSSP_CHAL_TARGET_INFO) + DEBUGADD(4, (" NTLMSSP_CHAL_TARGET_INFO\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_128) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_128\n")); + if (neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) + DEBUGADD(4, (" NTLMSSP_NEGOTIATE_KEY_EXCH\n")); +} + +/** + * Default challenge generation code. + * + */ + +static const uint8_t *get_challenge(const struct ntlmssp_state *ntlmssp_state) +{ + uint8_t *chal = talloc_size(ntlmssp_state, 8); + generate_random_buffer(chal, 8); + + return chal; +} + +/** + * Default 'we can set the challenge to anything we like' implementation + * + */ + +static BOOL may_set_challenge(const struct ntlmssp_state *ntlmssp_state) +{ + return True; +} + +/** + * Default 'we can set the challenge to anything we like' implementation + * + * Does not actually do anything, as the value is always in the structure anyway. + * + */ + +static NTSTATUS set_challenge(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *challenge) +{ + SMB_ASSERT(challenge->length == 8); + return NT_STATUS_OK; +} + +/** + * Set a username on an NTLMSSP context - ensures it is talloc()ed + * + */ + +NTSTATUS ntlmssp_set_username(struct ntlmssp_state *ntlmssp_state, const char *user) +{ + if (!user) { + /* it should be at least "" */ + DEBUG(1, ("NTLMSSP failed to set username - cannot accept NULL username\n")); + return NT_STATUS_INVALID_PARAMETER; + } + ntlmssp_state->user = talloc_strdup(ntlmssp_state, user); + if (!ntlmssp_state->user) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Set a password on an NTLMSSP context - ensures it is talloc()ed + * + */ +NTSTATUS ntlmssp_set_password(struct ntlmssp_state *ntlmssp_state, const char *password) +{ + if (!password) { + ntlmssp_state->password = NULL; + } else { + ntlmssp_state->password = talloc_strdup(ntlmssp_state, password); + if (!ntlmssp_state->password) { + return NT_STATUS_NO_MEMORY; + } + } + return NT_STATUS_OK; +} + +/** + * Set a domain on an NTLMSSP context - ensures it is talloc()ed + * + */ +NTSTATUS ntlmssp_set_domain(struct ntlmssp_state *ntlmssp_state, const char *domain) +{ + ntlmssp_state->domain = talloc_strdup(ntlmssp_state, domain); + if (!ntlmssp_state->domain) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Set a workstation on an NTLMSSP context - ensures it is talloc()ed + * + */ +NTSTATUS ntlmssp_set_workstation(struct ntlmssp_state *ntlmssp_state, const char *workstation) +{ + ntlmssp_state->workstation = talloc_strdup(ntlmssp_state, workstation); + if (!ntlmssp_state->workstation) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +/** + * Store a DATA_BLOB containing an NTLMSSP response, for use later. + * This copies the data blob + */ + +NTSTATUS ntlmssp_store_response(struct ntlmssp_state *ntlmssp_state, + DATA_BLOB response) +{ + ntlmssp_state->stored_response = data_blob_talloc(ntlmssp_state, + response.data, response.length); + return NT_STATUS_OK; +} + +/** + * Next state function for the NTLMSSP state machine + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ + +NTSTATUS ntlmssp_update(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + DATA_BLOB input; + uint32_t ntlmssp_command; + int i; + + *out = data_blob(NULL, 0); + + if (ntlmssp_state->expected_state == NTLMSSP_DONE) { + return NT_STATUS_OK; + } + + if (!out_mem_ctx) { + /* if the caller doesn't want to manage/own the memory, + we can put it on our context */ + out_mem_ctx = ntlmssp_state; + } + + if (!in.length && ntlmssp_state->stored_response.length) { + input = ntlmssp_state->stored_response; + + /* we only want to read the stored response once - overwrite it */ + ntlmssp_state->stored_response = data_blob(NULL, 0); + } else { + input = in; + } + + if (!input.length) { + switch (ntlmssp_state->role) { + case NTLMSSP_CLIENT: + ntlmssp_command = NTLMSSP_INITIAL; + break; + case NTLMSSP_SERVER: + /* 'datagram' mode - no neg packet */ + ntlmssp_command = NTLMSSP_NEGOTIATE; + break; + } + } else { + if (!msrpc_parse(ntlmssp_state, + &input, "Cd", + "NTLMSSP", + &ntlmssp_command)) { + DEBUG(1, ("Failed to parse NTLMSSP packet, could not extract NTLMSSP command\n")); + dump_data(2, input.data, input.length); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (ntlmssp_command != ntlmssp_state->expected_state) { + DEBUG(1, ("got NTLMSSP command %u, expected %u\n", ntlmssp_command, ntlmssp_state->expected_state)); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; ntlmssp_callbacks[i].fn; i++) { + if (ntlmssp_callbacks[i].role == ntlmssp_state->role + && ntlmssp_callbacks[i].ntlmssp_command == ntlmssp_command + && ntlmssp_callbacks[i].fn) { + return ntlmssp_callbacks[i].fn(ntlmssp_state, out_mem_ctx, input, out); + } + } + + DEBUG(1, ("failed to find NTLMSSP callback for NTLMSSP mode %u, command %u\n", + ntlmssp_state->role, ntlmssp_command)); + + return NT_STATUS_INVALID_PARAMETER; +} + +/** + * Return the NTLMSSP master session key + * + * @param ntlmssp_state NTLMSSP State + */ + +NTSTATUS ntlmssp_session_key(struct ntlmssp_state *ntlmssp_state, + DATA_BLOB *session_key) +{ + if (!ntlmssp_state->session_key.data) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + *session_key = ntlmssp_state->session_key; + + return NT_STATUS_OK; +} + +/** + * End an NTLMSSP state machine + * + * @param ntlmssp_state NTLMSSP State, free()ed by this function + */ + +void ntlmssp_end(struct ntlmssp_state **ntlmssp_state) +{ + (*ntlmssp_state)->ref_count--; + + if ((*ntlmssp_state)->ref_count == 0) { + talloc_free(*ntlmssp_state); + } + + *ntlmssp_state = NULL; + return; +} + +/** + * Determine correct target name flags for reply, given server role + * and negotiated flags + * + * @param ntlmssp_state NTLMSSP State + * @param neg_flags The flags from the packet + * @param chal_flags The flags to be set in the reply packet + * @return The 'target name' string. + */ + +static const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, uint32_t *chal_flags) +{ + if (neg_flags & NTLMSSP_REQUEST_TARGET) { + *chal_flags |= NTLMSSP_CHAL_TARGET_INFO; + *chal_flags |= NTLMSSP_REQUEST_TARGET; + if (ntlmssp_state->server_role == ROLE_STANDALONE) { + *chal_flags |= NTLMSSP_TARGET_TYPE_SERVER; + return ntlmssp_state->server_name; + } else { + *chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN; + return ntlmssp_state->get_domain(); + }; + } else { + return ""; + } +} + +static void ntlmssp_handle_neg_flags(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, BOOL allow_lm) { + if (neg_flags & NTLMSSP_NEGOTIATE_UNICODE) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = True; + } else { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = False; + } + + if ((neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) && allow_lm && !ntlmssp_state->use_ntlmv2) { + /* other end forcing us to use LM */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } else { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (neg_flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_SEAL)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_NTLM2)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_128)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_128; + if (neg_flags & NTLMSSP_NEGOTIATE_56) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56; + } + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_56)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_56; + } + + if (!(neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if ((neg_flags & NTLMSSP_REQUEST_TARGET)) { + ntlmssp_state->neg_flags |= NTLMSSP_REQUEST_TARGET; + } + +} + +/** + Weaken NTLMSSP keys to cope with down-level clients and servers. + + We probably should have some parameters to control this, but as + it only occours for LM_KEY connections, and this is controlled + by the client lanman auth/lanman auth parameters, it isn't too bad. +*/ + +static void ntlmssp_weaken_keys(struct ntlmssp_state *ntlmssp_state) { + /* Key weakening not performed on the master key for NTLM2 + and does not occour for NTLM1. Therefore we only need + to do this for the LM_KEY. + */ + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) { + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) { + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + ntlmssp_state->session_key.data[7] = 0xa0; + } else { /* forty bits */ + ntlmssp_state->session_key.data[5] = 0xe5; + ntlmssp_state->session_key.data[6] = 0x38; + ntlmssp_state->session_key.data[7] = 0xb0; + } + ntlmssp_state->session_key.length = 8; + } +} + +/** + * Next state function for the Negotiate packet + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Errors or MORE_PROCESSING_REQUIRED if a reply is sent. + */ + +static NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + DATA_BLOB struct_blob; + fstring dnsname, dnsdomname; + uint32_t neg_flags = 0; + uint32_t ntlmssp_command, chal_flags; + char *cliname=NULL, *domname=NULL; + const uint8_t *cryptkey; + const char *target_name; + + /* parse the NTLMSSP packet */ +#if 0 + file_save("ntlmssp_negotiate.dat", request.data, request.length); +#endif + + if (in.length) { + if (!msrpc_parse(ntlmssp_state, + &in, "CddAA", + "NTLMSSP", + &ntlmssp_command, + &neg_flags, + &cliname, + &domname)) { + DEBUG(1, ("ntlmssp_server_negotiate: failed to parse NTLMSSP:\n")); + dump_data(2, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + debug_ntlmssp_flags(neg_flags); + } + + ntlmssp_handle_neg_flags(ntlmssp_state, neg_flags, ntlmssp_state->allow_lm_key); + + /* Ask our caller what challenge they would like in the packet */ + cryptkey = ntlmssp_state->get_challenge(ntlmssp_state); + + /* Check if we may set the challenge */ + if (!ntlmssp_state->may_set_challenge(ntlmssp_state)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + /* The flags we send back are not just the negotiated flags, + * they are also 'what is in this packet'. Therfore, we + * operate on 'chal_flags' from here on + */ + + chal_flags = ntlmssp_state->neg_flags; + + /* get the right name to fill in as 'target' */ + target_name = ntlmssp_target_name(ntlmssp_state, + neg_flags, &chal_flags); + if (target_name == NULL) + return NT_STATUS_INVALID_PARAMETER; + + ntlmssp_state->chal = data_blob_talloc(ntlmssp_state, cryptkey, 8); + ntlmssp_state->internal_chal = data_blob_talloc(ntlmssp_state, cryptkey, 8); + + /* This should be a 'netbios domain -> DNS domain' mapping */ + dnsdomname[0] = '\0'; + get_mydomname(dnsdomname); + strlower_m(dnsdomname); + + dnsname[0] = '\0'; + get_myfullname(dnsname); + + /* This creates the 'blob' of names that appears at the end of the packet */ + if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) + { + const char *target_name_dns = ""; + if (chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN) { + target_name_dns = dnsdomname; + } else if (chal_flags |= NTLMSSP_TARGET_TYPE_SERVER) { + target_name_dns = dnsname; + } + + msrpc_gen(out_mem_ctx, + &struct_blob, "aaaaa", + NTLMSSP_NAME_TYPE_DOMAIN, target_name, + NTLMSSP_NAME_TYPE_SERVER, ntlmssp_state->server_name, + NTLMSSP_NAME_TYPE_DOMAIN_DNS, dnsdomname, + NTLMSSP_NAME_TYPE_SERVER_DNS, dnsname, + 0, ""); + } else { + struct_blob = data_blob(NULL, 0); + } + + { + /* Marshel the packet in the right format, be it unicode or ASCII */ + const char *gen_string; + if (ntlmssp_state->unicode) { + gen_string = "CdUdbddB"; + } else { + gen_string = "CdAdbddB"; + } + + msrpc_gen(out_mem_ctx, + out, gen_string, + "NTLMSSP", + NTLMSSP_CHALLENGE, + target_name, + chal_flags, + cryptkey, 8, + 0, 0, + struct_blob.data, struct_blob.length); + } + + ntlmssp_state->expected_state = NTLMSSP_AUTH; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +/** + * Next state function for the Authenticate packet + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_preauth(struct ntlmssp_state *ntlmssp_state, + const DATA_BLOB request) +{ + uint32_t ntlmssp_command, auth_flags; + NTSTATUS nt_status; + + uint8_t session_nonce_hash[16]; + + const char *parse_string; + char *domain = NULL; + char *user = NULL; + char *workstation = NULL; + +#if 0 + file_save("ntlmssp_auth.dat", request.data, request.length); +#endif + + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUUBd"; + } else { + parse_string = "CdBBAAABd"; + } + + /* zero these out */ + data_blob_free(&ntlmssp_state->lm_resp); + data_blob_free(&ntlmssp_state->nt_resp); + + ntlmssp_state->user = NULL; + ntlmssp_state->domain = NULL; + ntlmssp_state->workstation = NULL; + + /* now the NTLMSSP encoded auth hashes */ + if (!msrpc_parse(ntlmssp_state, + &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &domain, + &user, + &workstation, + &ntlmssp_state->encrypted_session_key, + &auth_flags)) { + DEBUG(10, ("ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal):\n")); + dump_data(10, request.data, request.length); + + /* zero this out */ + data_blob_free(&ntlmssp_state->encrypted_session_key); + auth_flags = 0; + + /* Try again with a shorter string (Win9X truncates this packet) */ + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUU"; + } else { + parse_string = "CdBBAAA"; + } + + /* now the NTLMSSP encoded auth hashes */ + if (!msrpc_parse(ntlmssp_state, + &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &domain, + &user, + &workstation)) { + DEBUG(1, ("ntlmssp_server_auth: failed to parse NTLMSSP:\n")); + dump_data(2, request.data, request.length); + + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (auth_flags) + ntlmssp_handle_neg_flags(ntlmssp_state, auth_flags, ntlmssp_state->allow_lm_key); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, domain))) { + /* zero this out */ + data_blob_free(&ntlmssp_state->encrypted_session_key); + return nt_status; + } + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, user))) { + /* zero this out */ + data_blob_free(&ntlmssp_state->encrypted_session_key); + return nt_status; + } + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_workstation(ntlmssp_state, workstation))) { + /* zero this out */ + data_blob_free(&ntlmssp_state->encrypted_session_key); + return nt_status; + } + + DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu\n", + ntlmssp_state->user, ntlmssp_state->domain, ntlmssp_state->workstation, (unsigned long)ntlmssp_state->lm_resp.length, (unsigned long)ntlmssp_state->nt_resp.length)); + +#if 0 + file_save("nthash1.dat", &ntlmssp_state->nt_resp.data, &ntlmssp_state->nt_resp.length); + file_save("lmhash1.dat", &ntlmssp_state->lm_resp.data, &ntlmssp_state->lm_resp.length); +#endif + + /* NTLM2 uses a 'challenge' that is made of up both the server challenge, and a + client challenge + + However, the NTLM2 flag may still be set for the real NTLMv2 logins, be careful. + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (ntlmssp_state->nt_resp.length == 24 && ntlmssp_state->lm_resp.length == 24) { + struct MD5Context md5_session_nonce_ctx; + SMB_ASSERT(ntlmssp_state->internal_chal.data + && ntlmssp_state->internal_chal.length == 8); + + ntlmssp_state->doing_ntlm2 = True; + + memcpy(ntlmssp_state->session_nonce, ntlmssp_state->internal_chal.data, 8); + memcpy(&ntlmssp_state->session_nonce[8], ntlmssp_state->lm_resp.data, 8); + + MD5Init(&md5_session_nonce_ctx); + MD5Update(&md5_session_nonce_ctx, ntlmssp_state->session_nonce, 16); + MD5Final(session_nonce_hash, &md5_session_nonce_ctx); + + ntlmssp_state->chal = data_blob_talloc(ntlmssp_state, + session_nonce_hash, 8); + + /* LM response is no longer useful, zero it out */ + data_blob_free(&ntlmssp_state->lm_resp); + + /* We changed the effective challenge - set it */ + if (!NT_STATUS_IS_OK(nt_status = + ntlmssp_state->set_challenge(ntlmssp_state, + &ntlmssp_state->chal))) { + /* zero this out */ + data_blob_free(&ntlmssp_state->encrypted_session_key); + return nt_status; + } + + /* LM Key is incompatible... */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + } + return NT_STATUS_OK; +} + +/** + * Next state function for the Authenticate packet + * (after authentication - figures out the session keys etc) + * + * @param ntlmssp_state NTLMSSP State + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_postauth(struct ntlmssp_state *ntlmssp_state, + DATA_BLOB *user_session_key, + DATA_BLOB *lm_session_key) +{ + NTSTATUS nt_status; + DATA_BLOB session_key = data_blob(NULL, 0); + + if (user_session_key) + dump_data_pw("USER session key:\n", user_session_key->data, user_session_key->length); + + if (lm_session_key) + dump_data_pw("LM first-8:\n", lm_session_key->data, lm_session_key->length); + + /* Handle the different session key derivation for NTLM2 */ + if (ntlmssp_state->doing_ntlm2) { + if (user_session_key && user_session_key->data && user_session_key->length == 16) { + session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + hmac_md5(user_session_key->data, ntlmssp_state->session_nonce, + sizeof(ntlmssp_state->session_nonce), session_key.data); + DEBUG(10,("ntlmssp_server_auth: Created NTLM2 session key.\n")); + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM2 session key.\n")); + session_key = data_blob(NULL, 0); + } + } else if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + /* Ensure we can never get here on NTLMv2 */ + && (ntlmssp_state->nt_resp.length == 0 || ntlmssp_state->nt_resp.length == 24)) { + + if (lm_session_key && lm_session_key->data && lm_session_key->length >= 8) { + if (ntlmssp_state->lm_resp.data && ntlmssp_state->lm_resp.length == 24) { + session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + SMBsesskeygen_lm_sess_key(lm_session_key->data, ntlmssp_state->lm_resp.data, + session_key.data); + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + dump_data_pw("LM session key:\n", session_key.data, session_key.length); + } else { + + /* When there is no LM response, just use zeros */ + static const uint8_t zeros[24]; + session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + SMBsesskeygen_lm_sess_key(zeros, zeros, + session_key.data); + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + dump_data_pw("LM session key:\n", session_key.data, session_key.length); + } + } else { + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM session key.\n")); + session_key = data_blob(NULL, 0); + } + + } else if (user_session_key && user_session_key->data) { + session_key = *user_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified nt session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else if (lm_session_key && lm_session_key->data) { + /* Very weird to have LM key, but no user session key, but anyway.. */ + session_key = *lm_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified lm session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create unmodified session key.\n")); + session_key = data_blob(NULL, 0); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + /* With KEY_EXCH, the client supplies the proposed session key, + but encrypts it with the long-term key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + if (!ntlmssp_state->encrypted_session_key.data + || ntlmssp_state->encrypted_session_key.length != 16) { + data_blob_free(&ntlmssp_state->encrypted_session_key); + DEBUG(1, ("Client-supplied KEY_EXCH session key was of invalid length (%u)!\n", + ntlmssp_state->encrypted_session_key.length)); + return NT_STATUS_INVALID_PARAMETER; + } else if (!session_key.data || session_key.length != 16) { + DEBUG(5, ("server session key is invalid (len == %u), cannot do KEY_EXCH!\n", + session_key.length)); + ntlmssp_state->session_key = session_key; + } else { + dump_data_pw("KEY_EXCH session key (enc):\n", + ntlmssp_state->encrypted_session_key.data, + ntlmssp_state->encrypted_session_key.length); + arcfour_crypt(ntlmssp_state->encrypted_session_key.data, + session_key.data, + ntlmssp_state->encrypted_session_key.length); + ntlmssp_state->session_key = data_blob_talloc(ntlmssp_state, + ntlmssp_state->encrypted_session_key.data, + ntlmssp_state->encrypted_session_key.length); + dump_data_pw("KEY_EXCH session key:\n", ntlmssp_state->encrypted_session_key.data, + ntlmssp_state->encrypted_session_key.length); + } + } else { + ntlmssp_state->session_key = session_key; + } + + /* The server might need us to use a partial-strength session key */ + ntlmssp_weaken_keys(ntlmssp_state); + + nt_status = ntlmssp_sign_init(ntlmssp_state); + + data_blob_free(&ntlmssp_state->encrypted_session_key); + + /* allow arbitarily many authentications, but watch that this will cause a + memory leak, until the ntlmssp_state is shutdown + */ + + if (ntlmssp_state->server_multiple_authentications) { + ntlmssp_state->expected_state = NTLMSSP_AUTH; + } else { + ntlmssp_state->expected_state = NTLMSSP_DONE; + } + + return nt_status; +} + + +/** + * Next state function for the Authenticate packet + * + * @param ntlmssp_state NTLMSSP State + * @param in The packet in from the NTLMSSP partner, as a DATA_BLOB + * @param out The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors, NT_STATUS_MORE_PROCESSING_REQUIRED or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + DATA_BLOB user_session_key = data_blob(NULL, 0); + DATA_BLOB lm_session_key = data_blob(NULL, 0); + NTSTATUS nt_status; + + /* zero the outbound NTLMSSP packet */ + *out = data_blob_talloc(out_mem_ctx, NULL, 0); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_server_preauth(ntlmssp_state, in))) { + return nt_status; + } + + /* + * Note we don't check here for NTLMv2 auth settings. If NTLMv2 auth + * is required (by "ntlm auth = no" and "lm auth = no" being set in the + * smb.conf file) and no NTLMv2 response was sent then the password check + * will fail here. JRA. + */ + + /* Finally, actually ask if the password is OK */ + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_state->check_password(ntlmssp_state, + &user_session_key, &lm_session_key))) { + return nt_status; + } + + if (ntlmssp_state->server_use_session_keys) { + return ntlmssp_server_postauth(ntlmssp_state, &user_session_key, &lm_session_key); + } else { + ntlmssp_state->session_key = data_blob(NULL, 0); + return NT_STATUS_OK; + } +} + +/** + * Create an NTLMSSP state machine + * + * @param ntlmssp_state NTLMSSP State, allocated by this function + */ + +NTSTATUS ntlmssp_server_start(TALLOC_CTX *mem_ctx, struct ntlmssp_state **ntlmssp_state) +{ + *ntlmssp_state = talloc(mem_ctx, struct ntlmssp_state); + if (!*ntlmssp_state) { + DEBUG(0,("ntlmssp_server_start: talloc failed!\n")); + return NT_STATUS_NO_MEMORY; + } + ZERO_STRUCTP(*ntlmssp_state); + + (*ntlmssp_state)->role = NTLMSSP_SERVER; + + (*ntlmssp_state)->get_challenge = get_challenge; + (*ntlmssp_state)->set_challenge = set_challenge; + (*ntlmssp_state)->may_set_challenge = may_set_challenge; + + (*ntlmssp_state)->workstation = NULL; + (*ntlmssp_state)->server_name = lp_netbios_name(); + + (*ntlmssp_state)->get_domain = lp_workgroup; + (*ntlmssp_state)->server_role = ROLE_DOMAIN_MEMBER; /* a good default */ + + (*ntlmssp_state)->expected_state = NTLMSSP_NEGOTIATE; + + (*ntlmssp_state)->allow_lm_key = (lp_lanman_auth() + && lp_parm_bool(-1, "ntlmssp_server", "allow_lm_key", False)); + + (*ntlmssp_state)->server_use_session_keys = True; + (*ntlmssp_state)->server_multiple_authentications = False; + + (*ntlmssp_state)->ref_count = 1; + + (*ntlmssp_state)->neg_flags = + NTLMSSP_NEGOTIATE_NTLM; + + if (lp_parm_bool(-1, "ntlmssp_server", "128bit", True)) { + (*ntlmssp_state)->neg_flags |= NTLMSSP_NEGOTIATE_128; + } + + if (lp_parm_bool(-1, "ntlmssp_server", "keyexchange", True)) { + (*ntlmssp_state)->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (lp_parm_bool(-1, "ntlmssp_server", "ntlm2", True)) { + (*ntlmssp_state)->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + return NT_STATUS_OK; +} + +/********************************************************************* + Client side NTLMSSP +*********************************************************************/ + +/** + * Next state function for the Initial packet + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The DATA_BLOB *out will be allocated on this context + * @param in The request, as a DATA_BLOB. reply.data must be NULL + * @param out The reply, as an talloc()ed DATA_BLOB, on out_mem_ctx + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_client_initial(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) +{ + if (ntlmssp_state->unicode) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + } else { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + } + + if (ntlmssp_state->use_ntlmv2) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + /* generate the ntlmssp negotiate packet */ + msrpc_gen(out_mem_ctx, + out, "CddAA", + "NTLMSSP", + NTLMSSP_NEGOTIATE, + ntlmssp_state->neg_flags, + ntlmssp_state->get_domain(), + ntlmssp_state->workstation); + + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +/** + * Next state function for the Challenge Packet. Generate an auth packet. + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB. reply.data must be NULL + * @param request The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_client_challenge(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + uint32_t chal_flags, ntlmssp_command, unkn1, unkn2; + DATA_BLOB server_domain_blob; + DATA_BLOB challenge_blob; + DATA_BLOB struct_blob = data_blob(NULL, 0); + char *server_domain; + const char *chal_parse_string; + const char *auth_gen_string; + uint8_t lm_hash[16]; + DATA_BLOB lm_response = data_blob(NULL, 0); + DATA_BLOB nt_response = data_blob(NULL, 0); + DATA_BLOB session_key = data_blob(NULL, 0); + DATA_BLOB lm_session_key = data_blob(NULL, 0); + DATA_BLOB encrypted_session_key = data_blob(NULL, 0); + NTSTATUS nt_status; + + if (!msrpc_parse(ntlmssp_state, + &in, "CdBd", + "NTLMSSP", + &ntlmssp_command, + &server_domain_blob, + &chal_flags)) { + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#1)\n")); + dump_data(2, in.data, in.length); + + return NT_STATUS_INVALID_PARAMETER; + } + + data_blob_free(&server_domain_blob); + + DEBUG(3, ("Got challenge flags:\n")); + debug_ntlmssp_flags(chal_flags); + + ntlmssp_handle_neg_flags(ntlmssp_state, chal_flags, ntlmssp_state->allow_lm_key); + + if (ntlmssp_state->unicode) { + if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) { + chal_parse_string = "CdUdbddB"; + } else { + chal_parse_string = "CdUdbdd"; + } + auth_gen_string = "CdBBUUUBd"; + } else { + if (chal_flags & NTLMSSP_CHAL_TARGET_INFO) { + chal_parse_string = "CdAdbddB"; + } else { + chal_parse_string = "CdAdbdd"; + } + + auth_gen_string = "CdBBAAABd"; + } + + DEBUG(3, ("NTLMSSP: Set final flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + if (!msrpc_parse(ntlmssp_state, + &in, chal_parse_string, + "NTLMSSP", + &ntlmssp_command, + &server_domain, + &chal_flags, + &challenge_blob, 8, + &unkn1, &unkn2, + &struct_blob)) { + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#2)\n")); + dump_data(2, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + ntlmssp_state->server_domain = server_domain; + + if (challenge_blob.length != 8) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->password) { + static const uint8_t zeros[16]; + /* do nothing - blobs are zero length */ + + /* session key is all zeros */ + session_key = data_blob_talloc(ntlmssp_state, zeros, 16); + lm_session_key = data_blob_talloc(ntlmssp_state, zeros, 16); + + /* not doing NLTM2 without a password */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } else if (ntlmssp_state->use_ntlmv2) { + + if (!struct_blob.length) { + /* be lazy, match win2k - we can't do NTLMv2 without it */ + DEBUG(1, ("Server did not provide 'target information', required for NTLMv2\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO: if the remote server is standalone, then we should replace 'domain' + with the server name as supplied above */ + + if (!SMBNTLMv2encrypt(ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->password, &challenge_blob, + &struct_blob, + &lm_response, &nt_response, + NULL, &session_key)) { + data_blob_free(&challenge_blob); + data_blob_free(&struct_blob); + return NT_STATUS_NO_MEMORY; + } + + /* LM Key is incompatible... */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + struct MD5Context md5_session_nonce_ctx; + uint8_t nt_hash[16]; + uint8_t session_nonce[16]; + uint8_t session_nonce_hash[16]; + uint8_t user_session_key[16]; + E_md4hash(ntlmssp_state->password, nt_hash); + + lm_response = data_blob_talloc(ntlmssp_state, NULL, 24); + generate_random_buffer(lm_response.data, 8); + memset(lm_response.data+8, 0, 16); + + memcpy(session_nonce, challenge_blob.data, 8); + memcpy(&session_nonce[8], lm_response.data, 8); + + MD5Init(&md5_session_nonce_ctx); + MD5Update(&md5_session_nonce_ctx, challenge_blob.data, 8); + MD5Update(&md5_session_nonce_ctx, lm_response.data, 8); + MD5Final(session_nonce_hash, &md5_session_nonce_ctx); + + DEBUG(5, ("NTLMSSP challenge set by NTLM2\n")); + DEBUG(5, ("challenge is: \n")); + dump_data(5, session_nonce_hash, 8); + + nt_response = data_blob_talloc(ntlmssp_state, NULL, 24); + SMBNTencrypt(ntlmssp_state->password, + session_nonce_hash, + nt_response.data); + + session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + + SMBsesskeygen_ntv1(nt_hash, user_session_key); + hmac_md5(user_session_key, session_nonce, sizeof(session_nonce), session_key.data); + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + + /* LM Key is incompatible... */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } else { + uint8_t nt_hash[16]; + + if (ntlmssp_state->use_nt_response) { + nt_response = data_blob_talloc(ntlmssp_state, NULL, 24); + SMBNTencrypt(ntlmssp_state->password,challenge_blob.data, + nt_response.data); + E_md4hash(ntlmssp_state->password, nt_hash); + session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + SMBsesskeygen_ntv1(nt_hash, session_key.data); + dump_data_pw("NT session key:\n", session_key.data, session_key.length); + } + + /* lanman auth is insecure, it may be disabled */ + if (lp_client_lanman_auth()) { + lm_response = data_blob_talloc(ntlmssp_state, NULL, 24); + if (!SMBencrypt(ntlmssp_state->password,challenge_blob.data, + lm_response.data)) { + /* If the LM password was too long (and therefore the LM hash being + of the first 14 chars only), don't send it */ + data_blob_free(&lm_response); + + /* LM Key is incompatible with 'long' passwords */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } else { + E_deshash(ntlmssp_state->password, lm_hash); + lm_session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + memcpy(lm_session_key.data, lm_hash, 8); + memset(&lm_session_key.data[8], '\0', 8); + + if (!ntlmssp_state->use_nt_response) { + session_key = lm_session_key; + } + } + } else { + /* LM Key is incompatible... */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + } + + if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + && lp_client_lanman_auth() && lm_session_key.length == 16) { + DATA_BLOB new_session_key = data_blob_talloc(ntlmssp_state, NULL, 16); + if (lm_response.length == 24) { + SMBsesskeygen_lm_sess_key(lm_session_key.data, lm_response.data, + new_session_key.data); + } else { + static const uint8_t zeros[24]; + SMBsesskeygen_lm_sess_key(lm_session_key.data, zeros, + new_session_key.data); + } + new_session_key.length = 16; + session_key = new_session_key; + dump_data_pw("LM session key\n", session_key.data, session_key.length); + } + + + /* Key exchange encryptes a new client-generated session key with + the password-derived key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + /* Make up a new session key */ + uint8_t client_session_key[16]; + generate_random_buffer(client_session_key, sizeof(client_session_key)); + + /* Encrypt the new session key with the old one */ + encrypted_session_key = data_blob_talloc(ntlmssp_state, + client_session_key, sizeof(client_session_key)); + dump_data_pw("KEY_EXCH session key:\n", encrypted_session_key.data, encrypted_session_key.length); + arcfour_crypt(encrypted_session_key.data, session_key.data, encrypted_session_key.length); + dump_data_pw("KEY_EXCH session key (enc):\n", encrypted_session_key.data, encrypted_session_key.length); + + /* Mark the new session key as the 'real' session key */ + session_key = data_blob_talloc(ntlmssp_state, client_session_key, sizeof(client_session_key)); + } + + /* this generates the actual auth packet */ + if (!msrpc_gen(out_mem_ctx, + out, auth_gen_string, + "NTLMSSP", + NTLMSSP_AUTH, + lm_response.data, lm_response.length, + nt_response.data, nt_response.length, + ntlmssp_state->domain, + ntlmssp_state->user, + ntlmssp_state->workstation, + encrypted_session_key.data, encrypted_session_key.length, + ntlmssp_state->neg_flags)) { + + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->session_key = session_key; + + /* The client might be using 56 or 40 bit weakened keys */ + ntlmssp_weaken_keys(ntlmssp_state); + + ntlmssp_state->chal = challenge_blob; + ntlmssp_state->lm_resp = lm_response; + ntlmssp_state->nt_resp = nt_response; + + ntlmssp_state->expected_state = NTLMSSP_DONE; + + nt_status = ntlmssp_sign_init(ntlmssp_state); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("Could not setup NTLMSSP signing/sealing system (error was: %s)\n", + nt_errstr(nt_status))); + return nt_status; + } + + return nt_status; +} + +NTSTATUS ntlmssp_client_start(TALLOC_CTX *mem_ctx, struct ntlmssp_state **ntlmssp_state) +{ + *ntlmssp_state = talloc(mem_ctx, struct ntlmssp_state); + if (!*ntlmssp_state) { + DEBUG(0,("ntlmssp_client_start: talloc failed!\n")); + return NT_STATUS_NO_MEMORY; + } + ZERO_STRUCTP(*ntlmssp_state); + + (*ntlmssp_state)->role = NTLMSSP_CLIENT; + + (*ntlmssp_state)->workstation = lp_netbios_name(); + (*ntlmssp_state)->get_domain = lp_workgroup; + + (*ntlmssp_state)->unicode = lp_parm_bool(-1, "ntlmssp_client", "unicode", True); + + (*ntlmssp_state)->use_nt_response = lp_parm_bool(-1, "ntlmssp_client", "send_nt_reponse", True); + + (*ntlmssp_state)->allow_lm_key = (lp_lanman_auth() + && lp_parm_bool(-1, "ntlmssp_client", "allow_lm_key", False)); + + (*ntlmssp_state)->use_ntlmv2 = lp_client_ntlmv2_auth(); + + (*ntlmssp_state)->expected_state = NTLMSSP_INITIAL; + + (*ntlmssp_state)->ref_count = 1; + + (*ntlmssp_state)->neg_flags = + NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_REQUEST_TARGET; + + if (lp_parm_bool(-1, "ntlmssp_client", "128bit", True)) { + (*ntlmssp_state)->neg_flags |= NTLMSSP_NEGOTIATE_128; + } + + if (lp_parm_bool(-1, "ntlmssp_client", "keyexchange", True)) { + (*ntlmssp_state)->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (lp_parm_bool(-1, "ntlmssp_client", "ntlm2", True)) { + (*ntlmssp_state)->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } else { + /* apparently we can't do ntlmv2 if we don't do ntlm2 */ + (*ntlmssp_state)->use_ntlmv2 = False; + } + + return NT_STATUS_OK; +} + diff --git a/source4/auth/gensec/ntlmssp.h b/source4/auth/gensec/ntlmssp.h new file mode 100644 index 0000000000..e17c133c8b --- /dev/null +++ b/source4/auth/gensec/ntlmssp.h @@ -0,0 +1,190 @@ +/* + Unix SMB/CIFS implementation. + SMB parameters and setup + Copyright (C) Andrew Tridgell 1992-1997 + Copyright (C) Luke Kenneth Casson Leighton 1996-1997 + Copyright (C) Paul Ashton 1997 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "librpc/gen_ndr/ndr_samr.h" + +/* NTLMSSP mode */ +enum ntlmssp_role +{ + NTLMSSP_SERVER, + NTLMSSP_CLIENT +}; + +/* NTLMSSP message types */ +enum ntlmssp_message_type +{ + NTLMSSP_INITIAL = 0 /* samba internal state */, + NTLMSSP_NEGOTIATE = 1, + NTLMSSP_CHALLENGE = 2, + NTLMSSP_AUTH = 3, + NTLMSSP_UNKNOWN = 4, + NTLMSSP_DONE = 5 /* samba final state */ +}; + +/* NTLMSSP negotiation flags */ +#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 +#define NTLMSSP_NEGOTIATE_OEM 0x00000002 +#define NTLMSSP_REQUEST_TARGET 0x00000004 +#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 /* Message integrity */ +#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 /* Message confidentiality */ +#define NTLMSSP_NEGOTIATE_DATAGRAM_STYLE 0x00000040 +#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080 +#define NTLMSSP_NEGOTIATE_NETWARE 0x00000100 +#define NTLMSSP_NEGOTIATE_NTLM 0x00000200 +#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000 +#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000 +#define NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL 0x00004000 +#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000 +#define NTLMSSP_TARGET_TYPE_DOMAIN 0x10000 +#define NTLMSSP_TARGET_TYPE_SERVER 0x20000 +#define NTLMSSP_CHAL_INIT_RESPONSE 0x00010000 + +#define NTLMSSP_CHAL_ACCEPT_RESPONSE 0x00020000 +#define NTLMSSP_CHAL_NON_NT_SESSION_KEY 0x00040000 +#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000 +#define NTLMSSP_CHAL_TARGET_INFO 0x00800000 +#define NTLMSSP_NEGOTIATE_128 0x20000000 /* 128-bit encryption */ +#define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000 +#define NTLMSSP_NEGOTIATE_56 0x80000000 + +#define NTLMSSP_NAME_TYPE_SERVER 0x01 +#define NTLMSSP_NAME_TYPE_DOMAIN 0x02 +#define NTLMSSP_NAME_TYPE_SERVER_DNS 0x03 +#define NTLMSSP_NAME_TYPE_DOMAIN_DNS 0x04 + +#define NTLMSSP_SIGN_VERSION 1 + +#define NTLMSSP_SIG_SIZE 16 + +struct ntlmssp_state +{ + uint_t ref_count; + enum ntlmssp_role role; + enum samr_Role server_role; + uint32_t expected_state; + + BOOL unicode; + BOOL use_ntlmv2; + BOOL use_nt_response; /* Set to 'False' to debug what happens when the NT response is omited */ + BOOL allow_lm_key; /* The LM_KEY code is not functional at this point, and it's not + very secure anyway */ + + BOOL server_use_session_keys; /* Set to 'False' for authentication only, + that will never return a session key */ + BOOL server_multiple_authentications; /* Set to 'True' to allow squid 2.5 + style 'challenge caching' */ + + char *user; + char *domain; + const char *workstation; + char *password; + char *server_domain; + + DATA_BLOB internal_chal; /* Random challenge as supplied to the client for NTLM authentication */ + + DATA_BLOB chal; /* Random challenge as input into the actual NTLM (or NTLM2) authentication */ + DATA_BLOB lm_resp; + DATA_BLOB nt_resp; + DATA_BLOB session_key; + + uint32_t neg_flags; /* the current state of negotiation with the NTLMSSP partner */ + + /* internal variables used by NTLM2 */ + BOOL doing_ntlm2; + uint8_t session_nonce[16]; + + /* internal variables used by KEY_EXCH (client-supplied user session key */ + DATA_BLOB encrypted_session_key; + + void *auth_context; + + /** + * Callback to get the 'challenge' used for NTLM authentication. + * + * @param ntlmssp_state This structure + * @return 8 bytes of challenge data, determined by the server to be the challenge for NTLM authentication + * + */ + const uint8_t *(*get_challenge)(const struct ntlmssp_state *ntlmssp_state); + + /** + * Callback to find if the challenge used by NTLM authentication may be modified + * + * The NTLM2 authentication scheme modifies the effective challenge, but this is not compatiable with the + * current 'security=server' implementation.. + * + * @param ntlmssp_state This structure + * @return Can the challenge be set to arbitary values? + * + */ + BOOL (*may_set_challenge)(const struct ntlmssp_state *ntlmssp_state); + + /** + * Callback to set the 'challenge' used for NTLM authentication. + * + * The callback may use the void *auth_context to store state information, but the same value is always available + * from the DATA_BLOB chal on this structure. + * + * @param ntlmssp_state This structure + * @param challange 8 bytes of data, agreed by the client and server to be the effective challenge for NTLM2 authentication + * + */ + NTSTATUS (*set_challenge)(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *challenge); + + /** + * Callback to check the user's password. + * + * The callback must reads the feilds of this structure for the information it needs on the user + * @param ntlmssp_state This structure + * @param nt_session_key If an NT session key is returned by the authentication process, return it here + * @param lm_session_key If an LM session key is returned by the authentication process, return it here + * + */ + NTSTATUS (*check_password)(struct ntlmssp_state *ntlmssp_state, DATA_BLOB *nt_session_key, DATA_BLOB *lm_session_key); + + const char *server_name; + const char *(*get_domain)(void); + + /* SMB Signing */ + uint32_t ntlm_seq_num; + uint32_t ntlm2_send_seq_num; + uint32_t ntlm2_recv_seq_num; + + /* ntlmv2 */ + DATA_BLOB send_sign_key; + DATA_BLOB send_seal_key; + DATA_BLOB recv_sign_key; + DATA_BLOB recv_seal_key; + + uint8_t send_seal_hash[258]; + uint8_t recv_seal_hash[258]; + + /* ntlmv1 */ + uint8_t ntlmssp_hash[258]; + + /* it turns out that we don't always get the + response in at the time we want to process it. + Store it here, until we need it */ + DATA_BLOB stored_response; + +}; + diff --git a/source4/auth/gensec/ntlmssp_parse.c b/source4/auth/gensec/ntlmssp_parse.c new file mode 100644 index 0000000000..42546cb130 --- /dev/null +++ b/source4/auth/gensec/ntlmssp_parse.c @@ -0,0 +1,338 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5/SPNEGO routines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002 + Copyright (C) Andrew Bartlett 2002-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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "pstring.h" + +/* + 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) + a = address (input is char *unix_string) + (1 byte type, 1 byte length, unicode/ASCII string, all inline) + A = ASCII string (input is unix string) + B = data blob (pointer + length) + b = data blob in header (pointer + length) + D + d = word (4 bytes) + C = constant ascii string + */ +BOOL msrpc_gen(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, + const char *format, ...) +{ + int i; + ssize_t n; + va_list ap; + char *s; + uint8_t *b; + int head_size=0, data_size=0; + int head_ofs, data_ofs; + int *intargs; + + DATA_BLOB *pointers; + + pointers = talloc_array(mem_ctx, DATA_BLOB, strlen(format)); + intargs = talloc_array(pointers, int, strlen(format)); + + /* 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; + n = push_ucs2_talloc(pointers, (void **)&pointers[i].data, s); + if (n == -1) { + return False; + } + pointers[i].length = n; + pointers[i].length -= 2; + data_size += pointers[i].length; + break; + case 'A': + s = va_arg(ap, char *); + head_size += 8; + n = push_ascii_talloc(pointers, (char **)&pointers[i].data, s); + if (n == -1) { + return False; + } + pointers[i].length = n; + pointers[i].length -= 1; + data_size += pointers[i].length; + break; + case 'a': + n = va_arg(ap, int); + intargs[i] = n; + s = va_arg(ap, char *); + n = push_ucs2_talloc(pointers, (void **)&pointers[i].data, s); + if (n == -1) { + return False; + } + pointers[i].length = n; + pointers[i].length -= 2; + data_size += pointers[i].length + 4; + break; + case 'B': + b = va_arg(ap, uint8_t *); + head_size += 8; + pointers[i].data = b; + pointers[i].length = va_arg(ap, int); + data_size += pointers[i].length; + break; + case 'b': + b = va_arg(ap, uint8_t *); + pointers[i].data = b; + pointers[i].length = va_arg(ap, int); + head_size += pointers[i].length; + break; + case 'd': + n = va_arg(ap, int); + intargs[i] = n; + head_size += 4; + break; + case 'C': + s = va_arg(ap, char *); + pointers[i].data = (uint8_t *)s; + pointers[i].length = strlen(s)+1; + head_size += pointers[i].length; + break; + } + } + va_end(ap); + + /* allocate the space, then scan the format again to fill in the values */ + *blob = data_blob_talloc(mem_ctx, NULL, head_size + data_size); + + head_ofs = 0; + data_ofs = head_size; + + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + case 'A': + case 'B': + n = pointers[i].length; + 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; + if (pointers[i].data && n) /* don't follow null pointers... */ + memcpy(blob->data+data_ofs, pointers[i].data, n); + data_ofs += n; + break; + case 'a': + n = intargs[i]; + SSVAL(blob->data, data_ofs, n); data_ofs += 2; + + n = pointers[i].length; + SSVAL(blob->data, data_ofs, n); data_ofs += 2; + if (n >= 0) { + memcpy(blob->data+data_ofs, pointers[i].data, n); + } + data_ofs += n; + break; + case 'd': + n = intargs[i]; + SIVAL(blob->data, head_ofs, n); + head_ofs += 4; + break; + case 'b': + n = pointers[i].length; + memcpy(blob->data + head_ofs, pointers[i].data, n); + head_ofs += n; + break; + case 'C': + n = pointers[i].length; + memcpy(blob->data + head_ofs, pointers[i].data, n); + head_ofs += n; + break; + } + } + va_end(ap); + + talloc_free(pointers); + + return True; +} + + +/* a helpful macro to avoid running over the end of our blob */ +#define NEED_DATA(amount) \ +if ((head_ofs + amount) > blob->length) { \ + return False; \ +} + +/* + this is a tiny msrpc packet parser. This the the partner of msrpc_gen + + format specifiers are: + + U = unicode string (output is unix string) + A = ascii string + B = data blob + b = data blob in header + d = word (4 bytes) + C = constant ascii string + */ + +BOOL msrpc_parse(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, + const char *format, ...) +{ + int i; + va_list ap; + const char **ps, *s; + DATA_BLOB *b; + size_t head_ofs = 0; + uint16_t len1, len2; + uint32_t ptr; + uint32_t *v; + pstring p; + + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + NEED_DATA(8); + 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; + + ps = (const char **)va_arg(ap, char **); + if (len1 == 0 && len2 == 0) { + *ps = ""; + } else { + /* make sure its in the right format - be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + return False; + } + if (len1 & 1) { + /* if odd length and unicode */ + return False; + } + if (blob->data + ptr < (uint8_t *)ptr || blob->data + ptr < blob->data) + return False; + + if (0 < len1) { + pull_string(p, blob->data + ptr, sizeof(p), + len1, + STR_UNICODE|STR_NOALIGN); + (*ps) = talloc_strdup(mem_ctx, p); + if (!(*ps)) { + return False; + } + } else { + (*ps) = ""; + } + } + break; + case 'A': + NEED_DATA(8); + 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; + + ps = (const char **)va_arg(ap, char **); + /* make sure its in the right format - be strict */ + if (len1 == 0 && len2 == 0) { + *ps = ""; + } else { + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + return False; + } + + if (blob->data + ptr < (uint8_t *)ptr || blob->data + ptr < blob->data) + return False; + + if (0 < len1) { + pull_string(p, blob->data + ptr, sizeof(p), + len1, + STR_ASCII|STR_NOALIGN); + (*ps) = talloc_strdup(mem_ctx, p); + if (!(*ps)) { + return False; + } + } else { + (*ps) = ""; + } + } + break; + case 'B': + NEED_DATA(8); + 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; + + b = (DATA_BLOB *)va_arg(ap, void *); + if (len1 == 0 && len2 == 0) { + *b = data_blob_talloc(mem_ctx, NULL, 0); + } else { + /* make sure its in the right format - be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + return False; + } + + if (blob->data + ptr < (uint8_t *)ptr || blob->data + ptr < blob->data) + return False; + + *b = data_blob_talloc(mem_ctx, blob->data + ptr, len1); + } + break; + case 'b': + b = (DATA_BLOB *)va_arg(ap, void *); + len1 = va_arg(ap, uint_t); + /* make sure its in the right format - be strict */ + NEED_DATA(len1); + if (blob->data + head_ofs < (uint8_t *)head_ofs || blob->data + head_ofs < blob->data) + return False; + + *b = data_blob_talloc(mem_ctx, blob->data + head_ofs, len1); + head_ofs += len1; + break; + case 'd': + v = va_arg(ap, uint32_t *); + NEED_DATA(4); + *v = IVAL(blob->data, head_ofs); head_ofs += 4; + break; + case 'C': + s = va_arg(ap, char *); + + if (blob->data + head_ofs < (uint8_t *)head_ofs || blob->data + head_ofs < blob->data) + return False; + + head_ofs += pull_string(p, blob->data+head_ofs, sizeof(p), + blob->length - head_ofs, + STR_ASCII|STR_TERMINATE); + if (strcmp(s, p) != 0) { + return False; + } + break; + } + } + va_end(ap); + + return True; +} diff --git a/source4/auth/gensec/ntlmssp_sign.c b/source4/auth/gensec/ntlmssp_sign.c new file mode 100644 index 0000000000..347a85da77 --- /dev/null +++ b/source4/auth/gensec/ntlmssp_sign.c @@ -0,0 +1,449 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Luke Kenneth Casson Leighton 1996-2001 + * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-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 + * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "includes.h" +#include "auth/auth.h" +#include "lib/crypto/crypto.h" + +#define CLI_SIGN "session key to client-to-server signing key magic constant" +#define CLI_SEAL "session key to client-to-server sealing key magic constant" +#define SRV_SIGN "session key to server-to-client signing key magic constant" +#define SRV_SEAL "session key to server-to-client sealing key magic constant" + +/** + * Some notes on then NTLM2 code: + * + * This code works correctly for the sealing part of the problem. If + * we disable the check for valid client signatures, then we see that + * the output of a rpcecho 'sinkdata' at smbd is correct. We get the + * valid data, and it is validly decrypted. + * + * This means that the quantity of data passing though the RC4 sealing + * pad is correct. + * + * This code also correctly matches test values that I have obtained, + * claiming to be the correct output of NTLM2 signature generation. + * + */ + +static void calc_ntlmv2_key(TALLOC_CTX *mem_ctx, + DATA_BLOB *subkey, + DATA_BLOB session_key, + const char *constant) +{ + struct MD5Context ctx3; + *subkey = data_blob_talloc(mem_ctx, NULL, 16); + MD5Init(&ctx3); + MD5Update(&ctx3, session_key.data, session_key.length); + MD5Update(&ctx3, constant, strlen(constant)+1); + MD5Final(subkey->data, &ctx3); +} + +enum ntlmssp_direction { + NTLMSSP_SEND, + NTLMSSP_RECEIVE +}; + +static NTSTATUS ntlmssp_make_packet_signature(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + enum ntlmssp_direction direction, + DATA_BLOB *sig, BOOL encrypt_sig) +{ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + + HMACMD5Context ctx; + uint8_t digest[16]; + uint8_t seq_num[4]; + + *sig = data_blob_talloc(sig_mem_ctx, NULL, NTLMSSP_SIG_SIZE); + if (!sig->data) { + return NT_STATUS_NO_MEMORY; + } + + switch (direction) { + case NTLMSSP_SEND: + SIVAL(seq_num, 0, ntlmssp_state->ntlm2_send_seq_num); + ntlmssp_state->ntlm2_send_seq_num++; + hmac_md5_init_limK_to_64(ntlmssp_state->send_sign_key.data, + ntlmssp_state->send_sign_key.length, &ctx); + break; + case NTLMSSP_RECEIVE: + SIVAL(seq_num, 0, ntlmssp_state->ntlm2_recv_seq_num); + ntlmssp_state->ntlm2_recv_seq_num++; + hmac_md5_init_limK_to_64(ntlmssp_state->recv_sign_key.data, + ntlmssp_state->recv_sign_key.length, &ctx); + break; + } + hmac_md5_update(seq_num, sizeof(seq_num), &ctx); + hmac_md5_update(whole_pdu, pdu_length, &ctx); + hmac_md5_final(digest, &ctx); + + if (encrypt_sig && ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + switch (direction) { + case NTLMSSP_SEND: + arcfour_crypt_sbox(ntlmssp_state->send_seal_hash, digest, 8); + break; + case NTLMSSP_RECEIVE: + arcfour_crypt_sbox(ntlmssp_state->recv_seal_hash, digest, 8); + break; + } + } + + SIVAL(sig->data, 0, NTLMSSP_SIGN_VERSION); + memcpy(sig->data + 4, digest, 8); + memcpy(sig->data + 12, seq_num, 4); + + } else { + uint32_t crc; + crc = crc32_calc_buffer(data, length); + if (!msrpc_gen(sig_mem_ctx, sig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, ntlmssp_state->ntlm_seq_num)) { + return NT_STATUS_NO_MEMORY; + } + ntlmssp_state->ntlm_seq_num++; + + arcfour_crypt_sbox(ntlmssp_state->ntlmssp_hash, sig->data+4, sig->length-4); + } + dump_data_pw("calculated ntlmssp signature\n", sig->data, sig->length); + return NT_STATUS_OK; +} + +NTSTATUS ntlmssp_sign_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check sign packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + DEBUG(3, ("NTLMSSP Signing not negotiated - cannot sign packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + return ntlmssp_make_packet_signature(ntlmssp_state, sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, sig, True); +} + +/** + * Check the signature of an incoming packet + * + */ + +NTSTATUS ntlmssp_check_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + DATA_BLOB local_sig; + NTSTATUS nt_status; + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check packet signature\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (sig->length < 8) { + DEBUG(0, ("NTLMSSP packet check failed due to short signature (%lu bytes)!\n", + (unsigned long)sig->length)); + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_RECEIVE, &local_sig, True); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0, ("NTLMSSP packet check failed with %s\n", nt_errstr(nt_status))); + return nt_status; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (local_sig.length != sig->length || + memcmp(local_sig.data, + sig->data, sig->length) != 0) { + DEBUG(5, ("BAD SIG NTLM2: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM2 packet check failed due to invalid signature!\n")); + return NT_STATUS_ACCESS_DENIED; + } + } else { + if (local_sig.length != sig->length || + memcmp(local_sig.data + 8, + sig->data + 8, sig->length - 8) != 0) { + DEBUG(5, ("BAD SIG NTLM1: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM1 packet check failed due to invalid signature!\n")); + return NT_STATUS_ACCESS_DENIED; + } + } + dump_data_pw("checked ntlmssp signature\n", sig->data, sig->length); + + return NT_STATUS_OK; +} + + +/** + * Seal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_seal_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + NTSTATUS nt_status; + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot seal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + DEBUG(3, ("NTLMSSP Sealing not negotiated - cannot seal packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10,("ntlmssp_seal_data: seal\n")); + dump_data_pw("ntlmssp clear data\n", data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + /* The order of these two operations matters - we must first seal the packet, + then seal the sequence number - this is becouse the send_seal_hash is not + constant, but is is rather updated with each iteration */ + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, sig, False); + arcfour_crypt_sbox(ntlmssp_state->send_seal_hash, data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + arcfour_crypt_sbox(ntlmssp_state->send_seal_hash, sig->data+4, 8); + } + } else { + uint32_t crc; + crc = crc32_calc_buffer(data, length); + if (!msrpc_gen(sig_mem_ctx, sig, "dddd", NTLMSSP_SIGN_VERSION, 0, crc, ntlmssp_state->ntlm_seq_num)) { + return NT_STATUS_NO_MEMORY; + } + + /* The order of these two operations matters - we must + first seal the packet, then seal the sequence + number - this is becouse the ntlmssp_hash is not + constant, but is is rather updated with each + iteration */ + + arcfour_crypt_sbox(ntlmssp_state->ntlmssp_hash, data, length); + arcfour_crypt_sbox(ntlmssp_state->ntlmssp_hash, sig->data+4, sig->length-4); + /* increment counter on send */ + ntlmssp_state->ntlm_seq_num++; + nt_status = NT_STATUS_OK; + } + dump_data_pw("ntlmssp signature\n", sig->data, sig->length); + dump_data_pw("ntlmssp sealed data\n", data, length); + + + return nt_status; +} + +/** + * Unseal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_unseal_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + DATA_BLOB local_sig; + NTSTATUS nt_status; + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot unseal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + dump_data_pw("ntlmssp sealed data\n", data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + arcfour_crypt_sbox(ntlmssp_state->recv_seal_hash, data, length); + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_RECEIVE, &local_sig, True); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (local_sig.length != sig->length || + memcmp(local_sig.data, + sig->data, sig->length) != 0) { + DEBUG(5, ("BAD SIG NTLM2: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM2 packet check failed due to invalid signature!\n")); + return NT_STATUS_ACCESS_DENIED; + } + + dump_data_pw("ntlmssp clear data\n", data, length); + return NT_STATUS_OK; + } else { + arcfour_crypt_sbox(ntlmssp_state->ntlmssp_hash, data, length); + dump_data_pw("ntlmssp clear data\n", data, length); + return ntlmssp_check_packet(ntlmssp_state, sig_mem_ctx, data, length, whole_pdu, pdu_length, sig); + } +} + +/** + Initialise the state for NTLMSSP signing. +*/ +NTSTATUS ntlmssp_sign_init(struct ntlmssp_state *ntlmssp_state) +{ + uint8_t p24[24]; + ZERO_STRUCT(p24); + + DEBUG(3, ("NTLMSSP Sign/Seal - Initialising with flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot intialise signing\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) + { + DATA_BLOB weak_session_key = ntlmssp_state->session_key; + const char *send_sign_const; + const char *send_seal_const; + const char *recv_sign_const; + const char *recv_seal_const; + + switch (ntlmssp_state->role) { + case NTLMSSP_CLIENT: + send_sign_const = CLI_SIGN; + send_seal_const = CLI_SEAL; + recv_sign_const = SRV_SIGN; + recv_seal_const = SRV_SEAL; + break; + case NTLMSSP_SERVER: + send_sign_const = SRV_SIGN; + send_seal_const = SRV_SEAL; + recv_sign_const = CLI_SIGN; + recv_seal_const = CLI_SEAL; + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + /** + Weaken NTLMSSP keys to cope with down-level clients, servers and export restrictions. + + We probably should have some parameters to control this, once we get NTLM2 working. + */ + + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) { + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key.length = 6; + } else { /* forty bits */ + weak_session_key.length = 5; + } + dump_data_pw("NTLMSSP weakend master key:\n", + weak_session_key.data, + weak_session_key.length); + + /* SEND */ + calc_ntlmv2_key(ntlmssp_state, + &ntlmssp_state->send_sign_key, + ntlmssp_state->session_key, send_sign_const); + dump_data_pw("NTLMSSP send sign key:\n", + ntlmssp_state->send_sign_key.data, + ntlmssp_state->send_sign_key.length); + + calc_ntlmv2_key(ntlmssp_state, + &ntlmssp_state->send_seal_key, + weak_session_key, send_seal_const); + dump_data_pw("NTLMSSP send seal key:\n", + ntlmssp_state->send_seal_key.data, + ntlmssp_state->send_seal_key.length); + + arcfour_init(ntlmssp_state->send_seal_hash, + &ntlmssp_state->send_seal_key); + + dump_data_pw("NTLMSSP send sesl hash:\n", + ntlmssp_state->send_seal_hash, + sizeof(ntlmssp_state->send_seal_hash)); + + /* RECV */ + calc_ntlmv2_key(ntlmssp_state, + &ntlmssp_state->recv_sign_key, + ntlmssp_state->session_key, recv_sign_const); + dump_data_pw("NTLMSSP recv sign key:\n", + ntlmssp_state->recv_sign_key.data, + ntlmssp_state->recv_sign_key.length); + + calc_ntlmv2_key(ntlmssp_state, + &ntlmssp_state->recv_seal_key, + weak_session_key, recv_seal_const); + dump_data_pw("NTLMSSP recv seal key:\n", + ntlmssp_state->recv_seal_key.data, + ntlmssp_state->recv_seal_key.length); + arcfour_init(ntlmssp_state->recv_seal_hash, + &ntlmssp_state->recv_seal_key); + + dump_data_pw("NTLMSSP receive seal hash:\n", + ntlmssp_state->recv_seal_hash, + sizeof(ntlmssp_state->recv_seal_hash)); + } else { + DEBUG(5, ("NTLMSSP Sign/Seal - using NTLM1\n")); + + arcfour_init(ntlmssp_state->ntlmssp_hash, + &ntlmssp_state->session_key); + dump_data_pw("NTLMSSP hash:\n", ntlmssp_state->ntlmssp_hash, + sizeof(ntlmssp_state->ntlmssp_hash)); + } + + ntlmssp_state->ntlm_seq_num = 0; + ntlmssp_state->ntlm2_send_seq_num = 0; + ntlmssp_state->ntlm2_recv_seq_num = 0; + + return NT_STATUS_OK; +} diff --git a/source4/auth/gensec/schannel.c b/source4/auth/gensec/schannel.c new file mode 100644 index 0000000000..0657de27d9 --- /dev/null +++ b/source4/auth/gensec/schannel.c @@ -0,0 +1,268 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_schannel.h" +#include "auth/auth.h" +#include "auth/gensec/schannel.h" + +static size_t schannel_sig_size(struct gensec_security *gensec_security) +{ + return 32; +} + +static NTSTATUS schannel_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +static NTSTATUS schannel_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct schannel_state *state = gensec_security->private_data; + NTSTATUS status; + struct schannel_bind bind_schannel; + struct schannel_bind_ack bind_schannel_ack; + struct creds_CredentialState *creds; + + const char *workstation; + const char *domain; + *out = data_blob(NULL, 0); + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + if (state->state != SCHANNEL_STATE_START) { + /* we could parse the bind ack, but we don't know what it is yet */ + return NT_STATUS_OK; + } + + state->creds = talloc_reference(state, cli_credentials_get_netlogon_creds(gensec_security->credentials)); + + bind_schannel.unknown1 = 0; +#if 0 + /* to support this we'd need to have access to the full domain name */ + bind_schannel.bind_type = 23; + bind_schannel.u.info23.domain = cli_credentials_get_domain(gensec_security->credentials); + bind_schannel.u.info23.account_name = cli_credentials_get_username(gensec_security->credentials); + bind_schannel.u.info23.dnsdomain = str_format_nbt_domain(out_mem_ctx, fulldomainname); + bind_schannel.u.info23.workstation = str_format_nbt_domain(out_mem_ctx, cli_credentials_get_workstation(gensec_security->credentials)); +#else + bind_schannel.bind_type = 3; + bind_schannel.u.info3.domain = cli_credentials_get_domain(gensec_security->credentials); + bind_schannel.u.info3.workstation = cli_credentials_get_workstation(gensec_security->credentials); +#endif + + status = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel, + (ndr_push_flags_fn_t)ndr_push_schannel_bind); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not create schannel bind: %s\n", + nt_errstr(status))); + return status; + } + + state->state = SCHANNEL_STATE_UPDATE_1; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + case GENSEC_SERVER: + + if (state->state != SCHANNEL_STATE_START) { + /* no third leg on this protocol */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the schannel startup blob */ + status = ndr_pull_struct_blob(&in, out_mem_ctx, &bind_schannel, + (ndr_pull_flags_fn_t)ndr_pull_schannel_bind); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (bind_schannel.bind_type == 23) { + workstation = bind_schannel.u.info23.workstation; + domain = bind_schannel.u.info23.domain; + } else { + workstation = bind_schannel.u.info3.workstation; + domain = bind_schannel.u.info3.domain; + } + + /* pull the session key for this client */ + status = schannel_fetch_session_key(out_mem_ctx, workstation, + domain, &creds); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not find session key for attempted schannel connection from %s: %s\n", + workstation, nt_errstr(status))); + return status; + } + + state->creds = talloc_reference(state, creds); + + bind_schannel_ack.unknown1 = 1; + bind_schannel_ack.unknown2 = 0; + bind_schannel_ack.unknown3 = 0x6c0000; + + status = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel_ack, + (ndr_push_flags_fn_t)ndr_push_schannel_bind_ack); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not return schannel bind ack for client %s: %s\n", + workstation, nt_errstr(status))); + return status; + } + + state->state = SCHANNEL_STATE_UPDATE_1; + + return NT_STATUS_OK; + } + return NT_STATUS_INVALID_PARAMETER; +} + +/** + * Return the struct creds_CredentialState. + * + * Make sure not to call this unless gensec is using schannel... + */ + +NTSTATUS dcerpc_schannel_creds(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct creds_CredentialState **creds) +{ + struct schannel_state *state = gensec_security->private_data; + + *creds = talloc_reference(mem_ctx, state->creds); + if (!*creds) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ + +static NTSTATUS schannel_session_info(struct gensec_security *gensec_security, + struct auth_session_info **session_info) +{ + (*session_info) = talloc(gensec_security, struct auth_session_info); + NT_STATUS_HAVE_NO_MEMORY(*session_info); + + ZERO_STRUCTP(*session_info); + + return NT_STATUS_OK; +} + +static NTSTATUS schannel_start(struct gensec_security *gensec_security) +{ + struct schannel_state *state; + + state = talloc(gensec_security, struct schannel_state); + if (!state) { + return NT_STATUS_NO_MEMORY; + } + + state->state = SCHANNEL_STATE_START; + state->seq_num = 0; + gensec_security->private_data = state; + + return NT_STATUS_OK; +} + +static NTSTATUS schannel_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS status; + struct schannel_state *state; + + status = schannel_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + state = gensec_security->private_data; + state->initiator = False; + + return NT_STATUS_OK; +} + +static NTSTATUS schannel_client_start(struct gensec_security *gensec_security) +{ + NTSTATUS status; + struct schannel_state *state; + + status = schannel_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + state = gensec_security->private_data; + state->initiator = True; + + return NT_STATUS_OK; +} + + +static BOOL schannel_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (feature & (GENSEC_FEATURE_SIGN | + GENSEC_FEATURE_SEAL)) { + return True; + } + return False; +} + + +static const struct gensec_security_ops gensec_schannel_security_ops = { + .name = "schannel", + .auth_type = DCERPC_AUTH_TYPE_SCHANNEL, + .client_start = schannel_client_start, + .server_start = schannel_server_start, + .update = schannel_update, + .seal_packet = schannel_seal_packet, + .sign_packet = schannel_sign_packet, + .check_packet = schannel_check_packet, + .unseal_packet = schannel_unseal_packet, + .session_key = schannel_session_key, + .session_info = schannel_session_info, + .sig_size = schannel_sig_size, + .have_feature = schannel_have_feature, + .enabled = True +}; + +NTSTATUS gensec_schannel_init(void) +{ + NTSTATUS ret; + ret = gensec_register(&gensec_schannel_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_schannel_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/schannel.h b/source4/auth/gensec/schannel.h new file mode 100644 index 0000000000..c109387c7c --- /dev/null +++ b/source4/auth/gensec/schannel.h @@ -0,0 +1,35 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +enum schannel_position { + SCHANNEL_STATE_START = 0, + SCHANNEL_STATE_UPDATE_1 +}; + +struct schannel_state { + enum schannel_position state; + uint32_t seq_num; + BOOL initiator; + struct creds_CredentialState *creds; +}; + diff --git a/source4/auth/gensec/schannel_sign.c b/source4/auth/gensec/schannel_sign.c new file mode 100644 index 0000000000..493b26f6c0 --- /dev/null +++ b/source4/auth/gensec/schannel_sign.c @@ -0,0 +1,283 @@ +/* + Unix SMB/CIFS implementation. + + schannel library code + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + + 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "lib/crypto/crypto.h" +#include "auth/auth.h" +#include "auth/gensec/schannel.h" + +#define NETSEC_SIGN_SIGNATURE { 0x77, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 } +#define NETSEC_SEAL_SIGNATURE { 0x77, 0x00, 0x7a, 0x00, 0xff, 0xff, 0x00, 0x00 } + +/******************************************************************* + Encode or Decode the sequence number (which is symmetric) + ********************************************************************/ +static void netsec_deal_with_seq_num(struct schannel_state *state, + const uint8_t packet_digest[8], + uint8_t seq_num[8]) +{ + static const uint8_t zeros[4]; + uint8_t sequence_key[16]; + uint8_t digest1[16]; + + hmac_md5(state->creds->session_key, zeros, sizeof(zeros), digest1); + hmac_md5(digest1, packet_digest, 8, sequence_key); + arcfour_crypt(seq_num, sequence_key, 8); + + state->seq_num++; +} + + +/******************************************************************* + Calculate the key with which to encode the data payload + ********************************************************************/ +static void netsec_get_sealing_key(const uint8_t session_key[16], + const uint8_t seq_num[8], + uint8_t sealing_key[16]) +{ + static const uint8_t zeros[4]; + uint8_t digest2[16]; + uint8_t sess_kf0[16]; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = session_key[i] ^ 0xf0; + } + + hmac_md5(sess_kf0, zeros, 4, digest2); + hmac_md5(digest2, seq_num, 8, sealing_key); +} + + +/******************************************************************* + Create a digest over the entire packet (including the data), and + MD5 it with the session key. + ********************************************************************/ +static void schannel_digest(const uint8_t sess_key[16], + const uint8_t netsec_sig[8], + const uint8_t *confounder, + const uint8_t *data, size_t data_len, + uint8_t digest_final[16]) +{ + uint8_t packet_digest[16]; + static const uint8_t zeros[4]; + struct MD5Context ctx; + + MD5Init(&ctx); + MD5Update(&ctx, zeros, 4); + MD5Update(&ctx, netsec_sig, 8); + if (confounder) { + MD5Update(&ctx, confounder, 8); + } + MD5Update(&ctx, data, data_len); + MD5Final(packet_digest, &ctx); + + hmac_md5(sess_key, packet_digest, sizeof(packet_digest), digest_final); +} + + +/* + unseal a packet +*/ +NTSTATUS schannel_unseal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct schannel_state *state = gensec_security->private_data; + + uint8_t digest_final[16]; + uint8_t confounder[8]; + uint8_t seq_num[8]; + uint8_t sealing_key[16]; + static const uint8_t netsec_sig[8] = NETSEC_SEAL_SIGNATURE; + + if (sig->length != 32) { + return NT_STATUS_ACCESS_DENIED; + } + + memcpy(confounder, sig->data+24, 8); + + RSIVAL(seq_num, 0, state->seq_num); + SIVAL(seq_num, 4, state->initiator?0:0x80); + + netsec_get_sealing_key(state->creds->session_key, seq_num, sealing_key); + arcfour_crypt(confounder, sealing_key, 8); + arcfour_crypt(data, sealing_key, length); + + schannel_digest(state->creds->session_key, + netsec_sig, confounder, + data, length, digest_final); + + if (memcmp(digest_final, sig->data+16, 8) != 0) { + dump_data_pw("calc digest:", digest_final, 8); + dump_data_pw("wire digest:", sig->data+16, 8); + return NT_STATUS_ACCESS_DENIED; + } + + netsec_deal_with_seq_num(state, digest_final, seq_num); + + if (memcmp(seq_num, sig->data+8, 8) != 0) { + dump_data_pw("calc seq num:", seq_num, 8); + dump_data_pw("wire seq num:", sig->data+8, 8); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +/* + check the signature on a packet +*/ +NTSTATUS schannel_check_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct schannel_state *state = gensec_security->private_data; + + uint8_t digest_final[16]; + uint8_t seq_num[8]; + static const uint8_t netsec_sig[8] = NETSEC_SIGN_SIGNATURE; + + /* w2k sends just 24 bytes and skip the confounder */ + if (sig->length != 32 && sig->length != 24) { + return NT_STATUS_ACCESS_DENIED; + } + + RSIVAL(seq_num, 0, state->seq_num); + SIVAL(seq_num, 4, state->initiator?0:0x80); + + dump_data_pw("seq_num:\n", seq_num, 8); + dump_data_pw("sess_key:\n", state->creds->session_key, 16); + + schannel_digest(state->creds->session_key, + netsec_sig, NULL, + data, length, digest_final); + + netsec_deal_with_seq_num(state, digest_final, seq_num); + + if (memcmp(seq_num, sig->data+8, 8) != 0) { + dump_data_pw("calc seq num:", seq_num, 8); + dump_data_pw("wire seq num:", sig->data+8, 8); + return NT_STATUS_ACCESS_DENIED; + } + + if (memcmp(digest_final, sig->data+16, 8) != 0) { + dump_data_pw("calc digest:", digest_final, 8); + dump_data_pw("wire digest:", sig->data+16, 8); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + + +/* + seal a packet +*/ +NTSTATUS schannel_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct schannel_state *state = gensec_security->private_data; + + uint8_t digest_final[16]; + uint8_t confounder[8]; + uint8_t seq_num[8]; + uint8_t sealing_key[16]; + static const uint8_t netsec_sig[8] = NETSEC_SEAL_SIGNATURE; + + generate_random_buffer(confounder, 8); + + RSIVAL(seq_num, 0, state->seq_num); + SIVAL(seq_num, 4, state->initiator?0x80:0); + + schannel_digest(state->creds->session_key, + netsec_sig, confounder, + data, length, digest_final); + + netsec_get_sealing_key(state->creds->session_key, seq_num, sealing_key); + arcfour_crypt(confounder, sealing_key, 8); + arcfour_crypt(data, sealing_key, length); + + netsec_deal_with_seq_num(state, digest_final, seq_num); + + (*sig) = data_blob_talloc(mem_ctx, NULL, 32); + + memcpy(sig->data, netsec_sig, 8); + memcpy(sig->data+8, seq_num, 8); + memcpy(sig->data+16, digest_final, 8); + memcpy(sig->data+24, confounder, 8); + + dump_data_pw("signature:", sig->data+ 0, 8); + dump_data_pw("seq_num :", sig->data+ 8, 8); + dump_data_pw("digest :", sig->data+16, 8); + dump_data_pw("confound :", sig->data+24, 8); + + return NT_STATUS_OK; +} + + +/* + sign a packet +*/ +NTSTATUS schannel_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct schannel_state *state = gensec_security->private_data; + + uint8_t digest_final[16]; + uint8_t seq_num[8]; + static const uint8_t netsec_sig[8] = NETSEC_SIGN_SIGNATURE; + + RSIVAL(seq_num, 0, state->seq_num); + SIVAL(seq_num, 4, state->initiator?0x80:0); + + schannel_digest(state->creds->session_key, + netsec_sig, NULL, + data, length, digest_final); + + netsec_deal_with_seq_num(state, digest_final, seq_num); + + (*sig) = data_blob_talloc(mem_ctx, NULL, 32); + + memcpy(sig->data, netsec_sig, 8); + memcpy(sig->data+8, seq_num, 8); + memcpy(sig->data+16, digest_final, 8); + memset(sig->data+24, 0, 8); + + dump_data_pw("signature:", sig->data+ 0, 8); + dump_data_pw("seq_num :", sig->data+ 8, 8); + dump_data_pw("digest :", sig->data+16, 8); + dump_data_pw("confound :", sig->data+24, 8); + + return NT_STATUS_OK; +} diff --git a/source4/auth/gensec/schannel_state.c b/source4/auth/gensec/schannel_state.c new file mode 100644 index 0000000000..b2d632a1f0 --- /dev/null +++ b/source4/auth/gensec/schannel_state.c @@ -0,0 +1,229 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel server + + Copyright (C) Andrew Tridgell 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 + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "system/time.h" +#include "auth/auth.h" +#include "lib/ldb/include/ldb.h" +#include "db_wrap.h" + +/* a reasonable amount of time to keep credentials live */ +#define SCHANNEL_CREDENTIALS_EXPIRY 600 + +/* + connect to the schannel ldb +*/ +static struct ldb_context *schannel_db_connect(TALLOC_CTX *mem_ctx) +{ + char *path; + struct ldb_context *ldb; + + path = smbd_tmp_path(mem_ctx, "schannel.ldb"); + if (!path) { + return NULL; + } + + ldb = ldb_wrap_connect(mem_ctx, path, 0, NULL); + talloc_free(path); + if (!ldb) { + return NULL; + } + + return ldb; +} + +/* + remember an established session key for a netr server authentication + use a simple ldb structure +*/ +NTSTATUS schannel_store_session_key(TALLOC_CTX *mem_ctx, + struct creds_CredentialState *creds) +{ + struct ldb_context *ldb; + struct ldb_message *msg; + struct ldb_val val, seed; + char *s; + char *f; + char *sct; + char *rid; + time_t expiry = time(NULL) + SCHANNEL_CREDENTIALS_EXPIRY; + int ret; + + ldb = schannel_db_connect(mem_ctx); + if (ldb == NULL) { + return NT_STATUS_NO_MEMORY; + } + + s = talloc_asprintf(mem_ctx, "%u", (unsigned int)expiry); + + if (s == NULL) { + talloc_free(ldb); + return NT_STATUS_NO_MEMORY; + } + + f = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->negotiate_flags); + + if (f == NULL) { + talloc_free(ldb); + return NT_STATUS_NO_MEMORY; + } + + sct = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->secure_channel_type); + + if (sct == NULL) { + talloc_free(ldb); + return NT_STATUS_NO_MEMORY; + } + + rid = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->rid); + + if (rid == NULL) { + talloc_free(ldb); + return NT_STATUS_NO_MEMORY; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + talloc_free(ldb); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = talloc_asprintf(msg, "computerName=%s", creds->computer_name); + if (msg->dn == NULL) { + talloc_free(ldb); + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + + val.data = creds->session_key; + val.length = sizeof(creds->session_key); + + seed.data = creds->seed.data; + seed.length = sizeof(creds->seed.data); + + ldb_msg_add_value(ldb, msg, "sessionKey", &val); + ldb_msg_add_value(ldb, msg, "seed", &seed); + ldb_msg_add_string(ldb, msg, "expiry", s); + ldb_msg_add_string(ldb, msg, "negotiateFlags", f); + ldb_msg_add_string(ldb, msg, "secureChannelType", sct); + ldb_msg_add_string(ldb, msg, "accountName", creds->account_name); + ldb_msg_add_string(ldb, msg, "computerName", creds->computer_name); + ldb_msg_add_string(ldb, msg, "flatname", creds->domain); + ldb_msg_add_string(ldb, msg, "rid", rid); + + ldb_delete(ldb, msg->dn); + + ret = ldb_add(ldb, msg); + + talloc_free(s); + + if (ret != 0) { + DEBUG(0,("Unable to add %s to session key db - %s\n", + msg->dn, ldb_errstring(ldb))); + talloc_free(ldb); + talloc_free(msg); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + talloc_free(msg); + talloc_free(ldb); + + return NT_STATUS_OK; +} + + +/* + read back a credentials back for a computer +*/ +NTSTATUS schannel_fetch_session_key(TALLOC_CTX *mem_ctx, + const char *computer_name, + const char *domain, + struct creds_CredentialState **creds) +{ + struct ldb_context *ldb; + time_t expiry; + struct ldb_message **res; + int ret; + const struct ldb_val *val; + char *expr=NULL; + + *creds = talloc_zero(mem_ctx, struct creds_CredentialState); + if (!*creds) { + return NT_STATUS_NO_MEMORY; + } + + ldb = schannel_db_connect(mem_ctx); + if (ldb == NULL) { + return NT_STATUS_NO_MEMORY; + } + + expr = talloc_asprintf(mem_ctx, "(&(computerName=%s)(flatname=%s))", computer_name, domain); + if (expr == NULL) { + talloc_free(ldb); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_search(ldb, NULL, LDB_SCOPE_SUBTREE, expr, NULL, &res); + if (ret != 1) { + talloc_free(ldb); + return NT_STATUS_INVALID_HANDLE; + } + + expiry = ldb_msg_find_uint(res[0], "expiry", 0); + if (expiry < time(NULL)) { + DEBUG(1,("schannel: attempt to use expired session key for %s\n", computer_name)); + talloc_free(ldb); + return NT_STATUS_INVALID_HANDLE; + } + + val = ldb_msg_find_ldb_val(res[0], "sessionKey"); + if (val == NULL || val->length != 16) { + talloc_free(ldb); + return NT_STATUS_INVALID_HANDLE; + } + + memcpy((*creds)->session_key, val->data, 16); + + val = ldb_msg_find_ldb_val(res[0], "seed"); + if (val == NULL || val->length != 8) { + talloc_free(ldb); + return NT_STATUS_INVALID_HANDLE; + } + + memcpy((*creds)->seed.data, val->data, 8); + + (*creds)->negotiate_flags = ldb_msg_find_int(res[0], "negotiateFlags", 0); + + (*creds)->secure_channel_type = ldb_msg_find_int(res[0], "secureChannelType", 0); + + (*creds)->account_name = talloc_reference(*creds, ldb_msg_find_string(res[0], "accountName", NULL)); + + (*creds)->computer_name = talloc_reference(*creds, ldb_msg_find_string(res[0], "computerName", NULL)); + + (*creds)->domain = talloc_reference(*creds, ldb_msg_find_string(res[0], "flatname", NULL)); + + (*creds)->rid = ldb_msg_find_uint(res[0], "rid", 0); + + talloc_free(ldb); + + return NT_STATUS_OK; +} diff --git a/source4/auth/gensec/spnego.c b/source4/auth/gensec/spnego.c new file mode 100644 index 0000000000..f5a091cd78 --- /dev/null +++ b/source4/auth/gensec/spnego.c @@ -0,0 +1,884 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + 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 + the Free Software Foundation; either version 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "auth/auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +enum spnego_state_position { + SPNEGO_SERVER_START, + SPNEGO_CLIENT_START, + SPNEGO_SERVER_TARG, + SPNEGO_CLIENT_TARG, + SPNEGO_FALLBACK, + SPNEGO_DONE +}; + +struct spnego_state { + uint_t ref_count; + enum spnego_message_type expected_packet; + enum spnego_state_position state_position; + struct gensec_security *sub_sec_security; + BOOL no_response_expected; +}; + + +static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state; + + spnego_state = talloc(gensec_security, struct spnego_state); + if (!spnego_state) { + return NT_STATUS_NO_MEMORY; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + spnego_state->state_position = SPNEGO_CLIENT_START; + spnego_state->sub_sec_security = NULL; + spnego_state->no_response_expected = False; + + gensec_security->private_data = spnego_state; + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state; + + spnego_state = talloc(gensec_security, struct spnego_state); + if (!spnego_state) { + return NT_STATUS_NO_MEMORY; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + spnego_state->state_position = SPNEGO_SERVER_START; + spnego_state->sub_sec_security = NULL; + spnego_state->no_response_expected = False; + + gensec_security->private_data = spnego_state; + return NT_STATUS_OK; +} + +/* + wrappers for the spnego_*() functions +*/ +static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + 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; + } + + return gensec_unseal_packet(spnego_state->sub_sec_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + 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; + } + + return gensec_check_packet(spnego_state->sub_sec_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + 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; + } + + return gensec_seal_packet(spnego_state->sub_sec_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + 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; + } + + return gensec_sign_packet(spnego_state->sub_sec_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +static NTSTATUS gensec_spnego_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + DEBUG(1, ("gensec_spnego_wrap: wrong state for wrap\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_wrap(spnego_state->sub_sec_security, + mem_ctx, in, out); +} + +static NTSTATUS gensec_spnego_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + DEBUG(1, ("gensec_spnego_unwrap: wrong state for unwrap\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_unwrap(spnego_state->sub_sec_security, + mem_ctx, in, out); +} + +static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return 0; + } + + return gensec_sig_size(spnego_state->sub_sec_security); +} + +static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, + DATA_BLOB *session_key) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + if (!spnego_state->sub_sec_security) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_session_key(spnego_state->sub_sec_security, + session_key); +} + +static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security, + struct auth_session_info **session_info) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + if (!spnego_state->sub_sec_security) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_session_info(spnego_state->sub_sec_security, + session_info); +} + +/** Fallback to another GENSEC mechanism, based on magic strings + * + * This is the 'fallback' case, where we don't get SPNEGO, and have to + * try all the other options (and hope they all have a magic string + * they check) +*/ + +static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + int i; + int num_ops; + const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops); + for (i=0; i < num_ops; i++) { + NTSTATUS nt_status; + if (!all_ops[i]->oid) { + continue; + } + if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[i]->oid) == 0) { + continue; + } + + nt_status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + /* select the sub context */ + nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + all_ops[i]->oid); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + continue; + } + nt_status = gensec_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; + } + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + } + DEBUG(1, ("Failed to parse SPNEGO request\n")); + return NT_STATUS_INVALID_PARAMETER; + +} + +/* + Parse the netTokenInit from the client, to the server. + + +*/ + +static NTSTATUS gensec_spnego_server_parse_negTokenInit(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + const char **mechType, + const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out) +{ + NTSTATUS nt_status; + + if (!mechType || !mechType[0]) { + DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + /* select the sub context */ + nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + mechType[0]); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + return nt_status; + } + + if (!unwrapped_in.length) { + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_update(spnego_state->sub_sec_security, + out_mem_ctx, + unwrapped_in, + unwrapped_out); + + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n", + spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + } + return nt_status; +} + +static NTSTATUS gensec_spnego_client_parse_negTokenInit(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + const char **mechType, + const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out) +{ + int i; + NTSTATUS nt_status; + DATA_BLOB null_data_blob = data_blob(NULL,0); + + for (i=0; mechType && mechType[i]; i++) { + nt_status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + break; + } + /* select the sub context */ + nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + mechType[i]); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + continue; + } + + if (i == 0) { + nt_status = gensec_update(spnego_state->sub_sec_security, + out_mem_ctx, + unwrapped_in, + unwrapped_out); + } else { + /* only get the helping start blob for the first OID */ + nt_status = gensec_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) && !NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n", + spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status))); + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + } + return nt_status; + } + if (!mechType || !mechType[i]) { + DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); + } + return NT_STATUS_INVALID_PARAMETER; +} + +/** create a client negTokenInit + * + * This is the case, where the client is the first one who sends data +*/ + +static NTSTATUS gensec_spnego_client_negTokenInit(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + DATA_BLOB null_data_blob = data_blob(NULL,0); + NTSTATUS nt_status; + const char **mechTypes = NULL; + DATA_BLOB unwrapped_out = data_blob(NULL,0); + + mechTypes = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO); + + if (!mechTypes) { + DEBUG(1, ("no GENSEC OID backends available\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + /* select our preferred mech */ + nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + mechTypes[0]); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + return nt_status; + } + nt_status = gensec_update(spnego_state->sub_sec_security, + out_mem_ctx, in, &unwrapped_out); + if (NT_STATUS_IS_OK(nt_status) || NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + struct spnego_data spnego_out; + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + spnego_out.negTokenInit.mechTypes = mechTypes; + spnego_out.negTokenInit.reqFlags = 0; + spnego_out.negTokenInit.mechListMIC = null_data_blob; + spnego_out.negTokenInit.mechToken = unwrapped_out; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_CLIENT_TARG; + + if (NT_STATUS_IS_OK(nt_status)) { + spnego_state->no_response_expected = True; + } + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + + DEBUG(1, ("Failed to setup SPNEGO negTokenInit request: %s\n", nt_errstr(nt_status))); + return NT_STATUS_INVALID_PARAMETER; +} + + +/** create a client negTokenTarg + * + * This is the case, where the client is the first one who sends data +*/ + +static NTSTATUS gensec_spnego_server_negTokenTarg(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + NTSTATUS nt_status, + const DATA_BLOB unwrapped_out, DATA_BLOB *out) +{ + struct spnego_data spnego_out; + DATA_BLOB null_data_blob = data_blob(NULL, 0); + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.responseToken = unwrapped_out; + spnego_out.negTokenTarg.mechListMIC = null_data_blob; + spnego_out.negTokenTarg.supportedMech = NULL; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + spnego_out.negTokenTarg.supportedMech + = spnego_state->sub_sec_security->ops->oid; + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; + spnego_state->state_position = SPNEGO_SERVER_TARG; + } else if (NT_STATUS_IS_OK(nt_status)) { + if (unwrapped_out.data) { + spnego_out.negTokenTarg.supportedMech + = spnego_state->sub_sec_security->ops->oid; + } + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED; + spnego_state->state_position = SPNEGO_DONE; + } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER)) { + if (spnego_state->sub_sec_security) { + /* we have a mech, but we just didn't get the input parameter */ + spnego_out.negTokenTarg.supportedMech + = spnego_state->sub_sec_security->ops->oid; + } else { + const char **mechTypes = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO); + if (!mechTypes) { + DEBUG(1, ("no GENSEC OID backends available\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + /* select our preferred mech */ + nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + mechTypes[0]); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(spnego_state->sub_sec_security); + spnego_state->sub_sec_security = NULL; + return nt_status; + } + + /* we should be sending the whole list here */ + spnego_out.negTokenTarg.supportedMech = mechTypes[0]; + } + + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; + spnego_state->state_position = SPNEGO_SERVER_TARG; + nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + spnego_out.negTokenTarg.negResult = SPNEGO_REJECT; + DEBUG(2, ("SPNEGO login failed: %s\n", nt_errstr(nt_status))); + spnego_state->state_position = SPNEGO_DONE; + } + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + + return nt_status; +} + + +static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + DATA_BLOB null_data_blob = data_blob(NULL, 0); + DATA_BLOB unwrapped_out = data_blob(NULL, 0); + struct spnego_data spnego_out; + struct spnego_data spnego; + + ssize_t len; + + *out = data_blob(NULL, 0); + + if (!out_mem_ctx) { + out_mem_ctx = spnego_state; + } + + /* and switch into the state machine */ + + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + return gensec_update(spnego_state->sub_sec_security, + out_mem_ctx, in, out); + case SPNEGO_SERVER_START: + { + if (in.length) { + NTSTATUS nt_status; + + len = spnego_read_data(in, &spnego); + if (len == -1) { + return gensec_spnego_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, in, out); + } + /* client sent NegTargetInit, we send NegTokenTarg */ + + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (spnego.type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, + spnego_state->expected_packet)); + dump_data(1, in.data, in.length); + spnego_free_data(&spnego); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_spnego_server_parse_negTokenInit(gensec_security, + spnego_state, + out_mem_ctx, + spnego.negTokenInit.mechTypes, + spnego.negTokenInit.mechToken, + &unwrapped_out); + + nt_status = gensec_spnego_server_negTokenTarg(gensec_security, + spnego_state, + out_mem_ctx, + nt_status, + unwrapped_out, + out); + + spnego_free_data(&spnego); + + return nt_status; + } else { + const char **mechlist = gensec_security_oids(out_mem_ctx, GENSEC_OID_SPNEGO); + + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + spnego_out.negTokenInit.mechTypes = mechlist; + spnego_out.negTokenInit.reqFlags = 0; + spnego_out.negTokenInit.mechListMIC + = data_blob_string_const(talloc_asprintf(out_mem_ctx, "%s$@%s", lp_netbios_name(), lp_realm())); + spnego_out.negTokenInit.mechToken = unwrapped_out; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_SERVER_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + } + + case SPNEGO_CLIENT_START: + { + /* The server offers a list of mechanisms */ + + const char *my_mechs[] = {NULL, NULL}; + NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; + + if (!in.length) { + /* client to produce negTokenInit */ + return gensec_spnego_client_negTokenInit(gensec_security, spnego_state, + out_mem_ctx, in, out); + } + + len = spnego_read_data(in, &spnego); + + if (len == -1) { + DEBUG(1, ("Invalid SPNEGO request:\n")); + dump_data(1, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (spnego.type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, + spnego_state->expected_packet)); + dump_data(1, in.data, in.length); + spnego_free_data(&spnego); + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego.negTokenInit.targetPrincipal) { + DEBUG(5, ("Server claims it's principal name is %s (ignored)\n", spnego.negTokenInit.targetPrincipal)); + } + + nt_status = gensec_spnego_client_parse_negTokenInit(gensec_security, + spnego_state, + out_mem_ctx, + spnego.negTokenInit.mechTypes, + spnego.negTokenInit.mechToken, + &unwrapped_out); + + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) { + spnego_free_data(&spnego); + return nt_status; + } + + /* compose reply */ + my_mechs[0] = spnego_state->sub_sec_security->ops->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 (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_CLIENT_TARG; + + if (NT_STATUS_IS_OK(nt_status)) { + spnego_state->no_response_expected = True; + } + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + case SPNEGO_SERVER_TARG: + { + NTSTATUS nt_status; + if (!in.length) { + return NT_STATUS_INVALID_PARAMETER; + } + + len = spnego_read_data(in, &spnego); + + if (len == -1) { + DEBUG(1, ("Invalid SPNEGO request:\n")); + dump_data(1, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (spnego.type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, + spnego_state->expected_packet)); + dump_data(1, in.data, in.length); + spnego_free_data(&spnego); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!spnego_state->sub_sec_security) { + DEBUG(1, ("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = gensec_update(spnego_state->sub_sec_security, + out_mem_ctx, + spnego.negTokenTarg.responseToken, + &unwrapped_out); + + nt_status = gensec_spnego_server_negTokenTarg(gensec_security, + spnego_state, + out_mem_ctx, + nt_status, + unwrapped_out, + out); + + spnego_free_data(&spnego); + + return nt_status; + } + case SPNEGO_CLIENT_TARG: + { + NTSTATUS nt_status; + if (!in.length) { + return NT_STATUS_INVALID_PARAMETER; + } + + len = spnego_read_data(in, &spnego); + + if (len == -1) { + DEBUG(1, ("Invalid SPNEGO request:\n")); + dump_data(1, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (spnego.type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", spnego.type, + spnego_state->expected_packet)); + dump_data(1, in.data, in.length); + spnego_free_data(&spnego); + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) { + return NT_STATUS_ACCESS_DENIED; + } + + if (spnego_state->no_response_expected) { + if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) { + DEBUG(3,("GENSEC SPNEGO: client GENSEC accepted, but server rejected (bad password?)\n")); + nt_status = NT_STATUS_INVALID_PARAMETER; + } else if (spnego.negTokenTarg.responseToken.length) { + DEBUG(2,("GENSEC SPNEGO: client GENSEC accepted, but server continued negotiation!\n")); + nt_status = NT_STATUS_INVALID_PARAMETER; + } else { + nt_status = NT_STATUS_OK; + } + } else { + nt_status = gensec_update(spnego_state->sub_sec_security, + out_mem_ctx, + spnego.negTokenTarg.responseToken, + &unwrapped_out); + + if (NT_STATUS_IS_OK(nt_status)) { + spnego_state->no_response_expected = True; + } + } + + spnego_free_data(&spnego); + + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) + && !NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(nt_status))); + return nt_status; + } + + if (unwrapped_out.length) { + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT; + spnego_out.negTokenTarg.supportedMech = NULL; + spnego_out.negTokenTarg.responseToken = unwrapped_out; + spnego_out.negTokenTarg.mechListMIC = null_data_blob; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->state_position = SPNEGO_CLIENT_TARG; + nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else { + + /* all done - server has accepted, and we agree */ + *out = null_data_blob; + + if (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED) { + /* unless of course it did not accept */ + DEBUG(1,("gensec_update ok but not accepted\n")); + nt_status = NT_STATUS_INVALID_PARAMETER; + } + } + + spnego_state->state_position = SPNEGO_DONE; + + return nt_status; + } + case SPNEGO_DONE: + return NT_STATUS_OK; + } + return NT_STATUS_INVALID_PARAMETER; +} + +static BOOL gensec_spnego_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + if (!spnego_state->sub_sec_security) { + return False; + } + + return gensec_have_feature(spnego_state->sub_sec_security, + feature); +} + +static const struct gensec_security_ops gensec_spnego_security_ops = { + .name = "spnego", + .sasl_name = "GSS-SPNEGO", + .auth_type = DCERPC_AUTH_TYPE_SPNEGO, + .oid = GENSEC_OID_SPNEGO, + .client_start = gensec_spnego_client_start, + .server_start = gensec_spnego_server_start, + .update = gensec_spnego_update, + .seal_packet = gensec_spnego_seal_packet, + .sign_packet = gensec_spnego_sign_packet, + .sig_size = gensec_spnego_sig_size, + .check_packet = gensec_spnego_check_packet, + .unseal_packet = gensec_spnego_unseal_packet, + .wrap = gensec_spnego_wrap, + .unwrap = gensec_spnego_unwrap, + .session_key = gensec_spnego_session_key, + .session_info = gensec_spnego_session_info, + .have_feature = gensec_spnego_have_feature, + .enabled = True +}; + +NTSTATUS gensec_spnego_init(void) +{ + NTSTATUS ret; + ret = gensec_register(&gensec_spnego_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_spnego_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/source4/auth/gensec/spnego.h b/source4/auth/gensec/spnego.h new file mode 100644 index 0000000000..1064370146 --- /dev/null +++ b/source4/auth/gensec/spnego.h @@ -0,0 +1,69 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef SAMBA_SPNEGO_H +#define SAMBA_SPNEGO_H + +#define SPNEGO_DELEG_FLAG 0x01 +#define SPNEGO_MUTUAL_FLAG 0x02 +#define SPNEGO_REPLAY_FLAG 0x04 +#define SPNEGO_SEQUENCE_FLAG 0x08 +#define SPNEGO_ANON_FLAG 0x10 +#define SPNEGO_CONF_FLAG 0x20 +#define SPNEGO_INTEG_FLAG 0x40 +#define SPNEGO_REQ_FLAG 0x80 + +enum spnego_negResult { + SPNEGO_ACCEPT_COMPLETED = 0, + SPNEGO_ACCEPT_INCOMPLETE = 1, + SPNEGO_REJECT = 2, + SPNEGO_NONE_RESULT = 3 +}; + +struct spnego_negTokenInit { + const char **mechTypes; + int reqFlags; + DATA_BLOB mechToken; + DATA_BLOB mechListMIC; + char *targetPrincipal; +}; + +struct spnego_negTokenTarg { + uint8_t negResult; + const char *supportedMech; + DATA_BLOB responseToken; + DATA_BLOB mechListMIC; +}; + +struct spnego_data { + int type; + struct spnego_negTokenInit negTokenInit; + struct spnego_negTokenTarg negTokenTarg; +}; + +enum spnego_message_type { + SPNEGO_NEG_TOKEN_INIT = 0, + SPNEGO_NEG_TOKEN_TARG = 1, +}; + +#endif diff --git a/source4/auth/gensec/spnego_parse.c b/source4/auth/gensec/spnego_parse.c new file mode 100644 index 0000000000..e48c32f0da --- /dev/null +++ b/source4/auth/gensec/spnego_parse.c @@ -0,0 +1,375 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 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 + (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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" +#include "auth/auth.h" +#include "asn_1.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +static BOOL read_negTokenInit(struct asn1_data *asn1, struct spnego_negTokenInit *token) +{ + 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; + uint8_t context; + if (!asn1_peek_uint8(asn1, &context)) { + asn1->has_error = True; + break; + } + + switch (context) { + /* Read mechTypes */ + case ASN1_CONTEXT(0): + asn1_start_tag(asn1, ASN1_CONTEXT(0)); + asn1_start_tag(asn1, ASN1_SEQUENCE(0)); + + token->mechTypes = talloc(NULL, const char *); + for (i = 0; !asn1->has_error && + 0 < asn1_tag_remaining(asn1); i++) { + token->mechTypes = talloc_realloc(NULL, + token->mechTypes, + const char *, i+2); + asn1_read_OID(asn1, token->mechTypes + i); + if (token->mechTypes[i]) { + talloc_steal(token->mechTypes, + 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): + { + uint8_t type_peek; + asn1_start_tag(asn1, ASN1_CONTEXT(3)); + if (!asn1_peek_uint8(asn1, &type_peek)) { + asn1->has_error = True; + break; + } + if (type_peek == 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->targetPrincipal = mechListMIC; + } + asn1_end_tag(asn1); + break; + } + default: + asn1->has_error = True; + break; + } + } + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + + return !asn1->has_error; +} + +static BOOL write_negTokenInit(struct asn1_data *asn1, struct spnego_negTokenInit *token) +{ + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + + /* 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); + } + + /* write reqFlags */ + if (token->reqFlags & SPNEGO_REQ_FLAG) { + int flags = token->reqFlags & ~SPNEGO_REQ_FLAG; + + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_write_Integer(asn1, flags); + asn1_pop_tag(asn1); + } + + /* 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); + } + + /* 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); + } + + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + return !asn1->has_error; +} + +static BOOL read_negTokenTarg(struct asn1_data *asn1, struct spnego_negTokenTarg *token) +{ + 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)) { + uint8_t context; + if (!asn1_peek_uint8(asn1, &context)) { + asn1->has_error = True; + break; + } + + switch (context) { + 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; + } + } + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + + return !asn1->has_error; +} + +static BOOL write_negTokenTarg(struct asn1_data *asn1, struct spnego_negTokenTarg *token) +{ + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_push_tag(asn1, ASN1_SEQUENCE(0)); + + if (token->negResult != SPNEGO_NONE_RESULT) { + asn1_push_tag(asn1, ASN1_CONTEXT(0)); + asn1_write_enumerated(asn1, token->negResult); + asn1_pop_tag(asn1); + } + + if (token->supportedMech) { + asn1_push_tag(asn1, ASN1_CONTEXT(1)); + asn1_write_OID(asn1, token->supportedMech); + asn1_pop_tag(asn1); + } + + 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 (token->mechListMIC.data) { + asn1_push_tag(asn1, ASN1_CONTEXT(3)); + asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length); + asn1_pop_tag(asn1); + } + + asn1_pop_tag(asn1); + asn1_pop_tag(asn1); + + return !asn1->has_error; +} + +ssize_t spnego_read_data(DATA_BLOB data, struct spnego_data *token) +{ + struct asn1_data asn1; + ssize_t ret = -1; + uint8_t context; + + ZERO_STRUCTP(token); + ZERO_STRUCT(asn1); + + if (data.length == 0) { + return ret; + } + + asn1_load(&asn1, data); + + if (!asn1_peek_uint8(&asn1, &context)) { + asn1.has_error = True; + } else { + switch (context) { + case ASN1_APPLICATION(0): + asn1_start_tag(&asn1, ASN1_APPLICATION(0)); + asn1_check_OID(&asn1, GENSEC_OID_SPNEGO); + if (read_negTokenInit(&asn1, &token->negTokenInit)) { + token->type = SPNEGO_NEG_TOKEN_INIT; + } + asn1_end_tag(&asn1); + break; + case ASN1_CONTEXT(1): + if (read_negTokenTarg(&asn1, &token->negTokenTarg)) { + token->type = SPNEGO_NEG_TOKEN_TARG; + } + break; + default: + asn1.has_error = True; + break; + } + } + + if (!asn1.has_error) ret = asn1.ofs; + asn1_free(&asn1); + + return ret; +} + +ssize_t spnego_write_data(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct spnego_data *spnego) +{ + struct 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, GENSEC_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; + } + + if (!asn1.has_error) { + *blob = data_blob_talloc(mem_ctx, asn1.data, asn1.length); + ret = asn1.ofs; + } + asn1_free(&asn1); + + return ret; +} + +BOOL spnego_free_data(struct spnego_data *spnego) +{ + BOOL ret = True; + + if (!spnego) goto out; + + switch(spnego->type) { + case SPNEGO_NEG_TOKEN_INIT: + if (spnego->negTokenInit.mechTypes) { + talloc_free(spnego->negTokenInit.mechTypes); + } + data_blob_free(&spnego->negTokenInit.mechToken); + data_blob_free(&spnego->negTokenInit.mechListMIC); + talloc_free(spnego->negTokenInit.targetPrincipal); + break; + case SPNEGO_NEG_TOKEN_TARG: + if (spnego->negTokenTarg.supportedMech) { + talloc_free(discard_const(spnego->negTokenTarg.supportedMech)); + } + data_blob_free(&spnego->negTokenTarg.responseToken); + data_blob_free(&spnego->negTokenTarg.mechListMIC); + break; + default: + ret = False; + break; + } + ZERO_STRUCTP(spnego); +out: + return ret; +} + |