diff options
Diffstat (limited to 'libcli')
-rw-r--r-- | libcli/auth/config.mk | 31 | ||||
-rw-r--r-- | libcli/auth/credentials.c | 446 | ||||
-rw-r--r-- | libcli/auth/credentials.h | 84 | ||||
-rw-r--r-- | libcli/auth/libcli_auth.h | 32 | ||||
-rw-r--r-- | libcli/auth/msrpc_parse.c | 368 | ||||
-rw-r--r-- | libcli/auth/ntlm_check.c | 596 | ||||
-rw-r--r-- | libcli/auth/ntlm_check.h | 76 | ||||
-rw-r--r-- | libcli/auth/schannel_state.c | 321 | ||||
-rw-r--r-- | libcli/auth/schannel_state.h | 24 | ||||
-rw-r--r-- | libcli/auth/session.c | 218 | ||||
-rw-r--r-- | libcli/auth/smbdes.c | 381 | ||||
-rw-r--r-- | libcli/auth/smbencrypt.c | 782 | ||||
-rw-r--r-- | libcli/drsuapi/config.mk | 8 | ||||
-rw-r--r-- | libcli/drsuapi/drsuapi.h | 33 | ||||
-rw-r--r-- | libcli/drsuapi/repl_decrypt.c | 188 | ||||
-rw-r--r-- | libcli/samsync/config.mk | 10 | ||||
-rw-r--r-- | libcli/samsync/decrypt.c | 174 |
17 files changed, 3772 insertions, 0 deletions
diff --git a/libcli/auth/config.mk b/libcli/auth/config.mk new file mode 100644 index 0000000000..103402099d --- /dev/null +++ b/libcli/auth/config.mk @@ -0,0 +1,31 @@ +[SUBSYSTEM::ntlm_check] +PRIVATE_DEPENDENCIES = LIBSAMBA-UTIL + +ntlm_check_OBJ_FILES = $(addprefix $(libclicommonsrcdir)/auth/, ntlm_check.o) + +[SUBSYSTEM::MSRPC_PARSE] + +MSRPC_PARSE_OBJ_FILES = $(addprefix $(libclicommonsrcdir)/auth/, msrpc_parse.o) + +$(eval $(call proto_header_template,$(libclicommonsrcdir)/auth/msrpc_parse.h,$(MSRPC_PARSE_OBJ_FILES:.o=.c))) + +[SUBSYSTEM::LIBCLI_AUTH] +PUBLIC_DEPENDENCIES = \ + MSRPC_PARSE \ + LIBSAMBA-HOSTCONFIG + +LIBCLI_AUTH_OBJ_FILES = $(addprefix $(libclicommonsrcdir)/auth/, \ + credentials.o \ + session.o \ + smbencrypt.o \ + smbdes.o) + +PUBLIC_HEADERS += ../libcli/auth/credentials.h +$(eval $(call proto_header_template,$(libclicommonsrcdir)/auth/proto.h,$(LIBCLI_AUTH_OBJ_FILES:.o=.c))) + +[SUBSYSTEM::COMMON_SCHANNELDB] +PRIVATE_DEPENDENCIES = LDB_WRAP + +COMMON_SCHANNELDB_OBJ_FILES = $(addprefix $(libclicommonsrcdir)/auth/, schannel_state.o) +$(eval $(call proto_header_template,$(libclicommonsrcdir)/auth/schannel_state_proto.h,$(COMMON_SCHANNELDB_OBJ_FILES:.o=.c))) + diff --git a/libcli/auth/credentials.c b/libcli/auth/credentials.c new file mode 100644 index 0000000000..dc84ffb474 --- /dev/null +++ b/libcli/auth/credentials.c @@ -0,0 +1,446 @@ +/* + Unix SMB/CIFS implementation. + + code to manipulate domain credentials + + Copyright (C) Andrew Tridgell 1997-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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "../lib/crypto/crypto.h" +#include "libcli/auth/libcli_auth.h" + +/* + initialise the credentials state for old-style 64 bit session keys + + this call is made after the netr_ServerReqChallenge call +*/ +static void netlogon_creds_init_64bit(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password) +{ + uint32_t sum[2]; + uint8_t sum2[8]; + + sum[0] = IVAL(client_challenge->data, 0) + IVAL(server_challenge->data, 0); + sum[1] = IVAL(client_challenge->data, 4) + IVAL(server_challenge->data, 4); + + SIVAL(sum2,0,sum[0]); + SIVAL(sum2,4,sum[1]); + + ZERO_STRUCT(creds->session_key); + + des_crypt128(creds->session_key, sum2, machine_password->hash); + + des_crypt112(creds->client.data, client_challenge->data, creds->session_key, 1); + des_crypt112(creds->server.data, server_challenge->data, creds->session_key, 1); + + creds->seed = creds->client; +} + +/* + initialise the credentials state for ADS-style 128 bit session keys + + this call is made after the netr_ServerReqChallenge call +*/ +static void netlogon_creds_init_128bit(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password) +{ + unsigned char zero[4], tmp[16]; + HMACMD5Context ctx; + struct MD5Context md5; + + ZERO_STRUCT(creds->session_key); + + memset(zero, 0, sizeof(zero)); + + hmac_md5_init_rfc2104(machine_password->hash, sizeof(machine_password->hash), &ctx); + MD5Init(&md5); + MD5Update(&md5, zero, sizeof(zero)); + MD5Update(&md5, client_challenge->data, 8); + MD5Update(&md5, server_challenge->data, 8); + MD5Final(tmp, &md5); + hmac_md5_update(tmp, sizeof(tmp), &ctx); + hmac_md5_final(creds->session_key, &ctx); + + creds->client = *client_challenge; + creds->server = *server_challenge; + + des_crypt112(creds->client.data, client_challenge->data, creds->session_key, 1); + des_crypt112(creds->server.data, server_challenge->data, creds->session_key, 1); + + creds->seed = creds->client; +} + + +/* + step the credentials to the next element in the chain, updating the + current client and server credentials and the seed +*/ +static void netlogon_creds_step(struct netlogon_creds_CredentialState *creds) +{ + struct netr_Credential time_cred; + + DEBUG(5,("\tseed %08x:%08x\n", + IVAL(creds->seed.data, 0), IVAL(creds->seed.data, 4))); + + SIVAL(time_cred.data, 0, IVAL(creds->seed.data, 0) + creds->sequence); + SIVAL(time_cred.data, 4, IVAL(creds->seed.data, 4)); + + DEBUG(5,("\tseed+time %08x:%08x\n", IVAL(time_cred.data, 0), IVAL(time_cred.data, 4))); + + des_crypt112(creds->client.data, time_cred.data, creds->session_key, 1); + + DEBUG(5,("\tCLIENT %08x:%08x\n", + IVAL(creds->client.data, 0), IVAL(creds->client.data, 4))); + + SIVAL(time_cred.data, 0, IVAL(creds->seed.data, 0) + creds->sequence + 1); + SIVAL(time_cred.data, 4, IVAL(creds->seed.data, 4)); + + DEBUG(5,("\tseed+time+1 %08x:%08x\n", + IVAL(time_cred.data, 0), IVAL(time_cred.data, 4))); + + des_crypt112(creds->server.data, time_cred.data, creds->session_key, 1); + + DEBUG(5,("\tSERVER %08x:%08x\n", + IVAL(creds->server.data, 0), IVAL(creds->server.data, 4))); + + creds->seed = time_cred; +} + + +/* + DES encrypt a 8 byte LMSessionKey buffer using the Netlogon session key +*/ +void netlogon_creds_des_encrypt_LMKey(struct netlogon_creds_CredentialState *creds, struct netr_LMSessionKey *key) +{ + struct netr_LMSessionKey tmp; + des_crypt56(tmp.key, key->key, creds->session_key, 1); + *key = tmp; +} + +/* + DES decrypt a 8 byte LMSessionKey buffer using the Netlogon session key +*/ +void netlogon_creds_des_decrypt_LMKey(struct netlogon_creds_CredentialState *creds, struct netr_LMSessionKey *key) +{ + struct netr_LMSessionKey tmp; + des_crypt56(tmp.key, key->key, creds->session_key, 0); + *key = tmp; +} + +/* + DES encrypt a 16 byte password buffer using the session key +*/ +void netlogon_creds_des_encrypt(struct netlogon_creds_CredentialState *creds, struct samr_Password *pass) +{ + struct samr_Password tmp; + des_crypt112_16(tmp.hash, pass->hash, creds->session_key, 1); + *pass = tmp; +} + +/* + DES decrypt a 16 byte password buffer using the session key +*/ +void netlogon_creds_des_decrypt(struct netlogon_creds_CredentialState *creds, struct samr_Password *pass) +{ + struct samr_Password tmp; + des_crypt112_16(tmp.hash, pass->hash, creds->session_key, 0); + *pass = tmp; +} + +/* + ARCFOUR encrypt/decrypt a password buffer using the session key +*/ +void netlogon_creds_arcfour_crypt(struct netlogon_creds_CredentialState *creds, uint8_t *data, size_t len) +{ + DATA_BLOB session_key = data_blob(creds->session_key, 16); + + arcfour_crypt_blob(data, len, &session_key); + + data_blob_free(&session_key); +} + +/***************************************************************** +The above functions are common to the client and server interface +next comes the client specific functions +******************************************************************/ + +/* + initialise the credentials chain and return the first client + credentials +*/ + +struct netlogon_creds_CredentialState *netlogon_creds_client_init(TALLOC_CTX *mem_ctx, + const char *client_account, + const char *client_computer_name, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password, + struct netr_Credential *initial_credential, + uint32_t negotiate_flags) +{ + struct netlogon_creds_CredentialState *creds = talloc(mem_ctx, struct netlogon_creds_CredentialState); + + if (!creds) { + return NULL; + } + + creds->sequence = time(NULL); + creds->negotiate_flags = negotiate_flags; + + creds->computer_name = talloc_strdup(creds, client_computer_name); + if (!creds->computer_name) { + talloc_free(creds); + return NULL; + } + creds->account_name = talloc_strdup(creds, client_account); + if (!creds->account_name) { + talloc_free(creds); + return NULL; + } + + dump_data_pw("Client chall", client_challenge->data, sizeof(client_challenge->data)); + dump_data_pw("Server chall", server_challenge->data, sizeof(server_challenge->data)); + dump_data_pw("Machine Pass", machine_password->hash, sizeof(machine_password->hash)); + + if (negotiate_flags & NETLOGON_NEG_128BIT) { + netlogon_creds_init_128bit(creds, client_challenge, server_challenge, machine_password); + } else { + netlogon_creds_init_64bit(creds, client_challenge, server_challenge, machine_password); + } + + dump_data_pw("Session key", creds->session_key, 16); + dump_data_pw("Credential ", creds->client.data, 8); + + *initial_credential = creds->client; + return creds; +} + +/* + initialise the credentials structure with only a session key. The caller better know what they are doing! + */ + +struct netlogon_creds_CredentialState *netlogon_creds_client_init_session_key(TALLOC_CTX *mem_ctx, + const uint8_t session_key[16]) +{ + struct netlogon_creds_CredentialState *creds = talloc(mem_ctx, struct netlogon_creds_CredentialState); + + if (!creds) { + return NULL; + } + + memcpy(creds->session_key, session_key, 16); + + return creds; +} + +/* + step the credentials to the next element in the chain, updating the + current client and server credentials and the seed + + produce the next authenticator in the sequence ready to send to + the server +*/ +void netlogon_creds_client_authenticator(struct netlogon_creds_CredentialState *creds, + struct netr_Authenticator *next) +{ + creds->sequence += 2; + netlogon_creds_step(creds); + + next->cred = creds->client; + next->timestamp = creds->sequence; +} + +/* + check that a credentials reply from a server is correct +*/ +bool netlogon_creds_client_check(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *received_credentials) +{ + if (!received_credentials || + memcmp(received_credentials->data, creds->server.data, 8) != 0) { + DEBUG(2,("credentials check failed\n")); + return false; + } + return true; +} + + +/***************************************************************** +The above functions are common to the client and server interface +next comes the server specific functions +******************************************************************/ + +/* + check that a credentials reply from a server is correct +*/ +static bool netlogon_creds_server_check_internal(const struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *received_credentials) +{ + if (memcmp(received_credentials->data, creds->client.data, 8) != 0) { + DEBUG(2,("credentials check failed\n")); + dump_data_pw("client creds", creds->client.data, 8); + dump_data_pw("calc creds", received_credentials->data, 8); + return false; + } + return true; +} + +/* + initialise the credentials chain and return the first server + credentials +*/ +struct netlogon_creds_CredentialState *netlogon_creds_server_init(TALLOC_CTX *mem_ctx, + const char *client_account, + const char *client_computer_name, + uint16_t secure_channel_type, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password, + struct netr_Credential *credentials_in, + struct netr_Credential *credentials_out, + uint32_t negotiate_flags) +{ + + struct netlogon_creds_CredentialState *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + + if (!creds) { + return NULL; + } + + creds->negotiate_flags = negotiate_flags; + + creds->computer_name = talloc_strdup(creds, client_computer_name); + if (!creds->computer_name) { + talloc_free(creds); + return NULL; + } + creds->account_name = talloc_strdup(creds, client_account); + if (!creds->account_name) { + talloc_free(creds); + return NULL; + } + + if (negotiate_flags & NETLOGON_NEG_128BIT) { + netlogon_creds_init_128bit(creds, client_challenge, server_challenge, + machine_password); + } else { + netlogon_creds_init_64bit(creds, client_challenge, server_challenge, + machine_password); + } + + /* And before we leak information about the machine account + * password, check that they got the first go right */ + if (!netlogon_creds_server_check_internal(creds, credentials_in)) { + talloc_free(creds); + return NULL; + } + + *credentials_out = creds->server; + + return creds; +} + +NTSTATUS netlogon_creds_server_step_check(struct netlogon_creds_CredentialState *creds, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator) +{ + if (!received_authenticator || !return_authenticator) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!creds) { + return NT_STATUS_ACCESS_DENIED; + } + + /* TODO: this may allow the a replay attack on a non-signed + connection. Should we check that this is increasing? */ + creds->sequence = received_authenticator->timestamp; + netlogon_creds_step(creds); + if (netlogon_creds_server_check_internal(creds, &received_authenticator->cred)) { + return_authenticator->cred = creds->server; + return_authenticator->timestamp = creds->sequence; + return NT_STATUS_OK; + } else { + ZERO_STRUCTP(return_authenticator); + return NT_STATUS_ACCESS_DENIED; + } +} + +void netlogon_creds_decrypt_samlogon(struct netlogon_creds_CredentialState *creds, + uint16_t validation_level, + union netr_Validation *validation) +{ + static const char zeros[16]; + + struct netr_SamBaseInfo *base = NULL; + switch (validation_level) { + case 2: + if (validation->sam2) { + base = &validation->sam2->base; + } + break; + case 3: + if (validation->sam3) { + base = &validation->sam3->base; + } + break; + case 6: + if (validation->sam6) { + base = &validation->sam6->base; + } + break; + default: + /* If we can't find it, we can't very well decrypt it */ + return; + } + + if (!base) { + return; + } + + /* find and decyrpt the session keys, return in parameters above */ + if (validation_level == 6) { + /* they aren't encrypted! */ + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + if (memcmp(base->key.key, zeros, + sizeof(base->key.key)) != 0) { + netlogon_creds_arcfour_crypt(creds, + base->key.key, + sizeof(base->key.key)); + } + + if (memcmp(base->LMSessKey.key, zeros, + sizeof(base->LMSessKey.key)) != 0) { + netlogon_creds_arcfour_crypt(creds, + base->LMSessKey.key, + sizeof(base->LMSessKey.key)); + } + } else { + if (memcmp(base->LMSessKey.key, zeros, + sizeof(base->LMSessKey.key)) != 0) { + netlogon_creds_des_decrypt_LMKey(creds, + &base->LMSessKey); + } + } +} + diff --git a/libcli/auth/credentials.h b/libcli/auth/credentials.h new file mode 100644 index 0000000000..b84b902fa0 --- /dev/null +++ b/libcli/auth/credentials.h @@ -0,0 +1,84 @@ +/* + Unix SMB/CIFS implementation. + + code to manipulate domain credentials + + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "librpc/gen_ndr/netlogon.h" + +struct netlogon_creds_CredentialState { + uint32_t negotiate_flags; + uint8_t session_key[16]; + uint32_t sequence; + struct netr_Credential seed; + struct netr_Credential client; + struct netr_Credential server; + uint16_t secure_channel_type; + const char *computer_name; + const char *account_name; + struct dom_sid *sid; +}; + +/* The 7 here seems to be required to get Win2k not to downgrade us + to NT4. Actually, anything other than 1ff would seem to do... */ +#define NETLOGON_NEG_AUTH2_FLAGS 0x000701ff +/* + (NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO) +*/ +#define NETLOGON_NEG_DOMAIN_TRUST_ACCOUNT 0x2010b000 + +/* these are the flags that ADS clients use */ +/* + (NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_SEND_PASSWORD_INFO_PDC | + NETLOGON_NEG_GENERIC_PASSTHROUGH | + NETLOGON_NEG_CONCURRENT_RPC | + NETLOGON_NEG_AVOID_ACCOUNT_DB_REPL | + NETLOGON_NEG_AVOID_SECURITYAUTH_DB_REPL | + NETLOGON_NEG_128BIT | + NETLOGON_NEG_TRANSITIVE_TRUSTS | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO | + NETLOGON_NEG_CROSS_FOREST_TRUSTS | + NETLOGON_NEG_AUTHENTICATED_RPC_LSASS | + NETLOGON_NEG_SCHANNEL) +*/ + +#define NETLOGON_NEG_AUTH2_ADS_FLAGS (0x200fbffb | NETLOGON_NEG_ARCFOUR | NETLOGON_NEG_128BIT | NETLOGON_NEG_SCHANNEL) + + diff --git a/libcli/auth/libcli_auth.h b/libcli/auth/libcli_auth.h new file mode 100644 index 0000000000..be43007d85 --- /dev/null +++ b/libcli/auth/libcli_auth.h @@ -0,0 +1,32 @@ +/* + samba -- Unix SMB/CIFS implementation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __LIBCLI_AUTH_H__ +#define __LIBCLI_AUTH_H__ + +#include "librpc/gen_ndr/netlogon.h" +#include "librpc/gen_ndr/wkssvc.h" +#include "libcli/auth/credentials.h" +#include "libcli/auth/ntlm_check.h" +#include "libcli/auth/proto.h" +#include "libcli/auth/msrpc_parse.h" + +#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 + +#endif /* __LIBCLI_AUTH_H__ */ diff --git a/libcli/auth/msrpc_parse.c b/libcli/auth/msrpc_parse.c new file mode 100644 index 0000000000..2f78f5f190 --- /dev/null +++ b/libcli/auth/msrpc_parse.c @@ -0,0 +1,368 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/auth/msrpc_parse.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, j; + bool ret; + va_list ap; + char *s; + uint8_t *b; + int head_size=0, data_size=0; + int head_ofs, data_ofs; + int *intargs; + size_t n; + + 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; + ret = push_ucs2_talloc(pointers, (smb_ucs2_t **)&pointers[i].data, + s, &n); + if (!ret) { + 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; + ret = push_ascii_talloc(pointers, (char **)&pointers[i].data, s, + &n); + if (!ret) { + return false; + } + pointers[i].length = n; + pointers[i].length -= 1; + data_size += pointers[i].length; + break; + case 'a': + j = va_arg(ap, int); + intargs[i] = j; + s = va_arg(ap, char *); + ret = push_ucs2_talloc(pointers, (smb_ucs2_t **)&pointers[i].data, + s, &n); + if (!ret) { + 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': + j = va_arg(ap, int); + intargs[i] = j; + 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': + j = intargs[i]; + SSVAL(blob->data, data_ofs, j); 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': + j = intargs[i]; + SIVAL(blob->data, head_ofs, j); + 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; + char **ps, *s; + DATA_BLOB *b; + size_t head_ofs = 0; + uint16_t len1, len2; + uint32_t ptr; + uint32_t *v; + size_t p_len = 1024; + char *p = talloc_array(mem_ctx, char, p_len); + bool ret = true; + + 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 = va_arg(ap, char **); + if (len1 == 0 && len2 == 0) { + *ps = discard_const(""); + } else { + /* make sure its in the right format - be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + ret = false; + goto cleanup; + } + if (len1 & 1) { + /* if odd length and unicode */ + ret = false; + goto cleanup; + } + if (blob->data + ptr < (uint8_t *)(uintptr_t)ptr || + blob->data + ptr < blob->data) { + ret = false; + goto cleanup; + } + + if (0 < len1) { + size_t pull_len; + if (!convert_string_talloc(mem_ctx, CH_UTF16, CH_UNIX, + blob->data + ptr, len1, + ps, &pull_len, false)) { + ret = false; + goto cleanup; + } + } else { + (*ps) = discard_const(""); + } + } + 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 = (char **)va_arg(ap, char **); + /* make sure its in the right format - be strict */ + if (len1 == 0 && len2 == 0) { + *ps = discard_const(""); + } else { + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + ret = false; + goto cleanup; + } + + if (blob->data + ptr < (uint8_t *)(uintptr_t)ptr || + blob->data + ptr < blob->data) { + ret = false; + goto cleanup; + } + + if (0 < len1) { + size_t pull_len; + + if (!convert_string_talloc(mem_ctx, CH_DOS, CH_UNIX, + blob->data + ptr, len1, + ps, &pull_len, false)) { + ret = false; + goto cleanup; + } + } else { + (*ps) = discard_const(""); + } + } + 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)) { + ret = false; + goto cleanup; + } + + if (blob->data + ptr < (uint8_t *)(uintptr_t)ptr || + blob->data + ptr < blob->data) { + ret = false; + goto cleanup; + } + + *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) { + ret = false; + goto cleanup; + } + + *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 || + (head_ofs + (strlen(s) + 1)) > blob->length) { + ret = false; + goto cleanup; + } + + if (memcmp(blob->data + head_ofs, s, strlen(s)+1) != 0) { + ret = false; + goto cleanup; + } + head_ofs += (strlen(s) + 1); + + break; + } + } + +cleanup: + va_end(ap); + talloc_free(p); + return ret; +} diff --git a/libcli/auth/ntlm_check.c b/libcli/auth/ntlm_check.c new file mode 100644 index 0000000000..2cfe8e1ef8 --- /dev/null +++ b/libcli/auth/ntlm_check.c @@ -0,0 +1,596 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004 + Copyright (C) Gerald Carter 2003 + Copyright (C) Luke Kenneth Casson Leighton 1996-2000 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../lib/crypto/crypto.h" +#include "librpc/gen_ndr/netlogon.h" +#include "libcli/auth/libcli_auth.h" + +/**************************************************************************** + Core of smb password checking routine. +****************************************************************************/ + +static bool smb_pwd_check_ntlmv1(TALLOC_CTX *mem_ctx, + const DATA_BLOB *nt_response, + const uint8_t *part_passwd, + const DATA_BLOB *sec_blob, + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uint8_t p24[24]; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false ! */ + return false; + } + + if (sec_blob->length != 8) { + DEBUG(0, ("smb_pwd_check_ntlmv1: incorrect challenge size (%lu)\n", + (unsigned long)sec_blob->length)); + return false; + } + + if (nt_response->length != 24) { + DEBUG(0, ("smb_pwd_check_ntlmv1: incorrect password length (%lu)\n", + (unsigned long)nt_response->length)); + return false; + } + + SMBOWFencrypt(part_passwd, sec_blob->data, p24); + +#if DEBUG_PASSWORD + DEBUG(100,("Part password (P16) was |\n")); + dump_data(100, part_passwd, 16); + DEBUGADD(100,("Password from client was |\n")); + dump_data(100, nt_response->data, nt_response->length); + DEBUGADD(100,("Given challenge was |\n")); + dump_data(100, sec_blob->data, sec_blob->length); + DEBUGADD(100,("Value from encryption was |\n")); + dump_data(100, p24, 24); +#endif + if (memcmp(p24, nt_response->data, 24) == 0) { + if (user_sess_key != NULL) { + *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16); + SMBsesskeygen_ntv1(part_passwd, user_sess_key->data); + } + return true; + } + return false; +} + +/**************************************************************************** + Core of smb password checking routine. (NTLMv2, LMv2) + Note: The same code works with both NTLMv2 and LMv2. +****************************************************************************/ + +static bool smb_pwd_check_ntlmv2(TALLOC_CTX *mem_ctx, + const DATA_BLOB *ntv2_response, + const uint8_t *part_passwd, + const DATA_BLOB *sec_blob, + const char *user, const char *domain, + bool upper_case_domain, /* should the domain be transformed into upper case? */ + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uint8_t kr[16]; + uint8_t value_from_encryption[16]; + DATA_BLOB client_key_data; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false */ + return false; + } + + if (sec_blob->length != 8) { + DEBUG(0, ("smb_pwd_check_ntlmv2: incorrect challenge size (%lu)\n", + (unsigned long)sec_blob->length)); + return false; + } + + if (ntv2_response->length < 24) { + /* We MUST have more than 16 bytes, or the stuff below will go + crazy. No known implementation sends less than the 24 bytes + for LMv2, let alone NTLMv2. */ + DEBUG(0, ("smb_pwd_check_ntlmv2: incorrect password length (%lu)\n", + (unsigned long)ntv2_response->length)); + return false; + } + + client_key_data = data_blob_talloc(mem_ctx, ntv2_response->data+16, ntv2_response->length-16); + /* + todo: should we be checking this for anything? We can't for LMv2, + but for NTLMv2 it is meant to contain the current time etc. + */ + + if (!ntv2_owf_gen(part_passwd, user, domain, upper_case_domain, kr)) { + return false; + } + + SMBOWFencrypt_ntv2(kr, sec_blob, &client_key_data, value_from_encryption); + +#if DEBUG_PASSWORD + DEBUG(100,("Part password (P16) was |\n")); + dump_data(100, part_passwd, 16); + DEBUGADD(100,("Password from client was |\n")); + dump_data(100, ntv2_response->data, ntv2_response->length); + DEBUGADD(100,("Variable data from client was |\n")); + dump_data(100, client_key_data.data, client_key_data.length); + DEBUGADD(100,("Given challenge was |\n")); + dump_data(100, sec_blob->data, sec_blob->length); + DEBUGADD(100,("Value from encryption was |\n")); + dump_data(100, value_from_encryption, 16); +#endif + data_blob_clear_free(&client_key_data); + if (memcmp(value_from_encryption, ntv2_response->data, 16) == 0) { + if (user_sess_key != NULL) { + *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16); + SMBsesskeygen_ntv2(kr, value_from_encryption, user_sess_key->data); + } + return true; + } + return false; +} + +/**************************************************************************** + Core of smb password checking routine. (NTLMv2, LMv2) + Note: The same code works with both NTLMv2 and LMv2. +****************************************************************************/ + +static bool smb_sess_key_ntlmv2(TALLOC_CTX *mem_ctx, + const DATA_BLOB *ntv2_response, + const uint8_t *part_passwd, + const DATA_BLOB *sec_blob, + const char *user, const char *domain, + bool upper_case_domain, /* should the domain be transformed into upper case? */ + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uint8_t kr[16]; + uint8_t value_from_encryption[16]; + DATA_BLOB client_key_data; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false */ + return false; + } + + if (sec_blob->length != 8) { + DEBUG(0, ("smb_sess_key_ntlmv2: incorrect challenge size (%lu)\n", + (unsigned long)sec_blob->length)); + return false; + } + + if (ntv2_response->length < 24) { + /* We MUST have more than 16 bytes, or the stuff below will go + crazy. No known implementation sends less than the 24 bytes + for LMv2, let alone NTLMv2. */ + DEBUG(0, ("smb_sess_key_ntlmv2: incorrect password length (%lu)\n", + (unsigned long)ntv2_response->length)); + return false; + } + + client_key_data = data_blob_talloc(mem_ctx, ntv2_response->data+16, ntv2_response->length-16); + + if (!ntv2_owf_gen(part_passwd, user, domain, upper_case_domain, kr)) { + return false; + } + + SMBOWFencrypt_ntv2(kr, sec_blob, &client_key_data, value_from_encryption); + *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16); + SMBsesskeygen_ntv2(kr, value_from_encryption, user_sess_key->data); + return true; +} + +/** + * Compare password hashes against those from the SAM + * + * @param mem_ctx talloc context + * @param client_lanman LANMAN password hash, as supplied by the client + * @param client_nt NT (MD4) password hash, as supplied by the client + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN password hash, as stored on the SAM + * @param stored_nt NT (MD4) password hash, as stored on the SAM + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS hash_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + const struct samr_Password *client_lanman, + const struct samr_Password *client_nt, + const char *username, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt) +{ + if (stored_nt == NULL) { + DEBUG(3,("ntlm_password_check: NO NT password stored for user %s.\n", + username)); + } + + if (client_nt && stored_nt) { + if (memcmp(client_nt->hash, stored_nt->hash, sizeof(stored_nt->hash)) == 0) { + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: Interactive logon: NT password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + } else if (client_lanman && stored_lanman) { + if (!lanman_auth) { + DEBUG(3,("ntlm_password_check: Interactive logon: only LANMAN password supplied for user %s, and LM passwords are disabled!\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + if (strchr_m(username, '@')) { + return NT_STATUS_NOT_FOUND; + } + + if (memcmp(client_lanman->hash, stored_lanman->hash, sizeof(stored_lanman->hash)) == 0) { + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: Interactive logon: LANMAN password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } + if (strchr_m(username, '@')) { + return NT_STATUS_NOT_FOUND; + } + return NT_STATUS_WRONG_PASSWORD; +} + +/** + * Check a challenge-response password against the value of the NT or + * LM password hash. + * + * @param mem_ctx talloc context + * @param challenge 8-byte challenge. If all zero, forces plaintext comparison + * @param nt_response 'unicode' NT response to the challenge, or unicode password + * @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN ASCII password from our passdb or similar + * @param stored_nt MD4 unicode password from our passdb or similar + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS ntlm_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + bool ntlm_auth, + uint32_t logon_parameters, + const DATA_BLOB *challenge, + const DATA_BLOB *lm_response, + const DATA_BLOB *nt_response, + const char *username, + const char *client_username, + const char *client_domain, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt, + DATA_BLOB *user_sess_key, + DATA_BLOB *lm_sess_key) +{ + const static uint8_t zeros[8]; + DATA_BLOB tmp_sess_key; + + if (stored_nt == NULL) { + DEBUG(3,("ntlm_password_check: NO NT password stored for user %s.\n", + username)); + } + + *lm_sess_key = data_blob(NULL, 0); + *user_sess_key = data_blob(NULL, 0); + + /* Check for cleartext netlogon. Used by Exchange 5.5. */ + if ((logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_ALLOWED) + && challenge->length == sizeof(zeros) + && (memcmp(challenge->data, zeros, challenge->length) == 0 )) { + struct samr_Password client_nt; + struct samr_Password client_lm; + char *unix_pw = NULL; + bool lm_ok; + + DEBUG(4,("ntlm_password_check: checking plaintext passwords for user %s\n", + username)); + mdfour(client_nt.hash, nt_response->data, nt_response->length); + + if (lm_response->length && + (convert_string_talloc(mem_ctx, CH_DOS, CH_UNIX, + lm_response->data, lm_response->length, + (void **)&unix_pw, NULL, false))) { + if (E_deshash(unix_pw, client_lm.hash)) { + lm_ok = true; + } else { + lm_ok = false; + } + } else { + lm_ok = false; + } + return hash_password_check(mem_ctx, + lanman_auth, + lm_ok ? &client_lm : NULL, + nt_response->length ? &client_nt : NULL, + username, + stored_lanman, stored_nt); + } + + if (nt_response->length != 0 && nt_response->length < 24) { + DEBUG(2,("ntlm_password_check: invalid NT password length (%lu) for user %s\n", + (unsigned long)nt_response->length, username)); + } + + if (nt_response->length > 24 && stored_nt) { + /* We have the NT MD4 hash challenge available - see if we can + use it + */ + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with domain [%s]\n", client_domain)); + if (smb_pwd_check_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + client_domain, + false, + user_sess_key)) { + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with uppercased version of domain [%s]\n", client_domain)); + if (smb_pwd_check_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + client_domain, + true, + user_sess_key)) { + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password without a domain\n")); + if (smb_pwd_check_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + "", + false, + user_sess_key)) { + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NTLMv2 password check failed\n")); + } + } else if (nt_response->length == 24 && stored_nt) { + if (ntlm_auth) { + /* We have the NT MD4 hash challenge available - see if we can + use it (ie. does it exist in the smbpasswd file). + */ + DEBUG(4,("ntlm_password_check: Checking NT MD4 password\n")); + if (smb_pwd_check_ntlmv1(mem_ctx, + nt_response, + stored_nt->hash, challenge, + user_sess_key)) { + /* The LM session key for this response is not very secure, + so use it only if we otherwise allow LM authentication */ + + if (lanman_auth && stored_lanman) { + *lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NT MD4 password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } else { + DEBUG(2,("ntlm_password_check: NTLMv1 passwords NOT PERMITTED for user %s\n", + username)); + /* no return, becouse we might pick up LMv2 in the LM field */ + } + } + + if (lm_response->length == 0) { + DEBUG(3,("ntlm_password_check: NEITHER LanMan nor NT password supplied for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (lm_response->length < 24) { + DEBUG(2,("ntlm_password_check: invalid LanMan password length (%lu) for user %s\n", + (unsigned long)nt_response->length, username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (!lanman_auth) { + DEBUG(3,("ntlm_password_check: Lanman passwords NOT PERMITTED for user %s\n", + username)); + } else if (!stored_lanman) { + DEBUG(3,("ntlm_password_check: NO LanMan password set for user %s (and no NT password supplied)\n", + username)); + } else if (strchr_m(username, '@')) { + DEBUG(3,("ntlm_password_check: NO LanMan password allowed for username@realm logins (user: %s)\n", + username)); + } else { + DEBUG(4,("ntlm_password_check: Checking LM password\n")); + if (smb_pwd_check_ntlmv1(mem_ctx, + lm_response, + stored_lanman->hash, challenge, + NULL)) { + /* The session key for this response is still very odd. + It not very secure, so use it only if we otherwise + allow LM authentication */ + + if (lanman_auth && stored_lanman) { + uint8_t first_8_lm_hash[16]; + memcpy(first_8_lm_hash, stored_lanman->hash, 8); + memset(first_8_lm_hash + 8, '\0', 8); + *user_sess_key = data_blob_talloc(mem_ctx, first_8_lm_hash, 16); + *lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8); + } + return NT_STATUS_OK; + } + } + + if (!stored_nt) { + DEBUG(4,("ntlm_password_check: LM password check failed for user, no NT password %s\n",username)); + return NT_STATUS_WRONG_PASSWORD; + } + + /* This is for 'LMv2' authentication. almost NTLMv2 but limited to 24 bytes. + - related to Win9X, legacy NAS pass-though authentication + */ + DEBUG(4,("ntlm_password_check: Checking LMv2 password with domain %s\n", client_domain)); + if (smb_pwd_check_ntlmv2(mem_ctx, + lm_response, + stored_nt->hash, challenge, + client_username, + client_domain, + false, + &tmp_sess_key)) { + if (nt_response->length > 24) { + /* If NTLMv2 authentication has preceeded us + * (even if it failed), then use the session + * key from that. See the RPC-SAMLOGON + * torture test */ + smb_sess_key_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + client_domain, + false, + user_sess_key); + } else { + /* Otherwise, use the LMv2 session key */ + *user_sess_key = tmp_sess_key; + } + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking LMv2 password with upper-cased version of domain %s\n", client_domain)); + if (smb_pwd_check_ntlmv2(mem_ctx, + lm_response, + stored_nt->hash, challenge, + client_username, + client_domain, + true, + &tmp_sess_key)) { + if (nt_response->length > 24) { + /* If NTLMv2 authentication has preceeded us + * (even if it failed), then use the session + * key from that. See the RPC-SAMLOGON + * torture test */ + smb_sess_key_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + client_domain, + true, + user_sess_key); + } else { + /* Otherwise, use the LMv2 session key */ + *user_sess_key = tmp_sess_key; + } + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking LMv2 password without a domain\n")); + if (smb_pwd_check_ntlmv2(mem_ctx, + lm_response, + stored_nt->hash, challenge, + client_username, + "", + false, + &tmp_sess_key)) { + if (nt_response->length > 24) { + /* If NTLMv2 authentication has preceeded us + * (even if it failed), then use the session + * key from that. See the RPC-SAMLOGON + * torture test */ + smb_sess_key_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + "", + false, + user_sess_key); + } else { + /* Otherwise, use the LMv2 session key */ + *user_sess_key = tmp_sess_key; + } + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + /* Apparently NT accepts NT responses in the LM field + - I think this is related to Win9X pass-though authentication + */ + DEBUG(4,("ntlm_password_check: Checking NT MD4 password in LM field\n")); + if (ntlm_auth) { + if (smb_pwd_check_ntlmv1(mem_ctx, + lm_response, + stored_nt->hash, challenge, + NULL)) { + /* The session key for this response is still very odd. + It not very secure, so use it only if we otherwise + allow LM authentication */ + + if (lanman_auth && stored_lanman) { + uint8_t first_8_lm_hash[16]; + memcpy(first_8_lm_hash, stored_lanman->hash, 8); + memset(first_8_lm_hash + 8, '\0', 8); + *user_sess_key = data_blob_talloc(mem_ctx, first_8_lm_hash, 16); + *lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8); + } + return NT_STATUS_OK; + } + DEBUG(3,("ntlm_password_check: LM password, NT MD4 password in LM field and LMv2 failed for user %s\n",username)); + } else { + DEBUG(3,("ntlm_password_check: LM password and LMv2 failed for user %s, and NT MD4 password in LM field not permitted\n",username)); + } + + /* Try and match error codes */ + if (strchr_m(username, '@')) { + return NT_STATUS_NOT_FOUND; + } + return NT_STATUS_WRONG_PASSWORD; +} + diff --git a/libcli/auth/ntlm_check.h b/libcli/auth/ntlm_check.h new file mode 100644 index 0000000000..df11f7d7a2 --- /dev/null +++ b/libcli/auth/ntlm_check.h @@ -0,0 +1,76 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004 + Copyright (C) Gerald Carter 2003 + Copyright (C) Luke Kenneth Casson Leighton 1996-2000 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/** + * Compare password hashes against those from the SAM + * + * @param mem_ctx talloc context + * @param client_lanman LANMAN password hash, as supplied by the client + * @param client_nt NT (MD4) password hash, as supplied by the client + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN password hash, as stored on the SAM + * @param stored_nt NT (MD4) password hash, as stored on the SAM + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS hash_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + const struct samr_Password *client_lanman, + const struct samr_Password *client_nt, + const char *username, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt); + +/** + * Check a challenge-response password against the value of the NT or + * LM password hash. + * + * @param mem_ctx talloc context + * @param challenge 8-byte challenge. If all zero, forces plaintext comparison + * @param nt_response 'unicode' NT response to the challenge, or unicode password + * @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN ASCII password from our passdb or similar + * @param stored_nt MD4 unicode password from our passdb or similar + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS ntlm_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + bool ntlm_auth, + uint32_t logon_parameters, + const DATA_BLOB *challenge, + const DATA_BLOB *lm_response, + const DATA_BLOB *nt_response, + const char *username, + const char *client_username, + const char *client_domain, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt, + DATA_BLOB *user_sess_key, + DATA_BLOB *lm_sess_key); diff --git a/libcli/auth/schannel_state.c b/libcli/auth/schannel_state.c new file mode 100644 index 0000000000..e01330010b --- /dev/null +++ b/libcli/auth/schannel_state.c @@ -0,0 +1,321 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel server + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/ldb/include/ldb.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "ldb_wrap.h" +#include "../lib/util/util_ldb.h" +#include "libcli/auth/libcli_auth.h" +#include "auth/auth.h" +#include "param/param.h" +#include "auth/gensec/schannel_state.h" + +static struct ldb_val *schannel_dom_sid_ldb_val(TALLOC_CTX *mem_ctx, + struct dom_sid *sid) +{ + enum ndr_err_code ndr_err; + struct ldb_val *v; + + v = talloc(mem_ctx, struct ldb_val); + if (!v) return NULL; + + ndr_err = ndr_push_struct_blob(v, mem_ctx, NULL, sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(v); + return NULL; + } + + return v; +} + +static struct dom_sid *schannel_ldb_val_dom_sid(TALLOC_CTX *mem_ctx, + const struct ldb_val *v) +{ + enum ndr_err_code ndr_err; + struct dom_sid *sid; + + sid = talloc(mem_ctx, struct dom_sid); + if (!sid) return NULL; + + ndr_err = ndr_pull_struct_blob(v, sid, NULL, sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(sid); + return NULL; + } + return sid; +} + + +/* + remember an established session key for a netr server authentication + use a simple ldb structure +*/ +NTSTATUS schannel_store_session_key(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds) +{ + struct ldb_message *msg; + struct ldb_val val, seed, client_state, server_state; + struct ldb_val *sid_val; + char *f; + char *sct; + int ret; + + f = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->negotiate_flags); + + if (f == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sct = talloc_asprintf(mem_ctx, "%u", (unsigned int)creds->secure_channel_type); + + if (sct == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg = ldb_msg_new(ldb); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_new_fmt(msg, ldb, "computerName=%s", creds->computer_name); + if ( ! msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + sid_val = schannel_dom_sid_ldb_val(msg, creds->sid); + if (sid_val == NULL) { + 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); + + client_state.data = creds->client.data; + client_state.length = sizeof(creds->client.data); + server_state.data = creds->server.data; + server_state.length = sizeof(creds->server.data); + + ldb_msg_add_string(msg, "objectClass", "schannelState"); + ldb_msg_add_value(msg, "sessionKey", &val, NULL); + ldb_msg_add_value(msg, "seed", &seed, NULL); + ldb_msg_add_value(msg, "clientState", &client_state, NULL); + ldb_msg_add_value(msg, "serverState", &server_state, NULL); + ldb_msg_add_string(msg, "negotiateFlags", f); + ldb_msg_add_string(msg, "secureChannelType", sct); + ldb_msg_add_string(msg, "accountName", creds->account_name); + ldb_msg_add_string(msg, "computerName", creds->computer_name); + ldb_msg_add_value(msg, "objectSid", sid_val, NULL); + + ret = ldb_add(ldb, msg); + if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) { + int i; + /* from samdb_replace() */ + /* mark all the message elements as LDB_FLAG_MOD_REPLACE */ + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_modify(ldb, msg); + } + + /* We don't need a transaction here, as we either add or + * modify records, never delete them, so it must exist */ + + if (ret != LDB_SUCCESS) { + DEBUG(0,("Unable to add %s to session key db - %s\n", + ldb_dn_get_linearized(msg->dn), ldb_errstring(ldb))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return NT_STATUS_OK; +} + +/* + read back a credentials back for a computer +*/ +NTSTATUS schannel_fetch_session_key(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *computer_name, + struct netlogon_creds_CredentialState **creds) +{ + struct ldb_result *res; + int ret; + const struct ldb_val *val; + + *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + if (!*creds) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_search(ldb, mem_ctx, &res, + NULL, LDB_SCOPE_SUBTREE, NULL, + "(computerName=%s)", computer_name); + if (ret != LDB_SUCCESS) { + DEBUG(3,("schannel: Failed to find a record for client %s: %s\n", computer_name, ldb_errstring(ldb))); + return NT_STATUS_INVALID_HANDLE; + } + if (res->count != 1) { + DEBUG(3,("schannel: Failed to find a record for client: %s (found %d records)\n", computer_name, res->count)); + talloc_free(res); + return NT_STATUS_INVALID_HANDLE; + } + + val = ldb_msg_find_ldb_val(res->msgs[0], "sessionKey"); + if (val == NULL || val->length != 16) { + DEBUG(1,("schannel: record in schannel DB must contain a sessionKey of length 16, when searching for client: %s\n", computer_name)); + talloc_free(res); + return NT_STATUS_INTERNAL_ERROR; + } + + memcpy((*creds)->session_key, val->data, 16); + + val = ldb_msg_find_ldb_val(res->msgs[0], "seed"); + if (val == NULL || val->length != 8) { + DEBUG(1,("schannel: record in schannel DB must contain a vaid seed of length 8, when searching for client: %s\n", computer_name)); + talloc_free(res); + return NT_STATUS_INTERNAL_ERROR; + } + + memcpy((*creds)->seed.data, val->data, 8); + + val = ldb_msg_find_ldb_val(res->msgs[0], "clientState"); + if (val == NULL || val->length != 8) { + DEBUG(1,("schannel: record in schannel DB must contain a vaid clientState of length 8, when searching for client: %s\n", computer_name)); + talloc_free(res); + return NT_STATUS_INTERNAL_ERROR; + } + memcpy((*creds)->client.data, val->data, 8); + + val = ldb_msg_find_ldb_val(res->msgs[0], "serverState"); + if (val == NULL || val->length != 8) { + DEBUG(1,("schannel: record in schannel DB must contain a vaid serverState of length 8, when searching for client: %s\n", computer_name)); + talloc_free(res); + return NT_STATUS_INTERNAL_ERROR; + } + memcpy((*creds)->server.data, val->data, 8); + + (*creds)->negotiate_flags = ldb_msg_find_attr_as_int(res->msgs[0], "negotiateFlags", 0); + + (*creds)->secure_channel_type = ldb_msg_find_attr_as_int(res->msgs[0], "secureChannelType", 0); + + (*creds)->account_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "accountName", NULL)); + if ((*creds)->account_name == NULL) { + talloc_free(res); + return NT_STATUS_NO_MEMORY; + } + + (*creds)->computer_name = talloc_strdup(*creds, ldb_msg_find_attr_as_string(res->msgs[0], "computerName", NULL)); + if ((*creds)->computer_name == NULL) { + talloc_free(res); + return NT_STATUS_NO_MEMORY; + } + + val = ldb_msg_find_ldb_val(res->msgs[0], "objectSid"); + if (val) { + (*creds)->sid = schannel_ldb_val_dom_sid(*creds, val); + if ((*creds)->sid == NULL) { + talloc_free(res); + return NT_STATUS_INTERNAL_ERROR; + } + } else { + (*creds)->sid = NULL; + } + + talloc_free(res); + return NT_STATUS_OK; +} + +/* + Validate an incoming authenticator against the credentials for the remote machine. + + The credentials are (re)read and from the schannel database, and + written back after the caclulations are performed. + + The creds_out parameter (if not NULL) returns the credentials, if + the caller needs some of that information. + +*/ +NTSTATUS schannel_creds_server_step_check(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + const char *computer_name, + bool schannel_required_for_call, + bool schannel_in_use, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator, + struct netlogon_creds_CredentialState **creds_out) +{ + struct netlogon_creds_CredentialState *creds; + NTSTATUS nt_status; + int ret; + + ret = ldb_transaction_start(ldb); + if (ret != 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* Because this is a shared structure (even across + * disconnects) we must update the database every time we + * update the structure */ + + nt_status = schannel_fetch_session_key(ldb, ldb, computer_name, + &creds); + + /* If we are flaged that schannel is required for a call, and + * it is not in use, then make this an error */ + + /* It would be good to make this mandetory once schannel is + * negoiated, bu this is not what windows does */ + if (schannel_required_for_call && !schannel_in_use) { + DEBUG(0,("schannel_creds_server_step_check: client %s not using schannel for netlogon, despite negotiating it\n", + creds->computer_name )); + ldb_transaction_cancel(ldb); + return NT_STATUS_ACCESS_DENIED; + } + + if (NT_STATUS_IS_OK(nt_status)) { + nt_status = netlogon_creds_server_step_check(creds, + received_authenticator, + return_authenticator); + } + + if (NT_STATUS_IS_OK(nt_status)) { + nt_status = schannel_store_session_key(ldb, mem_ctx, creds); + } + + if (NT_STATUS_IS_OK(nt_status)) { + ldb_transaction_commit(ldb); + if (creds_out) { + *creds_out = creds; + talloc_steal(mem_ctx, creds); + } + } else { + ldb_transaction_cancel(ldb); + } + return nt_status; +} diff --git a/libcli/auth/schannel_state.h b/libcli/auth/schannel_state.h new file mode 100644 index 0000000000..048baa90d2 --- /dev/null +++ b/libcli/auth/schannel_state.h @@ -0,0 +1,24 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel server + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +struct ldb_context; +#include "libcli/auth/schannel_state_proto.h" diff --git a/libcli/auth/session.c b/libcli/auth/session.c new file mode 100644 index 0000000000..10c728662d --- /dev/null +++ b/libcli/auth/session.c @@ -0,0 +1,218 @@ +/* + Unix SMB/CIFS implementation. + + code to encrypt/decrypt data using the user session key + + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/auth/libcli_auth.h" + +/* + encrypt or decrypt a blob of data using the user session key + as used in lsa_SetSecret + + before calling, the out blob must be initialised to be the same size + as the in blob +*/ +void sess_crypt_blob(DATA_BLOB *out, const DATA_BLOB *in, const DATA_BLOB *session_key, + bool forward) +{ + int i, k; + + for (i=0,k=0; + i<in->length; + i += 8, k += 7) { + uint8_t bin[8], bout[8], key[7]; + + memset(bin, 0, 8); + memcpy(bin, &in->data[i], MIN(8, in->length-i)); + + if (k + 7 > session_key->length) { + k = (session_key->length - k); + } + memcpy(key, &session_key->data[k], 7); + + des_crypt56(bout, bin, key, forward?1:0); + + memcpy(&out->data[i], bout, MIN(8, in->length-i)); + } +} + + +/* + a convenient wrapper around sess_crypt_blob() for strings, using the LSA convention + + note that we round the length to a multiple of 8. This seems to be needed for + compatibility with windows + + caller should free using data_blob_free() +*/ +DATA_BLOB sess_encrypt_string(const char *str, const DATA_BLOB *session_key) +{ + DATA_BLOB ret, src; + int slen = strlen(str); + int dlen = (slen+7) & ~7; + + src = data_blob(NULL, 8+dlen); + if (!src.data) { + return data_blob(NULL, 0); + } + + ret = data_blob(NULL, 8+dlen); + if (!ret.data) { + data_blob_free(&src); + return data_blob(NULL, 0); + } + + SIVAL(src.data, 0, slen); + SIVAL(src.data, 4, 1); + memset(src.data+8, 0, dlen); + memcpy(src.data+8, str, slen); + + sess_crypt_blob(&ret, &src, session_key, true); + + data_blob_free(&src); + + return ret; +} + +/* + a convenient wrapper around sess_crypt_blob() for strings, using the LSA convention + + caller should free the returned string +*/ +char *sess_decrypt_string(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, const DATA_BLOB *session_key) +{ + DATA_BLOB out; + int slen; + char *ret; + + if (blob->length < 8) { + return NULL; + } + + out = data_blob_talloc(mem_ctx, NULL, blob->length); + if (!out.data) { + return NULL; + } + + sess_crypt_blob(&out, blob, session_key, false); + + if (IVAL(out.data, 4) != 1) { + DEBUG(0,("Unexpected revision number %d in session crypted string\n", + IVAL(out.data, 4))); + data_blob_free(&out); + return NULL; + } + + slen = IVAL(out.data, 0); + if (slen > blob->length - 8) { + DEBUG(0,("Invalid crypt length %d\n", slen)); + data_blob_free(&out); + return NULL; + } + + ret = talloc_strndup(mem_ctx, (const char *)(out.data+8), slen); + + data_blob_free(&out); + + DEBUG(0,("decrypted string '%s' of length %d\n", ret, slen)); + + return ret; +} + +/* + a convenient wrapper around sess_crypt_blob() for DATA_BLOBs, using the LSA convention + + note that we round the length to a multiple of 8. This seems to be needed for + compatibility with windows + + caller should free using data_blob_free() +*/ +DATA_BLOB sess_encrypt_blob(TALLOC_CTX *mem_ctx, DATA_BLOB *blob_in, const DATA_BLOB *session_key) +{ + DATA_BLOB ret, src; + int dlen = (blob_in->length+7) & ~7; + + src = data_blob_talloc(mem_ctx, NULL, 8+dlen); + if (!src.data) { + return data_blob(NULL, 0); + } + + ret = data_blob_talloc(mem_ctx, NULL, 8+dlen); + if (!ret.data) { + data_blob_free(&src); + return data_blob(NULL, 0); + } + + SIVAL(src.data, 0, blob_in->length); + SIVAL(src.data, 4, 1); + memset(src.data+8, 0, dlen); + memcpy(src.data+8, blob_in->data, blob_in->length); + + sess_crypt_blob(&ret, &src, session_key, true); + + data_blob_free(&src); + + return ret; +} + +/* + Decrypt a DATA_BLOB using the LSA convention +*/ +NTSTATUS sess_decrypt_blob(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, const DATA_BLOB *session_key, + DATA_BLOB *ret) +{ + DATA_BLOB out; + int slen; + + if (blob->length < 8) { + DEBUG(0, ("Unexpected length %d in session crypted secret (BLOB)\n", + (int)blob->length)); + return NT_STATUS_INVALID_PARAMETER; + } + + out = data_blob_talloc(mem_ctx, NULL, blob->length); + if (!out.data) { + return NT_STATUS_NO_MEMORY; + } + + sess_crypt_blob(&out, blob, session_key, false); + + if (IVAL(out.data, 4) != 1) { + DEBUG(2,("Unexpected revision number %d in session crypted secret (BLOB)\n", + IVAL(out.data, 4))); + return NT_STATUS_UNKNOWN_REVISION; + } + + slen = IVAL(out.data, 0); + if (slen > blob->length - 8) { + DEBUG(0,("Invalid crypt length %d in session crypted secret (BLOB)\n", slen)); + return NT_STATUS_WRONG_PASSWORD; + } + + *ret = data_blob_talloc(mem_ctx, out.data+8, slen); + if (slen && !ret->data) { + return NT_STATUS_NO_MEMORY; + } + + data_blob_free(&out); + + return NT_STATUS_OK; +} diff --git a/libcli/auth/smbdes.c b/libcli/auth/smbdes.c new file mode 100644 index 0000000000..32e65e779d --- /dev/null +++ b/libcli/auth/smbdes.c @@ -0,0 +1,381 @@ +/* + Unix SMB/CIFS implementation. + + a partial implementation of DES designed for use in the + SMB authentication protocol + + Copyright (C) Andrew Tridgell 1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/auth/libcli_auth.h" + +/* NOTES: + + This code makes no attempt to be fast! In fact, it is a very + slow implementation + + This code is NOT a complete DES implementation. It implements only + the minimum necessary for SMB authentication, as used by all SMB + products (including every copy of Microsoft Windows95 ever sold) + + In particular, it can only do a unchained forward DES pass. This + means it is not possible to use this code for encryption/decryption + of data, instead it is only useful as a "hash" algorithm. + + There is no entry point into this code that allows normal DES operation. + + I believe this means that this code does not come under ITAR + regulations but this is NOT a legal opinion. If you are concerned + about the applicability of ITAR regulations to this code then you + should confirm it for yourself (and maybe let me know if you come + up with a different answer to the one above) +*/ + + +static const uint8_t perm1[56] = {57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4}; + +static const uint8_t perm2[48] = {14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32}; + +static const uint8_t perm3[64] = {58, 50, 42, 34, 26, 18, 10, 2, + 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, + 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, + 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, + 63, 55, 47, 39, 31, 23, 15, 7}; + +static const uint8_t perm4[48] = { 32, 1, 2, 3, 4, 5, + 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, + 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, + 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, + 28, 29, 30, 31, 32, 1}; + +static const uint8_t perm5[32] = { 16, 7, 20, 21, + 29, 12, 28, 17, + 1, 15, 23, 26, + 5, 18, 31, 10, + 2, 8, 24, 14, + 32, 27, 3, 9, + 19, 13, 30, 6, + 22, 11, 4, 25}; + + +static const uint8_t perm6[64] ={ 40, 8, 48, 16, 56, 24, 64, 32, + 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, + 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, + 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, + 33, 1, 41, 9, 49, 17, 57, 25}; + + +static const uint8_t sc[16] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1}; + +static const uint8_t sbox[8][4][16] = { + {{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7}, + {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8}, + {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0}, + {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}}, + + {{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10}, + {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5}, + {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15}, + {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}}, + + {{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8}, + {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1}, + {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7}, + {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}}, + + {{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15}, + {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9}, + {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4}, + {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}}, + + {{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9}, + {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6}, + {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14}, + {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}}, + + {{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11}, + {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8}, + {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6}, + {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}}, + + {{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1}, + {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6}, + {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2}, + {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}}, + + {{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7}, + {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2}, + {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8}, + {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}}}; + +static void permute(char *out, const char *in, const uint8_t *p, int n) +{ + int i; + for (i=0;i<n;i++) + out[i] = in[p[i]-1]; +} + +static void lshift(char *d, int count, int n) +{ + char out[64]; + int i; + for (i=0;i<n;i++) + out[i] = d[(i+count)%n]; + for (i=0;i<n;i++) + d[i] = out[i]; +} + +static void concat(char *out, char *in1, char *in2, int l1, int l2) +{ + while (l1--) + *out++ = *in1++; + while (l2--) + *out++ = *in2++; +} + +static void xor(char *out, char *in1, char *in2, int n) +{ + int i; + for (i=0;i<n;i++) + out[i] = in1[i] ^ in2[i]; +} + +static void dohash(char *out, char *in, char *key, int forw) +{ + int i, j, k; + char pk1[56]; + char c[28]; + char d[28]; + char cd[56]; + char ki[16][48]; + char pd1[64]; + char l[32], r[32]; + char rl[64]; + + permute(pk1, key, perm1, 56); + + for (i=0;i<28;i++) + c[i] = pk1[i]; + for (i=0;i<28;i++) + d[i] = pk1[i+28]; + + for (i=0;i<16;i++) { + lshift(c, sc[i], 28); + lshift(d, sc[i], 28); + + concat(cd, c, d, 28, 28); + permute(ki[i], cd, perm2, 48); + } + + permute(pd1, in, perm3, 64); + + for (j=0;j<32;j++) { + l[j] = pd1[j]; + r[j] = pd1[j+32]; + } + + for (i=0;i<16;i++) { + char er[48]; + char erk[48]; + char b[8][6]; + char cb[32]; + char pcb[32]; + char r2[32]; + + permute(er, r, perm4, 48); + + xor(erk, er, ki[forw ? i : 15 - i], 48); + + for (j=0;j<8;j++) + for (k=0;k<6;k++) + b[j][k] = erk[j*6 + k]; + + for (j=0;j<8;j++) { + int m, n; + m = (b[j][0]<<1) | b[j][5]; + + n = (b[j][1]<<3) | (b[j][2]<<2) | (b[j][3]<<1) | b[j][4]; + + for (k=0;k<4;k++) + b[j][k] = (sbox[j][m][n] & (1<<(3-k)))?1:0; + } + + for (j=0;j<8;j++) + for (k=0;k<4;k++) + cb[j*4+k] = b[j][k]; + permute(pcb, cb, perm5, 32); + + xor(r2, l, pcb, 32); + + for (j=0;j<32;j++) + l[j] = r[j]; + + for (j=0;j<32;j++) + r[j] = r2[j]; + } + + concat(rl, r, l, 32, 32); + + permute(out, rl, perm6, 64); +} + +static void str_to_key(const uint8_t *str,uint8_t *key) +{ + int i; + + key[0] = str[0]>>1; + key[1] = ((str[0]&0x01)<<6) | (str[1]>>2); + key[2] = ((str[1]&0x03)<<5) | (str[2]>>3); + key[3] = ((str[2]&0x07)<<4) | (str[3]>>4); + key[4] = ((str[3]&0x0F)<<3) | (str[4]>>5); + key[5] = ((str[4]&0x1F)<<2) | (str[5]>>6); + key[6] = ((str[5]&0x3F)<<1) | (str[6]>>7); + key[7] = str[6]&0x7F; + for (i=0;i<8;i++) { + key[i] = (key[i]<<1); + } +} + +/* + basic des crypt using a 56 bit (7 byte) key +*/ +void des_crypt56(uint8_t out[8], const uint8_t in[8], const uint8_t key[7], int forw) +{ + int i; + char outb[64]; + char inb[64]; + char keyb[64]; + uint8_t key2[8]; + + str_to_key(key, key2); + + for (i=0;i<64;i++) { + inb[i] = (in[i/8] & (1<<(7-(i%8)))) ? 1 : 0; + keyb[i] = (key2[i/8] & (1<<(7-(i%8)))) ? 1 : 0; + outb[i] = 0; + } + + dohash(outb, inb, keyb, forw); + + for (i=0;i<8;i++) { + out[i] = 0; + } + + for (i=0;i<64;i++) { + if (outb[i]) + out[i/8] |= (1<<(7-(i%8))); + } +} + +void E_P16(const uint8_t *p14,uint8_t *p16) +{ + const uint8_t sp8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; + des_crypt56(p16, sp8, p14, 1); + des_crypt56(p16+8, sp8, p14+7, 1); +} + +void E_P24(const uint8_t *p21, const uint8_t *c8, uint8_t *p24) +{ + des_crypt56(p24, c8, p21, 1); + des_crypt56(p24+8, c8, p21+7, 1); + des_crypt56(p24+16, c8, p21+14, 1); +} + +void D_P16(const uint8_t *p14, const uint8_t *in, uint8_t *out) +{ + des_crypt56(out, in, p14, 0); + des_crypt56(out+8, in+8, p14+7, 0); +} + +void E_old_pw_hash( uint8_t *p14, const uint8_t *in, uint8_t *out) +{ + des_crypt56(out, in, p14, 1); + des_crypt56(out+8, in+8, p14+7, 1); +} + +/* des encryption with a 128 bit key */ +void des_crypt128(uint8_t out[8], const uint8_t in[8], const uint8_t key[16]) +{ + uint8_t buf[8]; + des_crypt56(buf, in, key, 1); + des_crypt56(out, buf, key+9, 1); +} + +/* des encryption with a 64 bit key */ +void des_crypt64(uint8_t out[8], const uint8_t in[8], const uint8_t key[8], int forw) +{ + uint8_t buf[8]; + uint8_t key2[8]; + ZERO_STRUCT(key2); + des_crypt56(buf, in, key, forw); + key2[0] = key[7]; + des_crypt56(out, buf, key2, forw); +} + +/* des encryption with a 112 bit (14 byte) key */ +void des_crypt112(uint8_t out[8], const uint8_t in[8], const uint8_t key[14], int forw) +{ + uint8_t buf[8]; + des_crypt56(buf, in, key, forw); + des_crypt56(out, buf, key+7, forw); +} + +/* des encryption of a 16 byte lump of data with a 112 bit key */ +void des_crypt112_16(uint8_t out[16], uint8_t in[16], const uint8_t key[14], int forw) +{ + des_crypt56(out, in, key, forw); + des_crypt56(out + 8, in + 8, key+7, forw); +} + +/* Decode a sam password hash into a password. The password hash is the + same method used to store passwords in the NT registry. The DES key + used is based on the RID of the user. */ +void sam_rid_crypt(uint_t rid, const uint8_t *in, uint8_t *out, int forw) +{ + uint8_t s[14]; + + s[0] = s[4] = s[8] = s[12] = (uint8_t)(rid & 0xFF); + s[1] = s[5] = s[9] = s[13] = (uint8_t)((rid >> 8) & 0xFF); + s[2] = s[6] = s[10] = (uint8_t)((rid >> 16) & 0xFF); + s[3] = s[7] = s[11] = (uint8_t)((rid >> 24) & 0xFF); + + des_crypt56(out, in, s, forw); + des_crypt56(out+8, in+8, s+7, forw); +} diff --git a/libcli/auth/smbencrypt.c b/libcli/auth/smbencrypt.c new file mode 100644 index 0000000000..7659446b75 --- /dev/null +++ b/libcli/auth/smbencrypt.c @@ -0,0 +1,782 @@ +/* + Unix SMB/CIFS implementation. + SMB parameters and setup + Copyright (C) Andrew Tridgell 1992-1998 + Modified by Jeremy Allison 1995. + Copyright (C) Jeremy Allison 1995-2000. + Copyright (C) Luke Kennethc Casson Leighton 1996-2000. + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "../libcli/auth/msrpc_parse.h" +#include "../lib/crypto/crypto.h" +#include "../libcli/auth/libcli_auth.h" + +void SMBencrypt_hash(const uint8_t lm_hash[16], const uint8_t *c8, uint8_t p24[24]) +{ + uint8_t p21[21]; + + memset(p21,'\0',21); + memcpy(p21, lm_hash, 16); + + SMBOWFencrypt(p21, c8, p24); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("SMBencrypt_hash: lm#, challenge, response\n")); + dump_data(100, p21, 16); + dump_data(100, c8, 8); + dump_data(100, p24, 24); +#endif +} + +/* + This implements the X/Open SMB password encryption + It takes a password ('unix' string), a 8 byte "crypt key" + and puts 24 bytes of encrypted password into p24 + + Returns False if password must have been truncated to create LM hash +*/ + +bool SMBencrypt(const char *passwd, const uint8_t *c8, uint8_t p24[24]) +{ + bool ret; + uint8_t lm_hash[16]; + + ret = E_deshash(passwd, lm_hash); + SMBencrypt_hash(lm_hash, c8, p24); + return ret; +} + +/** + * Creates the MD4 Hash of the users password in NT UNICODE. + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with md4, caller allocated 16 byte buffer + */ + +bool E_md4hash(const char *passwd, uint8_t p16[16]) +{ + size_t len; + smb_ucs2_t *wpwd; + bool ret; + + ret = push_ucs2_talloc(NULL, &wpwd, passwd, &len); + if (!ret || len < 2) { + /* We don't want to return fixed data, as most callers + * don't check */ + mdfour(p16, (const uint8_t *)passwd, strlen(passwd)); + return false; + } + + len -= 2; + mdfour(p16, (const uint8_t *)wpwd, len); + + talloc_free(wpwd); + return true; +} + +/** + * Creates the MD5 Hash of a combination of 16 byte salt and 16 byte NT hash. + * @param 16 byte salt. + * @param 16 byte NT hash. + * @param 16 byte return hashed with md5, caller allocated 16 byte buffer + */ + +void E_md5hash(const uint8_t salt[16], const uint8_t nthash[16], uint8_t hash_out[16]) +{ + struct MD5Context tctx; + uint8_t array[32]; + + memset(hash_out, '\0', 16); + memcpy(array, salt, 16); + memcpy(&array[16], nthash, 16); + MD5Init(&tctx); + MD5Update(&tctx, array, 32); + MD5Final(hash_out, &tctx); +} + +/** + * Creates the DES forward-only Hash of the users password in DOS ASCII charset + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with DES, caller allocated 16 byte buffer + * @return false if password was > 14 characters, and therefore may be incorrect, otherwise true + * @note p16 is filled in regardless + */ + +bool E_deshash(const char *passwd, uint8_t p16[16]) +{ + bool ret = true; + char dospwd[256]; + ZERO_STRUCT(dospwd); + + /* Password must be converted to DOS charset - null terminated, uppercase. */ + push_string(dospwd, passwd, sizeof(dospwd), STR_ASCII|STR_UPPER|STR_TERMINATE); + + /* Only the first 14 chars are considered, password need not be null terminated. */ + E_P16((const uint8_t *)dospwd, p16); + + if (strlen(dospwd) > 14) { + ret = false; + } + + ZERO_STRUCT(dospwd); + + return ret; +} + +/** + * Creates the MD4 and DES (LM) Hash of the users password. + * MD4 is of the NT Unicode, DES is of the DOS UPPERCASE password. + * @param passwd password in 'unix' charset. + * @param nt_p16 return password hashed with md4, caller allocated 16 byte buffer + * @param p16 return password hashed with des, caller allocated 16 byte buffer + */ + +/* Does both the NT and LM owfs of a user's password */ +void nt_lm_owf_gen(const char *pwd, uint8_t nt_p16[16], uint8_t p16[16]) +{ + /* Calculate the MD4 hash (NT compatible) of the password */ + memset(nt_p16, '\0', 16); + E_md4hash(pwd, nt_p16); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("nt_lm_owf_gen: pwd, nt#\n")); + dump_data(120, (uint8_t *)pwd, strlen(pwd)); + dump_data(100, nt_p16, 16); +#endif + + E_deshash(pwd, (uint8_t *)p16); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("nt_lm_owf_gen: pwd, lm#\n")); + dump_data(120, (uint8_t *)pwd, strlen(pwd)); + dump_data(100, p16, 16); +#endif +} + +/* Does both the NTLMv2 owfs of a user's password */ +bool ntv2_owf_gen(const uint8_t owf[16], + const char *user_in, const char *domain_in, + bool upper_case_domain, /* Transform the domain into UPPER case */ + uint8_t kr_buf[16]) +{ + smb_ucs2_t *user; + smb_ucs2_t *domain; + size_t user_byte_len; + size_t domain_byte_len; + bool ret; + + HMACMD5Context ctx; + TALLOC_CTX *mem_ctx = talloc_init("ntv2_owf_gen for %s\\%s", domain_in, user_in); + + if (!mem_ctx) { + return false; + } + + if (!user_in) { + user_in = ""; + } + + if (!domain_in) { + domain_in = ""; + } + + user_in = strupper_talloc(mem_ctx, user_in); + if (user_in == NULL) { + talloc_free(mem_ctx); + return false; + } + + if (upper_case_domain) { + domain_in = strupper_talloc(mem_ctx, domain_in); + if (domain_in == NULL) { + talloc_free(mem_ctx); + return false; + } + } + + ret = push_ucs2_talloc(mem_ctx, &user, user_in, &user_byte_len ); + if (!ret) { + DEBUG(0, ("push_uss2_talloc() for user failed)\n")); + talloc_free(mem_ctx); + return false; + } + + ret = push_ucs2_talloc(mem_ctx, &domain, domain_in, &domain_byte_len); + if (!ret) { + DEBUG(0, ("push_ucs2_talloc() for domain failed\n")); + talloc_free(mem_ctx); + return false; + } + + SMB_ASSERT(user_byte_len >= 2); + SMB_ASSERT(domain_byte_len >= 2); + + /* We don't want null termination */ + user_byte_len = user_byte_len - 2; + domain_byte_len = domain_byte_len - 2; + + hmac_md5_init_limK_to_64(owf, 16, &ctx); + hmac_md5_update((const void *)user, user_byte_len, &ctx); + hmac_md5_update((const void *)domain, domain_byte_len, &ctx); + hmac_md5_final(kr_buf, &ctx); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("ntv2_owf_gen: user, domain, owfkey, kr\n")); + dump_data(100, (const void *)user, user_byte_len); + dump_data(100, (const void *)domain, domain_byte_len); + dump_data(100, owf, 16); + dump_data(100, kr_buf, 16); +#endif + + talloc_free(mem_ctx); + return true; +} + +/* Does the des encryption from the NT or LM MD4 hash. */ +void SMBOWFencrypt(const uint8_t passwd[16], const uint8_t *c8, uint8_t p24[24]) +{ + uint8_t p21[21]; + + ZERO_STRUCT(p21); + + memcpy(p21, passwd, 16); + E_P24(p21, c8, p24); +} + +/* Does the des encryption. */ + +void SMBNTencrypt_hash(const uint8_t nt_hash[16], uint8_t *c8, uint8_t *p24) +{ + uint8_t p21[21]; + + memset(p21,'\0',21); + memcpy(p21, nt_hash, 16); + SMBOWFencrypt(p21, c8, p24); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("SMBNTencrypt: nt#, challenge, response\n")); + dump_data(100, p21, 16); + dump_data(100, c8, 8); + dump_data(100, p24, 24); +#endif +} + +/* Does the NT MD4 hash then des encryption. Plaintext version of the above. */ + +void SMBNTencrypt(const char *passwd, uint8_t *c8, uint8_t *p24) +{ + uint8_t nt_hash[16]; + E_md4hash(passwd, nt_hash); + SMBNTencrypt_hash(nt_hash, c8, p24); +} + + +/* Does the md5 encryption from the Key Response for NTLMv2. */ +void SMBOWFencrypt_ntv2(const uint8_t kr[16], + const DATA_BLOB *srv_chal, + const DATA_BLOB *smbcli_chal, + uint8_t resp_buf[16]) +{ + HMACMD5Context ctx; + + hmac_md5_init_limK_to_64(kr, 16, &ctx); + hmac_md5_update(srv_chal->data, srv_chal->length, &ctx); + hmac_md5_update(smbcli_chal->data, smbcli_chal->length, &ctx); + hmac_md5_final(resp_buf, &ctx); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBOWFencrypt_ntv2: srv_chal, smbcli_chal, resp_buf\n")); + dump_data(100, srv_chal->data, srv_chal->length); + dump_data(100, smbcli_chal->data, smbcli_chal->length); + dump_data(100, resp_buf, 16); +#endif +} + +void SMBsesskeygen_ntv2(const uint8_t kr[16], + const uint8_t * nt_resp, uint8_t sess_key[16]) +{ + /* a very nice, 128 bit, variable session key */ + + HMACMD5Context ctx; + + hmac_md5_init_limK_to_64(kr, 16, &ctx); + hmac_md5_update(nt_resp, 16, &ctx); + hmac_md5_final((uint8_t *)sess_key, &ctx); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_ntv2:\n")); + dump_data(100, sess_key, 16); +#endif +} + +void SMBsesskeygen_ntv1(const uint8_t kr[16], uint8_t sess_key[16]) +{ + /* yes, this session key does not change - yes, this + is a problem - but it is 128 bits */ + + mdfour((uint8_t *)sess_key, kr, 16); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_ntv1:\n")); + dump_data(100, sess_key, 16); +#endif +} + +void SMBsesskeygen_lm_sess_key(const uint8_t lm_hash[16], + const uint8_t lm_resp[24], /* only uses 8 */ + uint8_t sess_key[16]) +{ + /* Calculate the LM session key (effective length 40 bits, + but changes with each session) */ + uint8_t p24[24]; + uint8_t partial_lm_hash[14]; + + memcpy(partial_lm_hash, lm_hash, 8); + memset(partial_lm_hash + 8, 0xbd, 6); + + des_crypt56(p24, lm_resp, partial_lm_hash, 1); + des_crypt56(p24+8, lm_resp, partial_lm_hash + 7, 1); + + memcpy(sess_key, p24, 16); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_lm_sess_key: \n")); + dump_data(100, sess_key, 16); +#endif +} + +DATA_BLOB NTLMv2_generate_names_blob(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *domain) +{ + DATA_BLOB names_blob = data_blob_talloc(mem_ctx, NULL, 0); + + msrpc_gen(mem_ctx, &names_blob, + "aaa", + NTLMSSP_NAME_TYPE_DOMAIN, domain, + NTLMSSP_NAME_TYPE_SERVER, hostname, + 0, ""); + return names_blob; +} + +static DATA_BLOB NTLMv2_generate_client_data(TALLOC_CTX *mem_ctx, const DATA_BLOB *names_blob) +{ + uint8_t client_chal[8]; + DATA_BLOB response = data_blob(NULL, 0); + uint8_t long_date[8]; + NTTIME nttime; + + unix_to_nt_time(&nttime, time(NULL)); + + generate_random_buffer(client_chal, sizeof(client_chal)); + + push_nttime(long_date, 0, nttime); + + /* See http://www.ubiqx.org/cifs/SMB.html#SMB.8.5 */ + + msrpc_gen(mem_ctx, &response, "ddbbdb", + 0x00000101, /* Header */ + 0, /* 'Reserved' */ + long_date, 8, /* Timestamp */ + client_chal, 8, /* client challenge */ + 0, /* Unknown */ + names_blob->data, names_blob->length); /* End of name list */ + + return response; +} + +static DATA_BLOB NTLMv2_generate_response(TALLOC_CTX *out_mem_ctx, + const uint8_t ntlm_v2_hash[16], + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob) +{ + uint8_t ntlmv2_response[16]; + DATA_BLOB ntlmv2_client_data; + DATA_BLOB final_response; + + TALLOC_CTX *mem_ctx = talloc_named(out_mem_ctx, 0, + "NTLMv2_generate_response internal context"); + + if (!mem_ctx) { + return data_blob(NULL, 0); + } + + /* NTLMv2 */ + /* generate some data to pass into the response function - including + the hostname and domain name of the server */ + ntlmv2_client_data = NTLMv2_generate_client_data(mem_ctx, names_blob); + + /* Given that data, and the challenge from the server, generate a response */ + SMBOWFencrypt_ntv2(ntlm_v2_hash, server_chal, &ntlmv2_client_data, ntlmv2_response); + + final_response = data_blob_talloc(out_mem_ctx, NULL, sizeof(ntlmv2_response) + ntlmv2_client_data.length); + + memcpy(final_response.data, ntlmv2_response, sizeof(ntlmv2_response)); + + memcpy(final_response.data+sizeof(ntlmv2_response), + ntlmv2_client_data.data, ntlmv2_client_data.length); + + talloc_free(mem_ctx); + + return final_response; +} + +static DATA_BLOB LMv2_generate_response(TALLOC_CTX *mem_ctx, + const uint8_t ntlm_v2_hash[16], + const DATA_BLOB *server_chal) +{ + uint8_t lmv2_response[16]; + DATA_BLOB lmv2_client_data = data_blob_talloc(mem_ctx, NULL, 8); + DATA_BLOB final_response = data_blob_talloc(mem_ctx, NULL,24); + + /* LMv2 */ + /* client-supplied random data */ + generate_random_buffer(lmv2_client_data.data, lmv2_client_data.length); + + /* Given that data, and the challenge from the server, generate a response */ + SMBOWFencrypt_ntv2(ntlm_v2_hash, server_chal, &lmv2_client_data, lmv2_response); + memcpy(final_response.data, lmv2_response, sizeof(lmv2_response)); + + /* after the first 16 bytes is the random data we generated above, + so the server can verify us with it */ + memcpy(final_response.data+sizeof(lmv2_response), + lmv2_client_data.data, lmv2_client_data.length); + + data_blob_free(&lmv2_client_data); + + return final_response; +} + +bool SMBNTLMv2encrypt_hash(TALLOC_CTX *mem_ctx, + const char *user, const char *domain, const uint8_t nt_hash[16], + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *lm_session_key, DATA_BLOB *user_session_key) +{ + uint8_t ntlm_v2_hash[16]; + + /* We don't use the NT# directly. Instead we use it mashed up with + the username and domain. + This prevents username swapping during the auth exchange + */ + if (!ntv2_owf_gen(nt_hash, user, domain, true, ntlm_v2_hash)) { + return false; + } + + if (nt_response) { + *nt_response = NTLMv2_generate_response(mem_ctx, + ntlm_v2_hash, server_chal, + names_blob); + if (user_session_key) { + *user_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + /* The NTLMv2 calculations also provide a session key, for signing etc later */ + /* use only the first 16 bytes of nt_response for session key */ + SMBsesskeygen_ntv2(ntlm_v2_hash, nt_response->data, user_session_key->data); + } + } + + /* LMv2 */ + + if (lm_response) { + *lm_response = LMv2_generate_response(mem_ctx, + ntlm_v2_hash, server_chal); + if (lm_session_key) { + *lm_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + /* The NTLMv2 calculations also provide a session key, for signing etc later */ + /* use only the first 16 bytes of lm_response for session key */ + SMBsesskeygen_ntv2(ntlm_v2_hash, lm_response->data, lm_session_key->data); + } + } + + return true; +} + +bool SMBNTLMv2encrypt(TALLOC_CTX *mem_ctx, + const char *user, const char *domain, + const char *password, + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *lm_session_key, DATA_BLOB *user_session_key) +{ + uint8_t nt_hash[16]; + E_md4hash(password, nt_hash); + + return SMBNTLMv2encrypt_hash(mem_ctx, + user, domain, nt_hash, server_chal, names_blob, + lm_response, nt_response, lm_session_key, user_session_key); +} + +/*********************************************************** + encode a password buffer with a unicode password. The buffer + is filled with random data to make it harder to attack. +************************************************************/ +bool encode_pw_buffer(uint8_t buffer[516], const char *password, int string_flags) +{ + uint8_t new_pw[512]; + size_t new_pw_len; + + /* the incoming buffer can be any alignment. */ + string_flags |= STR_NOALIGN; + + new_pw_len = push_string(new_pw, + password, + sizeof(new_pw), string_flags); + + memcpy(&buffer[512 - new_pw_len], new_pw, new_pw_len); + + generate_random_buffer(buffer, 512 - new_pw_len); + + /* + * The length of the new password is in the last 4 bytes of + * the data buffer. + */ + SIVAL(buffer, 512, new_pw_len); + ZERO_STRUCT(new_pw); + return true; +} + + +/*********************************************************** + decode a password buffer + *new_pw_len is the length in bytes of the possibly mulitbyte + returned password including termination. +************************************************************/ + +bool decode_pw_buffer(TALLOC_CTX *ctx, + uint8_t in_buffer[516], + char **pp_new_pwrd, + size_t *new_pw_len, + charset_t string_charset) +{ + int byte_len=0; + + *pp_new_pwrd = NULL; + *new_pw_len = 0; + + /* + Warning !!! : This function is called from some rpc call. + The password IN the buffer may be a UNICODE string. + The password IN new_pwrd is an ASCII string + If you reuse that code somewhere else check first. + */ + + /* The length of the new password is in the last 4 bytes of the data buffer. */ + + byte_len = IVAL(in_buffer, 512); + +#ifdef DEBUG_PASSWORD + dump_data(100, in_buffer, 516); +#endif + + /* Password cannot be longer than the size of the password buffer */ + if ( (byte_len < 0) || (byte_len > 512)) { + DEBUG(0, ("decode_pw_buffer: incorrect password length (%d).\n", byte_len)); + DEBUG(0, ("decode_pw_buffer: check that 'encrypt passwords = yes'\n")); + return false; + } + + /* decode into the return buffer. */ + if (!convert_string_talloc(ctx, string_charset, CH_UNIX, + &in_buffer[512 - byte_len], + byte_len, + (void *)pp_new_pwrd, + new_pw_len, + false)) { + DEBUG(0, ("decode_pw_buffer: failed to convert incoming password\n")); + return false; + } + +#ifdef DEBUG_PASSWORD + DEBUG(100,("decode_pw_buffer: new_pwrd: ")); + dump_data(100, (uint8_t *)*pp_new_pwrd, *new_pw_len); + DEBUG(100,("multibyte len:%lu\n", (unsigned long int)*new_pw_len)); + DEBUG(100,("original char len:%d\n", byte_len/2)); +#endif + + return true; +} + +/*********************************************************** + Decode an arc4 encrypted password change buffer. +************************************************************/ + +void encode_or_decode_arc4_passwd_buffer(unsigned char pw_buf[532], const DATA_BLOB *psession_key) +{ + struct MD5Context tctx; + unsigned char key_out[16]; + + /* Confounder is last 16 bytes. */ + + MD5Init(&tctx); + MD5Update(&tctx, &pw_buf[516], 16); + MD5Update(&tctx, psession_key->data, psession_key->length); + MD5Final(key_out, &tctx); + /* arc4 with key_out. */ + arcfour_crypt(pw_buf, key_out, 516); +} + +/*********************************************************** + encode a password buffer with an already unicode password. The + rest of the buffer is filled with random data to make it harder to attack. +************************************************************/ +bool set_pw_in_buffer(uint8_t buffer[516], DATA_BLOB *password) +{ + if (password->length > 512) { + return false; + } + + memcpy(&buffer[512 - password->length], password->data, password->length); + + generate_random_buffer(buffer, 512 - password->length); + + /* + * The length of the new password is in the last 4 bytes of + * the data buffer. + */ + SIVAL(buffer, 512, password->length); + return true; +} + +/*********************************************************** + decode a password buffer + *new_pw_size is the length in bytes of the extracted unicode password +************************************************************/ +bool extract_pw_from_buffer(TALLOC_CTX *mem_ctx, + uint8_t in_buffer[516], DATA_BLOB *new_pass) +{ + int byte_len=0; + + /* The length of the new password is in the last 4 bytes of the data buffer. */ + + byte_len = IVAL(in_buffer, 512); + +#ifdef DEBUG_PASSWORD + dump_data(100, in_buffer, 516); +#endif + + /* Password cannot be longer than the size of the password buffer */ + if ( (byte_len < 0) || (byte_len > 512)) { + return false; + } + + *new_pass = data_blob_talloc(mem_ctx, &in_buffer[512 - byte_len], byte_len); + + if (!new_pass->data) { + return false; + } + + return true; +} + + +/* encode a wkssvc_PasswordBuffer: + * + * similar to samr_CryptPasswordEx. Different: 8byte confounder (instead of + * 16byte), confounder in front of the 516 byte buffer (instead of after that + * buffer), calling MD5Update() first with session_key and then with confounder + * (vice versa in samr) - Guenther */ + +void encode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + const char *pwd, + DATA_BLOB *session_key, + struct wkssvc_PasswordBuffer **pwd_buf) +{ + uint8_t buffer[516]; + struct MD5Context ctx; + struct wkssvc_PasswordBuffer *my_pwd_buf = NULL; + DATA_BLOB confounded_session_key; + int confounder_len = 8; + uint8_t confounder[8]; + + my_pwd_buf = talloc_zero(mem_ctx, struct wkssvc_PasswordBuffer); + if (!my_pwd_buf) { + return; + } + + confounded_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + encode_pw_buffer(buffer, pwd, STR_UNICODE); + + generate_random_buffer((uint8_t *)confounder, confounder_len); + + MD5Init(&ctx); + MD5Update(&ctx, session_key->data, session_key->length); + MD5Update(&ctx, confounder, confounder_len); + MD5Final(confounded_session_key.data, &ctx); + + arcfour_crypt_blob(buffer, 516, &confounded_session_key); + + memcpy(&my_pwd_buf->data[0], confounder, confounder_len); + memcpy(&my_pwd_buf->data[8], buffer, 516); + + data_blob_free(&confounded_session_key); + + *pwd_buf = my_pwd_buf; +} + +WERROR decode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + struct wkssvc_PasswordBuffer *pwd_buf, + DATA_BLOB *session_key, + char **pwd) +{ + uint8_t buffer[516]; + struct MD5Context ctx; + size_t pwd_len; + + DATA_BLOB confounded_session_key; + + int confounder_len = 8; + uint8_t confounder[8]; + + *pwd = NULL; + + if (!pwd_buf) { + return WERR_BAD_PASSWORD; + } + + if (session_key->length != 16) { + DEBUG(10,("invalid session key\n")); + return WERR_BAD_PASSWORD; + } + + confounded_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + memcpy(&confounder, &pwd_buf->data[0], confounder_len); + memcpy(&buffer, &pwd_buf->data[8], 516); + + MD5Init(&ctx); + MD5Update(&ctx, session_key->data, session_key->length); + MD5Update(&ctx, confounder, confounder_len); + MD5Final(confounded_session_key.data, &ctx); + + arcfour_crypt_blob(buffer, 516, &confounded_session_key); + + if (!decode_pw_buffer(mem_ctx, buffer, pwd, &pwd_len, CH_UTF16)) { + data_blob_free(&confounded_session_key); + return WERR_BAD_PASSWORD; + } + + data_blob_free(&confounded_session_key); + + return WERR_OK; +} + diff --git a/libcli/drsuapi/config.mk b/libcli/drsuapi/config.mk new file mode 100644 index 0000000000..cb002ca3dc --- /dev/null +++ b/libcli/drsuapi/config.mk @@ -0,0 +1,8 @@ +[SUBSYSTEM::LIBCLI_DRSUAPI] +PUBLIC_DEPENDENCIES = \ + LIBCLI_AUTH + +LIBCLI_DRSUAPI_OBJ_FILES = $(addprefix $(libclicommonsrcdir)/drsuapi/, \ + repl_decrypt.o) + +PUBLIC_HEADERS += ../libcli/drsuapi/drsuapi.h diff --git a/libcli/drsuapi/drsuapi.h b/libcli/drsuapi/drsuapi.h new file mode 100644 index 0000000000..b95350779a --- /dev/null +++ b/libcli/drsuapi/drsuapi.h @@ -0,0 +1,33 @@ +/* + Unix SMB/CIFS mplementation. + Helper functions for applying replicated objects + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +WERROR drsuapi_decrypt_attribute_value(TALLOC_CTX *mem_ctx, + const DATA_BLOB *gensec_skey, + bool rid_crypt, + uint32_t rid, + DATA_BLOB *in, + DATA_BLOB *out); + +WERROR drsuapi_decrypt_attribute(TALLOC_CTX *mem_ctx, + const DATA_BLOB *gensec_skey, + uint32_t rid, + struct drsuapi_DsReplicaAttribute *attr); + diff --git a/libcli/drsuapi/repl_decrypt.c b/libcli/drsuapi/repl_decrypt.c new file mode 100644 index 0000000000..9d7c1b6acf --- /dev/null +++ b/libcli/drsuapi/repl_decrypt.c @@ -0,0 +1,188 @@ +/* + Unix SMB/CIFS mplementation. + Helper functions for applying replicated objects + + Copyright (C) Stefan Metzmacher <metze@samba.org> 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "../lib/crypto/crypto.h" +#include "../libcli/drsuapi/drsuapi.h" +#include "libcli/auth/libcli_auth.h" + +WERROR drsuapi_decrypt_attribute_value(TALLOC_CTX *mem_ctx, + const DATA_BLOB *gensec_skey, + bool rid_crypt, + uint32_t rid, + DATA_BLOB *in, + DATA_BLOB *out) +{ + DATA_BLOB confounder; + DATA_BLOB enc_buffer; + + struct MD5Context md5; + uint8_t _enc_key[16]; + DATA_BLOB enc_key; + + DATA_BLOB dec_buffer; + + uint32_t crc32_given; + uint32_t crc32_calc; + DATA_BLOB checked_buffer; + + DATA_BLOB plain_buffer; + + /* + * users with rid == 0 should not exist + */ + if (rid_crypt && rid == 0) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + /* + * the first 16 bytes at the beginning are the confounder + * followed by the 4 byte crc32 checksum + */ + if (in->length < 20) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + confounder = data_blob_const(in->data, 16); + enc_buffer = data_blob_const(in->data + 16, in->length - 16); + + /* + * build the encryption key md5 over the session key followed + * by the confounder + * + * here the gensec session key is used and + * not the dcerpc ncacn_ip_tcp "SystemLibraryDTC" key! + */ + enc_key = data_blob_const(_enc_key, sizeof(_enc_key)); + MD5Init(&md5); + MD5Update(&md5, gensec_skey->data, gensec_skey->length); + MD5Update(&md5, confounder.data, confounder.length); + MD5Final(enc_key.data, &md5); + + /* + * copy the encrypted buffer part and + * decrypt it using the created encryption key using arcfour + */ + dec_buffer = data_blob_const(enc_buffer.data, enc_buffer.length); + arcfour_crypt_blob(dec_buffer.data, dec_buffer.length, &enc_key); + + /* + * the first 4 byte are the crc32 checksum + * of the remaining bytes + */ + crc32_given = IVAL(dec_buffer.data, 0); + crc32_calc = crc32_calc_buffer(dec_buffer.data + 4 , dec_buffer.length - 4); + if (crc32_given != crc32_calc) { + return WERR_SEC_E_DECRYPT_FAILURE; + } + checked_buffer = data_blob_const(dec_buffer.data + 4, dec_buffer.length - 4); + + plain_buffer = data_blob_talloc(mem_ctx, checked_buffer.data, checked_buffer.length); + W_ERROR_HAVE_NO_MEMORY(plain_buffer.data); + + /* + * The following rid_crypt obfuscation isn't session specific + * and not really needed here, because we allways know the rid of the + * user account. + * + * some attributes with this 'additional encryption' include + * dBCSPwd, unicodePwd, ntPwdHistory, lmPwdHistory + * + * But for the rest of samba it's easier when we remove this static + * obfuscation here + */ + if (rid_crypt) { + uint32_t i, num_hashes; + + if ((checked_buffer.length % 16) != 0) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + num_hashes = plain_buffer.length / 16; + for (i = 0; i < num_hashes; i++) { + uint32_t offset = i * 16; + sam_rid_crypt(rid, checked_buffer.data + offset, plain_buffer.data + offset, 0); + } + } + + *out = plain_buffer; + return WERR_OK; +} + +WERROR drsuapi_decrypt_attribute(TALLOC_CTX *mem_ctx, + const DATA_BLOB *gensec_skey, + uint32_t rid, + struct drsuapi_DsReplicaAttribute *attr) +{ + WERROR status; + DATA_BLOB *enc_data; + DATA_BLOB plain_data; + bool rid_crypt = false; + + if (attr->value_ctr.num_values == 0) { + return WERR_OK; + } + + switch (attr->attid) { + case DRSUAPI_ATTRIBUTE_dBCSPwd: + case DRSUAPI_ATTRIBUTE_unicodePwd: + case DRSUAPI_ATTRIBUTE_ntPwdHistory: + case DRSUAPI_ATTRIBUTE_lmPwdHistory: + rid_crypt = true; + break; + case DRSUAPI_ATTRIBUTE_supplementalCredentials: + case DRSUAPI_ATTRIBUTE_priorValue: + case DRSUAPI_ATTRIBUTE_currentValue: + case DRSUAPI_ATTRIBUTE_trustAuthOutgoing: + case DRSUAPI_ATTRIBUTE_trustAuthIncoming: + case DRSUAPI_ATTRIBUTE_initialAuthOutgoing: + case DRSUAPI_ATTRIBUTE_initialAuthIncoming: + break; + default: + return WERR_OK; + } + + if (attr->value_ctr.num_values > 1) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + if (!attr->value_ctr.values[0].blob) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + enc_data = attr->value_ctr.values[0].blob; + + status = drsuapi_decrypt_attribute_value(mem_ctx, + gensec_skey, + rid_crypt, + rid, + enc_data, + &plain_data); + W_ERROR_NOT_OK_RETURN(status); + + talloc_free(attr->value_ctr.values[0].blob->data); + *attr->value_ctr.values[0].blob = plain_data; + + return WERR_OK; +} diff --git a/libcli/samsync/config.mk b/libcli/samsync/config.mk new file mode 100644 index 0000000000..69f798342e --- /dev/null +++ b/libcli/samsync/config.mk @@ -0,0 +1,10 @@ +[SUBSYSTEM::LIBCLI_SAMSYNC] +PUBLIC_DEPENDENCIES = \ + LIBCLI_AUTH + +LIBCLI_SAMSYNC_OBJ_FILES = $(addprefix $(libclicommonsrcdir)/samsync/, \ + decrypt.o) + +PUBLIC_HEADERS += ../libcli/samsync/samsync.h + +$(eval $(call proto_header_template,$(libclicommonsrcdir)/samsync/samsync.h,$(LIBCLI_SAMSYNC_OBJ_FILES:.o=.c))) diff --git a/libcli/samsync/decrypt.c b/libcli/samsync/decrypt.c new file mode 100644 index 0000000000..b3fab712bc --- /dev/null +++ b/libcli/samsync/decrypt.c @@ -0,0 +1,174 @@ +/* + Unix SMB/CIFS implementation. + + Extract the user/system database from a remote SamSync server + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Guenther Deschner <gd@samba.org> 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "includes.h" +#include "../lib/crypto/crypto.h" +#include "../libcli/auth/libcli_auth.h" +#include "../libcli/samsync/samsync.h" +#include "librpc/gen_ndr/ndr_netlogon.h" + +/** + * Decrypt and extract the user's passwords. + * + * The writes decrypted (no longer 'RID encrypted' or arcfour encrypted) + * passwords back into the structure + */ + +static NTSTATUS fix_user(TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *delta) +{ + + uint32_t rid = delta->delta_id_union.rid; + struct netr_DELTA_USER *user = delta->delta_union.user; + struct samr_Password lm_hash; + struct samr_Password nt_hash; + unsigned char zero_buf[16]; + + memset(zero_buf, '\0', sizeof(zero_buf)); + + /* Note that win2000 may send us all zeros + * for the hashes if it doesn't + * think this channel is secure enough. */ + if (user->lm_password_present) { + if (memcmp(user->lmpassword.hash, zero_buf, 16) != 0) { + sam_rid_crypt(rid, user->lmpassword.hash, lm_hash.hash, 0); + } else { + memset(lm_hash.hash, '\0', sizeof(lm_hash.hash)); + } + user->lmpassword = lm_hash; + } + + if (user->nt_password_present) { + if (memcmp(user->ntpassword.hash, zero_buf, 16) != 0) { + sam_rid_crypt(rid, user->ntpassword.hash, nt_hash.hash, 0); + } else { + memset(nt_hash.hash, '\0', sizeof(nt_hash.hash)); + } + user->ntpassword = nt_hash; + } + + if (user->user_private_info.SensitiveData) { + DATA_BLOB data; + struct netr_USER_KEYS keys; + enum ndr_err_code ndr_err; + data.data = user->user_private_info.SensitiveData; + data.length = user->user_private_info.DataLength; + netlogon_creds_arcfour_crypt(creds, data.data, data.length); + user->user_private_info.SensitiveData = data.data; + user->user_private_info.DataLength = data.length; + + ndr_err = ndr_pull_struct_blob(&data, mem_ctx, NULL, &keys, + (ndr_pull_flags_fn_t)ndr_pull_netr_USER_KEYS); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dump_data(10, data.data, data.length); + return ndr_map_error2ntstatus(ndr_err); + } + + /* Note that win2000 may send us all zeros + * for the hashes if it doesn't + * think this channel is secure enough. */ + if (keys.keys.keys2.lmpassword.length == 16) { + if (memcmp(keys.keys.keys2.lmpassword.pwd.hash, + zero_buf, 16) != 0) { + sam_rid_crypt(rid, + keys.keys.keys2.lmpassword.pwd.hash, + lm_hash.hash, 0); + } else { + memset(lm_hash.hash, '\0', sizeof(lm_hash.hash)); + } + user->lmpassword = lm_hash; + user->lm_password_present = true; + } + if (keys.keys.keys2.ntpassword.length == 16) { + if (memcmp(keys.keys.keys2.ntpassword.pwd.hash, + zero_buf, 16) != 0) { + sam_rid_crypt(rid, + keys.keys.keys2.ntpassword.pwd.hash, + nt_hash.hash, 0); + } else { + memset(nt_hash.hash, '\0', sizeof(nt_hash.hash)); + } + user->ntpassword = nt_hash; + user->nt_password_present = true; + } + /* TODO: rid decrypt history fields */ + } + return NT_STATUS_OK; +} + +/** + * Decrypt and extract the secrets + * + * The writes decrypted secrets back into the structure + */ +static NTSTATUS fix_secret(TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds, + enum netr_SamDatabaseID database, + struct netr_DELTA_ENUM *delta) +{ + struct netr_DELTA_SECRET *secret = delta->delta_union.secret; + netlogon_creds_arcfour_crypt(creds, secret->current_cipher.cipher_data, + secret->current_cipher.maxlen); + + netlogon_creds_arcfour_crypt(creds, secret->old_cipher.cipher_data, + secret->old_cipher.maxlen); + + return NT_STATUS_OK; +} + +/** + * Fix up the delta, dealing with encryption issues so that the final + * callback need only do the printing or application logic + */ + +NTSTATUS samsync_fix_delta(TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds, + enum netr_SamDatabaseID database_id, + struct netr_DELTA_ENUM *delta) +{ + NTSTATUS status = NT_STATUS_OK; + + switch (delta->delta_type) { + case NETR_DELTA_USER: + + status = fix_user(mem_ctx, + creds, + database_id, + delta); + break; + case NETR_DELTA_SECRET: + + status = fix_secret(mem_ctx, + creds, + database_id, + delta); + break; + default: + break; + } + + return status; +} + |