// Copyright 2017 Google Inc. // // 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 // // http://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 "tink/subtle/aes_eax_boringssl.h" #include <string> #include <vector> #include <memory> #include "openssl/err.h" #include "openssl/evp.h" #include "tink/aead.h" #include "tink/subtle/random.h" #include "tink/subtle/subtle_util_boringssl.h" #include "tink/util/errors.h" #include "tink/util/status.h" #include "tink/util/statusor.h" namespace crypto { namespace tink { namespace subtle { static const int BLOCK_SIZE = 16; namespace { // TODO(bleichen): There has to be a way to implement // the following routines fast. E.g. Clang 6.0.0 optimizes // Load64, Store64, BigendianLoad64, but does not optimize // BigEndianStore64. // Loads and stores 8 bytes. The endianness of the two routines // does not matter, as long as the two routines use the same order. uint64_t Load64(const uint8_t src[8]) { uint64_t res; memmove(&res, src, 8); return res; } void Store64(uint8_t dst[8], uint64_t val) { memmove(dst, &val, 8); } uint64_t BigEndianLoad64(const uint8_t src[8]) { return static_cast<uint64_t>(src[7]) | (static_cast<uint64_t>(src[6]) << 8) | (static_cast<uint64_t>(src[5]) << 16) | (static_cast<uint64_t>(src[4]) << 24) | (static_cast<uint64_t>(src[3]) << 32) | (static_cast<uint64_t>(src[2]) << 40) | (static_cast<uint64_t>(src[1]) << 48) | (static_cast<uint64_t>(src[0]) << 56); } void BigEndianStore64(uint8_t dst[8], uint64_t val) { dst[0] = (val >> 56) & 0xff; dst[1] = (val >> 48) & 0xff; dst[2] = (val >> 40) & 0xff; dst[3] = (val >> 32) & 0xff; dst[4] = (val >> 24) & 0xff; dst[5] = (val >> 16) & 0xff; dst[6] = (val >> 8) & 0xff; dst[7] = val & 0xff; } void XorBlock(const uint8_t x[BLOCK_SIZE], const uint8_t y[BLOCK_SIZE], uint8_t res[BLOCK_SIZE]) { uint64_t r0 = Load64(x) ^ Load64(y); uint64_t r1 = Load64(x + 8) ^ Load64(y + 8); Store64(res, r0); Store64(res + 8, r1); } void MultiplyByX(const uint8_t in[BLOCK_SIZE], uint8_t out[BLOCK_SIZE]) { uint64_t in_high = BigEndianLoad64(in); uint64_t in_low = BigEndianLoad64(in + 8); uint64_t out_high = (in_high << 1) ^ (in_low >> 63); // If the most significant bit is set then the result has to // be reduced by x^128 + x^7 + x^4 + x^2 + x + 1. // The representation of x^7 + x^4 + x^2 + x + 1 is 0x87. uint64_t out_low = (in_low << 1) ^ (in_high >> 63 ? 0x87 : 0); BigEndianStore64(out, out_high); BigEndianStore64(out + 8, out_low); } bool EqualBlocks(const uint8_t x[BLOCK_SIZE], const uint8_t y[BLOCK_SIZE]) { uint64_t res = Load64(x) ^ Load64(y); res |= Load64(x + 8) ^ Load64(y + 8); return res == 0; } } // namespace bool AesEaxBoringSsl::IsValidKeySize(size_t key_size_in_bytes) { return key_size_in_bytes == 16 || key_size_in_bytes == 32; } bool AesEaxBoringSsl::IsValidNonceSize(size_t nonce_size_in_bytes) { return nonce_size_in_bytes == 12 || nonce_size_in_bytes == 16; } AesEaxBoringSsl::AesEaxBoringSsl( absl::string_view key_value, size_t nonce_size) : nonce_size_(nonce_size) { int status = AES_set_encrypt_key( reinterpret_cast<const uint8_t*>(key_value.data()), key_value.size() * 8, &aeskey_); // status != 0 happens if key_value or aeskey_ is invalid. In both cases // this indicates a programming error. if (status != 0) { is_initialized_ = false; return; } uint8_t block[BLOCK_SIZE]; memset(block, 0, BLOCK_SIZE); EncryptBlock(block, block); MultiplyByX(block, B_); MultiplyByX(B_, P_); is_initialized_ = true; } crypto::tink::util::StatusOr<std::unique_ptr<Aead>> AesEaxBoringSsl::New( absl::string_view key_value, size_t nonce_size_in_bytes) { if (!IsValidKeySize(key_value.size())) { return util::Status(util::error::INTERNAL, "Invalid key"); } if (!IsValidNonceSize(nonce_size_in_bytes)) { return util::Status(util::error::INTERNAL, "Invalid nonce size"); } std::unique_ptr<AesEaxBoringSsl> aead( new AesEaxBoringSsl(key_value, nonce_size_in_bytes)); if (!aead->is_initialized_) { return util::Status(util::error::INTERNAL, "Could not initialize AesEaxBoringSsl"); } return std::unique_ptr<Aead>(aead.release()); } void AesEaxBoringSsl::Pad(const uint8_t* data, int len, uint8_t padded_block[BLOCK_SIZE]) const { // TODO(bleichen): What are we using in tink to encode assertions? // The caller must ensure that data is no longer than a block. // CHECK(0 <= len && len <= BLOCK_SIZE) << "Invalid data size"; memset(padded_block, 0, BLOCK_SIZE); memmove(padded_block, data, len); if (len == BLOCK_SIZE) { XorBlock(padded_block, B_, padded_block); } else { padded_block[len] = 0x80; XorBlock(padded_block, P_, padded_block); } } void AesEaxBoringSsl::EncryptBlock(const uint8_t in[BLOCK_SIZE], uint8_t out[BLOCK_SIZE]) const { AES_encrypt(in, out, &aeskey_); } void AesEaxBoringSsl::Omac( absl::string_view blob, int tag, uint8_t mac[BLOCK_SIZE]) const { Omac(reinterpret_cast<const uint8_t *>(blob.data()), blob.size(), tag, mac); } void AesEaxBoringSsl::Omac(const uint8_t* data, size_t len, int tag, uint8_t mac[BLOCK_SIZE]) const { uint8_t block[BLOCK_SIZE]; memset(block, 0, BLOCK_SIZE); block[15] = tag; if (len == 0) { XorBlock(block, B_, block); EncryptBlock(block, mac); return; } EncryptBlock(block, block); int idx = 0; while (len - idx > BLOCK_SIZE) { XorBlock(block, &data[idx], block); EncryptBlock(block, block); idx += BLOCK_SIZE; } uint8_t padded_block[BLOCK_SIZE]; Pad(&data[idx], len - idx, padded_block); XorBlock(block, padded_block, block); EncryptBlock(block, mac); } void AesEaxBoringSsl::CtrCrypt( const uint8_t N[BLOCK_SIZE], const uint8_t *in, uint8_t *result, size_t size) const { // This special case is necessary to avoid problems when in == null. // in == null is possible since absl::string_view can contain null pointers. if (size == 0) { return; } // Make a copy of N, since BoringSsl changes ctr. uint8_t ctr[BLOCK_SIZE]; memcpy(ctr, N, BLOCK_SIZE); unsigned int num = 0; uint8_t ecount_buf[BLOCK_SIZE]; memset(ecount_buf, 0, BLOCK_SIZE); AES_ctr128_encrypt(in, result, size, &aeskey_, ctr, ecount_buf, &num); } crypto::tink::util::StatusOr<std::string> AesEaxBoringSsl::Encrypt( absl::string_view plaintext, absl::string_view additional_data) const { // BoringSSL expects a non-null pointer for plaintext and additional_data, // regardless of whether the size is 0. plaintext = SubtleUtilBoringSSL::EnsureNonNull(plaintext); additional_data = SubtleUtilBoringSSL::EnsureNonNull(additional_data); size_t ciphertext_size = plaintext.size() + nonce_size_ + TAG_SIZE; std::string ciphertext(ciphertext_size, '\0'); uint8_t N[BLOCK_SIZE]; const std::string nonce = Random::GetRandomBytes(nonce_size_); Omac(nonce, 0, N); uint8_t H[BLOCK_SIZE]; Omac(additional_data, 1, H); uint8_t* ct_start = reinterpret_cast<uint8_t*>(&ciphertext[nonce_size_]); CtrCrypt(N, reinterpret_cast<const uint8_t*>(plaintext.data()), ct_start, plaintext.size()); uint8_t mac[BLOCK_SIZE]; Omac(ct_start, plaintext.size(), 2, mac); XorBlock(mac, N, mac); XorBlock(mac, H, mac); memmove(&ciphertext[0], nonce.data(), nonce_size_); memmove(&ciphertext[ciphertext_size - TAG_SIZE], mac, TAG_SIZE); return std::move(ciphertext); } crypto::tink::util::StatusOr<std::string> AesEaxBoringSsl::Decrypt( absl::string_view ciphertext, absl::string_view additional_data) const { // BoringSSL expects a non-null pointer for additional_data, // regardless of whether the size is 0. additional_data = SubtleUtilBoringSSL::EnsureNonNull(additional_data); size_t ct_size = ciphertext.size(); if (ct_size < nonce_size_ + TAG_SIZE) { return util::Status(util::error::INTERNAL, "Ciphertext too short"); } size_t out_size = ct_size - TAG_SIZE - nonce_size_; absl::string_view nonce = ciphertext.substr(0, nonce_size_); absl::string_view encrypted = ciphertext.substr(nonce_size_, out_size); absl::string_view tag = ciphertext.substr(ct_size - TAG_SIZE, TAG_SIZE); uint8_t N[BLOCK_SIZE]; Omac(nonce, 0, N); uint8_t H[BLOCK_SIZE]; Omac(additional_data, 1, H); uint8_t mac[BLOCK_SIZE]; Omac(encrypted, 2, mac); XorBlock(mac, N, mac); XorBlock(mac, H, mac); const uint8_t *sig = reinterpret_cast<const uint8_t*>(tag.data()); if (!EqualBlocks(mac, sig)) { return util::Status(util::error::INTERNAL, "Tag mismatch"); } std::string res(out_size, '\0'); CtrCrypt(N, reinterpret_cast<const uint8_t*>(encrypted.data()), reinterpret_cast<uint8_t*>(&res[0]), out_size); return std::move(res); } } // namespace subtle } // namespace tink } // namespace crypto