summaryrefslogtreecommitdiffstats
path: root/libcli/smb/smb_signing.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libcli/smb/smb_signing.c552
1 files changed, 552 insertions, 0 deletions
diff --git a/libcli/smb/smb_signing.c b/libcli/smb/smb_signing.c
new file mode 100644
index 0000000..d50c963
--- /dev/null
+++ b/libcli/smb/smb_signing.c
@@ -0,0 +1,552 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 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 "smb_common.h"
+#include "smb_signing.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+/* Used by the SMB1 signing functions. */
+
+struct smb1_signing_state {
+ /* is signing locally allowed */
+ bool allowed;
+
+ /* is signing locally desired */
+ bool desired;
+
+ /* is signing locally mandatory */
+ bool mandatory;
+
+ /* is signing negotiated by the peer */
+ bool negotiated;
+
+ bool active; /* Have I ever seen a validly signed packet? */
+
+ /* mac_key.length > 0 means signing is started */
+ DATA_BLOB mac_key;
+
+ /* the next expected seqnum */
+ uint32_t seqnum;
+
+ TALLOC_CTX *mem_ctx;
+ void *(*alloc_fn)(TALLOC_CTX *mem_ctx, size_t len);
+ void (*free_fn)(TALLOC_CTX *mem_ctx, void *ptr);
+};
+
+static void smb1_signing_reset_info(struct smb1_signing_state *si)
+{
+ si->active = false;
+ si->seqnum = 0;
+
+ if (si->free_fn) {
+ si->free_fn(si->mem_ctx, si->mac_key.data);
+ } else {
+ talloc_free(si->mac_key.data);
+ }
+ si->mac_key.data = NULL;
+ si->mac_key.length = 0;
+}
+
+struct smb1_signing_state *smb1_signing_init_ex(TALLOC_CTX *mem_ctx,
+ bool allowed,
+ bool desired,
+ bool mandatory,
+ void *(*alloc_fn)(TALLOC_CTX *, size_t),
+ void (*free_fn)(TALLOC_CTX *, void *))
+{
+ struct smb1_signing_state *si;
+
+ if (alloc_fn) {
+ void *p = alloc_fn(mem_ctx, sizeof(struct smb1_signing_state));
+ if (p == NULL) {
+ return NULL;
+ }
+ memset(p, 0, sizeof(struct smb1_signing_state));
+ si = (struct smb1_signing_state *)p;
+ si->mem_ctx = mem_ctx;
+ si->alloc_fn = alloc_fn;
+ si->free_fn = free_fn;
+ } else {
+ si = talloc_zero(mem_ctx, struct smb1_signing_state);
+ if (si == NULL) {
+ return NULL;
+ }
+ }
+
+ if (mandatory) {
+ desired = true;
+ }
+
+ if (desired) {
+ allowed = true;
+ }
+
+ si->allowed = allowed;
+ si->desired = desired;
+ si->mandatory = mandatory;
+
+ return si;
+}
+
+struct smb1_signing_state *smb1_signing_init(TALLOC_CTX *mem_ctx,
+ bool allowed,
+ bool desired,
+ bool mandatory)
+{
+ return smb1_signing_init_ex(mem_ctx, allowed, desired, mandatory,
+ NULL, NULL);
+}
+
+static bool smb1_signing_good(struct smb1_signing_state *si,
+ bool good, uint32_t seq)
+{
+ if (good) {
+ if (!si->active) {
+ si->active = true;
+ }
+ return true;
+ }
+
+ if (!si->mandatory && !si->active) {
+ /* Non-mandatory signing - just turn off if this is the first bad packet.. */
+ DBG_INFO("signing negotiated but not required and peer\n"
+ "isn't sending correct signatures. Turning off.\n");
+ smb1_signing_reset_info(si);
+ return true;
+ }
+
+ /* Mandatory signing or bad packet after signing started - fail and disconnect. */
+ DBG_ERR("BAD SIG: seq %u\n", (unsigned int)seq);
+ return false;
+}
+
+static NTSTATUS smb1_signing_md5(const DATA_BLOB *mac_key,
+ const uint8_t *hdr, size_t len,
+ uint32_t seq_number,
+ uint8_t calc_md5_mac[16])
+{
+ const size_t offset_end_of_sig = (HDR_SS_FIELD + 8);
+ uint8_t sequence_buf[8];
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ /*
+ * Firstly put the sequence number into the first 4 bytes.
+ * and zero out the next 4 bytes.
+ *
+ * We do this here, to avoid modifying the packet.
+ */
+
+ DBG_DEBUG("sequence number %u\n", seq_number );
+
+ SIVAL(sequence_buf, 0, seq_number);
+ SIVAL(sequence_buf, 4, 0);
+
+ /*
+ * Calculate the 16 byte MAC - but don't alter the data in the
+ * incoming packet.
+ *
+ * This makes for a bit of fussing about, but it's not too bad.
+ */
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ /* Initialise with the key. */
+ rc = gnutls_hash(hash_hnd, mac_key->data, mac_key->length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ /* Copy in the first bit of the SMB header. */
+ rc = gnutls_hash(hash_hnd, hdr, HDR_SS_FIELD);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ /* Copy in the sequence number, instead of the signature. */
+ rc = gnutls_hash(hash_hnd, sequence_buf, sizeof(sequence_buf));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ /* Copy in the rest of the packet in, skipping the signature. */
+ rc = gnutls_hash(hash_hnd, hdr + offset_end_of_sig, len - offset_end_of_sig);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+
+ gnutls_hash_deinit(hash_hnd, calc_md5_mac);
+
+ return NT_STATUS_OK;
+}
+
+uint32_t smb1_signing_next_seqnum(struct smb1_signing_state *si, bool oneway)
+{
+ uint32_t seqnum;
+
+ if (si->mac_key.length == 0) {
+ return 0;
+ }
+
+ seqnum = si->seqnum;
+ if (oneway) {
+ si->seqnum += 1;
+ } else {
+ si->seqnum += 2;
+ }
+
+ return seqnum;
+}
+
+void smb1_signing_cancel_reply(struct smb1_signing_state *si, bool oneway)
+{
+ if (si->mac_key.length == 0) {
+ return;
+ }
+
+ if (oneway) {
+ si->seqnum -= 1;
+ } else {
+ si->seqnum -= 2;
+ }
+}
+
+NTSTATUS smb1_signing_sign_pdu(struct smb1_signing_state *si,
+ uint8_t *outhdr, size_t len,
+ uint32_t seqnum)
+{
+ uint8_t calc_md5_mac[16];
+ uint8_t com;
+ uint8_t flags;
+
+ if (si->mac_key.length == 0) {
+ if (!si->negotiated) {
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* JRA Paranioa test - we should be able to get rid of this... */
+ if (len < (HDR_SS_FIELD + 8)) {
+ DBG_WARNING("Logic error. "
+ "Can't check signature on short packet! smb_len = %u\n",
+ (unsigned)len);
+ abort();
+ }
+
+ com = SVAL(outhdr, HDR_COM);
+ flags = SVAL(outhdr, HDR_FLG);
+
+ if (!(flags & FLAG_REPLY)) {
+ uint16_t flags2 = SVAL(outhdr, HDR_FLG2);
+ /*
+ * If this is a request, specify what is
+ * supported or required by the client
+ */
+ if (si->negotiated && si->desired) {
+ flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES;
+ }
+ if (si->negotiated && si->mandatory) {
+ flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED;
+ }
+ SSVAL(outhdr, HDR_FLG2, flags2);
+ }
+
+ if (si->mac_key.length == 0) {
+ /* I wonder what BSRSPYL stands for - but this is what MS
+ actually sends! */
+ if (com == SMBsesssetupX) {
+ memcpy(calc_md5_mac, "BSRSPYL ", 8);
+ } else {
+ memset(calc_md5_mac, 0, 8);
+ }
+ } else {
+ NTSTATUS status;
+
+ status = smb1_signing_md5(&si->mac_key,
+ outhdr,
+ len,
+ seqnum,
+ calc_md5_mac);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ DBG_DEBUG("sent SMB signature of\n");
+ dump_data(10, calc_md5_mac, 8);
+
+ memcpy(&outhdr[HDR_SS_FIELD], calc_md5_mac, 8);
+
+/* outhdr[HDR_SS_FIELD+2]=0;
+ Uncomment this to test if the remote server actually verifies signatures...*/
+
+ return NT_STATUS_OK;
+}
+
+bool smb1_signing_check_pdu(struct smb1_signing_state *si,
+ const uint8_t *inhdr, size_t len,
+ uint32_t seqnum)
+{
+ bool good;
+ uint8_t calc_md5_mac[16];
+ const uint8_t *reply_sent_mac;
+ NTSTATUS status;
+
+ if (si->mac_key.length == 0) {
+ return true;
+ }
+
+ if (len < (HDR_SS_FIELD + 8)) {
+ DBG_WARNING("Can't check signature "
+ "on short packet! smb_len = %u\n",
+ (unsigned)len);
+ return false;
+ }
+
+ status = smb1_signing_md5(&si->mac_key,
+ inhdr,
+ len,
+ seqnum,
+ calc_md5_mac);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to calculate signing mac: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ reply_sent_mac = &inhdr[HDR_SS_FIELD];
+ good = mem_equal_const_time(reply_sent_mac, calc_md5_mac, 8);
+
+ if (!good) {
+ int i;
+ const int sign_range = 5;
+
+ DBG_INFO("BAD SIG: wanted SMB signature of\n");
+ dump_data(5, calc_md5_mac, 8);
+
+ DBG_INFO("BAD SIG: got SMB signature of\n");
+ dump_data(5, reply_sent_mac, 8);
+
+ for (i = -sign_range; i < sign_range; i++) {
+ smb1_signing_md5(&si->mac_key, inhdr, len,
+ seqnum+i, calc_md5_mac);
+ if (mem_equal_const_time(reply_sent_mac, calc_md5_mac, 8)) {
+ DBG_ERR("out of seq. seq num %u matches. "
+ "We were expecting seq %u\n",
+ (unsigned int)seqnum+i,
+ (unsigned int)seqnum);
+ break;
+ }
+ }
+ } else {
+ DBG_DEBUG("seq %u: got good SMB signature of\n",
+ (unsigned int)seqnum);
+ dump_data(10, reply_sent_mac, 8);
+ }
+
+ return smb1_signing_good(si, good, seqnum);
+}
+
+bool smb1_signing_activate(struct smb1_signing_state *si,
+ const DATA_BLOB user_session_key,
+ const DATA_BLOB response)
+{
+ size_t len;
+ off_t ofs;
+
+ if (!user_session_key.length) {
+ return false;
+ }
+
+ if (!si->negotiated) {
+ return false;
+ }
+
+ if (si->active) {
+ return false;
+ }
+
+ if (si->mac_key.length > 0) {
+ return false;
+ }
+
+ smb1_signing_reset_info(si);
+
+ len = response.length + user_session_key.length;
+ if (si->alloc_fn) {
+ si->mac_key.data = (uint8_t *)si->alloc_fn(si->mem_ctx, len);
+ if (si->mac_key.data == NULL) {
+ return false;
+ }
+ } else {
+ si->mac_key.data = (uint8_t *)talloc_size(si, len);
+ if (si->mac_key.data == NULL) {
+ return false;
+ }
+ }
+ si->mac_key.length = len;
+
+ ofs = 0;
+ memcpy(&si->mac_key.data[ofs], user_session_key.data, user_session_key.length);
+
+ DBG_DEBUG("user_session_key\n");
+ dump_data(10, user_session_key.data, user_session_key.length);
+
+ if (response.length) {
+ ofs = user_session_key.length;
+ memcpy(&si->mac_key.data[ofs], response.data, response.length);
+ DBG_DEBUG("response_data\n");
+ dump_data(10, response.data, response.length);
+ } else {
+ DBG_DEBUG("NULL response_data\n");
+ }
+
+ dump_data_pw("smb1_signing_activate: mac key is:\n",
+ si->mac_key.data, si->mac_key.length);
+
+ /* Initialise the sequence number */
+ si->seqnum = 2;
+
+ return true;
+}
+
+bool smb1_signing_is_active(struct smb1_signing_state *si)
+{
+ return si->active;
+}
+
+bool smb1_signing_is_desired(struct smb1_signing_state *si)
+{
+ return si->desired;
+}
+
+bool smb1_signing_is_mandatory(struct smb1_signing_state *si)
+{
+ return si->mandatory;
+}
+
+bool smb1_signing_set_negotiated(struct smb1_signing_state *si,
+ bool allowed, bool mandatory)
+{
+ if (si->active) {
+ return true;
+ }
+
+ if (mandatory) {
+ allowed = true;
+ }
+
+ if (!si->allowed && mandatory) {
+ return false;
+ }
+
+ if (si->mandatory && !allowed) {
+ return false;
+ }
+
+ if (si->mandatory) {
+ si->negotiated = true;
+ return true;
+ }
+
+ if (mandatory) {
+ si->negotiated = true;
+ return true;
+ }
+
+ if (!si->desired) {
+ si->negotiated = false;
+ return true;
+ }
+
+ if (si->desired && allowed) {
+ si->negotiated = true;
+ return true;
+ }
+
+ si->negotiated = false;
+ return true;
+}
+
+bool smb1_signing_is_negotiated(struct smb1_signing_state *si)
+{
+ return si->negotiated;
+}
+
+NTSTATUS smb1_key_derivation(const uint8_t *KI,
+ size_t KI_len,
+ uint8_t KO[16])
+{
+ int rc;
+ static const uint8_t SSKeyHash[256] = {
+ 0x53, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79,
+ 0x20, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75,
+ 0x72, 0x65, 0x20, 0x4b, 0x65, 0x79, 0x20, 0x55,
+ 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x79, 0x07,
+ 0x6e, 0x28, 0x2e, 0x69, 0x88, 0x10, 0xb3, 0xdb,
+ 0x01, 0x55, 0x72, 0xfb, 0x74, 0x14, 0xfb, 0xc4,
+ 0xc5, 0xaf, 0x3b, 0x41, 0x65, 0x32, 0x17, 0xba,
+ 0xa3, 0x29, 0x08, 0xc1, 0xde, 0x16, 0x61, 0x7e,
+ 0x66, 0x98, 0xa4, 0x0b, 0xfe, 0x06, 0x83, 0x53,
+ 0x4d, 0x05, 0xdf, 0x6d, 0xa7, 0x51, 0x10, 0x73,
+ 0xc5, 0x50, 0xdc, 0x5e, 0xf8, 0x21, 0x46, 0xaa,
+ 0x96, 0x14, 0x33, 0xd7, 0x52, 0xeb, 0xaf, 0x1f,
+ 0xbf, 0x36, 0x6c, 0xfc, 0xb7, 0x1d, 0x21, 0x19,
+ 0x81, 0xd0, 0x6b, 0xfa, 0x77, 0xad, 0xbe, 0x18,
+ 0x78, 0xcf, 0x10, 0xbd, 0xd8, 0x78, 0xf7, 0xd3,
+ 0xc6, 0xdf, 0x43, 0x32, 0x19, 0xd3, 0x9b, 0xa8,
+ 0x4d, 0x9e, 0xaa, 0x41, 0xaf, 0xcb, 0xc6, 0xb9,
+ 0x34, 0xe7, 0x48, 0x25, 0xd4, 0x88, 0xc4, 0x51,
+ 0x60, 0x38, 0xd9, 0x62, 0xe8, 0x8d, 0x5b, 0x83,
+ 0x92, 0x7f, 0xb5, 0x0e, 0x1c, 0x2d, 0x06, 0x91,
+ 0xc3, 0x75, 0xb3, 0xcc, 0xf8, 0xf7, 0x92, 0x91,
+ 0x0b, 0x3d, 0xa1, 0x10, 0x5b, 0xd5, 0x0f, 0xa8,
+ 0x3f, 0x5d, 0x13, 0x83, 0x0a, 0x6b, 0x72, 0x93,
+ 0x14, 0x59, 0xd5, 0xab, 0xde, 0x26, 0x15, 0x6d,
+ 0x60, 0x67, 0x71, 0x06, 0x6e, 0x3d, 0x0d, 0xa7,
+ 0xcb, 0x70, 0xe9, 0x08, 0x5c, 0x99, 0xfa, 0x0a,
+ 0x5f, 0x3d, 0x44, 0xa3, 0x8b, 0xc0, 0x8d, 0xda,
+ 0xe2, 0x68, 0xd0, 0x0d, 0xcd, 0x7f, 0x3d, 0xf8,
+ 0x73, 0x7e, 0x35, 0x7f, 0x07, 0x02, 0x0a, 0xb5,
+ 0xe9, 0xb7, 0x87, 0xfb, 0xa1, 0xbf, 0xcb, 0x32,
+ 0x31, 0x66, 0x09, 0x48, 0x88, 0xcc, 0x18, 0xa3,
+ 0xb2, 0x1f, 0x1f, 0x1b, 0x90, 0x4e, 0xd7, 0xe1
+ };
+
+ /* The callers passing down KI_len of 16 so no need to limit to 64 */
+ rc = gnutls_hmac_fast(GNUTLS_MAC_MD5,
+ KI,
+ KI_len,
+ SSKeyHash,
+ sizeof(SSKeyHash),
+ KO);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED);
+ }
+
+ return NT_STATUS_OK;
+}