diff --git a/libwvdrmengine/oemcrypto/util/include/signed_csr_payload_validator.h b/libwvdrmengine/oemcrypto/util/include/signed_csr_payload_validator.h new file mode 100644 index 00000000..a2d8127f --- /dev/null +++ b/libwvdrmengine/oemcrypto/util/include/signed_csr_payload_validator.h @@ -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 +#include + +#include "cbor_validator.h" +#include "cppbor.h" + +namespace wvoec { +namespace util { +// SignedCsrPayloadValidator parses and validates a Cbor struct of +// SignedData. The definition of SignedData 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. + virtual CborMessageStatus Validate() override; + // Outputs SignedData 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_ diff --git a/libwvdrmengine/oemcrypto/util/src/signed_csr_payload_validator.cpp b/libwvdrmengine/oemcrypto/util/src/signed_csr_payload_validator.cpp new file mode 100644 index 00000000..439b56a0 --- /dev/null +++ b/libwvdrmengine/oemcrypto/util/src/signed_csr_payload_validator.cpp @@ -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 + +#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& 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 = [ +// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / +// AlgorithmES384 }, +// unprotected: {}, +// payload: bstr .cbor DataToBeSigned / nil, +// signature: bstr ; PureEd25519(priv_key, Sig_structure) / +// ; ECDSA(priv_key, Sig_structure) +// ] +// +// 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 = [ +// 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 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 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 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 += ""; + } 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 += ""; + 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 ""; + } + return msg_ss_.str(); +} +} // namespace util +} // namespace wvoec