diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /libcli/smb/smb2_signing.c | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | libcli/smb/smb2_signing.c | 1110 |
1 files changed, 1110 insertions, 0 deletions
diff --git a/libcli/smb/smb2_signing.c b/libcli/smb/smb2_signing.c new file mode 100644 index 0000000..94ff51f --- /dev/null +++ b/libcli/smb/smb2_signing.c @@ -0,0 +1,1110 @@ +/* + Unix SMB/CIFS implementation. + SMB2 signing + + Copyright (C) Stefan Metzmacher 2009 + + 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 "system/filesys.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#define SMB2_SIGNING_KEY_GNUTLS_TYPES 1 +#include "../libcli/smb/smb_common.h" +#include "../lib/crypto/crypto.h" +#include "lib/util/iov_buf.h" + +#include "lib/crypto/gnutls_helpers.h" + +void smb2_signing_derivations_fill_const_stack(struct smb2_signing_derivations *ds, + enum protocol_types protocol, + const DATA_BLOB preauth_hash) +{ + *ds = (struct smb2_signing_derivations) { .signing = NULL, }; + + if (protocol >= PROTOCOL_SMB3_11) { + struct smb2_signing_derivation *d = NULL; + + SMB_ASSERT(preauth_hash.length != 0); + + d = &ds->__signing; + ds->signing = d; + d->label = data_blob_string_const_null("SMBSigningKey"); + d->context = preauth_hash; + + d = &ds->__cipher_c2s; + ds->cipher_c2s = d; + d->label = data_blob_string_const_null("SMBC2SCipherKey"); + d->context = preauth_hash; + + d = &ds->__cipher_s2c; + ds->cipher_s2c = d; + d->label = data_blob_string_const_null("SMBS2CCipherKey"); + d->context = preauth_hash; + + d = &ds->__application; + ds->application = d; + d->label = data_blob_string_const_null("SMBAppKey"); + d->context = preauth_hash; + + } else if (protocol >= PROTOCOL_SMB3_00) { + struct smb2_signing_derivation *d = NULL; + + d = &ds->__signing; + ds->signing = d; + d->label = data_blob_string_const_null("SMB2AESCMAC"); + d->context = data_blob_string_const_null("SmbSign"); + + d = &ds->__cipher_c2s; + ds->cipher_c2s = d; + d->label = data_blob_string_const_null("SMB2AESCCM"); + d->context = data_blob_string_const_null("ServerIn "); + + d = &ds->__cipher_s2c; + ds->cipher_s2c = d; + d->label = data_blob_string_const_null("SMB2AESCCM"); + d->context = data_blob_string_const_null("ServerOut"); + + d = &ds->__application; + ds->application = d; + d->label = data_blob_string_const_null("SMB2APP"); + d->context = data_blob_string_const_null("SmbRpc"); + } +} + +static int smb2_signing_key_destructor(struct smb2_signing_key *key) +{ + if (key->hmac_hnd != NULL) { + gnutls_hmac_deinit(key->hmac_hnd, NULL); + key->hmac_hnd = NULL; + } + + if (key->cipher_hnd != NULL) { + gnutls_aead_cipher_deinit(key->cipher_hnd); + key->cipher_hnd = NULL; + } + + return 0; +} + +NTSTATUS smb2_signing_key_copy(TALLOC_CTX *mem_ctx, + const struct smb2_signing_key *src, + struct smb2_signing_key **_dst) +{ + struct smb2_signing_key *dst = NULL; + + dst = talloc_zero(mem_ctx, struct smb2_signing_key); + if (dst == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(dst, smb2_signing_key_destructor); + + dst->sign_algo_id = src->sign_algo_id; + dst->cipher_algo_id = src->cipher_algo_id; + + if (src->blob.length == 0) { + *_dst = dst; + return NT_STATUS_OK; + } + + dst->blob = data_blob_talloc_zero(dst, src->blob.length); + if (dst->blob.length == 0) { + TALLOC_FREE(dst); + return NT_STATUS_NO_MEMORY; + } + talloc_keep_secret(dst->blob.data); + memcpy(dst->blob.data, src->blob.data, dst->blob.length); + + *_dst = dst; + return NT_STATUS_OK; +} + +static NTSTATUS smb2_signing_key_create(TALLOC_CTX *mem_ctx, + uint16_t sign_algo_id, + uint16_t cipher_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key) +{ + struct smb2_signing_key *key = NULL; + size_t in_key_length = 16; + size_t out_key_length = 16; + NTSTATUS status; + + if (sign_algo_id != SMB2_SIGNING_INVALID_ALGO) { + SMB_ASSERT(cipher_algo_id == SMB2_ENCRYPTION_INVALID_ALGO); + } + if (cipher_algo_id != SMB2_ENCRYPTION_INVALID_ALGO) { + SMB_ASSERT(sign_algo_id == SMB2_SIGNING_INVALID_ALGO); + } + + key = talloc_zero(mem_ctx, struct smb2_signing_key); + if (key == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(key, smb2_signing_key_destructor); + + key->sign_algo_id = sign_algo_id; + key->cipher_algo_id = cipher_algo_id; + + if (master_key == NULL) { + SMB_ASSERT(d == NULL); + + *_key = key; + return NT_STATUS_OK; + } + + /* + * Per default use the full key. + */ + in_key_length = out_key_length = master_key->length; + switch (sign_algo_id) { + case SMB2_SIGNING_INVALID_ALGO: + /* + * This means we're processing cipher_algo_id below + */ + break; + case SMB2_SIGNING_MD5_SMB1: + SMB_ASSERT(d == NULL); + break; + case SMB2_SIGNING_HMAC_SHA256: + case SMB2_SIGNING_AES128_CMAC: + case SMB2_SIGNING_AES128_GMAC: + /* + * signing keys are padded or truncated to + * 16 bytes. + * + * Even with master_key->length = 0, + * we need to use 16 zeros. + */ + in_key_length = out_key_length = 16; + break; + default: + DBG_ERR("sign_algo_id[%u] not supported\n", sign_algo_id); + return NT_STATUS_HMAC_NOT_SUPPORTED; + } + switch (cipher_algo_id) { + case SMB2_ENCRYPTION_INVALID_ALGO: + /* + * This means we're processing sign_algo_id above + */ + break; + case SMB2_ENCRYPTION_NONE: + /* + * No encryption negotiated. + */ + break; + case SMB2_ENCRYPTION_AES128_CCM: + case SMB2_ENCRYPTION_AES128_GCM: + /* + * encryption keys are padded or truncated to + * 16 bytes. + */ + if (master_key->length == 0) { + DBG_ERR("cipher_algo_id[%u] without key\n", + cipher_algo_id); + return NT_STATUS_NO_USER_SESSION_KEY; + } + in_key_length = out_key_length = 16; + break; + case SMB2_ENCRYPTION_AES256_CCM: + case SMB2_ENCRYPTION_AES256_GCM: + /* + * AES256 uses the available input and + * generated a 32 byte encryption key. + */ + if (master_key->length == 0) { + DBG_ERR("cipher_algo_id[%u] without key\n", + cipher_algo_id); + return NT_STATUS_NO_USER_SESSION_KEY; + } + out_key_length = 32; + break; + default: + DBG_ERR("cipher_algo_id[%u] not supported\n", cipher_algo_id); + return NT_STATUS_FWP_INCOMPATIBLE_CIPHER_CONFIG; + } + + if (out_key_length == 0) { + *_key = key; + return NT_STATUS_OK; + } + + key->blob = data_blob_talloc_zero(key, out_key_length); + if (key->blob.length == 0) { + TALLOC_FREE(key); + return NT_STATUS_NO_MEMORY; + } + talloc_keep_secret(key->blob.data); + memcpy(key->blob.data, + master_key->data, + MIN(key->blob.length, master_key->length)); + + if (d == NULL) { + *_key = key; + return NT_STATUS_OK; + } + + status = samba_gnutls_sp800_108_derive_key(key->blob.data, + in_key_length, + NULL, + 0, + d->label.data, + d->label.length, + d->context.data, + d->context.length, + GNUTLS_MAC_SHA256, + key->blob.data, + out_key_length); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(key); + return status; + } + + *_key = key; + return NT_STATUS_OK; +} + +NTSTATUS smb2_signing_key_sign_create(TALLOC_CTX *mem_ctx, + uint16_t sign_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key) +{ + return smb2_signing_key_create(mem_ctx, + sign_algo_id, + SMB2_ENCRYPTION_INVALID_ALGO, + master_key, + d, + _key); +} + +NTSTATUS smb2_signing_key_cipher_create(TALLOC_CTX *mem_ctx, + uint16_t cipher_algo_id, + const DATA_BLOB *master_key, + const struct smb2_signing_derivation *d, + struct smb2_signing_key **_key) +{ + return smb2_signing_key_create(mem_ctx, + SMB2_SIGNING_INVALID_ALGO, + cipher_algo_id, + master_key, + d, + _key); +} + +bool smb2_signing_key_valid(const struct smb2_signing_key *key) +{ + if (key == NULL) { + return false; + } + + if (key->blob.length == 0 || key->blob.data == NULL) { + return false; + } + + return true; +} + +static NTSTATUS smb2_signing_gmac(gnutls_aead_cipher_hd_t cipher_hnd, + const uint8_t *iv, size_t iv_size, + const giovec_t *auth_iov, uint8_t auth_iovcnt, + uint8_t *tag, size_t _tag_size) +{ + size_t tag_size = _tag_size; + int rc; + + rc = gnutls_aead_cipher_encryptv2(cipher_hnd, + iv, iv_size, + auth_iov, auth_iovcnt, + NULL, 0, + tag, &tag_size); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + return NT_STATUS_OK; +} + +static NTSTATUS smb2_signing_calc_signature(struct smb2_signing_key *signing_key, + uint16_t sign_algo_id, + const struct iovec *vector, + int count, + uint8_t signature[16]) +{ + const uint8_t *hdr = (uint8_t *)vector[0].iov_base; + uint16_t opcode; + uint32_t flags; + uint64_t msg_id; + static const uint8_t zero_sig[16] = { 0, }; + gnutls_mac_algorithm_t hmac_algo = GNUTLS_MAC_UNKNOWN; + int i; + + /* + * We expect + * - SMB2 HDR + * - SMB2 BODY FIXED + * - (optional) SMB2 BODY DYN + * - (optional) PADDING + */ + SMB_ASSERT(count >= 2); + SMB_ASSERT(vector[0].iov_len == SMB2_HDR_BODY); + SMB_ASSERT(count <= 4); + + opcode = SVAL(hdr, SMB2_HDR_OPCODE); + flags = IVAL(hdr, SMB2_HDR_FLAGS); + if (flags & SMB2_HDR_FLAG_REDIRECT) { + NTSTATUS pdu_status = NT_STATUS(IVAL(hdr, SMB2_HDR_STATUS)); + if (NT_STATUS_EQUAL(pdu_status, NT_STATUS_PENDING)) { + DBG_ERR("opcode[%u] NT_STATUS_PENDING\n", opcode); + return NT_STATUS_INTERNAL_ERROR; + } + if (opcode == SMB2_OP_CANCEL) { + DBG_ERR("SMB2_OP_CANCEL response should not be signed\n"); + return NT_STATUS_INTERNAL_ERROR; + } + } + msg_id = BVAL(hdr, SMB2_HDR_MESSAGE_ID); + if (msg_id == 0) { + if (opcode != SMB2_OP_CANCEL || + sign_algo_id >= SMB2_SIGNING_AES128_GMAC) + { + DBG_ERR("opcode[%u] msg_id == 0\n", opcode); + return NT_STATUS_INTERNAL_ERROR; + } + /* + * Legacy algorithms allow MID 0 + * for cancel requests + */ + } + if (msg_id == UINT64_MAX) { + DBG_ERR("opcode[%u] msg_id == UINT64_MAX\n", opcode); + return NT_STATUS_INTERNAL_ERROR; + } + + switch (sign_algo_id) { + case SMB2_SIGNING_AES128_GMAC: { + gnutls_cipher_algorithm_t algo = GNUTLS_CIPHER_AES_128_GCM; + uint32_t key_size = gnutls_cipher_get_key_size(algo); + uint32_t iv_size = gnutls_cipher_get_iv_size(algo); + size_t tag_size = gnutls_cipher_get_tag_size(algo); + gnutls_datum_t key = { + .data = signing_key->blob.data, + .size = MIN(signing_key->blob.length, key_size), + }; + uint64_t high_bits = 0; + uint8_t iv[AES_BLOCK_SIZE] = {0}; + giovec_t auth_iov[count+1]; + size_t auth_iovcnt = 0; + NTSTATUS status; + int rc; + + high_bits = flags & SMB2_HDR_FLAG_REDIRECT; + if (opcode == SMB2_OP_CANCEL) { + high_bits |= SMB2_HDR_FLAG_ASYNC; + } + SBVAL(iv, 0, msg_id); + SBVAL(iv, 8, high_bits); + + if (signing_key->cipher_hnd == NULL) { + rc = gnutls_aead_cipher_init(&signing_key->cipher_hnd, + algo, + &key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + SMB_ASSERT(key_size == 16); + SMB_ASSERT(iv_size == 12); + SMB_ASSERT(tag_size == 16); + + auth_iov[auth_iovcnt++] = (giovec_t) { + .iov_base = discard_const_p(uint8_t, hdr), + .iov_len = SMB2_HDR_SIGNATURE, + }; + auth_iov[auth_iovcnt++] = (giovec_t) { + .iov_base = discard_const_p(uint8_t, zero_sig), + .iov_len = 16, + }; + for (i=1; i < count; i++) { + auth_iov[auth_iovcnt++] = (giovec_t) { + .iov_base = discard_const_p(uint8_t, vector[i].iov_base), + .iov_len = vector[i].iov_len, + }; + } + + status = smb2_signing_gmac(signing_key->cipher_hnd, + iv, + iv_size, + auth_iov, + auth_iovcnt, + signature, + tag_size); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; + } break; + + case SMB2_SIGNING_AES128_CMAC: + hmac_algo = GNUTLS_MAC_AES_CMAC_128; + break; + case SMB2_SIGNING_HMAC_SHA256: + hmac_algo = GNUTLS_MAC_SHA256; + break; + + default: + return NT_STATUS_HMAC_NOT_SUPPORTED; + } + + if (hmac_algo != GNUTLS_MAC_UNKNOWN) { + uint8_t digest[gnutls_hmac_get_len(hmac_algo)]; + gnutls_datum_t key = { + .data = signing_key->blob.data, + .size = MIN(signing_key->blob.length, 16), + }; + int rc; + + if (signing_key->hmac_hnd == NULL) { + rc = gnutls_hmac_init(&signing_key->hmac_hnd, + hmac_algo, + key.data, + key.size); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + rc = gnutls_hmac(signing_key->hmac_hnd, hdr, SMB2_HDR_SIGNATURE); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(signing_key->hmac_hnd, zero_sig, 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + + for (i = 1; i < count; i++) { + rc = gnutls_hmac(signing_key->hmac_hnd, + vector[i].iov_base, + vector[i].iov_len); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + gnutls_hmac_output(signing_key->hmac_hnd, digest); + memcpy(signature, digest, 16); + ZERO_ARRAY(digest); + return NT_STATUS_OK; + } + + return NT_STATUS_HMAC_NOT_SUPPORTED; +} + +NTSTATUS smb2_signing_sign_pdu(struct smb2_signing_key *signing_key, + struct iovec *vector, + int count) +{ + uint16_t sign_algo_id; + uint8_t *hdr; + uint64_t session_id; + uint8_t res[16]; + NTSTATUS status; + + /* + * We expect + * - SMB2 HDR + * - SMB2 BODY FIXED + * - (optional) SMB2 BODY DYN + * - (optional) PADDING + */ + SMB_ASSERT(count >= 2); + SMB_ASSERT(vector[0].iov_len == SMB2_HDR_BODY); + SMB_ASSERT(count <= 4); + + hdr = (uint8_t *)vector[0].iov_base; + + session_id = BVAL(hdr, SMB2_HDR_SESSION_ID); + if (session_id == 0) { + /* + * do not sign messages with a zero session_id. + * See MS-SMB2 3.2.4.1.1 + */ + return NT_STATUS_OK; + } + + if (!smb2_signing_key_valid(signing_key)) { + DBG_WARNING("No signing key for SMB2 signing\n"); + return NT_STATUS_ACCESS_DENIED; + } + + memset(hdr + SMB2_HDR_SIGNATURE, 0, 16); + + SIVAL(hdr, SMB2_HDR_FLAGS, IVAL(hdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_SIGNED); + + sign_algo_id = signing_key->sign_algo_id; + + status = smb2_signing_calc_signature(signing_key, + sign_algo_id, + vector, + count, + res); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("smb2_signing_calc_signature(sign_algo_id=%u) - %s\n", + (unsigned)sign_algo_id, nt_errstr(status)); + if (NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)) { + smb_panic(__location__); + } + return status; + } + + DEBUG(5,("signed SMB2 message (sign_algo_id=%u)\n", + (unsigned)sign_algo_id)); + + memcpy(hdr + SMB2_HDR_SIGNATURE, res, 16); + + return NT_STATUS_OK; +} + +NTSTATUS smb2_signing_check_pdu(struct smb2_signing_key *signing_key, + const struct iovec *vector, + int count) +{ + uint16_t sign_algo_id; + const uint8_t *hdr; + const uint8_t *sig; + uint64_t session_id; + uint8_t res[16]; + NTSTATUS status; + + /* + * We expect + * - SMB2 HDR + * - SMB2 BODY FIXED + * - (optional) SMB2 BODY DYN + * - (optional) PADDING + */ + SMB_ASSERT(count >= 2); + SMB_ASSERT(vector[0].iov_len == SMB2_HDR_BODY); + SMB_ASSERT(count <= 4); + + hdr = (const uint8_t *)vector[0].iov_base; + + session_id = BVAL(hdr, SMB2_HDR_SESSION_ID); + if (session_id == 0) { + /* + * do not sign messages with a zero session_id. + * See MS-SMB2 3.2.4.1.1 + */ + return NT_STATUS_OK; + } + + if (!smb2_signing_key_valid(signing_key)) { + /* we don't have the session key yet */ + return NT_STATUS_OK; + } + + sig = hdr+SMB2_HDR_SIGNATURE; + + sign_algo_id = signing_key->sign_algo_id; + + status = smb2_signing_calc_signature(signing_key, + sign_algo_id, + vector, + count, + res); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("smb2_signing_calc_signature(sign_algo_id=%u) - %s\n", + (unsigned)sign_algo_id, nt_errstr(status)); + if (NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_ERROR)) { + status = NT_STATUS_ACCESS_DENIED; + } + return status; + } + + if (!mem_equal_const_time(res, sig, 16)) { + DEBUG(0,("Bad SMB2 (sign_algo_id=%u) signature for message\n", + (unsigned)sign_algo_id)); + dump_data(0, sig, 16); + dump_data(0, res, 16); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +NTSTATUS smb2_signing_encrypt_pdu(struct smb2_signing_key *encryption_key, + struct iovec *vector, + int count) +{ + bool use_encryptv2 = false; + uint16_t cipher_id; + uint8_t *tf; + size_t a_total; + ssize_t m_total; + uint32_t iv_size = 0; + uint32_t key_size = 0; + size_t tag_size = 0; + gnutls_cipher_algorithm_t algo = 0; + gnutls_datum_t key; + gnutls_datum_t iv; + NTSTATUS status; + int rc; + + if (count < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (vector[0].iov_len != SMB2_TF_HDR_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + + tf = (uint8_t *)vector[0].iov_base; + + if (!smb2_signing_key_valid(encryption_key)) { + DBG_WARNING("No encryption key for SMB2 signing\n"); + return NT_STATUS_ACCESS_DENIED; + } + cipher_id = encryption_key->cipher_algo_id; + + a_total = SMB2_TF_HDR_SIZE - SMB2_TF_NONCE; + + m_total = iov_buflen(&vector[1], count-1); + if (m_total == -1) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + SSVAL(tf, SMB2_TF_FLAGS, SMB2_TF_FLAGS_ENCRYPTED); + SIVAL(tf, SMB2_TF_MSG_SIZE, m_total); + + switch (cipher_id) { + case SMB2_ENCRYPTION_AES128_CCM: + algo = GNUTLS_CIPHER_AES_128_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES128_GCM: + algo = GNUTLS_CIPHER_AES_128_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); + use_encryptv2 = true; + break; + case SMB2_ENCRYPTION_AES256_CCM: + algo = GNUTLS_CIPHER_AES_256_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES256_GCM: + algo = GNUTLS_CIPHER_AES_256_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); + use_encryptv2 = true; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + key_size = gnutls_cipher_get_key_size(algo); + tag_size = gnutls_cipher_get_tag_size(algo); + + if (key_size != encryption_key->blob.length) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (tag_size != 16) { + return NT_STATUS_INTERNAL_ERROR; + } + + key = (gnutls_datum_t) { + .data = encryption_key->blob.data, + .size = key_size, + }; + + iv = (gnutls_datum_t) { + .data = tf + SMB2_TF_NONCE, + .size = iv_size, + }; + + if (encryption_key->cipher_hnd == NULL) { + rc = gnutls_aead_cipher_init(&encryption_key->cipher_hnd, + algo, + &key); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + } + + memset(tf + SMB2_TF_NONCE + iv_size, + 0, + 16 - iv_size); + + if (use_encryptv2) { + uint8_t tag[tag_size]; + giovec_t auth_iov[1]; + + auth_iov[0] = (giovec_t) { + .iov_base = tf + SMB2_TF_NONCE, + .iov_len = a_total, + }; + + rc = gnutls_aead_cipher_encryptv2(encryption_key->cipher_hnd, + iv.data, + iv.size, + auth_iov, + 1, + &vector[1], + count - 1, + tag, + &tag_size); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + + memcpy(tf + SMB2_TF_SIGNATURE, tag, tag_size); + } else + { + size_t ptext_size = m_total; + uint8_t *ptext = NULL; + size_t ctext_size = m_total + tag_size; + uint8_t *ctext = NULL; + size_t len = 0; + int i; + TALLOC_CTX *tmp_ctx = NULL; + + /* + * If we come from python bindings, we don't have a stackframe + * around, so use the NULL context. + * + * This is fine as we make sure we free the memory. + */ + if (talloc_stackframe_exists()) { + tmp_ctx = talloc_tos(); + } + + ptext = talloc_size(tmp_ctx, ptext_size); + if (ptext == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + ctext = talloc_size(tmp_ctx, ctext_size); + if (ctext == NULL) { + TALLOC_FREE(ptext); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + for (i = 1; i < count; i++) { + if (vector[i].iov_base != NULL) { + memcpy(ptext + len, + vector[i].iov_base, + vector[i].iov_len); + } + + len += vector[i].iov_len; + if (len > ptext_size) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + } + + rc = gnutls_aead_cipher_encrypt(encryption_key->cipher_hnd, + iv.data, + iv.size, + tf + SMB2_TF_NONCE, + a_total, + tag_size, + ptext, + ptext_size, + ctext, + &ctext_size); + if (rc < 0 || ctext_size != m_total + tag_size) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + + len = 0; + for (i = 1; i < count; i++) { + if (vector[i].iov_base != NULL) { + memcpy(vector[i].iov_base, + ctext + len, + vector[i].iov_len); + } + + len += vector[i].iov_len; + } + + memcpy(tf + SMB2_TF_SIGNATURE, ctext + m_total, tag_size); + + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + } + + DBG_INFO("Encrypted SMB2 message\n"); + + status = NT_STATUS_OK; +out: + return status; +} + +NTSTATUS smb2_signing_decrypt_pdu(struct smb2_signing_key *decryption_key, + struct iovec *vector, + int count) +{ + bool use_encryptv2 = false; + uint16_t cipher_id; + uint8_t *tf; + uint16_t flags; + size_t a_total; + ssize_t m_total; + uint32_t msg_size = 0; + uint32_t iv_size = 0; + uint32_t key_size = 0; + size_t tag_size = 0; + gnutls_cipher_algorithm_t algo = 0; + gnutls_datum_t key; + gnutls_datum_t iv; + NTSTATUS status; + int rc; + + if (count < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (vector[0].iov_len != SMB2_TF_HDR_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + + tf = (uint8_t *)vector[0].iov_base; + + if (!smb2_signing_key_valid(decryption_key)) { + DBG_WARNING("No decryption key for SMB2 signing\n"); + return NT_STATUS_ACCESS_DENIED; + } + cipher_id = decryption_key->cipher_algo_id; + + a_total = SMB2_TF_HDR_SIZE - SMB2_TF_NONCE; + + m_total = iov_buflen(&vector[1], count-1); + if (m_total == -1) { + return NT_STATUS_BUFFER_TOO_SMALL; + } + + flags = SVAL(tf, SMB2_TF_FLAGS); + msg_size = IVAL(tf, SMB2_TF_MSG_SIZE); + + if (flags != SMB2_TF_FLAGS_ENCRYPTED) { + return NT_STATUS_ACCESS_DENIED; + } + + if (msg_size != m_total) { + return NT_STATUS_INTERNAL_ERROR; + } + + switch (cipher_id) { + case SMB2_ENCRYPTION_AES128_CCM: + algo = GNUTLS_CIPHER_AES_128_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES128_GCM: + algo = GNUTLS_CIPHER_AES_128_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); + use_encryptv2 = true; + break; + case SMB2_ENCRYPTION_AES256_CCM: + algo = GNUTLS_CIPHER_AES_256_CCM; + iv_size = SMB2_AES_128_CCM_NONCE_SIZE; +#ifdef ALLOW_GNUTLS_AEAD_CIPHER_ENCRYPTV2_AES_CCM + use_encryptv2 = true; +#endif + break; + case SMB2_ENCRYPTION_AES256_GCM: + algo = GNUTLS_CIPHER_AES_256_GCM; + iv_size = gnutls_cipher_get_iv_size(algo); + use_encryptv2 = true; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + key_size = gnutls_cipher_get_key_size(algo); + tag_size = gnutls_cipher_get_tag_size(algo); + + if (key_size != decryption_key->blob.length) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (tag_size != 16) { + return NT_STATUS_INTERNAL_ERROR; + } + + key = (gnutls_datum_t) { + .data = decryption_key->blob.data, + .size = key_size, + }; + + iv = (gnutls_datum_t) { + .data = tf + SMB2_TF_NONCE, + .size = iv_size, + }; + + if (decryption_key->cipher_hnd == NULL) { + rc = gnutls_aead_cipher_init(&decryption_key->cipher_hnd, + algo, + &key); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + } + + if (use_encryptv2) { + giovec_t auth_iov[1]; + + auth_iov[0] = (giovec_t) { + .iov_base = tf + SMB2_TF_NONCE, + .iov_len = a_total, + }; + + rc = gnutls_aead_cipher_decryptv2(decryption_key->cipher_hnd, + iv.data, + iv.size, + auth_iov, + 1, + &vector[1], + count - 1, + tf + SMB2_TF_SIGNATURE, + tag_size); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + } else + { + size_t ctext_size = m_total + tag_size; + uint8_t *ctext = NULL; + size_t ptext_size = m_total; + uint8_t *ptext = NULL; + size_t len = 0; + int i; + TALLOC_CTX *tmp_ctx = NULL; + + /* + * If we come from python bindings, we don't have a stackframe + * around, so use the NULL context. + * + * This is fine as we make sure we free the memory. + */ + if (talloc_stackframe_exists()) { + tmp_ctx = talloc_tos(); + } + + /* GnuTLS doesn't have a iovec API for decryption yet */ + + ptext = talloc_size(tmp_ctx, ptext_size); + if (ptext == NULL) { + status = NT_STATUS_NO_MEMORY; + goto out; + } + + ctext = talloc_size(tmp_ctx, ctext_size); + if (ctext == NULL) { + TALLOC_FREE(ptext); + status = NT_STATUS_NO_MEMORY; + goto out; + } + + + for (i = 1; i < count; i++) { + memcpy(ctext + len, + vector[i].iov_base, + vector[i].iov_len); + + len += vector[i].iov_len; + } + if (len != m_total) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = NT_STATUS_INTERNAL_ERROR; + goto out; + } + + memcpy(ctext + len, + tf + SMB2_TF_SIGNATURE, + tag_size); + + /* This function will verify the tag */ + rc = gnutls_aead_cipher_decrypt(decryption_key->cipher_hnd, + iv.data, + iv.size, + tf + SMB2_TF_NONCE, + a_total, + tag_size, + ctext, + ctext_size, + ptext, + &ptext_size); + if (rc < 0) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + if (ptext_size != m_total) { + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + rc = GNUTLS_E_SHORT_MEMORY_BUFFER; + status = gnutls_error_to_ntstatus(rc, NT_STATUS_INTERNAL_ERROR); + goto out; + } + + len = 0; + for (i = 1; i < count; i++) { + memcpy(vector[i].iov_base, + ptext + len, + vector[i].iov_len); + + len += vector[i].iov_len; + } + + TALLOC_FREE(ptext); + TALLOC_FREE(ctext); + } + + DBG_INFO("Decrypted SMB2 message\n"); + + status = NT_STATUS_OK; +out: + return status; +} |