729 lines
25 KiB
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);
|
|
}
|