diff options
author | Todd Short <tshort@akamai.com> | 2022-04-28 14:56:11 -0400 |
---|---|---|
committer | Todd Short <todd.short@me.com> | 2022-07-29 08:32:16 -0400 |
commit | 0113ec8460a918f8bc782130db8f75540b3b1ab2 (patch) | |
tree | 2e8c7100cd3be8c2a0cc32efed6330daf3f8395f /providers/implementations | |
parent | dffafaf48174497a724d546c3483d2493fc9b64c (diff) |
Implement AES-GCM-SIV (RFC8452)
Fixes #16721
This uses AES-ECB to create a counter mode AES-CTR32 (32bit counter, I could
not get AES-CTR to work as-is), and GHASH to implement POLYVAL. Optimally,
there would be separate polyval assembly implementation(s), but the only one
I could find (and it was SSE2 x86_64 code) was not Apache 2.0 licensed.
This implementation lives only in the default provider; there is no legacy
implementation.
The code offered in #16721 is not used; that implementation sits on top of
OpenSSL, this one is embedded inside OpenSSL.
Full test vectors from RFC8452 are included, except the 0 length plaintext;
that is not supported; and I'm not sure it's worthwhile to do so.
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18693)
Diffstat (limited to 'providers/implementations')
7 files changed, 915 insertions, 1 deletions
diff --git a/providers/implementations/ciphers/build.info b/providers/implementations/ciphers/build.info index b5d9d4f6c1..c8f35bb1d8 100644 --- a/providers/implementations/ciphers/build.info +++ b/providers/implementations/ciphers/build.info @@ -26,6 +26,50 @@ $CHACHA_GOAL=../../libdefault.a $CHACHAPOLY_GOAL=../../libdefault.a $SIV_GOAL=../../libdefault.a +IF[{- !$disabled{asm} -}] + $GHASHDEF_x86=GHASH_ASM + $GHASHDEF_x86_sse2=OPENSSL_IA32_SSE2 + + $GHASHDEF_x86_64=GHASH_ASM + $GHASHDEF_x86_64_sse2=OPENSSL_IA32_SSE2 + + # ghash-ia64.s doesn't work on VMS + IF[{- $config{target} !~ /^vms-/ -}] + $GHASHDEF_ia64=GHASH_ASM + ENDIF + + $GHASHDEF_sparcv9=GHASH_ASM + + $GHASHDEF_alpha=GHASH_ASM + + $GHASHDEF_s390x=GHASH_ASM + + $GHASHDEF_armv4=GHASH_ASM + $GHASHDEF_aarch64= + + $GHASHDEF_parisc11=GHASH_ASM + $GHASHDEF_parisc20_64=$GHASHDEF_parisc11 + + $GHASHDEF_ppc32= + $GHASHDEF_ppc64=$GHASHDEF_ppc32 + + $GHASHDEF_c64xplus=GHASH_ASM + + $GHASHDEF_riscv64=GHASH_ASM + + # Now that we have defined all the arch specific variables, use the + # appropriate one, and define the appropriate macros + + IF[$GHASHDEF_{- $target{asm_arch} -}] + $GHASHDEF=$GHASHDEF_{- $target{asm_arch} -} + IF[{- !$disabled{sse2} -}] + IF[$GHASHDEF_{- $target{asm_arch} -}_sse2] + $GHASHDEF=$GHASHDEF_{- $target{asm_arch} -}_sse2 + ENDIF + ENDIF + ENDIF +ENDIF + # This source is common building blocks for all ciphers in all our providers. SOURCE[$COMMON_GOAL]=\ ciphercommon.c ciphercommon_hw.c ciphercommon_block.c \ @@ -54,8 +98,10 @@ SOURCE[$AES_GOAL]=\ SOURCE[$AES_GOAL]=cipher_aes_xts_fips.c IF[{- !$disabled{siv} -}] + DEFINE[$SIV_GOAL]=$GHASHDEF SOURCE[$SIV_GOAL]=\ - cipher_aes_siv.c cipher_aes_siv_hw.c + cipher_aes_siv.c cipher_aes_siv_hw.c \ + cipher_aes_gcm_siv.c cipher_aes_gcm_siv_hw.c cipher_aes_gcm_siv_polyval.c ENDIF IF[{- !$disabled{des} -}] diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv.c b/providers/implementations/ciphers/cipher_aes_gcm_siv.c new file mode 100644 index 0000000000..93e65d530e --- /dev/null +++ b/providers/implementations/ciphers/cipher_aes_gcm_siv.c @@ -0,0 +1,327 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* Dispatch functions for AES SIV mode */ + +/* + * This file uses the low level AES functions (which are deprecated for + * non-internal use) in order to implement provider AES ciphers. + */ +#include "internal/deprecated.h" + +#include <openssl/proverr.h> +#include "prov/implementations.h" +#include "prov/providercommon.h" +#include "prov/ciphercommon_aead.h" +#include "prov/provider_ctx.h" +#include "cipher_aes_gcm_siv.h" + +static int ossl_aes_gcm_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[]); + +static void *ossl_aes_gcm_siv_newctx(void *provctx, size_t keybits) +{ + PROV_AES_GCM_SIV_CTX *ctx; + + if (!ossl_prov_is_running()) + return NULL; + + ctx = OPENSSL_zalloc(sizeof(*ctx)); + if (ctx != NULL) { + ctx->key_len = keybits / 8; + ctx->hw = ossl_prov_cipher_hw_aes_gcm_siv(keybits); + ctx->libctx = PROV_LIBCTX_OF(provctx); + ctx->provctx = provctx; + } + return ctx; +} + +static void ossl_aes_gcm_siv_freectx(void *vctx) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + + if (ctx == NULL) + return; + + OPENSSL_clear_free(ctx->aad, ctx->aad_len); + ctx->hw->clean_ctx(ctx); + OPENSSL_clear_free(ctx, sizeof(*ctx)); +} + +static void *ossl_aes_gcm_siv_dupctx(void *vctx) +{ + PROV_AES_GCM_SIV_CTX *in = (PROV_AES_GCM_SIV_CTX *)vctx; + PROV_AES_GCM_SIV_CTX *ret; + + if (!ossl_prov_is_running()) + return NULL; + + if (in->hw == NULL) + return NULL; + + ret = OPENSSL_memdup(in, sizeof(*in)); + if (ret == NULL) { + ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); + return NULL; + } + /* NULL-out these things we create later */ + ret->aad = NULL; + ret->ecb_ctx = NULL; + + if (in->aad == NULL) { + if ((ret->aad = OPENSSL_memdup(in->aad, UP16(ret->aad_len))) == NULL) { + ERR_raise(ERR_LIB_PROV, ERR_R_MALLOC_FAILURE); + goto err; + } + } + + if (!in->hw->dup_ctx(ret, in)) + goto err; + + return ret; + err: + if (ret != NULL) { + OPENSSL_clear_free(ret->aad, ret->aad_len); + OPENSSL_free(ret); + } + return NULL; +} + +static int ossl_aes_gcm_siv_init(void *vctx, const unsigned char *key, size_t keylen, + const unsigned char *iv, size_t ivlen, + const OSSL_PARAM params[], int enc) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + + if (!ossl_prov_is_running()) + return 0; + + ctx->enc = enc; + + if (key != NULL) { + if (keylen != ctx->key_len) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); + return 0; + } + memcpy(ctx->key_gen_key, key, ctx->key_len); + } + if (iv != NULL) { + if (ivlen != sizeof(ctx->nonce)) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH); + return 0; + } + memcpy(ctx->nonce, iv, sizeof(ctx->nonce)); + } + + if (!ctx->hw->initkey(ctx)) + return 0; + + return ossl_aes_gcm_siv_set_ctx_params(ctx, params); +} + +static int ossl_aes_gcm_siv_einit(void *vctx, const unsigned char *key, size_t keylen, + const unsigned char *iv, size_t ivlen, + const OSSL_PARAM params[]) +{ + return ossl_aes_gcm_siv_init(vctx, key, keylen, iv, ivlen, params, 1); +} + +static int ossl_aes_gcm_siv_dinit(void *vctx, const unsigned char *key, size_t keylen, + const unsigned char *iv, size_t ivlen, + const OSSL_PARAM params[]) +{ + return ossl_aes_gcm_siv_init(vctx, key, keylen, iv, ivlen, params, 0); +} + +#define ossl_aes_gcm_siv_stream_update ossl_aes_gcm_siv_cipher +static int ossl_aes_gcm_siv_cipher(void *vctx, unsigned char *out, size_t *outl, + size_t outsize, const unsigned char *in, size_t inl) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + int error = 0; + + if (!ossl_prov_is_running()) + return 0; + + /* The RFC has a test case for this, but we don't try to do anything */ + if (inl == 0) { + if (outl != NULL) + *outl = 0; + return 1; + } + + if (outsize < inl) { + ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL); + return 0; + } + + error |= !ctx->hw->cipher(ctx, out, in, inl); + + if (outl != NULL && !error) + *outl = inl; + return !error; +} + +static int ossl_aes_gcm_siv_stream_final(void *vctx, unsigned char *out, size_t *outl, + size_t outsize) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + int error = 0; + + if (!ossl_prov_is_running()) + return 0; + + error |= !ctx->hw->cipher(vctx, out, NULL, 0); + + if (outl != NULL && !error) + *outl = 0; + return !error; +} + +static int ossl_aes_gcm_siv_get_ctx_params(void *vctx, OSSL_PARAM params[]) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + OSSL_PARAM *p; + + p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_AEAD_TAG); + if (p != NULL && p->data_type == OSSL_PARAM_OCTET_STRING) { + if (!ctx->enc || !ctx->generated_tag + || p->data_size != sizeof(ctx->tag) + || !OSSL_PARAM_set_octet_string(p, ctx->tag, sizeof(ctx->tag))) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER); + return 0; + } + } + p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_AEAD_TAGLEN); + if (p != NULL && !OSSL_PARAM_set_size_t(p, sizeof(ctx->tag))) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER); + return 0; + } + p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_KEYLEN); + if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->key_len)) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER); + return 0; + } + return 1; +} + +static const OSSL_PARAM aes_gcm_siv_known_gettable_ctx_params[] = { + OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL), + OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_AEAD_TAGLEN, NULL), + OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, NULL, 0), + OSSL_PARAM_END +}; + +static const OSSL_PARAM *ossl_aes_gcm_siv_gettable_ctx_params(ossl_unused void *cctx, + ossl_unused void *provctx) +{ + return aes_gcm_siv_known_gettable_ctx_params; +} + +static int ossl_aes_gcm_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[]) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + const OSSL_PARAM *p; + unsigned int speed = 0; + + if (params == NULL) + return 1; + + p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_AEAD_TAG); + if (p != NULL) { + if (p->data_type != OSSL_PARAM_OCTET_STRING + || p->data_size != sizeof(ctx->user_tag)) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER); + return 0; + } + if (!ctx->enc) { + memcpy(ctx->user_tag, p->data, sizeof(ctx->tag)); + ctx->have_user_tag = 1; + } + } + p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_SPEED); + if (p != NULL) { + if (!OSSL_PARAM_get_uint(p, &speed)) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER); + return 0; + } + ctx->speed = !!speed; + } + p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_KEYLEN); + if (p != NULL) { + size_t key_len; + + if (!OSSL_PARAM_get_size_t(p, &key_len)) { + ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER); + return 0; + } + /* The key length can not be modified */ + if (key_len != ctx->key_len) { + ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH); + return 0; + } + } + return 1; +} + +static const OSSL_PARAM aes_gcm_siv_known_settable_ctx_params[] = { + OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_KEYLEN, NULL), + OSSL_PARAM_uint(OSSL_CIPHER_PARAM_SPEED, NULL), + OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, NULL, 0), + OSSL_PARAM_END +}; +static const OSSL_PARAM *ossl_aes_gcm_siv_settable_ctx_params(ossl_unused void *cctx, + ossl_unused void *provctx) +{ + return aes_gcm_siv_known_settable_ctx_params; +} + +#define IMPLEMENT_cipher(alg, lc, UCMODE, flags, kbits, blkbits, ivbits) \ +static OSSL_FUNC_cipher_newctx_fn ossl_##alg##kbits##_##lc##_newctx; \ +static OSSL_FUNC_cipher_freectx_fn ossl_##alg##_##lc##_freectx; \ +static OSSL_FUNC_cipher_dupctx_fn ossl_##alg##_##lc##_dupctx; \ +static OSSL_FUNC_cipher_encrypt_init_fn ossl_##alg##_##lc##_einit; \ +static OSSL_FUNC_cipher_decrypt_init_fn ossl_##alg##_##lc##_dinit; \ +static OSSL_FUNC_cipher_update_fn ossl_##alg##_##lc##_stream_update; \ +static OSSL_FUNC_cipher_final_fn ossl_##alg##_##lc##_stream_final; \ +static OSSL_FUNC_cipher_cipher_fn ossl_##alg##_##lc##_cipher; \ +static OSSL_FUNC_cipher_get_params_fn ossl_##alg##_##kbits##_##lc##_get_params; \ +static OSSL_FUNC_cipher_get_ctx_params_fn ossl_##alg##_##lc##_get_ctx_params; \ +static OSSL_FUNC_cipher_gettable_ctx_params_fn ossl_##alg##_##lc##_gettable_ctx_params; \ +static OSSL_FUNC_cipher_set_ctx_params_fn ossl_##alg##_##lc##_set_ctx_params; \ +static OSSL_FUNC_cipher_settable_ctx_params_fn ossl_##alg##_##lc##_settable_ctx_params; \ +static int ossl_##alg##_##kbits##_##lc##_get_params(OSSL_PARAM params[]) \ +{ \ + return ossl_cipher_generic_get_params(params, EVP_CIPH_##UCMODE##_MODE, \ + flags, kbits, blkbits, ivbits); \ +} \ +static void * ossl_##alg##kbits##_##lc##_newctx(void *provctx) \ +{ \ + return ossl_##alg##_##lc##_newctx(provctx, kbits); \ +} \ +const OSSL_DISPATCH ossl_##alg##kbits##lc##_functions[] = { \ + { OSSL_FUNC_CIPHER_NEWCTX, (void (*)(void))ossl_##alg##kbits##_##lc##_newctx }, \ + { OSSL_FUNC_CIPHER_FREECTX, (void (*)(void))ossl_##alg##_##lc##_freectx }, \ + { OSSL_FUNC_CIPHER_DUPCTX, (void (*)(void))ossl_##alg##_##lc##_dupctx }, \ + { OSSL_FUNC_CIPHER_ENCRYPT_INIT, (void (*)(void))ossl_##alg##_##lc##_einit }, \ + { OSSL_FUNC_CIPHER_DECRYPT_INIT, (void (*)(void))ossl_##alg##_##lc##_dinit }, \ + { OSSL_FUNC_CIPHER_UPDATE, (void (*)(void))ossl_##alg##_##lc##_stream_update }, \ + { OSSL_FUNC_CIPHER_FINAL, (void (*)(void))ossl_##alg##_##lc##_stream_final }, \ + { OSSL_FUNC_CIPHER_CIPHER, (void (*)(void))ossl_##alg##_##lc##_cipher }, \ + { OSSL_FUNC_CIPHER_GET_PARAMS, (void (*)(void))ossl_##alg##_##kbits##_##lc##_get_params }, \ + { OSSL_FUNC_CIPHER_GETTABLE_PARAMS, (void (*)(void))ossl_cipher_generic_gettable_params }, \ + { OSSL_FUNC_CIPHER_GET_CTX_PARAMS, (void (*)(void))ossl_##alg##_##lc##_get_ctx_params }, \ + { OSSL_FUNC_CIPHER_GETTABLE_CTX_PARAMS, (void (*)(void))ossl_##alg##_##lc##_gettable_ctx_params }, \ + { OSSL_FUNC_CIPHER_SET_CTX_PARAMS, (void (*)(void))ossl_##alg##_##lc##_set_ctx_params }, \ + { OSSL_FUNC_CIPHER_SETTABLE_CTX_PARAMS, (void (*)(void))ossl_##alg##_##lc##_settable_ctx_params }, \ + { 0, NULL } \ +} + +IMPLEMENT_cipher(aes, gcm_siv, GCM_SIV, AEAD_FLAGS, 128, 8, 96); +IMPLEMENT_cipher(aes, gcm_siv, GCM_SIV, AEAD_FLAGS, 192, 8, 96); +IMPLEMENT_cipher(aes, gcm_siv, GCM_SIV, AEAD_FLAGS, 256, 8, 96); diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv.h b/providers/implementations/ciphers/cipher_aes_gcm_siv.h new file mode 100644 index 0000000000..1224512e3a --- /dev/null +++ b/providers/implementations/ciphers/cipher_aes_gcm_siv.h @@ -0,0 +1,78 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include <openssl/aes.h> +#include "prov/ciphercommon.h" +#include "crypto/aes_platform.h" + +#define BLOCK_SIZE 16 +#define NONCE_SIZE 12 +#define TAG_SIZE 16 + +/* AAD manipulation macros */ +#define UP16(x) (((x) + 15) & ~0x0F) +#define DOWN16(x) ((x) & ~0x0F) +#define REMAINDER16(x) ((x) & 0x0F) +#define IS16(x) (((x) & 0x0F) == 0) + +typedef struct prov_cipher_hw_aes_gcm_siv_st { + int (*initkey)(void *vctx); + int (*cipher)(void *vctx, unsigned char *out, const unsigned char *in, + size_t len); + int (*dup_ctx)(void *vdst, void *vsrc); + void (*clean_ctx)(void *vctx); +} PROV_CIPHER_HW_AES_GCM_SIV; + +/* Arranged for alignment purposes */ +typedef struct prov_aes_gcm_siv_ctx_st { + EVP_CIPHER_CTX *ecb_ctx; + const PROV_CIPHER_HW_AES_GCM_SIV *hw; /* maybe not used, yet? */ + uint8_t *aad; /* Allocated, rounded up to 16 bytes, from user */ + OSSL_LIB_CTX *libctx; + OSSL_PROVIDER *provctx; + size_t aad_len; /* actual AAD length */ + size_t key_len; + uint8_t key_gen_key[32]; /* from user */ + uint8_t msg_enc_key[32]; /* depends on key size */ + uint8_t msg_auth_key[BLOCK_SIZE]; + uint8_t tag[TAG_SIZE]; /* generated tag, given to user or compared to user */ + uint8_t user_tag[TAG_SIZE]; /* from user */ + uint8_t nonce[NONCE_SIZE]; /* from user */ + u128 Htable[16]; /* Polyval calculations via ghash */ + unsigned int enc : 1; /* Set to 1 if we are encrypting or 0 otherwise */ + unsigned int have_user_tag : 1; + unsigned int generated_tag : 1; + unsigned int used_enc : 1; + unsigned int used_dec : 1; + unsigned int speed : 1; +} PROV_AES_GCM_SIV_CTX; + +const PROV_CIPHER_HW_AES_GCM_SIV *ossl_prov_cipher_hw_aes_gcm_siv(size_t keybits); + +void ossl_polyval_ghash_init(u128 Htable[16], const uint64_t H[2]); +void ossl_polyval_ghash_hash(const u128 Htable[16], uint8_t *tag, const uint8_t *inp, size_t len); + +/* Define our own BSWAP8/BSWAP4, if not already defined */ +#ifndef BSWAP8 +static ossl_inline uint64_t BSWAP8(uint64_t n) +{ + uint8_t *p = (uint8_t *)&n; + + return (uint64_t)GETU32(p) << 32 | GETU32(p + 4); +} +#endif + +#ifndef BSWAP4 +static ossl_inline uint32_t BSWAP4(uint32_t n) +{ + uint8_t *p = (uint8_t *)&n; + + return GETU32(p); +} +#endif diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c b/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c new file mode 100644 index 0000000000..9ee5c32f4f --- /dev/null +++ b/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c @@ -0,0 +1,373 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * AES low level APIs are deprecated for public use, but still ok for internal + * use where we're using them to implement the higher level EVP interface, as is + * the case here. + */ +#include "internal/deprecated.h" + +#include <openssl/evp.h> +#include <internal/endian.h> +#include <prov/implementations.h> +#include "cipher_aes_gcm_siv.h" + +static int aes_gcm_siv_ctr32(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *init_counter, + unsigned char *out, const unsigned char *in, size_t len); + +static int aes_gcm_siv_initkey(void *vctx) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + uint8_t output[BLOCK_SIZE]; + uint32_t counter = 0x0; + size_t i; + union { + uint32_t counter; + uint8_t block[BLOCK_SIZE]; + } data; + int out_len; + EVP_CIPHER *ecb = NULL; + DECLARE_IS_ENDIAN; + + switch (ctx->key_len) { + case 16: + ecb = EVP_CIPHER_fetch(ctx->libctx, "AES-128-ECB", NULL); + break; + case 24: + ecb = EVP_CIPHER_fetch(ctx->libctx, "AES-192-ECB", NULL); + break; + case 32: + ecb = EVP_CIPHER_fetch(ctx->libctx, "AES-256-ECB", NULL); + break; + default: + goto err; + } + + if (ctx->ecb_ctx == NULL && (ctx->ecb_ctx = EVP_CIPHER_CTX_new()) == NULL) + goto err; + if (!EVP_EncryptInit_ex2(ctx->ecb_ctx, ecb, ctx->key_gen_key, NULL, NULL)) + goto err; + + memset(&data, 0, sizeof(data)); + memcpy(&data.block[sizeof(data.counter)], ctx->nonce, NONCE_SIZE); + + /* msg_auth_key is always 16 bytes in size, regardless of AES128/AES256 */ + /* counter is stored little-endian */ + for (i = 0; i < BLOCK_SIZE; i += 8) { + if (IS_LITTLE_ENDIAN) { + data.counter = counter; + } else { + data.counter = BSWAP4(counter); + } + /* Block size is 16 (128 bits), but only 8 bytes are used */ + out_len = BLOCK_SIZE; + if (!EVP_EncryptUpdate(ctx->ecb_ctx, output, &out_len, data.block, BLOCK_SIZE)) + goto err; + memcpy(&ctx->msg_auth_key[i], output, 8); + counter++; + } + + /* msg_enc_key length is directly tied to key length AES128/AES256 */ + for (i = 0; i < ctx->key_len; i += 8) { + if (IS_LITTLE_ENDIAN) { + data.counter = counter; + } else { + data.counter = BSWAP4(counter); + } + /* Block size is 16 bytes (128 bits), but only 8 bytes are used */ + out_len = BLOCK_SIZE; + if (!EVP_EncryptUpdate(ctx->ecb_ctx, output, &out_len, data.block, BLOCK_SIZE)) + goto err; + memcpy(&ctx->msg_enc_key[i], output, 8); + counter++; + } + + if (!EVP_EncryptInit_ex2(ctx->ecb_ctx, ecb, ctx->msg_enc_key, NULL, NULL)) + goto err; + + /* Freshen up the state */ + ctx->used_enc = 0; + ctx->used_dec = 0; + EVP_CIPHER_free(ecb); + return 1; + err: + EVP_CIPHER_CTX_free(ctx->ecb_ctx); + EVP_CIPHER_free(ecb); + ctx->ecb_ctx = NULL; + return 0; +} + +static int aes_gcm_siv_aad(PROV_AES_GCM_SIV_CTX *ctx, + const unsigned char *aad, size_t len) +{ + size_t to_alloc; + uint8_t *ptr; + uint64_t len64; + + /* length of 0 resets the AAD */ + if (len == 0) { + OPENSSL_free(ctx->aad); + ctx->aad = NULL; + ctx->aad_len = 0; + return 1; + } + to_alloc = UP16(ctx->aad_len + len); + /* need to check the size of the AAD per RFC8452 */ + len64 = to_alloc; + if (len64 > ((uint64_t)1 << 36)) + return 0; + ptr = OPENSSL_realloc(ctx->aad, to_alloc); + if (ptr == NULL) + return 0; + ctx->aad = ptr; + memcpy(&ctx->aad[ctx->aad_len], aad, len); + ctx->aad_len += len; + if (to_alloc > ctx->aad_len) + memset(&ctx->aad[ctx->aad_len], 0, to_alloc - ctx->aad_len); + return 1; +} + +static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx) +{ + int ret = 0; + + if (ctx->enc) + return ctx->generated_tag; + ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag)); + ret &= ctx->have_user_tag; + return ret; +} + +static int aes_gcm_siv_encrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *in, + unsigned char *out, size_t len) +{ + uint64_t len_blk[2]; + uint8_t S_s[TAG_SIZE]; + uint8_t counter_block[TAG_SIZE]; + uint8_t padding[BLOCK_SIZE]; + size_t i; + int64_t len64 = len; + int out_len; + int error = 0; + DECLARE_IS_ENDIAN; + + ctx->generated_tag = 0; + if (!ctx->speed && ctx->used_enc) + return 0; + /* need to check the size of the input! */ + if (len64 > ((int64_t)1 << 36) || len == 0) + return 0; + + if (IS_LITTLE_ENDIAN) { + len_blk[0] = (uint64_t)ctx->aad_len * 8; + len_blk[1] = (uint64_t)len * 8; + } else { + len_blk[0] = BSWAP8((uint64_t)ctx->aad_len * 8); + len_blk[1] = BSWAP8((uint64_t)len * 8); + } + memset(S_s, 0, TAG_SIZE); + ossl_polyval_ghash_init(ctx->Htable, (const uint64_t*)ctx->msg_auth_key); + + if (ctx->aad != NULL) { + /* AAD is allocated with padding, but need to adjust length */ + ossl_polyval_ghash_hash(ctx->Htable, S_s, ctx->aad, UP16(ctx->aad_len)); + } + if (DOWN16(len) > 0) + ossl_polyval_ghash_hash(ctx->Htable, S_s, (uint8_t *) in, DOWN16(len)); + if (!IS16(len)) { + /* deal with padding - probably easier to memset the padding first rather than calculate */ + memset(padding, 0, sizeof(padding)); + memcpy(padding, &in[DOWN16(len)], REMAINDER16(len)); + ossl_polyval_ghash_hash(ctx->Htable, S_s, padding, sizeof(padding)); + } + ossl_polyval_ghash_hash(ctx->Htable, S_s, (uint8_t *) len_blk, sizeof(len_blk)); + + for (i = 0; i < NONCE_SIZE; i++) + S_s[i] ^= ctx->nonce[i]; + + S_s[TAG_SIZE - 1] &= 0x7f; + out_len = sizeof(ctx->tag); + error |= !EVP_EncryptUpdate(ctx->ecb_ctx, ctx->tag, &out_len, S_s, sizeof(S_s)); + memcpy(counter_block, ctx->tag, TAG_SIZE); + counter_block[TAG_SIZE - 1] |= 0x80; + + error |= !aes_gcm_siv_ctr32(ctx, counter_block, out, in, len); + + ctx->generated_tag = !error; + /* Regardless of error */ + ctx->used_enc = 1; + return !error; +} + +static int aes_gcm_siv_decrypt(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *in, + unsigned char *out, size_t len) +{ + uint8_t counter_block[TAG_SIZE]; + uint64_t len_blk[2]; + uint8_t S_s[TAG_SIZE]; + size_t i; + uint64_t padding[2]; + int64_t len64 = len; + int out_len; + int error = 0; + DECLARE_IS_ENDIAN; + + ctx->generated_tag = 0; + if (!ctx->speed && ctx->used_dec) + return 0; + /* need to check the size of the input! */ + if (len64 > ((int64_t)1 << 36) || len == 0) + return 0; + + memcpy(counter_block, ctx->user_tag, sizeof(counter_block)); + counter_block[TAG_SIZE - 1] |= 0x80; + + error |= !aes_gcm_siv_ctr32(ctx, counter_block, out, in, len); + + if (IS_LITTLE_ENDIAN) { + len_blk[0] = (uint64_t)ctx->aad_len * 8; + len_blk[1] = (uint64_t)len * 8; + } else { + len_blk[0] = BSWAP8((uint64_t)ctx->aad_len * 8); + len_blk[1] = BSWAP8((uint64_t)len * 8); + } + memset(S_s, 0, TAG_SIZE); + ossl_polyval_ghash_init(ctx->Htable, (const uint64_t*)ctx->msg_auth_key); + if (ctx->aad != NULL) { + /* AAD allocated with padding, but need to adjust length */ + ossl_polyval_ghash_hash(ctx->Htable, S_s, ctx->aad, UP16(ctx->aad_len)); + } + if (DOWN16(len) > 0) + ossl_polyval_ghash_hash(ctx->Htable, S_s, out, DOWN16(len)); + if (!IS16(len)) { + /* deal with padding - probably easier to "memset" the padding first rather than calculate */ + padding[0] = padding[1] = 0; + memcpy(padding, &out[DOWN16(len)], REMAINDER16(len)); + ossl_polyval_ghash_hash(ctx->Htable, S_s, (uint8_t *)padding, sizeof(padding)); + } + ossl_polyval_ghash_hash(ctx->Htable, S_s, (uint8_t *)len_blk, TAG_SIZE); + + for (i = 0; i < NONCE_SIZE; i++) + S_s[i] ^= ctx->nonce[i]; + + S_s[TAG_SIZE - 1] &= 0x7f; + + /* + * In the ctx, user_tag is the one received/set by the user, + * and tag is generated from the input + */ + out_len = sizeof(ctx->tag); + error |= !EVP_EncryptUpdate(ctx->ecb_ctx, ctx->tag, &out_len, S_s, sizeof(S_s)); + ctx->generated_tag = !error; + /* Regardless of error */ + ctx->used_dec = 1; + return !error; +} + +static int aes_gcm_siv_cipher(void *vctx, unsigned char *out, + const unsigned char *in, size_t len) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + + /* EncryptFinal or DecryptFinal */ + if (in == NULL) + return aes_gcm_siv_finish(ctx); + + /* Deal with associated data */ + if (out == NULL) + return aes_gcm_siv_aad(ctx, in, len); + + if (ctx->enc) + return aes_gcm_siv_encrypt(ctx, in, out, len); + + return aes_gcm_siv_decrypt(ctx, in, out, len); +} + +static void aes_gcm_siv_clean_ctx(void *vctx) +{ + PROV_AES_GCM_SIV_CTX *ctx = (PROV_AES_GCM_SIV_CTX *)vctx; + + EVP_CIPHER_CTX_free(ctx->ecb_ctx); + ctx->ecb_ctx = NULL; +} + +static int aes_gcm_siv_dup_ctx(void *vdst, void *vsrc) +{ + PROV_AES_GCM_SIV_CTX *dst = (PROV_AES_GCM_SIV_CTX *)vdst; + PROV_AES_GCM_SIV_CTX *src = (PROV_AES_GCM_SIV_CTX *)vsrc; + + dst->ecb_ctx = NULL; + if (src->ecb_ctx != NULL) { + if ((dst->ecb_ctx = EVP_CIPHER_CTX_new()) == NULL) + goto err; + if (!EVP_CIPHER_CTX_copy(dst->ecb_ctx, src->ecb_ctx)) + goto err; + } + return 1; + + err: + EVP_CIPHER_CTX_free(dst->ecb_ctx); + dst->ecb_ctx = NULL; + return 0; +} + +static const PROV_CIPHER_HW_AES_GCM_SIV aes_gcm_siv_hw = +{ + aes_gcm_siv_initkey, + aes_gcm_siv_cipher, + aes_gcm_siv_dup_ctx, + aes_gcm_siv_clean_ctx, +}; + +const PROV_CIPHER_HW_AES_GCM_SIV *ossl_prov_cipher_hw_aes_gcm_siv(size_t keybits) +{ + return &aes_gcm_siv_hw; +} + +/* AES-GCM-SIV needs AES-CTR32, which is different than the AES-CTR implementation */ +static int aes_gcm_siv_ctr32(PROV_AES_GCM_SIV_CTX *ctx, const unsigned char *init_counter, + unsigned char *out, const unsigned char *in, size_t len) +{ + uint8_t keystream[BLOCK_SIZE]; + int out_len; + size_t i; + size_t j; + size_t todo; + uint32_t counter; + int error = 0; + union { + uint32_t x32[BLOCK_SIZE / sizeof(uint32_t)]; + uint8_t x8[BLOCK_SIZE]; + } block; + DECLARE_IS_ENDIAN; + + memcpy(&block, init_counter, sizeof(block)); + if (IS_BIG_ENDIAN) { + counter = BSWAP4(block.x32[0]); + } + + for (i = 0; i < len; i += sizeof(block)) { + out_len = BLOCK_SIZE; + error |= !EVP_EncryptUpdate(ctx->ecb_ctx, keystream, &out_len, (uint8_t*)&block, sizeof(block)); + if (IS_LITTLE_ENDIAN) { + block.x32[0]++; + } else { + counter++; + block.x32[0] = BSWAP4(counter); + } + todo = len - i; + if (todo > sizeof(keystream)) + todo = sizeof(keystream); + /* Non optimal, but avoids alignment issues */ + for (j = 0; j < todo; j++) + out[i + j] = in[i + j] ^ keystream[j]; + } + return !error; +} diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv_polyval.c b/providers/implementations/ciphers/cipher_aes_gcm_siv_polyval.c new file mode 100644 index 0000000000..66f6ed457e --- /dev/null +++ b/providers/implementations/ciphers/cipher_aes_gcm_siv_polyval.c @@ -0,0 +1,84 @@ +/* + * Copyright 2019-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * AES low level APIs are deprecated for public use, but still ok for internal + * use where we're using them to implement the higher level EVP interface, as is + * the case here. + */ +#include "internal/deprecated.h" + +#include <openssl/evp.h> +#include <internal/endian.h> +#include <prov/implementations.h> +#include "cipher_aes_gcm_siv.h" + +static ossl_inline void mulx_ghash(uint64_t *a) +{ + uint64_t t[2], mask; + + t[0] = BSWAP8(a[0]); + t[1] = BSWAP8(a[1]); + mask = -(int64_t)(t[1] & 1) & 0xe1; + mask <<= 56; + + a[1] = BSWAP8((t[1] >> 1) ^ (t[0] << 63)); + a[0] = BSWAP8((t[0] >> 1) ^ mask); +} + +#define aligned64(p) (((uintptr_t)p & 0x07) == 0) +static ossl_inline void byte_reverse16(uint8_t *out, const uint8_t *in) +{ + if (aligned64(out) && aligned64(in)) { + ((uint64_t *)out)[0] = BSWAP8(((uint64_t *)in)[1]); + ((uint64_t *)out)[1] = BSWAP8(((uint64_t *)in)[0]); + } else { + int i; + + for (i = 0; i < 16; i++) + out[i] = in[15 - i]; + } +} + +/* Initialization of POLYVAL via existing GHASH imp |