From 08845ad61641a66f9019a9ae35ff765d9d093ea9 Mon Sep 17 00:00:00 2001 From: Michael Adam Date: Wed, 8 Oct 2014 19:25:15 +0200 Subject: [PATCH] s3:smb2_negprot.c: add support SMB 3.1 negotiate contexts Used for: - preauthentication validation - negotiation of ciphers for sigingn and encryprtion Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Michael Adam Signed-off-by: Stefan Metzmacher --- source3/smbd/globals.h | 7 ++ source3/smbd/smb2_negprot.c | 243 +++++++++++++++++++++++++++++++++++++++++++- source3/smbd/smb2_server.c | 28 +++++ 3 files changed, 274 insertions(+), 4 deletions(-) diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h index 22cf5d60890..3ddafafa49b 100644 --- a/source3/smbd/globals.h +++ b/source3/smbd/globals.h @@ -344,6 +344,10 @@ bool push_deferred_open_message_smb2(struct smbd_smb2_request *smb2req, struct smbXsrv_client; +struct smbXsrv_preauth { + uint8_t sha512_value[64]; +}; + struct smbXsrv_connection { struct smbXsrv_connection *prev, *next; @@ -516,6 +520,8 @@ struct smbXsrv_connection { uint16_t cipher; } server; + struct smbXsrv_preauth preauth; + struct smbd_smb2_request *requests; } smb2; }; @@ -662,6 +668,7 @@ struct smbd_smb2_request { * request/response of a compound chain */ DATA_BLOB last_key; + struct smbXsrv_preauth *preauth; struct timeval request_time; diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c index ae2f3f7d53e..199dc147ab6 100644 --- a/source3/smbd/smb2_negprot.c +++ b/source3/smbd/smb2_negprot.c @@ -22,6 +22,7 @@ #include "smbd/smbd.h" #include "smbd/globals.h" #include "../libcli/smb/smb_common.h" +#include "../libcli/smb/smb2_negotiate_context.h" #include "../lib/tsocket/tsocket.h" #include "../librpc/ndr/libndr.h" @@ -140,6 +141,13 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) uint32_t in_capabilities; DATA_BLOB in_guid_blob; struct GUID in_guid; + struct smb2_negotiate_contexts in_c = { .num_contexts = 0, }; + struct smb2_negotiate_context *in_preauth = NULL; + struct smb2_negotiate_context *in_cipher = NULL; + struct smb2_negotiate_contexts out_c = { .num_contexts = 0, }; + DATA_BLOB out_negotiate_context_blob = data_blob_null; + uint32_t out_negotiate_context_offset = 0; + uint16_t out_negotiate_context_count = 0; uint16_t dialect = 0; uint32_t capabilities; DATA_BLOB out_guid_blob; @@ -201,6 +209,53 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) return smbd_smb2_request_error(req, NT_STATUS_NOT_SUPPORTED); } + if (protocol >= PROTOCOL_SMB3_10) { + uint32_t in_negotiate_context_offset = 0; + uint16_t in_negotiate_context_count = 0; + DATA_BLOB in_negotiate_context_blob = data_blob_null; + size_t ofs; + + in_negotiate_context_offset = IVAL(inbody, 0x1C); + in_negotiate_context_count = SVAL(inbody, 0x20); + + ofs = SMB2_HDR_BODY; + ofs += SMBD_SMB2_IN_BODY_LEN(req); + ofs += expected_dyn_size; + if ((ofs % 8) != 0) { + ofs += 8 - (ofs % 8); + } + + if (in_negotiate_context_offset != ofs) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + ofs -= SMB2_HDR_BODY; + ofs -= SMBD_SMB2_IN_BODY_LEN(req); + + if (SMBD_SMB2_IN_DYN_LEN(req) < ofs) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + in_negotiate_context_blob = data_blob_const(indyn, + SMBD_SMB2_IN_DYN_LEN(req)); + + in_negotiate_context_blob.data += ofs; + in_negotiate_context_blob.length -= ofs; + + status = smb2_negotiate_context_parse(req, + in_negotiate_context_blob, &in_c); + if (!NT_STATUS_IS_OK(status)) { + return smbd_smb2_request_error(req, status); + } + + if (in_negotiate_context_count != in_c.num_contexts) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + } + if (get_remote_arch() != RA_SAMBA) { set_remote_arch(RA_VISTA); } @@ -211,6 +266,14 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) reload_services(req->sconn, conn_snum_used, true); DEBUG(3,("Selected protocol %s\n", remote_proto)); + in_preauth = smb2_negotiate_context_find(&in_c, + SMB2_PREAUTH_INTEGRITY_CAPABILITIES); + if (protocol >= PROTOCOL_SMB3_10 && in_preauth == NULL) { + return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER); + } + in_cipher = smb2_negotiate_context_find(&in_c, + SMB2_ENCRYPTION_CAPABILITIES); + /* negprot_spnego() returns a the server guid in the first 16 bytes */ negprot_spnego_blob = negprot_spnego(req, xconn); if (negprot_spnego_blob.data == NULL) { @@ -285,6 +348,131 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) max_read = MIN(max_limit, lp_smb2_max_read()); max_write = MIN(max_limit, lp_smb2_max_write()); + if (in_preauth != NULL) { + size_t needed = 4; + uint16_t hash_count; + uint16_t salt_length; + uint16_t selected_preauth = 0; + const uint8_t *p; + uint8_t buf[38]; + DATA_BLOB b; + size_t i; + + if (in_preauth->data.length < needed) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + hash_count = SVAL(in_preauth->data.data, 0); + salt_length = SVAL(in_preauth->data.data, 2); + + if (hash_count == 0) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + p = in_preauth->data.data + needed; + needed += hash_count * 2; + needed += salt_length; + + if (in_preauth->data.length < needed) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + for (i=0; i < hash_count; i++) { + uint16_t v; + + v = SVAL(p, 0); + p += 2; + + if (v == SMB2_PREAUTH_INTEGRITY_SHA512) { + selected_preauth = v; + break; + } + } + + if (selected_preauth == 0) { + return smbd_smb2_request_error(req, + NT_STATUS_SMB_NO_PREAUTH_INTEGRITY_HASH_OVERLAP); + } + + SSVAL(buf, 0, 1); /* HashAlgorithmCount */ + SSVAL(buf, 2, 32); /* SaltLength */ + SSVAL(buf, 4, selected_preauth); + generate_random_buffer(buf + 6, 32); + + b = data_blob_const(buf, sizeof(buf)); + status = smb2_negotiate_context_add(req, &out_c, + SMB2_PREAUTH_INTEGRITY_CAPABILITIES, b); + if (!NT_STATUS_IS_OK(status)) { + return smbd_smb2_request_error(req, status); + } + + req->preauth = &req->xconn->smb2.preauth; + } + + if (!(capabilities & SMB2_CAP_ENCRYPTION)) { + in_cipher = NULL; + } + + if (in_cipher != NULL) { + size_t needed = 2; + uint16_t cipher_count; + const uint8_t *p; + uint8_t buf[4]; + DATA_BLOB b; + size_t i; + + capabilities &= ~SMB2_CAP_ENCRYPTION; + + if (in_cipher->data.length < needed) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + cipher_count = SVAL(in_cipher->data.data, 0); + + if (cipher_count == 0) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + p = in_cipher->data.data + needed; + needed += cipher_count * 2; + + if (in_cipher->data.length < needed) { + return smbd_smb2_request_error(req, + NT_STATUS_INVALID_PARAMETER); + } + + for (i=0; i < cipher_count; i++) { + uint16_t v; + + v = SVAL(p, 0); + p += 2; + + if (v == SMB2_ENCRYPTION_AES128_GCM) { + xconn->smb2.server.cipher = v; + break; + } + if (v == SMB2_ENCRYPTION_AES128_CCM) { + xconn->smb2.server.cipher = v; + break; + } + } + + SSVAL(buf, 0, 1); /* ChiperCount */ + SSVAL(buf, 2, xconn->smb2.server.cipher); + + b = data_blob_const(buf, sizeof(buf)); + status = smb2_negotiate_context_add(req, &out_c, + SMB2_ENCRYPTION_CAPABILITIES, b); + if (!NT_STATUS_IS_OK(status)) { + return smbd_smb2_request_error(req, status); + } + } + if (capabilities & SMB2_CAP_ENCRYPTION) { xconn->smb2.server.cipher = SMB2_ENCRYPTION_AES128_CCM; } @@ -300,6 +488,53 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) security_buffer = data_blob_const(NULL, 0); #endif + if (out_c.num_contexts != 0) { + status = smb2_negotiate_context_push(req, + &out_negotiate_context_blob, + out_c); + if (!NT_STATUS_IS_OK(status)) { + return smbd_smb2_request_error(req, status); + } + } + + if (out_negotiate_context_blob.length != 0) { + static const uint8_t zeros[8]; + size_t pad = 0; + size_t ofs; + bool ok; + + outdyn = data_blob_dup_talloc(req, security_buffer); + if (outdyn.length != security_buffer.length) { + return smbd_smb2_request_error(req, + NT_STATUS_NO_MEMORY); + } + + ofs = security_offset + security_buffer.length; + if ((ofs % 8) != 0) { + pad = 8 - (ofs % 8); + } + ofs += pad; + + ok = data_blob_append(req, &outdyn, zeros, pad); + if (!ok) { + return smbd_smb2_request_error(req, + NT_STATUS_NO_MEMORY); + } + + ok = data_blob_append(req, &outdyn, + out_negotiate_context_blob.data, + out_negotiate_context_blob.length); + if (!ok) { + return smbd_smb2_request_error(req, + NT_STATUS_NO_MEMORY); + } + + out_negotiate_context_offset = ofs; + out_negotiate_context_count = out_c.num_contexts; + } else { + outdyn = security_buffer; + } + out_guid_blob = data_blob_const(negprot_spnego_blob.data, 16); status = GUID_from_ndr_blob(&out_guid_blob, &out_guid); if (!NT_STATUS_IS_OK(status)) { @@ -315,7 +550,8 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) SSVAL(outbody.data, 0x02, security_mode); /* security mode */ SSVAL(outbody.data, 0x04, dialect); /* dialect revision */ - SSVAL(outbody.data, 0x06, 0); /* reserved */ + SSVAL(outbody.data, 0x06, + out_negotiate_context_count); /* reserved/NegotiateContextCount */ memcpy(outbody.data + 0x08, out_guid_blob.data, 16); /* server guid */ SIVAL(outbody.data, 0x18, @@ -329,9 +565,8 @@ NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req) security_offset); /* security buffer offset */ SSVAL(outbody.data, 0x3A, security_buffer.length); /* security buffer length */ - SIVAL(outbody.data, 0x3C, 0); /* reserved */ - - outdyn = security_buffer; + SIVAL(outbody.data, 0x3C, + out_negotiate_context_offset); /* reserved/NegotiateContextOffset */ req->sconn->using_smb2 = true; diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c index 9658534a062..9e5eff7cb60 100644 --- a/source3/smbd/smb2_server.c +++ b/source3/smbd/smb2_server.c @@ -30,6 +30,7 @@ #include "../librpc/gen_ndr/krb5pac.h" #include "lib/util/iov_buf.h" #include "auth.h" +#include "lib/crypto/sha512.h" static void smbd_smb2_connection_handler(struct tevent_context *ev, struct tevent_fd *fde, @@ -2523,6 +2524,33 @@ static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req) data_blob_clear_free(&req->first_key); } + if (req->preauth != NULL) { + struct hc_sha512state sctx; + int i; + + samba_SHA512_Init(&sctx); + samba_SHA512_Update(&sctx, req->preauth->sha512_value, + sizeof(req->preauth->sha512_value)); + for (i = 1; i < req->in.vector_count; i++) { + samba_SHA512_Update(&sctx, + req->in.vector[i].iov_base, + req->in.vector[i].iov_len); + } + samba_SHA512_Final(req->preauth->sha512_value, &sctx); + + samba_SHA512_Init(&sctx); + samba_SHA512_Update(&sctx, req->preauth->sha512_value, + sizeof(req->preauth->sha512_value)); + for (i = 1; i < req->out.vector_count; i++) { + samba_SHA512_Update(&sctx, + req->out.vector[i].iov_base, + req->out.vector[i].iov_len); + } + samba_SHA512_Final(req->preauth->sha512_value, &sctx); + + req->preauth = NULL; + } + /* I am a sick, sick man... :-). Sendfile hack ... JRA. */ if (req->out.vector_count < (2*SMBD_SMB2_NUM_IOV_PER_REQ) && outdyn->iov_base == NULL && outdyn->iov_len != 0) { -- 2.11.4.GIT