| // Copyright 2014 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <openssl/aead.h> | |
| #include <assert.h> | |
| #include <string.h> | |
| #include <openssl/chacha.h> | |
| #include <openssl/cipher.h> | |
| #include <openssl/err.h> | |
| #include <openssl/mem.h> | |
| #include <openssl/poly1305.h> | |
| #include <openssl/span.h> | |
| #include "../chacha/internal.h" | |
| #include "../fipsmodule/cipher/internal.h" | |
| #include "../internal.h" | |
| #include "internal.h" | |
| using namespace bssl; | |
| struct aead_chacha20_poly1305_ctx { | |
| uint8_t key[32]; | |
| }; | |
| static_assert(sizeof(((EVP_AEAD_CTX *)nullptr)->state) >= | |
| sizeof(struct aead_chacha20_poly1305_ctx), | |
| "AEAD state is too small"); | |
| static_assert(alignof(union evp_aead_ctx_st_state) >= | |
| alignof(struct aead_chacha20_poly1305_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) { | |
| struct aead_chacha20_poly1305_ctx *c20_ctx = | |
| (struct aead_chacha20_poly1305_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_pre prepares filling `tag` with the authentication tag for the given | |
| // inputs. | |
| static size_t calc_tag_pre(poly1305_state *ctx, const uint8_t key[32], | |
| const uint8_t nonce[12], | |
| Span<const CRYPTO_IVEC> aadvecs) { | |
| alignas(16) uint8_t poly1305_key[32]; | |
| 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. | |
| CRYPTO_poly1305_init(ctx, poly1305_key); | |
| size_t ad_len = 0; | |
| for (const CRYPTO_IVEC &aadvec : aadvecs) { | |
| CRYPTO_poly1305_update(ctx, aadvec.in, aadvec.len); | |
| ad_len += aadvec.len; | |
| } | |
| if (ad_len % 16 != 0) { | |
| CRYPTO_poly1305_update(ctx, padding, sizeof(padding) - (ad_len % 16)); | |
| } | |
| return ad_len; | |
| } | |
| static void calc_tag_post(poly1305_state *ctx, uint8_t tag[POLY1305_TAG_LEN], | |
| size_t ciphertext_total, size_t ad_len) { | |
| static const uint8_t padding[16] = {0}; // Padding is all zeros. | |
| 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_sealv(const uint8_t *key, | |
| Span<const CRYPTO_IOVEC> iovecs, | |
| Span<uint8_t> out_tag, size_t *out_tag_len, | |
| Span<const uint8_t> nonce, | |
| Span<const CRYPTO_IVEC> aadvecs, | |
| size_t tag_len) { | |
| if (out_tag.size() < tag_len) { | |
| OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BUFFER_TOO_SMALL); | |
| return 0; | |
| } | |
| if (nonce.size() != 12) { | |
| 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 = bssl::iovec::TotalLength(iovecs); | |
| if (in_len_64 >= (UINT64_C(1) << 32) * 64 - 64) { | |
| OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_TOO_LARGE); | |
| return 0; | |
| } | |
| union chacha20_poly1305_seal_data data; | |
| if (chacha20_poly1305_asm_capable() && iovecs.size() <= 2 && | |
| aadvecs.size() <= 1) { | |
| OPENSSL_memcpy(data.in.key, key, 32); | |
| data.in.counter = 0; | |
| CopySpan(nonce, data.in.nonce); | |
| if (iovecs.size() >= 2) { | |
| // `chacha20_poly1305_seal` only supports one extra input and expects it | |
| // to have been encrypted ahead of time. (Historically it was only used | |
| // for very short inputs.) | |
| constexpr size_t kChaChaBlockSize = 64; | |
| uint32_t block_counter = | |
| (uint32_t)(1 + (iovecs[0].len / kChaChaBlockSize)); | |
| size_t offset = iovecs[0].len % kChaChaBlockSize; | |
| size_t done = 0; | |
| if (offset != 0) { | |
| uint8_t block[kChaChaBlockSize]; | |
| memset(block, 0, sizeof(block)); | |
| CRYPTO_chacha_20(block, block, sizeof(block), key, nonce.data(), | |
| block_counter); | |
| for (size_t i = offset; i < sizeof(block) && done < iovecs[1].len; | |
| i++, done++) { | |
| iovecs[1].out[done] = iovecs[1].in[done] ^ block[i]; | |
| } | |
| ++block_counter; | |
| } | |
| if (done < iovecs[1].len) { | |
| CRYPTO_chacha_20(iovecs[1].out + done, iovecs[1].in + done, | |
| iovecs[1].len - done, key, nonce.data(), | |
| block_counter); | |
| } | |
| // TODO(crbug.com/473454967): Support more than 1 extra ciphertext. | |
| data.in.extra_ciphertext = iovecs[1].out; | |
| data.in.extra_ciphertext_len = iovecs[1].len; | |
| } else { | |
| data.in.extra_ciphertext = nullptr; | |
| data.in.extra_ciphertext_len = 0; | |
| } | |
| chacha20_poly1305_seal(iovecs.size() >= 1 ? iovecs[0].out : nullptr, | |
| iovecs.size() >= 1 ? iovecs[0].in : nullptr, | |
| iovecs.size() >= 1 ? iovecs[0].len : 0, | |
| aadvecs.size() >= 1 ? aadvecs[0].in : nullptr, | |
| aadvecs.size() >= 1 ? aadvecs[0].len : 0, &data); | |
| } else { | |
| poly1305_state ctx; | |
| size_t ad_len = calc_tag_pre(&ctx, key, nonce.data(), aadvecs); | |
| size_t ciphertext_total = 0; | |
| size_t block = 1; | |
| bssl::iovec::ForEachBlockRange<64, /*WriteOut=*/true>( | |
| iovecs, | |
| [&](const uint8_t *in, uint8_t *out, size_t len) { | |
| // TODO(crbug.com/473454967): Maybe just provide asm version of this? | |
| // Here, len is always a multiple of 64. | |
| CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); | |
| CRYPTO_poly1305_update(&ctx, out, len); | |
| ciphertext_total += len; | |
| block += len / 64; | |
| return true; | |
| }, | |
| [&](const uint8_t *in, uint8_t *out, size_t len) { | |
| // Here, len may be anything. If an asm version can't handle that, | |
| // it will be worth splitting off multiples of 64 here. | |
| CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); | |
| CRYPTO_poly1305_update(&ctx, out, len); | |
| ciphertext_total += len; | |
| return true; | |
| }); | |
| calc_tag_post(&ctx, data.out.tag, ciphertext_total, ad_len); | |
| } | |
| CopyToPrefix(Span(data.out.tag).first(tag_len), out_tag); | |
| *out_tag_len = tag_len; | |
| return 1; | |
| } | |
| static int aead_chacha20_poly1305_sealv(const EVP_AEAD_CTX *ctx, | |
| Span<const CRYPTO_IOVEC> iovecs, | |
| Span<uint8_t> out_tag, | |
| size_t *out_tag_len, | |
| Span<const uint8_t> nonce, | |
| Span<const CRYPTO_IVEC> aadvecs) { | |
| const struct aead_chacha20_poly1305_ctx *c20_ctx = | |
| (struct aead_chacha20_poly1305_ctx *)&ctx->state; | |
| return chacha20_poly1305_sealv(c20_ctx->key, iovecs, out_tag, out_tag_len, | |
| nonce, aadvecs, ctx->tag_len); | |
| } | |
| static int aead_xchacha20_poly1305_sealv(const EVP_AEAD_CTX *ctx, | |
| Span<const CRYPTO_IOVEC> iovecs, | |
| Span<uint8_t> out_tag, | |
| size_t *out_tag_len, | |
| Span<const uint8_t> nonce, | |
| Span<const CRYPTO_IVEC> aadvecs) { | |
| const struct aead_chacha20_poly1305_ctx *c20_ctx = | |
| (struct aead_chacha20_poly1305_ctx *)&ctx->state; | |
| if (nonce.size() != 24) { | |
| OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); | |
| return 0; | |
| } | |
| alignas(4) uint8_t derived_key[32]; | |
| alignas(4) uint8_t derived_nonce[12]; | |
| CRYPTO_hchacha20(derived_key, c20_ctx->key, nonce.data()); | |
| OPENSSL_memset(derived_nonce, 0, 4); | |
| OPENSSL_memcpy(&derived_nonce[4], &nonce[16], 8); | |
| return chacha20_poly1305_sealv(derived_key, iovecs, out_tag, out_tag_len, | |
| derived_nonce, aadvecs, ctx->tag_len); | |
| } | |
| static int chacha20_poly1305_openv_detached(const uint8_t *key, | |
| Span<const CRYPTO_IOVEC> iovecs, | |
| Span<const uint8_t> nonce, | |
| Span<const uint8_t> in_tag, | |
| Span<const CRYPTO_IVEC> aadvecs, | |
| size_t tag_len) { | |
| if (nonce.size() != 12) { | |
| OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); | |
| return 0; | |
| } | |
| if (in_tag.size() != 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 = bssl::iovec::TotalLength(iovecs); | |
| 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() && iovecs.size() <= 1 && | |
| aadvecs.size() <= 1) { | |
| // TODO(crbug.com/473454967): Support more than 1 ciphertext segment. | |
| OPENSSL_memcpy(data.in.key, key, 32); | |
| data.in.counter = 0; | |
| CopySpan(nonce, data.in.nonce); | |
| chacha20_poly1305_open(iovecs.size() >= 1 ? iovecs[0].out : nullptr, | |
| iovecs.size() >= 1 ? iovecs[0].in : nullptr, | |
| iovecs.size() >= 1 ? iovecs[0].len : 0, | |
| aadvecs.size() >= 1 ? aadvecs[0].in : nullptr, | |
| aadvecs.size() >= 1 ? aadvecs[0].len : 0, &data); | |
| } else { | |
| poly1305_state ctx; | |
| size_t ad_len = calc_tag_pre(&ctx, key, nonce.data(), aadvecs); | |
| size_t ciphertext_total = 0; | |
| size_t block = 1; | |
| bssl::iovec::ForEachBlockRange<64, /*WriteOut=*/true>( | |
| iovecs, | |
| [&](const uint8_t *in, uint8_t *out, size_t len) { | |
| // TODO(crbug.com/473454967): Maybe just provide asm version of this? | |
| // Here, len is always a multiple of 64. | |
| CRYPTO_poly1305_update(&ctx, in, len); | |
| CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); | |
| ciphertext_total += len; | |
| block += len / 64; | |
| return true; | |
| }, | |
| [&](const uint8_t *in, uint8_t *out, size_t len) { | |
| // Here, len may be anything. If an asm version can't handle that, | |
| // it will be worth splitting off multiples of 64 here. | |
| CRYPTO_poly1305_update(&ctx, in, len); | |
| CRYPTO_chacha_20(out, in, len, key, nonce.data(), block); | |
| ciphertext_total += len; | |
| return true; | |
| }); | |
| calc_tag_post(&ctx, data.out.tag, ciphertext_total, ad_len); | |
| } | |
| if (CRYPTO_memcmp(data.out.tag, in_tag.data(), tag_len) != 0) { | |
| OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_BAD_DECRYPT); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| static int aead_chacha20_poly1305_openv_detached( | |
| const EVP_AEAD_CTX *ctx, Span<const CRYPTO_IOVEC> iovecs, | |
| Span<const uint8_t> nonce, Span<const uint8_t> in_tag, | |
| Span<const CRYPTO_IVEC> aadvecs) { | |
| const struct aead_chacha20_poly1305_ctx *c20_ctx = | |
| (struct aead_chacha20_poly1305_ctx *)&ctx->state; | |
| return chacha20_poly1305_openv_detached(c20_ctx->key, iovecs, nonce, in_tag, | |
| aadvecs, ctx->tag_len); | |
| } | |
| static int aead_xchacha20_poly1305_openv_detached( | |
| const EVP_AEAD_CTX *ctx, Span<const CRYPTO_IOVEC> iovecs, | |
| Span<const uint8_t> nonce, Span<const uint8_t> in_tag, | |
| Span<const CRYPTO_IVEC> aadvecs) { | |
| const struct aead_chacha20_poly1305_ctx *c20_ctx = | |
| (struct aead_chacha20_poly1305_ctx *)&ctx->state; | |
| if (nonce.size() != 24) { | |
| OPENSSL_PUT_ERROR(CIPHER, CIPHER_R_UNSUPPORTED_NONCE_SIZE); | |
| return 0; | |
| } | |
| alignas(4) uint8_t derived_key[32]; | |
| alignas(4) uint8_t derived_nonce[12]; | |
| CRYPTO_hchacha20(derived_key, c20_ctx->key, nonce.data()); | |
| OPENSSL_memset(derived_nonce, 0, 4); | |
| OPENSSL_memcpy(&derived_nonce[4], &nonce[16], 8); | |
| return chacha20_poly1305_openv_detached(derived_key, iovecs, derived_nonce, | |
| in_tag, aadvecs, ctx->tag_len); | |
| } | |
| static const EVP_AEAD aead_chacha20_poly1305 = { | |
| 32, // key len | |
| 12, // nonce len | |
| POLY1305_TAG_LEN, // overhead | |
| POLY1305_TAG_LEN, // max tag length | |
| aead_chacha20_poly1305_init, | |
| nullptr, // init_with_direction | |
| aead_chacha20_poly1305_cleanup, | |
| nullptr, // openv | |
| aead_chacha20_poly1305_sealv, | |
| aead_chacha20_poly1305_openv_detached, | |
| nullptr, // get_iv | |
| nullptr, // tag_len | |
| }; | |
| static const EVP_AEAD aead_xchacha20_poly1305 = { | |
| 32, // key len | |
| 24, // nonce len | |
| POLY1305_TAG_LEN, // overhead | |
| POLY1305_TAG_LEN, // max tag length | |
| aead_chacha20_poly1305_init, | |
| nullptr, // init_with_direction | |
| aead_chacha20_poly1305_cleanup, | |
| nullptr, // openv | |
| aead_xchacha20_poly1305_sealv, | |
| aead_xchacha20_poly1305_openv_detached, | |
| nullptr, // get_iv | |
| nullptr, // tag_len | |
| }; | |
| const EVP_AEAD *EVP_aead_chacha20_poly1305() { return &aead_chacha20_poly1305; } | |
| const EVP_AEAD *EVP_aead_xchacha20_poly1305() { | |
| return &aead_xchacha20_poly1305; | |
| } |