openssl/providers/implementations/ciphers/ciphercommon_gcm.c

547 lines
16 KiB
C
Raw Normal View History

/*
* Copyright 2019-2020 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 gcm mode */
#include "prov/ciphercommon.h"
#include "prov/ciphercommon_gcm.h"
#include "prov/providercommonerr.h"
#include <openssl/rand.h>
#include "prov/provider_ctx.h"
static int gcm_tls_init(PROV_GCM_CTX *dat, unsigned char *aad, size_t aad_len);
static int gcm_tls_iv_set_fixed(PROV_GCM_CTX *ctx, unsigned char *iv,
size_t len);
static int gcm_tls_cipher(PROV_GCM_CTX *ctx, unsigned char *out, size_t *padlen,
const unsigned char *in, size_t len);
static int gcm_cipher_internal(PROV_GCM_CTX *ctx, unsigned char *out,
size_t *padlen, const unsigned char *in,
size_t len);
void gcm_initctx(void *provctx, PROV_GCM_CTX *ctx, size_t keybits,
const PROV_GCM_HW *hw, size_t ivlen_min)
{
ctx->pad = 1;
ctx->mode = EVP_CIPH_GCM_MODE;
ctx->taglen = UNINITIALISED_SIZET;
ctx->tls_aad_len = UNINITIALISED_SIZET;
ctx->ivlen_min = ivlen_min;
ctx->ivlen = (EVP_GCM_TLS_FIXED_IV_LEN + EVP_GCM_TLS_EXPLICIT_IV_LEN);
ctx->keylen = keybits / 8;
ctx->hw = hw;
ctx->libctx = PROV_LIBRARY_CONTEXT_OF(provctx);
}
static int gcm_init(void *vctx, const unsigned char *key, size_t keylen,
const unsigned char *iv, size_t ivlen, int enc)
{
PROV_GCM_CTX *ctx = (PROV_GCM_CTX *)vctx;
ctx->enc = enc;
if (iv != NULL) {
if (ivlen < ctx->ivlen_min || ivlen > sizeof(ctx->iv)) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH);
return 0;
}
ctx->ivlen = ivlen;
memcpy(ctx->iv, iv, ivlen);
ctx->iv_state = IV_STATE_BUFFERED;
}
if (key != NULL) {
if (keylen != ctx->keylen) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_KEY_LENGTH);
return 0;
}
return ctx->hw->setkey(ctx, key, ctx->keylen);
}
return 1;
}
int gcm_einit(void *vctx, const unsigned char *key, size_t keylen,
const unsigned char *iv, size_t ivlen)
{
return gcm_init(vctx, key, keylen, iv, ivlen, 1);
}
int gcm_dinit(void *vctx, const unsigned char *key, size_t keylen,
const unsigned char *iv, size_t ivlen)
{
return gcm_init(vctx, key, keylen, iv, ivlen, 0);
}
/* increment counter (64-bit int) by 1 */
static void ctr64_inc(unsigned char *counter)
{
int n = 8;
unsigned char c;
do {
--n;
c = counter[n];
++c;
counter[n] = c;
if (c > 0)
return;
} while (n > 0);
}
static int getivgen(PROV_GCM_CTX *ctx, unsigned char *out, size_t olen)
{
if (!ctx->iv_gen
|| !ctx->key_set
|| !ctx->hw->setiv(ctx, ctx->iv, ctx->ivlen))
return 0;
if (olen == 0 || olen > ctx->ivlen)
olen = ctx->ivlen;
memcpy(out, ctx->iv + ctx->ivlen - olen, olen);
/*
* Invocation field will be at least 8 bytes in size and so no need
* to check wrap around or increment more than last 8 bytes.
*/
ctr64_inc(ctx->iv + ctx->ivlen - 8);
ctx->iv_state = IV_STATE_COPIED;
return 1;
}
static int setivinv(PROV_GCM_CTX *ctx, unsigned char *in, size_t inl)
{
if (!ctx->iv_gen
|| !ctx->key_set
|| ctx->enc)
return 0;
memcpy(ctx->iv + ctx->ivlen - inl, in, inl);
if (!ctx->hw->setiv(ctx, ctx->iv, ctx->ivlen))
return 0;
ctx->iv_state = IV_STATE_COPIED;
return 1;
}
int gcm_get_ctx_params(void *vctx, OSSL_PARAM params[])
{
PROV_GCM_CTX *ctx = (PROV_GCM_CTX *)vctx;
OSSL_PARAM *p;
size_t sz;
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IVLEN);
if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->ivlen)) {
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->keylen)) {
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) {
size_t taglen = (ctx->taglen != UNINITIALISED_SIZET) ? ctx->taglen :
GCM_TAG_MAX_SIZE;
if (!OSSL_PARAM_set_size_t(p, taglen)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
return 0;
}
}
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IV);
if (p != NULL) {
if (ctx->iv_gen != 1 && ctx->iv_gen_rand != 1)
return 0;
if (ctx->ivlen > p->data_size) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH);
return 0;
}
if (!OSSL_PARAM_set_octet_string(p, ctx->iv, ctx->ivlen)
&& !OSSL_PARAM_set_octet_ptr(p, &ctx->iv, ctx->ivlen)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
return 0;
}
}
Support cipher provider "iv state" Some modes (e.g., CBC and OFB) update the effective IV with each block-cipher invocation, making the "IV" stored in the (historically) EVP_CIPHER_CTX or (current) PROV_CIPHER_CTX distinct from the initial IV passed in at cipher initialization time. The latter is stored in the "oiv" (original IV) field, and has historically been accessible via the EVP_CIPHER_CTX_original_iv() API. The "effective IV" has also historically been accessible, via both EVP_CIPHER_CTX_iv() and EVP_CIPHER_CTX_iv_noconst(), the latter of which allows for *write* access to the internal cipher state. This is particularly problematic given that provider-internal cipher state need not, in general, even be accessible from the same address space as libcrypto, so these APIs are not sustainable in the long term. However, it still remains necessary to provide access to the contents of the "IV state" (e.g., when serializing cipher state for in-kernel TLS); a subsequent reinitialization of a cipher context using the "IV state" as the input IV will be able to resume processing of data in a compatible manner. This problem was introduced in commit 089cb623be76b88a1eea6fcd135101037661bbc3, which effectively caused all IV queries to return the "original IV", removing access to the current IV state of the cipher. These functions for accessing the (even the "original") IV had remained undocumented for quite some time, presumably due to unease about exposing the internals of the cipher state in such a manner. Note that this also as a side effect "fixes" some "bugs" where things had been referring to the 'iv' field that should have been using the 'oiv' field. It also fixes the EVP_CTRL_GET_IV cipher control, which was clearly intended to expose the non-original IV, for use exporting the cipher state into the kernel for kTLS. Reviewed-by: Tomas Mraz <tmraz@fedoraproject.org> (Merged from https://github.com/openssl/openssl/pull/12233)
2020-06-20 04:50:22 +08:00
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_IV_STATE);
if (p != NULL) {
if (ctx->iv_gen != 1 && ctx->iv_gen_rand != 1)
return 0;
if (ctx->ivlen > p->data_size) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH);
return 0;
}
if (!OSSL_PARAM_set_octet_string(p, ctx->iv, ctx->ivlen)
&& !OSSL_PARAM_set_octet_ptr(p, &ctx->iv, ctx->ivlen)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
return 0;
}
}
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_AEAD_TLS1_AAD_PAD);
if (p != NULL && !OSSL_PARAM_set_size_t(p, ctx->tls_aad_pad_sz)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
return 0;
}
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_AEAD_TAG);
if (p != NULL) {
sz = p->data_size;
if (sz == 0
|| sz > EVP_GCM_TLS_TAG_LEN
|| !ctx->enc
|| ctx->taglen == UNINITIALISED_SIZET) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_TAG);
return 0;
}
if (!OSSL_PARAM_set_octet_string(p, ctx->buf, sz)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
return 0;
}
}
p = OSSL_PARAM_locate(params, OSSL_CIPHER_PARAM_AEAD_TLS1_GET_IV_GEN);
if (p != NULL) {
if (p->data == NULL
|| p->data_type != OSSL_PARAM_OCTET_STRING
|| !getivgen(ctx, p->data, p->data_size))
return 0;
}
return 1;
}
int gcm_set_ctx_params(void *vctx, const OSSL_PARAM params[])
{
PROV_GCM_CTX *ctx = (PROV_GCM_CTX *)vctx;
const OSSL_PARAM *p;
size_t sz;
void *vp;
p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_AEAD_TAG);
if (p != NULL) {
vp = ctx->buf;
if (!OSSL_PARAM_get_octet_string(p, &vp, EVP_GCM_TLS_TAG_LEN, &sz)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
if (sz == 0 || ctx->enc) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_TAG);
return 0;
}
ctx->taglen = sz;
}
p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_AEAD_IVLEN);
if (p != NULL) {
if (!OSSL_PARAM_get_size_t(p, &sz)) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
if (sz == 0 || sz > sizeof(ctx->iv)) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_IV_LENGTH);
return 0;
}
ctx->ivlen = sz;
}
p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_AEAD_TLS1_AAD);
if (p != NULL) {
if (p->data_type != OSSL_PARAM_OCTET_STRING) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
sz = gcm_tls_init(ctx, p->data, p->data_size);
if (sz == 0) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_AAD);
return 0;
}
ctx->tls_aad_pad_sz = sz;
}
p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_AEAD_TLS1_IV_FIXED);
if (p != NULL) {
if (p->data_type != OSSL_PARAM_OCTET_STRING) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
if (gcm_tls_iv_set_fixed(ctx, p->data, p->data_size) == 0) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
}
p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_AEAD_TLS1_SET_IV_INV);
if (p != NULL) {
if (p->data == NULL
|| p->data_type != OSSL_PARAM_OCTET_STRING
|| !setivinv(ctx, p->data, p->data_size))
return 0;
}
return 1;
}
int gcm_stream_update(void *vctx, unsigned char *out, size_t *outl,
size_t outsize, const unsigned char *in, size_t inl)
{
PROV_GCM_CTX *ctx = (PROV_GCM_CTX *)vctx;
if (inl == 0) {
*outl = 0;
return 1;
}
if (outsize < inl) {
ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
return 0;
}
if (gcm_cipher_internal(ctx, out, outl, in, inl) <= 0) {
ERR_raise(ERR_LIB_PROV, PROV_R_CIPHER_OPERATION_FAILED);
return 0;
}
return 1;
}
int gcm_stream_final(void *vctx, unsigned char *out, size_t *outl,
size_t outsize)
{
PROV_GCM_CTX *ctx = (PROV_GCM_CTX *)vctx;
int i;
i = gcm_cipher_internal(ctx, out, outl, NULL, 0);
if (i <= 0)
return 0;
*outl = 0;
return 1;
}
int gcm_cipher(void *vctx,
unsigned char *out, size_t *outl, size_t outsize,
const unsigned char *in, size_t inl)
{
PROV_GCM_CTX *ctx = (PROV_GCM_CTX *)vctx;
if (outsize < inl) {
ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
return 0;
}
if (gcm_cipher_internal(ctx, out, outl, in, inl) <= 0)
return 0;
*outl = inl;
return 1;
}
/*
* See SP800-38D (GCM) Section 8 "Uniqueness requirement on IVS and keys"
*
* See also 8.2.2 RBG-based construction.
* Random construction consists of a free field (which can be NULL) and a
* random field which will use a DRBG that can return at least 96 bits of
* entropy strength. (The DRBG must be seeded by the FIPS module).
*/
static int gcm_iv_generate(PROV_GCM_CTX *ctx, int offset)
{
int sz = ctx->ivlen - offset;
/* Must be at least 96 bits */
if (sz <= 0 || ctx->ivlen < GCM_IV_DEFAULT_SIZE)
return 0;
/* Use DRBG to generate random iv */
if (RAND_bytes_ex(ctx->libctx, ctx->iv + offset, sz) <= 0)
return 0;
ctx->iv_state = IV_STATE_BUFFERED;
ctx->iv_gen_rand = 1;
return 1;
}
static int gcm_cipher_internal(PROV_GCM_CTX *ctx, unsigned char *out,
size_t *padlen, const unsigned char *in,
size_t len)
{
size_t olen = 0;
int rv = 0;
const PROV_GCM_HW *hw = ctx->hw;
if (ctx->tls_aad_len != UNINITIALISED_SIZET)
return gcm_tls_cipher(ctx, out, padlen, in, len);
if (!ctx->key_set || ctx->iv_state == IV_STATE_FINISHED)
goto err;
/*
* FIPS requires generation of AES-GCM IV's inside the FIPS module.
* The IV can still be set externally (the security policy will state that
* this is not FIPS compliant). There are some applications
* where setting the IV externally is the only option available.
*/
if (ctx->iv_state == IV_STATE_UNINITIALISED) {
if (!ctx->enc || !gcm_iv_generate(ctx, 0))
goto err;
}
if (ctx->iv_state == IV_STATE_BUFFERED) {
if (!hw->setiv(ctx, ctx->iv, ctx->ivlen))
goto err;
ctx->iv_state = IV_STATE_COPIED;
}
if (in != NULL) {
/* The input is AAD if out is NULL */
if (out == NULL) {
if (!hw->aadupdate(ctx, in, len))
goto err;
} else {
/* The input is ciphertext OR plaintext */
if (!hw->cipherupdate(ctx, in, len, out))
goto err;
}
} else {
/* The tag must be set before actually decrypting data */
if (!ctx->enc && ctx->taglen == UNINITIALISED_SIZET)
goto err;
if (!hw->cipherfinal(ctx, ctx->buf))
goto err;
ctx->iv_state = IV_STATE_FINISHED; /* Don't reuse the IV */
goto finish;
}
olen = len;
finish:
rv = 1;
err:
*padlen = olen;
return rv;
}
static int gcm_tls_init(PROV_GCM_CTX *dat, unsigned char *aad, size_t aad_len)
{
unsigned char *buf;
size_t len;
if (aad_len != EVP_AEAD_TLS1_AAD_LEN)
return 0;
/* Save the aad for later use. */
buf = dat->buf;
memcpy(buf, aad, aad_len);
dat->tls_aad_len = aad_len;
dat->tls_enc_records = 0;
len = buf[aad_len - 2] << 8 | buf[aad_len - 1];
/* Correct length for explicit iv. */
if (len < EVP_GCM_TLS_EXPLICIT_IV_LEN)
return 0;
len -= EVP_GCM_TLS_EXPLICIT_IV_LEN;
/* If decrypting correct for tag too. */
if (!dat->enc) {
if (len < EVP_GCM_TLS_TAG_LEN)
return 0;
len -= EVP_GCM_TLS_TAG_LEN;
}
buf[aad_len - 2] = (unsigned char)(len >> 8);
buf[aad_len - 1] = (unsigned char)(len & 0xff);
/* Extra padding: tag appended to record. */
return EVP_GCM_TLS_TAG_LEN;
}
static int gcm_tls_iv_set_fixed(PROV_GCM_CTX *ctx, unsigned char *iv,
size_t len)
{
/* Special case: -1 length restores whole IV */
if (len == (size_t)-1) {
memcpy(ctx->iv, iv, ctx->ivlen);
ctx->iv_gen = 1;
ctx->iv_state = IV_STATE_BUFFERED;
return 1;
}
/* Fixed field must be at least 4 bytes and invocation field at least 8 */
if ((len < EVP_GCM_TLS_FIXED_IV_LEN)
|| (ctx->ivlen - (int)len) < EVP_GCM_TLS_EXPLICIT_IV_LEN)
return 0;
if (len > 0)
memcpy(ctx->iv, iv, len);
if (ctx->enc
&& RAND_bytes_ex(ctx->libctx, ctx->iv + len, ctx->ivlen - len) <= 0)
return 0;
ctx->iv_gen = 1;
ctx->iv_state = IV_STATE_BUFFERED;
return 1;
}
/*
* Handle TLS GCM packet format. This consists of the last portion of the IV
* followed by the payload and finally the tag. On encrypt generate IV,
* encrypt payload and write the tag. On verify retrieve IV, decrypt payload
* and verify tag.
*/
static int gcm_tls_cipher(PROV_GCM_CTX *ctx, unsigned char *out, size_t *padlen,
const unsigned char *in, size_t len)
{
int rv = 0;
size_t arg = EVP_GCM_TLS_EXPLICIT_IV_LEN;
size_t plen = 0;
unsigned char *tag = NULL;
if (!ctx->key_set)
goto err;
/* Encrypt/decrypt must be performed in place */
if (out != in || len < (EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN))
goto err;
/*
* Check for too many keys as per FIPS 140-2 IG A.5 "Key/IV Pair Uniqueness
* Requirements from SP 800-38D". The requirements is for one party to the
* communication to fail after 2^64 - 1 keys. We do this on the encrypting
* side only.
*/
if (ctx->enc && ++ctx->tls_enc_records == 0) {
ERR_raise(ERR_LIB_PROV, EVP_R_TOO_MANY_RECORDS);
goto err;
}
/*
* Set IV from start of buffer or generate IV and write to start of
* buffer.
*/
if (ctx->enc) {
if (!getivgen(ctx, out, arg))
goto err;
} else {
if (!setivinv(ctx, out, arg))
goto err;
}
/* Fix buffer and length to point to payload */
in += EVP_GCM_TLS_EXPLICIT_IV_LEN;
out += EVP_GCM_TLS_EXPLICIT_IV_LEN;
len -= EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN;
tag = ctx->enc ? out + len : (unsigned char *)in + len;
if (!ctx->hw->oneshot(ctx, ctx->buf, ctx->tls_aad_len, in, len, out, tag,
EVP_GCM_TLS_TAG_LEN)) {
if (!ctx->enc)
OPENSSL_cleanse(out, len);
goto err;
}
if (ctx->enc)
plen = len + EVP_GCM_TLS_EXPLICIT_IV_LEN + EVP_GCM_TLS_TAG_LEN;
else
plen = len;
rv = 1;
err:
ctx->iv_state = IV_STATE_FINISHED;
ctx->tls_aad_len = UNINITIALISED_SIZET;
*padlen = plen;
return rv;
}