// 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 #include #include #include #include #include #include #include #include #include #include #include #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(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(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