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 2009 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*
  28  * Signing support, using libmd
  29  */
  30 
  31 #include <errno.h>
  32 #include <stdio.h>
  33 #include <stdlib.h>
  34 #include <unistd.h>
  35 #include <strings.h>
  36 
  37 #include <sys/types.h>
  38 #include <sys/md5.h>
  39 
  40 #include <netsmb/mchain.h>
  41 #include <netsmb/smb.h>
  42 #include <netsmb/smb_lib.h>
  43 
  44 #include "private.h"
  45 
  46 #define SMBSIGOFF       14      /* SMB signature offset */
  47 #define SMBSIGLEN       8       /* SMB signature length */
  48 
  49 /*
  50  * Set this to a small number to debug sequence numbers
  51  * that seem to get out of step.
  52  */
  53 #ifdef DEBUG
  54 int nsmb_signing_fudge = 4;
  55 #endif
  56 
  57 /*
  58  * Compute MD5 digest of packet data, using the stored MAC key.
  59  *
  60  * See similar code in the driver:
  61  *      uts/common/fs/smbclnt/netsmb/smb_signing.c
  62  * and on the server side:
  63  *      uts/common/fs/smbsrv/smb_signing.c
  64  */
  65 static int
  66 smb_compute_MAC(struct smb_ctx *ctx, mbuf_t *m,
  67         uint32_t seqno, uchar_t *signature)
  68 {
  69         MD5_CTX md5;
  70         uchar_t digest[MD5_DIGEST_LENGTH];
  71 
  72         /*
  73          * This union is a little bit of trickery to:
  74          * (1) get the sequence number int aligned, and
  75          * (2) reduce the number of digest calls, at the
  76          * cost of a copying 32 bytes instead of 8.
  77          * Both sides of this union are 2+32 bytes.
  78          */
  79         union {
  80                 struct {
  81                         uint8_t skip[2]; /* not used - just alignment */
  82                         uint8_t raw[SMB_HDRLEN];  /* header length (32) */
  83                 } r;
  84                 struct {
  85                         uint8_t skip[2]; /* not used - just alignment */
  86                         uint8_t hdr[SMBSIGOFF]; /* sig. offset (14) */
  87                         uint32_t sig[2]; /* MAC signature, aligned! */
  88                         uint16_t ids[5]; /* pad, Tid, Pid, Uid, Mid */
  89                 } s;
  90         } smbhdr;
  91 
  92         if (m->m_len < SMB_HDRLEN)
  93                 return (EIO);
  94         if (ctx->ct_mackey == NULL)
  95                 return (EINVAL);
  96 
  97         /*
  98          * Make an aligned copy of the SMB header
  99          * and fill in the sequence number.
 100          */
 101         bcopy(m->m_data, smbhdr.r.raw, SMB_HDRLEN);
 102         smbhdr.s.sig[0] = htolel(seqno);
 103         smbhdr.s.sig[1] = 0;
 104 
 105         /*
 106          * Compute the MAC: MD5(concat(Key, message))
 107          */
 108         MD5Init(&md5);
 109 
 110         /* Digest the MAC Key */
 111         MD5Update(&md5, ctx->ct_mackey, ctx->ct_mackeylen);
 112 
 113         /* Digest the (copied) SMB header */
 114         MD5Update(&md5, smbhdr.r.raw, SMB_HDRLEN);
 115 
 116         /* Digest the rest of the first mbuf */
 117         if (m->m_len > SMB_HDRLEN) {
 118                 MD5Update(&md5, m->m_data + SMB_HDRLEN,
 119                     m->m_len - SMB_HDRLEN);
 120         }
 121         m = m->m_next;
 122 
 123         /* Digest rest of the SMB message. */
 124         while (m) {
 125                 MD5Update(&md5, m->m_data, m->m_len);
 126                 m = m->m_next;
 127         }
 128 
 129         /* Final */
 130         MD5Final(digest, &md5);
 131 
 132         /*
 133          * Finally, store the signature.
 134          * (first 8 bytes of the digest)
 135          */
 136         if (signature)
 137                 bcopy(digest, signature, SMBSIGLEN);
 138 
 139         return (0);
 140 }
 141 
 142 /*
 143  * Sign a request with HMAC-MD5.
 144  */
 145 void
 146 smb_rq_sign(struct smb_rq *rqp)
 147 {
 148         struct smb_ctx *ctx = rqp->rq_ctx;
 149         mbuf_t *m = rqp->rq_rq.mb_top;
 150         uint8_t *sigloc;
 151         int err;
 152 
 153         /*
 154          * Our mblk allocation ensures this,
 155          * but just in case...
 156          */
 157         if (m->m_len < SMB_HDRLEN)
 158                 return;
 159         sigloc = (uchar_t *)m->m_data + SMBSIGOFF;
 160 
 161         if (ctx->ct_mackey == NULL) {
 162                 /*
 163                  * Signing is required, but we have no key yet
 164                  * fill in with the magic fake signing value.
 165                  * This happens with SPNEGO, NTLMSSP, ...
 166                  */
 167                 bcopy("BSRSPLY", sigloc, 8);
 168                 return;
 169         }
 170 
 171         /*
 172          * This will compute the MAC and store it
 173          * directly into the message at sigloc.
 174          */
 175         rqp->rq_seqno = ctx->ct_mac_seqno;
 176         ctx->ct_mac_seqno += 2;
 177         err = smb_compute_MAC(ctx, m, rqp->rq_seqno, sigloc);
 178         if (err) {
 179                 DPRINT("compute MAC, err %d", err);
 180                 bzero(sigloc, SMBSIGLEN);
 181         }
 182 }
 183 
 184 /*
 185  * Verify reply signature.
 186  */
 187 int
 188 smb_rq_verify(struct smb_rq *rqp)
 189 {
 190         struct smb_ctx *ctx = rqp->rq_ctx;
 191         mbuf_t *m = rqp->rq_rp.mb_top;
 192         uint8_t sigbuf[SMBSIGLEN];
 193         uint8_t *sigloc;
 194         uint32_t rseqno;
 195         int err, fudge;
 196 
 197         /*
 198          * Note ct_mackey and ct_mackeylen gets initialized by
 199          * smb_smb_ssnsetup.  It's normal to have a null MAC key
 200          * during extended security session setup.
 201          */
 202         if (ctx->ct_mackey == NULL)
 203                 return (0);
 204 
 205         /*
 206          * Let caller deal with empty reply or short messages by
 207          * returning zero.  Caller will fail later, in parsing.
 208          */
 209         if (m == NULL) {
 210                 DPRINT("empty reply");
 211                 return (0);
 212         }
 213         if (m->m_len < SMB_HDRLEN) {
 214                 DPRINT("short reply");
 215                 return (0);
 216         }
 217 
 218         sigloc = (uchar_t *)m->m_data + SMBSIGOFF;
 219         rseqno = rqp->rq_seqno + 1;
 220 
 221         DPRINT("rq_rseqno = 0x%x", rseqno);
 222 
 223         err = smb_compute_MAC(ctx, m, rseqno, sigbuf);
 224         if (err) {
 225                 DPRINT("compute MAC, err %d", err);
 226                 /*
 227                  * If we can't compute a MAC, then there's
 228                  * no point trying other seqno values.
 229                  */
 230                 return (EBADRPC);
 231         }
 232 
 233         /*
 234          * Compare the computed signature with the
 235          * one found in the message (at sigloc)
 236          */
 237         if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0)
 238                 return (0);
 239 
 240         DPRINT("BAD signature, MID=0x%x", rqp->rq_mid);
 241 
 242 #ifdef DEBUG
 243         /*
 244          * For diag purposes, we check whether the client/server idea
 245          * of the sequence # has gotten a bit out of sync.
 246          */
 247         for (fudge = 1; fudge <= nsmb_signing_fudge; fudge++) {
 248                 (void) smb_compute_MAC(ctx, m, rseqno + fudge, sigbuf);
 249                 if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0)
 250                         break;
 251                 (void) smb_compute_MAC(ctx, m, rseqno - fudge, sigbuf);
 252                 if (bcmp(sigbuf, sigloc, SMBSIGLEN) == 0) {
 253                         fudge = -fudge;
 254                         break;
 255                 }
 256         }
 257         if (fudge <= nsmb_signing_fudge) {
 258                 DPRINT("rseqno=%d, but %d would have worked",
 259                     rseqno, rseqno + fudge);
 260         }
 261 #endif
 262         return (EBADRPC);
 263 }