Move ASOP factory extraction tool to its own directory
Moved some source to common folder. Added uploading script which is also shared by CE CDM partners. Added README. Test: m wv_factory_extraction_tool Bug: 414642286 Change-Id: I565027b75528ab28f9f1eb8d9086c0213de992d0
This commit is contained in:
34
libwvdrmengine/tools/factory_upload_tool/aosp/README.md
Normal file
34
libwvdrmengine/tools/factory_upload_tool/aosp/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Widevine Factory Extraction Tool for AOSP partners
|
||||
|
||||
This tool extracts the BCC and generates the Certificate Signing Request (CSR)
|
||||
needed to be uploaded to Wideivine Provisioning server for Prov4 device registration.
|
||||
|
||||
## CSR extraction instructions:
|
||||
|
||||
1. Make `wv_factory_extraction_tool`:
|
||||
- m wv_factory_extraction_tool
|
||||
|
||||
2. Locate build output and adb push it to the target device, e.g.:
|
||||
- adb push out/target/product/{product name}/vendor/bin/hw/wv_factory_extraction_tool /vendor/bin/hw/wv_factory_extraction_tool
|
||||
|
||||
3. Restart Widevine service on the device to ensure a clean state
|
||||
before running the tool:
|
||||
- adb shell pkill -f -9 widevine
|
||||
|
||||
4. Run the wv_factory_extractor tool on the target device. By default,
|
||||
the tool prints the CSR in JSON format directly to the console.
|
||||
- adb shell /vendor/bin/hw/wv_factory_extraction_tool json_csr
|
||||
or just
|
||||
- adb shell /vendor/bin/hw/wv_factory_extraction_tool
|
||||
For additional options, run the tool with the `help` argument:
|
||||
- adb shell /vendor/bin/hw/wv_factory_extraction_tool help
|
||||
|
||||
## Uploading instructions:
|
||||
|
||||
1. Save the extracted CSR to `csr.json`.
|
||||
|
||||
2. Upload the `csr.json` file using `common/wv_upload_tool.py`:
|
||||
python3 wv_upload_tool.py --credentials=cred.json --org-name={your organization name} --json-csr=csr.json
|
||||
- Replace cred.json with the path to your OAuth 2.0 client credentials file.
|
||||
You can obtain this file through the Google Cloud Platform.
|
||||
- Replace {your organization name} with the name of your organization.
|
||||
259
libwvdrmengine/tools/factory_upload_tool/aosp/cli.cpp
Normal file
259
libwvdrmengine/tools/factory_upload_tool/aosp/cli.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
//
|
||||
|
||||
#define LOG_TAG "wv_factory_extraction_tool"
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
#include <sys/random.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "BccParser.h"
|
||||
#include "WidevineProvisioner.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
|
||||
constexpr size_t kChallengeSize = 64;
|
||||
|
||||
// The Google root key for the Endpoint Encryption Key chain, encoded as
|
||||
// COSE_Sign1
|
||||
inline constexpr uint8_t kCoseEncodedRootCert[] = {
|
||||
0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x2a, 0xa4, 0x01, 0x01, 0x03,
|
||||
0x27, 0x20, 0x06, 0x21, 0x58, 0x20, 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, 0x58, 0x40, 0x1e, 0x22, 0x08, 0x4b, 0xa4, 0xb7, 0xa4, 0xc8,
|
||||
0xd7, 0x4e, 0x03, 0x0e, 0xfe, 0xb8, 0xaf, 0x14, 0x4c, 0xa7, 0x3b, 0x6f,
|
||||
0xa5, 0xcd, 0xdc, 0xda, 0x79, 0xc6, 0x2b, 0x64, 0xfe, 0x99, 0x39, 0xaf,
|
||||
0x76, 0xe7, 0x80, 0xfa, 0x66, 0x00, 0x85, 0x0d, 0x07, 0x98, 0x2a, 0xac,
|
||||
0x91, 0x5c, 0xa7, 0x25, 0x14, 0x49, 0x06, 0x34, 0x75, 0xca, 0x8a, 0x27,
|
||||
0x7a, 0xd9, 0xe3, 0x5a, 0x49, 0xeb, 0x02, 0x03};
|
||||
|
||||
// The Google Endpoint Encryption Key certificate, encoded as COSE_Sign1
|
||||
inline constexpr uint8_t kCoseEncodedGeekCert[] = {
|
||||
0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x4e, 0xa5, 0x01, 0x01, 0x02,
|
||||
0x58, 0x20, 0xd0, 0xae, 0xc1, 0x15, 0xca, 0x2a, 0xcf, 0x73, 0xae, 0x6b,
|
||||
0xcc, 0xcb, 0xd1, 0x96, 0x1d, 0x65, 0xe8, 0xb1, 0xdd, 0xd7, 0x4a, 0x1a,
|
||||
0x37, 0xb9, 0x43, 0x3a, 0x97, 0xd5, 0x99, 0xdf, 0x98, 0x08, 0x03, 0x38,
|
||||
0x18, 0x20, 0x04, 0x21, 0x58, 0x20, 0xbe, 0x85, 0xe7, 0x46, 0xc4, 0xa3,
|
||||
0x42, 0x5a, 0x40, 0xd9, 0x36, 0x3a, 0xa6, 0x15, 0xd0, 0x2c, 0x58, 0x7e,
|
||||
0x3d, 0xdc, 0x33, 0x02, 0x32, 0xd2, 0xfc, 0x5e, 0x1e, 0x87, 0x25, 0x5f,
|
||||
0x72, 0x60, 0x58, 0x40, 0x9b, 0xcf, 0x90, 0xe2, 0x2e, 0x4b, 0xab, 0xd1,
|
||||
0x18, 0xb1, 0x0e, 0x8e, 0x5d, 0x20, 0x27, 0x4b, 0x84, 0x58, 0xfe, 0xfc,
|
||||
0x32, 0x90, 0x7e, 0x72, 0x05, 0x83, 0xbc, 0xd7, 0x82, 0xbe, 0xfa, 0x64,
|
||||
0x78, 0x2d, 0x54, 0x10, 0x4b, 0xc0, 0x31, 0xbf, 0x6b, 0xe8, 0x1e, 0x35,
|
||||
0xe2, 0xf0, 0x2d, 0xce, 0x6c, 0x2f, 0x4f, 0xf2, 0xf5, 0x4f, 0xa5, 0xd4,
|
||||
0x83, 0xad, 0x96, 0xa2, 0xf1, 0x87, 0x58, 0x04};
|
||||
|
||||
std::vector<uint8_t> generateChallenge() {
|
||||
std::vector<uint8_t> challenge(kChallengeSize);
|
||||
|
||||
ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size());
|
||||
uint8_t* writePtr = challenge.data();
|
||||
while (bytesRemaining > 0) {
|
||||
int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0);
|
||||
if (bytesRead < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
std::cerr << errno << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
bytesRemaining -= bytesRead;
|
||||
writePtr += bytesRead;
|
||||
}
|
||||
|
||||
return challenge;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> getEekChain() {
|
||||
cppbor::Array chain;
|
||||
chain.add(cppbor::EncodedItem(std::vector<uint8_t>(
|
||||
std::begin(kCoseEncodedRootCert), std::end(kCoseEncodedRootCert))));
|
||||
chain.add(cppbor::EncodedItem(std::vector<uint8_t>(
|
||||
std::begin(kCoseEncodedGeekCert), std::end(kCoseEncodedGeekCert))));
|
||||
return chain.encode();
|
||||
}
|
||||
|
||||
cppbor::Array composeCertificateRequest(
|
||||
const std::vector<uint8_t>& protectedData,
|
||||
const std::vector<uint8_t>& verifiedDeviceInfo,
|
||||
const std::vector<uint8_t>& challenge) {
|
||||
cppbor::Array macedKeysToSign =
|
||||
cppbor::Array()
|
||||
.add(std::vector<uint8_t>(0)) // empty protected headers as bstr
|
||||
.add(cppbor::Map()) // empty unprotected headers
|
||||
.add(cppbor::Null()) // nil for the payload
|
||||
.add(std::vector<uint8_t>(0)); // MAC as returned from the HAL
|
||||
|
||||
cppbor::Array deviceInfo = cppbor::Array()
|
||||
.add(cppbor::EncodedItem(verifiedDeviceInfo))
|
||||
.add(cppbor::Map()); // Empty device info
|
||||
|
||||
cppbor::Array certificateRequest =
|
||||
cppbor::Array()
|
||||
.add(std::move(deviceInfo))
|
||||
.add(challenge)
|
||||
.add(cppbor::EncodedItem(protectedData))
|
||||
.add(std::move(macedKeysToSign));
|
||||
return certificateRequest;
|
||||
}
|
||||
|
||||
cppbor::Array getCsr(widevine::WidevineProvisioner& provisioner) {
|
||||
const std::vector<uint8_t> challenge = generateChallenge();
|
||||
std::vector<uint8_t> verifiedDeviceInfo;
|
||||
std::vector<uint8_t> protectedData;
|
||||
|
||||
if (!provisioner.GenerateCertificateRequest(
|
||||
false, getEekChain(), verifiedDeviceInfo, protectedData)) {
|
||||
std::cerr << "Failed to generate certificate request." << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
auto csr =
|
||||
composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge);
|
||||
return csr;
|
||||
}
|
||||
|
||||
std::unique_ptr<cppbor::Array> composeCertificateRequestV3(
|
||||
const std::vector<uint8_t>& csr) {
|
||||
auto [parsedCsr, _, csrErrMsg] = cppbor::parse(csr);
|
||||
if (!parsedCsr) {
|
||||
LOGE("Failed to parse input CSR.");
|
||||
return nullptr;
|
||||
}
|
||||
if (!parsedCsr->asArray()) {
|
||||
LOGE("Input CSR is not a CBOR array.");
|
||||
return nullptr;
|
||||
}
|
||||
std::string fingerPrint;
|
||||
if (!wvcdm::Properties::GetBuildInfo(&fingerPrint)) {
|
||||
LOGE("Failed to get finger print.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cppbor::Map unverifiedDeviceInfo =
|
||||
cppbor::Map().add("fingerprint", cppbor::Tstr(fingerPrint));
|
||||
parsedCsr->asArray()->add(std::move(unverifiedDeviceInfo));
|
||||
return std::unique_ptr<cppbor::Array>(parsedCsr.release()->asArray());
|
||||
}
|
||||
|
||||
std::unique_ptr<cppbor::Array> getCsrV3(
|
||||
widevine::WidevineProvisioner& provisioner) {
|
||||
const std::vector<uint8_t> challenge = generateChallenge();
|
||||
std::vector<uint8_t> csr;
|
||||
if (!provisioner.GenerateCertificateRequestV2(challenge, &csr)) {
|
||||
std::cerr << "Failed to generate certificate request v2." << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
return composeCertificateRequestV3(csr);
|
||||
}
|
||||
|
||||
void printHelp(const char* tool_name) {
|
||||
fprintf(stdout, "Widevine Factory Extraction Tool for AOSP\n\n");
|
||||
fprintf(stdout, "Usage: %s [command]\n\n", tool_name);
|
||||
fprintf(stdout,
|
||||
"This tool extracts BCC and device information and generates CSR "
|
||||
"required for Widevine Provisioning 4.0 factory uploading.\n\n");
|
||||
fprintf(stdout, "Commands:\n");
|
||||
fprintf(stdout, " json_csr (default)\n");
|
||||
fprintf(stdout,
|
||||
" Generates and prints a JSON-formatted "
|
||||
"Certificate Signing\n");
|
||||
fprintf(stdout,
|
||||
" Request (CSR) to be uploaded to the "
|
||||
"Widevine provisioning server.\n\n");
|
||||
fprintf(stdout, " bcc\n");
|
||||
fprintf(stdout,
|
||||
" Outputs the raw binary Bootloader "
|
||||
"Certificate Chain (BCC).\n\n");
|
||||
fprintf(stdout, " bcc_str\n");
|
||||
fprintf(stdout,
|
||||
" Outputs a human-readable, parsed string "
|
||||
"representation of the BCC.\n\n");
|
||||
fprintf(stdout, " device_info\n");
|
||||
fprintf(
|
||||
stdout,
|
||||
" Outputs the raw binary device information blob.\n\n");
|
||||
fprintf(stdout, " csr\n");
|
||||
fprintf(stdout,
|
||||
" Generates and outputs a legacy format Certificate "
|
||||
"Signing Request (CSR) to be uploaded to RKP backend.\n\n");
|
||||
fprintf(stdout, " csr_v3\n");
|
||||
fprintf(stdout,
|
||||
" Generates and outputs a V3 format Certificate "
|
||||
"Signing Request (CSR) to be uploaded to RKP backend.\n\n");
|
||||
fprintf(stdout, " help\n");
|
||||
fprintf(stdout, " Displays this help message.\n");
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
widevine::WidevineProvisioner provisioner;
|
||||
|
||||
// Default to Widevine uploading request format "json_csr" if no arguments are
|
||||
// provided.
|
||||
const char* command = (argc > 1) ? argv[1] : "json_csr";
|
||||
if (!std::strcmp(command, "json_csr")) {
|
||||
std::string request;
|
||||
if (provisioner.GenerateWidevineUploadRequest(request)) {
|
||||
std::copy(request.begin(), request.end(),
|
||||
std::ostream_iterator<char>(std::cout));
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"Failed to generate Widevine uploading request json CSR.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (!std::strcmp(command, "help")) {
|
||||
printHelp(argv[0]);
|
||||
} else if (!std::strcmp(command, "bcc")) {
|
||||
auto bcc = provisioner.GetBcc();
|
||||
fwrite(bcc.data(), 1, bcc.size(), stdout);
|
||||
fflush(stdout);
|
||||
} else if (!std::strcmp(command, "bcc_str")) {
|
||||
auto bcc = provisioner.GetBcc();
|
||||
widevine::BccParser bcc_parser;
|
||||
std::string parsed_bcc = bcc_parser.Parse(bcc);
|
||||
std::copy(parsed_bcc.begin(), parsed_bcc.end(),
|
||||
std::ostream_iterator<char>(std::cout));
|
||||
} else if (!std::strcmp(command, "device_info")) {
|
||||
std::vector<uint8_t> deviceInfo;
|
||||
if (provisioner.GetDeviceInfo(deviceInfo)) {
|
||||
fwrite(deviceInfo.data(), 1, deviceInfo.size(), stdout);
|
||||
fflush(stdout);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to get device info.\n");
|
||||
return 1;
|
||||
}
|
||||
} else if (!std::strcmp(command, "csr")) {
|
||||
auto csr = getCsr(provisioner);
|
||||
auto bytes = csr.encode();
|
||||
std::copy(bytes.begin(), bytes.end(),
|
||||
std::ostream_iterator<char>(std::cout));
|
||||
} else if (!std::strcmp(argv[1], "csr_v3")) {
|
||||
auto csr = getCsrV3(provisioner);
|
||||
if (csr != nullptr) {
|
||||
auto bytes = csr->encode();
|
||||
std::copy(bytes.begin(), bytes.end(),
|
||||
std::ostream_iterator<char>(std::cout));
|
||||
} else {
|
||||
fprintf(stderr, "Failed to generate CSR V3.\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Error: Unknown command '%s'\n\n", command);
|
||||
printHelp(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef BCC_PARSER_H_
|
||||
#define BCC_PARSER_H_
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace widevine {
|
||||
|
||||
// BccParser processes a Provisioning 4.0 device root of trust. It extracts
|
||||
// relevant pieces of information and outputs to std::string.
|
||||
// Relevant documents:
|
||||
// Android definition: go/remote-provisioning-hal#bcc.
|
||||
// Google Dice Profile: go/dice-profile
|
||||
class BccParser {
|
||||
public:
|
||||
explicit BccParser() {}
|
||||
virtual ~BccParser() = default;
|
||||
BccParser(const BccParser&) = delete;
|
||||
BccParser& operator=(const BccParser&) = delete;
|
||||
// Parse and verify a client generated root of trust. This message is part of
|
||||
// an attestation model conforming to the Google Open Dice Profile. This
|
||||
// message is received from a client device to attest it is a valid Widevine
|
||||
// device.
|
||||
virtual std::string Parse(const std::vector<uint8_t>& bcc);
|
||||
|
||||
private:
|
||||
// Process and print CoseKey PubKeyEd25519 / PubKeyECDSA256.
|
||||
bool ProcessDevicePublicKeyInfo(std::stringstream& ss,
|
||||
const cppbor::Map& public_key_info_map);
|
||||
|
||||
// Process and print the DiceChainEntryPayload, which contains subject public
|
||||
// key.
|
||||
bool ProcessDiceChainEntryPayload(std::stringstream& ss,
|
||||
std::string& payload);
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // BCC_PARSER_H_
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef DICE_CBOR_CONSTANTS_H_
|
||||
#define DICE_CBOR_CONSTANTS_H_
|
||||
|
||||
namespace widevine {
|
||||
|
||||
// The BCC is encoded using RFC 8949- Concise Binary Object Representation
|
||||
// (CBOR).
|
||||
|
||||
// The full definition of the following enums can be found here:
|
||||
// go/remote-provisioning-hal#bcc.
|
||||
|
||||
// The device key is encoded in a cbor map. The key values are a mix of
|
||||
// positive and negative integer values.
|
||||
enum {
|
||||
MAP_KEY_DEVICE_KEY_TYPE = 1,
|
||||
MAP_KEY_DEVICE_KEY_ALGORITHM = 3,
|
||||
MAP_KEY_DEVICE_KEY_OPS = 4,
|
||||
MAP_KEY_DEVICE_KEY_CURVE = -1,
|
||||
MAP_KEY_DEVICE_KEY_BYTES_0 = -2,
|
||||
MAP_KEY_DEVICE_KEY_BYTES_1 = -3,
|
||||
};
|
||||
|
||||
// The device key may be encoded in the BCC as either X,Y elliptic curve
|
||||
// coordinates, or as raw bytes. The value is identified using
|
||||
// MAP_KEY_DEVICE_KEY_TYPE.
|
||||
enum {
|
||||
DEVICE_KEY_ENCODING_UNKNOWN = 0,
|
||||
DEVICE_KEY_BYTE_STRING = 1,
|
||||
DEVICE_KEY_OCTET_PAIR = 2,
|
||||
};
|
||||
|
||||
// Android/Widevine Dice Attestation allows two signing models. This is
|
||||
// identified using MAP_KEY_DEVICE_KEY_ALGORITHM.
|
||||
enum {
|
||||
DEVICE_KEY_ALGORITHM_ES256 = -7, // EC key with SHA-256
|
||||
DEVICE_KEY_ALGORITHM_EDDSA = -8, // Pure ED25519.
|
||||
DEVICE_KEY_ALGORITHM_ES384 = -35, // EC key with SHA-384
|
||||
};
|
||||
|
||||
// The curve used to generate the device public key is identified using the
|
||||
// MAP_KEY_DEVICE_KEY_CURVE.
|
||||
enum {
|
||||
DEVICE_KEY_CURVE_P256 = 1,
|
||||
DEVICE_KEY_CURVE_P384 = 2,
|
||||
DEVICE_KEY_CURVE_ED25519 = 6,
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // DICE_CBOR_CONSTANTS_H_
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#ifndef WIDEVINE_PROVISIONER_H_
|
||||
#define WIDEVINE_PROVISIONER_H_
|
||||
|
||||
#include <cppbor.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "WidevineOemcryptoInterface.h"
|
||||
|
||||
namespace widevine {
|
||||
|
||||
class WidevineProvisioner {
|
||||
public:
|
||||
WidevineProvisioner();
|
||||
WidevineProvisioner(const WidevineProvisioner&) = delete;
|
||||
WidevineProvisioner& operator=(const WidevineProvisioner&) = delete;
|
||||
virtual ~WidevineProvisioner() = default;
|
||||
|
||||
std::vector<uint8_t> GetBcc();
|
||||
|
||||
bool GenerateCertificateRequest(
|
||||
bool testMode, const std::vector<uint8_t>& endpointEncCertChain,
|
||||
std::vector<uint8_t>& deviceInfo, std::vector<uint8_t>& protectedData);
|
||||
bool GenerateCertificateRequestV2(const std::vector<uint8_t>& challenge,
|
||||
std::vector<uint8_t>* csr);
|
||||
bool GetDeviceInfo(std::vector<uint8_t>& device_info);
|
||||
bool GenerateWidevineUploadRequest(std::string& request);
|
||||
|
||||
private:
|
||||
bool 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;
|
||||
bool 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;
|
||||
cppbor::Array BuildCertReqRecipients(const std::vector<uint8_t>& pubkey,
|
||||
const std::vector<uint8_t>& kid) const;
|
||||
void InitializeCryptoInterface();
|
||||
bool GetDeviceInfoCommon(cppbor::Map& device_info_map);
|
||||
bool TryAddVerifiedDeviceInfo(cppbor::Map& device_info_map);
|
||||
bool GetDeviceInfoV2(cppbor::Map& device_info_map);
|
||||
void PopulateDeviceInfoFromCborMap(
|
||||
const cppbor::Map& device_info_map,
|
||||
std::map<std::string, std::string>& request_map);
|
||||
|
||||
std::unique_ptr<OEMCryptoInterface> crypto_interface_;
|
||||
};
|
||||
|
||||
} // namespace widevine
|
||||
|
||||
#endif // WIDEVINE_PROVISIONER_H_
|
||||
373
libwvdrmengine/tools/factory_upload_tool/aosp/src/BccParser.cpp
Normal file
373
libwvdrmengine/tools/factory_upload_tool/aosp/src/BccParser.cpp
Normal file
@@ -0,0 +1,373 @@
|
||||
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "BccParser.h"
|
||||
|
||||
#include <cppbor.h>
|
||||
#include <cppbor_parse.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "DiceCborConstants.h"
|
||||
|
||||
namespace widevine {
|
||||
namespace {
|
||||
|
||||
// Sized to hold a P256 public key uncompressed point compatible with X9.62.
|
||||
// The key is formatted in an Z/X/Y format in which Z == 0x04 and X and Y are
|
||||
// the public key coordinates. X and Y are each 32 bytes.
|
||||
constexpr int kP256KeyCoordinateSizeBytes = 256 / 8;
|
||||
// Sized to hold a P384 public key uncompressed point compatible with X9.62.
|
||||
// The key is formatted in an Z/X/Y format in which Z == 0x04 and X and Y are
|
||||
// the public key coordinates. X and Y are each 48 bytes.
|
||||
constexpr int kP384KeyCoordinateSizeBytes = 384 / 8;
|
||||
constexpr int kMarshaledP384KeySizeBytes = kP384KeyCoordinateSizeBytes * 2 + 1;
|
||||
constexpr int kMaxMarshaledECKeySizeBytes = kMarshaledP384KeySizeBytes;
|
||||
constexpr char kMarshaledECKeyZValue = 0x04;
|
||||
constexpr int kED25519KeyDataItemSizeBytes = 32;
|
||||
// The Issuer field key in BccEntryPayload.
|
||||
constexpr int64_t kIssuer = 1;
|
||||
// The Subject field key in BccEntryPayload.
|
||||
constexpr int64_t kSubject = 2;
|
||||
// The SubjectPublicKey field key in BccEntryPayload.
|
||||
constexpr int64_t kSubjectPublicKey = -4670552;
|
||||
|
||||
std::string TypeNameFromType(cppbor::MajorType type) {
|
||||
switch (type) {
|
||||
case cppbor::UINT:
|
||||
return "UINT";
|
||||
case cppbor::NINT:
|
||||
return "NINT";
|
||||
case cppbor::BSTR:
|
||||
return "BSTR";
|
||||
case cppbor::TSTR:
|
||||
return "TSTR";
|
||||
case cppbor::ARRAY:
|
||||
return "ARRAY";
|
||||
case cppbor::MAP:
|
||||
return "MAP";
|
||||
case cppbor::SEMANTIC:
|
||||
return "SEMANTIC";
|
||||
case cppbor::SIMPLE:
|
||||
return "SIMPLE";
|
||||
default:
|
||||
return "undefined type";
|
||||
}
|
||||
}
|
||||
|
||||
std::string GetIssuerSubjectFromBccEntryPayload(
|
||||
const cppbor::Map* bcc_entry_payload) {
|
||||
std::string issuer = "Issuer: ";
|
||||
std::string subject = "Subject: ";
|
||||
for (size_t i = 0; i < bcc_entry_payload->size(); ++i) {
|
||||
const auto& entry = (*bcc_entry_payload)[i];
|
||||
if (entry.first == nullptr || entry.first->asInt() == nullptr ||
|
||||
entry.second == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (entry.first->asInt()->value() == kIssuer) {
|
||||
issuer += (entry.second->asTstr()->value() + "\n");
|
||||
} else if (entry.first->asInt()->value() == kSubject) {
|
||||
subject += (entry.second->asTstr()->value() + "\n");
|
||||
}
|
||||
}
|
||||
return issuer + subject;
|
||||
}
|
||||
|
||||
const cppbor::Bstr* GetSubjectPublicKeyFromBccEntryPayload(
|
||||
const cppbor::Map* bcc_entry_payload) {
|
||||
for (size_t i = 0; i < bcc_entry_payload->size(); ++i) {
|
||||
const auto& entry = (*bcc_entry_payload)[i];
|
||||
if (entry.first == nullptr || entry.first->asInt() == nullptr ||
|
||||
entry.second == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (entry.first->asInt()->value() == kSubjectPublicKey) {
|
||||
return entry.second->asBstr();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// BCC/DiceCertChain definition:
|
||||
// https://source.corp.google.com/android-internal/hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
|
||||
std::string BccParser::Parse(const std::vector<uint8_t>& bcc) {
|
||||
std::stringstream ss;
|
||||
auto [parsed_bcc, _, err_msg] = cppbor::parse(bcc);
|
||||
if (parsed_bcc == nullptr) {
|
||||
ss << "Failed to parse input BCC: " << err_msg.c_str() << "\n";
|
||||
return ss.str();
|
||||
}
|
||||
if (parsed_bcc->asArray() == nullptr) {
|
||||
ss << "Input BCC is not a CBOR array: "
|
||||
<< TypeNameFromType(parsed_bcc->type()) << "\n";
|
||||
return ss.str();
|
||||
}
|
||||
const cppbor::Array* bcc_array = parsed_bcc->asArray();
|
||||
if (bcc_array->size() < 2) {
|
||||
ss << "Input BCC should contain at least two elements, actual: "
|
||||
<< bcc_array->size() << "\n";
|
||||
return ss.str();
|
||||
}
|
||||
// The first element in the array contains the root device public key
|
||||
// definition.
|
||||
ss << "ROOT DEVICE PUBLIC KEY: \n";
|
||||
const cppbor::Map* device_public_key_info = (*bcc_array)[0]->asMap();
|
||||
if (device_public_key_info == nullptr) {
|
||||
ss << "Invalid dice cert chain type for the first element"
|
||||
<< "\n";
|
||||
return ss.str();
|
||||
}
|
||||
if (!ProcessDevicePublicKeyInfo(ss, *device_public_key_info)) {
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Parse each certificate in the chain. The structure of thr entries are
|
||||
// COSE_Sign1 (untagged).
|
||||
for (size_t i = 1; i < bcc_array->size(); ++i) {
|
||||
ss << "\nCDI PUBLIC KEY " << i << ": \n";
|
||||
const cppbor::Array* bcc_entry = (*bcc_array)[i]->asArray();
|
||||
if (bcc_entry == nullptr) {
|
||||
ss << "Invalid dice cert chain type"
|
||||
<< "\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Skip CoseSign1 signature verification here, only extract pub keys
|
||||
if (bcc_entry->size() != 4 || (*bcc_entry)[0]->type() != cppbor::BSTR ||
|
||||
(*bcc_entry)[1]->type() != cppbor::MAP ||
|
||||
(*bcc_entry)[2]->type() != cppbor::BSTR ||
|
||||
(*bcc_entry)[3]->type() != cppbor::BSTR) {
|
||||
ss << "Invalid signature array"
|
||||
<< "\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
const std::vector<uint8_t>& key_payload =
|
||||
(*bcc_entry)[2]->asBstr()->value();
|
||||
std::string payload(key_payload.begin(), key_payload.end());
|
||||
if (!ProcessDiceChainEntryPayload(ss, payload)) {
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool BccParser::ProcessDevicePublicKeyInfo(
|
||||
std::stringstream& ss, const cppbor::Map& public_key_info_map) {
|
||||
int key_encoding_format = DEVICE_KEY_ENCODING_UNKNOWN;
|
||||
std::string device_key_bytes_0;
|
||||
std::string device_key_bytes_1;
|
||||
for (size_t index = 0; index < public_key_info_map.size(); ++index) {
|
||||
std::pair<const std::unique_ptr<cppbor::Item>&,
|
||||
const std::unique_ptr<cppbor::Item>&>
|
||||
entry = public_key_info_map[index];
|
||||
if (entry.first->type() != cppbor::NINT &&
|
||||
entry.first->type() != cppbor::UINT) {
|
||||
ss << "Invalid map key type " << TypeNameFromType(entry.first->type())
|
||||
<< " in device key info"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
int map_key = entry.first->type() == cppbor::NINT
|
||||
? entry.first->asNint()->value()
|
||||
: entry.first->asInt()->value();
|
||||
switch (map_key) {
|
||||
case MAP_KEY_DEVICE_KEY_TYPE: {
|
||||
if (entry.second->type() != cppbor::UINT) {
|
||||
ss << "Invalid map value type "
|
||||
<< TypeNameFromType(entry.second->type()) << " for device key"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
ss << "key encoding format: ";
|
||||
int64_t value = entry.second->asUint()->value();
|
||||
switch (value) {
|
||||
case DEVICE_KEY_OCTET_PAIR:
|
||||
key_encoding_format = DEVICE_KEY_OCTET_PAIR;
|
||||
ss << "DEVICE_KEY_OCTET_PAIR"
|
||||
<< "\n";
|
||||
break;
|
||||
case DEVICE_KEY_BYTE_STRING:
|
||||
key_encoding_format = DEVICE_KEY_BYTE_STRING;
|
||||
ss << "DEVICE_KEY_BYTE_STRING"
|
||||
<< "\n";
|
||||
break;
|
||||
default:
|
||||
ss << "Unhandled cbor value for device key format: " << value
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case MAP_KEY_DEVICE_KEY_ALGORITHM: {
|
||||
if (entry.second->type() != cppbor::NINT) {
|
||||
ss << "Invalid map value type "
|
||||
<< TypeNameFromType(entry.second->type())
|
||||
<< " for device key algorithm"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
ss << "key algorithm type: ";
|
||||
int64_t value = entry.second->asNint()->value();
|
||||
switch (value) {
|
||||
case DEVICE_KEY_ALGORITHM_ES256:
|
||||
ss << "ECDSA_SHA256";
|
||||
break;
|
||||
case DEVICE_KEY_ALGORITHM_ES384:
|
||||
ss << "ECDSA_SHA384";
|
||||
break;
|
||||
case DEVICE_KEY_ALGORITHM_EDDSA:
|
||||
ss << "EDDSA";
|
||||
break;
|
||||
}
|
||||
ss << "\n";
|
||||
} break;
|
||||
case MAP_KEY_DEVICE_KEY_OPS:
|
||||
// The OPS is an array. Ignored for now.
|
||||
break;
|
||||
case MAP_KEY_DEVICE_KEY_CURVE: {
|
||||
if (entry.second->type() != cppbor::UINT) {
|
||||
ss << "Invalid map value type "
|
||||
<< TypeNameFromType(entry.second->type())
|
||||
<< " for device key curve"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
ss << "curve: ";
|
||||
int64_t value = entry.second->asUint()->value();
|
||||
switch (value) {
|
||||
case DEVICE_KEY_CURVE_ED25519:
|
||||
ss << "ED25519";
|
||||
break;
|
||||
case DEVICE_KEY_CURVE_P256:
|
||||
ss << "P256";
|
||||
break;
|
||||
case DEVICE_KEY_CURVE_P384:
|
||||
ss << "P384";
|
||||
break;
|
||||
default:
|
||||
ss << "Invalid map value " << value << " for device key curve"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
ss << "\n";
|
||||
} break;
|
||||
case MAP_KEY_DEVICE_KEY_BYTES_0:
|
||||
case MAP_KEY_DEVICE_KEY_BYTES_1:
|
||||
// BCC encodes keys as either two X, Y octet strings or a single
|
||||
// octet string. The format used depends on the key type.
|
||||
if (entry.second->type() != cppbor::BSTR) {
|
||||
ss << "Unexpected cbor type for device key bytes"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
const std::vector<uint8_t>& key_bytes = entry.second->asBstr()->value();
|
||||
// Key byte length depends upon the key type.
|
||||
if (key_bytes.size() != kED25519KeyDataItemSizeBytes &&
|
||||
key_bytes.size() != kP256KeyCoordinateSizeBytes &&
|
||||
key_bytes.size() != kP384KeyCoordinateSizeBytes) {
|
||||
ss << "Malformed device public key data size of" << key_bytes.size()
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
std::string& key_bytes_str = map_key == MAP_KEY_DEVICE_KEY_BYTES_0
|
||||
? device_key_bytes_0
|
||||
: device_key_bytes_1;
|
||||
key_bytes_str.assign(key_bytes.begin(), key_bytes.end());
|
||||
}
|
||||
}
|
||||
if (device_key_bytes_0.empty() ||
|
||||
(key_encoding_format == DEVICE_KEY_OCTET_PAIR &&
|
||||
device_key_bytes_1.empty())) {
|
||||
ss << "Malformed device public key definition. Missing device public key "
|
||||
"bytes"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
std::string device_key_bytes;
|
||||
if (key_encoding_format == DEVICE_KEY_OCTET_PAIR) {
|
||||
// Key is an ECDSA elliptic key. We need to return the ANSI X9.62
|
||||
// marshaled public key. Generate the marshaled key if needed. The
|
||||
// marshaled key is needed to create an ECPublicKey object.
|
||||
// std::string* device_key_bytes = public_key_info.mutable_key_bytes();
|
||||
device_key_bytes.reserve(kMaxMarshaledECKeySizeBytes);
|
||||
device_key_bytes.resize(1);
|
||||
device_key_bytes[0] = kMarshaledECKeyZValue;
|
||||
device_key_bytes.append(device_key_bytes_0);
|
||||
device_key_bytes.append(device_key_bytes_1);
|
||||
} else {
|
||||
device_key_bytes = device_key_bytes_0;
|
||||
}
|
||||
std::ostringstream ss_hex;
|
||||
ss_hex << std::setfill('0');
|
||||
for (size_t i = 0; i < device_key_bytes.size(); i++) {
|
||||
ss_hex << std::setw(2) << std::hex << static_cast<int>(device_key_bytes[i]);
|
||||
}
|
||||
ss << "public key bytes: " << ss_hex.str() << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BccParser::ProcessDiceChainEntryPayload(std::stringstream& ss,
|
||||
std::string& payload) {
|
||||
if (payload.empty()) {
|
||||
ss << "Empty bcc entry payload"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto parse_result = cppbor::parse(
|
||||
reinterpret_cast<const uint8_t*>(payload.data()),
|
||||
reinterpret_cast<const uint8_t*>(payload.data() + payload.size()));
|
||||
std::unique_ptr<cppbor::Item> item = std::move(std::get<0>(parse_result));
|
||||
std::string error_message = std::get<2>(parse_result);
|
||||
if (item == nullptr || !error_message.empty()) {
|
||||
ss << "Unable to parse bcc entry payload: " << error_message << "\n";
|
||||
return false;
|
||||
}
|
||||
if (item->type() != cppbor::MAP) {
|
||||
ss << "Unexpected bcc entry payload type"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string issuer_subject =
|
||||
GetIssuerSubjectFromBccEntryPayload(item->asMap());
|
||||
ss << issuer_subject;
|
||||
|
||||
const cppbor::Bstr* subject_public_key =
|
||||
GetSubjectPublicKeyFromBccEntryPayload(item->asMap());
|
||||
if (subject_public_key == nullptr) {
|
||||
ss << "Bcc entry payload has no subject public key"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now parse the serialized subject public key.
|
||||
parse_result = cppbor::parse(
|
||||
subject_public_key->value().data(),
|
||||
subject_public_key->value().data() + subject_public_key->value().size());
|
||||
item = std::move(std::get<0>(parse_result));
|
||||
error_message = std::get<2>(parse_result);
|
||||
if (item == nullptr || !error_message.empty()) {
|
||||
ss << "Unable to parse serialized subject public key: " << error_message
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
const cppbor::Map* subject_public_key_info = item->asMap();
|
||||
if (subject_public_key_info == nullptr) {
|
||||
ss << "Invalid subject public key type"
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
return ProcessDevicePublicKeyInfo(ss, *subject_public_key_info);
|
||||
}
|
||||
|
||||
} // namespace widevine
|
||||
@@ -0,0 +1,498 @@
|
||||
// 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"
|
||||
#include "string_conversions.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},
|
||||
};
|
||||
|
||||
std::string EscapeJson(const std::string& input) {
|
||||
std::string result;
|
||||
for (std::string::const_iterator c = input.begin(); c != input.end(); ++c) {
|
||||
switch (*c) {
|
||||
case '\"':
|
||||
result += "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
result += "\\\\";
|
||||
break;
|
||||
case '\b':
|
||||
result += "\\b";
|
||||
break;
|
||||
case '\f':
|
||||
result += "\\f";
|
||||
break;
|
||||
case '\n':
|
||||
result += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
result += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
result += "\\t";
|
||||
break;
|
||||
default:
|
||||
result += *c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string StringMapToJson(
|
||||
const std::map<std::string, std::string>& string_map) {
|
||||
std::string json = "{";
|
||||
for (const auto& value_pair : string_map) {
|
||||
json.append("\"" + value_pair.first + "\": " + "\"" + value_pair.second +
|
||||
"\",");
|
||||
}
|
||||
json.resize(json.size() - 1); // Remove the last comma.
|
||||
json.append("}");
|
||||
return json;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WidevineProvisioner::WidevineProvisioner() {
|
||||
InitializeCryptoInterface();
|
||||
assert(crypto_interface_ != nullptr);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WidevineProvisioner::GetBcc() {
|
||||
std::vector<uint8_t> bcc;
|
||||
OEMCryptoResult result = crypto_interface_->GetBcc(bcc);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("Failed to get BCC, result = %d", result);
|
||||
}
|
||||
return bcc;
|
||||
}
|
||||
|
||||
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, result = %d", result);
|
||||
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) {
|
||||
std::vector<uint8_t> 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, result = %d", result);
|
||||
return false;
|
||||
}
|
||||
auto [parsed, _, err] = cppbor::parse(
|
||||
reinterpret_cast<const uint8_t*>(verified_device_info.data()),
|
||||
verified_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();
|
||||
for (size_t i = 0; i < verified_device_info_map->size(); i++) {
|
||||
auto& [key_item, value_item] = (*verified_device_info_map)[i];
|
||||
LOGI("Found device info %s", key_item->asTstr()->value().data());
|
||||
if (value_item != nullptr && value_item->asTstr() != nullptr &&
|
||||
value_item->asTstr()->value().empty()) {
|
||||
LOGI("Value is empty. Skip");
|
||||
continue;
|
||||
}
|
||||
device_info_map.add(key_item->clone(), value_item->clone());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WidevineProvisioner::GetDeviceInfoCommon(cppbor::Map& device_info_map) {
|
||||
// Best effort to populate device info from TEE first
|
||||
TryAddVerifiedDeviceInfo(device_info_map);
|
||||
// Add device information from OS properties if the verified device info is
|
||||
// not present
|
||||
if (device_info_map.get("brand") == nullptr ||
|
||||
device_info_map.get("brand")->asTstr()->value().empty()) {
|
||||
std::string brand_name;
|
||||
if (!wvcdm::Properties::GetBrandName(&brand_name) || brand_name.empty()) {
|
||||
LOGE("Failed to get brand name.");
|
||||
return false;
|
||||
}
|
||||
device_info_map.add(cppbor::Tstr("brand"), cppbor::Tstr(brand_name));
|
||||
LOGI("use OS property brand: %s", brand_name.data());
|
||||
} else {
|
||||
LOGI("use verified brand: %s",
|
||||
device_info_map.get("brand")->asTstr()->value().data());
|
||||
}
|
||||
|
||||
if (device_info_map.get("manufacturer") == nullptr ||
|
||||
device_info_map.get("manufacturer")->asTstr()->value().empty()) {
|
||||
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));
|
||||
LOGI("use OS property manufacturer: %s", company_name.data());
|
||||
} else {
|
||||
LOGI("use verified manufacture: %s",
|
||||
device_info_map.get("manufacturer")->asTstr()->value().data());
|
||||
}
|
||||
|
||||
if (device_info_map.get("model") == nullptr ||
|
||||
device_info_map.get("model")->asTstr()->value().empty()) {
|
||||
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));
|
||||
LOGI("use OS property model: %s", model_name.data());
|
||||
} else {
|
||||
LOGI("use verified model: %s",
|
||||
device_info_map.get("model")->asTstr()->value().data());
|
||||
}
|
||||
|
||||
if (device_info_map.get("device") == nullptr ||
|
||||
device_info_map.get("device")->asTstr()->value().empty()) {
|
||||
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));
|
||||
LOGI("use OS property device: %s", device_name.data());
|
||||
} else {
|
||||
LOGI("use verified device: %s",
|
||||
device_info_map.get("device")->asTstr()->value().data());
|
||||
}
|
||||
|
||||
if (device_info_map.get("product") == nullptr ||
|
||||
device_info_map.get("product")->asTstr()->value().empty()) {
|
||||
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));
|
||||
LOGI("use OS property product: %s", product_name.data());
|
||||
} else {
|
||||
LOGI("use verified product: %s",
|
||||
device_info_map.get("product")->asTstr()->value().data());
|
||||
}
|
||||
|
||||
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, result = %d", result);
|
||||
return false;
|
||||
}
|
||||
device_info_map.add(cppbor::Tstr("oemcrypto_build_info"),
|
||||
cppbor::Tstr(oemcrypto_build_info));
|
||||
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));
|
||||
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
|
||||
}
|
||||
|
||||
bool WidevineProvisioner::GetDeviceInfoV2(cppbor::Map& device_info_map) {
|
||||
if (!GetDeviceInfoCommon(device_info_map)) return false;
|
||||
device_info_map.canonicalize();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WidevineProvisioner::GenerateCertificateRequestV2(
|
||||
const std::vector<uint8_t>& challenge, std::vector<uint8_t>* csr) {
|
||||
if (csr == nullptr) {
|
||||
LOGE("CSR is null.");
|
||||
return false;
|
||||
}
|
||||
// Prepare BCC
|
||||
std::vector<uint8_t> bcc;
|
||||
OEMCryptoResult result = crypto_interface_->GetBcc(bcc);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("Failed to get BCC, result = %d", result);
|
||||
return false;
|
||||
}
|
||||
// Prepare device info
|
||||
auto device_info_map = cppbor::Map();
|
||||
if (!GetDeviceInfoV2(device_info_map)) {
|
||||
LOGE("Failed to get device_info.");
|
||||
return false;
|
||||
}
|
||||
// Prepare signed CSR payload
|
||||
auto device_info = device_info_map.encode();
|
||||
std::vector<uint8_t> signed_csr_payload;
|
||||
result = crypto_interface_->GetSignedCsrPayload(challenge, device_info,
|
||||
signed_csr_payload);
|
||||
if (result != OEMCrypto_SUCCESS) {
|
||||
LOGE("Failed to get the signed CSR payload, result = %d", result);
|
||||
return false;
|
||||
}
|
||||
// https://source.corp.google.com/android-internal/hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
|
||||
*csr = cppbor::Array()
|
||||
.add(1 /* version */)
|
||||
.add(cppbor::Map() /* UdsCerts */)
|
||||
.add(cppbor::EncodedItem(std::move(bcc)))
|
||||
.add(cppbor::EncodedItem(std::move(signed_csr_payload)))
|
||||
.encode();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Caller ensures the validity of `device_info_map` as a `cppbor::Map&`.
|
||||
void WidevineProvisioner::PopulateDeviceInfoFromCborMap(
|
||||
const cppbor::Map& device_info_map,
|
||||
std::map<std::string, std::string>& request_map) {
|
||||
if (device_info_map.get("manufacturer")) {
|
||||
request_map["company"] =
|
||||
device_info_map.get("manufacturer")->asTstr()->value();
|
||||
}
|
||||
if (device_info_map.get("device")) {
|
||||
request_map["name"] = device_info_map.get("device")->asTstr()->value();
|
||||
}
|
||||
if (device_info_map.get("architecture")) {
|
||||
request_map["architecture"] =
|
||||
device_info_map.get("architecture")->asTstr()->value();
|
||||
}
|
||||
if (device_info_map.get("model")) {
|
||||
request_map["model"] = device_info_map.get("model")->asTstr()->value();
|
||||
}
|
||||
if (device_info_map.get("product")) {
|
||||
request_map["product"] = device_info_map.get("product")->asTstr()->value();
|
||||
}
|
||||
if (device_info_map.get("fingerprint")) {
|
||||
request_map["build_info"] =
|
||||
device_info_map.get("fingerprint")->asTstr()->value();
|
||||
}
|
||||
if (device_info_map.get("oemcrypto_build_info")) {
|
||||
request_map["oemcrypto_build_info"] = EscapeJson(
|
||||
device_info_map.get("oemcrypto_build_info")->asTstr()->value());
|
||||
}
|
||||
}
|
||||
|
||||
bool WidevineProvisioner::GenerateWidevineUploadRequest(std::string& request) {
|
||||
std::map<std::string, std::string> request_map;
|
||||
auto bcc = GetBcc();
|
||||
request_map["bcc"] = wvutil::Base64Encode(bcc);
|
||||
|
||||
auto device_info_map = cppbor::Map();
|
||||
if (!GetDeviceInfoCommon(device_info_map)) return false;
|
||||
PopulateDeviceInfoFromCborMap(device_info_map, request_map);
|
||||
|
||||
request = StringMapToJson(request_map);
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
91
libwvdrmengine/tools/factory_upload_tool/aosp/src/log.cpp
Normal file
91
libwvdrmengine/tools/factory_upload_tool/aosp/src/log.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
//
|
||||
// Log - implemented using the standard Android logging mechanism
|
||||
|
||||
/*
|
||||
* Qutoing from system/core/include/log/log.h:
|
||||
* Normally we strip ALOGV (VERBOSE messages) from release builds.
|
||||
* You can modify this (for example with "#define LOG_NDEBUG 0"
|
||||
* at the top of your source file) to change that behavior.
|
||||
*/
|
||||
#ifndef LOG_NDEBUG
|
||||
# ifdef NDEBUG
|
||||
# define LOG_NDEBUG 1
|
||||
# else
|
||||
# define LOG_NDEBUG 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define LOG_TAG "Widevine"
|
||||
#define LOG_BUF_SIZE 1024
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* Uncomment the line below if you want to have the LOGV messages to print
|
||||
* IMPORTANT : this will affect all of CDM
|
||||
*/
|
||||
|
||||
// #define LOG_NDEBUG 0
|
||||
|
||||
namespace wvutil {
|
||||
|
||||
LogPriority g_cutoff = CDM_LOG_INFO;
|
||||
|
||||
void InitLogging() {}
|
||||
|
||||
void Log(const char* file, const char* function, int line, LogPriority level,
|
||||
const char* format, ...) {
|
||||
if (level > g_cutoff) return;
|
||||
|
||||
const char* filename = strrchr(file, '/');
|
||||
filename = filename == nullptr ? file : filename + 1;
|
||||
|
||||
char buf[LOG_BUF_SIZE];
|
||||
int len =
|
||||
snprintf(buf, LOG_BUF_SIZE, "[%s(%d):%s] ", filename, line, function);
|
||||
if (len < 0) len = 0;
|
||||
if (static_cast<unsigned int>(len) < sizeof(buf)) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vsnprintf(buf + len, LOG_BUF_SIZE - len, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
android_LogPriority prio = ANDROID_LOG_VERBOSE;
|
||||
|
||||
switch (level) {
|
||||
case CDM_LOG_ERROR:
|
||||
prio = ANDROID_LOG_ERROR;
|
||||
break;
|
||||
case CDM_LOG_WARN:
|
||||
prio = ANDROID_LOG_WARN;
|
||||
break;
|
||||
case CDM_LOG_INFO:
|
||||
prio = ANDROID_LOG_INFO;
|
||||
break;
|
||||
case CDM_LOG_DEBUG:
|
||||
prio = ANDROID_LOG_DEBUG;
|
||||
break;
|
||||
#if LOG_NDEBUG
|
||||
case CDM_LOG_VERBOSE:
|
||||
return;
|
||||
#else
|
||||
case CDM_LOG_VERBOSE:
|
||||
prio = ANDROID_LOG_VERBOSE;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
__android_log_write(prio, LOG_TAG, buf);
|
||||
}
|
||||
|
||||
} // namespace wvutil
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include <android-base/properties.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool GetAndroidProperty(const char* key, std::string* value) {
|
||||
if (!key) {
|
||||
LOGW("GetAndroidProperty: Invalid property key parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
LOGW("GetAndroidProperty: Invalid property value parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
*value = android::base::GetProperty(key, "");
|
||||
|
||||
return value->size() > 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool Properties::GetBrandName(std::string* brand_name) {
|
||||
if (!brand_name) {
|
||||
LOGW("Properties::GetBrandName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.brand", brand_name);
|
||||
}
|
||||
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
if (!company_name) {
|
||||
LOGW("Properties::GetCompanyName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.manufacturer", company_name);
|
||||
}
|
||||
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
if (!model_name) {
|
||||
LOGW("Properties::GetModelName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.model", model_name);
|
||||
}
|
||||
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
if (!arch_name) {
|
||||
LOGW("Properties::GetArchitectureName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.cpu.abi", arch_name);
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
if (!device_name) {
|
||||
LOGW("Properties::GetDeviceName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.device", device_name);
|
||||
}
|
||||
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
if (!product_name) {
|
||||
LOGW("Properties::GetProductName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.product.name", product_name);
|
||||
}
|
||||
|
||||
bool Properties::GetBuildInfo(std::string* build_info) {
|
||||
if (!build_info) {
|
||||
LOGW("Properties::GetBuildInfo: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return GetAndroidProperty("ro.build.fingerprint", build_info);
|
||||
}
|
||||
|
||||
bool Properties::GetOEMCryptoPath(std::string* library_name) {
|
||||
if (!library_name) {
|
||||
LOGW("Properties::GetOEMCryptoPath: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*library_name = "liboemcrypto.so";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
Reference in New Issue
Block a user