summaryrefslogtreecommitdiff
path: root/usr/src/lib/smbsrv/libmlsvc/common/netr_ssp.c
blob: 8b342f455f459bc1dc80a43844448a8934156b7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2020 Tintri by DDN, Inc. All Rights Reserved.
 */

#include <sys/md5.h>
#include <strings.h>
#include <stdio.h>
#include <smbsrv/netrauth.h>
#include <smbsrv/string.h>
#include <smbsrv/libsmb.h>
#include <libmlsvc.h>
#include <resolv.h>

/*
 * NETLOGON SSP for "Secure RPC" works as follows:
 * 1. The client binds to the DC without RPC-level authentication.
 * 2. The client and server negotiate a Session Key using a client
 * and server challenge, plus a shared secret (the machine password).
 * This happens via NetrServerReqChallenge and NetrServerAuthenticate.
 * The key is bound to a particular Computer/Server Name pair.
 * 3. The client then establishes a new bind (or alters its existing one),
 * this time requesting the NETLOGON provider for RPC-level authentication.
 * The server uses the Computer and Domain names provided in the
 * authentication token in the bind request in order to find
 * the previously-negotiated Session Key (and rejects the bind if none
 * exists).
 * 4. The client and server then use this Session Key to provide
 * integrity and/or confidentiality to future NETLOGON RPC messages.
 *
 * The functions in this file implement the NETLOGON SSP, as defined in
 * [MS-NRPC] 3.3 "Netlogon as a Security Support Provider".
 *
 * Session Key negotiation is implemented in netr_auth.c.
 * It is the same key that is used for generating NETLOGON credentials.
 */

enum nl_token_type {
	NL_AUTH_REQUEST = 0x00000000,
	NL_AUTH_RESPONSE = 0x00000001
};

/*
 * DOMAIN = domain name
 * COMPUTER = client computer name
 * HOST = client host name
 *
 * NB = NetBios format
 * DNS = FQDN
 *
 * OEM = OEM_STRING
 * COMPRESSED = Compressed UTF-8 string
 *
 * Each of these is NULL-terminated, and delinated by such.
 * They are always found in this order, when specified.
 *
 * We currently use everything but NL_HOST_DNS_COMPRESSED_FLAG.
 */
#define	NL_DOMAIN_NB_OEM_FLAG		0x00000001
#define	NL_COMPUTER_NB_OEM_FLAG		0x00000002
#define	NL_DOMAIN_DNS_COMPRESSED_FLAG	0x00000004
#define	NL_HOST_DNS_COMPRESSED_FLAG	0x00000008
#define	NL_COMPUTER_NB_COMPRESSED_FLAG	0x00000010

#define	NL_DOMAIN_FLAGS			\
	(NL_DOMAIN_NB_OEM_FLAG|NL_DOMAIN_DNS_COMPRESSED_FLAG)
#define	NL_COMPUTER_FLAGS		\
	(NL_COMPUTER_NB_OEM_FLAG|		\
	NL_HOST_DNS_COMPRESSED_FLAG|		\
	NL_COMPUTER_NB_COMPRESSED_FLAG)

#define	MD_DIGEST_LEN 16

/* These structures are OPAQUE at the RPC level - not marshalled. */
typedef struct nl_auth_message {
	uint32_t nam_type;
	uint32_t nam_flags;
	uchar_t nam_str[1];
} nl_auth_message_t;

/*
 * The size of this structure is used for space accounting.
 * The confounder is not present on the wire unless confidentiality
 * has been negotiated. If we ever support confidentiality,
 * we'll need to adjust space accounting based on whether
 * the confounder is needed.
 */
typedef struct nl_auth_sig {
	uint16_t nas_sig_alg;
	uint16_t nas_seal_alg;
	uint16_t nas_pad;
	uint16_t nas_flags;
	uchar_t nas_seqnum[8];
	uchar_t nas_sig[8];
	/* uchar_t nas_confounder[8]; */ /* only for encryption */
} nl_auth_sig_t;

void
netr_show_msg(nl_auth_message_t *nam, ndr_stream_t *nds)
{
	ndo_printf(nds, NULL, "nl_auth_message: type=0x%x flags=0x%x");
}

void
netr_show_sig(nl_auth_sig_t *nas, ndr_stream_t *nds)
{
	ndo_printf(nds, NULL, "nl_auth_sig: SignatureAlg=0x%x SealAlg=0x%x "
	    "pad=0x%x flags=0x%x SequenceNumber=%llu Signature=0x%x",
	    nas->nas_sig_alg, nas->nas_seal_alg, nas->nas_pad,
	    nas->nas_flags, *(uint64_t *)nas->nas_seqnum,
	    *(uint64_t *)nas->nas_sig);
}

/*
 * NETLOGON SSP gss_init_sec_context equivalent
 * [MS-RPCE] 3.3.4.1.1 "Generating an Initial NL_AUTH_MESSAGE"
 *
 * We need to encode at least one Computer name and at least one
 * Domain name. The server uses this to find the Session Key
 * negotiated earlier between this client and server.
 *
 * We attempt to provide NL_DOMAIN_NB_OEM_FLAG, NL_COMPUTER_NB_OEM_FLAG,
 * NL_DOMAIN_DNS_COMPRESSED_FLAG, and NL_COMPUTER_NB_COMPRESSED_FLAG.
 *
 * See the above comments for how these are encoded.
 */
int
netr_ssp_init(void *arg, ndr_xa_t *mxa)
{
	netr_info_t *auth = arg;
	ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
	nl_auth_message_t *nam;
	size_t domain_len, comp_len, len;
	int slen;
	uchar_t *dnptrs[3], **dnlastptr;

	domain_len = smb_sbequiv_strlen(auth->nb_domain);
	comp_len = smb_sbequiv_strlen(auth->hostname);

	/*
	 * Need to allocate length for two OEM_STRINGs + NULL bytes, plus space
	 * sufficient for two NULL-terminated compressed UTF-8 strings.
	 * For the UTF-8 strings, use 2*len as a heuristic.
	 */
	len = domain_len + 1 + comp_len + 1 +
	    strlen(auth->hostname) * 2 + strlen(auth->fqdn_domain) * 2;

	hdr->auth_length = 0;

	nam = NDR_MALLOC(mxa, len);
	if (nam == NULL)
		return (NDR_DRC_FAULT_SEC_OUT_OF_MEMORY);

	nam->nam_type = NL_AUTH_REQUEST;
	nam->nam_flags = 0;

	if (domain_len != -1) {
		slen = smb_mbstooem(nam->nam_str, auth->nb_domain, domain_len);
		if (slen >= 0) {
			hdr->auth_length += slen + 1;
			nam->nam_str[hdr->auth_length - 1] = '\0';
			nam->nam_flags |= NL_DOMAIN_NB_OEM_FLAG;
		}
	}

	if (comp_len != -1) {
		slen = smb_mbstooem(nam->nam_str + hdr->auth_length,
		    auth->hostname, comp_len);
		if (slen >= 0) {
			hdr->auth_length += slen + 1;
			nam->nam_str[hdr->auth_length - 1] = '\0';
			nam->nam_flags |= NL_COMPUTER_NB_OEM_FLAG;
		}
	}

	dnptrs[0] = NULL;
	dnlastptr = &dnptrs[sizeof (dnptrs) / sizeof (dnptrs[0])];

	slen = dn_comp(auth->fqdn_domain, nam->nam_str + hdr->auth_length,
	    len - hdr->auth_length, dnptrs, dnlastptr);

	if (slen >= 0) {
		hdr->auth_length += slen;
		nam->nam_str[hdr->auth_length] = '\0';
		nam->nam_flags |= NL_DOMAIN_DNS_COMPRESSED_FLAG;
	}

	slen = dn_comp(auth->hostname, nam->nam_str + hdr->auth_length,
	    len - hdr->auth_length, dnptrs, dnlastptr);
	if (slen >= 0) {
		hdr->auth_length += slen;
		nam->nam_str[hdr->auth_length] = '\0';
		nam->nam_flags |= NL_COMPUTER_NB_COMPRESSED_FLAG;
	}

	/* We must provide at least one Domain Name and Computer Name */
	if ((nam->nam_flags & NL_DOMAIN_FLAGS) == 0 ||
	    (nam->nam_flags & NL_COMPUTER_FLAGS) == 0)
		return (NDR_DRC_FAULT_SEC_ENCODE_FAILED);

	mxa->send_auth.auth_value = (void *)nam;
	hdr->auth_length += sizeof (nam->nam_flags) + sizeof (nam->nam_type);

	return (0);
}

/*
 * NETLOGON SSP response-side gss_init_sec_context equivalent
 * [MS-RPCE] 3.3.4.1.4 "Receiving a Return NL_AUTH_MESSAGE"
 */
int
netr_ssp_recv(void *arg, ndr_xa_t *mxa)
{
	netr_info_t *auth = arg;
	ndr_common_header_t *ahdr = &mxa->recv_hdr.common_hdr;
	ndr_sec_t *ack_secp = &mxa->recv_auth;
	nl_auth_message_t *nam;
	int rc;

	nam = (nl_auth_message_t *)ack_secp->auth_value;

	/* We only need to verify the length ("at least 12") and the type */
	if (ahdr->auth_length < 12) {
		rc = NDR_DRC_FAULT_SEC_AUTH_LENGTH_INVALID;
		goto errout;
	}
	if (nam->nam_type != NL_AUTH_RESPONSE) {
		rc = NDR_DRC_FAULT_SEC_META_INVALID;
		goto errout;
	}
	auth->clh_seqnum = 0;

	return (NDR_DRC_OK);

errout:
	netr_show_msg(nam, &mxa->recv_nds);
	return (rc);
}

/* returns byte N of seqnum */
#define	CLS_BYTE(n, seqnum) ((seqnum >> (8 * (n))) & 0xff)

/*
 * NETLOGON SSP gss_MICEx equivalent
 * [MS-RPCE] 3.3.4.2.1 "Generating a Client Netlogon Signature Token"
 *
 * Set up the metadata, encrypt and increment the SequenceNumber,
 * and sign the PDU body.
 */
int
netr_ssp_sign(void *arg, ndr_xa_t *mxa)
{
	uint32_t zeroes = 0;
	netr_info_t *auth = arg;
	ndr_common_header_t *hdr = &mxa->send_hdr.common_hdr;
	ndr_stream_t *nds = &mxa->send_nds;
	nl_auth_sig_t *nas;
	MD5_CTX md5h;
	BYTE local_sig[MD_DIGEST_LEN];
	BYTE enc_key[MD_DIGEST_LEN];

	hdr->auth_length = sizeof (nl_auth_sig_t);

	nas = NDR_MALLOC(mxa, hdr->auth_length);
	if (nas == NULL)
		return (NDR_DRC_FAULT_SEC_OUT_OF_MEMORY);

	/*
	 * SignatureAlgorithm is first byte 0x77, second byte 00 for HMAC-MD5
	 * or 0x13, 0x00 for AES-HMAC-SHA256.
	 *
	 * SealAlgorithm is first byte 0x7A, second byte 00 for RC4
	 * or 0x1A, 0x00 for AES-CFB8, or 0xffff for No Sealing.
	 *
	 * Pad is always 0xffff, and flags is always 0x0000.
	 *
	 * SequenceNumber is a computed, encrypted, 64-bit number.
	 *
	 * Each of these is always encoded in little-endian order.
	 */
	nas->nas_sig_alg = 0x0077;
	nas->nas_seal_alg = 0xffff;
	nas->nas_pad = 0xffff;
	nas->nas_flags = 0;

	/*
	 * Calculate the SequenceNumber.
	 * Note that byte 4 gets modified, as per the spec -
	 * It's the only byte that is not just set to some other byte.
	 */
	nas->nas_seqnum[0] = CLS_BYTE(3, auth->clh_seqnum);
	nas->nas_seqnum[1] = CLS_BYTE(2, auth->clh_seqnum);
	nas->nas_seqnum[2] = CLS_BYTE(1, auth->clh_seqnum);
	nas->nas_seqnum[3] = CLS_BYTE(0, auth->clh_seqnum);
	nas->nas_seqnum[4] = CLS_BYTE(7, auth->clh_seqnum) | 0x80;
	nas->nas_seqnum[5] = CLS_BYTE(6, auth->clh_seqnum);
	nas->nas_seqnum[6] = CLS_BYTE(5, auth->clh_seqnum);
	nas->nas_seqnum[7] = CLS_BYTE(4, auth->clh_seqnum);

	auth->clh_seqnum++;

	/*
	 * The HMAC-MD5 signature is computed as follows:
	 * First 8 bytes of
	 * HMAC_MD5(
	 *	MD5(0x00000000 | sig_alg | seal_alg | pad | flags | PDU body),
	 *	session_key)
	 */
	MD5Init(&md5h);
	MD5Update(&md5h, (uchar_t *)&zeroes, 4);
	MD5Update(&md5h, (uchar_t *)nas, 8);
	MD5Update(&md5h,
	    (uchar_t *)nds->pdu_base_addr + nds->pdu_body_offset,
	    nds->pdu_body_size);

	MD5Final(local_sig, &md5h);
	if (smb_auth_hmac_md5(local_sig, sizeof (local_sig),
	    auth->session_key.key, auth->session_key.len,
	    local_sig) != 0)
		return (NDR_DRC_FAULT_SEC_SSP_FAILED);

	bcopy(local_sig, nas->nas_sig, 8);

	/*
	 * Encrypt the SequenceNumber.
	 * For RC4 Encryption, the EncryptionKey is computed as follows:
	 * HMAC_MD5(signature, HMAC_MD5(0x00000000, session_key))
	 */
	if (smb_auth_hmac_md5((uchar_t *)&zeroes, 4,
	    auth->session_key.key, auth->session_key.len,
	    enc_key) != 0)
		return (NDR_DRC_FAULT_SEC_SSP_FAILED);
	if (smb_auth_hmac_md5((uchar_t *)nas->nas_sig, sizeof (nas->nas_sig),
	    enc_key, sizeof (enc_key),
	    enc_key) != 0)
		return (NDR_DRC_FAULT_SEC_SSP_FAILED);

	if (smb_auth_RC4(nas->nas_seqnum, sizeof (nas->nas_seqnum),
	    enc_key, sizeof (enc_key),
	    nas->nas_seqnum, sizeof (nas->nas_seqnum)) != 0)
		return (NDR_DRC_FAULT_SEC_SSP_FAILED);

	mxa->send_auth.auth_value = (void *)nas;

	return (NDR_DRC_OK);
}

/*
 * NETLOGON SSP gss_VerifyMICEx equivalent
 * [MS-RPCE] 3.3.4.2.4 "Receiving a Server Netlogon Signature Token"
 *
 * Verify the metadata, decrypt, verify, and increment the SequenceNumber,
 * and validate the PDU body against the provided signature.
 */
int
netr_ssp_verify(void *arg, ndr_xa_t *mxa, boolean_t verify_resp)
{
	uint32_t zeroes = 0;
	netr_info_t *auth = arg;
	ndr_sec_t *secp = &mxa->recv_auth;
	ndr_stream_t *nds = &mxa->recv_nds;
	nl_auth_sig_t *nas;
	MD5_CTX md5h;
	BYTE local_sig[MD_DIGEST_LEN];
	BYTE dec_key[MD_DIGEST_LEN];
	BYTE local_seqnum[8];
	int rc;
	boolean_t seqnum_bumped = B_FALSE;

	nas = (nl_auth_sig_t *)secp->auth_value;

	/*
	 * Verify SignatureAlgorithm, SealAlgorithm, and Pad are as expected.
	 * These follow the same values as in the Client Signature.
	 */
	if (nas->nas_sig_alg != 0x0077 ||
	    nas->nas_seal_alg != 0xffff ||
	    nas->nas_pad != 0xffff) {
		rc = NDR_DRC_FAULT_SEC_META_INVALID;
		goto errout;
	}

	/* Decrypt the SequenceNumber. This is done the same as the Client. */
	if (smb_auth_hmac_md5((uchar_t *)&zeroes, 4,
	    auth->session_key.key, auth->session_key.len,
	    dec_key) != 0) {
		rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
		goto errout;
	}
	if (smb_auth_hmac_md5((uchar_t *)nas->nas_sig, sizeof (nas->nas_sig),
	    dec_key, sizeof (dec_key),
	    dec_key) != 0) {
		rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
		goto errout;
	}

	if (smb_auth_RC4(nas->nas_seqnum, sizeof (nas->nas_seqnum),
	    dec_key, sizeof (dec_key),
	    nas->nas_seqnum, sizeof (nas->nas_seqnum)) != 0) {
		rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
		goto errout;
	}

	/*
	 * Calculate a local version of the SequenceNumber.
	 * Note that byte 4 does NOT get modified, unlike the client.
	 */
	local_seqnum[0] = CLS_BYTE(3, auth->clh_seqnum);
	local_seqnum[1] = CLS_BYTE(2, auth->clh_seqnum);
	local_seqnum[2] = CLS_BYTE(1, auth->clh_seqnum);
	local_seqnum[3] = CLS_BYTE(0, auth->clh_seqnum);
	local_seqnum[4] = CLS_BYTE(7, auth->clh_seqnum);
	local_seqnum[5] = CLS_BYTE(6, auth->clh_seqnum);
	local_seqnum[6] = CLS_BYTE(5, auth->clh_seqnum);
	local_seqnum[7] = CLS_BYTE(4, auth->clh_seqnum);

	/* If the SequenceNumbers don't match, this is out of order - drop it */
	if (bcmp(local_seqnum, nas->nas_seqnum, sizeof (local_seqnum)) != 0) {
		ndo_printf(nds, NULL, "CalculatedSeqnum: %llu "
		    "DecryptedSeqnum: %llu",
		    *(uint64_t *)local_seqnum, *(uint64_t *)nas->nas_seqnum);
		rc = NDR_DRC_FAULT_SEC_SEQNUM_INVALID;
		goto errout;
	}

	auth->clh_seqnum++;
	seqnum_bumped = B_TRUE;

	/*
	 * Calculate the signature.
	 * This is done the same as the Client.
	 */
	MD5Init(&md5h);
	MD5Update(&md5h, (uchar_t *)&zeroes, 4);
	MD5Update(&md5h, (uchar_t *)nas, 8);
	MD5Update(&md5h,
	    (uchar_t *)nds->pdu_base_addr + nds->pdu_body_offset,
	    nds->pdu_body_size);
	MD5Final(local_sig, &md5h);
	if (smb_auth_hmac_md5(local_sig, sizeof (local_sig),
	    auth->session_key.key, auth->session_key.len,
	    local_sig) != 0) {
		rc = NDR_DRC_FAULT_SEC_SSP_FAILED;
		goto errout;
	}

	/* If the first 8 bytes don't match, drop it */
	if (bcmp(local_sig, nas->nas_sig, 8) != 0) {
		ndo_printf(nds, NULL, "CalculatedSig: %llu "
		    "PacketSig: %llu",
		    *(uint64_t *)local_sig, *(uint64_t *)nas->nas_sig);
		rc = NDR_DRC_FAULT_SEC_SIG_INVALID;
		goto errout;
	}

	return (NDR_DRC_OK);

errout:
	netr_show_sig(nas, &mxa->recv_nds);

	if (!verify_resp) {
		if (!seqnum_bumped)
			auth->clh_seqnum++;
		return (NDR_DRC_OK);
	}

	return (rc);
}

extern struct netr_info netr_global_info;

ndr_auth_ctx_t netr_ssp_ctx = {
	.auth_ops = {
		.nao_init = netr_ssp_init,
		.nao_recv = netr_ssp_recv,
		.nao_sign = netr_ssp_sign,
		.nao_verify = netr_ssp_verify
	},
	.auth_ctx = &netr_global_info,
	.auth_context_id = 0,
	.auth_type = NDR_C_AUTHN_GSS_NETLOGON,
	.auth_level = NDR_C_AUTHN_LEVEL_PKT_INTEGRITY,
	.auth_verify_resp = B_TRUE
};