// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *   Copyright (C) 2016 Namjae Jeon <linkinjeon@kernel.org>
 *   Copyright (C) 2018 Samsung Electronics Co., Ltd.
 */

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/backing-dev.h>
#include <linux/writeback.h>
#include <linux/uio.h>
#include <linux/xattr.h>
#include <crypto/hash.h>
#include <crypto/aead.h>
#include <crypto/md5.h>
#include <crypto/sha2.h>
#include <crypto/utils.h>
#include <linux/random.h>
#include <linux/scatterlist.h>

#include "auth.h"
#include "glob.h"

#include <linux/fips.h>
#include <crypto/arc4.h>
#include <crypto/des.h>

#include "server.h"
#include "smb_common.h"
#include "connection.h"
#include "mgmt/user_session.h"
#include "mgmt/user_config.h"
#include "crypto_ctx.h"
#include "transport_ipc.h"

/*
 * Fixed format data defining GSS header and fixed string
 * "not_defined_in_RFC4178@please_ignore".
 * So sec blob data in neg phase could be generated statically.
 */
static char NEGOTIATE_GSS_HEADER[AUTH_GSS_LENGTH] = {
#ifdef CONFIG_SMB_SERVER_KERBEROS5
	0x60, 0x5e, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05,
	0x05, 0x02, 0xa0, 0x54, 0x30, 0x52, 0xa0, 0x24,
	0x30, 0x22, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
	0xf7, 0x12, 0x01, 0x02, 0x02, 0x06, 0x09, 0x2a,
	0x86, 0x48, 0x82, 0xf7, 0x12, 0x01, 0x02, 0x02,
	0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82,
	0x37, 0x02, 0x02, 0x0a, 0xa3, 0x2a, 0x30, 0x28,
	0xa0, 0x26, 0x1b, 0x24, 0x6e, 0x6f, 0x74, 0x5f,
	0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x5f,
	0x69, 0x6e, 0x5f, 0x52, 0x46, 0x43, 0x34, 0x31,
	0x37, 0x38, 0x40, 0x70, 0x6c, 0x65, 0x61, 0x73,
	0x65, 0x5f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65
#else
	0x60, 0x48, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05,
	0x05, 0x02, 0xa0, 0x3e, 0x30, 0x3c, 0xa0, 0x0e,
	0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04,
	0x01, 0x82, 0x37, 0x02, 0x02, 0x0a, 0xa3, 0x2a,
	0x30, 0x28, 0xa0, 0x26, 0x1b, 0x24, 0x6e, 0x6f,
	0x74, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65,
	0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x52, 0x46, 0x43,
	0x34, 0x31, 0x37, 0x38, 0x40, 0x70, 0x6c, 0x65,
	0x61, 0x73, 0x65, 0x5f, 0x69, 0x67, 0x6e, 0x6f,
	0x72, 0x65
#endif
};

void ksmbd_copy_gss_neg_header(void *buf)
{
	memcpy(buf, NEGOTIATE_GSS_HEADER, AUTH_GSS_LENGTH);
}

static int calc_ntlmv2_hash(struct ksmbd_conn *conn, struct ksmbd_session *sess,
			    char *ntlmv2_hash, char *dname)
{
	int ret, len, conv_len;
	wchar_t *domain = NULL;
	__le16 *uniname = NULL;
	struct hmac_md5_ctx ctx;

	hmac_md5_init_usingrawkey(&ctx, user_passkey(sess->user),
				  CIFS_ENCPWD_SIZE);

	/* convert user_name to unicode */
	len = strlen(user_name(sess->user));
	uniname = kzalloc(2 + UNICODE_LEN(len), KSMBD_DEFAULT_GFP);
	if (!uniname) {
		ret = -ENOMEM;
		goto out;
	}

	conv_len = smb_strtoUTF16(uniname, user_name(sess->user), len,
				  conn->local_nls);
	if (conv_len < 0 || conv_len > len) {
		ret = -EINVAL;
		goto out;
	}
	UniStrupr(uniname);

	hmac_md5_update(&ctx, (const u8 *)uniname, UNICODE_LEN(conv_len));

	/* Convert domain name or conn name to unicode and uppercase */
	len = strlen(dname);
	domain = kzalloc(2 + UNICODE_LEN(len), KSMBD_DEFAULT_GFP);
	if (!domain) {
		ret = -ENOMEM;
		goto out;
	}

	conv_len = smb_strtoUTF16((__le16 *)domain, dname, len,
				  conn->local_nls);
	if (conv_len < 0 || conv_len > len) {
		ret = -EINVAL;
		goto out;
	}

	hmac_md5_update(&ctx, (const u8 *)domain, UNICODE_LEN(conv_len));
	hmac_md5_final(&ctx, ntlmv2_hash);
	ret = 0;
out:
	kfree(uniname);
	kfree(domain);
	return ret;
}

/**
 * ksmbd_auth_ntlmv2() - NTLMv2 authentication handler
 * @conn:		connection
 * @sess:		session of connection
 * @ntlmv2:		NTLMv2 challenge response
 * @blen:		NTLMv2 blob length
 * @domain_name:	domain name
 * @cryptkey:		session crypto key
 *
 * Return:	0 on success, error number on error
 */
int ksmbd_auth_ntlmv2(struct ksmbd_conn *conn, struct ksmbd_session *sess,
		      struct ntlmv2_resp *ntlmv2, int blen, char *domain_name,
		      char *cryptkey)
{
	char ntlmv2_hash[CIFS_ENCPWD_SIZE];
	char ntlmv2_rsp[CIFS_HMAC_MD5_HASH_SIZE];
	struct hmac_md5_ctx ctx;
	int rc;

	if (fips_enabled) {
		ksmbd_debug(AUTH, "NTLMv2 support is disabled due to FIPS\n");
		return -EOPNOTSUPP;
	}

	rc = calc_ntlmv2_hash(conn, sess, ntlmv2_hash, domain_name);
	if (rc) {
		ksmbd_debug(AUTH, "could not get v2 hash rc %d\n", rc);
		return rc;
	}

	hmac_md5_init_usingrawkey(&ctx, ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE);
	hmac_md5_update(&ctx, cryptkey, CIFS_CRYPTO_KEY_SIZE);
	hmac_md5_update(&ctx, (const u8 *)&ntlmv2->blob_signature, blen);
	hmac_md5_final(&ctx, ntlmv2_rsp);

	/* Generate the session key */
	hmac_md5_usingrawkey(ntlmv2_hash, CIFS_HMAC_MD5_HASH_SIZE,
			     ntlmv2_rsp, CIFS_HMAC_MD5_HASH_SIZE,
			     sess->sess_key);

	if (crypto_memneq(ntlmv2->ntlmv2_hash, ntlmv2_rsp,
			  CIFS_HMAC_MD5_HASH_SIZE))
		return -EINVAL;
	return 0;
}

/**
 * ksmbd_decode_ntlmssp_auth_blob() - helper function to construct
 * authenticate blob
 * @authblob:	authenticate blob source pointer
 * @blob_len:	length of the @authblob message
 * @conn:	connection
 * @sess:	session of connection
 *
 * Return:	0 on success, error number on error
 */
int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
				   int blob_len, struct ksmbd_conn *conn,
				   struct ksmbd_session *sess)
{
	char *domain_name;
	unsigned int nt_off, dn_off;
	unsigned short nt_len, dn_len;
	int ret;

	if (blob_len < sizeof(struct authenticate_message)) {
		ksmbd_debug(AUTH, "negotiate blob len %d too small\n",
			    blob_len);
		return -EINVAL;
	}

	if (memcmp(authblob->Signature, "NTLMSSP", 8)) {
		ksmbd_debug(AUTH, "blob signature incorrect %s\n",
			    authblob->Signature);
		return -EINVAL;
	}

	nt_off = le32_to_cpu(authblob->NtChallengeResponse.BufferOffset);
	nt_len = le16_to_cpu(authblob->NtChallengeResponse.Length);
	dn_off = le32_to_cpu(authblob->DomainName.BufferOffset);
	dn_len = le16_to_cpu(authblob->DomainName.Length);

	if (blob_len < (u64)dn_off + dn_len || blob_len < (u64)nt_off + nt_len ||
	    nt_len < CIFS_ENCPWD_SIZE)
		return -EINVAL;

	/* TODO : use domain name that imported from configuration file */
	domain_name = smb_strndup_from_utf16((const char *)authblob + dn_off,
					     dn_len, true, conn->local_nls);
	if (IS_ERR(domain_name))
		return PTR_ERR(domain_name);

	/* process NTLMv2 authentication */
	ksmbd_debug(AUTH, "decode_ntlmssp_authenticate_blob dname%s\n",
		    domain_name);
	ret = ksmbd_auth_ntlmv2(conn, sess,
				(struct ntlmv2_resp *)((char *)authblob + nt_off),
				nt_len - CIFS_ENCPWD_SIZE,
				domain_name, conn->ntlmssp.cryptkey);
	kfree(domain_name);

	/* The recovered secondary session key */
	if (conn->ntlmssp.client_flags & NTLMSSP_NEGOTIATE_KEY_XCH) {
		struct arc4_ctx *ctx_arc4;
		unsigned int sess_key_off, sess_key_len;

		sess_key_off = le32_to_cpu(authblob->SessionKey.BufferOffset);
		sess_key_len = le16_to_cpu(authblob->SessionKey.Length);

		if (blob_len < (u64)sess_key_off + sess_key_len)
			return -EINVAL;

		if (sess_key_len > CIFS_KEY_SIZE)
			return -EINVAL;

		ctx_arc4 = kmalloc_obj(*ctx_arc4, KSMBD_DEFAULT_GFP);
		if (!ctx_arc4)
			return -ENOMEM;

		arc4_setkey(ctx_arc4, sess->sess_key, SMB2_NTLMV2_SESSKEY_SIZE);
		arc4_crypt(ctx_arc4, sess->sess_key,
			   (char *)authblob + sess_key_off, sess_key_len);
		kfree_sensitive(ctx_arc4);
	}

	return ret;
}

/**
 * ksmbd_decode_ntlmssp_neg_blob() - helper function to construct
 * negotiate blob
 * @negblob: negotiate blob source pointer
 * @blob_len:	length of the @authblob message
 * @conn:	connection
 *
 */
int ksmbd_decode_ntlmssp_neg_blob(struct negotiate_message *negblob,
				  int blob_len, struct ksmbd_conn *conn)
{
	if (blob_len < sizeof(struct negotiate_message)) {
		ksmbd_debug(AUTH, "negotiate blob len %d too small\n",
			    blob_len);
		return -EINVAL;
	}

	if (memcmp(negblob->Signature, "NTLMSSP", 8)) {
		ksmbd_debug(AUTH, "blob signature incorrect %s\n",
			    negblob->Signature);
		return -EINVAL;
	}

	conn->ntlmssp.client_flags = le32_to_cpu(negblob->NegotiateFlags);
	return 0;
}

/**
 * ksmbd_build_ntlmssp_challenge_blob() - helper function to construct
 * challenge blob
 * @chgblob: challenge blob source pointer to initialize
 * @conn:	connection
 *
 */
unsigned int
ksmbd_build_ntlmssp_challenge_blob(struct challenge_message *chgblob,
				   struct ksmbd_conn *conn)
{
	struct target_info *tinfo;
	wchar_t *name;
	__u8 *target_name;
	unsigned int flags, blob_off, blob_len, type, target_info_len = 0;
	int len, uni_len, conv_len;
	int cflags = conn->ntlmssp.client_flags;

	memcpy(chgblob->Signature, NTLMSSP_SIGNATURE, 8);
	chgblob->MessageType = NtLmChallenge;

	flags = NTLMSSP_NEGOTIATE_UNICODE |
		NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_TARGET_TYPE_SERVER |
		NTLMSSP_NEGOTIATE_TARGET_INFO;

	if (cflags & NTLMSSP_NEGOTIATE_SIGN) {
		flags |= NTLMSSP_NEGOTIATE_SIGN;
		flags |= cflags & (NTLMSSP_NEGOTIATE_128 |
				   NTLMSSP_NEGOTIATE_56);
	}

	if (cflags & NTLMSSP_NEGOTIATE_SEAL && smb3_encryption_negotiated(conn))
		flags |= NTLMSSP_NEGOTIATE_SEAL;

	if (cflags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)
		flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN;

	if (cflags & NTLMSSP_REQUEST_TARGET)
		flags |= NTLMSSP_REQUEST_TARGET;

	if (conn->use_spnego &&
	    (cflags & NTLMSSP_NEGOTIATE_EXTENDED_SEC))
		flags |= NTLMSSP_NEGOTIATE_EXTENDED_SEC;

	if (cflags & NTLMSSP_NEGOTIATE_KEY_XCH)
		flags |= NTLMSSP_NEGOTIATE_KEY_XCH;

	chgblob->NegotiateFlags = cpu_to_le32(flags);
	len = strlen(ksmbd_netbios_name());
	name = kmalloc(2 + UNICODE_LEN(len), KSMBD_DEFAULT_GFP);
	if (!name)
		return -ENOMEM;

	conv_len = smb_strtoUTF16((__le16 *)name, ksmbd_netbios_name(), len,
				  conn->local_nls);
	if (conv_len < 0 || conv_len > len) {
		kfree(name);
		return -EINVAL;
	}

	uni_len = UNICODE_LEN(conv_len);

	blob_off = sizeof(struct challenge_message);
	blob_len = blob_off + uni_len;

	chgblob->TargetName.Length = cpu_to_le16(uni_len);
	chgblob->TargetName.MaximumLength = cpu_to_le16(uni_len);
	chgblob->TargetName.BufferOffset = cpu_to_le32(blob_off);

	/* Initialize random conn challenge */
	get_random_bytes(conn->ntlmssp.cryptkey, sizeof(__u64));
	memcpy(chgblob->Challenge, conn->ntlmssp.cryptkey,
	       CIFS_CRYPTO_KEY_SIZE);

	/* Add Target Information to security buffer */
	chgblob->TargetInfoArray.BufferOffset = cpu_to_le32(blob_len);

	target_name = (__u8 *)chgblob + blob_off;
	memcpy(target_name, name, uni_len);
	tinfo = (struct target_info *)(target_name + uni_len);

	chgblob->TargetInfoArray.Length = 0;
	/* Add target info list for NetBIOS/DNS settings */
	for (type = NTLMSSP_AV_NB_COMPUTER_NAME;
	     type <= NTLMSSP_AV_DNS_DOMAIN_NAME; type++) {
		tinfo->Type = cpu_to_le16(type);
		tinfo->Length = cpu_to_le16(uni_len);
		memcpy(tinfo->Content, name, uni_len);
		tinfo = (struct target_info *)((char *)tinfo + 4 + uni_len);
		target_info_len += 4 + uni_len;
	}

	/* Add terminator subblock */
	tinfo->Type = 0;
	tinfo->Length = 0;
	target_info_len += 4;

	chgblob->TargetInfoArray.Length = cpu_to_le16(target_info_len);
	chgblob->TargetInfoArray.MaximumLength = cpu_to_le16(target_info_len);
	blob_len += target_info_len;
	kfree(name);
	ksmbd_debug(AUTH, "NTLMSSP SecurityBufferLength %d\n", blob_len);
	return blob_len;
}

#ifdef CONFIG_SMB_SERVER_KERBEROS5
int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
			    int in_len, char *out_blob, int *out_len)
{
	struct ksmbd_spnego_authen_response *resp;
	struct ksmbd_login_response_ext *resp_ext = NULL;
	struct ksmbd_user *user = NULL;
	int retval;

	resp = ksmbd_ipc_spnego_authen_request(in_blob, in_len);
	if (!resp) {
		ksmbd_debug(AUTH, "SPNEGO_AUTHEN_REQUEST failure\n");
		return -EINVAL;
	}

	if (!(resp->login_response.status & KSMBD_USER_FLAG_OK)) {
		ksmbd_debug(AUTH, "krb5 authentication failure\n");
		retval = -EPERM;
		goto out;
	}

	if (*out_len <= resp->spnego_blob_len) {
		ksmbd_debug(AUTH, "buf len %d, but blob len %d\n",
			    *out_len, resp->spnego_blob_len);
		retval = -EINVAL;
		goto out;
	}

	if (resp->session_key_len > sizeof(sess->sess_key)) {
		ksmbd_debug(AUTH, "session key is too long\n");
		retval = -EINVAL;
		goto out;
	}

	if (resp->login_response.status & KSMBD_USER_FLAG_EXTENSION)
		resp_ext = ksmbd_ipc_login_request_ext(resp->login_response.account);

	user = ksmbd_alloc_user(&resp->login_response, resp_ext);
	if (!user) {
		ksmbd_debug(AUTH, "login failure\n");
		retval = -ENOMEM;
		goto out;
	}

	if (!sess->user) {
		/* First successful authentication */
		sess->user = user;
	} else {
		if (!ksmbd_compare_user(sess->user, user)) {
			ksmbd_debug(AUTH, "different user tried to reuse session\n");
			retval = -EPERM;
			ksmbd_free_user(user);
			goto out;
		}
		ksmbd_free_user(user);
	}

	memcpy(sess->sess_key, resp->payload, resp->session_key_len);
	memcpy(out_blob, resp->payload + resp->session_key_len,
	       resp->spnego_blob_len);
	*out_len = resp->spnego_blob_len;
	retval = 0;
out:
	kvfree(resp);
	return retval;
}
#else
int ksmbd_krb5_authenticate(struct ksmbd_session *sess, char *in_blob,
			    int in_len, char *out_blob, int *out_len)
{
	return -EOPNOTSUPP;
}
#endif

/**
 * ksmbd_sign_smb2_pdu() - function to generate packet signing
 * @conn:	connection
 * @key:	signing key
 * @iov:        buffer iov array
 * @n_vec:	number of iovecs
 * @sig:	signature value generated for client request packet
 *
 */
void ksmbd_sign_smb2_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
			 int n_vec, char *sig)
{
	struct hmac_sha256_ctx ctx;
	int i;

	hmac_sha256_init_usingrawkey(&ctx, key, SMB2_NTLMV2_SESSKEY_SIZE);
	for (i = 0; i < n_vec; i++)
		hmac_sha256_update(&ctx, iov[i].iov_base, iov[i].iov_len);
	hmac_sha256_final(&ctx, sig);
}

/**
 * ksmbd_sign_smb3_pdu() - function to generate packet signing
 * @conn:	connection
 * @key:	signing key
 * @iov:        buffer iov array
 * @n_vec:	number of iovecs
 * @sig:	signature value generated for client request packet
 *
 */
int ksmbd_sign_smb3_pdu(struct ksmbd_conn *conn, char *key, struct kvec *iov,
			int n_vec, char *sig)
{
	struct ksmbd_crypto_ctx *ctx;
	int rc, i;

	ctx = ksmbd_crypto_ctx_find_cmacaes();
	if (!ctx) {
		ksmbd_debug(AUTH, "could not crypto alloc cmac\n");
		return -ENOMEM;
	}

	rc = crypto_shash_setkey(CRYPTO_CMACAES_TFM(ctx),
				 key,
				 SMB2_CMACAES_SIZE);
	if (rc)
		goto out;

	rc = crypto_shash_init(CRYPTO_CMACAES(ctx));
	if (rc) {
		ksmbd_debug(AUTH, "cmaces init error %d\n", rc);
		goto out;
	}

	for (i = 0; i < n_vec; i++) {
		rc = crypto_shash_update(CRYPTO_CMACAES(ctx),
					 iov[i].iov_base,
					 iov[i].iov_len);
		if (rc) {
			ksmbd_debug(AUTH, "cmaces update error %d\n", rc);
			goto out;
		}
	}

	rc = crypto_shash_final(CRYPTO_CMACAES(ctx), sig);
	if (rc)
		ksmbd_debug(AUTH, "cmaces generation error %d\n", rc);
out:
	ksmbd_release_crypto_ctx(ctx);
	return rc;
}

struct derivation {
	struct kvec label;
	struct kvec context;
	bool binding;
};

static void generate_key(struct ksmbd_conn *conn, struct ksmbd_session *sess,
			 struct kvec label, struct kvec context, __u8 *key,
			 unsigned int key_size)
{
	unsigned char zero = 0x0;
	__u8 i[4] = {0, 0, 0, 1};
	__u8 L128[4] = {0, 0, 0, 128};
	__u8 L256[4] = {0, 0, 1, 0};
	unsigned char prfhash[SMB2_HMACSHA256_SIZE];
	struct hmac_sha256_ctx ctx;

	hmac_sha256_init_usingrawkey(&ctx, sess->sess_key,
				     SMB2_NTLMV2_SESSKEY_SIZE);
	hmac_sha256_update(&ctx, i, 4);
	hmac_sha256_update(&ctx, label.iov_base, label.iov_len);
	hmac_sha256_update(&ctx, &zero, 1);
	hmac_sha256_update(&ctx, context.iov_base, context.iov_len);

	if (key_size == SMB3_ENC_DEC_KEY_SIZE &&
	    (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
	     conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM))
		hmac_sha256_update(&ctx, L256, 4);
	else
		hmac_sha256_update(&ctx, L128, 4);

	hmac_sha256_final(&ctx, prfhash);
	memcpy(key, prfhash, key_size);
}

static int generate_smb3signingkey(struct ksmbd_session *sess,
				   struct ksmbd_conn *conn,
				   const struct derivation *signing)
{
	struct channel *chann;
	char *key;

	chann = lookup_chann_list(sess, conn);
	if (!chann)
		return 0;

	if (conn->dialect >= SMB30_PROT_ID && signing->binding)
		key = chann->smb3signingkey;
	else
		key = sess->smb3signingkey;

	generate_key(conn, sess, signing->label, signing->context, key,
		     SMB3_SIGN_KEY_SIZE);

	if (!(conn->dialect >= SMB30_PROT_ID && signing->binding))
		memcpy(chann->smb3signingkey, key, SMB3_SIGN_KEY_SIZE);

	ksmbd_debug(AUTH, "generated SMB3 signing key\n");
	ksmbd_debug(AUTH, "Session Id    %llu\n", sess->id);
	return 0;
}

int ksmbd_gen_smb30_signingkey(struct ksmbd_session *sess,
			       struct ksmbd_conn *conn)
{
	struct derivation d;

	d.label.iov_base = "SMB2AESCMAC";
	d.label.iov_len = 12;
	d.context.iov_base = "SmbSign";
	d.context.iov_len = 8;
	d.binding = conn->binding;

	return generate_smb3signingkey(sess, conn, &d);
}

int ksmbd_gen_smb311_signingkey(struct ksmbd_session *sess,
				struct ksmbd_conn *conn)
{
	struct derivation d;

	d.label.iov_base = "SMBSigningKey";
	d.label.iov_len = 14;
	if (conn->binding) {
		struct preauth_session *preauth_sess;

		preauth_sess = ksmbd_preauth_session_lookup(conn, sess->id);
		if (!preauth_sess)
			return -ENOENT;
		d.context.iov_base = preauth_sess->Preauth_HashValue;
	} else {
		d.context.iov_base = sess->Preauth_HashValue;
	}
	d.context.iov_len = 64;
	d.binding = conn->binding;

	return generate_smb3signingkey(sess, conn, &d);
}

struct derivation_twin {
	struct derivation encryption;
	struct derivation decryption;
};

static void generate_smb3encryptionkey(struct ksmbd_conn *conn,
				       struct ksmbd_session *sess,
				       const struct derivation_twin *ptwin)
{
	generate_key(conn, sess, ptwin->encryption.label,
		     ptwin->encryption.context, sess->smb3encryptionkey,
		     SMB3_ENC_DEC_KEY_SIZE);

	generate_key(conn, sess, ptwin->decryption.label,
		     ptwin->decryption.context,
		     sess->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE);

	ksmbd_debug(AUTH, "generated SMB3 encryption/decryption keys\n");
	ksmbd_debug(AUTH, "Cipher type   %d\n", conn->cipher_type);
	ksmbd_debug(AUTH, "Session Id    %llu\n", sess->id);
}

void ksmbd_gen_smb30_encryptionkey(struct ksmbd_conn *conn,
				   struct ksmbd_session *sess)
{
	struct derivation_twin twin;
	struct derivation *d;

	d = &twin.encryption;
	d->label.iov_base = "SMB2AESCCM";
	d->label.iov_len = 11;
	d->context.iov_base = "ServerOut";
	d->context.iov_len = 10;

	d = &twin.decryption;
	d->label.iov_base = "SMB2AESCCM";
	d->label.iov_len = 11;
	d->context.iov_base = "ServerIn ";
	d->context.iov_len = 10;

	generate_smb3encryptionkey(conn, sess, &twin);
}

void ksmbd_gen_smb311_encryptionkey(struct ksmbd_conn *conn,
				    struct ksmbd_session *sess)
{
	struct derivation_twin twin;
	struct derivation *d;

	d = &twin.encryption;
	d->label.iov_base = "SMBS2CCipherKey";
	d->label.iov_len = 16;
	d->context.iov_base = sess->Preauth_HashValue;
	d->context.iov_len = 64;

	d = &twin.decryption;
	d->label.iov_base = "SMBC2SCipherKey";
	d->label.iov_len = 16;
	d->context.iov_base = sess->Preauth_HashValue;
	d->context.iov_len = 64;

	generate_smb3encryptionkey(conn, sess, &twin);
}

int ksmbd_gen_preauth_integrity_hash(struct ksmbd_conn *conn, char *buf,
				     __u8 *pi_hash)
{
	struct smb2_hdr *rcv_hdr = smb_get_msg(buf);
	char *all_bytes_msg = (char *)&rcv_hdr->ProtocolId;
	int msg_size = get_rfc1002_len(buf);
	struct sha512_ctx sha_ctx;

	if (conn->preauth_info->Preauth_HashId !=
	    SMB2_PREAUTH_INTEGRITY_SHA512)
		return -EINVAL;

	sha512_init(&sha_ctx);
	sha512_update(&sha_ctx, pi_hash, 64);
	sha512_update(&sha_ctx, all_bytes_msg, msg_size);
	sha512_final(&sha_ctx, pi_hash);
	return 0;
}

static int ksmbd_get_encryption_key(struct ksmbd_work *work, __u64 ses_id,
				    int enc, u8 *key)
{
	struct ksmbd_session *sess;
	u8 *ses_enc_key;

	if (enc)
		sess = work->sess;
	else
		sess = ksmbd_session_lookup_all(work->conn, ses_id);
	if (!sess)
		return -EINVAL;

	ses_enc_key = enc ? sess->smb3encryptionkey :
		sess->smb3decryptionkey;
	memcpy(key, ses_enc_key, SMB3_ENC_DEC_KEY_SIZE);
	if (!enc)
		ksmbd_user_session_put(sess);

	return 0;
}

static inline void smb2_sg_set_buf(struct scatterlist *sg, const void *buf,
				   unsigned int buflen)
{
	void *addr;

	if (is_vmalloc_addr(buf))
		addr = vmalloc_to_page(buf);
	else
		addr = virt_to_page(buf);
	sg_set_page(sg, addr, buflen, offset_in_page(buf));
}

static struct scatterlist *ksmbd_init_sg(struct kvec *iov, unsigned int nvec,
					 u8 *sign)
{
	struct scatterlist *sg;
	unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20;
	int i, *nr_entries, total_entries = 0, sg_idx = 0;

	if (!nvec)
		return NULL;

	nr_entries = kzalloc_objs(int, nvec, KSMBD_DEFAULT_GFP);
	if (!nr_entries)
		return NULL;

	for (i = 0; i < nvec - 1; i++) {
		unsigned long kaddr = (unsigned long)iov[i + 1].iov_base;

		if (is_vmalloc_addr(iov[i + 1].iov_base)) {
			nr_entries[i] = ((kaddr + iov[i + 1].iov_len +
					PAGE_SIZE - 1) >> PAGE_SHIFT) -
				(kaddr >> PAGE_SHIFT);
		} else {
			nr_entries[i]++;
		}
		total_entries += nr_entries[i];
	}

	/* Add two entries for transform header and signature */
	total_entries += 2;

	sg = kmalloc_objs(struct scatterlist, total_entries, KSMBD_DEFAULT_GFP);
	if (!sg) {
		kfree(nr_entries);
		return NULL;
	}

	sg_init_table(sg, total_entries);
	smb2_sg_set_buf(&sg[sg_idx++], iov[0].iov_base + 24, assoc_data_len);
	for (i = 0; i < nvec - 1; i++) {
		void *data = iov[i + 1].iov_base;
		int len = iov[i + 1].iov_len;

		if (is_vmalloc_addr(data)) {
			int j, offset = offset_in_page(data);

			for (j = 0; j < nr_entries[i]; j++) {
				unsigned int bytes = PAGE_SIZE - offset;

				if (!len)
					break;

				if (bytes > len)
					bytes = len;

				sg_set_page(&sg[sg_idx++],
					    vmalloc_to_page(data), bytes,
					    offset_in_page(data));

				data += bytes;
				len -= bytes;
				offset = 0;
			}
		} else {
			sg_set_page(&sg[sg_idx++], virt_to_page(data), len,
				    offset_in_page(data));
		}
	}
	smb2_sg_set_buf(&sg[sg_idx], sign, SMB2_SIGNATURE_SIZE);
	kfree(nr_entries);
	return sg;
}

int ksmbd_crypt_message(struct ksmbd_work *work, struct kvec *iov,
			unsigned int nvec, int enc)
{
	struct ksmbd_conn *conn = work->conn;
	struct smb2_transform_hdr *tr_hdr = smb_get_msg(iov[0].iov_base);
	unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 20;
	int rc;
	struct scatterlist *sg;
	u8 sign[SMB2_SIGNATURE_SIZE] = {};
	u8 key[SMB3_ENC_DEC_KEY_SIZE];
	struct aead_request *req;
	char *iv;
	unsigned int iv_len;
	struct crypto_aead *tfm;
	unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
	struct ksmbd_crypto_ctx *ctx;

	rc = ksmbd_get_encryption_key(work,
				      le64_to_cpu(tr_hdr->SessionId),
				      enc,
				      key);
	if (rc) {
		pr_err("Could not get %scryption key\n", enc ? "en" : "de");
		return rc;
	}

	if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
	    conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
		ctx = ksmbd_crypto_ctx_find_gcm();
	else
		ctx = ksmbd_crypto_ctx_find_ccm();
	if (!ctx) {
		pr_err("crypto alloc failed\n");
		return -ENOMEM;
	}

	if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
	    conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM)
		tfm = CRYPTO_GCM(ctx);
	else
		tfm = CRYPTO_CCM(ctx);

	if (conn->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
	    conn->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) {
		pr_err("Failed to set aead key %d\n", rc);
		goto free_ctx;
	}

	rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE);
	if (rc) {
		pr_err("Failed to set authsize %d\n", rc);
		goto free_ctx;
	}

	req = aead_request_alloc(tfm, KSMBD_DEFAULT_GFP);
	if (!req) {
		rc = -ENOMEM;
		goto free_ctx;
	}

	if (!enc) {
		memcpy(sign, &tr_hdr->Signature, SMB2_SIGNATURE_SIZE);
		crypt_len += SMB2_SIGNATURE_SIZE;
	}

	sg = ksmbd_init_sg(iov, nvec, sign);
	if (!sg) {
		pr_err("Failed to init sg\n");
		rc = -ENOMEM;
		goto free_req;
	}

	iv_len = crypto_aead_ivsize(tfm);
	iv = kzalloc(iv_len, KSMBD_DEFAULT_GFP);
	if (!iv) {
		rc = -ENOMEM;
		goto free_sg;
	}

	if (conn->cipher_type == SMB2_ENCRYPTION_AES128_GCM ||
	    conn->cipher_type == SMB2_ENCRYPTION_AES256_GCM) {
		memcpy(iv, (char *)tr_hdr->Nonce, SMB3_AES_GCM_NONCE);
	} else {
		iv[0] = 3;
		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);
	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL);

	if (enc)
		rc = crypto_aead_encrypt(req);
	else
		rc = crypto_aead_decrypt(req);
	if (rc)
		goto free_iv;

	if (enc)
		memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);

free_iv:
	kfree(iv);
free_sg:
	kfree(sg);
free_req:
	aead_request_free(req);
free_ctx:
	ksmbd_release_crypto_ctx(ctx);
	return rc;
}
