//////////////////////////////////////////////////////////////////////////////// // Copyright 2016 Google LLC. // // This software is licensed under the terms defined in the Widevine Master // License Agreement. For a copy of this agreement, please contact // widevine-licensing@google.com. //////////////////////////////////////////////////////////////////////////////// #include "provisioning_sdk/internal/oem_device_cert.h" #include #include "glog/logging.h" #include "absl/memory/memory.h" #include "openssl/asn1.h" #include "openssl/pkcs7.h" #include "openssl/x509.h" #include "openssl/x509v3.h" #include "provisioning_sdk/internal/certificates/root_oem_certificates.h" namespace widevine { namespace { const int kExtensionOidSize = 64; const char kWidevineSystemIdExtensionOid[] = "1.3.6.1.4.1.11129.4.1.1"; bool ExtractPublicKey(X509* x509, std::unique_ptr* public_key) { DCHECK(x509); ScopedPKEY pkey(X509_get_pubkey(x509)); if (!pkey) { LOG(WARNING) << "X509_get_pubkey failed."; return false; } *public_key = absl::make_unique(EVP_PKEY_get1_RSA(pkey.get())); return true; } bool ExtractSystemId(X509* x509, uint32_t* system_id) { DCHECK(x509); const int num_of_extensions = X509_get_ext_count(x509); for (int i = 0; i < num_of_extensions; ++i) { X509_EXTENSION* extension = X509_get_ext(x509, i); if (!extension) { LOG(WARNING) << "X509_get_ext failed."; return false; } ASN1_OBJECT* extension_object = X509_EXTENSION_get_object(extension); if (!extension_object) { LOG(WARNING) << "X509_EXTENSION_get_object failed."; return false; } char extension_name[kExtensionOidSize + 1]; OBJ_obj2txt(extension_name, kExtensionOidSize, extension_object, 1); if (strcmp(extension_name, kWidevineSystemIdExtensionOid)) continue; ASN1_OCTET_STRING* octet_str = X509_EXTENSION_get_data(extension); if (!octet_str) { LOG(WARNING) << "X509_EXTENSION_get_data failed."; return false; } const unsigned char* data = octet_str->data; if (!data) { LOG(WARNING) << "null data in octet string."; return false; } ScopedAsn1Integer asn1_integer( d2i_ASN1_INTEGER(nullptr, &data, octet_str->length)); if (!asn1_integer) { LOG(WARNING) << "d2i_ASN1_INTEGER failed."; return false; } int64_t system_id_long = ASN1_INTEGER_get(asn1_integer.get()); if (system_id_long == -1) { LOG(WARNING) << "ASN1_INTEGER_get failed."; return false; } *system_id = system_id_long; return true; } LOG(WARNING) << "Widevine system ID extension not found."; return false; } bool ExtractSerialNumber(X509* x509, std::string* oem_ca_serial_number) { ASN1_INTEGER* serial = X509_get_serialNumber(x509); if (!serial) { LOG(WARNING) << "X509_get_serialNumber failed."; return false; } uint8_t* buffer = ASN1_STRING_data(serial); if (!buffer) { LOG(WARNING) << "ASN1_STRING_data failed."; return false; } int length = ASN1_STRING_length(serial); if (length <= 0) { LOG(WARNING) << "ASN1_STRING_length returns " << length; } oem_ca_serial_number->assign(reinterpret_cast(buffer), length); return true; } } // namespace OemDeviceCert::OemDeviceCert() {} OemDeviceCert::~OemDeviceCert() {} bool OemDeviceCert::Initialize(CertificateType certificate_type) { RootOemCertificates root_certificates; switch (certificate_type) { case kCertificateTypeTesting: case kCertificateTypeDevelopment: return Initialize(root_certificates.oem_root_dev_certificate()); case kCertificateTypeProduction: return Initialize(root_certificates.oem_root_prod_certificate()); default: LOG(WARNING) << "Invalid certificate type " << certificate_type; return false; } } bool OemDeviceCert::VerifyCertificateChain( const std::string& certificate_chain, std::unique_ptr* leaf_public_key, uint32_t* system_id, std::string* oem_ca_serial_number) const { if (certificate_chain.empty()) { LOG(WARNING) << "Empty certificate."; return false; } ScopedX509Stack x509_stack(sk_X509_new_null()); if (!x509_stack) { LOG(WARNING) << "sk_X509_new_null returned NULL."; return false; } CBS pkcs7; CBS_init(&pkcs7, reinterpret_cast(certificate_chain.data()), certificate_chain.size()); if (!PKCS7_get_certificates(x509_stack.get(), &pkcs7)) { LOG(WARNING) << "PKCS7_get_certificates failed."; return false; } if (sk_X509_num(x509_stack.get()) != 2) { LOG(WARNING) << "Expecting one and only one intermediate certificate."; return false; } X509* leaf_certificate = sk_X509_value(x509_stack.get(), 0); X509* intermediate_certificate = sk_X509_value(x509_stack.get(), 1); DCHECK(leaf_certificate); DCHECK(intermediate_certificate); // Verify the cert. // TODO(user): Implement some form of caching mechanism so that we do not // have to verify the same intermediate certs over and over. ScopedX509StoreCtx context(X509_STORE_CTX_new()); if (!context) { LOG(WARNING) << "X509_STORE_CTX_new returned NULL."; return false; } if (!X509_STORE_CTX_init(context.get(), store_.get(), leaf_certificate, x509_stack.get())) { LOG(WARNING) << "X509_STORE_CTX_init failed."; return false; } if (X509_verify_cert(context.get()) != 1) { LOG(WARNING) << "Verification error: " << X509_verify_cert_error_string( X509_STORE_CTX_get_error(context.get())); return false; } if (sk_X509_num(context->chain) < 3) { LOG(WARNING) << "The leaf certificate should not be signed by the root " "certificate directly."; return false; } // Extract public key from the leaf certificate and system_id extension and // oem serial number from the intermediate certificate. return ExtractPublicKey(leaf_certificate, leaf_public_key) && ExtractSystemId(intermediate_certificate, system_id) && ExtractSerialNumber(intermediate_certificate, oem_ca_serial_number); } bool OemDeviceCert::Initialize(const std::string& serialized_root_certificate) { if (serialized_root_certificate.empty()) { LOG(WARNING) << "Empty root certificate."; return false; } store_.reset(X509_STORE_new()); if (!store_) { LOG(WARNING) << "X509_STORE_new returned NULL"; return false; } const char* data = serialized_root_certificate.data(); ScopedX509 root_certificate(d2i_X509(nullptr, reinterpret_cast(&data), serialized_root_certificate.size())); if (!root_certificate) { LOG(WARNING) << "d2i_X509 returned NULL."; return false; } if (!X509_STORE_add_cert(store_.get(), root_certificate.get())) { LOG(WARNING) << "X509_STORE_add_cert failed."; return false; } return true; } } // namespace widevine