s3:smb2_negprot.c: add support SMB 3.1 negotiate contexts
authorMichael Adam <obnox@samba.org>
Wed, 8 Oct 2014 17:25:15 +0000 (19:25 +0200)
committerMichael Adam <obnox@samba.org>
Fri, 8 May 2015 11:00:28 +0000 (13:00 +0200)
Used for:
- preauthentication validation
- negotiation of ciphers for sigingn and encryprtion

Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Michael Adam <obnox@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
source3/smbd/globals.h
source3/smbd/smb2_negprot.c
source3/smbd/smb2_server.c

index 22cf5d608904b4e3258dbc607b2cf60eadbe7b61..3ddafafa49ba1797dcd6f63ffa6b51c526c0fe83 100644 (file)
@@ -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;
 
index ae2f3f7d53ec5c69e10082b7103e96f26bb40631..199dc147ab630aa1fac49c54c97fecd8417cedac 100644 (file)
@@ -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;
 
index 9658534a06240044c0fe0bdbac56bcb718b311a2..9e5eff7cb6007e16dadb29954ec30da95ecc05de 100644 (file)
@@ -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) {