blob: 7bf9ba502329c91d320fac2eb1ed52a70f28f344 [file] [log] [blame]
// Copyright 2015 The Chromium OS 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 "attestation/common/crypto_utility_impl.h"
#include <limits>
#include <string>
#include <vector>
#include <arpa/inet.h>
#include <base/hash/sha1.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <crypto/libcrypto-compat.h>
#include <crypto/scoped_openssl_types.h>
#include <crypto/secure_util.h>
#include <crypto/sha2.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/x509.h>
namespace {
const size_t kAesKeySize = 32;
const size_t kAesBlockSize = 16;
const char kHashHeaderForEncrypt[] = "ENCRYPT";
const char kHashHeaderForMac[] = "MAC";
const unsigned int kWellKnownExponent = 65537;
std::string GetOpenSSLError() {
BIO* bio = BIO_new(BIO_s_mem());
ERR_print_errors(bio);
char* data = nullptr;
int data_len = BIO_get_mem_data(bio, &data);
std::string error_string(data, data_len);
BIO_free(bio);
return error_string;
}
unsigned char* StringAsOpenSSLBuffer(std::string* s) {
return reinterpret_cast<unsigned char*>(base::data(*s));
}
const unsigned char* StringAsConstOpenSSLBuffer(const std::string& s) {
return reinterpret_cast<const unsigned char*>(s.data());
}
crypto::ScopedRSA CreateRSAFromHexModulus(const std::string& hex_modulus) {
crypto::ScopedRSA rsa(RSA_new());
crypto::ScopedBIGNUM e(BN_new()), n(BN_new());
if (!rsa || !e || !n) {
LOG(ERROR) << __func__ << ": Failed to allocate RSA or BIGNUMs.";
return nullptr;
}
BIGNUM* pn = n.get();
if (!BN_set_word(e.get(), kWellKnownExponent) ||
!BN_hex2bn(&pn, hex_modulus.c_str())) {
LOG(ERROR) << __func__ << ": Failed to generate exponent or modulus.";
return nullptr;
}
if (!RSA_set0_key(rsa.get(), n.release(), e.release(), nullptr)) {
LOG(ERROR) << __func__ << ": Failed to set exponent or modulus.";
return nullptr;
}
return rsa;
}
crypto::ScopedOpenSSL<X509, X509_free> CreateX509FromCertificate(
const std::string& certificate) {
const unsigned char* asn1_ptr =
reinterpret_cast<const unsigned char*>(certificate.data());
crypto::ScopedOpenSSL<X509, X509_free> x509(
d2i_X509(NULL, &asn1_ptr, certificate.size()));
return x509;
}
} // namespace
namespace attestation {
CryptoUtilityImpl::CryptoUtilityImpl(TpmUtility* tpm_utility)
: tpm_utility_(tpm_utility) {
OpenSSL_add_all_algorithms();
EVP_PKEY_asn1_add_alias(EVP_PKEY_RSA, NID_rsaesOaep);
ERR_load_crypto_strings();
}
CryptoUtilityImpl::~CryptoUtilityImpl() {
EVP_cleanup();
ERR_free_strings();
}
bool CryptoUtilityImpl::GetRandom(size_t num_bytes,
std::string* random_data) const {
// OpenSSL takes a signed integer.
if (num_bytes > static_cast<size_t>(std::numeric_limits<int>::max())) {
return false;
}
random_data->resize(num_bytes);
unsigned char* buffer = StringAsOpenSSLBuffer(random_data);
return (RAND_bytes(buffer, num_bytes) == 1);
}
bool CryptoUtilityImpl::CreateSealedKey(std::string* aes_key,
std::string* sealed_key) {
if (!GetRandom(kAesKeySize, aes_key)) {
LOG(ERROR) << __func__ << ": GetRandom failed.";
return false;
}
if (!tpm_utility_->SealToPCR0(*aes_key, sealed_key)) {
LOG(ERROR) << __func__ << ": Failed to seal cipher key.";
return false;
}
return true;
}
bool CryptoUtilityImpl::EncryptData(const std::string& data,
const std::string& aes_key,
const std::string& sealed_key,
std::string* encrypted_data) {
EncryptedData encrypted_pb;
encrypted_pb.set_wrapped_key(sealed_key);
if (!EncryptWithSeed(KeyDerivationScheme::kNone, data, aes_key,
&encrypted_pb)) {
return false;
}
if (!encrypted_pb.SerializeToString(encrypted_data)) {
LOG(ERROR) << __func__ << ": Failed to serialize protobuf.";
return false;
}
return true;
}
bool CryptoUtilityImpl::UnsealKey(const std::string& encrypted_data,
std::string* aes_key,
std::string* sealed_key) {
EncryptedData encrypted_pb;
if (!encrypted_pb.ParseFromString(encrypted_data)) {
LOG(ERROR) << __func__ << ": Failed to parse protobuf.";
return false;
}
*sealed_key = encrypted_pb.wrapped_key();
if (!tpm_utility_->Unseal(*sealed_key, aes_key)) {
LOG(ERROR) << __func__ << ": Cannot unseal aes key.";
return false;
}
return true;
}
bool CryptoUtilityImpl::DecryptData(const std::string& encrypted_data,
const std::string& aes_key,
std::string* data) {
EncryptedData encrypted_pb;
if (!encrypted_pb.ParseFromString(encrypted_data)) {
LOG(ERROR) << __func__ << ": Failed to parse protobuf.";
return false;
}
return DecryptWithSeed(KeyDerivationScheme::kNone, encrypted_pb, aes_key,
data);
}
bool CryptoUtilityImpl::GetRSASubjectPublicKeyInfo(
const std::string& public_key, std::string* public_key_info) {
auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data());
crypto::ScopedRSA rsa(
d2i_RSAPublicKey(nullptr, &asn1_ptr, public_key.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode public key: " << GetOpenSSLError();
return false;
}
unsigned char* buffer = nullptr;
int length = i2d_RSA_PUBKEY(rsa.get(), &buffer);
if (length <= 0) {
LOG(ERROR) << __func__
<< ": Failed to encode public key: " << GetOpenSSLError();
return false;
}
crypto::ScopedOpenSSLBytes scoped_buffer(buffer);
public_key_info->assign(reinterpret_cast<char*>(buffer), length);
return true;
}
bool CryptoUtilityImpl::GetRSAPublicKey(const std::string& public_key_info,
std::string* public_key) {
auto asn1_ptr =
reinterpret_cast<const unsigned char*>(public_key_info.data());
crypto::ScopedRSA rsa(
d2i_RSA_PUBKEY(NULL, &asn1_ptr, public_key_info.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode public key: " << GetOpenSSLError();
return false;
}
unsigned char* buffer = NULL;
int length = i2d_RSAPublicKey(rsa.get(), &buffer);
if (length <= 0) {
LOG(ERROR) << __func__
<< ": Failed to encode public key: " << GetOpenSSLError();
return false;
}
crypto::ScopedOpenSSLBytes scoped_buffer(buffer);
public_key->assign(reinterpret_cast<char*>(buffer), length);
return true;
}
bool CryptoUtilityImpl::EncryptIdentityCredential(
TpmVersion tpm_version,
const std::string& credential,
const std::string& ek_public_key_info,
const std::string& aik_public_key,
EncryptedIdentityCredential* encrypted) {
auto asn1_ptr =
reinterpret_cast<const unsigned char*>(ek_public_key_info.data());
encrypted->set_tpm_version(tpm_version);
if (tpm_version == TPM_1_2) {
// TODO(crbug/942487): Only use d2i_RSA_PUBKEY for both TPM version and move
// it back to the start of this function after the bug is resolved.
crypto::ScopedRSA rsa(
d2i_RSAPublicKey(NULL, &asn1_ptr, ek_public_key_info.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode EK public key: " << GetOpenSSLError();
return false;
}
const char kAlgAES256 = 9; // This comes from TPM_ALG_AES256.
const char kEncModeCBC = 2; // This comes from TPM_SYM_MODE_CBC.
const char kAsymContentHeader[] = {0, 0, 0, kAlgAES256,
0, kEncModeCBC, 0, kAesKeySize};
const char kSymContentHeader[12] = {};
// Generate an AES key and encrypt the credential.
std::string aes_key;
if (!GetRandom(kAesKeySize, &aes_key)) {
LOG(ERROR) << __func__ << ": GetRandom failed.";
return false;
}
std::string encrypted_credential;
if (!TssCompatibleEncrypt(credential, aes_key, &encrypted_credential)) {
LOG(ERROR) << __func__ << ": Failed to encrypt credential.";
return false;
}
// Construct a TPM_ASYM_CA_CONTENTS structure.
std::string asym_header(std::begin(kAsymContentHeader),
std::end(kAsymContentHeader));
std::string asym_content =
asym_header + aes_key + base::SHA1HashString(aik_public_key);
// Encrypt the TPM_ASYM_CA_CONTENTS with the EK public key.
std::string encrypted_asym_content;
if (!TpmCompatibleOAEPEncrypt(asym_content, rsa.get(),
&encrypted_asym_content)) {
LOG(ERROR) << __func__ << ": Failed to encrypt with EK public key.";
return false;
}
// Construct a TPM_SYM_CA_ATTESTATION structure.
uint32_t length = htonl(encrypted_credential.size());
auto length_bytes = reinterpret_cast<const char*>(&length);
std::string length_blob(length_bytes, sizeof(uint32_t));
std::string sym_header(std::begin(kSymContentHeader),
std::end(kSymContentHeader));
std::string sym_content = length_blob + sym_header + encrypted_credential;
encrypted->set_asym_ca_contents(encrypted_asym_content);
encrypted->set_sym_ca_attestation(sym_content);
} else if (tpm_version == TPM_2_0) {
crypto::ScopedRSA rsa(
d2i_RSA_PUBKEY(NULL, &asn1_ptr, ek_public_key_info.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode EK public key: " << GetOpenSSLError();
return false;
}
// The 'credential' parameter is actually the certificate. The 'credential'
// used in the wrapping process is referred to as 'inner_credential' below.
std::string certificate = credential;
// Generate a random seed and derive from it an AES and HMAC key as
// documented in TPM 2.0 specification Part 1 Rev 1.16 Section 24.
std::string seed;
if (!GetRandom(kAesKeySize, &seed)) {
return false;
}
std::string identity_key_name = GetTpm2KeyNameFromPublicKey(aik_public_key);
std::string aes_key =
Tpm2CompatibleKDFa(seed, "STORAGE", identity_key_name, 128);
std::string hmac_key = Tpm2CompatibleKDFa(seed, "INTEGRITY", "", 256);
// This will be the 'credential' that the TPM decrypts during activation.
std::string inner_credential;
if (!GetRandom(kAesKeySize, &inner_credential)) {
return false;
}
// Wrap the credential with the seed using an Encrypt-then-MAC scheme
// documented in TPM 2.0 specification Part 1 Rev 1.16 Section 24.
std::string encrypted_credential;
std::string iv(kAesBlockSize, 0);
std::string inner_credential_size_bytes("\x00\x20", 2); // Big-endian 32.
if (!AesEncrypt(EVP_aes_128_cfb(),
inner_credential_size_bytes + inner_credential, aes_key, iv,
&encrypted_credential)) {
return false;
}
encrypted->set_credential_mac(
HmacSha256(hmac_key, encrypted_credential + identity_key_name));
// Wrap the certificate with the credential using the scheme required by the
// EncryptedIdentityCredential protobuf.
EncryptedData* encrypted_certificate =
encrypted->mutable_wrapped_certificate();
if (!EncryptWithSeed(KeyDerivationScheme::kHashWithHeaders, certificate,
inner_credential, encrypted_certificate)) {
return false;
}
encrypted_certificate->set_wrapped_key(encrypted_credential);
// At this point, the credential can be recovered given the seed, and the
// certificate can be recovered given the credential. All that remains is to
// encrypt the seed with the EK public key.
if (!Tpm2CompatibleOAEPEncrypt("IDENTITY", seed, rsa.get(),
encrypted->mutable_encrypted_seed())) {
return false;
}
} else {
LOG(ERROR) << "Unsupported TPM version.";
return false;
}
return true;
}
bool CryptoUtilityImpl::DecryptIdentityCertificateForTpm2(
const std::string& credential,
const EncryptedData& encrypted_certificate,
std::string* certificate) {
return DecryptWithSeed(KeyDerivationScheme::kHashWithHeaders,
encrypted_certificate, credential, certificate);
}
bool CryptoUtilityImpl::EncryptForUnbind(const std::string& public_key,
const std::string& data,
std::string* encrypted_data) {
// Construct a TPM_BOUND_DATA structure.
const char kBoundDataHeader[] = {1, 1, 0, 0, 2 /* TPM_PT_BIND */};
std::string header(std::begin(kBoundDataHeader), std::end(kBoundDataHeader));
std::string bound_data = header + data;
// Encrypt using the TPM_ES_RSAESOAEP_SHA1_MGF1 scheme.
auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data());
crypto::ScopedRSA rsa(d2i_RSA_PUBKEY(NULL, &asn1_ptr, public_key.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode public key: " << GetOpenSSLError();
return false;
}
if (!TpmCompatibleOAEPEncrypt(bound_data, rsa.get(), encrypted_data)) {
LOG(ERROR) << __func__ << ": Failed to encrypt with public key.";
return false;
}
return true;
}
bool CryptoUtilityImpl::VerifySignature(int digest_nid,
const std::string& public_key,
const std::string& data,
const std::string& signature) {
auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data());
crypto::ScopedEVP_PKEY pubkey(d2i_PUBKEY(NULL, &asn1_ptr, public_key.size()));
if (!pubkey.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode public key: " << GetOpenSSLError();
return false;
}
return VerifySignatureInner(digest_nid, pubkey, data, signature);
}
bool CryptoUtilityImpl::VerifySignatureUsingHexKey(
int digest_nid,
const std::string& public_key_hex,
const std::string& data,
const std::string& signature) {
crypto::ScopedRSA rsa = CreateRSAFromHexModulus(public_key_hex);
if (!rsa.get()) {
LOG(ERROR) << __func__ << ": Failed to decode public key.";
return false;
}
crypto::ScopedEVP_PKEY evp_pkey(EVP_PKEY_new());
if (!evp_pkey.get()) {
LOG(ERROR) << __func__ << ": Failed to allocate EVP PKEY.";
return false;
}
EVP_PKEY_assign_RSA(evp_pkey.get(), rsa.release());
return VerifySignatureInner(digest_nid, evp_pkey, data, signature);
}
bool CryptoUtilityImpl::VerifySignatureInner(
int digest_nid,
const crypto::ScopedEVP_PKEY& pubkey,
const std::string& data,
const std::string& signature) {
const EVP_MD* md = EVP_get_digestbynid(digest_nid);
if (md == nullptr) {
LOG(ERROR) << __func__ << ": Failed to get hash algorithm from digest NID: "
<< digest_nid;
return false;
}
crypto::ScopedEVP_MD_CTX mdctx(EVP_MD_CTX_new());
if (!mdctx) {
LOG(ERROR) << __func__
<< ": Failed to allocate EVP_MD_CTX: " << GetOpenSSLError();
return false;
}
if (!EVP_DigestVerifyInit(mdctx.get(), nullptr, md, nullptr, pubkey.get())) {
LOG(ERROR) << __func__ << ": Failed to initialize verifying process: "
<< GetOpenSSLError();
return false;
}
if (!EVP_DigestVerifyUpdate(mdctx.get(), data.data(), data.length())) {
LOG(ERROR) << __func__
<< ": Failed to hash the input data: " << GetOpenSSLError();
return false;
}
return EVP_DigestVerifyFinal(
mdctx.get(), StringAsConstOpenSSLBuffer(signature), signature.size());
}
bool CryptoUtilityImpl::EncryptDataForGoogle(const std::string& data,
const std::string& public_key_hex,
const std::string& key_id,
EncryptedData* encrypted_data) {
crypto::ScopedRSA rsa = CreateRSAFromHexModulus(public_key_hex);
if (!rsa.get()) {
LOG(ERROR) << __func__ << ": Failed to decode public key.";
return false;
}
std::string key;
if (!GetRandom(kAesKeySize, &key)) {
return false;
}
if (!EncryptWithSeed(KeyDerivationScheme::kNone, data, key, encrypted_data)) {
return false;
}
if (!WrapKeyOAEP(key, rsa.get(), key_id, encrypted_data)) {
encrypted_data->Clear();
return false;
}
return true;
}
bool CryptoUtilityImpl::AesEncrypt(const EVP_CIPHER* cipher,
const std::string& data,
const std::string& key,
const std::string& iv,
std::string* encrypted_data) {
if (key.size() != static_cast<size_t>(EVP_CIPHER_key_length(cipher)) ||
iv.size() != kAesBlockSize) {
return false;
}
if (data.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
// EVP_EncryptUpdate takes a signed int.
return false;
}
std::string mutable_data(data);
unsigned char* input_buffer = StringAsOpenSSLBuffer(&mutable_data);
std::string mutable_key(key);
unsigned char* key_buffer = StringAsOpenSSLBuffer(&mutable_key);
std::string mutable_iv(iv);
unsigned char* iv_buffer = StringAsOpenSSLBuffer(&mutable_iv);
// Allocate enough space for the output (including padding).
encrypted_data->resize(data.size() + kAesBlockSize);
auto output_buffer =
reinterpret_cast<unsigned char*>(base::data(*encrypted_data));
int output_size = 0;
crypto::ScopedEVP_CIPHER_CTX encryption_context(EVP_CIPHER_CTX_new());
if (!encryption_context) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
if (!EVP_EncryptInit_ex(encryption_context.get(), cipher, nullptr, key_buffer,
iv_buffer)) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
if (!EVP_EncryptUpdate(encryption_context.get(), output_buffer, &output_size,
input_buffer, data.size())) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
size_t total_size = output_size;
output_buffer += output_size;
output_size = 0;
if (!EVP_EncryptFinal_ex(encryption_context.get(), output_buffer,
&output_size)) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
total_size += output_size;
encrypted_data->resize(total_size);
return true;
}
bool CryptoUtilityImpl::AesDecrypt(const EVP_CIPHER* cipher,
const std::string& encrypted_data,
const std::string& key,
const std::string& iv,
std::string* data) {
if (key.size() != static_cast<size_t>(EVP_CIPHER_key_length(cipher)) ||
iv.size() != kAesBlockSize) {
return false;
}
if (encrypted_data.size() >
static_cast<size_t>(std::numeric_limits<int>::max())) {
// EVP_DecryptUpdate takes a signed int.
return false;
}
std::string mutable_encrypted_data(encrypted_data);
unsigned char* input_buffer = StringAsOpenSSLBuffer(&mutable_encrypted_data);
std::string mutable_key(key);
unsigned char* key_buffer = StringAsOpenSSLBuffer(&mutable_key);
std::string mutable_iv(iv);
unsigned char* iv_buffer = StringAsOpenSSLBuffer(&mutable_iv);
// Allocate enough space for the output.
data->resize(encrypted_data.size());
unsigned char* output_buffer = StringAsOpenSSLBuffer(data);
int output_size = 0;
crypto::ScopedEVP_CIPHER_CTX decryption_context(EVP_CIPHER_CTX_new());
if (!decryption_context) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
if (!EVP_DecryptInit_ex(decryption_context.get(), cipher, nullptr, key_buffer,
iv_buffer)) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
if (!EVP_DecryptUpdate(decryption_context.get(), output_buffer, &output_size,
input_buffer, encrypted_data.size())) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
size_t total_size = output_size;
output_buffer += output_size;
output_size = 0;
if (!EVP_DecryptFinal_ex(decryption_context.get(), output_buffer,
&output_size)) {
LOG(ERROR) << __func__ << ": " << GetOpenSSLError();
return false;
}
total_size += output_size;
data->resize(total_size);
return true;
}
std::string CryptoUtilityImpl::HmacSha256(const std::string& key,
const std::string& data) {
unsigned char mac[SHA256_DIGEST_LENGTH];
std::string mutable_data(data);
unsigned char* data_buffer = StringAsOpenSSLBuffer(&mutable_data);
HMAC(EVP_sha256(), key.data(), key.size(), data_buffer, data.size(), mac,
nullptr);
return std::string(std::begin(mac), std::end(mac));
}
std::string CryptoUtilityImpl::HmacSha512(const std::string& key,
const std::string& data) {
unsigned char mac[SHA512_DIGEST_LENGTH];
std::string mutable_data(data);
unsigned char* data_buffer = StringAsOpenSSLBuffer(&mutable_data);
HMAC(EVP_sha512(), key.data(), key.size(), data_buffer, data.size(), mac,
nullptr);
return std::string(std::begin(mac), std::end(mac));
}
int CryptoUtilityImpl::DefaultDigestAlgoForSignature() {
switch (tpm_utility_->GetVersion()) {
case attestation::TPM_2_0:
return NID_sha256;
case attestation::TPM_1_2:
return NID_sha1;
}
}
bool CryptoUtilityImpl::TssCompatibleEncrypt(const std::string& input,
const std::string& key,
std::string* output) {
CHECK(output);
CHECK_EQ(key.size(), kAesKeySize);
std::string iv;
if (!GetRandom(kAesBlockSize, &iv)) {
LOG(ERROR) << __func__ << ": GetRandom failed.";
return false;
}
std::string encrypted;
if (!AesEncrypt(EVP_aes_256_cbc(), input, key, iv, &encrypted)) {
LOG(ERROR) << __func__ << ": Encryption failed.";
return false;
}
*output = iv + encrypted;
return true;
}
bool CryptoUtilityImpl::TpmCompatibleOAEPEncrypt(const std::string& input,
RSA* key,
std::string* output) {
CHECK(output);
// The custom OAEP parameter as specified in TPM Main Part 1, Section 31.1.1.
return OAEPEncryptWithLabel("TCPA", input, key, EVP_sha1(), EVP_sha1(),
output);
}
bool CryptoUtilityImpl::EncryptWithSeed(KeyDerivationScheme derivation_scheme,
const std::string& input,
const std::string& seed,
EncryptedData* encrypted) {
std::string iv;
if (!GetRandom(kAesBlockSize, &iv)) {
return false;
}
std::string aes_key;
std::string hmac_key;
if (derivation_scheme == KeyDerivationScheme::kNone) {
aes_key = hmac_key = seed;
} else if (derivation_scheme == KeyDerivationScheme::kHashWithHeaders) {
aes_key = crypto::SHA256HashString(kHashHeaderForEncrypt + seed);
hmac_key = crypto::SHA256HashString(kHashHeaderForMac + seed);
}
std::string encrypted_data;
if (!AesEncrypt(EVP_aes_256_cbc(), input, aes_key, iv, &encrypted_data)) {
return false;
}
encrypted->set_encrypted_data(encrypted_data);
encrypted->set_iv(iv);
encrypted->set_mac(HmacSha512(hmac_key, iv + encrypted_data));
return true;
}
bool CryptoUtilityImpl::DecryptWithSeed(KeyDerivationScheme derivation_scheme,
const EncryptedData& input,
const std::string& seed,
std::string* decrypted) {
std::string aes_key;
std::string hmac_key;
if (derivation_scheme == KeyDerivationScheme::kNone) {
aes_key = hmac_key = seed;
} else if (derivation_scheme == KeyDerivationScheme::kHashWithHeaders) {
aes_key = crypto::SHA256HashString(kHashHeaderForEncrypt + seed);
hmac_key = crypto::SHA256HashString(kHashHeaderForMac + seed);
}
std::string expected_mac =
HmacSha512(hmac_key, input.iv() + input.encrypted_data());
if (expected_mac.length() != input.mac().length()) {
LOG(ERROR) << __func__ << ": MAC length mismatch.";
return false;
}
if (!crypto::SecureMemEqual(expected_mac.data(), input.mac().data(),
expected_mac.length())) {
LOG(ERROR) << __func__ << ": MAC mismatch.";
return false;
}
if (!AesDecrypt(EVP_aes_256_cbc(), input.encrypted_data(), aes_key,
input.iv(), decrypted)) {
return false;
}
return true;
}
bool CryptoUtilityImpl::WrapKeyOAEP(const std::string& key,
RSA* wrapping_key,
const std::string& wrapping_key_id,
EncryptedData* output) {
const unsigned char* key_buffer = StringAsConstOpenSSLBuffer(key);
std::string encrypted_key;
encrypted_key.resize(RSA_size(wrapping_key));
unsigned char* encrypted_key_buffer = StringAsOpenSSLBuffer(&encrypted_key);
int length = RSA_public_encrypt(key.size(), key_buffer, encrypted_key_buffer,
wrapping_key, RSA_PKCS1_OAEP_PADDING);
if (length == -1) {
LOG(ERROR) << "RSA_public_encrypt failed.";
return false;
}
encrypted_key.resize(length);
output->set_wrapped_key(encrypted_key);
output->set_wrapping_key_id(wrapping_key_id);
return true;
}
std::string CryptoUtilityImpl::GetTpm2KeyNameFromPublicKey(
const std::string& public_key_tpm_format) {
// TPM_ALG_SHA256 = 0x000B, here in big-endian order.
std::string prefix("\x00\x0B", 2);
return prefix + crypto::SHA256HashString(public_key_tpm_format);
}
std::string CryptoUtilityImpl::Tpm2CompatibleKDFa(const std::string& key,
const std::string& label,
const std::string& context,
int bits) {
// Due to the assumptions of SHA256 and a 128/256-bit output, we can simplify
// to just one iteration.
if (bits != 128 && bits != 256) {
LOG(ERROR) << __func__ << ": Unsupported key size: " << bits;
return std::string("");
}
std::string iteration("\x00\x00\x00\x01", 4); // Big-endian 32-bit 1.
std::string null_separator("\x00", 1);
// Encode number of bits as big-endian 32-bit value (128 or 256).
std::string b_buf(bits == 128 ? "\x00\x00\x00\x80" : "\x00\x00\x01\x00", 4);
return HmacSha256(key, iteration + label + null_separator + context + b_buf)
.substr(0, bits / 8);
}
bool CryptoUtilityImpl::Tpm2CompatibleOAEPEncrypt(const std::string& label,
const std::string& input,
RSA* key,
std::string* output) {
std::string zero_terminated_label = label + std::string(1, '\x00');
return OAEPEncryptWithLabel(zero_terminated_label, input, key, EVP_sha256(),
EVP_sha256(), output);
}
bool CryptoUtilityImpl::OAEPEncryptWithLabel(const std::string& label,
const std::string& input,
RSA* key,
const EVP_MD* md,
const EVP_MD* mgf1md,
std::string* output) {
std::string padded_input;
padded_input.resize(RSA_size(key));
auto padded_buffer =
reinterpret_cast<unsigned char*>(base::data(padded_input));
auto input_buffer = reinterpret_cast<const unsigned char*>(input.data());
auto label_buffer = reinterpret_cast<const unsigned char*>(label.data());
int result = RSA_padding_add_PKCS1_OAEP_mgf1(
padded_buffer, padded_input.size(), input_buffer, input.size(),
label_buffer, label.size(), md, mgf1md);
if (!result) {
LOG(ERROR) << __func__
<< ": Failed to add OAEP padding: " << GetOpenSSLError();
return false;
}
output->resize(padded_input.size());
auto output_buffer = reinterpret_cast<unsigned char*>(base::data(*output));
result = RSA_public_encrypt(padded_input.size(), padded_buffer, output_buffer,
key, RSA_NO_PADDING);
if (result == -1) {
LOG(ERROR) << __func__ << ": Failed to encrypt OAEP padded input: "
<< GetOpenSSLError();
return false;
}
return true;
}
bool CryptoUtilityImpl::CreateSPKAC(const std::string& key_blob,
const std::string& public_key,
KeyType key_type,
std::string* spkac) {
// Get the certified public key as an EVP_PKEY.
const unsigned char* asn1_ptr =
reinterpret_cast<const unsigned char*>(public_key.data());
crypto::ScopedEVP_PKEY evp_pkey(EVP_PKEY_new());
if (!evp_pkey.get()) {
LOG(ERROR) << __func__
<< ": Failed to call EVP_PKEY_new: " << GetOpenSSLError();
return false;
}
if (key_type == KEY_TYPE_RSA) {
crypto::ScopedRSA rsa(
d2i_RSAPublicKey(nullptr, &asn1_ptr, public_key.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode public key: " << GetOpenSSLError();
return false;
}
if (!EVP_PKEY_set1_RSA(evp_pkey.get(), rsa.get())) {
LOG(ERROR) << __func__
<< ": Failed to call EVP_PKEY_set1_RSA: " << GetOpenSSLError();
return false;
}
} else if (key_type == KEY_TYPE_ECC) {
crypto::ScopedEC_KEY ec_key(
d2i_EC_PUBKEY(nullptr, &asn1_ptr, public_key.size()));
if (!ec_key.get()) {
LOG(ERROR) << __func__
<< ": Failed to decode ECC public key: " << GetOpenSSLError();
return false;
}
if (EVP_PKEY_set1_EC_KEY(evp_pkey.get(), ec_key.get()) != 1) {
LOG(ERROR) << __func__ << ": Failed to call EVP_PKEY_set1_EC_KEY: "
<< GetOpenSSLError();
return false;
}
} else {
LOG(DFATAL) << __func__ << ": Unrecognized key type.";
return false;
}
return CreateSPKACInternal(key_blob, evp_pkey, spkac);
}
bool CryptoUtilityImpl::CreateSPKACInternal(
const std::string& key_blob,
const crypto::ScopedEVP_PKEY& public_key,
std::string* spkac) {
CHECK(public_key.get());
// Fill in the public key.
crypto::ScopedOpenSSL<NETSCAPE_SPKI, NETSCAPE_SPKI_free> spki(
NETSCAPE_SPKI_new());
if (!spki.get()) {
LOG(ERROR) << __func__ << ": Failed to create SPKI.";
return false;
}
if (!NETSCAPE_SPKI_set_pubkey(spki.get(), public_key.get())) {
LOG(ERROR) << __func__ << ": Failed to set pubkey for SPKI.";
return false;
}
// Fill in a random challenge.
std::string challenge;
size_t challenge_size = (tpm_utility_->GetVersion() == TPM_1_2)
? base::kSHA1Length
: crypto::kSHA256Length;
if (!GetRandom(challenge_size, &challenge)) {
LOG(ERROR) << __func__ << ": Failed to GetRandom(challenge).";
return false;
}
std::string challenge_hex =
base::HexEncode(challenge.data(), challenge.size());
if (!ASN1_STRING_set(spki.get()->spkac->challenge, challenge_hex.data(),
challenge_hex.size())) {
LOG(ERROR) << __func__ << ": Failed to set challenge in SPKAC.";
return false;
}
// Generate the signature.
unsigned char* buffer = NULL;
int length = i2d_NETSCAPE_SPKAC(spki.get()->spkac, &buffer);
if (length <= 0) {
LOG(ERROR) << __func__ << ": Failed to get SPKAC.";
return false;
}
std::string data_to_sign(reinterpret_cast<char*>(buffer), length);
OPENSSL_free(buffer);
std::string signature;
if (!tpm_utility_->Sign(key_blob, data_to_sign, &signature)) {
LOG(ERROR) << __func__ << ": Failed to sign SPKAC.";
return false;
}
// Fill in the signature and algorithm.
if (!ASN1_BIT_STRING_set(
spki.get()->signature,
reinterpret_cast<unsigned char*>(const_cast<char*>(signature.data())),
signature.size())) {
LOG(ERROR) << __func__ << ": Failed to set signature in SPKAC.";
return false;
}
// Be explicit that there are zero unused bits; otherwise i2d below will
// automatically detect unused bits but signatures require zero unused bits.
spki.get()->signature->flags = ASN1_STRING_FLAG_BITS_LEFT;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
X509_ALGOR* sig_algor = spki.get()->sig_algor;
#else
X509_ALGOR* sig_algor = &spki.get()->sig_algor;
#endif
const int sig_algo_nid = EVP_PKEY_base_id(public_key.get()) == EVP_PKEY_RSA
? NID_sha256WithRSAEncryption
: NID_ecdsa_with_SHA256;
X509_ALGOR_set0(sig_algor, OBJ_nid2obj(sig_algo_nid), V_ASN1_NULL, NULL);
// DER encode.
buffer = NULL;
length = i2d_NETSCAPE_SPKI(spki.get(), &buffer);
if (length <= 0) {
LOG(ERROR) << __func__ << ": Failed to get SPKI.";
return false;
}
spkac->assign(reinterpret_cast<char*>(buffer), length);
OPENSSL_free(buffer);
return true;
}
bool CryptoUtilityImpl::VerifyCertificate(
const std::string& certificate, const std::string& ca_public_key_hex) {
crypto::ScopedRSA rsa = CreateRSAFromHexModulus(ca_public_key_hex);
if (!rsa.get()) {
LOG(ERROR) << __func__ << ": Failed to decode CA public key.";
return false;
}
crypto::ScopedEVP_PKEY issuer_key(EVP_PKEY_new());
if (!issuer_key.get()) {
LOG(ERROR) << __func__ << ": Failed to create EVP PKEY.";
return false;
}
EVP_PKEY_assign_RSA(issuer_key.get(), rsa.release());
auto x509 = CreateX509FromCertificate(certificate);
if (!x509.get()) {
LOG(ERROR) << __func__ << ": Failed to parse certificate.";
return false;
}
if (X509_verify(x509.get(), issuer_key.get()) != 1) {
LOG(ERROR) << __func__ << ": Bad certificate signature.";
return false;
}
return true;
}
bool CryptoUtilityImpl::GetCertificateSubjectPublicKeyInfo(
const std::string& certificate, std::string* public_key) {
// Some TPM 1.2 certificates use OAEP key type (rsaOAEP (PKCS #1)). It is not
// supported algorithm in OpenSSL, so we can't parse the public key data to
// public key object.
//
// At here, we only decode X509 format and store raw byte string
// (ASN1_BIT_STRING) of SubjectPublicKeyInfo to x509->cert_info->key, such
// that we can safely pass this i2d_X509_PUBKEY, since it directly output raw
// byte string. But we can't pass it some utility which attempt to parse it
// such like X509_PUBKEY_get().
auto x509 = CreateX509FromCertificate(certificate);
if (!x509.get()) {
LOG(ERROR) << __func__ << ": Failed to parse certificate.";
return false;
}
unsigned char* pubkey_buffer = nullptr;
int length =
i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509.get()), &pubkey_buffer);
if (length < 0) {
LOG(ERROR) << __func__
<< ": Failed to dump SubjectPublicKeyInfo from cert.";
return false;
}
crypto::ScopedOpenSSLBytes scoped_pubkey_buffer(pubkey_buffer);
public_key->assign(reinterpret_cast<char*>(pubkey_buffer), length);
return true;
}
bool CryptoUtilityImpl::GetCertificatePublicKey(const std::string& certificate,
std::string* public_key) {
auto x509 = CreateX509FromCertificate(certificate);
if (!x509.get()) {
LOG(ERROR) << __func__ << ": Failed to parse certificate.";
return false;
}
crypto::ScopedEVP_PKEY pkey(X509_get_pubkey(x509.get()));
if (!pkey) {
LOG(ERROR) << __func__ << ": Failed to get EVP_PKEY from the certificate: "
<< GetOpenSSLError();
return false;
}
crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey.get()));
if (!rsa) {
LOG(ERROR) << __func__
<< ": Failed to get RSA from EVP_PKEY: " << GetOpenSSLError();
return false;
}
int der_length = i2d_RSAPublicKey(rsa.get(), nullptr);
if (der_length < 0) {
LOG(ERROR) << __func__
<< ": Bad length of der-encoded output: " << GetOpenSSLError();
return false;
}
public_key->resize(der_length);
unsigned char* der_buffer =
reinterpret_cast<unsigned char*>(base::data(*public_key));
if (i2d_RSAPublicKey(rsa.get(), &der_buffer) < 0) {
LOG(ERROR) << __func__
<< ": Bad length of der-encoded output: " << GetOpenSSLError();
return false;
}
return true;
}
bool CryptoUtilityImpl::GetCertificateIssuerName(const std::string& certificate,
std::string* issuer_name) {
auto x509 = CreateX509FromCertificate(certificate);
if (!x509.get()) {
LOG(ERROR) << __func__ << ": Failed to parse certificate.";
return false;
}
char issuer_buf[100]; // A longer CN will truncate.
X509_NAME_get_text_by_NID(X509_get_issuer_name(x509.get()), NID_commonName,
issuer_buf, base::size(issuer_buf));
issuer_name->assign(issuer_buf);
return true;
}
bool CryptoUtilityImpl::GetKeyDigest(const std::string& public_key,
std::string* key_digest) {
auto asn1_ptr = reinterpret_cast<const unsigned char*>(public_key.data());
crypto::ScopedRSA rsa(d2i_RSA_PUBKEY(NULL, &asn1_ptr, public_key.size()));
if (!rsa.get()) {
LOG(ERROR) << __func__ << ": Failed to decode certified public key.";
return false;
}
std::vector<unsigned char> modulus(RSA_size(rsa.get()));
const BIGNUM* n;
RSA_get0_key(rsa.get(), &n, nullptr, nullptr);
if (BN_bn2bin(n, modulus.data()) != modulus.size()) {
LOG(ERROR) << __func__ << ": Failed to extract modulus.";
return false;
}
char digest_buf[base::kSHA1Length];
base::SHA1HashBytes(modulus.data(), modulus.size(),
reinterpret_cast<unsigned char*>(digest_buf));
key_digest->assign(digest_buf, sizeof(digest_buf));
return true;
}
} // namespace attestation