blob: 152b3c72bbd1f72366f638331dfd25fb0f78864a [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <crypto/aead.h>
#include <crypto/bytes.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <fbl/unique_ptr.h>
#include <lib/zircon-internal/debug.h>
#include <zircon/errors.h>
#include <zircon/types.h>
// See note in //zircon/third_party/ulib/uboringssl/rules.mk
#define BORINGSSL_NO_CXX
#include <openssl/aead.h>
#include "error.h"
#define ZXDEBUG 0
namespace crypto {
// The previously opaque crypto implementation context. Guaranteed to clean up on destruction.
struct AEAD::Context {
Context() {}
~Context() { EVP_AEAD_CTX_cleanup(&impl); }
EVP_AEAD_CTX impl;
};
namespace {
// Get the aead for the given |version|.
zx_status_t GetAEAD(AEAD::Algorithm aead, const EVP_AEAD** out) {
switch (aead) {
case AEAD::kUninitialized:
xprintf("not initialized\n");
return ZX_ERR_INVALID_ARGS;
case AEAD::kAES128_GCM:
*out = EVP_aead_aes_128_gcm();
return ZX_OK;
case AEAD::kAES128_GCM_SIV:
*out = EVP_aead_aes_128_gcm_siv();
return ZX_OK;
default:
xprintf("invalid aead = %u\n", aead);
return ZX_ERR_NOT_SUPPORTED;
}
}
} // namespace
// Public methods
zx_status_t AEAD::GetKeyLen(Algorithm algo, size_t* out) {
zx_status_t rc;
if (!out) {
xprintf("missing output pointer\n");
return ZX_ERR_INVALID_ARGS;
}
const EVP_AEAD* aead;
if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
return rc;
}
*out = EVP_AEAD_key_length(aead);
return ZX_OK;
}
zx_status_t AEAD::GetIVLen(Algorithm algo, size_t* out) {
zx_status_t rc;
if (!out) {
xprintf("missing output pointer\n");
return ZX_ERR_INVALID_ARGS;
}
const EVP_AEAD* aead;
if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
return rc;
}
*out = EVP_AEAD_nonce_length(aead);
return ZX_OK;
}
zx_status_t AEAD::GetTagLen(Algorithm algo, size_t* out) {
zx_status_t rc;
if (!out) {
xprintf("missing output pointer\n");
return ZX_ERR_INVALID_ARGS;
}
const EVP_AEAD* aead;
if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
return rc;
}
*out = EVP_AEAD_max_tag_len(aead);
return ZX_OK;
}
AEAD::AEAD()
: ctx_(nullptr), direction_(Cipher::kUnset), tag_len_(0) {}
AEAD::~AEAD() {}
zx_status_t AEAD::Seal(const Bytes& ptext, const uint8_t* aad, size_t aad_len, uint64_t* out_nonce,
Bytes* out_ctext) {
zx_status_t rc;
if (direction_ != Cipher::kEncrypt) {
xprintf("not configured to encrypt\n");
return ZX_ERR_BAD_STATE;
}
size_t ptext_len = ptext.len();
if (!out_nonce || !out_ctext) {
xprintf("bad parameter(s): out_nonce=%p, ctext=%p\n", out_nonce, out_ctext);
return ZX_ERR_INVALID_ARGS;
}
// If the caller recycles the |Bytes| used for|ctext|, this becomes a no-op.
size_t ctext_len = ptext_len + tag_len_;
if ((rc = out_ctext->Resize(ctext_len)) != ZX_OK) {
return rc;
}
uint8_t* iv8 = reinterpret_cast<uint8_t*>(iv_.get());
size_t out_len;
if (EVP_AEAD_CTX_seal(&ctx_->impl, out_ctext->get(), &out_len, ctext_len, iv8, iv_len_,
ptext.get(), ptext_len, aad, aad_len) != 1) {
xprintf_crypto_errors(&rc);
return rc;
}
if (out_len != ctext_len) {
xprintf("length mismatch: expected %zu, got %zu\n", ptext_len, out_len);
return ZX_ERR_INTERNAL;
}
// Increment nonce
uint64_t nonce = iv_[0];
iv_[0] += 1;
if (iv_[0] == iv0_) {
xprintf("exceeded maximum operations with this key\n");
return ZX_ERR_BAD_STATE;
}
*out_nonce = nonce;
return ZX_OK;
}
zx_status_t AEAD::Open(uint64_t nonce, const Bytes& ctext, const uint8_t* aad, size_t aad_len, Bytes* out_ptext) {
zx_status_t rc;
if (direction_ != Cipher::kDecrypt) {
xprintf("not configured to decrypt\n");
return ZX_ERR_BAD_STATE;
}
size_t ctext_len = ctext.len();
if (ctext_len < tag_len_ || !out_ptext) {
xprintf("bad parameter(s): ctext.len=%zu, ptext=%p\n", ctext_len, out_ptext);
return ZX_ERR_INVALID_ARGS;
}
size_t ptext_len = ctext_len - tag_len_;
if ((rc = out_ptext->Resize(ptext_len)) != ZX_OK) {
return rc;
}
// Inject nonce
iv_[0] = nonce;
uint8_t* iv8 = reinterpret_cast<uint8_t*>(iv_.get());
size_t out_len;
if (EVP_AEAD_CTX_open(&ctx_->impl, out_ptext->get(), &out_len, ptext_len, iv8, iv_len_,
ctext.get(), ctext_len, aad, aad_len) != 1) {
xprintf_crypto_errors(&rc);
return rc;
}
if (out_len != ptext_len) {
xprintf("length mismatch: expected %zu, got %zu\n", ptext_len, out_len);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
void AEAD::Reset() {
ctx_.reset();
direction_ = Cipher::kUnset;
iv_len_ = 0;
tag_len_ = 0;
}
// Private methods
zx_status_t AEAD::Init(Algorithm algo, const Secret& key, const Bytes& iv,
Cipher::Direction direction) {
zx_status_t rc;
Reset();
auto cleanup = fbl::MakeAutoCall([&] { Reset(); });
// Look up specific algorithm
const EVP_AEAD* aead;
if ((rc = GetAEAD(algo, &aead)) != ZX_OK) {
return rc;
}
size_t key_len = EVP_AEAD_key_length(aead);
iv_len_ = EVP_AEAD_nonce_length(aead);
tag_len_ = EVP_AEAD_max_tag_len(aead);
// Check parameters
if (key.len() != key_len) {
xprintf("wrong key length; have %zu, need %zu\n", key.len(), key_len);
return ZX_ERR_INVALID_ARGS;
}
if (iv.len() != iv_len_) {
xprintf("wrong IV length; have %zu, need %zu\n", iv.len(), iv_len_);
return ZX_ERR_INVALID_ARGS;
}
// Allocate context
fbl::AllocChecker ac;
ctx_.reset(new (&ac) Context());
if (!ac.check()) {
xprintf("allocation failed: %zu bytes\n", sizeof(Context));
return ZX_ERR_NO_MEMORY;
}
// Initialize context
if (EVP_AEAD_CTX_init(&ctx_->impl, aead, key.get(), key.len(), EVP_AEAD_DEFAULT_TAG_LENGTH,
nullptr) != 1) {
xprintf_crypto_errors(&rc);
return rc;
}
direction_ = direction;
// Reserve space for IV
size_t n = fbl::round_up(iv_len_, sizeof(uint64_t)) / sizeof(uint64_t);
iv_.reset(new (&ac) uint64_t[n]{0});
if (!ac.check()) {
xprintf("failed to allocate %zu bytes\n", n * sizeof(uint64_t));
return ZX_ERR_NO_MEMORY;
}
memcpy(iv_.get(), iv.get(), iv_len_);
cleanup.cancel();
return ZX_OK;
}
} // namespace crypto