From c5e11daa8bb00665efabbf7939062e7e60112ced Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sat, 22 May 2004 11:16:21 +0000 Subject: r818: added server side SMB signing to Samba4 (This used to be commit 8e5ddf5e8eb74f667897f90baa2d00f02ca5818b) --- source4/include/context.h | 12 ++++ source4/include/smb.h | 2 + source4/param/loadparm.c | 26 +++++++ source4/smb_server/config.m4 | 3 +- source4/smb_server/negprot.c | 16 ++++- source4/smb_server/reply.c | 7 +- source4/smb_server/request.c | 19 ++++- source4/smb_server/sesssetup.c | 2 + source4/smb_server/signing.c | 149 ++++++++++++++++++++++++++++++++++++++++ source4/smb_server/smb_server.c | 8 ++- 10 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 source4/smb_server/signing.c diff --git a/source4/include/context.h b/source4/include/context.h index 17664c50f8..e533c6d243 100644 --- a/source4/include/context.h +++ b/source4/include/context.h @@ -114,6 +114,9 @@ struct request_context { /* how far through the chain of SMB commands have we gone? */ unsigned chain_count; + /* the sequence number for signing */ + large_t seq_num; + /* the async structure allows backend functions to delay replying to requests. To use this, the front end must set async.send_fn to a function to be called by the backend @@ -329,6 +332,13 @@ struct timers_context { time_t last_smb_conf_reload; }; + +struct signing_context { + DATA_BLOB mac_key; + large_t next_seq_num; + enum smb_signing_state signing_state; +}; + #include "smbd/process_model.h" /* smb context structure. This should contain all the state @@ -355,6 +365,8 @@ struct server_context { struct dcesrv_context dcesrv; + struct signing_context signing; + /* the pid of the process handling this session */ pid_t pid; diff --git a/source4/include/smb.h b/source4/include/smb.h index 911aa9bd41..0881469af3 100644 --- a/source4/include/smb.h +++ b/source4/include/smb.h @@ -37,6 +37,8 @@ #define True (1) #define Auto (2) +enum smb_signing_state {SMB_SIGNING_OFF, SMB_SIGNING_SUPPORTED, SMB_SIGNING_REQUIRED}; + #ifndef _BOOL typedef int BOOL; #define _BOOL /* So we don't typedef BOOL again in vfs.h */ diff --git a/source4/param/loadparm.c b/source4/param/loadparm.c index 3cd6b0b9ef..f357703013 100644 --- a/source4/param/loadparm.c +++ b/source4/param/loadparm.c @@ -211,6 +211,7 @@ typedef struct BOOL bLanmanAuth; BOOL bNTLMAuth; BOOL bUseSpnego; + BOOL server_signing; BOOL bClientLanManAuth; BOOL bClientNTLMv2Auth; BOOL bHostMSDfs; @@ -487,6 +488,27 @@ static const struct enum_list enum_csc_policy[] = { {-1, NULL} }; +/* SMB signing types. */ +static const struct enum_list enum_smb_signing_vals[] = { + {SMB_SIGNING_OFF, "No"}, + {SMB_SIGNING_OFF, "False"}, + {SMB_SIGNING_OFF, "0"}, + {SMB_SIGNING_OFF, "Off"}, + {SMB_SIGNING_OFF, "disabled"}, + {SMB_SIGNING_SUPPORTED, "Yes"}, + {SMB_SIGNING_SUPPORTED, "True"}, + {SMB_SIGNING_SUPPORTED, "1"}, + {SMB_SIGNING_SUPPORTED, "On"}, + {SMB_SIGNING_SUPPORTED, "enabled"}, + {SMB_SIGNING_SUPPORTED, "auto"}, + {SMB_SIGNING_REQUIRED, "required"}, + {SMB_SIGNING_REQUIRED, "mandatory"}, + {SMB_SIGNING_REQUIRED, "force"}, + {SMB_SIGNING_REQUIRED, "forced"}, + {SMB_SIGNING_REQUIRED, "enforced"}, + {-1, NULL} +}; + /* Do you want session setups at user level security with a invalid password to be rejected or allowed in as guest? WinNT rejects them @@ -631,6 +653,7 @@ static struct parm_struct parm_table[] = { {"time server", P_BOOL, P_GLOBAL, &Globals.bTimeServer, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER}, {"unix extensions", P_BOOL, P_GLOBAL, &Globals.bUnixExtensions, NULL, NULL, FLAG_ADVANCED | FLAG_DEVELOPER}, {"use spnego", P_BOOL, P_GLOBAL, &Globals.bUseSpnego, NULL, NULL, FLAG_DEVELOPER}, + {"server signing", P_ENUM, P_GLOBAL, &Globals.server_signing, NULL, enum_smb_signing_vals, FLAG_ADVANCED}, {"rpc big endian", P_BOOL, P_GLOBAL, &Globals.bRpcBigEndian, NULL, NULL, FLAG_DEVELOPER}, {"Tuning Options", P_SEP, P_SEPARATOR}, @@ -1083,6 +1106,8 @@ static void init_globals(void) Globals.bUseSpnego = True; + Globals.server_signing = False; + string_set(&Globals.smb_ports, SMB_PORTS); } @@ -1352,6 +1377,7 @@ FN_GLOBAL_INTEGER(lp_winbind_cache_time, &Globals.winbind_cache_time) FN_GLOBAL_BOOL(lp_hide_local_users, &Globals.bHideLocalUsers) FN_GLOBAL_INTEGER(lp_algorithmic_rid_base, &Globals.AlgorithmicRidBase) FN_GLOBAL_INTEGER(lp_name_cache_timeout, &Globals.name_cache_timeout) +FN_GLOBAL_INTEGER(lp_server_signing, &Globals.server_signing) /* local prototypes */ diff --git a/source4/smb_server/config.m4 b/source4/smb_server/config.m4 index e94dbf1444..4722faf8bb 100644 --- a/source4/smb_server/config.m4 +++ b/source4/smb_server/config.m4 @@ -13,4 +13,5 @@ SMB_SUBSYSTEM(SMB,smb_server/smb_server.o, smb_server/session.o smb_server/sesssetup.o smb_server/srvtime.o - smb_server/trans2.o]) + smb_server/trans2.o + smb_server/signing.o]) diff --git a/source4/smb_server/negprot.c b/source4/smb_server/negprot.c index d9d65d4cf2..253dc1d7a4 100644 --- a/source4/smb_server/negprot.c +++ b/source4/smb_server/negprot.c @@ -288,6 +288,20 @@ static void reply_nt1(struct request_context *req, uint16 choice) if (req->smb->negotiate.encrypted_passwords) { secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE; } + + req->smb->signing.signing_state = lp_server_signing(); + + switch (req->smb->signing.signing_state) { + case SMB_SIGNING_OFF: + break; + case SMB_SIGNING_SUPPORTED: + secword |= NEGOTIATE_SECURITY_SIGNATURES_ENABLED; + break; + case SMB_SIGNING_REQUIRED: + secword |= NEGOTIATE_SECURITY_SIGNATURES_ENABLED | + NEGOTIATE_SECURITY_SIGNATURES_REQUIRED; + break; + } req->smb->negotiate.protocol = PROTOCOL_NT1; @@ -334,7 +348,7 @@ static void reply_nt1(struct request_context *req, uint16 choice) #endif } - req_send_reply(req); + req_send_reply_nosign(req); } /* these are the protocol lists used for auto architecture detection: diff --git a/source4/smb_server/reply.c b/source4/smb_server/reply.c index 8b9bb4fb5e..073ee956ca 100644 --- a/source4/smb_server/reply.c +++ b/source4/smb_server/reply.c @@ -710,7 +710,8 @@ failed: req->out.size = 4; req->out.buffer = talloc(req->mem_ctx, req->out.size); SIVAL(req->out.buffer, 0, 0); /* init NBT header */ - req_send_reply(req); + + req_send_reply_nosign(req); } @@ -2335,7 +2336,7 @@ void reply_special(struct request_context *req) req->out.buffer = buf; req->out.size = 4; - req_send_reply(req); + req_send_reply_nosign(req); return; case 0x89: /* session keepalive request @@ -2344,7 +2345,7 @@ void reply_special(struct request_context *req) SCVAL(buf, 3, 0); req->out.buffer = buf; req->out.size = 4; - req_send_reply(req); + req_send_reply_nosign(req); return; case SMBkeepalive: diff --git a/source4/smb_server/request.c b/source4/smb_server/request.c index 964f4a2d70..5c1d14a9d0 100644 --- a/source4/smb_server/request.c +++ b/source4/smb_server/request.c @@ -256,7 +256,7 @@ void req_grow_data(struct request_context *req, unsigned new_size) note that this only looks at req->out.buffer and req->out.size, allowing manually constructed packets to be sent */ -void req_send_reply(struct request_context *req) +void req_send_reply_nosign(struct request_context *req) { if (req->out.size > NBT_HDR_SIZE) { _smb_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE); @@ -269,6 +269,23 @@ void req_send_reply(struct request_context *req) req_destroy(req); } +/* + possibly sign a message then send a reply and destroy the request buffer + + note that this only looks at req->out.buffer and req->out.size, allowing manually + constructed packets to be sent +*/ +void req_send_reply(struct request_context *req) +{ + if (req->out.size > NBT_HDR_SIZE) { + _smb_setlen(req->out.buffer, req->out.size - NBT_HDR_SIZE); + } + + req_sign_packet(req); + + req_send_reply_nosign(req); +} + /* diff --git a/source4/smb_server/sesssetup.c b/source4/smb_server/sesssetup.c index f42efcb7ec..fdcc1d298a 100644 --- a/source4/smb_server/sesssetup.c +++ b/source4/smb_server/sesssetup.c @@ -127,6 +127,8 @@ static NTSTATUS sesssetup_nt1(struct request_context *req, union smb_sesssetup * &sess->nt1.out.lanman, &sess->nt1.out.domain); + srv_setup_signing(req->smb, &session_key, &sess->nt1.in.password2); + return NT_STATUS_OK; } diff --git a/source4/smb_server/signing.c b/source4/smb_server/signing.c new file mode 100644 index 0000000000..a3779e17cf --- /dev/null +++ b/source4/smb_server/signing.c @@ -0,0 +1,149 @@ +/* + Unix SMB/CIFS implementation. + + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "includes.h" + +/* + mark the flags2 field in a packet as signed +*/ +static void mark_packet_signed(struct request_context *req) +{ + uint16 flags2; + flags2 = SVAL(req->out.hdr, HDR_FLG2); + flags2 |= FLAGS2_SMB_SECURITY_SIGNATURES; + SSVAL(req->out.hdr, HDR_FLG2, flags2); +} + +/* + calculate the signature for a message +*/ +static void calc_signature(uint8 *buffer, size_t length, + DATA_BLOB *mac_key, uint8 signature[8]) +{ + unsigned char calc_md5_mac[16]; + struct MD5Context md5_ctx; + + MD5Init(&md5_ctx); + MD5Update(&md5_ctx, mac_key->data, mac_key->length); + MD5Update(&md5_ctx, buffer, length); + MD5Final(calc_md5_mac, &md5_ctx); + memcpy(signature, calc_md5_mac, 8); +} + + +/* + sign an outgoing packet +*/ +void req_sign_packet(struct request_context *req) +{ + /* check if we are doing signing on this connection */ + if (req->smb->signing.signing_state != SMB_SIGNING_REQUIRED) { + return; + } + + SBVAL(req->out.hdr, HDR_SS_FIELD, req->seq_num+1); + + mark_packet_signed(req); + + calc_signature(req->out.hdr, req->out.size - NBT_HDR_SIZE, + &req->smb->signing.mac_key, + &req->out.hdr[HDR_SS_FIELD]); +} + + +/* + setup the signing key for a connection. Called after authentication succeeds + in a session setup +*/ +void srv_setup_signing(struct server_context *smb, + DATA_BLOB *session_key, + DATA_BLOB *session_response) +{ + smb->signing.mac_key = data_blob(NULL, + session_key->length + session_response->length); + memcpy(smb->signing.mac_key.data, session_key->data, session_key->length); + if (session_response->length != 0) { + memcpy(&smb->signing.mac_key.data[session_key->length], + session_response->data, + session_response->length); + } +} + + +/* + allocate a sequence number to a request +*/ +static void req_signing_alloc_seq_num(struct request_context *req) +{ + req->seq_num = req->smb->signing.next_seq_num; + + /* TODO: we need to handle one-way requests like NTcancel, which + only increment the sequence number by 1 */ + if (req->smb->signing.signing_state != SMB_SIGNING_OFF) { + req->smb->signing.next_seq_num += 2; + } +} + +/* + check the signature of an incoming packet +*/ +BOOL req_signing_check_incoming(struct request_context *req) +{ + unsigned char client_md5_mac[8], signature[8]; + + switch (req->smb->signing.signing_state) { + case SMB_SIGNING_OFF: + return True; + case SMB_SIGNING_SUPPORTED: + if (req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES) { + req->smb->signing.signing_state = SMB_SIGNING_REQUIRED; + } + return True; + case SMB_SIGNING_REQUIRED: + break; + } + + req_signing_alloc_seq_num(req); + + /* the first packet isn't checked as the key hasn't been established */ + if (req->seq_num == 0) { + return True; + } + + /* room enough for the signature? */ + if (req->in.size < NBT_HDR_SIZE + HDR_SS_FIELD + 8) { + return False; + } + + memcpy(client_md5_mac, req->in.hdr + HDR_SS_FIELD, 8); + + SBVAL(req->in.hdr, HDR_SS_FIELD, req->seq_num); + + calc_signature(req->in.hdr, req->in.size - NBT_HDR_SIZE, + &req->smb->signing.mac_key, + signature); + + if (memcmp(client_md5_mac, signature, 8) != 0) { + DEBUG(2,("Bad SMB signature seq_num=%d\n", (int)req->seq_num)); + return False; + } + + return True; +} diff --git a/source4/smb_server/smb_server.c b/source4/smb_server/smb_server.c index aceae08ad8..ce50df04c3 100644 --- a/source4/smb_server/smb_server.c +++ b/source4/smb_server/smb_server.c @@ -558,7 +558,6 @@ static void construct_reply(struct request_context *req) return; } - /* Make sure this is an SMB packet */ if (memcmp(req->in.hdr,"\377SMB",4) != 0) { DEBUG(2,("Non-SMB packet of length %d. Terminating connection\n", @@ -584,6 +583,11 @@ static void construct_reply(struct request_context *req) req->flags = CVAL(req->in.hdr, HDR_FLG); req->flags2 = SVAL(req->in.hdr, HDR_FLG2); + if (!req_signing_check_incoming(req)) { + req_reply_error(req, NT_STATUS_ACCESS_DENIED); + return; + } + switch_message(type, req); } @@ -733,7 +737,7 @@ void init_smbsession(struct event_context *ev, struct model_ops *model_ops, int mem_ctx = talloc_init("server_context"); - smb = (struct server_context *)talloc(mem_ctx, sizeof(*smb)); + smb = talloc_p(mem_ctx, struct server_context); if (!smb) return; ZERO_STRUCTP(smb); -- cgit