Files
cli/vendor/aws-lc-sys/aws-lc/crypto/cipher_extra/e_chacha20poly1305.c

729 lines
25 KiB
C

// Copyright (c) 2014, Google Inc.
// SPDX-License-Identifier: ISC
#include <openssl/aead.h>
#include <string.h>
#include <openssl/chacha.h>
#include <openssl/cipher.h>
#include <openssl/err.h>
#include <openssl/mem.h>
#include <openssl/nid.h>
#include <openssl/poly1305.h>
#include <openssl/type_check.h>
#include "../chacha/internal.h"
#include "../fipsmodule/cipher/internal.h"
#include "../internal.h"
#include "internal.h"
#define CHACHA_KEY_LEN 32
#define CHACHA_IV_LEN 12
#define CHACHA_BLOCK_LEN 64
#define CHACHA_CTR_IV_LEN 16
// ChaCha-Poly specific context within an EVP_CIPHER_CTX
#define CCP_CTX(ctx) ((CIPHER_CHACHA_POLY_CTX *) ctx->cipher_data)
// Return the CIPHER_CHACHA_KEY from a CIPHER_CHACHA_POLY_CTX
#define CC_KEY(ccp) (&(ccp)->key)
// Return the poly1305_state from a CIPHER_CHACHA_POLY_CTX
#define POLY_CTX(ccp) (&(ccp)->poly_ctx)
// Struct for Poly1305 key within an EVP_AEAD_CTX
typedef struct aead_chacha20_poly1305_ctx {
uint8_t key[CHACHA_KEY_LEN];
} AEAD_CHACHA_POLY_CTX;
// Struct for ChaCha key within an EVP_CIPHER_CTX
typedef struct {
uint32_t key[CHACHA_KEY_LEN / 4];
// Buffer containing both the counter and nonce.
// The index 0 is the counter and the remaining portion is the nonce.
uint32_t counter_nonce[CHACHA_CTR_IV_LEN / 4];
// Buffer for any partially used keys
uint8_t buf[CHACHA_BLOCK_LEN];
uint32_t partial_len;
} CIPHER_CHACHA_KEY;
typedef struct cipher_chacha_poly_ctx {
CIPHER_CHACHA_KEY key;
uint32_t iv[CHACHA_IV_LEN / 4];
uint8_t tag_len;
uint8_t tag[POLY1305_TAG_LEN];
// Use 64-bit integers so this struct can be passed directly into poly1305
struct { uint64_t aad, text; } len;
int32_t poly_initialized;
int32_t pad_aad;
poly1305_state poly_ctx;
} CIPHER_CHACHA_POLY_CTX;
OPENSSL_STATIC_ASSERT(sizeof(((EVP_AEAD_CTX *)NULL)->state) >=
sizeof(AEAD_CHACHA_POLY_CTX),
AEAD_state_is_too_small)
OPENSSL_STATIC_ASSERT(alignof(union evp_aead_ctx_st_state) >=
alignof(AEAD_CHACHA_POLY_CTX),
AEAD_state_has_insufficient_alignment)
static int aead_chacha20_poly1305_init(EVP_AEAD_CTX *ctx, const uint8_t *key,
size_t key_len, size_t tag_len) {
AEAD_CHACHA_POLY_CTX *c20_ctx =
(AEAD_CHACHA_POLY_CTX *)&ctx->state;
if (tag_len == 0) {
tag_len = POLY1305_TAG_LEN;
}
if (tag_len > POLY1305_TAG_LEN) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
return 0;
}
if (key_len != sizeof(c20_ctx->key)) {
return 0; // internal error - EVP_AEAD_CTX_init should catch this.
}
OPENSSL_memcpy(c20_ctx->key, key, key_len);
ctx->tag_len = tag_len;
return 1;
}
static void aead_chacha20_poly1305_cleanup(EVP_AEAD_CTX *ctx) {}
static void poly1305_update_length(poly1305_state *poly1305, size_t data_len) {
uint8_t length_bytes[8];
for (unsigned i = 0; i < sizeof(length_bytes); i++) {
length_bytes[i] = data_len;
data_len >>= 8;
}
CRYPTO_poly1305_update(poly1305, length_bytes, sizeof(length_bytes));
}
// calc_tag fills |tag| with the authentication tag for the given inputs.
static void calc_tag(uint8_t tag[POLY1305_TAG_LEN], const uint8_t *key,
const uint8_t nonce[CHACHA_IV_LEN], const uint8_t *ad,
size_t ad_len, const uint8_t *ciphertext,
size_t ciphertext_len, const uint8_t *ciphertext_extra,
size_t ciphertext_extra_len) {
alignas(16) uint8_t poly1305_key[CHACHA_KEY_LEN];
OPENSSL_memset(poly1305_key, 0, sizeof(poly1305_key));
CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key, nonce,
0);
static const uint8_t padding[16] = {0}; // Padding is all zeros.
poly1305_state ctx;
CRYPTO_poly1305_init(&ctx, poly1305_key);
CRYPTO_poly1305_update(&ctx, ad, ad_len);
if (ad_len % 16 != 0) {
CRYPTO_poly1305_update(&ctx, padding, sizeof(padding) - (ad_len % 16));
}
CRYPTO_poly1305_update(&ctx, ciphertext, ciphertext_len);
CRYPTO_poly1305_update(&ctx, ciphertext_extra, ciphertext_extra_len);
const size_t ciphertext_total = ciphertext_len + ciphertext_extra_len;
if (ciphertext_total % 16 != 0) {
CRYPTO_poly1305_update(&ctx, padding,
sizeof(padding) - (ciphertext_total % 16));
}
poly1305_update_length(&ctx, ad_len);
poly1305_update_length(&ctx, ciphertext_total);
CRYPTO_poly1305_finish(&ctx, tag);
}
static int chacha20_poly1305_seal_scatter(
const uint8_t *key, uint8_t *out, uint8_t *out_tag, size_t *out_tag_len,
size_t max_out_tag_len, const uint8_t *nonce, size_t nonce_len,
const uint8_t *in, size_t in_len, const uint8_t *extra_in,
size_t extra_in_len, const uint8_t *ad, size_t ad_len, size_t tag_len) {
if (extra_in_len + tag_len < tag_len) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
return 0;
}
if (max_out_tag_len < tag_len + extra_in_len) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
return 0;
}
if (nonce_len != CHACHA_IV_LEN) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE);
return 0;
}
// |CRYPTO_chacha_20| uses a 32-bit block counter. Therefore we disallow
// individual operations that work on more than 256GB at a time.
// |in_len_64| is needed because, on 32-bit platforms, size_t is only
// 32-bits and this produces a warning because it's always false.
// Casting to uint64_t inside the conditional is not sufficient to stop
// the warning.
const uint64_t in_len_64 = in_len;
if (in_len_64 >= (UINT64_C(1) << 32) * 64 - 64) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
return 0;
}
if (max_out_tag_len < tag_len) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL);
return 0;
}
// The the extra input is given, it is expected to be very short and so is
// encrypted byte-by-byte first.
if (extra_in_len) {
static const size_t kChaChaBlockSize = 64;
uint32_t block_counter = (uint32_t)(1 + (in_len / kChaChaBlockSize));
size_t offset = in_len % kChaChaBlockSize;
uint8_t block[64 /* kChaChaBlockSize */];
for (size_t done = 0; done < extra_in_len; block_counter++) {
memset(block, 0, sizeof(block));
CRYPTO_chacha_20(block, block, sizeof(block), key, nonce, block_counter);
for (size_t i = offset; i < sizeof(block) && done < extra_in_len;
i++, done++) {
out_tag[done] = extra_in[done] ^ block[i];
}
offset = 0;
}
}
union chacha20_poly1305_seal_data data;
if (chacha20_poly1305_asm_capable()) {
OPENSSL_memcpy(data.in.key, key, CHACHA_KEY_LEN);
data.in.counter = 0;
OPENSSL_memcpy(data.in.nonce, nonce, CHACHA_IV_LEN);
data.in.extra_ciphertext = out_tag;
data.in.extra_ciphertext_len = extra_in_len;
chacha20_poly1305_seal(out, in, in_len, ad, ad_len, &data);
} else {
CRYPTO_chacha_20(out, in, in_len, key, nonce, 1);
calc_tag(data.out.tag, key, nonce, ad, ad_len, out, in_len, out_tag,
extra_in_len);
}
OPENSSL_memcpy(out_tag + extra_in_len, data.out.tag, tag_len);
*out_tag_len = extra_in_len + tag_len;
return 1;
}
static int aead_chacha20_poly1305_seal_scatter(
const EVP_AEAD_CTX *ctx, uint8_t *out, uint8_t *out_tag,
size_t *out_tag_len, size_t max_out_tag_len, const uint8_t *nonce,
size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *extra_in,
size_t extra_in_len, const uint8_t *ad, size_t ad_len) {
const AEAD_CHACHA_POLY_CTX *c20_ctx =
(AEAD_CHACHA_POLY_CTX *)&ctx->state;
return chacha20_poly1305_seal_scatter(
c20_ctx->key, out, out_tag, out_tag_len, max_out_tag_len, nonce,
nonce_len, in, in_len, extra_in, extra_in_len, ad, ad_len, ctx->tag_len);
}
static int aead_xchacha20_poly1305_seal_scatter(
const EVP_AEAD_CTX *ctx, uint8_t *out, uint8_t *out_tag,
size_t *out_tag_len, size_t max_out_tag_len, const uint8_t *nonce,
size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *extra_in,
size_t extra_in_len, const uint8_t *ad, size_t ad_len) {
const AEAD_CHACHA_POLY_CTX *c20_ctx =
(AEAD_CHACHA_POLY_CTX *)&ctx->state;
if (nonce_len != 24) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE);
return 0;
}
alignas(4) uint8_t derived_key[CHACHA_KEY_LEN];
alignas(4) uint8_t derived_nonce[CHACHA_IV_LEN];
CRYPTO_hchacha20(derived_key, c20_ctx->key, nonce);
OPENSSL_memset(derived_nonce, 0, 4);
OPENSSL_memcpy(&derived_nonce[4], &nonce[16], 8);
return chacha20_poly1305_seal_scatter(
derived_key, out, out_tag, out_tag_len, max_out_tag_len, derived_nonce,
sizeof(derived_nonce), in, in_len, extra_in, extra_in_len, ad, ad_len,
ctx->tag_len);
}
static int chacha20_poly1305_open_gather(const uint8_t *key, uint8_t *out,
const uint8_t *nonce, size_t nonce_len,
const uint8_t *in, size_t in_len,
const uint8_t *in_tag,
size_t in_tag_len, const uint8_t *ad,
size_t ad_len, size_t tag_len) {
if (nonce_len != CHACHA_IV_LEN) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE);
return 0;
}
if (in_tag_len != tag_len) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
return 0;
}
// |CRYPTO_chacha_20| uses a 32-bit block counter. Therefore we disallow
// individual operations that work on more than 256GB at a time.
// |in_len_64| is needed because, on 32-bit platforms, size_t is only
// 32-bits and this produces a warning because it's always false.
// Casting to uint64_t inside the conditional is not sufficient to stop
// the warning.
const uint64_t in_len_64 = in_len;
if (in_len_64 >= (UINT64_C(1) << 32) * 64 - 64) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE);
return 0;
}
union chacha20_poly1305_open_data data;
if (chacha20_poly1305_asm_capable()) {
OPENSSL_memcpy(data.in.key, key, CHACHA_KEY_LEN);
data.in.counter = 0;
OPENSSL_memcpy(data.in.nonce, nonce, CHACHA_IV_LEN);
chacha20_poly1305_open(out, in, in_len, ad, ad_len, &data);
} else {
calc_tag(data.out.tag, key, nonce, ad, ad_len, in, in_len, NULL, 0);
CRYPTO_chacha_20(out, in, in_len, key, nonce, 1);
}
if (CRYPTO_memcmp(data.out.tag, in_tag, tag_len) != 0) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT);
return 0;
}
return 1;
}
static int aead_chacha20_poly1305_open_gather(
const EVP_AEAD_CTX *ctx, uint8_t *out, const uint8_t *nonce,
size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *in_tag,
size_t in_tag_len, const uint8_t *ad, size_t ad_len) {
const AEAD_CHACHA_POLY_CTX *c20_ctx =
(AEAD_CHACHA_POLY_CTX *)&ctx->state;
return chacha20_poly1305_open_gather(c20_ctx->key, out, nonce, nonce_len, in,
in_len, in_tag, in_tag_len, ad, ad_len,
ctx->tag_len);
}
static int aead_xchacha20_poly1305_open_gather(
const EVP_AEAD_CTX *ctx, uint8_t *out, const uint8_t *nonce,
size_t nonce_len, const uint8_t *in, size_t in_len, const uint8_t *in_tag,
size_t in_tag_len, const uint8_t *ad, size_t ad_len) {
const AEAD_CHACHA_POLY_CTX *c20_ctx =
(AEAD_CHACHA_POLY_CTX *)&ctx->state;
if (nonce_len != 24) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE);
return 0;
}
alignas(4) uint8_t derived_key[CHACHA_KEY_LEN];
alignas(4) uint8_t derived_nonce[CHACHA_IV_LEN];
CRYPTO_hchacha20(derived_key, c20_ctx->key, nonce);
OPENSSL_memset(derived_nonce, 0, 4);
OPENSSL_memcpy(&derived_nonce[4], &nonce[16], 8);
return chacha20_poly1305_open_gather(
derived_key, out, derived_nonce, sizeof(derived_nonce), in, in_len,
in_tag, in_tag_len, ad, ad_len, ctx->tag_len);
}
static const EVP_AEAD aead_chacha20_poly1305 = {
CHACHA_KEY_LEN, // key len
CHACHA_IV_LEN, // nonce len
POLY1305_TAG_LEN, // overhead
POLY1305_TAG_LEN, // max tag length
AEAD_CHACHA20_POLY1305_ID, // evp_aead_id
1, // seal_scatter_supports_extra_in
aead_chacha20_poly1305_init,
NULL, // init_with_direction
aead_chacha20_poly1305_cleanup,
NULL /* open */,
aead_chacha20_poly1305_seal_scatter,
aead_chacha20_poly1305_open_gather,
NULL, // get_iv
NULL, // tag_len
NULL, // serialize_state
NULL, // deserialize_state
};
static const EVP_AEAD aead_xchacha20_poly1305 = {
CHACHA_KEY_LEN, // key len
24, // nonce len
POLY1305_TAG_LEN, // overhead
POLY1305_TAG_LEN, // max tag length
AEAD_XCHACHA20_POLY1305_ID, // evp_aead_id
1, // seal_scatter_supports_extra_in
aead_chacha20_poly1305_init,
NULL, // init_with_direction
aead_chacha20_poly1305_cleanup,
NULL /* open */,
aead_xchacha20_poly1305_seal_scatter,
aead_xchacha20_poly1305_open_gather,
NULL, // get_iv
NULL, // tag_len
NULL, // serialize_state
NULL, // deserialize_state
};
const EVP_AEAD *EVP_aead_chacha20_poly1305(void) {
return &aead_chacha20_poly1305;
}
const EVP_AEAD *EVP_aead_xchacha20_poly1305(void) {
return &aead_xchacha20_poly1305;
}
static int cipher_chacha20_poly1305_init_key(CIPHER_CHACHA_POLY_CTX *ctx,
const uint8_t user_key[CHACHA_KEY_LEN],
const uint8_t counter_nonce[CHACHA_CTR_IV_LEN])
{
CIPHER_CHACHA_KEY *key = CC_KEY(ctx);
uint32_t i;
if (user_key) {
for (i = 0; i < (CHACHA_KEY_LEN / 4); i++) {
key->key[i] = CRYPTO_load_u32_le(user_key + (i * 4));
}
}
if (counter_nonce) {
for (i = 0; i < CHACHA_CTR_IV_LEN / 4; i++) {
key->counter_nonce[i] = CRYPTO_load_u32_le(counter_nonce + (i * 4));
}
}
key->partial_len = 0;
return 1;
}
static int cipher_chacha20_poly1305_init(EVP_CIPHER_CTX *ctx,
const uint8_t *key,
const uint8_t *iv, int32_t enc) {
CIPHER_CHACHA_POLY_CTX *cipher_ctx = CCP_CTX(ctx);
cipher_ctx->len.aad = 0;
cipher_ctx->len.text = 0;
cipher_ctx->pad_aad = 0;
cipher_ctx->poly_initialized = 0;
if (!key && !iv) {
return 1;
}
// Init can be called multiple times before starting the cipher to
// independently initialize any combination of Key/IV/NULL.
if (iv != NULL) {
// Start the counter at 0 and copy over the nonce(iv)
uint8_t counter_nonce[CHACHA_CTR_IV_LEN] = {0};
OPENSSL_memcpy(counter_nonce + CHACHA_CTR_IV_LEN - CHACHA_IV_LEN, iv,
CHACHA_IV_LEN);
cipher_chacha20_poly1305_init_key(cipher_ctx, key, counter_nonce);
// Nonce occupies the last 3 indices of the array
cipher_ctx->iv[0] = cipher_ctx->key.counter_nonce[1];
cipher_ctx->iv[1] = cipher_ctx->key.counter_nonce[2];
cipher_ctx->iv[2] = cipher_ctx->key.counter_nonce[3];
} else {
cipher_chacha20_poly1305_init_key(cipher_ctx, key, NULL);
}
return 1;
}
static int cipher_chacha20_do_cipher(EVP_CIPHER_CTX *ctx, uint8_t *out,
const uint8_t *in, size_t in_len)
{
CIPHER_CHACHA_POLY_CTX *cipher_ctx = CCP_CTX(ctx);
CIPHER_CHACHA_KEY *key = CC_KEY(cipher_ctx);
uint32_t n, rem, counter;
// Complete any partial block
n = key->partial_len;
if (n) {
// Compute the cipher using our partially used key and any new input up
// to the next block
while (in_len && n < CHACHA_BLOCK_LEN) {
// Compute 1-byte of output by xor'ing 1-byte of input with the
// corresponding key byte and increment it all for the next byte.
*out++ = *in++ ^ key->buf[n++];
in_len--;
}
key->partial_len = n;
// If we consumed all the input, we're done
if (in_len == 0) {
return 1;
}
// If we completed a block, increment the counter
if (n == CHACHA_BLOCK_LEN) {
key->partial_len = 0;
// If this overflows we let the cipher wrap. This would be a bug in the
// calling code as overflow behavior is not defined in RFC 8439.
key->counter_nonce[0]++;
}
}
#ifdef OPENSSL_BIG_ENDIAN
// |CRYPTO_chacha_20| expects the input as a little-endian byte array.
uint8_t chacha_key[CHACHA_KEY_LEN];
uint8_t nonce[CHACHA_IV_LEN];
for(size_t i = 0; i < CHACHA_KEY_LEN / 4; i++) {
CRYPTO_store_u32_le(chacha_key + (i * sizeof(uint32_t)),
cipher_ctx->key.key[i]);
}
for(size_t i = 0; i < CHACHA_IV_LEN / 4; i++) {
CRYPTO_store_u32_le(nonce + (i * sizeof(uint32_t)),
cipher_ctx->iv[i]);
}
#else
const uint8_t *chacha_key = (const uint8_t *) cipher_ctx->key.key;
const uint8_t *nonce = (const uint8_t *) cipher_ctx->iv;
#endif
// Truncate down to the last complete block prior to the bulk cipher
rem = (uint32_t)(in_len % CHACHA_BLOCK_LEN);
in_len -= rem;
counter = key->counter_nonce[0];
while (in_len >= CHACHA_BLOCK_LEN) {
size_t blocks = in_len / CHACHA_BLOCK_LEN;
// 1<<28 is just a not-so-small yet not-so-large number... Below
// condition is practically never met, but it has to be checked for code
// correctness.
if (sizeof(size_t) > sizeof(uint32_t) && blocks > (1U<<28)) {
blocks = (1U << 28);
}
// As ChaCha20_ctr32 operates on 32-bit counter, caller has to handle
// overflow. 'if' below detects the overflow, which is then handled by
// limiting the amount of blocks to the exact overflow point. This while
// loop then continues the cipher by wrapping around with counter=0.
counter += (uint32_t) blocks;
if (counter < blocks) {
blocks -= counter;
counter = 0;
}
blocks *= CHACHA_BLOCK_LEN;
CRYPTO_chacha_20(out, in, blocks, chacha_key, nonce,
key->counter_nonce[0]);
in_len -= blocks;
in += blocks;
out += blocks;
key->counter_nonce[0] = counter;
}
// Start the next block if we have any leftover input
if (rem) {
OPENSSL_memset(key->buf, 0, sizeof(key->buf));
// Obtain the current key and store it in the context
CRYPTO_chacha_20(key->buf, key->buf, CHACHA_BLOCK_LEN, chacha_key, nonce,
key->counter_nonce[0]);
for (n = 0; n < rem; n++) {
out[n] = in[n] ^ key->buf[n];
}
key->partial_len = rem;
}
return 1;
}
static int cipher_chacha20_poly1305_do_cipher(
EVP_CIPHER_CTX *ctx, unsigned char *out, const unsigned char *in,
size_t in_len) {
CIPHER_CHACHA_POLY_CTX *cipher_ctx = CCP_CTX(ctx);
poly1305_state *poly_ctx = POLY_CTX(cipher_ctx);
size_t remainder;
if (!cipher_ctx->poly_initialized) {
#ifdef OPENSSL_BIG_ENDIAN
// |CRYPTO_chacha_20| expects the input as a little-endian byte array.
uint8_t chacha_key[CHACHA_KEY_LEN];
uint8_t nonce[CHACHA_IV_LEN];
for(int i = 0; i < CHACHA_KEY_LEN / 4; i++) {
CRYPTO_store_u32_le(chacha_key + (i * sizeof(uint32_t)),
cipher_ctx->key.key[i]);
}
for(size_t i = 0; i < CHACHA_IV_LEN / 4; i++) {
CRYPTO_store_u32_le(nonce + (i * sizeof(uint32_t)),
cipher_ctx->iv[i]);
}
#else
const uint8_t *chacha_key = (const uint8_t *) cipher_ctx->key.key;
const uint8_t *nonce = (const uint8_t *) cipher_ctx->iv;
#endif
// Obtain the poly1305 key by computing the 0th chacha20 key
alignas(16) uint8_t poly1305_key[CHACHA_KEY_LEN];
OPENSSL_memset(poly1305_key, 0, sizeof(poly1305_key));
CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key),
chacha_key, nonce, 0);
// Initialize the poly1305 context
CRYPTO_poly1305_init(poly_ctx, poly1305_key);
cipher_ctx->key.counter_nonce[0] = 1;
cipher_ctx->key.partial_len = 0;
cipher_ctx->len.aad = 0;
cipher_ctx->len.text = 0;
cipher_ctx->poly_initialized = 1;
}
// Handle an |EVP_CipherUpdate|
if (in) {
if (out == NULL) {
// NULL |out| signals an AAD update
CRYPTO_poly1305_update(poly_ctx, in, in_len);
cipher_ctx->len.aad += in_len;
cipher_ctx->pad_aad = 1;
return (int32_t) in_len;
} else {
// Finish AAD by applying padding
if (cipher_ctx->pad_aad) {
remainder = cipher_ctx->len.aad % POLY1305_TAG_LEN;
if (remainder != 0) {
static const uint8_t padding[POLY1305_TAG_LEN] = {0};
CRYPTO_poly1305_update(poly_ctx, padding, sizeof(padding) - remainder);
}
cipher_ctx->pad_aad = 0;
}
// cipher/plain text |EVP_CipherUpdate|
if (EVP_CIPHER_CTX_encrypting(ctx)) {
// Encryption
cipher_chacha20_do_cipher(ctx, out, in, in_len);
// Update poly1305 with computed ciphertext
CRYPTO_poly1305_update(poly_ctx, out, in_len);
cipher_ctx->len.text += in_len;
} else {
// Decryption
// Update poly1305 with incoming ciphertext
CRYPTO_poly1305_update(poly_ctx, in, in_len);
cipher_chacha20_do_cipher(ctx, out, in, in_len);
cipher_ctx->len.text += in_len;
}
}
}
// Process an |EVP_CipherFinal|
if (in == NULL) {
uint8_t temp[POLY1305_TAG_LEN];
static const uint8_t padding[POLY1305_TAG_LEN] = {0};
// Finish AAD inp case there were no intermediate Update() calls
if (cipher_ctx->pad_aad) {
remainder = cipher_ctx->len.aad % POLY1305_TAG_LEN;
if (remainder != 0) {
CRYPTO_poly1305_update(poly_ctx, padding, sizeof(padding) - remainder);
}
cipher_ctx->pad_aad = 0;
}
// Apply padding for the text
remainder = cipher_ctx->len.text % POLY1305_TAG_LEN;
if (remainder != 0) {
CRYPTO_poly1305_update(poly_ctx, padding, sizeof(padding) - remainder);
}
// ChaCha20-Poly1305 passes the AAD and CT lengths through Poly1305 as two
// 64-bit little-endian integers.
#ifdef OPENSSL_BIG_ENDIAN
uint8_t length_bytes[2 * sizeof(uint64_t)];
CRYPTO_store_u64_le(length_bytes, cipher_ctx->len.aad);
CRYPTO_store_u64_le(length_bytes + sizeof(uint64_t), cipher_ctx->len.text);
#else
// For a little-endian platform, the struct's layout in memory works as-is.
const uint8_t *length_bytes = (const uint8_t *) &cipher_ctx->len;
#endif
CRYPTO_poly1305_update(poly_ctx, length_bytes, 2 * sizeof(uint64_t));
// Compute the tag and write it to scratch or the cipher context
CRYPTO_poly1305_finish(poly_ctx, EVP_CIPHER_CTX_encrypting(ctx) ?
cipher_ctx->tag : temp);
cipher_ctx->poly_initialized = 0;
// Check the tags if we're decrypting
if (!EVP_CIPHER_CTX_encrypting(ctx)) {
if (CRYPTO_memcmp(temp, cipher_ctx->tag, cipher_ctx->tag_len)) {
return -1;
}
}
}
return (int32_t) in_len;
}
static void cipher_chacha20_poly1305_cleanup(EVP_CIPHER_CTX *ctx) {
if (ctx->cipher_data) {
OPENSSL_cleanse(ctx->cipher_data, sizeof(CIPHER_CHACHA_POLY_CTX));
}
}
static int32_t cipher_chacha20_poly1305_ctrl(EVP_CIPHER_CTX *ctx, int32_t type,
int32_t arg, void *ptr) {
CIPHER_CHACHA_POLY_CTX *cipher_ctx = CCP_CTX(ctx);
switch (type) {
case EVP_CTRL_INIT:
if (cipher_ctx == NULL) {
cipher_ctx = ctx->cipher_data = OPENSSL_zalloc(ctx->cipher->ctx_size);
if (cipher_ctx == NULL) {
OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_INITIALIZATION_ERROR);
return 0;
}
} else {
cipher_ctx->len.aad = 0;
cipher_ctx->len.text = 0;
cipher_ctx->pad_aad = 0;
cipher_ctx->poly_initialized = 0;
cipher_ctx->tag_len = 0;
}
return 1;
case EVP_CTRL_COPY:
if (cipher_ctx && cipher_ctx->poly_initialized) {
// The poly1305 context needs to be aligned on a 64-byte boundary.
// The destination context doesn't necessarily have the same
// alignment so we have to fix that here.
EVP_CIPHER_CTX *dst = (EVP_CIPHER_CTX *) ptr;
void *source_base = align_pointer((void *) POLY_CTX(CCP_CTX(ctx)), 64);
void *dest_base = align_pointer((void *) POLY_CTX(CCP_CTX(dst)), 64);
// We have 63 bytes of padding for alignment, so the actual size of
// the poly1305 context is the difference of that and the total buffer.
size_t length = sizeof(poly1305_state) - 63;
OPENSSL_memcpy(dest_base, source_base, length);
}
return 1;
case EVP_CTRL_AEAD_SET_IVLEN:
if (arg != CHACHA_IV_LEN) {
return 0;
}
return 1;
case EVP_CTRL_AEAD_GET_TAG:
if (arg <= 0 || arg > POLY1305_TAG_LEN ||
!EVP_CIPHER_CTX_encrypting(ctx)) {
return 0;
}
OPENSSL_memcpy(ptr, cipher_ctx->tag, arg);
return 1;
case EVP_CTRL_AEAD_SET_TAG:
if (arg <= 0 || arg > POLY1305_TAG_LEN ||
EVP_CIPHER_CTX_encrypting(ctx)) {
return 0;
}
if (ptr != NULL) {
OPENSSL_memcpy(cipher_ctx->tag, ptr, arg);
cipher_ctx->tag_len = arg;
}
return 1;
default:
return -1;
}
}
static EVP_CIPHER cipher_chacha20_poly1305 = {
NID_chacha20_poly1305,
1, // stream cipher
CHACHA_KEY_LEN,
CHACHA_IV_LEN,
sizeof(CIPHER_CHACHA_POLY_CTX),
EVP_CIPH_FLAG_AEAD_CIPHER | EVP_CIPH_CUSTOM_IV | EVP_CIPH_ALWAYS_CALL_INIT |
EVP_CIPH_CTRL_INIT | EVP_CIPH_CUSTOM_COPY | EVP_CIPH_FLAG_CUSTOM_CIPHER,
cipher_chacha20_poly1305_init,
cipher_chacha20_poly1305_do_cipher,
cipher_chacha20_poly1305_cleanup,
cipher_chacha20_poly1305_ctrl
};
const EVP_CIPHER *EVP_chacha20_poly1305(void)
{
return(&cipher_chacha20_poly1305);
}