blob: 5c9fca9ff962b65ab7c4f9babc37d01422315439 [file] [log] [blame]
// 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