1
0
mirror of https://github.com/samba-team/samba.git synced 2025-05-28 21:05:48 +03:00

implemented client side SMB2 signing

This doessn't work against Windows yet, and I've submitted a WSPP
request for clarification of the docs to try and find out
why. Meanwhile this is no worse than what we had, as it only gets used
when the server demands signing, and we didn't work then anyway.
(This used to be commit b788096add3586d7277efcd3bf5ca7f3a604cb7a)
This commit is contained in:
Andrew Tridgell 2008-05-30 17:03:54 +10:00
parent 27f465619b
commit beaa01e403
15 changed files with 308 additions and 51 deletions

View File

@ -61,10 +61,10 @@ NTSTATUS smb2_cancel(struct smb2_request *r)
SSVAL(c->out.body, 0x02, 0);
old_timeout = c->transport->options.timeout;
c->transport->options.timeout = 0;
old_timeout = c->transport->options.request_timeout;
c->transport->options.request_timeout = 0;
smb2_transport_send(c);
c->transport->options.timeout = old_timeout;
c->transport->options.request_timeout = old_timeout;
if (c->state == SMB2_REQUEST_ERROR) {
status = c->status;

View File

@ -5,6 +5,6 @@ LIBCLI_SMB2_OBJ_FILES = $(addprefix $(libclisrcdir)/smb2/, \
transport.o request.o negprot.o session.o tcon.o \
create.o close.o connect.o getinfo.o write.o read.o \
setinfo.o find.o ioctl.o logoff.o tdis.o flush.o \
lock.o notify.o cancel.o keepalive.o break.o util.o)
lock.o notify.o cancel.o keepalive.o break.o util.o signing.o)
$(eval $(call proto_header_template,$(libclisrcdir)/smb2/smb2_proto.h,$(LIBCLI_SMB2_OBJ_FILES:.o=.c)))

View File

@ -33,6 +33,7 @@ struct smb2_connect_state {
struct resolve_context *resolve_ctx;
const char *host;
const char *share;
struct smbcli_options options;
struct smb2_negprot negprot;
struct smb2_tree_connect tcon;
struct smb2_session *session;
@ -103,6 +104,34 @@ static void continue_negprot(struct smb2_request *req)
transport->negotiate.system_time = state->negprot.out.system_time;
transport->negotiate.server_start_time = state->negprot.out.server_start_time;
transport->negotiate.security_mode = state->negprot.out.security_mode;
switch (transport->options.signing) {
case SMB_SIGNING_OFF:
if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) {
composite_error(c, NT_STATUS_ACCESS_DENIED);
return;
}
transport->signing.doing_signing = false;
break;
case SMB_SIGNING_SUPPORTED:
case SMB_SIGNING_AUTO:
if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) {
transport->signing.doing_signing = true;
} else {
transport->signing.doing_signing = false;
}
break;
case SMB_SIGNING_REQUIRED:
if (transport->negotiate.security_mode & SMB2_NEGOTIATE_SIGNING_ENABLED) {
transport->signing.doing_signing = true;
} else {
composite_error(c, NT_STATUS_ACCESS_DENIED);
return;
}
break;
}
state->session = smb2_session_init(transport, global_loadparm, state, true);
if (composite_nomem(state->session, c)) return;
@ -129,12 +158,24 @@ static void continue_socket(struct composite_context *creq)
c->status = smbcli_sock_connect_recv(creq, state, &sock);
if (!composite_is_ok(c)) return;
transport = smb2_transport_init(sock, state);
transport = smb2_transport_init(sock, state, &state->options);
if (composite_nomem(transport, c)) return;
ZERO_STRUCT(state->negprot);
state->negprot.in.dialect_count = 2;
state->negprot.in.security_mode = 0;
switch (transport->options.signing) {
case SMB_SIGNING_OFF:
state->negprot.in.security_mode = 0;
break;
case SMB_SIGNING_SUPPORTED:
case SMB_SIGNING_AUTO:
state->negprot.in.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
break;
case SMB_SIGNING_REQUIRED:
state->negprot.in.security_mode =
SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED;
break;
}
state->negprot.in.capabilities = 0;
unix_to_nt_time(&state->negprot.in.start_time, time(NULL));
dialects[0] = 0;
@ -178,7 +219,8 @@ struct composite_context *smb2_connect_send(TALLOC_CTX *mem_ctx,
const char *share,
struct resolve_context *resolve_ctx,
struct cli_credentials *credentials,
struct event_context *ev)
struct event_context *ev,
struct smbcli_options *options)
{
struct composite_context *c;
struct smb2_connect_state *state;
@ -193,6 +235,7 @@ struct composite_context *smb2_connect_send(TALLOC_CTX *mem_ctx,
c->private_data = state;
state->credentials = credentials;
state->options = *options;
state->host = talloc_strdup(c, host);
if (composite_nomem(state->host, c)) return c;
state->share = talloc_strdup(c, share);
@ -232,10 +275,11 @@ NTSTATUS smb2_connect(TALLOC_CTX *mem_ctx,
struct resolve_context *resolve_ctx,
struct cli_credentials *credentials,
struct smb2_tree **tree,
struct event_context *ev)
struct event_context *ev,
struct smbcli_options *options)
{
struct composite_context *c = smb2_connect_send(mem_ctx, host, share,
resolve_ctx,
credentials, ev);
credentials, ev, options);
return smb2_connect_recv(c, mem_ctx, tree);
}

View File

@ -44,10 +44,10 @@ struct smb2_request *smb2_notify_send(struct smb2_tree *tree, struct smb2_notify
SIVAL(req->out.body, 0x18, io->in.completion_filter);
SIVAL(req->out.body, 0x1C, io->in.unknown);
old_timeout = req->transport->options.timeout;
req->transport->options.timeout = 0;
old_timeout = req->transport->options.request_timeout;
req->transport->options.request_timeout = 0;
smb2_transport_send(req);
req->transport->options.timeout = old_timeout;
req->transport->options.request_timeout = old_timeout;
return req;
}

View File

@ -164,8 +164,8 @@ static void session_request_handler(struct smb2_request *req)
session_key_err = gensec_session_key(session->gensec, &session_key);
if (NT_STATUS_IS_OK(session_key_err)) {
session->session_key = session_key;
}
session->transport->signing.session_key = session_key;
}
}
session->uid = state->io.out.uid;
@ -187,6 +187,14 @@ static void session_request_handler(struct smb2_request *req)
return;
}
if (session->transport->signing.doing_signing) {
c->status = smb2_start_signing(session->transport);
if (!NT_STATUS_IS_OK(c->status)) {
composite_error(c, c->status);
return;
}
}
composite_done(c);
}
@ -208,7 +216,10 @@ struct composite_context *smb2_session_setup_spnego_send(struct smb2_session *se
ZERO_STRUCT(state->io);
state->io.in.vc_number = 0;
state->io.in.security_mode = 0;
if (session->transport->signing.doing_signing) {
state->io.in.security_mode =
SMB2_NEGOTIATE_SIGNING_ENABLED | SMB2_NEGOTIATE_SIGNING_REQUIRED;
}
state->io.in.capabilities = 0;
state->io.in.channel = 0;
state->io.in.previous_sessionid = 0;

View File

@ -0,0 +1,165 @@
/*
Unix SMB/CIFS implementation.
SMB2 Signing Code
Copyright (C) Andrew Tridgell <tridge@samba.org> 2008
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 "libcli/raw/libcliraw.h"
#include "libcli/smb2/smb2.h"
#include "libcli/smb2/smb2_calls.h"
#include "heimdal/lib/hcrypto/sha.h"
/*
NOTE: this code does not yet interoperate with the windows SMB2
implementation. We are waiting on feedback on the docs to find out
why
*/
/*
setup signing on a transport
*/
NTSTATUS smb2_start_signing(struct smb2_transport *transport)
{
if (transport->signing.session_key.length != 16) {
DEBUG(2,("Wrong session key length %u for SMB2 signing\n",
(unsigned)transport->signing.session_key.length));
return NT_STATUS_ACCESS_DENIED;
}
transport->signing.signing_started = true;
return NT_STATUS_OK;
}
/*
sign an outgoing message
*/
NTSTATUS smb2_sign_message(struct smb2_request *req)
{
struct smb2_request_buffer *buf = &req->out;
uint64_t session_id;
SHA256_CTX m;
uint8_t res[32];
if (!req->transport->signing.doing_signing ||
!req->transport->signing.signing_started) {
return NT_STATUS_OK;
}
if (buf->size < NBT_HDR_SIZE + SMB2_HDR_SIGNATURE + 16) {
/* can't sign non-SMB2 messages */
return NT_STATUS_OK;
}
session_id = BVAL(buf->hdr, SMB2_HDR_SESSION_ID);
if (session_id == 0) {
/* we don't sign messages with a zero session_id. See
MS-SMB2 3.2.4.1.1 */
return NT_STATUS_OK;
}
if (req->transport->signing.session_key.length != 16) {
DEBUG(2,("Wrong session key length %u for SMB2 signing\n",
(unsigned)req->transport->signing.session_key.length));
return NT_STATUS_ACCESS_DENIED;
}
memset(buf->hdr + SMB2_HDR_SIGNATURE, 0, 16);
SIVAL(buf->hdr, SMB2_HDR_FLAGS, IVAL(buf->hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED);
ZERO_STRUCT(m);
SHA256_Init(&m);
SHA256_Update(&m, req->transport->signing.session_key.data,
req->transport->signing.session_key.length);
SHA256_Update(&m, buf->buffer+NBT_HDR_SIZE, buf->size-NBT_HDR_SIZE);
SHA256_Final(res, &m);
DEBUG(5,("signed SMB2 message of size %u\n", (unsigned)buf->size - NBT_HDR_SIZE));
memcpy(buf->hdr + SMB2_HDR_SIGNATURE, res, 16);
if (DEBUGLVL(5)) {
/* check our own signature */
smb2_check_signature(req->transport, buf->buffer, buf->size);
}
return NT_STATUS_OK;
}
/*
check an incoming signature
*/
NTSTATUS smb2_check_signature(struct smb2_transport *transport,
uint8_t *buffer, uint_t length)
{
uint64_t session_id;
SHA256_CTX m;
uint8_t res[SHA256_DIGEST_LENGTH];
uint8_t sig[16];
if (!transport->signing.signing_started ||
!transport->signing.doing_signing) {
return NT_STATUS_OK;
}
if (length < NBT_HDR_SIZE + SMB2_HDR_SIGNATURE + 16) {
/* can't check non-SMB2 messages */
return NT_STATUS_OK;
}
session_id = BVAL(buffer+NBT_HDR_SIZE, SMB2_HDR_SESSION_ID);
if (session_id == 0) {
/* don't sign messages with a zero session_id. See
MS-SMB2 3.2.4.1.1 */
return NT_STATUS_OK;
}
if (transport->signing.session_key.length == 0) {
/* we don't have the session key yet */
return NT_STATUS_OK;
}
if (transport->signing.session_key.length != 16) {
DEBUG(2,("Wrong session key length %u for SMB2 signing\n",
(unsigned)transport->signing.session_key.length));
return NT_STATUS_ACCESS_DENIED;
}
memcpy(sig, buffer+NBT_HDR_SIZE+SMB2_HDR_SIGNATURE, 16);
memset(buffer + NBT_HDR_SIZE + SMB2_HDR_SIGNATURE, 0, 16);
ZERO_STRUCT(m);
SHA256_Init(&m);
SHA256_Update(&m, transport->signing.session_key.data, 16);
SHA256_Update(&m, buffer+NBT_HDR_SIZE, length-NBT_HDR_SIZE);
SHA256_Final(res, &m);
memcpy(buffer+NBT_HDR_SIZE+SMB2_HDR_SIGNATURE, sig, 16);
if (memcmp(res, sig, 16) != 0) {
DEBUG(0,("Bad SMB2 signature for message of size %u\n", length));
dump_data(0, sig, 16);
dump_data(0, res, 16);
return NT_STATUS_ACCESS_DENIED;
}
return NT_STATUS_OK;
}

View File

@ -23,20 +23,24 @@
#define __LIBCLI_SMB2_SMB2_H__
#include "libcli/raw/request.h"
#include "libcli/raw/libcliraw.h"
struct smb2_handle;
struct smb2_options {
uint32_t timeout;
struct smb2_signing_context {
bool doing_signing;
bool signing_started;
DATA_BLOB session_key;
};
/*
information returned from the negotiate response
information returned from the negotiate process
*/
struct smb2_negotiate {
DATA_BLOB secblob;
NTTIME system_time;
NTTIME server_start_time;
uint16_t security_mode;
};
/* this is the context for the smb2 transport layer */
@ -44,7 +48,6 @@ struct smb2_transport {
/* socket level info */
struct smbcli_socket *socket;
struct smb2_options options;
struct smb2_negotiate negotiate;
/* next seqnum to allocate */
@ -74,6 +77,9 @@ struct smb2_transport {
/* private data passed to the oplock handler */
void *private_data;
} oplock;
struct smbcli_options options;
struct smb2_signing_context signing;
};
@ -92,7 +98,6 @@ struct smb2_session {
struct smb2_transport *transport;
struct gensec_security *gensec;
uint64_t uid;
DATA_BLOB session_key;
};
@ -193,6 +198,13 @@ struct smb2_request {
#define SMB2_HDR_SIGNATURE 0x30 /* 16 bytes */
#define SMB2_HDR_BODY 0x40
/* header flags */
#define SMB2_HDR_FLAG_REDIRECT 0x01
#define SMB2_HDR_FLAG_ASYNC 0x02
#define SMB2_HDR_FLAG_CHAINED 0x04
#define SMB2_HDR_FLAG_SIGNED 0x08
#define SMB2_HDR_FLAG_DFS 0x10000000
/* SMB2 opcodes */
#define SMB2_OP_NEGPROT 0x00
#define SMB2_OP_SESSSETUP 0x01

View File

@ -74,7 +74,8 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob);
create a transport structure based on an established socket
*/
struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
TALLOC_CTX *parent_ctx)
TALLOC_CTX *parent_ctx,
struct smbcli_options *options)
{
struct smb2_transport *transport;
@ -82,6 +83,7 @@ struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
if (!transport) return NULL;
transport->socket = talloc_steal(transport, sock);
transport->options = *options;
/* setup the stream -> packet parser */
transport->packet = packet_init(transport);
@ -112,8 +114,6 @@ struct smb2_transport *smb2_transport_init(struct smbcli_socket *sock,
talloc_set_destructor(transport, transport_destructor);
transport->options.timeout = 30;
return transport;
}
@ -140,27 +140,24 @@ void smb2_transport_dead(struct smb2_transport *transport, NTSTATUS status)
}
}
static bool smb2_handle_oplock_break(struct smb2_transport *transport,
const DATA_BLOB *blob)
static NTSTATUS smb2_handle_oplock_break(struct smb2_transport *transport,
const DATA_BLOB *blob)
{
uint8_t *hdr;
uint16_t opcode;
uint64_t seqnum;
hdr = blob->data+NBT_HDR_SIZE;
if (blob->length < (SMB2_MIN_SIZE+0x18)) {
DEBUG(1,("Discarding smb2 oplock reply of size %u\n",
blob->length));
return false;
(unsigned)blob->length));
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
opcode = SVAL(hdr, SMB2_HDR_OPCODE);
seqnum = BVAL(hdr, SMB2_HDR_MESSAGE_ID);
if ((opcode != SMB2_OP_BREAK) ||
(seqnum != UINT64_MAX)) {
return false;
if (opcode != SMB2_OP_BREAK) {
return NT_STATUS_INVALID_NETWORK_RESPONSE;
}
if (transport->oplock.handler) {
@ -173,9 +170,11 @@ static bool smb2_handle_oplock_break(struct smb2_transport *transport,
transport->oplock.handler(transport, &h, level,
transport->oplock.private_data);
} else {
DEBUG(5,("Got SMB2 oplock break with no handler\n"));
}
return true;
return NT_STATUS_OK;
}
/*
@ -194,6 +193,7 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
uint16_t buffer_code;
uint32_t dynamic_size;
uint32_t i;
NTSTATUS status;
buffer = blob.data;
len = blob.length;
@ -205,14 +205,20 @@ static NTSTATUS smb2_transport_finish_recv(void *private, DATA_BLOB blob)
goto error;
}
if (smb2_handle_oplock_break(transport, &blob)) {
status = smb2_check_signature(transport, buffer, len);
if (!NT_STATUS_IS_OK(status)) {
talloc_free(buffer);
return NT_STATUS_OK;
return status;
}
flags = IVAL(hdr, SMB2_HDR_FLAGS);
seqnum = BVAL(hdr, SMB2_HDR_MESSAGE_ID);
/* see MS-SMB2 3.2.5.19 */
if (seqnum == UINT64_MAX) {
return smb2_handle_oplock_break(transport, &blob);
}
/* match the incoming request against the list of pending requests */
for (req=transport->pending_recv; req; req=req->next) {
if (req->seqnum == seqnum) break;
@ -340,6 +346,13 @@ void smb2_transport_send(struct smb2_request *req)
return;
}
status = smb2_sign_message(req);
if (!NT_STATUS_IS_OK(status)) {
req->state = SMB2_REQUEST_ERROR;
req->status = status;
return;
}
blob = data_blob_const(req->out.buffer, req->out.size);
status = packet_send(req->transport->packet, blob);
if (!NT_STATUS_IS_OK(status)) {
@ -352,9 +365,9 @@ void smb2_transport_send(struct smb2_request *req)
DLIST_ADD(req->transport->pending_recv, req);
/* add a timeout */
if (req->transport->options.timeout) {
if (req->transport->options.request_timeout) {
event_add_timed(req->transport->socket->event.ctx, req,
timeval_current_ofs(req->transport->options.timeout, 0),
timeval_current_ofs(req->transport->options.request_timeout, 0),
smb2_timeout_handler, req);
}

View File

@ -218,6 +218,7 @@ static struct composite_context *dcerpc_pipe_connect_ncacn_np_smb2_send(
struct composite_context *c;
struct pipe_np_smb2_state *s;
struct composite_context *conn_req;
struct smbcli_options options;
/* composite context allocation and setup */
c = composite_create(mem_ctx, io->pipe->conn->event_ctx);
@ -240,11 +241,14 @@ static struct composite_context *dcerpc_pipe_connect_ncacn_np_smb2_send(
cli_credentials_guess(s->io.creds, lp_ctx);
}
lp_smbcli_options(lp_ctx, &options);
/* send smb2 connect request */
conn_req = smb2_connect_send(mem_ctx, s->io.binding->host, "IPC$",
s->io.resolve_ctx,
s->io.creds,
c->event_ctx);
c->event_ctx,
&options);
composite_continue(c, conn_req, continue_smb2_connect, c);
return c;
}

View File

@ -376,7 +376,7 @@ static NTSTATUS smb2_session_key(struct dcerpc_connection *c, DATA_BLOB *session
{
struct smb2_private *smb = talloc_get_type(c->transport.private_data,
struct smb2_private);
*session_key = smb->tree->session->session_key;
*session_key = smb->tree->session->transport->signing.session_key;
if (session_key->data == NULL) {
return NT_STATUS_NO_USER_SESSION_KEY;
}

View File

@ -162,9 +162,9 @@ static NTSTATUS cvfs_connect(struct ntvfs_module_context *ntvfs,
struct composite_context *creq;
struct share_config *scfg = ntvfs->ctx->config;
struct smb2_tree *tree;
struct cli_credentials *credentials;
bool machine_account;
struct smbcli_options options;
/* Here we need to determine which server to connect to.
* For now we use parametric options, type cifs.
@ -224,10 +224,12 @@ static NTSTATUS cvfs_connect(struct ntvfs_module_context *ntvfs,
return NT_STATUS_INVALID_PARAMETER;
}
lp_smbcli_options(ntvfs->ctx->lp_ctx, &options);
creq = smb2_connect_send(private, host, remote_share,
lp_resolve_context(ntvfs->ctx->lp_ctx),
credentials,
ntvfs->ctx->event_ctx);
ntvfs->ctx->event_ctx, &options);
status = smb2_connect_recv(creq, private, &tree);
NT_STATUS_NOT_OK_RETURN(status);

View File

@ -213,6 +213,9 @@ static bool connect_servers(struct event_context *ev,
for (i=0;i<NSERVERS;i++) {
for (j=0;j<NINSTANCES;j++) {
NTSTATUS status;
struct smbcli_options smb_options;
lp_smbcli_options(lp_ctx, &smb_options);
printf("Connecting to \\\\%s\\%s as %s - instance %d\n",
servers[i].server_name, servers[i].share_name,
servers[i].credentials->username, j);
@ -226,10 +229,8 @@ static bool connect_servers(struct event_context *ev,
lp_resolve_context(lp_ctx),
servers[i].credentials,
&servers[i].smb2_tree[j],
ev);
ev, &smb_options);
} else {
struct smbcli_options smb_options;
lp_smbcli_options(lp_ctx, &smb_options);
status = smbcli_tree_full_connection(NULL,
&servers[i].smb_tree[j],
servers[i].server_name,

View File

@ -180,7 +180,6 @@ static bool test_read_dir(struct torture_context *torture, struct smb2_tree *tre
bool ret = true;
NTSTATUS status;
struct smb2_handle h;
uint8_t buf[100];
struct smb2_read rd;
TALLOC_CTX *tmp_ctx = talloc_new(tree);

View File

@ -203,17 +203,20 @@ bool torture_smb2_scan(struct torture_context *torture)
NTSTATUS status;
int opcode;
struct smb2_request *req;
struct smbcli_options options;
lp_smbcli_options(torture->lp_ctx, &options);
status = smb2_connect(mem_ctx, host, share,
lp_resolve_context(torture->lp_ctx),
credentials, &tree,
torture->ev);
torture->ev, &options);
if (!NT_STATUS_IS_OK(status)) {
printf("Connection failed - %s\n", nt_errstr(status));
return false;
}
tree->session->transport->options.timeout = 3;
tree->session->transport->options.request_timeout = 3;
for (opcode=0;opcode<1000;opcode++) {
req = smb2_request_init_tree(tree, opcode, 2, false, 0);
@ -224,12 +227,12 @@ bool torture_smb2_scan(struct torture_context *torture)
status = smb2_connect(mem_ctx, host, share,
lp_resolve_context(torture->lp_ctx),
credentials, &tree,
torture->ev);
torture->ev, &options);
if (!NT_STATUS_IS_OK(status)) {
printf("Connection failed - %s\n", nt_errstr(status));
return false;
}
tree->session->transport->options.timeout = 3;
tree->session->transport->options.request_timeout = 3;
} else {
status = smb2_request_destroy(req);
printf("active opcode %4d gave status %s\n", opcode, nt_errstr(status));

View File

@ -270,11 +270,14 @@ bool torture_smb2_connection(struct torture_context *tctx, struct smb2_tree **tr
const char *host = torture_setting_string(tctx, "host", NULL);
const char *share = torture_setting_string(tctx, "share", NULL);
struct cli_credentials *credentials = cmdline_credentials;
struct smbcli_options options;
lp_smbcli_options(tctx->lp_ctx, &options);
status = smb2_connect(tctx, host, share,
lp_resolve_context(tctx->lp_ctx),
credentials, tree,
tctx->ev);
tctx->ev, &options);
if (!NT_STATUS_IS_OK(status)) {
printf("Failed to connect to SMB2 share \\\\%s\\%s - %s\n",
host, share, nt_errstr(status));