Add Signed CSR payload validator to oemcrypto util and unit tests

Validator that can parse and validate SignedCsrPayload Cbor object.
The SignedCsrPayload is generated by
OEMCrypto_GetDeviceSignedCsrPayload() and will be put into prov4 CSR
request during factory uploading.

Test: opk_ta_p40
Bug: 300304834

Change-Id: Ib569dc22fe76dbaa98657e96aa4c93a272bbcd1b
This commit is contained in:
Cong Lin
2023-12-06 21:24:06 -08:00
committed by Robert Shih
parent d89faef0f3
commit 778d4f7026
2 changed files with 437 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
//
// Reference implementation utilities of OEMCrypto APIs
//
#ifndef WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_
#define WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_
#include <sstream>
#include <string>
#include "cbor_validator.h"
#include "cppbor.h"
namespace wvoec {
namespace util {
// SignedCsrPayloadValidator parses and validates a Cbor struct of
// SignedData<CsrPayload>. The definition of SignedData<T> and CsrPayload can be
// found at:
// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl
class SignedCsrPayloadValidator : public CborValidator {
public:
explicit SignedCsrPayloadValidator() {}
virtual ~SignedCsrPayloadValidator() override = default;
SignedCsrPayloadValidator(const SignedCsrPayloadValidator&) = delete;
SignedCsrPayloadValidator& operator=(const SignedCsrPayloadValidator&) =
delete;
// Verifies the Cbor struct of a client generated SignedData<CsrPayload>.
virtual CborMessageStatus Validate() override;
// Outputs SignedData<CsrPayload> in YAML.
virtual std::string GetFormattedMessage() const override;
private:
CborMessageStatus ValidateProtectedParams(
const cppbor::Bstr* protected_params);
CborMessageStatus ValidateDataToBeSigned(const cppbor::Bstr* data);
// Used to generate formatted message.
std::stringstream msg_ss_;
};
} // namespace util
} // namespace wvoec
#endif // WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_

View File

@@ -0,0 +1,393 @@
// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine License
// Agreement.
//
// Reference implementation utilities of OEMCrypto APIs
//
#include "signed_csr_payload_validator.h"
#include <vector>
#include "string_conversions.h"
namespace wvoec {
namespace util {
namespace {
enum CoseKeyAlgorithm : int {
AES_GCM_256 = 3,
HMAC_256 = 5,
ES256 = -7, // ECDSA with SHA-256
EDDSA = -8,
ECDH_ES_HKDF_256 = -25,
ES384 = -35, // ECDSA with SHA-384
};
void AddMessages(std::stringstream& ss,
const std::vector<std::string>& fmt_msgs, int indent) {
const std::string spaces = std::string(indent * 2, ' ');
for (auto& msg : fmt_msgs) {
ss << spaces << msg << "\n";
}
}
} // namespace
// clang-format off
// Signed CSR payload CBOR data to be verified:
// SignedData<CsrPayload> = [
// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 /
// AlgorithmES384 },
// unprotected: {},
// payload: bstr .cbor DataToBeSigned / nil,
// signature: bstr ; PureEd25519(priv_key, Sig_structure<DataToBeSigned>) /
// ; ECDSA(priv_key, Sig_structure<DataToBeSigned>)
// ]
//
// DataToBeSigned = [
// challenge: bstr .size (0..64),
// bstr .cbor CsrPayload,
// ]
//
// CsrPayload = [ ; CBOR Array defining the payload for CSR.
// version: 3, ; The CsrPayload CDDL Schema version.
// CertificateType: "widevine" ; The type of certificate being requested.
// DeviceInfo, ; Defined in Android DeviceInfo.aidl
// KeysToSign: [] ; Empty list
// ]
//
// Sig_structure<DataToBeSigned> = [
// context: "Signature1",
// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / AlgorithmES384 },
// external_aad: bstr .size 0,
// payload: bstr .cbor DataToBeSigned / nil,
// ]
// clang-format on
// Caller ensures the pointer is not NULL.
CborMessageStatus SignedCsrPayloadValidator::ValidateProtectedParams(
const cppbor::Bstr* protected_params) {
auto parse_result = cppbor::parse(protected_params);
std::unique_ptr<cppbor::Item> parsed_protected_params =
std::move(std::get<0>(parse_result));
const std::string error_message = std::move(std::get<2>(parse_result));
if (!parsed_protected_params || !error_message.empty()) {
AddValidationMessage(kCborValidateFatal,
"Unable to parse protectedParams: " + error_message);
return message_status_;
}
// |parsed_protected_params| is a CBOR map of 1 entry
const cppbor::Map* protected_params_map = parsed_protected_params->asMap();
if (!protected_params_map) {
AddValidationMessage(
kCborValidateFatal,
"ProtectedParams must be a CBOR map. Actual type: " +
CppborMajorTypeToString(parsed_protected_params->type()));
return message_status_;
}
if (protected_params_map->size() == 0) {
AddValidationMessage(kCborValidateFatal, "ProtectedParams is empty.");
return message_status_;
}
if (protected_params_map->size() > 1) {
AddValidationMessage(kCborValidateWarning,
"ProtectedParams expects 1 entry, but got " +
std::to_string(protected_params_map->size()));
}
// TODO(b/314141962): Replace this with the map lookup function in cppbor
// library
bool algo_found = false;
for (auto const& entry : *protected_params_map) {
if (!entry.first->asInt()) {
AddValidationMessage(kCborValidateWarning,
"Unsupported key type: " +
CppborMajorTypeToString(entry.first->type()));
} else if (entry.first->asInt()->value() != 1) {
AddValidationMessage(kCborValidateWarning,
"Unsupported key value: " +
std::to_string(entry.first->asInt()->value()));
} else {
auto& algorithm = entry.second;
if (!algorithm) {
AddValidationMessage(kCborValidateFatal, "Algorithm value is missing.");
return message_status_;
}
if (!algorithm->asInt()) {
AddValidationMessage(kCborValidateFatal,
"Unsupported signature algorithm value type: " +
CppborMajorTypeToString(algorithm->type()));
return message_status_;
}
std::string kv = "1: ";
const int64_t algo = algorithm->asInt()->value();
if (algo == EDDSA)
kv += "EDDSA";
else if (algo == ES256)
kv += "ES256";
else if (algo == ES384)
kv += "ES384";
else {
kv += std::to_string(algo);
AddValidationMessage(
kCborValidateFatal,
"Unsupported signature algorithm value: " + std::to_string(algo));
return message_status_;
}
AddMessages(msg_ss_, {kv}, 1);
algo_found = true;
}
}
if (!algo_found) {
AddValidationMessage(kCborValidateFatal,
"ProtectedParams has no signature algorithm.");
}
return message_status_;
}
// Caller ensures the pointer is not NULL.
CborMessageStatus SignedCsrPayloadValidator::ValidateDataToBeSigned(
const cppbor::Bstr* data) {
if (data->value().empty()) {
AddValidationMessage(kCborValidateFatal, "Payload to be signed is empty.");
return message_status_;
}
auto parse_result = cppbor::parse(data);
std::unique_ptr<cppbor::Item> parsed_payload_to_be_signed =
std::move(std::get<0>(parse_result));
std::string error_message = std::move(std::get<2>(parse_result));
if (!parsed_payload_to_be_signed || !error_message.empty()) {
AddValidationMessage(
kCborValidateFatal,
"Unable to parse the payload to be signed: " + error_message);
return message_status_;
}
// Verify that |parsed_payload_to_be_signed| is a valid array.
const cppbor::Array* payload_to_be_signed_array =
parsed_payload_to_be_signed->asArray();
if (!payload_to_be_signed_array) {
AddValidationMessage(
kCborValidateFatal,
"Payload to be signed must be a CBOR array. Actual type: " +
CppborMajorTypeToString(parsed_payload_to_be_signed->type()));
return message_status_;
}
if (payload_to_be_signed_array->size() != 2U) {
AddValidationMessage(
kCborValidateFatal,
"Payload to be signed must contain the challenge and "
"payload. Actual size: " +
std::to_string(payload_to_be_signed_array->size()));
return message_status_;
}
// Element 0: challenge.
std::string msg = "- Challenge: ";
const cppbor::Bstr* challenge = payload_to_be_signed_array->get(0)->asBstr();
if (!challenge) {
AddValidationMessage(kCborValidateFatal,
"Challenge must be Bstr. Actual type: " +
CppborMajorTypeToString(
payload_to_be_signed_array->get(0)->type()));
return message_status_;
}
if (challenge->value().size() > 64) {
AddValidationMessage(
kCborValidateError,
"Challenge size must be between 0 and 64 bytes inclusive. "
"However, challenge is " +
std::to_string(challenge->value().size()) + " bytes long.");
}
msg += wvutil::b2a_hex(challenge->value());
AddMessages(msg_ss_, {msg}, 1);
// Element 1: CsrPayload.
AddMessages(msg_ss_, {"- CsrPayload:"}, 1);
const cppbor::Bstr* csr_payload =
payload_to_be_signed_array->get(1)->asBstr();
if (!csr_payload) {
AddValidationMessage(kCborValidateFatal, "CSR payload is missing.");
return message_status_;
}
parse_result = cppbor::parse(csr_payload);
std::unique_ptr<cppbor::Item> parsed_csr_payload =
std::move(std::get<0>(parse_result));
error_message = std::move(std::get<2>(parse_result));
if (!parsed_csr_payload) {
AddValidationMessage(kCborValidateFatal,
"Unable to parse CSR payload: " + error_message);
return message_status_;
}
if (!parsed_csr_payload->asArray()) {
AddValidationMessage(
kCborValidateFatal,
"CSR payload must be an array. Actual: " +
CppborMajorTypeToString(parsed_csr_payload->type()));
return message_status_;
}
if (parsed_csr_payload->asArray()->size() != 4U) {
AddValidationMessage(
kCborValidateFatal,
"CSR payload must contain 4 elements. Actual size: " +
std::to_string(parsed_csr_payload->asArray()->size()));
return message_status_;
}
// |parsed_csr_payload| is an array of 4 elements.
const cppbor::Uint* version = parsed_csr_payload->asArray()->get(0)->asUint();
if (!version) {
AddValidationMessage(kCborValidateFatal,
"CSR payload version must be an unsigned integer.");
return message_status_;
}
AddMessages(msg_ss_, {"- version: " + std::to_string(version->value())}, 2);
if (version->value() != 3U) {
AddValidationMessage(kCborValidateError,
"CSR payload version must be must be equal to 3.");
}
const cppbor::Tstr* certificate_type =
parsed_csr_payload->asArray()->get(1)->asTstr();
if (!certificate_type) {
// Certificate type is allowed to be extended by vendor, i.e. we can't
// enforce its value.
AddValidationMessage(
kCborValidateFatal,
"Certificate type must be Tstr. Actual type: " +
CppborMajorTypeToString(
parsed_csr_payload->asArray()->get(1)->type()));
return message_status_;
}
AddMessages(msg_ss_, {"- certificate_type: " + certificate_type->value()}, 2);
const cppbor::Map* device_info =
parsed_csr_payload->asArray()->get(2)->asMap();
if (!device_info) {
AddValidationMessage(
kCborValidateFatal,
"Device info must be a CBOR map. Actual type: " +
CppborMajorTypeToString(
parsed_csr_payload->asArray()->get(2)->type()));
return message_status_;
}
AddMessages(msg_ss_, {"- device_info: " + cppbor::prettyPrint(device_info)},
2);
const cppbor::Array* keys = parsed_csr_payload->asArray()->get(3)->asArray();
if (!keys) {
AddValidationMessage(
kCborValidateFatal,
"Keys must be a CBOR array. Actual type: " +
CppborMajorTypeToString(
parsed_csr_payload->asArray()->get(3)->type()));
return message_status_;
}
AddMessages(msg_ss_, {"- keys_to_sign: " + cppbor::prettyPrint(keys)}, 2);
return message_status_;
}
CborMessageStatus SignedCsrPayloadValidator::Validate() {
if (message_status_ != kCborParseOk) return message_status_;
const cppbor::Item* parsed_signed_csr_payload =
std::get<0>(parse_result()).get();
if (!parsed_signed_csr_payload) {
AddValidationMessage(kCborValidateFatal, "Signed CSR payload is empty.");
return message_status_;
}
// Verify that |parsed_signed_csr_payload| is a valid array.
if (!parsed_signed_csr_payload->asArray()) {
AddValidationMessage(
kCborValidateFatal,
"Signed CSR payload must be a CBOR array. Actual type: " +
CppborMajorTypeToString(parsed_signed_csr_payload->type()));
return message_status_;
}
const cppbor::Array* signed_csr_payload_array =
parsed_signed_csr_payload->asArray();
if (signed_csr_payload_array->size() != 4U) {
AddValidationMessage(kCborValidateFatal,
"Invalid CoseSign1 size. Actual: " +
std::to_string(signed_csr_payload_array->size()));
return message_status_;
}
// Writes YAML-formatted output to |msg_ss_| during validation.
msg_ss_.str(std::string());
msg_ss_ << "---"
<< "\n";
// Element 0: protected params.
msg_ss_ << "- Protected params:\n";
const cppbor::Bstr* protected_params =
signed_csr_payload_array->get(0)->asBstr();
CborMessageStatus status = ValidateProtectedParams(protected_params);
if (status == kCborValidateFatal) return kCborValidateFatal;
// Element 1: unprotected params map.
std::string msg = "- Unprotected params: ";
const cppbor::Map* unprotected_params =
signed_csr_payload_array->get(1)->asMap();
if (!unprotected_params) {
AddValidationMessage(
kCborValidateFatal,
"UnprotectedParams must be a CBOR map. Actual type: " +
CppborMajorTypeToString(signed_csr_payload_array->get(1)->type()));
return message_status_;
}
if (unprotected_params->size() == 0) {
msg += "<null>";
} else {
msg += "non-null map";
AddValidationMessage(kCborValidateWarning,
"UnprotectedParams is expected to be empty.");
}
msg_ss_ << msg << "\n";
// Element 2: payload (DataToBeSigned).
msg_ss_ << "- Payload:\n";
const cppbor::Bstr* payload_to_be_signed =
signed_csr_payload_array->get(2)->asBstr();
if (!payload_to_be_signed) {
AddValidationMessage(
kCborValidateFatal,
"Payload to be signed must be Bstr. Actual type: " +
CppborMajorTypeToString(signed_csr_payload_array->get(2)->type()));
return message_status_;
}
status = ValidateDataToBeSigned(payload_to_be_signed);
if (status == kCborValidateFatal) return kCborValidateFatal;
// Element 3: signature.
std::string sig_msg = "- Signature: ";
const cppbor::Bstr* signature = signed_csr_payload_array->get(3)->asBstr();
if (!signature) {
AddValidationMessage(
kCborValidateFatal,
"CoseSign1 signature must be Bstr. Actual type: " +
CppborMajorTypeToString(signed_csr_payload_array->get(3)->type()));
return message_status_;
}
// Skip CoseSign1 signature verification since the validator doesn't have
// verifying keys.
if (signature->value().empty()) {
sig_msg += "<null>";
AddValidationMessage(kCborValidateError, "CoseSign1 signature is missing.");
} else {
sig_msg += wvutil::b2a_hex(signature->value());
}
msg_ss_ << sig_msg << "\n";
msg_ss_ << "...\n";
if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk;
return message_status_;
}
std::string SignedCsrPayloadValidator::GetFormattedMessage() const {
if (message_status_ == kCborUninitialized ||
message_status_ == kCborParseError) {
return std::string();
}
const cppbor::Item* parsed_item = std::get<0>(parse_result()).get();
if (parsed_item == nullptr) {
return "<null>";
}
return msg_ss_.str();
}
} // namespace util
} // namespace wvoec