Files
android/libwvdrmengine/factory_upload_tool/src/WidevineProvisioner.cpp
Cong Lin 691f43cbef Factory BCC extraction tool to consume verified device info
BCC extraction tool calls OEMCrypto_GetDeviceInformation() to read
verified device info from TEE. If the verified device info is not
available, (e.g. not implemented), it falls back to using OS properties.

This CL is mostly identical to ag/20799640, which has the same change
for our internal extraction tool. For historical reason, we keep two
copies of the extraction tool which are slightly different from each
other, one for factory use, one for debug use. Long term they will be
merged.

Test: Ran the tool on Pixel 7 w/wo verified device info being present
Bug: 263312447
Change-Id: Ib9c77dee45e9ff996fc2dc2da14f16f60eaff77c
2022-12-20 20:07:10 -08:00

327 lines
11 KiB
C++

// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
#include "WidevineProvisioner.h"
#include <cppbor.h>
#include <cppbor_parse.h>
#include <keymaster/cppcose/cppcose.h>
#include <openssl/bn.h>
#include <openssl/curve25519.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/hkdf.h>
#include <openssl/rand.h>
#include <string.h>
#include <memory>
#include <string>
#include <utility>
#include "WidevineOemcryptoInterface.h"
#include "log.h"
#include "properties.h"
namespace widevine {
namespace {
const std::vector<std::vector<uint8_t>> kAuthorizedEekRoots = {
{0x99, 0xB9, 0xEE, 0xDD, 0x5E, 0xE4, 0x52, 0xF6, 0x85, 0xC6, 0x4C,
0x62, 0xDC, 0x3E, 0x61, 0xAB, 0x57, 0x48, 0x7D, 0x75, 0x37, 0x29,
0xAD, 0x76, 0x80, 0x32, 0xD2, 0xB3, 0xCB, 0x63, 0x58, 0xD9},
};
} // namespace
WidevineProvisioner::WidevineProvisioner() {
InitializeCryptoInterface();
assert(crypto_interface_ != nullptr);
}
bool WidevineProvisioner::GenerateCertificateRequest(
bool testMode, const std::vector<uint8_t>& endpointEncCertChain,
std::vector<uint8_t>& deviceInfo, std::vector<uint8_t>& protectedData) {
if (!GetDeviceInfo(deviceInfo)) {
LOGE("Failed to get device_info.");
return false;
}
std::vector<uint8_t> bcc;
OEMCryptoResult result = crypto_interface_->GetBcc(bcc);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get BCC.");
return false;
}
if (!GenerateProtectedData(testMode, endpointEncCertChain, bcc,
protectedData)) {
LOGE("Failed to generate protected data.");
return false;
}
return true;
}
bool WidevineProvisioner::TryAddVerifiedDeviceInfo(
cppbor::Map& device_info_map) {
VerifiedDeviceInfo verified_device_info;
OEMCryptoResult result =
crypto_interface_->GetVerifiedDeviceInformation(verified_device_info);
if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
// OEMCrypto v17 and earlier doesn't support GetDeviceInformation()
LOGI("OEMCrypto_GetDeviceInformation is not implemented.");
return true;
}
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get verified device information.");
return false;
}
auto [parsed, _, err] = cppbor::parse(
reinterpret_cast<const uint8_t*>(verified_device_info.device_info.data()),
verified_device_info.device_info.size());
if (!parsed || !parsed->asMap()) {
LOGE("Failed to parse the verified device info cbor: %s", err.c_str());
return false;
}
const cppbor::Map* verified_device_info_map = parsed->asMap();
auto& make = verified_device_info_map->get("manufacturer");
if (make && make->asTstr() && make->asTstr()->value() != "") {
device_info_map.add("manufacturer", make->asTstr()->value());
}
auto& model = verified_device_info_map->get("model");
if (model && model->asTstr() && model->asTstr()->value() != "") {
device_info_map.add("model", model->asTstr()->value());
}
auto& fused = verified_device_info_map->get("fused");
if (fused && fused->asUint()) {
device_info_map.add("fused", fused->asUint()->value());
}
device_info_map.canonicalize();
return true;
}
bool WidevineProvisioner::GetDeviceInfoCommon(cppbor::Map& device_info_map) {
if (!TryAddVerifiedDeviceInfo(device_info_map)) return false;
// Add device information from OS properties if the verified device info is
// not present
if (device_info_map.get("manufacturer") == nullptr) {
std::string company_name;
if (!wvcdm::Properties::GetCompanyName(&company_name) ||
company_name.empty()) {
LOGE("Failed to get company name.");
return false;
}
device_info_map.add(cppbor::Tstr("manufacturer"),
cppbor::Tstr(company_name));
}
if (device_info_map.get("model") == nullptr) {
std::string model_name;
if (!wvcdm::Properties::GetModelName(&model_name) || model_name.empty()) {
LOGE("Failed to get model name.");
return false;
}
device_info_map.add(cppbor::Tstr("model"), cppbor::Tstr(model_name));
}
if (device_info_map.get("device") == nullptr) {
std::string device_name;
if (!wvcdm::Properties::GetDeviceName(&device_name) ||
device_name.empty()) {
LOGE("Failed to get device name.");
return false;
}
device_info_map.add(cppbor::Tstr("device"), cppbor::Tstr(device_name));
}
if (device_info_map.get("product") == nullptr) {
std::string product_name;
if (!wvcdm::Properties::GetProductName(&product_name) ||
product_name.empty()) {
LOGE("Failed to get product name.");
return false;
}
device_info_map.add(cppbor::Tstr("product"), cppbor::Tstr(product_name));
}
std::string arch_name;
if (!wvcdm::Properties::GetArchitectureName(&arch_name) ||
arch_name.empty()) {
LOGE("Failed to get architecture name.");
return false;
}
device_info_map.add(cppbor::Tstr("architecture"), cppbor::Tstr(arch_name));
std::string build_info;
if (!wvcdm::Properties::GetBuildInfo(&build_info) || build_info.empty()) {
LOGE("Failed to get build info.");
return false;
}
device_info_map.add(cppbor::Tstr("fingerprint"), cppbor::Tstr(build_info));
std::string oemcrypto_build_info;
OEMCryptoResult result =
crypto_interface_->GetOEMCryptoBuildInfo(oemcrypto_build_info);
if (result != OEMCrypto_SUCCESS) {
LOGE("Failed to get oemcrypto build info.");
return false;
}
device_info_map.add(cppbor::Tstr("oemcrypto_build_info"),
cppbor::Tstr(oemcrypto_build_info));
device_info_map.canonicalize();
return true;
}
bool WidevineProvisioner::GetDeviceInfo(std::vector<uint8_t>& device_info) {
auto device_info_map = cppbor::Map();
device_info_map.add(cppbor::Tstr("type"), cppbor::Tstr("widevine"));
device_info_map.add(cppbor::Tstr("version"), cppbor::Uint(2));
device_info_map.canonicalize();
if (!GetDeviceInfoCommon(device_info_map)) return false;
device_info = device_info_map.canonicalize().encode();
return true;
}
bool WidevineProvisioner::GenerateProtectedData(
bool test_mode, const std::vector<uint8_t>& endpoint_encryption_cert_chain,
std::vector<uint8_t> bcc, std::vector<uint8_t>& protected_data) const {
// Encrypt |signedMac| and |bcc_| with GEEK.
std::vector<uint8_t> eek_pub;
std::vector<uint8_t> eek_id;
if (!ValidateAndExtractEekPubAndId(test_mode, endpoint_encryption_cert_chain,
&eek_pub, &eek_id)) {
LOGE("Failed to validate and extract the endpoint encryption key.");
return false;
}
std::vector<uint8_t> ephemeralPrivKey(X25519_PRIVATE_KEY_LEN);
std::vector<uint8_t> ephemeralPubKey(X25519_PUBLIC_VALUE_LEN);
X25519_keypair(ephemeralPubKey.data(), ephemeralPrivKey.data());
auto sessionKey = cppcose::x25519_HKDF_DeriveKey(
ephemeralPubKey, ephemeralPrivKey, eek_pub, true /* senderIsA */);
if (!sessionKey) {
LOGE("Failed to derive the session key.");
return false;
}
// Generate 4 bytes of random data as IV
std::vector<uint8_t> iv(cppcose::kAesGcmNonceLength);
if (RAND_bytes(iv.data(), iv.size()) != 1) {
LOGE("Failed to generate a random nonce.");
return false;
}
auto bcc_parse_result = cppbor::parse(bcc.data(), bcc.size());
cppbor::Item* bcc_item = std::get<0>(bcc_parse_result).get();
auto coseEncrypted = cppcose::constructCoseEncrypt(
*sessionKey, iv,
cppbor::Array() // payload
.add(cppbor::Array()) // Empty signedMac
.add(std::move(bcc_item->clone()))
.encode(),
{}, // aad
BuildCertReqRecipients(ephemeralPubKey, eek_id));
if (!coseEncrypted) {
LOGE("Failed to construct a COSE_Encrypt ProtectedData structure");
return false;
}
protected_data = coseEncrypted->encode();
return true;
}
bool WidevineProvisioner::ValidateAndExtractEekPubAndId(
bool test_mode, const std::vector<uint8_t>& endpoint_encryption_cert_chain,
std::vector<uint8_t>* eek_pub, std::vector<uint8_t>* eek_id) const {
auto parse_result = cppbor::parse(endpoint_encryption_cert_chain);
auto item = std::move(std::get<0>(parse_result));
if (!item || !item->asArray()) {
LOGE("Error parsing EEK chain: %s", std::get<2>(parse_result).c_str());
return false;
}
const cppbor::Array* certArr = item->asArray();
std::vector<uint8_t> lastPubKey;
for (size_t i = 0; i < certArr->size(); ++i) {
auto cosePubKey = cppcose::verifyAndParseCoseSign1(
certArr->get(i)->asArray(), std::move(lastPubKey), {} /* AAD */);
if (!cosePubKey) {
LOGE("Failed to validate EEK chain: %s",
cosePubKey.moveMessage().c_str());
return false;
}
lastPubKey = *std::move(cosePubKey);
// In prod mode the first pubkey should match a well-known Google public
// key.
if (!test_mode && i == 0) {
auto parsedPubKey = cppcose::CoseKey::parse(lastPubKey);
if (!parsedPubKey) {
LOGE("%s", parsedPubKey.moveMessage().c_str());
return false;
}
auto rawPubKey = parsedPubKey->getBstrValue(cppcose::CoseKey::PUBKEY_X);
if (!rawPubKey) {
LOGE("Key is missing required label 'PUBKEY_X'");
return false;
}
std::vector<uint8_t> matcher(rawPubKey->data(),
rawPubKey->data() + rawPubKey->size());
if (std::find(std::begin(kAuthorizedEekRoots),
std::end(kAuthorizedEekRoots),
matcher) == std::end(kAuthorizedEekRoots)) {
LOGE("Unrecognized root of EEK chain");
return false;
}
}
}
auto eek = cppcose::CoseKey::parseX25519(lastPubKey, true /* requireKid */);
if (!eek) {
LOGE("Failed to get EEK: %s", eek.moveMessage().c_str());
return false;
}
*eek_pub = eek->getBstrValue(cppcose::CoseKey::PUBKEY_X).value();
*eek_id = eek->getBstrValue(cppcose::CoseKey::KEY_ID).value();
return true;
}
cppbor::Array WidevineProvisioner::BuildCertReqRecipients(
const std::vector<uint8_t>& pubkey, const std::vector<uint8_t>& kid) const {
return cppbor::Array() // Array of recipients
.add(cppbor::Array() // Recipient
.add(cppbor::Map() // Protected
.add(cppcose::ALGORITHM, cppcose::ECDH_ES_HKDF_256)
.canonicalize()
.encode())
.add(cppbor::Map() // Unprotected
.add(cppcose::COSE_KEY,
cppbor::Map()
.add(cppcose::CoseKey::KEY_TYPE,
cppcose::OCTET_KEY_PAIR)
.add(cppcose::CoseKey::CURVE, cppcose::X25519)
.add(cppcose::CoseKey::PUBKEY_X, pubkey)
.canonicalize())
.add(cppcose::KEY_ID, kid)
.canonicalize())
.add(cppbor::Null())); // No ciphertext
}
void WidevineProvisioner::InitializeCryptoInterface() {
std::string oemcrypto_path;
if (!wvcdm::Properties::GetOEMCryptoPath(&oemcrypto_path)) {
LOGE("Failed to get OEMCrypto path.");
}
LOGI("OEMCrypto path is %s", oemcrypto_path.c_str());
crypto_interface_ = std::make_unique<OEMCryptoInterface>();
if (!crypto_interface_->Init(oemcrypto_path)) {
LOGE("Failed to initialize OEMCrypto interface.");
crypto_interface_.reset(nullptr);
}
}
} // namespace widevine