xref: /illumos-gate/usr/src/uts/common/fs/smbclnt/netsmb/smb_sign.c (revision 3fe455549728ac525df3be56130ad8e075d645d7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
25  * Copyright 2024 RackTop Systems, Inc.
26  */
27 
28 /*
29  * Support for SMB "signing" (message integrity)
30  */
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/conf.h>
35 #include <sys/proc.h>
36 #include <sys/fcntl.h>
37 #include <sys/socket.h>
38 #include <sys/md4.h>
39 #include <sys/md5.h>
40 #include <sys/des.h>
41 #include <sys/kmem.h>
42 #include <sys/cmn_err.h>
43 #include <sys/stream.h>
44 #include <sys/strsun.h>
45 #include <sys/sdt.h>
46 
47 #include <netsmb/nsmb_kcrypt.h>
48 
49 #include <netsmb/smb_osdep.h>
50 #include <netsmb/smb.h>
51 #include <netsmb/smb_conn.h>
52 #include <netsmb/smb_subr.h>
53 #include <netsmb/smb_dev.h>
54 #include <netsmb/smb_rq.h>
55 
56 #ifdef DEBUG
57 /*
58  * Set this to a small number to debug sequence numbers
59  * that seem to get out of step.
60  */
61 int nsmb_signing_fudge = 0;
62 #endif
63 
64 /*
65  * This is called just after session setup completes,
66  * at the top of smb_iod_vc_work().  Initialize signing.
67  */
68 int
69 smb_sign_init(smb_vc_t *vcp)
70 {
71 	int rc;
72 
73 	ASSERT(vcp->vc_ssnkey != NULL);
74 	ASSERT(vcp->vc_mackey == NULL);
75 
76 	rc = nsmb_md5_getmech(&vcp->vc_signmech);
77 	if (rc != 0) {
78 		cmn_err(CE_NOTE, "smb can't get signing mechanism");
79 		return (EAUTH);
80 	}
81 
82 	/*
83 	 * Convert the session key to the MAC key.
84 	 * SMB1 uses the whole session key.
85 	 */
86 	vcp->vc_mackeylen = vcp->vc_ssnkeylen;
87 	vcp->vc_mackey = kmem_zalloc(vcp->vc_mackeylen, KM_SLEEP);
88 	bcopy(vcp->vc_ssnkey, vcp->vc_mackey, vcp->vc_mackeylen);
89 
90 	/* The initial sequence number is two. */
91 	vcp->vc_next_seq = 2;
92 
93 	return (0);
94 }
95 
96 
97 #define	SMBSIGLEN	8	/* SMB signature length */
98 #define	SMBSIGOFF	14	/* SMB signature offset */
99 
100 /*
101  * Compute HMAC-MD5 of packet data, using the stored MAC key.
102  *
103  * See similar code for the server side:
104  * uts/common/fs/smbsrv/smb_signing.c : smb_sign_calc
105  */
106 static int
107 smb_compute_MAC(struct smb_vc *vcp, mblk_t *mp,
108     uint32_t seqno, uchar_t *signature)
109 {
110 	uchar_t digest[MD5_DIGEST_LENGTH];
111 	smb_sign_ctx_t ctx = 0;
112 	mblk_t *m = mp;
113 	int size;
114 	int rc;
115 
116 	/*
117 	 * This union is a little bit of trickery to:
118 	 * (1) get the sequence number int aligned, and
119 	 * (2) reduce the number of digest calls, at the
120 	 * cost of a copying 32 bytes instead of 8.
121 	 * Both sides of this union are 2+32 bytes.
122 	 */
123 	union {
124 		struct {
125 			uint8_t skip[2]; /* not used - just alignment */
126 			uint8_t raw[SMB_HDRLEN];  /* header length (32) */
127 		} r;
128 		struct {
129 			uint8_t skip[2]; /* not used - just alignment */
130 			uint8_t hdr[SMBSIGOFF]; /* sig. offset (14) */
131 			uint32_t sig[2]; /* MAC signature, aligned! */
132 			uint16_t ids[5]; /* pad, Tid, Pid, Uid, Mid */
133 		} s;
134 	} smbhdr;
135 
136 	if (vcp->vc_mackey == NULL)
137 		return (-1);
138 
139 	if ((rc = nsmb_md5_init(&ctx, &vcp->vc_signmech)) != 0)
140 		return (rc);
141 
142 	/* Digest the MAC Key */
143 	rc = nsmb_md5_update(ctx, vcp->vc_mackey, vcp->vc_mackeylen);
144 	if (rc != 0)
145 		return (rc);
146 
147 	/* Our caller should ensure mp has a contiguous header */
148 	ASSERT(m != NULL);
149 	ASSERT(MBLKL(m) >= SMB_HDRLEN);
150 
151 	/*
152 	 * Make an aligned copy of the SMB header,
153 	 * fill in the sequence number, and digest.
154 	 */
155 	size = SMB_HDRLEN;
156 	bcopy(m->b_rptr, smbhdr.r.raw, size);
157 	smbhdr.s.sig[0] = htolel(seqno);
158 	smbhdr.s.sig[1] = 0;
159 
160 	rc = nsmb_md5_update(ctx, &smbhdr.r.raw, size);
161 	if (rc != 0)
162 		return (rc);
163 
164 	/*
165 	 * Digest the rest of the SMB header packet, starting at
166 	 * the data just after the SMB header.
167 	 */
168 	size = MBLKL(m) - SMB_HDRLEN;
169 	rc = nsmb_md5_update(ctx, m->b_rptr + SMB_HDRLEN, size);
170 	if (rc != 0)
171 		return (rc);
172 	m = m->b_cont;
173 
174 	/* Digest rest of the SMB message. */
175 	while (m != NULL) {
176 		size = MBLKL(m);
177 		if (size > 0) {
178 			rc = nsmb_md5_update(ctx, m->b_rptr, size);
179 			if (rc != 0)
180 				return (rc);
181 		}
182 		m = m->b_cont;
183 	}
184 	rc = nsmb_md5_final(ctx, digest);
185 	if (rc != 0)
186 		return (rc);
187 
188 	/*
189 	 * Finally, store the signature.
190 	 * (first 8 bytes of the mac)
191 	 */
192 	if (signature != NULL)
193 		bcopy(digest, signature, SMBSIGLEN);
194 
195 	return (0);
196 }
197 
198 /*
199  * Sign a request with HMAC-MD5.
200  */
201 void
202 smb_rq_sign(struct smb_rq *rqp)
203 {
204 	struct smb_vc *vcp = rqp->sr_vc;
205 	mblk_t *mp = rqp->sr_rq.mb_top;
206 	uint8_t *sigloc;
207 	int status;
208 
209 	/*
210 	 * smb_rq_new() ensures this,
211 	 * but just in case..
212 	 */
213 	ASSERT(MBLKL(mp) >= SMB_HDRLEN);
214 	sigloc = mp->b_rptr + SMBSIGOFF;
215 
216 	if (vcp->vc_mackey == NULL) {
217 		/*
218 		 * Signing is required, but we have no key yet
219 		 * fill in with the magic fake signing value.
220 		 * This happens with SPNEGO, NTLMSSP, ...
221 		 */
222 		bcopy("BSRSPLY", sigloc, 8);
223 		return;
224 	}
225 
226 	/*
227 	 * This will compute the MAC and store it
228 	 * directly into the message at sigloc.
229 	 */
230 	status = smb_compute_MAC(vcp, mp, rqp->sr_seqno, sigloc);
231 	if (status != 0) {
232 		SMBSDEBUG("Crypto error %d", status);
233 		bzero(sigloc, SMBSIGLEN);
234 	}
235 }
236 
237 /*
238  * Verify reply signature.
239  */
240 int
241 smb_rq_verify(struct smb_rq *rqp)
242 {
243 	struct smb_vc *vcp = rqp->sr_vc;
244 	mblk_t *mp = rqp->sr_rp.md_top;
245 	uint8_t sigbuf[SMBSIGLEN];
246 	uint8_t *sigloc;
247 	int fudge, rsn, status;
248 
249 	/*
250 	 * Note vc_mackey and vc_mackeylen gets filled in by
251 	 * smb_usr_iod_work as the connection comes in.
252 	 */
253 	if (vcp->vc_mackey == NULL) {
254 		SMBSDEBUG("no mac key\n");
255 		return (0);
256 	}
257 
258 	/*
259 	 * Let caller deal with empty reply or short messages by
260 	 * returning zero.  Caller will fail later, in parsing.
261 	 */
262 	if (mp == NULL) {
263 		SMBSDEBUG("empty reply\n");
264 		return (0);
265 	}
266 
267 	ASSERT(MBLKL(mp) >= SMB_HDRLEN);
268 	sigloc = mp->b_rptr + SMBSIGOFF;
269 
270 	/*
271 	 * Compute the expected signature in sigbuf.
272 	 */
273 	rsn = rqp->sr_rseqno;
274 	status = smb_compute_MAC(vcp, mp, rsn, sigbuf);
275 	if (status != 0) {
276 		SMBSDEBUG("Crypto error %d", status);
277 		/*
278 		 * If we can't compute a MAC, then there's
279 		 * no point trying other seqno values.
280 		 */
281 		return (EBADRPC);
282 	}
283 
284 	/*
285 	 * Compare the computed signature with the
286 	 * one found in the message (at sigloc)
287 	 */
288 	if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0)
289 		return (0);
290 
291 	SMBERROR("BAD signature, Server=%s MID=0x%x Seq=%d\n",
292 	    vcp->vc_srvname, rqp->sr_mid, rsn);
293 
294 #ifdef DEBUG
295 	/*
296 	 * For diag purposes, we check whether the client/server idea
297 	 * of the sequence # has gotten a bit out of sync.
298 	 */
299 	for (fudge = 1; fudge <= nsmb_signing_fudge; fudge++) {
300 		(void) smb_compute_MAC(vcp, mp, rsn + fudge, sigbuf);
301 		if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0)
302 			break;
303 		(void) smb_compute_MAC(vcp, mp, rsn - fudge, sigbuf);
304 		if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0) {
305 			fudge = -fudge;
306 			break;
307 		}
308 	}
309 	if (fudge <= nsmb_signing_fudge) {
310 		SMBERROR("MID=0x%x, Seq=%d, but %d would have worked\n",
311 		    rqp->sr_mid, rsn, rsn + fudge);
312 	}
313 #endif
314 	return (EBADRPC);
315 }
316