@@ -743,6 +743,11 @@ cifs_crypto_secmech_release(struct TCP_Server_Info *server)
server->secmech.hmacmd5 = NULL;
}
+ if (server->secmech.aes_gmac) {
+ crypto_free_aead(server->secmech.aes_gmac);
+ server->secmech.aes_gmac = NULL;
+ }
+
if (server->secmech.ccmaesencrypt) {
crypto_free_aead(server->secmech.ccmaesencrypt);
server->secmech.ccmaesencrypt = NULL;
@@ -171,6 +171,7 @@ struct cifs_secmech {
struct sdesc *sdeschmacsha256; /* ctxt to generate smb2 signature */
struct sdesc *sdesccmacaes; /* ctxt to generate smb3 signature */
struct sdesc *sdescsha512; /* ctxt to generate smb3.11 signing key */
+ struct crypto_aead *aes_gmac; /* to generate SMB3.1.1 signature */
struct crypto_aead *ccmaesencrypt; /* smb3 encryption aead */
struct crypto_aead *ccmaesdecrypt; /* smb3 decryption aead */
};
@@ -704,7 +705,13 @@ struct TCP_Server_Info {
unsigned int max_write;
unsigned int min_offload;
__le16 compress_algorithm;
- __u16 signing_algorithm;
+ /*
+ * algorithm to be used to sign messages:
+ * SMB 2.x - HMAC-SHA256 (unsupported in SMB 3.1.1)
+ * SMB 3.0.x - AES-CMAC
+ * SMB 3.1.1 - AES-GMAC, if server negotiated it, or AES-CMAC otherwise
+ */
+ __u16 signing_algorithm;
__le16 cipher_type;
/* save initital negprot hash */
__u8 preauth_sha_hash[SMB2_PREAUTH_HASH_SIZE];
@@ -458,6 +458,8 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
* We need to allocate the server crypto now as we will need
* to sign packets before we generate the channel signing key
* (we sign with the session key)
+ *
+ * AES-GMAC secmech is allocated in smb311_update_preauth_hash() call.
*/
rc = smb311_crypto_shash_allocate(chan->server);
if (rc) {
@@ -854,6 +854,8 @@ smb2_handle_cancelled_mid(struct mid_q_entry *mid, struct TCP_Server_Info *serve
return rc;
}
+extern int smb311_aes_gmac_alloc(struct crypto_aead **);
+
/**
* smb311_update_preauth_hash - update @ses hash with the packet data in @iov
*
@@ -897,6 +899,10 @@ smb311_update_preauth_hash(struct cifs_ses *ses, struct TCP_Server_Info *server,
return 0;
ok:
+ rc = smb311_aes_gmac_alloc(&server->secmech.aes_gmac);
+ if (rc)
+ return rc;
+
rc = smb311_crypto_shash_allocate(server);
if (rc)
return rc;
@@ -12,6 +12,7 @@
#include <linux/uuid.h>
#include <linux/sort.h>
#include <crypto/aead.h>
+#include <crypto/gcm.h>
#include <linux/fiemap.h>
#include <uapi/linux/magic.h>
#include "cifsfs.h"
@@ -4221,21 +4222,52 @@ static inline void smb2_sg_set_buf(struct scatterlist *sg, const void *buf,
sg_set_page(sg, addr, buflen, offset_in_page(buf));
}
-/* Assumes the first rqst has a transform header as the first iov.
- * I.e.
- * rqst[0].rq_iov[0] is transform header
- * rqst[0].rq_iov[1+] data to be encrypted/decrypted
- * rqst[1+].rq_iov[0+] data to be encrypted/decrypted
+/*
+ * Initialize a scatterlist for encrypt/decrypt/sign (AES-GMAC) operations
+ *
+ * @num_rqst: Number of requests that will be transformed. Note that this is always 1 for AES-GMAC
+ * signing.
+ * @rqst: Points to the first request to be transformed. Note that this is always a single request
+ * for AES-GMAC signing, that might contain 1 or more iovs.
+ * @sig: Points to a caller-allocated buffer that will hold the computed signature.
+ * @crypt: If true, indicates that this SG list is for a encryption/decryption transform. If false,
+ * this is an AES-GMAC signing operation. See 'skip' variable.
+ *
+ * General notes for callers:
+ *
+ * - The crypto API fully supports using the same SG list for encrypt/decrypt operations,i.e. it
+ * encrypts/decrypts the plain/cipher text in src SG and then copies the result into dst SG,
+ * where src == dst. The AAD buffer are should not be modified
+ * - If it's desired to use 2 different SGs, one for src, another for dst, make sure they have the
+ * same layout, and same AAD/text/signature sizes
+ * - It's ok to have a hole/pad in the SG, if required, but make sure to account for its size when
+ * setting crypt len/AAD len. Also, there should be no NULL buffers. init_sg() checks for that
+ * when looping through the iovs, and will return ERR_PTR(-EIO) in case a NULL iov is found.
+ *
+ * Notes for encrypt/decrypt:
+ * - Assumes the first rqst has a transform header as the first iov, i.e.:
+ * - rqst[0].rq_iov[0]: Transform header. The first 20 bytes of the transform header are not
+ * part of the encrypted blob (see 'skip' variable)
+ * - rqst[0].rq_iov[1+]: Data to be encrypted/decrypted
+ * - rqst[1+].rq_iov[0+]: Data to be encrypted/decrypted
+ *
+ * Notes for AES-GMAC signing:
+ * - @num_rqst is always 1
+ * - 'skip' variable must be 0 (rqst[0].rq_iov[0] is smb2_hdr already)
+ * - The memory layout is slightly different from encrypt/decrypt:
+ * crypt: [ AAD (20 bytes) | plain/cipher text (iovs, variable length) | signature buffer (16 bytes) ]
+ * sign: [ AAD (iovs, variable length) | empty plaintext (0 bytes, not NULL) | signature buffer (16 bytes) ]
+ *
+ * Return: On success, returns an SG filled with the iovs from @rqst. On
+ * failure, returns ERR_PTR(errno).
*/
-static struct scatterlist *
-init_sg(int num_rqst, struct smb_rqst *rqst, u8 *sign)
+static struct scatterlist *init_sg(int num_rqst, struct smb_rqst *rqst,
+ u8 *sig, bool crypt)
{
unsigned int sg_len;
struct scatterlist *sg;
- unsigned int i;
- unsigned int j;
- unsigned int idx = 0;
- int skip;
+ unsigned int i, j, idx = 0;
+ int skip = 0;
sg_len = 1;
for (i = 0; i < num_rqst; i++)
@@ -4243,20 +4275,26 @@ init_sg(int num_rqst, struct smb_rqst *rqst, u8 *sign)
sg = kmalloc_array(sg_len, sizeof(struct scatterlist), GFP_KERNEL);
if (!sg)
- return NULL;
+ return ERR_PTR(-ENOMEM);
sg_init_table(sg, sg_len);
+
+ /*
+ * initializes the plain/cipher text buffers for encrypt/decrypt, or
+ * the AAD area for signing
+ */
for (i = 0; i < num_rqst; i++) {
for (j = 0; j < rqst[i].rq_nvec; j++) {
- /*
- * The first rqst has a transform header where the
- * first 20 bytes are not part of the encrypted blob
- */
- skip = (i == 0) && (j == 0) ? 20 : 0;
+ if (unlikely(!rqst[i].rq_iov[j].iov_base))
+ return ERR_PTR(-EIO);
+
+ if (crypt)
+ skip = (i == 0) && (j == 0) ? 20 : 0;
+
smb2_sg_set_buf(&sg[idx++],
rqst[i].rq_iov[j].iov_base + skip,
rqst[i].rq_iov[j].iov_len - skip);
- }
+ }
for (j = 0; j < rqst[i].rq_npages; j++) {
unsigned int len, offset;
@@ -4265,7 +4303,10 @@ init_sg(int num_rqst, struct smb_rqst *rqst, u8 *sign)
sg_set_page(&sg[idx++], rqst[i].rq_pages[j], len, offset);
}
}
- smb2_sg_set_buf(&sg[idx], sign, SMB2_SIGNATURE_SIZE);
+
+ /* initialize signature buffer */
+ smb2_sg_set_buf(&sg[idx], sig, SMB2_SIGNATURE_SIZE);
+
return sg;
}
@@ -4293,6 +4334,178 @@ smb2_get_enc_key(struct TCP_Server_Info *server, __u64 ses_id, int enc, u8 *key)
return -EAGAIN;
}
+
+/**
+ * smb311_crypt_sign() - Encrypts, decrypts, or sign an SMB2 message using AES-GCM algorithm.
+ * @rqst: SMB2 request to transform.
+ * @num_rqst: Number of requests to transform. Must be 1 if @sign_only is true.
+ * @enc: True for an encryption operation, false for decryption. If both @enc and @sign_only are
+ * true, assumes an encryption operation and set @sign_only to false.
+ * @sign_only: True if the request must only have the signature computed.
+ * @tfm: AES-GCM crypto transformation object. Must be allocated and freed by the caller.
+ * @key: The private key to be used for the operation. Must be allocated and freed by the caller.
+ * @keylen: The size of @key. Must be 16 for AES-128-GCM crypt ops and AES-GMAC, or 32 for
+ * AES-256-GCM.
+ * @iv: The Initialization Vector, a.k.a. nonce. Must be allocated and freed by the caller.
+ * @assoclen: Size of the Additional Authenticated Data (AAD) (or Associated Data (AD)). Must be
+ * size of smb2_transform_hdr - 20 for encryption/decryption, and the size of the whole
+ * SMB2 message for signing (e.g gotten from smb_rqst_len()).
+ * @cryptlen: Size of the plain/cipher text buffer. Must be 0 if @sign_only is true.
+ *
+ * This function is shared between the SMB 3.1.1 AES-GCM encryption/decryption operations
+ * (crypt_message()), and AES-GMAC signing operation (smb311_calc_aes_gmac()).
+ *
+ * This function will perform the core operations (encrypt and decrypt) using the parameters passed
+ * by the callers, which is what differ encrypt/decrypt ops from signing ops.
+ *
+ * Note that signing functionality (@sign_only == true) must only be used when the request must NOT
+ * be encrypted, as encrypted requests will have their own signatures, but computed differently.
+ *
+ * References:
+ * MS-SMB2 3.2.4.1.1 "Signing the Message"
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+static int smb311_crypt_sign(struct smb_rqst *rqst, int num_rqst, int enc,
+ bool sign_only, struct crypto_aead *tfm, u8 *key,
+ unsigned int keylen, u8 *iv, unsigned int assoclen,
+ unsigned int cryptlen)
+{
+ struct smb2_hdr *shdr = (struct smb2_hdr *)rqst[0].rq_iov[0].iov_base;
+ struct smb2_transform_hdr *tr_hdr =
+ (struct smb2_transform_hdr *)rqst[0].rq_iov[0].iov_base;
+ u8 sig[SMB2_SIGNATURE_SIZE] = { 0 };
+ struct aead_request *aead_req;
+ DECLARE_CRYPTO_WAIT(wait);
+ struct scatterlist *sg;
+ int rc = 0;
+
+ /* basic checks */
+ if (!rqst || !tfm || !key || !iv)
+ return -EINVAL;
+
+ if (unlikely(!rqst))
+ return -ENODATA;
+
+ /* signing checks */
+ if (sign_only) {
+ /*
+ * !enc && !sign_only == decrypt, but enc && sign_only is
+ * invalid.
+ *
+ * Warn the user, but continue normally assuming this is an
+ * encryption operation.
+ *
+ * (this is probably a bug from the caller)
+ */
+ if (unlikely(enc)) {
+ pr_warn_once("%s: cannot have enc == true and sign_only == true, assuming "
+ "encrypt\n", __func__);
+ sign_only = false;
+ goto check_key;
+ }
+
+ /* signing is done on single requests only */
+ if (num_rqst > 1) {
+ cifs_dbg(VFS, "%s: invalid number of requests to sign '%u', expected 1\n",
+ __func__, num_rqst);
+ return -EINVAL;
+ }
+
+ if (unlikely(assoclen == 0)) {
+ cifs_dbg(FYI, "%s: assoclen is 0 for signing operation\n", __func__);
+ return -ENODATA;
+ }
+
+ if (unlikely(cryptlen > 0)) {
+ cifs_dbg(FYI, "%s: AES-GMAC signing must have cryptlen 0, got '%u'\n",
+ __func__, cryptlen);
+ return -EINVAL;
+ }
+ } else {
+ /* crypt checks */
+ if (unlikely(assoclen != sizeof(struct smb2_transform_hdr) - 20)) {
+ cifs_dbg(FYI, "%s: invalid assoclen '%u' for %scrypt operation\n",
+ __func__, assoclen, enc ? "en" : "de");
+ return -EINVAL;
+ }
+
+ if (unlikely(cryptlen == 0)) {
+ cifs_dbg(FYI, "%s: empty cryptlen for %scrypt operation\n", __func__,
+ enc ? "en" : "de");
+ return -ENODATA;
+ }
+ }
+
+check_key:
+ if (keylen != SMB3_GCM128_CRYPTKEY_SIZE && /* 16 bytes, for AES-GMAC too */
+ keylen != SMB3_GCM256_CRYPTKEY_SIZE) { /* 32 bytes */
+ cifs_dbg(FYI, "%s: invalid key size '%u'\n", __func__, keylen);
+ return -EINVAL;
+ }
+
+ rc = crypto_aead_setkey(tfm, key, keylen);
+ if (rc) {
+ cifs_dbg(VFS, "%s: Failed to set AEAD key, rc=%d\n", __func__, rc);
+ return rc;
+ }
+
+ rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE);
+ if (rc) {
+ cifs_dbg(VFS, "%s: Failed to set AEAD authsize, rc=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ aead_req = aead_request_alloc(tfm, GFP_KERNEL);
+ if (!aead_req) {
+ cifs_dbg(VFS, "%s: Failed to alloc AEAD request\n", __func__);
+ return -ENOMEM;
+ }
+
+ /* decrypting */
+ if (!enc && !sign_only) {
+ memcpy(sig, &tr_hdr->Signature, SMB2_SIGNATURE_SIZE);
+ cryptlen += SMB2_SIGNATURE_SIZE;
+ }
+
+ sg = init_sg(num_rqst, rqst, sig, !sign_only);
+ if (IS_ERR(sg)) {
+ rc = PTR_ERR(sg);
+ cifs_dbg(VFS, "%s: Failed to init SG, rc=%d\n", __func__, rc);
+ goto out_free_req;
+ }
+
+ aead_request_set_crypt(aead_req, sg, sg, cryptlen, iv);
+ aead_request_set_ad(aead_req, assoclen);
+ aead_request_set_callback(aead_req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+ crypto_req_done, &wait);
+
+ /*
+ * Note for AES-GMAC (@sign_only): whether signing or verifying a signature, we must
+ * always use the encrypt function, as AES-GCM decrypt will internally try to match the
+ * authentication codes, which were computed based on the ciphertext, and fail (-EBADMSG),
+ * as expected.
+ */
+ if (enc || sign_only)
+ rc = crypto_wait_req(crypto_aead_encrypt(aead_req), &wait);
+ else
+ rc = crypto_wait_req(crypto_aead_decrypt(aead_req), &wait);
+
+ if (!rc) {
+ if (enc)
+ memcpy(&tr_hdr->Signature, sig, SMB2_SIGNATURE_SIZE);
+ else if (sign_only)
+ memcpy(&shdr->Signature, sig, SMB2_SIGNATURE_SIZE);
+ }
+
+ kfree(sg);
+out_free_req:
+ kfree(aead_req);
+
+ return rc;
+}
+
/*
* Encrypt or decrypt @rqst message. @rqst[0] has the following format:
* iov[0] - transform header (associate data),
@@ -4306,17 +4519,16 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
{
struct smb2_transform_hdr *tr_hdr =
(struct smb2_transform_hdr *)rqst[0].rq_iov[0].iov_base;
- unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20;
- int rc = 0;
- struct scatterlist *sg;
- u8 sign[SMB2_SIGNATURE_SIZE] = {};
+ unsigned int assoclen, cryptlen;
u8 key[SMB3_ENC_DEC_KEY_SIZE];
- struct aead_request *req;
- char *iv;
- unsigned int iv_len;
- DECLARE_CRYPTO_WAIT(wait);
struct crypto_aead *tfm;
- unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
+ unsigned int iv_len;
+ unsigned int keylen;
+ char *iv;
+ int rc = 0;
+
+ assoclen = sizeof(struct smb2_transform_hdr) - 20;
+ cryptlen = le32_to_cpu(tr_hdr->OriginalMessageSize);
rc = smb2_get_enc_key(server, le64_to_cpu(tr_hdr->SessionId), enc, key);
if (rc) {
@@ -4332,49 +4544,24 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
}
tfm = enc ? server->secmech.ccmaesencrypt :
- server->secmech.ccmaesdecrypt;
-
- if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) ||
- (server->cipher_type == SMB2_ENCRYPTION_AES256_GCM))
- rc = crypto_aead_setkey(tfm, key, SMB3_GCM256_CRYPTKEY_SIZE);
- else
- rc = crypto_aead_setkey(tfm, key, SMB3_GCM128_CRYPTKEY_SIZE);
-
- if (rc) {
- cifs_server_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc);
- return rc;
- }
-
- rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE);
- if (rc) {
- cifs_server_dbg(VFS, "%s: Failed to set authsize %d\n", __func__, rc);
- return rc;
- }
-
- req = aead_request_alloc(tfm, GFP_KERNEL);
- if (!req) {
- cifs_server_dbg(VFS, "%s: Failed to alloc aead request\n", __func__);
- return -ENOMEM;
- }
-
- if (!enc) {
- memcpy(sign, &tr_hdr->Signature, SMB2_SIGNATURE_SIZE);
- crypt_len += SMB2_SIGNATURE_SIZE;
+ server->secmech.ccmaesdecrypt;
+ /* sanity check -- shouldn't happen */
+ if (unlikely(!tfm)) {
+ cifs_server_dbg(FYI, "%s: AEAD TFM is NULL\n", __func__);
+ return -EIO;
}
- sg = init_sg(num_rqst, rqst, sign);
- if (!sg) {
- cifs_server_dbg(VFS, "%s: Failed to init sg\n", __func__);
- rc = -ENOMEM;
- goto free_req;
- }
+ if (server->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
+ server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
+ keylen = SMB3_GCM256_CRYPTKEY_SIZE;
+ else
+ keylen = SMB3_GCM128_CRYPTKEY_SIZE;
iv_len = crypto_aead_ivsize(tfm);
iv = kzalloc(iv_len, GFP_KERNEL);
if (!iv) {
cifs_server_dbg(VFS, "%s: Failed to alloc iv\n", __func__);
- rc = -ENOMEM;
- goto free_sg;
+ return -ENOMEM;
}
if ((server->cipher_type == SMB2_ENCRYPTION_AES128_GCM) ||
@@ -4385,23 +4572,158 @@ crypt_message(struct TCP_Server_Info *server, int num_rqst,
memcpy(iv + 1, (char *)tr_hdr->Nonce, SMB3_AES_CCM_NONCE);
}
- aead_request_set_crypt(req, sg, sg, crypt_len, iv);
- aead_request_set_ad(req, assoc_data_len);
+ rc = smb311_crypt_sign(rqst, num_rqst, enc, false, tfm, key, keylen,
+ iv, assoclen, cryptlen);
+ if (rc)
+ cifs_server_dbg(VFS, "%s: Failed to %scrypt request, rc=%d\n",
+ __func__, enc ? "en" : "de", rc);
- aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
- crypto_req_done, &wait);
+ kfree(iv);
+ return rc;
+}
- rc = crypto_wait_req(enc ? crypto_aead_encrypt(req)
- : crypto_aead_decrypt(req), &wait);
+/**
+ * smb311_aes_gmac_nonce() - Setup AES-GMAC nonce.
+ * @shdr: SMB2 header containing the MessageId and the Command of the request.
+ * @is_server: True if the request came from the server (i.e. we're verifying a signature), false
+ * if the request is being sent from the client (i.e. we're signing a request to be
+ * sent).
+ * @out_nonce: Output buffer to put the computed nonce. Must be allocated and freed by the caller.
+ * @*out_nonce will be allocated here, and, on success, must be freed by the caller.
+ *
+ * Allocates @*out_nonce and fill it with the nonce computed.
+ *
+ * MS-SMB2 3.1.4.1 "Signing An Outgoing Message", item 2:
+ *
+ * If Connection.SigningAlgorithmId is AES-GMAC, Nonce specified in [RFC4543], MUST be initialized
+ * to 12 bytes with the following syntax:
+ * - First 8 bytes are set to MessageId.
+ * - Following 4 bytes are set as follows: If the sender is a client, least significant bit is
+ * set to zero, otherwise set to 1. If the message is SMB2 CANCEL request, the penultimate bit
+ * is set to 1, otherwise set to zero. Remaining 30 bits are set to zero.
+ *
+ * References:
+ * MS-SMB2 3.1.4.1 "Signing An Outgoing Message"
+ * RFC 4543 "GMAC in IPsec ESP and AH", section 3.2 "Nonce Format"
+ * https://www.ietf.org/rfc/rfc4543.txt
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+static int smb311_aes_gmac_nonce(struct smb2_hdr *shdr, bool is_server,
+ u8 **out_nonce)
+{
+ struct {
+ /* for MessageId (8 bytes) */
+ __le64 mid;
+ /* for role (client or server) and if SMB2 CANCEL (4 bytes) */
+ __le32 role;
+ } __packed nonce;
+
+ if (!shdr || !out_nonce)
+ return -EINVAL;
- if (!rc && enc)
- memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);
+ *out_nonce = kzalloc(SMB3_AES_GCM_NONCE, GFP_KERNEL);
+ if (!*out_nonce)
+ return -ENOMEM;
- kfree(iv);
-free_sg:
- kfree(sg);
-free_req:
- kfree(req);
+ memset(&nonce, 0, SMB3_AES_GCM_NONCE);
+
+ /* note that nonce must always be little endian */
+ nonce.mid = shdr->MessageId;
+
+ /* request is coming from the server */
+ if (is_server)
+ set_bit(0, (unsigned long *)&nonce.role);
+
+ /* set penultimate bit if SMB2_CANCEL command */
+ if (shdr->Command == SMB2_CANCEL)
+ set_bit(1, (unsigned long *)&nonce.role);
+
+ memcpy(*out_nonce, (u8 *)&nonce, SMB3_AES_GCM_NONCE);
+
+ return 0;
+}
+
+extern int smb2_get_sign_key(__u64, struct TCP_Server_Info *, u8 *);
+
+/**
+ * smb311_calc_aes_gmac() - Calculate the signature for a request using AES-GMAC algorithm.
+ * @rqst: SMB2 request to be signed.
+ * @server: Server pointer that holds the secmech to be used.
+ * @verify: If true, compute the nonce considering it came from the server.
+ *
+ * This function implements AES-GMAC signing for SMB2 messages as described in MS-SMB2
+ * specification. This algorithm is only supported on SMB 3.1.1.
+ *
+ * For our purposes, AES-GMAC is AES-128-GCM, but without encrypting anything. IOW, this is what's
+ * done:
+ * - set an Additional Authenticated Data (AAD) buffer, with the contents of the SMB2 request,
+ * starting from the SMB2 header, and the length of rq_nvec + rq_npages
+ * - set plaintext buffer to have a 0 length, so AES-GCM will not encrypt anything
+ * - set a signature buffer, where AES-GCM will place the signature computed from the AAD buffer
+ *
+ * Most of that is done in smb311_crypt_sign().
+ *
+ * Note: even though Microsoft mentions RFC4543 in MS-SMB2, the mechanism used _must_ be the "raw"
+ * AES-128-GCM. RFC4543 is designed for IPsec Encapsulating Security Payload (ESP) and
+ * Authentication Header (AH). Trying to use "rfc(gcm(aes)))" as the AEAD algorithm will fail the
+ * signature calculation.
+ *
+ * References:
+ * MS-SMB2 3.1.4.1 "Signing An Outgoing Message"
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int smb311_calc_aes_gmac(struct smb_rqst *rqst, struct TCP_Server_Info *server,
+ bool verify)
+{
+ u8 key[SMB3_SIGN_KEY_SIZE] = { 0 };
+ struct crypto_aead *tfm = NULL;
+ struct smb2_hdr *shdr;
+ unsigned int assoclen;
+ u8 *nonce = NULL;
+ int rc = 0;
+
+ /* allocated in smb311_update_preauth_hash() */
+ tfm = server->secmech.aes_gmac;
+ /* sanity check -- shouldn't happen */
+ if (unlikely(!tfm)) {
+ cifs_server_dbg(FYI, "%s: AES-GMAC TFM is NULL\n", __func__);
+ return -EIO;
+ }
+
+ shdr = (struct smb2_hdr *)rqst->rq_iov[0].iov_base;
+ memset(shdr->Signature, 0, SMB2_SIGNATURE_SIZE);
+
+ rc = smb2_get_sign_key(le64_to_cpu(shdr->SessionId), server, key);
+ if (rc) {
+ cifs_server_dbg(VFS, "%s: Could not get AES-GMAC signing key, rc=%d\n", __func__,
+ rc);
+ goto out;
+ }
+
+ rc = smb311_aes_gmac_nonce(shdr, verify, &nonce);
+ if (rc) {
+ cifs_server_dbg(VFS, "%s: Could not get AES-GMAC nonce, rc=%d\n", __func__, rc);
+ goto out;
+ }
+
+ /*
+ * Set the Additional Authenticated Data (AAD)/Associated Data (AD) length to the SMB
+ * request length, which corresponds to the part of the buffer we want to sign/authenticate.
+ */
+ assoclen = smb_rqst_len(server, rqst);
+
+ /*
+ * Use 0 for cryptlen because we're not interested in encrypting, but only computing the
+ * authentication tag (signature) of the AAD buffer.
+ */
+ rc = smb311_crypt_sign(rqst, 1, false, true, tfm, key, SMB3_SIGN_KEY_SIZE, nonce, assoclen, 0);
+ if (rc)
+ cifs_server_dbg(VFS, "%s: Failed to compute AES-GMAC signature for request, rc=%d\n",
+ __func__, rc);
+ kfree(nonce);
+out:
return rc;
}
@@ -5458,7 +5780,7 @@ struct smb_version_operations smb30_operations = {
.set_lease_key = smb2_set_lease_key,
.new_lease_key = smb2_new_lease_key,
.generate_signingkey = generate_smb30signingkey,
- .calc_signature = smb3_calc_signature,
+ .calc_signature = smb3_calc_aes_cmac,
.set_integrity = smb3_set_integrity,
.is_read_op = smb21_is_read_op,
.set_oplock_level = smb3_set_oplock_level,
@@ -5572,7 +5894,11 @@ struct smb_version_operations smb311_operations = {
.set_lease_key = smb2_set_lease_key,
.new_lease_key = smb2_new_lease_key,
.generate_signingkey = generate_smb311signingkey,
- .calc_signature = smb3_calc_signature,
+ /*
+ * .calc_signature is replaced by smb311_calc_aes_gmac if AES-GMAC
+ * gets negotiated with the server.
+ */
+ .calc_signature = smb3_calc_aes_cmac,
.set_integrity = smb3_set_integrity,
.is_read_op = smb21_is_read_op,
.set_oplock_level = smb3_set_oplock_level,
@@ -460,7 +460,7 @@ static unsigned int
build_signing_ctxt(struct smb2_signing_capabilities *pneg_ctxt)
{
unsigned int ctxt_len = sizeof(struct smb2_signing_capabilities);
- unsigned short num_algs = 1; /* number of signing algorithms sent */
+ unsigned short num_algs = 2; /* number of signing algorithms sent */
pneg_ctxt->ContextType = SMB2_SIGNING_CAPABILITIES;
/*
@@ -469,14 +469,25 @@ build_signing_ctxt(struct smb2_signing_capabilities *pneg_ctxt)
pneg_ctxt->DataLength = cpu_to_le16(DIV_ROUND_UP(
sizeof(struct smb2_signing_capabilities) -
sizeof(struct smb2_neg_context) +
- (num_algs * 2 /* sizeof u16 */), 8) * 8);
+ (num_algs * sizeof(u16)), 8) * 8);
pneg_ctxt->SigningAlgorithmCount = cpu_to_le16(num_algs);
- pneg_ctxt->SigningAlgorithms[0] = cpu_to_le16(SIGNING_ALG_AES_CMAC);
- ctxt_len += 2 /* sizeof le16 */ * num_algs;
+ /*
+ * MS-SMB2 2.2.3.1.7:
+ * These IDs MUST be in an order such that the most preferred
+ * signing algorithm MUST be at the beginning of the array and least
+ * preferred signing algorithm at the end of the array.
+ *
+ * Hint the server that we prefer AES-GMAC, but there's no guarantee
+ * it'll be used, e.g. server might not support it.
+ */
+ pneg_ctxt->SigningAlgorithms[0] = SIGNING_ALG_AES_GMAC_LE;
+ pneg_ctxt->SigningAlgorithms[1] = SIGNING_ALG_AES_CMAC_LE;
+ /* SMB 3.1.1 doesn't accept HMAC-SHA256, so no need to send it */
+
+ ctxt_len += sizeof(__le16) * num_algs;
ctxt_len = DIV_ROUND_UP(ctxt_len, 8) * 8;
return ctxt_len;
- /* TBD add SIGNING_ALG_AES_GMAC and/or SIGNING_ALG_HMAC_SHA256 */
}
static void
@@ -613,7 +624,6 @@ assemble_neg_contexts(struct smb2_negotiate_req *req,
/* check for and add transport_capabilities and signing capabilities */
req->NegotiateContextCount = cpu_to_le16(neg_context_count);
-
}
static void decode_preauth_context(struct smb2_preauth_neg_context *ctxt)
@@ -702,30 +712,49 @@ static int decode_encrypt_ctx(struct TCP_Server_Info *server,
return 0;
}
+/* XXX: maybe move this somewhere else? */
+static const char *smb3_signing_algo_str(u16 algo)
+{
+ switch (algo) {
+ case SIGNING_ALG_AES_CMAC: return "AES-CMAC";
+ case SIGNING_ALG_AES_GMAC: return "AES-GMAC";
+ /* HMAC-SHA256 is unused in SMB3+ context */
+ default: return "unknown";
+ }
+}
+
static void decode_signing_ctx(struct TCP_Server_Info *server,
struct smb2_signing_capabilities *pctxt)
{
unsigned int len = le16_to_cpu(pctxt->DataLength);
if ((len < 4) || (len > 16)) {
- pr_warn_once("server sent bad signing negcontext\n");
+ pr_warn_once("server sent bad signing negcontext, len=%u\n", len);
return;
}
+
if (le16_to_cpu(pctxt->SigningAlgorithmCount) != 1) {
- pr_warn_once("Invalid signing algorithm count\n");
+ pr_warn_once("invalid signing algorithm count '%u'\n",
+ le16_to_cpu(pctxt->SigningAlgorithmCount));
return;
}
- if (le16_to_cpu(pctxt->SigningAlgorithms[0]) > 2) {
- pr_warn_once("unknown signing algorithm\n");
+
+ if (le16_to_cpu(pctxt->SigningAlgorithms[0]) > SIGNING_ALG_AES_GMAC) {
+ pr_warn_once("unknown signing algorithm '%u'\n",
+ le16_to_cpu(pctxt->SigningAlgorithms[0]));
return;
}
server->signing_negotiated = true;
server->signing_algorithm = le16_to_cpu(pctxt->SigningAlgorithms[0]);
- cifs_dbg(FYI, "signing algorithm %d chosen\n",
- server->signing_algorithm);
-}
+ if (server->signing_algorithm == SIGNING_ALG_AES_GMAC)
+ server->ops->calc_signature = smb311_calc_aes_gmac;
+ /* AES-CMAC is already the default, in case AES-GMAC wasn't negotiated */
+
+ cifs_dbg(FYI, "negotiated signing algorithm '%s'\n",
+ smb3_signing_algo_str(server->signing_algorithm));
+}
static int smb311_decode_neg_context(struct smb2_negotiate_rsp *rsp,
struct TCP_Server_Info *server,
@@ -745,6 +774,9 @@ static int smb311_decode_neg_context(struct smb2_negotiate_rsp *rsp,
len_of_ctxts = len_of_smb - offset;
+ /* ensure this is false before decoding */
+ server->signing_negotiated = false;
+
for (i = 0; i < ctxt_cnt; i++) {
int clen;
/* check that offset is not beyond end of SMB */
@@ -784,6 +816,21 @@ static int smb311_decode_neg_context(struct smb2_negotiate_rsp *rsp,
offset += clen + sizeof(struct smb2_neg_context);
len_of_ctxts -= clen;
}
+
+ /*
+ * Throw a warning if user requested signing to be negotiated, but it
+ * wasn't.
+ *
+ * Some servers will not send a SMB2_SIGNING_CAPABILITIES context (*),
+ * so we use AES-CMAC (default in smb311 ops) as it is expected to be
+ * accepted (e.g. only Windows Server 2022 supports AES-GMAC)
+ *
+ * (*) see note "<125> Section 3.2.4.2.2.2" in MS-SMB2
+ */
+ if (!server->signing_negotiated && enable_negotiate_signing)
+ cifs_dbg(VFS, "signing capabilities were not negotiated, using "
+ "AES-CMAC for message signing\n");
+
return rc;
}
@@ -931,6 +978,15 @@ SMB2_negotiate(const unsigned int xid,
if (ses->chan_max > 1)
req->Capabilities |= cpu_to_le32(SMB2_GLOBAL_CAP_MULTI_CHANNEL);
+ /* set default settings for signing */
+ if (server->vals->protocol_id >= SMB30_PROT_ID) {
+ server->signing_algorithm = SIGNING_ALG_AES_CMAC;
+ server->ops->calc_signature = smb3_calc_aes_cmac;
+ } else if (server->vals->protocol_id >= SMB20_PROT_ID) {
+ server->signing_algorithm = SIGNING_ALG_HMAC_SHA256;
+ /* ->calc_signature is already set to smb2_calc_signature */
+ }
+
/* ClientGUID must be zero for SMB2.02 dialect */
if (server->vals->protocol_id == SMB20_PROT_ID)
memset(req->ClientGUID, 0, SMB2_CLIENT_GUID_SIZE);
@@ -1078,11 +1134,15 @@ SMB2_negotiate(const unsigned int xid,
}
if (rsp->DialectRevision == cpu_to_le16(SMB311_PROT_ID)) {
- if (rsp->NegotiateContextCount)
+ if (rsp->NegotiateContextCount) {
rc = smb311_decode_neg_context(rsp, server,
rsp_iov.iov_len);
- else
+ } else {
cifs_server_dbg(VFS, "Missing expected negotiate contexts\n");
+ cifs_server_dbg(VFS, "Using default signing algorithm (AES-CMAC)\n");
+ server->signing_algorithm = SIGNING_ALG_AES_CMAC;
+ server->ops->calc_signature = smb3_calc_aes_cmac;
+ }
}
neg_exit:
free_rsp_buf(resp_buftype, rsp);
@@ -1387,8 +1447,7 @@ SMB2_sess_establish_session(struct SMB2_sess_data *sess_data)
if (server->ops->generate_signingkey) {
rc = server->ops->generate_signingkey(ses, server);
if (rc) {
- cifs_dbg(FYI,
- "SMB3 session key generation failed\n");
+ cifs_dbg(FYI, "SMB3 session key generation failed\n");
cifs_server_unlock(server);
return rc;
}
@@ -44,9 +44,12 @@ extern struct cifs_tcon *smb2_find_smb_tcon(struct TCP_Server_Info *server,
extern int smb2_calc_signature(struct smb_rqst *rqst,
struct TCP_Server_Info *server,
bool allocate_crypto);
-extern int smb3_calc_signature(struct smb_rqst *rqst,
+extern int smb3_calc_aes_cmac(struct smb_rqst *rqst,
+ struct TCP_Server_Info *server,
+ bool allocate_crypto);
+extern int smb311_calc_aes_gmac(struct smb_rqst *rqst,
struct TCP_Server_Info *server,
- bool allocate_crypto);
+ bool alloc);
extern void smb2_echo_request(struct work_struct *work);
extern __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode);
extern bool smb2_is_valid_oplock_break(char *buffer,
@@ -76,8 +76,6 @@ smb311_crypto_shash_allocate(struct TCP_Server_Info *server)
return rc;
}
-
-static
int smb2_get_sign_key(__u64 ses_id, struct TCP_Server_Info *server, u8 *key)
{
struct cifs_chan *chan;
@@ -542,8 +540,8 @@ generate_smb311signingkey(struct cifs_ses *ses,
}
int
-smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
- bool allocate_crypto)
+smb3_calc_aes_cmac(struct smb_rqst *rqst, struct TCP_Server_Info *server,
+ bool allocate_crypto)
{
int rc;
unsigned char smb3_signature[SMB2_CMACAES_SIZE];
@@ -625,7 +623,6 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server,
static int
smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server)
{
- int rc = 0;
struct smb2_hdr *shdr;
struct smb2_sess_setup_req *ssr;
bool is_binding;
@@ -652,9 +649,7 @@ smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server)
return 0;
}
- rc = server->ops->calc_signature(rqst, server, false);
-
- return rc;
+ return server->ops->calc_signature(rqst, server, false);
}
int
@@ -668,6 +663,8 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
if ((shdr->Command == SMB2_NEGOTIATE) ||
(shdr->Command == SMB2_SESSION_SETUP) ||
(shdr->Command == SMB2_OPLOCK_BREAK) ||
+ (shdr->Command == 0xFFFFFFFFFFFFFFFF) || /* MS-SMB2 3.2.5.1.3 */
+ (shdr->Status == STATUS_PENDING) || /* MS-SMB2 3.2.5.1.3 */
server->ignore_signature ||
(!server->session_estab))
return 0;
@@ -677,21 +674,17 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
* server does not send one? BB
*/
- /* Do not need to verify session setups with signature "BSRSPYL " */
- if (memcmp(shdr->Signature, "BSRSPYL ", 8) == 0)
- cifs_dbg(FYI, "dummy signature received for smb command 0x%x\n",
- shdr->Command);
-
/*
* Save off the origiginal signature so we can modify the smb and check
* our calculated signature against what the server sent.
*/
memcpy(server_response_sig, shdr->Signature, SMB2_SIGNATURE_SIZE);
- memset(shdr->Signature, 0, SMB2_SIGNATURE_SIZE);
-
+ /*
+ * all implementations of ->calc_signature() will zero shdr->Signature
+ * before computing it
+ */
rc = server->ops->calc_signature(rqst, server, true);
-
if (rc)
return rc;
@@ -699,8 +692,9 @@ smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
cifs_dbg(VFS, "sign fail cmd 0x%x message id 0x%llx\n",
shdr->Command, shdr->MessageId);
return -EACCES;
- } else
- return 0;
+ }
+
+ return 0;
}
/*
@@ -843,7 +837,23 @@ smb2_setup_request(struct cifs_ses *ses, struct TCP_Server_Info *server,
int rc;
struct smb2_hdr *shdr =
(struct smb2_hdr *)rqst->rq_iov[0].iov_base;
+ struct smb2_transform_hdr *trhdr =
+ (struct smb2_transform_hdr *)rqst->rq_iov[0].iov_base;
struct mid_q_entry *mid;
+ bool is_encrypted;
+
+ /*
+ * Client must not sign the request is encrypted.
+ *
+ * Note: we can't rely on SMB2_SESSION_FLAG_ENCRYPT_DATA or
+ * SMB2_GLOBAL_CAP_ENCRYPTION here because they might be set, but not
+ * being actively used (e.g. not mounted with "seal"). So we just check
+ * if the request header is a transform header.
+ *
+ * References:
+ * MS-SMB2 3.2.4.1.1
+ */
+ is_encrypted = (trhdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM);
smb2_seq_num_into_buf(server, shdr);
@@ -853,11 +863,13 @@ smb2_setup_request(struct cifs_ses *ses, struct TCP_Server_Info *server,
return ERR_PTR(rc);
}
- rc = smb2_sign_rqst(rqst, server);
- if (rc) {
- revert_current_mid_from_hdr(server, shdr);
- delete_mid(mid);
- return ERR_PTR(rc);
+ if (!is_encrypted) {
+ rc = smb2_sign_rqst(rqst, server);
+ if (rc) {
+ revert_current_mid_from_hdr(server, shdr);
+ delete_mid(mid);
+ return ERR_PTR(rc);
+ }
}
return mid;
@@ -897,6 +909,35 @@ smb2_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst)
return mid;
}
+int smb311_aes_gmac_alloc(struct crypto_aead **tfm)
+{
+ int rc = 0;
+
+ if (!tfm)
+ return -EIO;
+
+ /*
+ * This is unlikely as we only call this once per TCP session in
+ * smb311_update_preauth_hash(). If *tfm is already allocated, this
+ * is probably a bug.
+ *
+ * XXX: rc == 0 here, maybe return an error here instead?
+ */
+ if (unlikely(*tfm))
+ return rc;
+
+ *tfm = crypto_alloc_aead("gcm(aes)", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(*tfm)) {
+ rc = PTR_ERR(*tfm);
+
+ cifs_dbg(VFS, "%s: Failed to alloc AES-GMAC AEAD, rc=%d\n",
+ __func__, rc);
+ *tfm = NULL;
+ }
+
+ return rc;
+}
+
int
smb3_crypto_aead_allocate(struct TCP_Server_Info *server)
{
This patch implements the support for AES-GMAC message signing, as specified in MS-SMB2 3.1.4.1 "Signing An Outgoing Message". The core function is smb311_calc_aes_gmac(), which is to be used as the ->calc_signature op when a signing negotiate context (SMB2_SIGNING_CAPABILITIES) has been sent ("enable_negotiate_signing" module parameter) to the server and replied to with SIGNING_ALG_AES_GMAC (i.e. the server supports it). If "enable_negotiate_signing" is false (default) or if the server reply with SIGNING_ALG_AES_CMAC, ->calc_signature will point to smb3_calc_aes_cmac() (this was renamed from smb3_calc_signature() to better identify which functions are dealing with which algorithms). Since, in the crypto API context, AES-GMAC is not a hashing algorithm, but rather AES-128-GCM with an empty plaintext buffer, the following modifications were made: - Introduce smb311_crypt_sign() to accommodate the common code for encrypt/decrypt and signing operations that deals with the crypto API - crypt_message() has been modified to adopt smb311_crypt_sign() usage; no change of behaviour whatsoever - init_sg() now takes a bool argument 'crypt' to indicate when it's initializing an SG list for crypt operations, or set to false when it's for initializing it for AES-GMAC signing. This is needed because crypt operations use a transform header, and it needs to skip the first 20 bytes of the first iov. For AES-GMAC signing this is not needed because iov[0] is already the beginning of the message - Introduce smb311_aes_gmac_alloc() to allocate the secmech. This is called only once in smb311_update_preauth_hash(). The TFM has the lifetime of the TCP session, so it's only free when the TCP session is put() (via cifs_crypto_secmech_release()) - Introduce smb311_aes_gmac_nonce() to produce the nonce for AES-GMAC signing (as per MS-SMB2 3.1.4.1, item 2) More implementation-specific information can be found in the kernel-doc comments for the introduced functions. Other smaller modifications: - Check if the request is encrypted in smb2_setup_request(), as we must not sign a message if it is (MS-SMB2 3.2.4.1.1) -- AES-GCM and AES-CCM will generate their signatures based on the ciphertext (AES-GCM) or the plaintext (AES-CCM) - smb2_verify_signature(): - Remove extra call to memset to zero the header signature as this is already done in the beginning ->calc_signature() implementations - Remove useless call to check for "BSRSPYL" signature as it's SMB1 only - Add checks for if command is 0xFFFFFFFFFFFFFFFF or if status is STATUS_PENDING (MS-SMB2 3.2.5.1.3) - Remove useless variable from smb2_sign_rqst() - smb2_get_sign_key() is no longer static as it's now used by smb311_calc_aes_gmac() - Use sizeof u16/__le16 in build_signing_ctxt() instead of hardcoded values Signed-off-by: Enzo Matsumiya <ematsumiya@suse.de> --- fs/cifs/cifsencrypt.c | 5 + fs/cifs/cifsglob.h | 9 +- fs/cifs/sess.c | 2 + fs/cifs/smb2misc.c | 6 + fs/cifs/smb2ops.c | 484 +++++++++++++++++++++++++++++++++------- fs/cifs/smb2pdu.c | 93 ++++++-- fs/cifs/smb2proto.h | 7 +- fs/cifs/smb2transport.c | 87 ++++++-- 8 files changed, 571 insertions(+), 122 deletions(-)