Make SMB signing work with Windows 2008 and kerberos.
[kai/samba.git] / source / libcli / raw / smb_signing.c
index c82946ac53d1a947b69e2fc6dec1c6e54039289c..1d03686d9ab67e8fb033cb6dc33913f9e8fa7f7f 100644 (file)
@@ -7,7 +7,7 @@
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
+   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,
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #include "includes.h"
-
-struct smb_basic_signing_context {
-       DATA_BLOB mac_key;
-       uint32_t next_seq_num;
-};
+#include "smb.h"
+#include "libcli/raw/libcliraw.h"
+#include "libcli/raw/raw_proto.h"
+#include "lib/crypto/crypto.h"
+#include "param/param.h"
 
 /***********************************************************
  SMB signing - Common code before we set a new signing implementation
 ************************************************************/
-static BOOL set_smb_signing_common(struct cli_transport *transport)
+bool set_smb_signing_common(struct smb_signing_context *sign_info)
 {
-       if (!(transport->negotiate.sec_mode & 
-             (NEGOTIATE_SECURITY_SIGNATURES_REQUIRED|NEGOTIATE_SECURITY_SIGNATURES_ENABLED))) {
-               return False;
+       if (sign_info->doing_signing) {
+               DEBUG(5, ("SMB Signing already in progress, so we don't start it again\n"));
+               return false;
        }
 
-       if (transport->negotiate.sign_info.doing_signing) {
-               return False;
+       if (!sign_info->allow_smb_signing) {
+               DEBUG(5, ("SMB Signing has been locally disabled\n"));
+               return false;
        }
-       
-       if (transport->negotiate.sign_info.free_signing_context)
-               transport->negotiate.sign_info.free_signing_context(transport);
 
-       /* These calls are INCOMPATIBLE with SMB signing */
-       transport->negotiate.readbraw_supported = False;
-       transport->negotiate.writebraw_supported = False;
-       
-       return True;
+       return true;
 }
 
 /***********************************************************
- SMB signing - Common code for 'real' implementations
+ SMB signing - Common code before we set a new signing implementation
 ************************************************************/
-static BOOL set_smb_signing_real_common(struct cli_transport *transport) 
+static bool smbcli_set_smb_signing_common(struct smbcli_transport *transport)
 {
-       if (transport->negotiate.sec_mode & NEGOTIATE_SECURITY_SIGNATURES_REQUIRED) {
-               DEBUG(5, ("Mandatory SMB signing enabled!\n"));
-               transport->negotiate.sign_info.doing_signing = True;
+       if (!set_smb_signing_common(&transport->negotiate.sign_info)) {
+               return false;
        }
 
-       DEBUG(5, ("SMB signing enabled!\n"));
+       if (!(transport->negotiate.sec_mode & 
+             (NEGOTIATE_SECURITY_SIGNATURES_REQUIRED|NEGOTIATE_SECURITY_SIGNATURES_ENABLED))) {
+               DEBUG(5, ("SMB Signing is not negotiated by the peer\n"));
+               return false;
+       }
 
-       return True;
+       /* These calls are INCOMPATIBLE with SMB signing */
+       transport->negotiate.readbraw_supported = false;
+       transport->negotiate.writebraw_supported = false;
+
+       return true;
 }
 
-static void mark_packet_signed(struct cli_request *req
+void mark_packet_signed(struct smb_request_buffer *out
 {
        uint16_t flags2;
-       flags2 = SVAL(req->out.hdr, HDR_FLG2);
+       flags2 = SVAL(out->hdr, HDR_FLG2);
        flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES;
-       SSVAL(req->out.hdr, HDR_FLG2, flags2);
+       SSVAL(out->hdr, HDR_FLG2, flags2);
 }
 
-static BOOL signing_good(struct cli_request *req, BOOL good) 
+bool signing_good(struct smb_signing_context *sign_info, 
+                        unsigned int seq, bool good) 
 {
-       if (good && !req->transport->negotiate.sign_info.doing_signing) {
-               req->transport->negotiate.sign_info.doing_signing = True;
-       }
-
-       if (!good) {
-               if (req->transport->negotiate.sign_info.doing_signing) {
-                       DEBUG(1, ("SMB signature check failed!\n"));
-                       return False;
+       if (good) {
+               if (!sign_info->doing_signing) {
+                       DEBUG(5, ("Seen valid packet, so turning signing on\n"));
+                       sign_info->doing_signing = true;
+               }
+               if (!sign_info->seen_valid) {
+                       DEBUG(5, ("Seen valid packet, so marking signing as 'seen valid'\n"));
+                       sign_info->seen_valid = true;
+               }
+       } else {
+               if (!sign_info->seen_valid) {
+                       /* If we have never seen a good packet, just turn it off */
+                       DEBUG(5, ("signing_good: signing negotiated but not required and peer\n"
+                                 "isn't sending correct signatures. Turning off.\n"));
+                       smbcli_set_signing_off(sign_info);
+                       return true;
                } else {
-                       DEBUG(3, ("Server did not sign reply correctly\n"));
-                       cli_transport_free_signing_context(req->transport);
-                       return False;
+                       /* bad packet after signing started - fail and disconnect. */
+                       DEBUG(0, ("signing_good: BAD SIG: seq %u\n", seq));
+                       return false;
                }
        }
-       return True;
-}      
+       return true;
+}
 
-/***********************************************************
- SMB signing - Simple implementation - calculate a MAC to send.
-************************************************************/
-static void cli_request_simple_sign_outgoing_message(struct cli_request *req)
+void sign_outgoing_message(struct smb_request_buffer *out, DATA_BLOB *mac_key, unsigned int seq_num) 
 {
        uint8_t calc_md5_mac[16];
        struct MD5Context md5_ctx;
-       struct smb_basic_signing_context *data = req->transport->negotiate.sign_info.signing_context;
-
-#if 0
-       /* enable this when packet signing is preventing you working out why valgrind 
-          says that data is uninitialised */
-       file_save("pkt.dat", req->out.buffer, req->out.size);
-#endif
-
-       req->seq_num = data->next_seq_num;
-       
-       /* some requests (eg. NTcancel) are one way, and the sequence number
-          should be increased by 1 not 2 */
-       if (req->one_way_request) {
-               data->next_seq_num += 1;
-       } else {
-               data->next_seq_num += 2;
-       }
 
        /*
         * Firstly put the sequence number into the first 4 bytes.
         * and zero out the next 4 bytes.
         */
-       SIVAL(req->out.hdr, HDR_SS_FIELD, req->seq_num);
-       SIVAL(req->out.hdr, HDR_SS_FIELD + 4, 0);
+       SIVAL(out->hdr, HDR_SS_FIELD, seq_num);
+       SIVAL(out->hdr, HDR_SS_FIELD + 4, 0);
 
        /* mark the packet as signed - BEFORE we sign it...*/
-       mark_packet_signed(req);
+       mark_packet_signed(out);
 
        /* Calculate the 16 byte MAC and place first 8 bytes into the field. */
        MD5Init(&md5_ctx);
-       MD5Update(&md5_ctx, data->mac_key.data, 
-                 data->mac_key.length); 
+        MD5Update(&md5_ctx, mac_key->data, mac_key->length);
        MD5Update(&md5_ctx, 
-                 req->out.buffer + NBT_HDR_SIZE, 
-                 req->out.size - NBT_HDR_SIZE);
+                 out->buffer + NBT_HDR_SIZE, 
+                 out->size - NBT_HDR_SIZE);
        MD5Final(calc_md5_mac, &md5_ctx);
 
-       memcpy(&req->out.hdr[HDR_SS_FIELD], calc_md5_mac, 8);
+       memcpy(&out->hdr[HDR_SS_FIELD], calc_md5_mac, 8);
 
+       DEBUG(5, ("sign_outgoing_message: SENT SIG (seq: %d): sent SMB signature of\n", 
+                 seq_num));
+       dump_data(5, calc_md5_mac, 8);
 /*     req->out.hdr[HDR_SS_FIELD+2]=0; 
        Uncomment this to test if the remote server actually verifies signitures...*/
 }
 
-
-/***********************************************************
- SMB signing - Simple implementation - check a MAC sent by server.
-************************************************************/
-static BOOL cli_request_simple_check_incoming_message(struct cli_request *req)
+bool check_signed_incoming_message(struct smb_request_buffer *in, DATA_BLOB *mac_key, uint_t seq_num) 
 {
-       BOOL good;
+       bool good;
        uint8_t calc_md5_mac[16];
-       uint8_t server_sent_mac[8];
+       uint8_t *server_sent_mac;
        uint8_t sequence_buf[8];
        struct MD5Context md5_ctx;
-       struct smb_basic_signing_context *data = req->transport->negotiate.sign_info.signing_context;
        const size_t offset_end_of_sig = (HDR_SS_FIELD + 8);
        int i;
        const int sign_range = 0;
 
+       /* room enough for the signature? */
+       if (in->size < NBT_HDR_SIZE + HDR_SS_FIELD + 8) {
+               return false;
+       }
+
+       if (!mac_key->length) {
+               /* NO key yet */
+               return false;
+       }
+
        /* its quite bogus to be guessing sequence numbers, but very useful
           when debugging signing implementations */
-       for (i = 1-sign_range; i <= 1+sign_range; i++) {
+       for (i = 0-sign_range; i <= 0+sign_range; i++) {
                /*
                 * Firstly put the sequence number into the first 4 bytes.
                 * and zero out the next 4 bytes.
                 */
-               SIVAL(sequence_buf, 0, req->seq_num+i);
+               SIVAL(sequence_buf, 0, seq_num + i);
                SIVAL(sequence_buf, 4, 0);
                
                /* get a copy of the server-sent mac */
-               memcpy(server_sent_mac, &req->in.hdr[HDR_SS_FIELD], sizeof(server_sent_mac));
+               server_sent_mac = &in->hdr[HDR_SS_FIELD];
                
                /* Calculate the 16 byte MAC and place first 8 bytes into the field. */
                MD5Init(&md5_ctx);
-               MD5Update(&md5_ctx, data->mac_key.data, 
-                         data->mac_key.length); 
-               MD5Update(&md5_ctx, req->in.hdr, HDR_SS_FIELD);
+               MD5Update(&md5_ctx, mac_key->data, 
+                         mac_key->length); 
+               MD5Update(&md5_ctx, in->hdr, HDR_SS_FIELD);
                MD5Update(&md5_ctx, sequence_buf, sizeof(sequence_buf));
                
-               MD5Update(&md5_ctx, req->in.hdr + offset_end_of_sig, 
-                         req->in.size - NBT_HDR_SIZE - (offset_end_of_sig));
+               MD5Update(&md5_ctx, in->hdr + offset_end_of_sig, 
+                         in->size - NBT_HDR_SIZE - (offset_end_of_sig));
                MD5Final(calc_md5_mac, &md5_ctx);
                
                good = (memcmp(server_sent_mac, calc_md5_mac, 8) == 0);
+
+               if (i == 0) {
+                       if (!good) {
+                               DEBUG(5, ("check_signed_incoming_message: BAD SIG (seq: %d): wanted SMB signature of\n", seq_num + i));
+                               dump_data(5, calc_md5_mac, 8);
+                               
+                               DEBUG(5, ("check_signed_incoming_message: BAD SIG (seq: %d): got SMB signature of\n", seq_num + i));
+                               dump_data(5, server_sent_mac, 8);
+                       } else {
+                               DEBUG(15, ("check_signed_incoming_message: GOOD SIG (seq: %d): got SMB signature of\n", seq_num + i));
+                               dump_data(5, server_sent_mac, 8);
+                       }
+               }
+
                if (good) break;
        }
 
-       if (good && i != 1) {
-               DEBUG(0,("SIGNING OFFSET %d\n", i));
+       if (good && i != 0) {
+               DEBUG(0,("SIGNING OFFSET %d (should be %d)\n", i, seq_num));
        }
 
-       if (!good) {
-               DEBUG(5, ("cli_request_simple_check_incoming_message: BAD SIG: wanted SMB signature of\n"));
-               dump_data(5, calc_md5_mac, 8);
-               
-               DEBUG(5, ("cli_request_simple_check_incoming_message: BAD SIG: got SMB signature of\n"));
-               dump_data(5, server_sent_mac, 8);
-       }
-       return signing_good(req, good);
+       return good;
 }
 
+static void smbcli_req_allocate_seq_num(struct smbcli_request *req) 
+{
+       req->seq_num = req->transport->negotiate.sign_info.next_seq_num;
+       
+       /* some requests (eg. NTcancel) are one way, and the sequence number
+          should be increased by 1 not 2 */
+       if (req->sign_single_increment) {
+               req->transport->negotiate.sign_info.next_seq_num += 1;
+       } else {
+               req->transport->negotiate.sign_info.next_seq_num += 2;
+       }
+}
 
 /***********************************************************
- SMB signing - Simple implementation - free signing context
+ SMB signing - Simple implementation - calculate a MAC to send.
 ************************************************************/
-static void cli_transport_simple_free_signing_context(struct cli_transport *transport)
+void smbcli_request_calculate_sign_mac(struct smbcli_request *req)
 {
-       struct smb_basic_signing_context *data = transport->negotiate.sign_info.signing_context;
+#if 0
+       /* enable this when packet signing is preventing you working out why valgrind 
+          says that data is uninitialised */
+       file_save("pkt.dat", req->out.buffer, req->out.size);
+#endif
 
-       data_blob_free(&data->mac_key);
-       SAFE_FREE(transport->negotiate.sign_info.signing_context);
+       switch (req->transport->negotiate.sign_info.signing_state) {
+       case SMB_SIGNING_ENGINE_OFF:
+               break;
 
+       case SMB_SIGNING_ENGINE_BSRSPYL:
+               /* mark the packet as signed - BEFORE we sign it...*/
+               mark_packet_signed(&req->out);
+               
+               /* I wonder what BSRSPYL stands for - but this is what MS 
+                  actually sends! */
+               memcpy((req->out.hdr + HDR_SS_FIELD), "BSRSPYL ", 8);
+               break;
+
+       case SMB_SIGNING_ENGINE_ON:
+                       
+               smbcli_req_allocate_seq_num(req);
+               sign_outgoing_message(&req->out, 
+                                     &req->transport->negotiate.sign_info.mac_key, 
+                                     req->seq_num);
+               break;
+       }
        return;
 }
 
 
-/***********************************************************
- SMB signing - Simple implementation - setup the MAC key.
-************************************************************/
-BOOL cli_transport_simple_set_signing(struct cli_transport *transport,
-                                     const DATA_BLOB user_session_key, 
-                                     const DATA_BLOB response)
-{
-       struct smb_basic_signing_context *data;
-
-       if (!set_smb_signing_common(transport)) {
-               return False;
-       }
-
-       if (!set_smb_signing_real_common(transport)) {
-               return False;
-       }
+/**
+ SMB signing - NULL implementation
 
-       data = smb_xmalloc(sizeof(*data));
-       transport->negotiate.sign_info.signing_context = data;
-       
-       data->mac_key = data_blob(NULL, response.length + user_session_key.length);
+ @note Used as an initialisation only - it will not correctly
+       shut down a real signing mechanism
+*/
+bool smbcli_set_signing_off(struct smb_signing_context *sign_info)
+{
+       DEBUG(5, ("Shutdown SMB signing\n"));
+       sign_info->doing_signing = false;
+       data_blob_free(&sign_info->mac_key);
+       sign_info->signing_state = SMB_SIGNING_ENGINE_OFF;
+       return true;
+}
 
-       memcpy(&data->mac_key.data[0], user_session_key.data, user_session_key.length);
+/**
+ SMB signing - TEMP implementation - setup the MAC key.
 
-       if (response.length) {
-               memcpy(&data->mac_key.data[user_session_key.length],response.data, response.length);
+*/
+bool smbcli_temp_set_signing(struct smbcli_transport *transport)
+{
+       if (!smbcli_set_smb_signing_common(transport)) {
+               return false;
        }
+       DEBUG(5, ("BSRSPYL SMB signing enabled\n"));
+       smbcli_set_signing_off(&transport->negotiate.sign_info);
 
-       /* Initialise the sequence number */
-       data->next_seq_num = 0;
-
-       transport->negotiate.sign_info.sign_outgoing_message = cli_request_simple_sign_outgoing_message;
-       transport->negotiate.sign_info.check_incoming_message = cli_request_simple_check_incoming_message;
-       transport->negotiate.sign_info.free_signing_context = cli_transport_simple_free_signing_context;
+       transport->negotiate.sign_info.mac_key = data_blob(NULL, 0);
+       transport->negotiate.sign_info.signing_state = SMB_SIGNING_ENGINE_BSRSPYL;
 
-       return True;
+       return true;
 }
 
-
 /***********************************************************
- SMB signing - NULL implementation - calculate a MAC to send.
+ SMB signing - Simple implementation - check a MAC sent by server.
 ************************************************************/
-static void cli_request_null_sign_outgoing_message(struct cli_request *req)
+/**
+ * Check a packet supplied by the server.
+ * @return false if we had an established signing connection
+ *         which had a back checksum, true otherwise
+ */
+bool smbcli_request_check_sign_mac(struct smbcli_request *req) 
 {
-       /* we can't zero out the sig, as we might be trying to send a
-          transport request - which is NBT-level, not SMB level and doesn't
-          have the field */
+       bool good;
+
+       switch (req->transport->negotiate.sign_info.signing_state) 
+       {
+       case SMB_SIGNING_ENGINE_OFF:
+               return true;
+       case SMB_SIGNING_ENGINE_BSRSPYL:
+       case SMB_SIGNING_ENGINE_ON:
+       {                       
+               if (req->in.size < (HDR_SS_FIELD + 8)) {
+                       return false;
+               } else {
+                       good = check_signed_incoming_message(&req->in, 
+                                                            &req->transport->negotiate.sign_info.mac_key, 
+                                                            req->seq_num+1);
+                       
+                       return signing_good(&req->transport->negotiate.sign_info, 
+                                           req->seq_num+1, good);
+               }
+       }
+       }
+       return false;
 }
 
 
 /***********************************************************
- SMB signing - NULL implementation - check a MAC sent by server.
+ SMB signing - Simple implementation - setup the MAC key.
 ************************************************************/
-static BOOL cli_request_null_check_incoming_message(struct cli_request *req)
+bool smbcli_simple_set_signing(TALLOC_CTX *mem_ctx,
+                              struct smb_signing_context *sign_info,
+                              const DATA_BLOB *user_session_key, 
+                              const DATA_BLOB *response)
 {
-       return True;
-}
+       if (sign_info->mandatory_signing) {
+               DEBUG(5, ("Mandatory SMB signing enabled!\n"));
+       }
 
+       DEBUG(5, ("SMB signing enabled!\n"));
 
-/***********************************************************
- SMB signing - NULL implementation - free signing context
-************************************************************/
-static void cli_null_free_signing_context(struct cli_transport *transport)
-{
-}
+       if (response && response->length) {
+               sign_info->mac_key = data_blob_talloc(mem_ctx, NULL, response->length + user_session_key->length);
+       } else {
+               sign_info->mac_key = data_blob_talloc(mem_ctx, NULL, user_session_key->length);
+       }
+               
+       memcpy(&sign_info->mac_key.data[0], user_session_key->data, user_session_key->length);
 
-/**
- SMB signing - NULL implementation - setup the MAC key.
+       if (response && response->length) {
+               memcpy(&sign_info->mac_key.data[user_session_key->length],response->data, response->length);
+       }
 
- @note Used as an initialisation only - it will not correctly
-       shut down a real signing mechanism
-*/
-BOOL cli_null_set_signing(struct cli_transport *transport)
-{
-       transport->negotiate.sign_info.signing_context = NULL;
-       
-       transport->negotiate.sign_info.sign_outgoing_message = cli_request_null_sign_outgoing_message;
-       transport->negotiate.sign_info.check_incoming_message = cli_request_null_check_incoming_message;
-       transport->negotiate.sign_info.free_signing_context = cli_null_free_signing_context;
+       dump_data_pw("Started Signing with key:\n", sign_info->mac_key.data, sign_info->mac_key.length);
+
+       sign_info->signing_state = SMB_SIGNING_ENGINE_ON;
 
-       return True;
+       return true;
 }
 
 
-/**
- * Free the signing context
- */
-void cli_transport_free_signing_context(struct cli_transport *transport) 
+/***********************************************************
+ SMB signing - Simple implementation - setup the MAC key.
+************************************************************/
+bool smbcli_transport_simple_set_signing(struct smbcli_transport *transport,
+                                        const DATA_BLOB user_session_key, 
+                                        const DATA_BLOB response)
 {
-       if (transport->negotiate.sign_info.free_signing_context) {
-               transport->negotiate.sign_info.free_signing_context(transport);
+       if (!smbcli_set_smb_signing_common(transport)) {
+               return false;
        }
 
-       cli_null_set_signing(transport);
-}
-
-
-/**
- * Sign a packet with the current mechanism
- */
-void cli_request_calculate_sign_mac(struct cli_request *req)
-{
-       req->transport->negotiate.sign_info.sign_outgoing_message(req);
+       return smbcli_simple_set_signing(transport,
+                                        &transport->negotiate.sign_info,
+                                        &user_session_key,
+                                        &response);
 }
 
 
-/**
- * Check a packet with the current mechanism
- * @return False if we had an established signing connection
- *         which had a back checksum, True otherwise
- */
-BOOL cli_request_check_sign_mac(struct cli_request *req) 
+bool smbcli_init_signing(struct smbcli_transport *transport) 
 {
-       BOOL good;
-
-       if (req->in.size < (HDR_SS_FIELD + 8)) {
-               good = False;
-       } else {
-               good = req->transport->negotiate.sign_info.check_incoming_message(req);
+       transport->negotiate.sign_info.next_seq_num = 0;
+       transport->negotiate.sign_info.mac_key = data_blob(NULL, 0);
+       if (!smbcli_set_signing_off(&transport->negotiate.sign_info)) {
+               return false;
        }
-
-       if (!good && req->transport->negotiate.sign_info.doing_signing) {
-               return False;
+       
+       switch (transport->options.signing) {
+       case SMB_SIGNING_OFF:
+               transport->negotiate.sign_info.allow_smb_signing = false;
+               break;
+       case SMB_SIGNING_SUPPORTED:
+       case SMB_SIGNING_AUTO:
+               transport->negotiate.sign_info.allow_smb_signing = true;
+               break;
+       case SMB_SIGNING_REQUIRED:
+               transport->negotiate.sign_info.allow_smb_signing = true;
+               transport->negotiate.sign_info.mandatory_signing = true;
+               break;
        }
-
-       return True;
+       return true;
 }