369 lines
16 KiB
C++
369 lines
16 KiB
C++
/*
|
|
* Copyright 2015 The Android Open Source Project
|
|
*
|
|
* 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 <keymaster/key_blob_utils/auth_encrypted_key_blob.h>
|
|
|
|
#include <openssl/digest.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/hkdf.h>
|
|
|
|
#include <keymaster/android_keymaster_utils.h>
|
|
#include <keymaster/authorization_set.h>
|
|
#include <keymaster/key_blob_utils/ocb_utils.h>
|
|
#include <keymaster/km_openssl/openssl_err.h>
|
|
#include <keymaster/logger.h>
|
|
#include <keymaster/random_source.h>
|
|
|
|
namespace keymaster {
|
|
|
|
namespace {
|
|
|
|
constexpr uint8_t kAesGcmDescriptor1[] = "AES-256-GCM-HKDF-SHA-256, version 1";
|
|
constexpr uint8_t kAesGcmDescriptor2[] = "AES-256-GCM-HKDF-SHA-256, version 2";
|
|
constexpr size_t kAesGcmNonceLength = 12;
|
|
constexpr size_t kAesGcmTagLength = 16;
|
|
constexpr size_t kAes256KeyLength = 256 / 8;
|
|
|
|
KmErrorOr<Buffer> generate_nonce(const RandomSource& random, size_t size) {
|
|
Buffer nonce;
|
|
if (!nonce.Reinitialize(size)) return KM_ERROR_MEMORY_ALLOCATION_FAILED;
|
|
|
|
random.GenerateRandom(nonce.peek_write(), size);
|
|
nonce.advance_write(size);
|
|
return nonce;
|
|
}
|
|
|
|
KmErrorOr<Buffer> BuildDerivationInfo(const AuthEncryptedBlobFormat format, //
|
|
const AuthorizationSet& hw_enforced, //
|
|
const AuthorizationSet& sw_enforced, //
|
|
const AuthorizationSet& hidden,
|
|
const SecureDeletionData& secure_deletion_data) {
|
|
bool use_sdd = requiresSecureDeletion(format);
|
|
|
|
size_t info_len =
|
|
hidden.SerializedSize() + hw_enforced.SerializedSize() + sw_enforced.SerializedSize();
|
|
if (use_sdd) {
|
|
info_len += sizeof(kAesGcmDescriptor2) +
|
|
secure_deletion_data.factory_reset_secret.SerializedSize() +
|
|
secure_deletion_data.secure_deletion_secret.SerializedSize() +
|
|
sizeof(secure_deletion_data.key_slot);
|
|
} else {
|
|
info_len += sizeof(kAesGcmDescriptor1);
|
|
}
|
|
|
|
Buffer info(info_len);
|
|
info.write(use_sdd ? kAesGcmDescriptor2 : kAesGcmDescriptor1);
|
|
uint8_t* buf = info.peek_write();
|
|
const uint8_t* end = info.peek_write() + info.available_write();
|
|
buf = hidden.Serialize(buf, end);
|
|
buf = hw_enforced.Serialize(buf, end);
|
|
buf = sw_enforced.Serialize(buf, end);
|
|
|
|
if (use_sdd) {
|
|
buf = secure_deletion_data.factory_reset_secret.Serialize(buf, end);
|
|
buf = secure_deletion_data.secure_deletion_secret.Serialize(buf, end);
|
|
static_assert(std::is_same_v<decltype(secure_deletion_data.key_slot), uint32_t>);
|
|
buf = append_uint32_to_buf(buf, end, secure_deletion_data.key_slot);
|
|
}
|
|
|
|
if (!buf || buf != end || !info.advance_write(buf - info.peek_write())) {
|
|
LOG_S("Buffer management error", 0);
|
|
return KM_ERROR_UNKNOWN_ERROR;
|
|
}
|
|
|
|
return info;
|
|
}
|
|
|
|
KmErrorOr<Buffer> DeriveAesGcmKeyEncryptionKey(const AuthEncryptedBlobFormat format, //
|
|
const AuthorizationSet& hw_enforced, //
|
|
const AuthorizationSet& sw_enforced, //
|
|
const AuthorizationSet& hidden, //
|
|
const SecureDeletionData& secure_deletion_data, //
|
|
const KeymasterKeyBlob& master_key) {
|
|
Buffer prk(EVP_MAX_MD_SIZE);
|
|
size_t out_len = EVP_MAX_MD_SIZE;
|
|
if (!HKDF_extract(prk.peek_write(), &out_len, EVP_sha256(), master_key.key_material,
|
|
master_key.key_material_size, nullptr /* salt */, 0 /* salt_len */)) {
|
|
return TranslateLastOpenSslError();
|
|
}
|
|
|
|
KmErrorOr<Buffer> info =
|
|
BuildDerivationInfo(format, hw_enforced, sw_enforced, hidden, secure_deletion_data);
|
|
if (!info) return info.error();
|
|
|
|
if (!prk.advance_write(out_len) || !prk.available_read() || !info->available_read()) {
|
|
return KM_ERROR_UNKNOWN_ERROR;
|
|
}
|
|
|
|
Buffer keyEncryptionKey(kAes256KeyLength);
|
|
if (!HKDF_expand(keyEncryptionKey.peek_write(), keyEncryptionKey.available_write(), //
|
|
EVP_sha256(), //
|
|
prk.peek_read(), prk.available_read(), //
|
|
info->peek_read(), info->available_read())) {
|
|
return TranslateLastOpenSslError();
|
|
}
|
|
|
|
return keyEncryptionKey;
|
|
}
|
|
|
|
KmErrorOr<EncryptedKey> AesGcmEncryptKey(const AuthorizationSet& hw_enforced, //
|
|
const AuthorizationSet& sw_enforced, //
|
|
const AuthorizationSet& hidden, //
|
|
const SecureDeletionData& secure_deletion_data, //
|
|
const KeymasterKeyBlob& master_key, //
|
|
const KeymasterKeyBlob& plaintext, //
|
|
const AuthEncryptedBlobFormat format, //
|
|
Buffer nonce) {
|
|
KmErrorOr<Buffer> kek = DeriveAesGcmKeyEncryptionKey(format, hw_enforced, sw_enforced, hidden,
|
|
secure_deletion_data, master_key);
|
|
if (!kek) return kek.error();
|
|
|
|
bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
|
|
if (!ctx) return KM_ERROR_MEMORY_ALLOCATION_FAILED;
|
|
|
|
int ciphertext_len = plaintext.size();
|
|
int unused_len = 0;
|
|
EncryptedKey retval;
|
|
retval.format = format;
|
|
retval.ciphertext = KeymasterKeyBlob(ciphertext_len);
|
|
retval.nonce = move(nonce);
|
|
retval.tag = Buffer(kAesGcmTagLength);
|
|
|
|
if (!(EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, kek->peek_read(),
|
|
retval.nonce.peek_read()) &&
|
|
EVP_EncryptUpdate(ctx.get(), retval.ciphertext.writable_data(), &ciphertext_len,
|
|
plaintext.key_material, plaintext.size()) &&
|
|
EVP_EncryptFinal_ex(ctx.get(), retval.ciphertext.writable_data() /* not written to */,
|
|
&unused_len) &&
|
|
EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagLength,
|
|
retval.tag.peek_write()))) {
|
|
return TranslateLastOpenSslError();
|
|
}
|
|
|
|
if (plaintext.size() != static_cast<size_t>(ciphertext_len) || 0 != unused_len ||
|
|
!retval.tag.advance_write(kAesGcmTagLength)) {
|
|
return KM_ERROR_UNKNOWN_ERROR;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
KmErrorOr<KeymasterKeyBlob> AesGcmDecryptKey(const DeserializedKey& key,
|
|
const AuthorizationSet& hidden,
|
|
const SecureDeletionData& secure_deletion_data,
|
|
const KeymasterKeyBlob& master_key) {
|
|
KmErrorOr<Buffer> kek =
|
|
DeriveAesGcmKeyEncryptionKey(key.encrypted_key.format, key.hw_enforced, key.sw_enforced,
|
|
hidden, secure_deletion_data, master_key);
|
|
if (!kek) return kek.error();
|
|
|
|
bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
|
|
if (!ctx) return KM_ERROR_MEMORY_ALLOCATION_FAILED;
|
|
|
|
int plaintext_len = key.encrypted_key.ciphertext.size();
|
|
int unused_len = 0;
|
|
KeymasterKeyBlob plaintext(plaintext_len);
|
|
if (!(EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, kek->peek_read(),
|
|
key.encrypted_key.nonce.peek_read()) &&
|
|
EVP_DecryptUpdate(ctx.get(), plaintext.writable_data(), &plaintext_len,
|
|
key.encrypted_key.ciphertext.key_material,
|
|
key.encrypted_key.ciphertext.size()) &&
|
|
EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagLength,
|
|
const_cast<uint8_t*>(key.encrypted_key.tag.peek_read())))) {
|
|
return TranslateLastOpenSslError();
|
|
}
|
|
|
|
if (!EVP_DecryptFinal_ex(ctx.get(), plaintext.writable_data() /* not written to */,
|
|
&unused_len)) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
|
|
if (key.encrypted_key.ciphertext.size() != plaintext.size() || 0 != unused_len) {
|
|
return KM_ERROR_UNKNOWN_ERROR;
|
|
}
|
|
|
|
return plaintext;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
KmErrorOr<KeymasterKeyBlob> SerializeAuthEncryptedBlob(const EncryptedKey& encrypted_key,
|
|
const AuthorizationSet& hw_enforced,
|
|
const AuthorizationSet& sw_enforced,
|
|
uint32_t key_slot) {
|
|
bool use_key_slot = requiresSecureDeletion(encrypted_key.format);
|
|
|
|
size_t size = 1 /* version byte */ + encrypted_key.nonce.SerializedSize() +
|
|
encrypted_key.ciphertext.SerializedSize() + encrypted_key.tag.SerializedSize() +
|
|
hw_enforced.SerializedSize() + sw_enforced.SerializedSize();
|
|
if (use_key_slot) size += sizeof(key_slot);
|
|
if (isVersionedFormat(encrypted_key.format)) {
|
|
size += sizeof(encrypted_key.kdf_version);
|
|
size += sizeof(encrypted_key.addl_info);
|
|
}
|
|
KeymasterKeyBlob retval;
|
|
if (!retval.Reset(size)) return KM_ERROR_MEMORY_ALLOCATION_FAILED;
|
|
|
|
uint8_t* buf = retval.writable_data();
|
|
const uint8_t* end = retval.end();
|
|
|
|
*buf++ = encrypted_key.format;
|
|
buf = encrypted_key.nonce.Serialize(buf, end);
|
|
buf = encrypted_key.ciphertext.Serialize(buf, end);
|
|
buf = encrypted_key.tag.Serialize(buf, end);
|
|
if (isVersionedFormat(encrypted_key.format)) {
|
|
buf = append_uint32_to_buf(buf, end, encrypted_key.kdf_version);
|
|
buf = append_uint32_to_buf(buf, end, encrypted_key.addl_info);
|
|
}
|
|
buf = hw_enforced.Serialize(buf, end);
|
|
buf = sw_enforced.Serialize(buf, end);
|
|
if (use_key_slot) buf = append_uint32_to_buf(buf, end, key_slot);
|
|
|
|
if (buf != retval.end()) return KM_ERROR_UNKNOWN_ERROR;
|
|
|
|
return retval;
|
|
}
|
|
|
|
KmErrorOr<DeserializedKey> DeserializeAuthEncryptedBlob(const KeymasterKeyBlob& key_blob) {
|
|
if (!key_blob.key_material || key_blob.key_material_size == 0) return KM_ERROR_INVALID_KEY_BLOB;
|
|
|
|
const uint8_t* tmp = key_blob.key_material;
|
|
const uint8_t** buf_ptr = &tmp;
|
|
const uint8_t* end = tmp + key_blob.key_material_size;
|
|
|
|
if (end <= *buf_ptr) return KM_ERROR_INVALID_KEY_BLOB;
|
|
|
|
DeserializedKey retval{};
|
|
retval.encrypted_key.format = static_cast<AuthEncryptedBlobFormat>(*(*buf_ptr)++);
|
|
if (!retval.encrypted_key.nonce.Deserialize(buf_ptr, end) || //
|
|
!retval.encrypted_key.ciphertext.Deserialize(buf_ptr, end) || //
|
|
!retval.encrypted_key.tag.Deserialize(buf_ptr, end)) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
|
|
if (isVersionedFormat(retval.encrypted_key.format)) {
|
|
if (!copy_uint32_from_buf(buf_ptr, end, &retval.encrypted_key.kdf_version) ||
|
|
!copy_uint32_from_buf(buf_ptr, end, &retval.encrypted_key.addl_info)) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
}
|
|
|
|
if (!retval.hw_enforced.Deserialize(buf_ptr, end) || //
|
|
!retval.sw_enforced.Deserialize(buf_ptr, end)) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
|
|
if (requiresSecureDeletion(retval.encrypted_key.format)) {
|
|
if (!copy_uint32_from_buf(buf_ptr, end, &retval.key_slot)) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
}
|
|
|
|
if (*buf_ptr != end) return KM_ERROR_INVALID_KEY_BLOB;
|
|
|
|
switch (retval.encrypted_key.format) {
|
|
case AES_OCB:
|
|
if (retval.encrypted_key.nonce.available_read() != OCB_NONCE_LENGTH ||
|
|
retval.encrypted_key.tag.available_read() != OCB_TAG_LENGTH) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
return retval;
|
|
|
|
case AES_GCM_WITH_SW_ENFORCED:
|
|
case AES_GCM_WITH_SECURE_DELETION:
|
|
case AES_GCM_WITH_SW_ENFORCED_VERSIONED:
|
|
case AES_GCM_WITH_SECURE_DELETION_VERSIONED:
|
|
if (retval.encrypted_key.nonce.available_read() != kAesGcmNonceLength ||
|
|
retval.encrypted_key.tag.available_read() != kAesGcmTagLength) {
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
LOG_E("Invalid key blob format %d", retval.encrypted_key.format);
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
|
|
KmErrorOr<EncryptedKey>
|
|
EncryptKey(const KeymasterKeyBlob& plaintext, AuthEncryptedBlobFormat format,
|
|
const AuthorizationSet& hw_enforced, const AuthorizationSet& sw_enforced,
|
|
const AuthorizationSet& hidden, const SecureDeletionData& secure_deletion_data,
|
|
const KeymasterKeyBlob& master_key, const RandomSource& random) {
|
|
switch (format) {
|
|
case AES_OCB: {
|
|
EncryptedKey retval;
|
|
retval.format = format;
|
|
auto nonce = generate_nonce(random, OCB_NONCE_LENGTH);
|
|
if (!nonce) return nonce.error();
|
|
retval.nonce = std::move(*nonce);
|
|
retval.tag.Reinitialize(OCB_TAG_LENGTH);
|
|
keymaster_error_t error =
|
|
OcbEncryptKey(hw_enforced, sw_enforced, hidden, master_key, plaintext, retval.nonce,
|
|
&retval.ciphertext, &retval.tag);
|
|
if (error != KM_ERROR_OK) return error;
|
|
return retval;
|
|
}
|
|
|
|
case AES_GCM_WITH_SW_ENFORCED:
|
|
case AES_GCM_WITH_SECURE_DELETION:
|
|
case AES_GCM_WITH_SW_ENFORCED_VERSIONED:
|
|
case AES_GCM_WITH_SECURE_DELETION_VERSIONED: {
|
|
auto nonce = generate_nonce(random, kAesGcmNonceLength);
|
|
if (!nonce) return nonce.error();
|
|
return AesGcmEncryptKey(hw_enforced, sw_enforced, hidden, secure_deletion_data, master_key,
|
|
plaintext, format, std::move(*nonce));
|
|
}
|
|
}
|
|
|
|
LOG_E("Invalid key blob format %d", format);
|
|
return KM_ERROR_UNKNOWN_ERROR;
|
|
}
|
|
|
|
KmErrorOr<KeymasterKeyBlob> DecryptKey(const DeserializedKey& key, const AuthorizationSet& hidden,
|
|
const SecureDeletionData& secure_deletion_data,
|
|
const KeymasterKeyBlob& master_key) {
|
|
KeymasterKeyBlob retval;
|
|
switch (key.encrypted_key.format) {
|
|
case AES_OCB: {
|
|
keymaster_error_t error = OcbDecryptKey(
|
|
key.hw_enforced, key.sw_enforced, hidden, master_key, key.encrypted_key.ciphertext,
|
|
key.encrypted_key.nonce, key.encrypted_key.tag, &retval);
|
|
if (error != KM_ERROR_OK) return error;
|
|
return retval;
|
|
}
|
|
|
|
case AES_GCM_WITH_SW_ENFORCED:
|
|
case AES_GCM_WITH_SECURE_DELETION:
|
|
case AES_GCM_WITH_SW_ENFORCED_VERSIONED:
|
|
case AES_GCM_WITH_SECURE_DELETION_VERSIONED:
|
|
return AesGcmDecryptKey(key, hidden, secure_deletion_data, master_key);
|
|
}
|
|
|
|
LOG_E("Invalid key blob format %d", key.encrypted_key.format);
|
|
return KM_ERROR_INVALID_KEY_BLOB;
|
|
}
|
|
|
|
bool requiresSecureDeletion(const AuthEncryptedBlobFormat& fmt) {
|
|
return fmt == AES_GCM_WITH_SECURE_DELETION || fmt == AES_GCM_WITH_SECURE_DELETION_VERSIONED;
|
|
}
|
|
|
|
bool isVersionedFormat(const AuthEncryptedBlobFormat& fmt) {
|
|
return fmt == AES_GCM_WITH_SW_ENFORCED_VERSIONED ||
|
|
fmt == AES_GCM_WITH_SECURE_DELETION_VERSIONED;
|
|
}
|
|
|
|
} // namespace keymaster
|