mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-30 21:09:38 +03:00
10fa254539
Contrary to initial testing we cannot rely on these kernels to invalidate the per-cpu FPU state and restore the FPU registers. Nor can we guarantee that the kernel won't modify the FPU state which we saved in the task struck. Therefore, the kfpu_begin() and kfpu_end() functions have been updated to save and restore the FPU state using our own dedicated per-cpu FPU state variables. This has the additional advantage of allowing us to use the FPU again in user threads. So we remove the code which was added to use task queues to ensure some functions ran in kernel threads. Reviewed-by: Fabian Grünbichler <f.gruenbichler@proxmox.com> Reviewed-by: Tony Hutter <hutter2@llnl.gov> Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Issue #9346 Closes #9403
1445 lines
39 KiB
C
1445 lines
39 KiB
C
/*
|
|
* CDDL HEADER START
|
|
*
|
|
* The contents of this file are subject to the terms of the
|
|
* Common Development and Distribution License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
*
|
|
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
|
|
* or http://www.opensolaris.org/os/licensing.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
*
|
|
* When distributing Covered Code, include this CDDL HEADER in each
|
|
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
|
|
* If applicable, add the following below this CDDL HEADER, with the
|
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
|
* information: Portions Copyright [yyyy] [name of copyright owner]
|
|
*
|
|
* CDDL HEADER END
|
|
*/
|
|
/*
|
|
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
|
|
*/
|
|
|
|
/*
|
|
* AES provider for the Kernel Cryptographic Framework (KCF)
|
|
*/
|
|
|
|
#include <sys/zfs_context.h>
|
|
#include <sys/crypto/common.h>
|
|
#include <sys/crypto/impl.h>
|
|
#include <sys/crypto/spi.h>
|
|
#include <sys/crypto/icp.h>
|
|
#include <modes/modes.h>
|
|
#include <sys/modctl.h>
|
|
#define _AES_IMPL
|
|
#include <aes/aes_impl.h>
|
|
#include <modes/gcm_impl.h>
|
|
|
|
#define CRYPTO_PROVIDER_NAME "aes"
|
|
|
|
extern struct mod_ops mod_cryptoops;
|
|
|
|
/*
|
|
* Module linkage information for the kernel.
|
|
*/
|
|
static struct modlcrypto modlcrypto = {
|
|
&mod_cryptoops,
|
|
"AES Kernel SW Provider"
|
|
};
|
|
|
|
static struct modlinkage modlinkage = {
|
|
MODREV_1, { (void *)&modlcrypto, NULL }
|
|
};
|
|
|
|
/*
|
|
* Mechanism info structure passed to KCF during registration.
|
|
*/
|
|
static crypto_mech_info_t aes_mech_info_tab[] = {
|
|
/* AES_ECB */
|
|
{SUN_CKM_AES_ECB, AES_ECB_MECH_INFO_TYPE,
|
|
CRYPTO_FG_ENCRYPT | CRYPTO_FG_ENCRYPT_ATOMIC |
|
|
CRYPTO_FG_DECRYPT | CRYPTO_FG_DECRYPT_ATOMIC,
|
|
AES_MIN_KEY_BYTES, AES_MAX_KEY_BYTES, CRYPTO_KEYSIZE_UNIT_IN_BYTES},
|
|
/* AES_CBC */
|
|
{SUN_CKM_AES_CBC, AES_CBC_MECH_INFO_TYPE,
|
|
CRYPTO_FG_ENCRYPT | CRYPTO_FG_ENCRYPT_ATOMIC |
|
|
CRYPTO_FG_DECRYPT | CRYPTO_FG_DECRYPT_ATOMIC,
|
|
AES_MIN_KEY_BYTES, AES_MAX_KEY_BYTES, CRYPTO_KEYSIZE_UNIT_IN_BYTES},
|
|
/* AES_CTR */
|
|
{SUN_CKM_AES_CTR, AES_CTR_MECH_INFO_TYPE,
|
|
CRYPTO_FG_ENCRYPT | CRYPTO_FG_ENCRYPT_ATOMIC |
|
|
CRYPTO_FG_DECRYPT | CRYPTO_FG_DECRYPT_ATOMIC,
|
|
AES_MIN_KEY_BYTES, AES_MAX_KEY_BYTES, CRYPTO_KEYSIZE_UNIT_IN_BYTES},
|
|
/* AES_CCM */
|
|
{SUN_CKM_AES_CCM, AES_CCM_MECH_INFO_TYPE,
|
|
CRYPTO_FG_ENCRYPT | CRYPTO_FG_ENCRYPT_ATOMIC |
|
|
CRYPTO_FG_DECRYPT | CRYPTO_FG_DECRYPT_ATOMIC,
|
|
AES_MIN_KEY_BYTES, AES_MAX_KEY_BYTES, CRYPTO_KEYSIZE_UNIT_IN_BYTES},
|
|
/* AES_GCM */
|
|
{SUN_CKM_AES_GCM, AES_GCM_MECH_INFO_TYPE,
|
|
CRYPTO_FG_ENCRYPT | CRYPTO_FG_ENCRYPT_ATOMIC |
|
|
CRYPTO_FG_DECRYPT | CRYPTO_FG_DECRYPT_ATOMIC,
|
|
AES_MIN_KEY_BYTES, AES_MAX_KEY_BYTES, CRYPTO_KEYSIZE_UNIT_IN_BYTES},
|
|
/* AES_GMAC */
|
|
{SUN_CKM_AES_GMAC, AES_GMAC_MECH_INFO_TYPE,
|
|
CRYPTO_FG_ENCRYPT | CRYPTO_FG_ENCRYPT_ATOMIC |
|
|
CRYPTO_FG_DECRYPT | CRYPTO_FG_DECRYPT_ATOMIC |
|
|
CRYPTO_FG_MAC | CRYPTO_FG_MAC_ATOMIC |
|
|
CRYPTO_FG_SIGN | CRYPTO_FG_SIGN_ATOMIC |
|
|
CRYPTO_FG_VERIFY | CRYPTO_FG_VERIFY_ATOMIC,
|
|
AES_MIN_KEY_BYTES, AES_MAX_KEY_BYTES, CRYPTO_KEYSIZE_UNIT_IN_BYTES}
|
|
};
|
|
|
|
/* operations are in-place if the output buffer is NULL */
|
|
#define AES_ARG_INPLACE(input, output) \
|
|
if ((output) == NULL) \
|
|
(output) = (input);
|
|
|
|
static void aes_provider_status(crypto_provider_handle_t, uint_t *);
|
|
|
|
static crypto_control_ops_t aes_control_ops = {
|
|
aes_provider_status
|
|
};
|
|
|
|
static int aes_encrypt_init(crypto_ctx_t *, crypto_mechanism_t *,
|
|
crypto_key_t *, crypto_spi_ctx_template_t, crypto_req_handle_t);
|
|
static int aes_decrypt_init(crypto_ctx_t *, crypto_mechanism_t *,
|
|
crypto_key_t *, crypto_spi_ctx_template_t, crypto_req_handle_t);
|
|
static int aes_common_init(crypto_ctx_t *, crypto_mechanism_t *,
|
|
crypto_key_t *, crypto_spi_ctx_template_t, crypto_req_handle_t, boolean_t);
|
|
static int aes_common_init_ctx(aes_ctx_t *, crypto_spi_ctx_template_t *,
|
|
crypto_mechanism_t *, crypto_key_t *, int, boolean_t);
|
|
static int aes_encrypt_final(crypto_ctx_t *, crypto_data_t *,
|
|
crypto_req_handle_t);
|
|
static int aes_decrypt_final(crypto_ctx_t *, crypto_data_t *,
|
|
crypto_req_handle_t);
|
|
|
|
static int aes_encrypt(crypto_ctx_t *, crypto_data_t *, crypto_data_t *,
|
|
crypto_req_handle_t);
|
|
static int aes_encrypt_update(crypto_ctx_t *, crypto_data_t *,
|
|
crypto_data_t *, crypto_req_handle_t);
|
|
static int aes_encrypt_atomic(crypto_provider_handle_t, crypto_session_id_t,
|
|
crypto_mechanism_t *, crypto_key_t *, crypto_data_t *,
|
|
crypto_data_t *, crypto_spi_ctx_template_t, crypto_req_handle_t);
|
|
|
|
static int aes_decrypt(crypto_ctx_t *, crypto_data_t *, crypto_data_t *,
|
|
crypto_req_handle_t);
|
|
static int aes_decrypt_update(crypto_ctx_t *, crypto_data_t *,
|
|
crypto_data_t *, crypto_req_handle_t);
|
|
static int aes_decrypt_atomic(crypto_provider_handle_t, crypto_session_id_t,
|
|
crypto_mechanism_t *, crypto_key_t *, crypto_data_t *,
|
|
crypto_data_t *, crypto_spi_ctx_template_t, crypto_req_handle_t);
|
|
|
|
static crypto_cipher_ops_t aes_cipher_ops = {
|
|
.encrypt_init = aes_encrypt_init,
|
|
.encrypt = aes_encrypt,
|
|
.encrypt_update = aes_encrypt_update,
|
|
.encrypt_final = aes_encrypt_final,
|
|
.encrypt_atomic = aes_encrypt_atomic,
|
|
.decrypt_init = aes_decrypt_init,
|
|
.decrypt = aes_decrypt,
|
|
.decrypt_update = aes_decrypt_update,
|
|
.decrypt_final = aes_decrypt_final,
|
|
.decrypt_atomic = aes_decrypt_atomic
|
|
};
|
|
|
|
static int aes_mac_atomic(crypto_provider_handle_t, crypto_session_id_t,
|
|
crypto_mechanism_t *, crypto_key_t *, crypto_data_t *, crypto_data_t *,
|
|
crypto_spi_ctx_template_t, crypto_req_handle_t);
|
|
static int aes_mac_verify_atomic(crypto_provider_handle_t, crypto_session_id_t,
|
|
crypto_mechanism_t *, crypto_key_t *, crypto_data_t *, crypto_data_t *,
|
|
crypto_spi_ctx_template_t, crypto_req_handle_t);
|
|
|
|
static crypto_mac_ops_t aes_mac_ops = {
|
|
.mac_init = NULL,
|
|
.mac = NULL,
|
|
.mac_update = NULL,
|
|
.mac_final = NULL,
|
|
.mac_atomic = aes_mac_atomic,
|
|
.mac_verify_atomic = aes_mac_verify_atomic
|
|
};
|
|
|
|
static int aes_create_ctx_template(crypto_provider_handle_t,
|
|
crypto_mechanism_t *, crypto_key_t *, crypto_spi_ctx_template_t *,
|
|
size_t *, crypto_req_handle_t);
|
|
static int aes_free_context(crypto_ctx_t *);
|
|
|
|
static crypto_ctx_ops_t aes_ctx_ops = {
|
|
.create_ctx_template = aes_create_ctx_template,
|
|
.free_context = aes_free_context
|
|
};
|
|
|
|
static crypto_ops_t aes_crypto_ops = {{{{{
|
|
&aes_control_ops,
|
|
NULL,
|
|
&aes_cipher_ops,
|
|
&aes_mac_ops,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&aes_ctx_ops
|
|
}}}}};
|
|
|
|
static crypto_provider_info_t aes_prov_info = {{{{
|
|
CRYPTO_SPI_VERSION_1,
|
|
"AES Software Provider",
|
|
CRYPTO_SW_PROVIDER,
|
|
NULL,
|
|
&aes_crypto_ops,
|
|
sizeof (aes_mech_info_tab)/sizeof (crypto_mech_info_t),
|
|
aes_mech_info_tab
|
|
}}}};
|
|
|
|
static crypto_kcf_provider_handle_t aes_prov_handle = 0;
|
|
static crypto_data_t null_crypto_data = { CRYPTO_DATA_RAW };
|
|
|
|
int
|
|
aes_mod_init(void)
|
|
{
|
|
int ret;
|
|
|
|
/* Determine the fastest available implementation. */
|
|
aes_impl_init();
|
|
gcm_impl_init();
|
|
|
|
if ((ret = mod_install(&modlinkage)) != 0)
|
|
return (ret);
|
|
|
|
/* Register with KCF. If the registration fails, remove the module. */
|
|
if (crypto_register_provider(&aes_prov_info, &aes_prov_handle)) {
|
|
(void) mod_remove(&modlinkage);
|
|
return (EACCES);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
aes_mod_fini(void)
|
|
{
|
|
/* Unregister from KCF if module is registered */
|
|
if (aes_prov_handle != 0) {
|
|
if (crypto_unregister_provider(aes_prov_handle))
|
|
return (EBUSY);
|
|
|
|
aes_prov_handle = 0;
|
|
}
|
|
|
|
return (mod_remove(&modlinkage));
|
|
}
|
|
|
|
static int
|
|
aes_check_mech_param(crypto_mechanism_t *mechanism, aes_ctx_t **ctx, int kmflag)
|
|
{
|
|
void *p = NULL;
|
|
boolean_t param_required = B_TRUE;
|
|
size_t param_len;
|
|
void *(*alloc_fun)(int);
|
|
int rv = CRYPTO_SUCCESS;
|
|
|
|
switch (mechanism->cm_type) {
|
|
case AES_ECB_MECH_INFO_TYPE:
|
|
param_required = B_FALSE;
|
|
alloc_fun = ecb_alloc_ctx;
|
|
break;
|
|
case AES_CBC_MECH_INFO_TYPE:
|
|
param_len = AES_BLOCK_LEN;
|
|
alloc_fun = cbc_alloc_ctx;
|
|
break;
|
|
case AES_CTR_MECH_INFO_TYPE:
|
|
param_len = sizeof (CK_AES_CTR_PARAMS);
|
|
alloc_fun = ctr_alloc_ctx;
|
|
break;
|
|
case AES_CCM_MECH_INFO_TYPE:
|
|
param_len = sizeof (CK_AES_CCM_PARAMS);
|
|
alloc_fun = ccm_alloc_ctx;
|
|
break;
|
|
case AES_GCM_MECH_INFO_TYPE:
|
|
param_len = sizeof (CK_AES_GCM_PARAMS);
|
|
alloc_fun = gcm_alloc_ctx;
|
|
break;
|
|
case AES_GMAC_MECH_INFO_TYPE:
|
|
param_len = sizeof (CK_AES_GMAC_PARAMS);
|
|
alloc_fun = gmac_alloc_ctx;
|
|
break;
|
|
default:
|
|
rv = CRYPTO_MECHANISM_INVALID;
|
|
return (rv);
|
|
}
|
|
if (param_required && mechanism->cm_param != NULL &&
|
|
mechanism->cm_param_len != param_len) {
|
|
rv = CRYPTO_MECHANISM_PARAM_INVALID;
|
|
}
|
|
if (ctx != NULL) {
|
|
p = (alloc_fun)(kmflag);
|
|
*ctx = p;
|
|
}
|
|
return (rv);
|
|
}
|
|
|
|
/*
|
|
* Initialize key schedules for AES
|
|
*/
|
|
static int
|
|
init_keysched(crypto_key_t *key, void *newbie)
|
|
{
|
|
/*
|
|
* Only keys by value are supported by this module.
|
|
*/
|
|
switch (key->ck_format) {
|
|
case CRYPTO_KEY_RAW:
|
|
if (key->ck_length < AES_MINBITS ||
|
|
key->ck_length > AES_MAXBITS) {
|
|
return (CRYPTO_KEY_SIZE_RANGE);
|
|
}
|
|
|
|
/* key length must be either 128, 192, or 256 */
|
|
if ((key->ck_length & 63) != 0)
|
|
return (CRYPTO_KEY_SIZE_RANGE);
|
|
break;
|
|
default:
|
|
return (CRYPTO_KEY_TYPE_INCONSISTENT);
|
|
}
|
|
|
|
aes_init_keysched(key->ck_data, key->ck_length, newbie);
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* KCF software provider control entry points.
|
|
*/
|
|
/* ARGSUSED */
|
|
static void
|
|
aes_provider_status(crypto_provider_handle_t provider, uint_t *status)
|
|
{
|
|
*status = CRYPTO_PROVIDER_READY;
|
|
}
|
|
|
|
static int
|
|
aes_encrypt_init(crypto_ctx_t *ctx, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_spi_ctx_template_t template,
|
|
crypto_req_handle_t req)
|
|
{
|
|
return (aes_common_init(ctx, mechanism, key, template, req, B_TRUE));
|
|
}
|
|
|
|
static int
|
|
aes_decrypt_init(crypto_ctx_t *ctx, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_spi_ctx_template_t template,
|
|
crypto_req_handle_t req)
|
|
{
|
|
return (aes_common_init(ctx, mechanism, key, template, req, B_FALSE));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* KCF software provider encrypt entry points.
|
|
*/
|
|
static int
|
|
aes_common_init(crypto_ctx_t *ctx, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_spi_ctx_template_t template,
|
|
crypto_req_handle_t req, boolean_t is_encrypt_init)
|
|
{
|
|
aes_ctx_t *aes_ctx;
|
|
int rv;
|
|
int kmflag;
|
|
|
|
/*
|
|
* Only keys by value are supported by this module.
|
|
*/
|
|
if (key->ck_format != CRYPTO_KEY_RAW) {
|
|
return (CRYPTO_KEY_TYPE_INCONSISTENT);
|
|
}
|
|
|
|
kmflag = crypto_kmflag(req);
|
|
if ((rv = aes_check_mech_param(mechanism, &aes_ctx, kmflag))
|
|
!= CRYPTO_SUCCESS)
|
|
return (rv);
|
|
|
|
rv = aes_common_init_ctx(aes_ctx, template, mechanism, key, kmflag,
|
|
is_encrypt_init);
|
|
if (rv != CRYPTO_SUCCESS) {
|
|
crypto_free_mode_ctx(aes_ctx);
|
|
return (rv);
|
|
}
|
|
|
|
ctx->cc_provider_private = aes_ctx;
|
|
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
aes_copy_block64(uint8_t *in, uint64_t *out)
|
|
{
|
|
if (IS_P2ALIGNED(in, sizeof (uint64_t))) {
|
|
/* LINTED: pointer alignment */
|
|
out[0] = *(uint64_t *)&in[0];
|
|
/* LINTED: pointer alignment */
|
|
out[1] = *(uint64_t *)&in[8];
|
|
} else {
|
|
uint8_t *iv8 = (uint8_t *)&out[0];
|
|
|
|
AES_COPY_BLOCK(in, iv8);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
aes_encrypt(crypto_ctx_t *ctx, crypto_data_t *plaintext,
|
|
crypto_data_t *ciphertext, crypto_req_handle_t req)
|
|
{
|
|
int ret = CRYPTO_FAILED;
|
|
|
|
aes_ctx_t *aes_ctx;
|
|
size_t saved_length, saved_offset, length_needed;
|
|
|
|
ASSERT(ctx->cc_provider_private != NULL);
|
|
aes_ctx = ctx->cc_provider_private;
|
|
|
|
/*
|
|
* For block ciphers, plaintext must be a multiple of AES block size.
|
|
* This test is only valid for ciphers whose blocksize is a power of 2.
|
|
*/
|
|
if (((aes_ctx->ac_flags & (CTR_MODE|CCM_MODE|GCM_MODE|GMAC_MODE))
|
|
== 0) && (plaintext->cd_length & (AES_BLOCK_LEN - 1)) != 0)
|
|
return (CRYPTO_DATA_LEN_RANGE);
|
|
|
|
AES_ARG_INPLACE(plaintext, ciphertext);
|
|
|
|
/*
|
|
* We need to just return the length needed to store the output.
|
|
* We should not destroy the context for the following case.
|
|
*/
|
|
switch (aes_ctx->ac_flags & (CCM_MODE|GCM_MODE|GMAC_MODE)) {
|
|
case CCM_MODE:
|
|
length_needed = plaintext->cd_length + aes_ctx->ac_mac_len;
|
|
break;
|
|
case GCM_MODE:
|
|
length_needed = plaintext->cd_length + aes_ctx->ac_tag_len;
|
|
break;
|
|
case GMAC_MODE:
|
|
if (plaintext->cd_length != 0)
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
|
|
length_needed = aes_ctx->ac_tag_len;
|
|
break;
|
|
default:
|
|
length_needed = plaintext->cd_length;
|
|
}
|
|
|
|
if (ciphertext->cd_length < length_needed) {
|
|
ciphertext->cd_length = length_needed;
|
|
return (CRYPTO_BUFFER_TOO_SMALL);
|
|
}
|
|
|
|
saved_length = ciphertext->cd_length;
|
|
saved_offset = ciphertext->cd_offset;
|
|
|
|
/*
|
|
* Do an update on the specified input data.
|
|
*/
|
|
ret = aes_encrypt_update(ctx, plaintext, ciphertext, req);
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* For CCM mode, aes_ccm_encrypt_final() will take care of any
|
|
* left-over unprocessed data, and compute the MAC
|
|
*/
|
|
if (aes_ctx->ac_flags & CCM_MODE) {
|
|
/*
|
|
* ccm_encrypt_final() will compute the MAC and append
|
|
* it to existing ciphertext. So, need to adjust the left over
|
|
* length value accordingly
|
|
*/
|
|
|
|
/* order of following 2 lines MUST not be reversed */
|
|
ciphertext->cd_offset = ciphertext->cd_length;
|
|
ciphertext->cd_length = saved_length - ciphertext->cd_length;
|
|
ret = ccm_encrypt_final((ccm_ctx_t *)aes_ctx, ciphertext,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_xor_block);
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
|
|
if (plaintext != ciphertext) {
|
|
ciphertext->cd_length =
|
|
ciphertext->cd_offset - saved_offset;
|
|
}
|
|
ciphertext->cd_offset = saved_offset;
|
|
} else if (aes_ctx->ac_flags & (GCM_MODE|GMAC_MODE)) {
|
|
/*
|
|
* gcm_encrypt_final() will compute the MAC and append
|
|
* it to existing ciphertext. So, need to adjust the left over
|
|
* length value accordingly
|
|
*/
|
|
|
|
/* order of following 2 lines MUST not be reversed */
|
|
ciphertext->cd_offset = ciphertext->cd_length;
|
|
ciphertext->cd_length = saved_length - ciphertext->cd_length;
|
|
ret = gcm_encrypt_final((gcm_ctx_t *)aes_ctx, ciphertext,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_copy_block,
|
|
aes_xor_block);
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
|
|
if (plaintext != ciphertext) {
|
|
ciphertext->cd_length =
|
|
ciphertext->cd_offset - saved_offset;
|
|
}
|
|
ciphertext->cd_offset = saved_offset;
|
|
}
|
|
|
|
ASSERT(aes_ctx->ac_remainder_len == 0);
|
|
(void) aes_free_context(ctx);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
static int
|
|
aes_decrypt(crypto_ctx_t *ctx, crypto_data_t *ciphertext,
|
|
crypto_data_t *plaintext, crypto_req_handle_t req)
|
|
{
|
|
int ret = CRYPTO_FAILED;
|
|
|
|
aes_ctx_t *aes_ctx;
|
|
off_t saved_offset;
|
|
size_t saved_length, length_needed;
|
|
|
|
ASSERT(ctx->cc_provider_private != NULL);
|
|
aes_ctx = ctx->cc_provider_private;
|
|
|
|
/*
|
|
* For block ciphers, plaintext must be a multiple of AES block size.
|
|
* This test is only valid for ciphers whose blocksize is a power of 2.
|
|
*/
|
|
if (((aes_ctx->ac_flags & (CTR_MODE|CCM_MODE|GCM_MODE|GMAC_MODE))
|
|
== 0) && (ciphertext->cd_length & (AES_BLOCK_LEN - 1)) != 0) {
|
|
return (CRYPTO_ENCRYPTED_DATA_LEN_RANGE);
|
|
}
|
|
|
|
AES_ARG_INPLACE(ciphertext, plaintext);
|
|
|
|
/*
|
|
* Return length needed to store the output.
|
|
* Do not destroy context when plaintext buffer is too small.
|
|
*
|
|
* CCM: plaintext is MAC len smaller than cipher text
|
|
* GCM: plaintext is TAG len smaller than cipher text
|
|
* GMAC: plaintext length must be zero
|
|
*/
|
|
switch (aes_ctx->ac_flags & (CCM_MODE|GCM_MODE|GMAC_MODE)) {
|
|
case CCM_MODE:
|
|
length_needed = aes_ctx->ac_processed_data_len;
|
|
break;
|
|
case GCM_MODE:
|
|
length_needed = ciphertext->cd_length - aes_ctx->ac_tag_len;
|
|
break;
|
|
case GMAC_MODE:
|
|
if (plaintext->cd_length != 0)
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
|
|
length_needed = 0;
|
|
break;
|
|
default:
|
|
length_needed = ciphertext->cd_length;
|
|
}
|
|
|
|
if (plaintext->cd_length < length_needed) {
|
|
plaintext->cd_length = length_needed;
|
|
return (CRYPTO_BUFFER_TOO_SMALL);
|
|
}
|
|
|
|
saved_offset = plaintext->cd_offset;
|
|
saved_length = plaintext->cd_length;
|
|
|
|
/*
|
|
* Do an update on the specified input data.
|
|
*/
|
|
ret = aes_decrypt_update(ctx, ciphertext, plaintext, req);
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (aes_ctx->ac_flags & CCM_MODE) {
|
|
ASSERT(aes_ctx->ac_processed_data_len == aes_ctx->ac_data_len);
|
|
ASSERT(aes_ctx->ac_processed_mac_len == aes_ctx->ac_mac_len);
|
|
|
|
/* order of following 2 lines MUST not be reversed */
|
|
plaintext->cd_offset = plaintext->cd_length;
|
|
plaintext->cd_length = saved_length - plaintext->cd_length;
|
|
|
|
ret = ccm_decrypt_final((ccm_ctx_t *)aes_ctx, plaintext,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_copy_block,
|
|
aes_xor_block);
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
if (plaintext != ciphertext) {
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
}
|
|
} else {
|
|
plaintext->cd_length = saved_length;
|
|
}
|
|
|
|
plaintext->cd_offset = saved_offset;
|
|
} else if (aes_ctx->ac_flags & (GCM_MODE|GMAC_MODE)) {
|
|
/* order of following 2 lines MUST not be reversed */
|
|
plaintext->cd_offset = plaintext->cd_length;
|
|
plaintext->cd_length = saved_length - plaintext->cd_length;
|
|
|
|
ret = gcm_decrypt_final((gcm_ctx_t *)aes_ctx, plaintext,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_xor_block);
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
if (plaintext != ciphertext) {
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
}
|
|
} else {
|
|
plaintext->cd_length = saved_length;
|
|
}
|
|
|
|
plaintext->cd_offset = saved_offset;
|
|
}
|
|
|
|
ASSERT(aes_ctx->ac_remainder_len == 0);
|
|
|
|
cleanup:
|
|
(void) aes_free_context(ctx);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
aes_encrypt_update(crypto_ctx_t *ctx, crypto_data_t *plaintext,
|
|
crypto_data_t *ciphertext, crypto_req_handle_t req)
|
|
{
|
|
off_t saved_offset;
|
|
size_t saved_length, out_len;
|
|
int ret = CRYPTO_SUCCESS;
|
|
aes_ctx_t *aes_ctx;
|
|
|
|
ASSERT(ctx->cc_provider_private != NULL);
|
|
aes_ctx = ctx->cc_provider_private;
|
|
|
|
AES_ARG_INPLACE(plaintext, ciphertext);
|
|
|
|
/* compute number of bytes that will hold the ciphertext */
|
|
out_len = aes_ctx->ac_remainder_len;
|
|
out_len += plaintext->cd_length;
|
|
out_len &= ~(AES_BLOCK_LEN - 1);
|
|
|
|
/* return length needed to store the output */
|
|
if (ciphertext->cd_length < out_len) {
|
|
ciphertext->cd_length = out_len;
|
|
return (CRYPTO_BUFFER_TOO_SMALL);
|
|
}
|
|
|
|
saved_offset = ciphertext->cd_offset;
|
|
saved_length = ciphertext->cd_length;
|
|
|
|
/*
|
|
* Do the AES update on the specified input data.
|
|
*/
|
|
switch (plaintext->cd_format) {
|
|
case CRYPTO_DATA_RAW:
|
|
ret = crypto_update_iov(ctx->cc_provider_private,
|
|
plaintext, ciphertext, aes_encrypt_contiguous_blocks,
|
|
aes_copy_block64);
|
|
break;
|
|
case CRYPTO_DATA_UIO:
|
|
ret = crypto_update_uio(ctx->cc_provider_private,
|
|
plaintext, ciphertext, aes_encrypt_contiguous_blocks,
|
|
aes_copy_block64);
|
|
break;
|
|
default:
|
|
ret = CRYPTO_ARGUMENTS_BAD;
|
|
}
|
|
|
|
/*
|
|
* Since AES counter mode is a stream cipher, we call
|
|
* ctr_mode_final() to pick up any remaining bytes.
|
|
* It is an internal function that does not destroy
|
|
* the context like *normal* final routines.
|
|
*/
|
|
if ((aes_ctx->ac_flags & CTR_MODE) && (aes_ctx->ac_remainder_len > 0)) {
|
|
ret = ctr_mode_final((ctr_ctx_t *)aes_ctx,
|
|
ciphertext, aes_encrypt_block);
|
|
}
|
|
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
if (plaintext != ciphertext)
|
|
ciphertext->cd_length =
|
|
ciphertext->cd_offset - saved_offset;
|
|
} else {
|
|
ciphertext->cd_length = saved_length;
|
|
}
|
|
ciphertext->cd_offset = saved_offset;
|
|
|
|
return (ret);
|
|
}
|
|
|
|
|
|
static int
|
|
aes_decrypt_update(crypto_ctx_t *ctx, crypto_data_t *ciphertext,
|
|
crypto_data_t *plaintext, crypto_req_handle_t req)
|
|
{
|
|
off_t saved_offset;
|
|
size_t saved_length, out_len;
|
|
int ret = CRYPTO_SUCCESS;
|
|
aes_ctx_t *aes_ctx;
|
|
|
|
ASSERT(ctx->cc_provider_private != NULL);
|
|
aes_ctx = ctx->cc_provider_private;
|
|
|
|
AES_ARG_INPLACE(ciphertext, plaintext);
|
|
|
|
/*
|
|
* Compute number of bytes that will hold the plaintext.
|
|
* This is not necessary for CCM, GCM, and GMAC since these
|
|
* mechanisms never return plaintext for update operations.
|
|
*/
|
|
if ((aes_ctx->ac_flags & (CCM_MODE|GCM_MODE|GMAC_MODE)) == 0) {
|
|
out_len = aes_ctx->ac_remainder_len;
|
|
out_len += ciphertext->cd_length;
|
|
out_len &= ~(AES_BLOCK_LEN - 1);
|
|
|
|
/* return length needed to store the output */
|
|
if (plaintext->cd_length < out_len) {
|
|
plaintext->cd_length = out_len;
|
|
return (CRYPTO_BUFFER_TOO_SMALL);
|
|
}
|
|
}
|
|
|
|
saved_offset = plaintext->cd_offset;
|
|
saved_length = plaintext->cd_length;
|
|
|
|
if (aes_ctx->ac_flags & (GCM_MODE|GMAC_MODE))
|
|
gcm_set_kmflag((gcm_ctx_t *)aes_ctx, crypto_kmflag(req));
|
|
|
|
/*
|
|
* Do the AES update on the specified input data.
|
|
*/
|
|
switch (ciphertext->cd_format) {
|
|
case CRYPTO_DATA_RAW:
|
|
ret = crypto_update_iov(ctx->cc_provider_private,
|
|
ciphertext, plaintext, aes_decrypt_contiguous_blocks,
|
|
aes_copy_block64);
|
|
break;
|
|
case CRYPTO_DATA_UIO:
|
|
ret = crypto_update_uio(ctx->cc_provider_private,
|
|
ciphertext, plaintext, aes_decrypt_contiguous_blocks,
|
|
aes_copy_block64);
|
|
break;
|
|
default:
|
|
ret = CRYPTO_ARGUMENTS_BAD;
|
|
}
|
|
|
|
/*
|
|
* Since AES counter mode is a stream cipher, we call
|
|
* ctr_mode_final() to pick up any remaining bytes.
|
|
* It is an internal function that does not destroy
|
|
* the context like *normal* final routines.
|
|
*/
|
|
if ((aes_ctx->ac_flags & CTR_MODE) && (aes_ctx->ac_remainder_len > 0)) {
|
|
ret = ctr_mode_final((ctr_ctx_t *)aes_ctx, plaintext,
|
|
aes_encrypt_block);
|
|
if (ret == CRYPTO_DATA_LEN_RANGE)
|
|
ret = CRYPTO_ENCRYPTED_DATA_LEN_RANGE;
|
|
}
|
|
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
if (ciphertext != plaintext)
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
} else {
|
|
plaintext->cd_length = saved_length;
|
|
}
|
|
plaintext->cd_offset = saved_offset;
|
|
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
aes_encrypt_final(crypto_ctx_t *ctx, crypto_data_t *data,
|
|
crypto_req_handle_t req)
|
|
{
|
|
aes_ctx_t *aes_ctx;
|
|
int ret;
|
|
|
|
ASSERT(ctx->cc_provider_private != NULL);
|
|
aes_ctx = ctx->cc_provider_private;
|
|
|
|
if (data->cd_format != CRYPTO_DATA_RAW &&
|
|
data->cd_format != CRYPTO_DATA_UIO) {
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
}
|
|
|
|
if (aes_ctx->ac_flags & CTR_MODE) {
|
|
if (aes_ctx->ac_remainder_len > 0) {
|
|
ret = ctr_mode_final((ctr_ctx_t *)aes_ctx, data,
|
|
aes_encrypt_block);
|
|
if (ret != CRYPTO_SUCCESS)
|
|
return (ret);
|
|
}
|
|
} else if (aes_ctx->ac_flags & CCM_MODE) {
|
|
ret = ccm_encrypt_final((ccm_ctx_t *)aes_ctx, data,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_xor_block);
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
} else if (aes_ctx->ac_flags & (GCM_MODE|GMAC_MODE)) {
|
|
size_t saved_offset = data->cd_offset;
|
|
|
|
ret = gcm_encrypt_final((gcm_ctx_t *)aes_ctx, data,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_copy_block,
|
|
aes_xor_block);
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
data->cd_length = data->cd_offset - saved_offset;
|
|
data->cd_offset = saved_offset;
|
|
} else {
|
|
/*
|
|
* There must be no unprocessed plaintext.
|
|
* This happens if the length of the last data is
|
|
* not a multiple of the AES block length.
|
|
*/
|
|
if (aes_ctx->ac_remainder_len > 0) {
|
|
return (CRYPTO_DATA_LEN_RANGE);
|
|
}
|
|
data->cd_length = 0;
|
|
}
|
|
|
|
(void) aes_free_context(ctx);
|
|
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
aes_decrypt_final(crypto_ctx_t *ctx, crypto_data_t *data,
|
|
crypto_req_handle_t req)
|
|
{
|
|
aes_ctx_t *aes_ctx;
|
|
int ret;
|
|
off_t saved_offset;
|
|
size_t saved_length;
|
|
|
|
ASSERT(ctx->cc_provider_private != NULL);
|
|
aes_ctx = ctx->cc_provider_private;
|
|
|
|
if (data->cd_format != CRYPTO_DATA_RAW &&
|
|
data->cd_format != CRYPTO_DATA_UIO) {
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
}
|
|
|
|
/*
|
|
* There must be no unprocessed ciphertext.
|
|
* This happens if the length of the last ciphertext is
|
|
* not a multiple of the AES block length.
|
|
*/
|
|
if (aes_ctx->ac_remainder_len > 0) {
|
|
if ((aes_ctx->ac_flags & CTR_MODE) == 0)
|
|
return (CRYPTO_ENCRYPTED_DATA_LEN_RANGE);
|
|
else {
|
|
ret = ctr_mode_final((ctr_ctx_t *)aes_ctx, data,
|
|
aes_encrypt_block);
|
|
if (ret == CRYPTO_DATA_LEN_RANGE)
|
|
ret = CRYPTO_ENCRYPTED_DATA_LEN_RANGE;
|
|
if (ret != CRYPTO_SUCCESS)
|
|
return (ret);
|
|
}
|
|
}
|
|
|
|
if (aes_ctx->ac_flags & CCM_MODE) {
|
|
/*
|
|
* This is where all the plaintext is returned, make sure
|
|
* the plaintext buffer is big enough
|
|
*/
|
|
size_t pt_len = aes_ctx->ac_data_len;
|
|
if (data->cd_length < pt_len) {
|
|
data->cd_length = pt_len;
|
|
return (CRYPTO_BUFFER_TOO_SMALL);
|
|
}
|
|
|
|
ASSERT(aes_ctx->ac_processed_data_len == pt_len);
|
|
ASSERT(aes_ctx->ac_processed_mac_len == aes_ctx->ac_mac_len);
|
|
saved_offset = data->cd_offset;
|
|
saved_length = data->cd_length;
|
|
ret = ccm_decrypt_final((ccm_ctx_t *)aes_ctx, data,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_copy_block,
|
|
aes_xor_block);
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
data->cd_length = data->cd_offset - saved_offset;
|
|
} else {
|
|
data->cd_length = saved_length;
|
|
}
|
|
|
|
data->cd_offset = saved_offset;
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
} else if (aes_ctx->ac_flags & (GCM_MODE|GMAC_MODE)) {
|
|
/*
|
|
* This is where all the plaintext is returned, make sure
|
|
* the plaintext buffer is big enough
|
|
*/
|
|
gcm_ctx_t *ctx = (gcm_ctx_t *)aes_ctx;
|
|
size_t pt_len = ctx->gcm_processed_data_len - ctx->gcm_tag_len;
|
|
|
|
if (data->cd_length < pt_len) {
|
|
data->cd_length = pt_len;
|
|
return (CRYPTO_BUFFER_TOO_SMALL);
|
|
}
|
|
|
|
saved_offset = data->cd_offset;
|
|
saved_length = data->cd_length;
|
|
ret = gcm_decrypt_final((gcm_ctx_t *)aes_ctx, data,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_xor_block);
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
data->cd_length = data->cd_offset - saved_offset;
|
|
} else {
|
|
data->cd_length = saved_length;
|
|
}
|
|
|
|
data->cd_offset = saved_offset;
|
|
if (ret != CRYPTO_SUCCESS) {
|
|
return (ret);
|
|
}
|
|
}
|
|
|
|
|
|
if ((aes_ctx->ac_flags & (CTR_MODE|CCM_MODE|GCM_MODE|GMAC_MODE)) == 0) {
|
|
data->cd_length = 0;
|
|
}
|
|
|
|
(void) aes_free_context(ctx);
|
|
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
aes_encrypt_atomic(crypto_provider_handle_t provider,
|
|
crypto_session_id_t session_id, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_data_t *plaintext, crypto_data_t *ciphertext,
|
|
crypto_spi_ctx_template_t template, crypto_req_handle_t req)
|
|
{
|
|
aes_ctx_t aes_ctx; /* on the stack */
|
|
off_t saved_offset;
|
|
size_t saved_length;
|
|
size_t length_needed;
|
|
int ret;
|
|
|
|
AES_ARG_INPLACE(plaintext, ciphertext);
|
|
|
|
/*
|
|
* CTR, CCM, GCM, and GMAC modes do not require that plaintext
|
|
* be a multiple of AES block size.
|
|
*/
|
|
switch (mechanism->cm_type) {
|
|
case AES_CTR_MECH_INFO_TYPE:
|
|
case AES_CCM_MECH_INFO_TYPE:
|
|
case AES_GCM_MECH_INFO_TYPE:
|
|
case AES_GMAC_MECH_INFO_TYPE:
|
|
break;
|
|
default:
|
|
if ((plaintext->cd_length & (AES_BLOCK_LEN - 1)) != 0)
|
|
return (CRYPTO_DATA_LEN_RANGE);
|
|
}
|
|
|
|
if ((ret = aes_check_mech_param(mechanism, NULL, 0)) != CRYPTO_SUCCESS)
|
|
return (ret);
|
|
|
|
bzero(&aes_ctx, sizeof (aes_ctx_t));
|
|
|
|
ret = aes_common_init_ctx(&aes_ctx, template, mechanism, key,
|
|
crypto_kmflag(req), B_TRUE);
|
|
if (ret != CRYPTO_SUCCESS)
|
|
return (ret);
|
|
|
|
switch (mechanism->cm_type) {
|
|
case AES_CCM_MECH_INFO_TYPE:
|
|
length_needed = plaintext->cd_length + aes_ctx.ac_mac_len;
|
|
break;
|
|
case AES_GMAC_MECH_INFO_TYPE:
|
|
if (plaintext->cd_length != 0)
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
/* FALLTHRU */
|
|
case AES_GCM_MECH_INFO_TYPE:
|
|
length_needed = plaintext->cd_length + aes_ctx.ac_tag_len;
|
|
break;
|
|
default:
|
|
length_needed = plaintext->cd_length;
|
|
}
|
|
|
|
/* return size of buffer needed to store output */
|
|
if (ciphertext->cd_length < length_needed) {
|
|
ciphertext->cd_length = length_needed;
|
|
ret = CRYPTO_BUFFER_TOO_SMALL;
|
|
goto out;
|
|
}
|
|
|
|
saved_offset = ciphertext->cd_offset;
|
|
saved_length = ciphertext->cd_length;
|
|
|
|
/*
|
|
* Do an update on the specified input data.
|
|
*/
|
|
switch (plaintext->cd_format) {
|
|
case CRYPTO_DATA_RAW:
|
|
ret = crypto_update_iov(&aes_ctx, plaintext, ciphertext,
|
|
aes_encrypt_contiguous_blocks, aes_copy_block64);
|
|
break;
|
|
case CRYPTO_DATA_UIO:
|
|
ret = crypto_update_uio(&aes_ctx, plaintext, ciphertext,
|
|
aes_encrypt_contiguous_blocks, aes_copy_block64);
|
|
break;
|
|
default:
|
|
ret = CRYPTO_ARGUMENTS_BAD;
|
|
}
|
|
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
if (mechanism->cm_type == AES_CCM_MECH_INFO_TYPE) {
|
|
ret = ccm_encrypt_final((ccm_ctx_t *)&aes_ctx,
|
|
ciphertext, AES_BLOCK_LEN, aes_encrypt_block,
|
|
aes_xor_block);
|
|
if (ret != CRYPTO_SUCCESS)
|
|
goto out;
|
|
ASSERT(aes_ctx.ac_remainder_len == 0);
|
|
} else if (mechanism->cm_type == AES_GCM_MECH_INFO_TYPE ||
|
|
mechanism->cm_type == AES_GMAC_MECH_INFO_TYPE) {
|
|
ret = gcm_encrypt_final((gcm_ctx_t *)&aes_ctx,
|
|
ciphertext, AES_BLOCK_LEN, aes_encrypt_block,
|
|
aes_copy_block, aes_xor_block);
|
|
if (ret != CRYPTO_SUCCESS)
|
|
goto out;
|
|
ASSERT(aes_ctx.ac_remainder_len == 0);
|
|
} else if (mechanism->cm_type == AES_CTR_MECH_INFO_TYPE) {
|
|
if (aes_ctx.ac_remainder_len > 0) {
|
|
ret = ctr_mode_final((ctr_ctx_t *)&aes_ctx,
|
|
ciphertext, aes_encrypt_block);
|
|
if (ret != CRYPTO_SUCCESS)
|
|
goto out;
|
|
}
|
|
} else {
|
|
ASSERT(aes_ctx.ac_remainder_len == 0);
|
|
}
|
|
|
|
if (plaintext != ciphertext) {
|
|
ciphertext->cd_length =
|
|
ciphertext->cd_offset - saved_offset;
|
|
}
|
|
} else {
|
|
ciphertext->cd_length = saved_length;
|
|
}
|
|
ciphertext->cd_offset = saved_offset;
|
|
|
|
out:
|
|
if (aes_ctx.ac_flags & PROVIDER_OWNS_KEY_SCHEDULE) {
|
|
bzero(aes_ctx.ac_keysched, aes_ctx.ac_keysched_len);
|
|
kmem_free(aes_ctx.ac_keysched, aes_ctx.ac_keysched_len);
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
aes_decrypt_atomic(crypto_provider_handle_t provider,
|
|
crypto_session_id_t session_id, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_data_t *ciphertext, crypto_data_t *plaintext,
|
|
crypto_spi_ctx_template_t template, crypto_req_handle_t req)
|
|
{
|
|
aes_ctx_t aes_ctx; /* on the stack */
|
|
off_t saved_offset;
|
|
size_t saved_length;
|
|
size_t length_needed;
|
|
int ret;
|
|
|
|
AES_ARG_INPLACE(ciphertext, plaintext);
|
|
|
|
/*
|
|
* CCM, GCM, CTR, and GMAC modes do not require that ciphertext
|
|
* be a multiple of AES block size.
|
|
*/
|
|
switch (mechanism->cm_type) {
|
|
case AES_CTR_MECH_INFO_TYPE:
|
|
case AES_CCM_MECH_INFO_TYPE:
|
|
case AES_GCM_MECH_INFO_TYPE:
|
|
case AES_GMAC_MECH_INFO_TYPE:
|
|
break;
|
|
default:
|
|
if ((ciphertext->cd_length & (AES_BLOCK_LEN - 1)) != 0)
|
|
return (CRYPTO_ENCRYPTED_DATA_LEN_RANGE);
|
|
}
|
|
|
|
if ((ret = aes_check_mech_param(mechanism, NULL, 0)) != CRYPTO_SUCCESS)
|
|
return (ret);
|
|
|
|
bzero(&aes_ctx, sizeof (aes_ctx_t));
|
|
|
|
ret = aes_common_init_ctx(&aes_ctx, template, mechanism, key,
|
|
crypto_kmflag(req), B_FALSE);
|
|
if (ret != CRYPTO_SUCCESS)
|
|
return (ret);
|
|
|
|
switch (mechanism->cm_type) {
|
|
case AES_CCM_MECH_INFO_TYPE:
|
|
length_needed = aes_ctx.ac_data_len;
|
|
break;
|
|
case AES_GCM_MECH_INFO_TYPE:
|
|
length_needed = ciphertext->cd_length - aes_ctx.ac_tag_len;
|
|
break;
|
|
case AES_GMAC_MECH_INFO_TYPE:
|
|
if (plaintext->cd_length != 0)
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
length_needed = 0;
|
|
break;
|
|
default:
|
|
length_needed = ciphertext->cd_length;
|
|
}
|
|
|
|
/* return size of buffer needed to store output */
|
|
if (plaintext->cd_length < length_needed) {
|
|
plaintext->cd_length = length_needed;
|
|
ret = CRYPTO_BUFFER_TOO_SMALL;
|
|
goto out;
|
|
}
|
|
|
|
saved_offset = plaintext->cd_offset;
|
|
saved_length = plaintext->cd_length;
|
|
|
|
if (mechanism->cm_type == AES_GCM_MECH_INFO_TYPE ||
|
|
mechanism->cm_type == AES_GMAC_MECH_INFO_TYPE)
|
|
gcm_set_kmflag((gcm_ctx_t *)&aes_ctx, crypto_kmflag(req));
|
|
|
|
/*
|
|
* Do an update on the specified input data.
|
|
*/
|
|
switch (ciphertext->cd_format) {
|
|
case CRYPTO_DATA_RAW:
|
|
ret = crypto_update_iov(&aes_ctx, ciphertext, plaintext,
|
|
aes_decrypt_contiguous_blocks, aes_copy_block64);
|
|
break;
|
|
case CRYPTO_DATA_UIO:
|
|
ret = crypto_update_uio(&aes_ctx, ciphertext, plaintext,
|
|
aes_decrypt_contiguous_blocks, aes_copy_block64);
|
|
break;
|
|
default:
|
|
ret = CRYPTO_ARGUMENTS_BAD;
|
|
}
|
|
|
|
if (ret == CRYPTO_SUCCESS) {
|
|
if (mechanism->cm_type == AES_CCM_MECH_INFO_TYPE) {
|
|
ASSERT(aes_ctx.ac_processed_data_len
|
|
== aes_ctx.ac_data_len);
|
|
ASSERT(aes_ctx.ac_processed_mac_len
|
|
== aes_ctx.ac_mac_len);
|
|
ret = ccm_decrypt_final((ccm_ctx_t *)&aes_ctx,
|
|
plaintext, AES_BLOCK_LEN, aes_encrypt_block,
|
|
aes_copy_block, aes_xor_block);
|
|
ASSERT(aes_ctx.ac_remainder_len == 0);
|
|
if ((ret == CRYPTO_SUCCESS) &&
|
|
(ciphertext != plaintext)) {
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
} else {
|
|
plaintext->cd_length = saved_length;
|
|
}
|
|
} else if (mechanism->cm_type == AES_GCM_MECH_INFO_TYPE ||
|
|
mechanism->cm_type == AES_GMAC_MECH_INFO_TYPE) {
|
|
ret = gcm_decrypt_final((gcm_ctx_t *)&aes_ctx,
|
|
plaintext, AES_BLOCK_LEN, aes_encrypt_block,
|
|
aes_xor_block);
|
|
ASSERT(aes_ctx.ac_remainder_len == 0);
|
|
if ((ret == CRYPTO_SUCCESS) &&
|
|
(ciphertext != plaintext)) {
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
} else {
|
|
plaintext->cd_length = saved_length;
|
|
}
|
|
} else if (mechanism->cm_type != AES_CTR_MECH_INFO_TYPE) {
|
|
ASSERT(aes_ctx.ac_remainder_len == 0);
|
|
if (ciphertext != plaintext)
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
} else {
|
|
if (aes_ctx.ac_remainder_len > 0) {
|
|
ret = ctr_mode_final((ctr_ctx_t *)&aes_ctx,
|
|
plaintext, aes_encrypt_block);
|
|
if (ret == CRYPTO_DATA_LEN_RANGE)
|
|
ret = CRYPTO_ENCRYPTED_DATA_LEN_RANGE;
|
|
if (ret != CRYPTO_SUCCESS)
|
|
goto out;
|
|
}
|
|
if (ciphertext != plaintext)
|
|
plaintext->cd_length =
|
|
plaintext->cd_offset - saved_offset;
|
|
}
|
|
} else {
|
|
plaintext->cd_length = saved_length;
|
|
}
|
|
plaintext->cd_offset = saved_offset;
|
|
|
|
out:
|
|
if (aes_ctx.ac_flags & PROVIDER_OWNS_KEY_SCHEDULE) {
|
|
bzero(aes_ctx.ac_keysched, aes_ctx.ac_keysched_len);
|
|
kmem_free(aes_ctx.ac_keysched, aes_ctx.ac_keysched_len);
|
|
}
|
|
|
|
if (aes_ctx.ac_flags & CCM_MODE) {
|
|
if (aes_ctx.ac_pt_buf != NULL) {
|
|
vmem_free(aes_ctx.ac_pt_buf, aes_ctx.ac_data_len);
|
|
}
|
|
} else if (aes_ctx.ac_flags & (GCM_MODE|GMAC_MODE)) {
|
|
if (((gcm_ctx_t *)&aes_ctx)->gcm_pt_buf != NULL) {
|
|
vmem_free(((gcm_ctx_t *)&aes_ctx)->gcm_pt_buf,
|
|
((gcm_ctx_t *)&aes_ctx)->gcm_pt_buf_len);
|
|
}
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* KCF software provider context template entry points.
|
|
*/
|
|
/* ARGSUSED */
|
|
static int
|
|
aes_create_ctx_template(crypto_provider_handle_t provider,
|
|
crypto_mechanism_t *mechanism, crypto_key_t *key,
|
|
crypto_spi_ctx_template_t *tmpl, size_t *tmpl_size, crypto_req_handle_t req)
|
|
{
|
|
void *keysched;
|
|
size_t size;
|
|
int rv;
|
|
|
|
if (mechanism->cm_type != AES_ECB_MECH_INFO_TYPE &&
|
|
mechanism->cm_type != AES_CBC_MECH_INFO_TYPE &&
|
|
mechanism->cm_type != AES_CTR_MECH_INFO_TYPE &&
|
|
mechanism->cm_type != AES_CCM_MECH_INFO_TYPE &&
|
|
mechanism->cm_type != AES_GCM_MECH_INFO_TYPE &&
|
|
mechanism->cm_type != AES_GMAC_MECH_INFO_TYPE)
|
|
return (CRYPTO_MECHANISM_INVALID);
|
|
|
|
if ((keysched = aes_alloc_keysched(&size,
|
|
crypto_kmflag(req))) == NULL) {
|
|
return (CRYPTO_HOST_MEMORY);
|
|
}
|
|
|
|
/*
|
|
* Initialize key schedule. Key length information is stored
|
|
* in the key.
|
|
*/
|
|
if ((rv = init_keysched(key, keysched)) != CRYPTO_SUCCESS) {
|
|
bzero(keysched, size);
|
|
kmem_free(keysched, size);
|
|
return (rv);
|
|
}
|
|
|
|
*tmpl = keysched;
|
|
*tmpl_size = size;
|
|
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
|
|
static int
|
|
aes_free_context(crypto_ctx_t *ctx)
|
|
{
|
|
aes_ctx_t *aes_ctx = ctx->cc_provider_private;
|
|
|
|
if (aes_ctx != NULL) {
|
|
if (aes_ctx->ac_flags & PROVIDER_OWNS_KEY_SCHEDULE) {
|
|
ASSERT(aes_ctx->ac_keysched_len != 0);
|
|
bzero(aes_ctx->ac_keysched, aes_ctx->ac_keysched_len);
|
|
kmem_free(aes_ctx->ac_keysched,
|
|
aes_ctx->ac_keysched_len);
|
|
}
|
|
crypto_free_mode_ctx(aes_ctx);
|
|
ctx->cc_provider_private = NULL;
|
|
}
|
|
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
|
|
static int
|
|
aes_common_init_ctx(aes_ctx_t *aes_ctx, crypto_spi_ctx_template_t *template,
|
|
crypto_mechanism_t *mechanism, crypto_key_t *key, int kmflag,
|
|
boolean_t is_encrypt_init)
|
|
{
|
|
int rv = CRYPTO_SUCCESS;
|
|
void *keysched;
|
|
size_t size = 0;
|
|
|
|
if (template == NULL) {
|
|
if ((keysched = aes_alloc_keysched(&size, kmflag)) == NULL)
|
|
return (CRYPTO_HOST_MEMORY);
|
|
/*
|
|
* Initialize key schedule.
|
|
* Key length is stored in the key.
|
|
*/
|
|
if ((rv = init_keysched(key, keysched)) != CRYPTO_SUCCESS) {
|
|
kmem_free(keysched, size);
|
|
return (rv);
|
|
}
|
|
|
|
aes_ctx->ac_flags |= PROVIDER_OWNS_KEY_SCHEDULE;
|
|
aes_ctx->ac_keysched_len = size;
|
|
} else {
|
|
keysched = template;
|
|
}
|
|
aes_ctx->ac_keysched = keysched;
|
|
|
|
switch (mechanism->cm_type) {
|
|
case AES_CBC_MECH_INFO_TYPE:
|
|
rv = cbc_init_ctx((cbc_ctx_t *)aes_ctx, mechanism->cm_param,
|
|
mechanism->cm_param_len, AES_BLOCK_LEN, aes_copy_block64);
|
|
break;
|
|
case AES_CTR_MECH_INFO_TYPE: {
|
|
CK_AES_CTR_PARAMS *pp;
|
|
|
|
if (mechanism->cm_param == NULL ||
|
|
mechanism->cm_param_len != sizeof (CK_AES_CTR_PARAMS)) {
|
|
return (CRYPTO_MECHANISM_PARAM_INVALID);
|
|
}
|
|
pp = (CK_AES_CTR_PARAMS *)(void *)mechanism->cm_param;
|
|
rv = ctr_init_ctx((ctr_ctx_t *)aes_ctx, pp->ulCounterBits,
|
|
pp->cb, aes_copy_block);
|
|
break;
|
|
}
|
|
case AES_CCM_MECH_INFO_TYPE:
|
|
if (mechanism->cm_param == NULL ||
|
|
mechanism->cm_param_len != sizeof (CK_AES_CCM_PARAMS)) {
|
|
return (CRYPTO_MECHANISM_PARAM_INVALID);
|
|
}
|
|
rv = ccm_init_ctx((ccm_ctx_t *)aes_ctx, mechanism->cm_param,
|
|
kmflag, is_encrypt_init, AES_BLOCK_LEN, aes_encrypt_block,
|
|
aes_xor_block);
|
|
break;
|
|
case AES_GCM_MECH_INFO_TYPE:
|
|
if (mechanism->cm_param == NULL ||
|
|
mechanism->cm_param_len != sizeof (CK_AES_GCM_PARAMS)) {
|
|
return (CRYPTO_MECHANISM_PARAM_INVALID);
|
|
}
|
|
rv = gcm_init_ctx((gcm_ctx_t *)aes_ctx, mechanism->cm_param,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_copy_block,
|
|
aes_xor_block);
|
|
break;
|
|
case AES_GMAC_MECH_INFO_TYPE:
|
|
if (mechanism->cm_param == NULL ||
|
|
mechanism->cm_param_len != sizeof (CK_AES_GMAC_PARAMS)) {
|
|
return (CRYPTO_MECHANISM_PARAM_INVALID);
|
|
}
|
|
rv = gmac_init_ctx((gcm_ctx_t *)aes_ctx, mechanism->cm_param,
|
|
AES_BLOCK_LEN, aes_encrypt_block, aes_copy_block,
|
|
aes_xor_block);
|
|
break;
|
|
case AES_ECB_MECH_INFO_TYPE:
|
|
aes_ctx->ac_flags |= ECB_MODE;
|
|
}
|
|
|
|
if (rv != CRYPTO_SUCCESS) {
|
|
if (aes_ctx->ac_flags & PROVIDER_OWNS_KEY_SCHEDULE) {
|
|
bzero(keysched, size);
|
|
kmem_free(keysched, size);
|
|
}
|
|
}
|
|
|
|
return (rv);
|
|
}
|
|
|
|
static int
|
|
process_gmac_mech(crypto_mechanism_t *mech, crypto_data_t *data,
|
|
CK_AES_GCM_PARAMS *gcm_params)
|
|
{
|
|
/* LINTED: pointer alignment */
|
|
CK_AES_GMAC_PARAMS *params = (CK_AES_GMAC_PARAMS *)mech->cm_param;
|
|
|
|
if (mech->cm_type != AES_GMAC_MECH_INFO_TYPE)
|
|
return (CRYPTO_MECHANISM_INVALID);
|
|
|
|
if (mech->cm_param_len != sizeof (CK_AES_GMAC_PARAMS))
|
|
return (CRYPTO_MECHANISM_PARAM_INVALID);
|
|
|
|
if (params->pIv == NULL)
|
|
return (CRYPTO_MECHANISM_PARAM_INVALID);
|
|
|
|
gcm_params->pIv = params->pIv;
|
|
gcm_params->ulIvLen = AES_GMAC_IV_LEN;
|
|
gcm_params->ulTagBits = AES_GMAC_TAG_BITS;
|
|
|
|
if (data == NULL)
|
|
return (CRYPTO_SUCCESS);
|
|
|
|
if (data->cd_format != CRYPTO_DATA_RAW)
|
|
return (CRYPTO_ARGUMENTS_BAD);
|
|
|
|
gcm_params->pAAD = (uchar_t *)data->cd_raw.iov_base;
|
|
gcm_params->ulAADLen = data->cd_length;
|
|
return (CRYPTO_SUCCESS);
|
|
}
|
|
|
|
static int
|
|
aes_mac_atomic(crypto_provider_handle_t provider,
|
|
crypto_session_id_t session_id, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_data_t *data, crypto_data_t *mac,
|
|
crypto_spi_ctx_template_t template, crypto_req_handle_t req)
|
|
{
|
|
CK_AES_GCM_PARAMS gcm_params;
|
|
crypto_mechanism_t gcm_mech;
|
|
int rv;
|
|
|
|
if ((rv = process_gmac_mech(mechanism, data, &gcm_params))
|
|
!= CRYPTO_SUCCESS)
|
|
return (rv);
|
|
|
|
gcm_mech.cm_type = AES_GCM_MECH_INFO_TYPE;
|
|
gcm_mech.cm_param_len = sizeof (CK_AES_GCM_PARAMS);
|
|
gcm_mech.cm_param = (char *)&gcm_params;
|
|
|
|
return (aes_encrypt_atomic(provider, session_id, &gcm_mech,
|
|
key, &null_crypto_data, mac, template, req));
|
|
}
|
|
|
|
static int
|
|
aes_mac_verify_atomic(crypto_provider_handle_t provider,
|
|
crypto_session_id_t session_id, crypto_mechanism_t *mechanism,
|
|
crypto_key_t *key, crypto_data_t *data, crypto_data_t *mac,
|
|
crypto_spi_ctx_template_t template, crypto_req_handle_t req)
|
|
{
|
|
CK_AES_GCM_PARAMS gcm_params;
|
|
crypto_mechanism_t gcm_mech;
|
|
int rv;
|
|
|
|
if ((rv = process_gmac_mech(mechanism, data, &gcm_params))
|
|
!= CRYPTO_SUCCESS)
|
|
return (rv);
|
|
|
|
gcm_mech.cm_type = AES_GCM_MECH_INFO_TYPE;
|
|
gcm_mech.cm_param_len = sizeof (CK_AES_GCM_PARAMS);
|
|
gcm_mech.cm_param = (char *)&gcm_params;
|
|
|
|
return (aes_decrypt_atomic(provider, session_id, &gcm_mech,
|
|
key, mac, &null_crypto_data, template, req));
|
|
}
|