1471 lines
40 KiB
C
1471 lines
40 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;
|
|
|
|
#if defined(_KERNEL)
|
|
/*
|
|
* Determine the fastest available implementation. The benchmarks
|
|
* are run in dedicated kernel threads to allow Linux 5.0+ kernels
|
|
* to use SIMD operations. If for some reason this isn't possible,
|
|
* fallback to the generic implementations. See the comment in
|
|
* linux/simd_x86.h for additional details. Additionally, this has
|
|
* the benefit of allowing them to be run in parallel.
|
|
*/
|
|
taskqid_t aes_id = taskq_dispatch(system_taskq, aes_impl_init,
|
|
NULL, TQ_SLEEP);
|
|
taskqid_t gcm_id = taskq_dispatch(system_taskq, gcm_impl_init,
|
|
NULL, TQ_SLEEP);
|
|
|
|
if (aes_id != TASKQID_INVALID) {
|
|
taskq_wait_id(system_taskq, aes_id);
|
|
} else {
|
|
aes_impl_init(NULL);
|
|
}
|
|
|
|
if (gcm_id != TASKQID_INVALID) {
|
|
taskq_wait_id(system_taskq, gcm_id);
|
|
} else {
|
|
gcm_impl_init(NULL);
|
|
}
|
|
#else
|
|
aes_impl_init(NULL);
|
|
gcm_impl_init(NULL);
|
|
#endif
|
|
|
|
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));
|
|
}
|