//////////////////////////////////////////////////////////////////////////////// // Copyright 2017 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 "common/x509_cert.h" #include #include #include "glog/logging.h" #include "absl/strings/escaping.h" #include "absl/synchronization/mutex.h" #include "openssl/bio.h" #include "openssl/evp.h" #include "openssl/pem.h" #include "openssl/pkcs7.h" #include "openssl/x509.h" #include "openssl/x509v3.h" #include "common/openssl_util.h" #include "common/rsa_key.h" namespace { // Serializes the X509 |certificate| into a PEM-encoded string. Returns true // on success, false otherwise. The caller retains ownership of // |certificate| and |serialized_certificate|. |serialized_certificate| must // not be NULL. bool PemEncodeX509Certificate(const X509& certificate, std::string* serialized_certificate) { CHECK(serialized_certificate) << "serialized_certificate can not be null."; ScopedBIO bio(BIO_new(BIO_s_mem())); if (bio == nullptr) { return false; } // The const_cast is necessary for the openssl call. PEM_write_bio_X509(bio.get(), const_cast(&certificate)); int serialized_size = BIO_pending(bio.get()); serialized_certificate->resize(serialized_size); if (BIO_read(bio.get(), &(*serialized_certificate)[0], serialized_size) != serialized_size) { return false; } return true; } } // anonymous namespace. namespace widevine { std::unique_ptr X509Cert::FromOpenSslCert(ScopedX509 certificate) { return std::unique_ptr(new X509Cert(certificate.release())); } X509Cert::X509Cert() : openssl_cert_(NULL) {} X509Cert::~X509Cert() { if (openssl_cert_ != NULL) { X509_free(openssl_cert_); } } X509Cert::X509Cert(X509* openssl_cert) : openssl_cert_(openssl_cert) {} util::Status X509Cert::LoadPem(const std::string& pem_cert) { if (pem_cert.empty()) { return util::Status(util::error::INVALID_ARGUMENT, "Empty PEM certificate"); } BIO* bio(NULL); X509* new_cert(NULL); bio = BIO_new_mem_buf(const_cast(pem_cert.data()), pem_cert.size()); if (bio == NULL) { return util::Status(util::error::INTERNAL, "BIO allocation failed"); } util::Status status; new_cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); if (new_cert == NULL) { status = util::Status(util::Status::canonical_space(), util::error::INVALID_ARGUMENT, "PEM certificate load failed"); goto cleanup; } if (openssl_cert_ != NULL) { X509_free(openssl_cert_); } openssl_cert_ = new_cert; cleanup: if (bio != NULL) { BIO_free(bio); } return status; } util::Status X509Cert::LoadDer(const std::string& der_cert) { if (der_cert.empty()) { return util::Status(util::error::INVALID_ARGUMENT, "Empty DER certificate"); } const unsigned char* cert_data = reinterpret_cast(der_cert.data()); X509* new_cert = d2i_X509(NULL, &cert_data, der_cert.size()); if (new_cert == NULL) { return util::Status(util::error::INVALID_ARGUMENT, "DER certificate load failed"); } if (openssl_cert_ != NULL) { X509_free(openssl_cert_); } openssl_cert_ = new_cert; return util::OkStatus(); } std::string X509Cert::GetPem() const { std::string serialized_certificate; if (!PemEncodeX509Certificate(*openssl_cert_, &serialized_certificate)) { return ""; } return serialized_certificate; } std::unique_ptr X509Cert::GetRsaPublicKey() const { ScopedPKEY pkey(X509_get_pubkey(openssl_cert_)); return std::unique_ptr( new RsaPublicKey(EVP_PKEY_get1_RSA(pkey.get()))); } const std::string& X509Cert::GetSubjectName() { if (subject_name_.empty() && (openssl_cert_ != NULL)) { X509_NAME* subject = X509_get_subject_name(openssl_cert_); if (subject != NULL) { BIO* bio = BIO_new(BIO_s_mem()); if (bio != NULL) { X509_NAME_print_ex(bio, subject, 0, 0); int size = BIO_pending(bio); std::unique_ptr buffer(new char[size]); int bytes_read = BIO_read(bio, buffer.get(), size); if (bytes_read == size) { subject_name_.assign(buffer.get(), bytes_read); } BIO_free(bio); } } } return subject_name_; } std::string X509Cert::GetSubjectNameField(const std::string& field) { if (field.empty()) { return std::string(); } const std::string& subject = GetSubjectName(); size_t start_pos = subject.find(field + "="); if (start_pos == std::string::npos) { return std::string(); } start_pos += field.size() + 1; size_t end_pos = subject.find(",", start_pos); if (end_pos == std::string::npos) { end_pos = subject.size(); } return subject.substr(start_pos, end_pos - start_pos); } std::string X509Cert::GetSerialNumber() const { if (openssl_cert_ == NULL) { return std::string(); } BIGNUM* bn = ASN1_INTEGER_to_BN(X509_get_serialNumber(openssl_cert_), NULL); if (bn == NULL) { return std::string(); } std::string result; char* openssl_sn = BN_bn2hex(bn); if (openssl_sn != NULL) { result = absl::HexStringToBytes(openssl_sn); OPENSSL_free(openssl_sn); } BN_free(bn); return result; } bool X509Cert::IsCaCertificate() const { return X509_check_ca(openssl_cert_) != 0; } bool X509Cert::GetV3BooleanExtension(const std::string& oid, bool* value) const { ScopedAsn1Object extension_name(OBJ_txt2obj(oid.c_str(), 1)); int ext_pos = X509_get_ext_by_OBJ(openssl_cert_, extension_name.get(), -1); if (ext_pos < 0) return false; X509_EXTENSION* extension(X509_get_ext(openssl_cert_, ext_pos)); if (!extension) return false; ASN1_OCTET_STRING* extension_data(X509_EXTENSION_get_data(extension)); if (!extension_data) return false; if ((extension_data->length != 3) || (extension_data->data[0] != 1) || (extension_data->data[1] != 1)) return false; *value = extension_data->data[2] != 0; return true; } X509CertChain::~X509CertChain() { Reset(); } void X509CertChain::Reset() { for (auto certp : cert_chain_) { delete certp; } cert_chain_.clear(); } util::Status X509CertChain::LoadPem(const std::string& pem_cert_chain) { static const char kBeginCertificate[] = "-----BEGIN CERTIFICATE-----"; static const char kEndCertificate[] = "-----END CERTIFICATE-----"; Reset(); size_t begin_pos = pem_cert_chain.find(kBeginCertificate); while (begin_pos != std::string::npos) { size_t end_pos = pem_cert_chain.find( kEndCertificate, begin_pos + sizeof(kBeginCertificate) - 1); if (end_pos != std::string::npos) { end_pos += sizeof(kEndCertificate) - 1; std::unique_ptr new_cert(new X509Cert); util::Status status = new_cert->LoadPem( pem_cert_chain.substr(begin_pos, end_pos - begin_pos)); if (!status.ok()) { return status; } cert_chain_.push_back(new_cert.release()); begin_pos = pem_cert_chain.find(kBeginCertificate, end_pos); } } return util::OkStatus(); } util::Status X509CertChain::LoadPkcs7(const std::string& pk7_cert_chain) { ScopedX509Stack cert_stack(sk_X509_new_null()); CBS cbs; CBS_init(&cbs, reinterpret_cast(pk7_cert_chain.data()), pk7_cert_chain.size()); if (!PKCS7_get_certificates(cert_stack.get(), &cbs)) { return util::Status(util::error::INVALID_ARGUMENT, "Unable to load PKCS#7 certificate chain"); } while (sk_X509_num(cert_stack.get()) > 0) { cert_chain_.insert(cert_chain_.begin(), new X509Cert(sk_X509_pop(cert_stack.get()))); } return util::OkStatus(); } std::string X509CertChain::GetPkcs7() { std::string pkcs7_cert; ScopedX509Stack cert_stack(sk_X509_new_null()); for (X509Cert* cert : cert_chain_) { // X509 stack takes ownership of certificates. Copy certificates to retain // |cert_chain_|. X509Cert cert_copy; if (!cert_copy.LoadPem(cert->GetPem()).ok()) { LOG(WARNING) << "Certificate chain serialization failed"; return ""; } X509* openssl_cert_copy = const_cast(cert_copy.openssl_cert()); cert_copy.openssl_cert_ = nullptr; sk_X509_push(cert_stack.get(), openssl_cert_copy); } ScopedPKCS7 pkcs7( PKCS7_sign(nullptr, nullptr, cert_stack.get(), nullptr, PKCS7_DETACHED)); if (!pkcs7) { LOG(WARNING) << "Could not convert certificate chain to PKCS7"; return ""; } ScopedBIO bio(BIO_new(BIO_s_mem())); if (bio.get() == nullptr || !i2d_PKCS7_bio(bio.get(), pkcs7.get())) { LOG(WARNING) << "Failed writing PKCS7 to bio"; return ""; } int cert_size = BIO_pending(bio.get()); pkcs7_cert.resize(cert_size); if (BIO_read(bio.get(), &pkcs7_cert[0], cert_size) != cert_size) { LOG(WARNING) << "BIO_read failure"; return ""; } return pkcs7_cert; } X509Cert* X509CertChain::GetCert(size_t cert_index) const { if (cert_index >= cert_chain_.size()) { return NULL; } return cert_chain_[cert_index]; } X509CA::X509CA(X509Cert* ca_cert) : ca_cert_(ca_cert), openssl_store_(NULL) {} X509CA::~X509CA() { if (openssl_store_ != NULL) { X509_STORE_free(openssl_store_); } } util::Status X509CA::InitializeStore() { absl::WriterMutexLock lock(&openssl_store_mutex_); if (openssl_store_ == NULL) { if (ca_cert_ == NULL) { return util::Status(util::error::INTERNAL, "CA X.509Cert is NULL"); } openssl_store_ = X509_STORE_new(); if (openssl_store_ == NULL) { return util::Status(util::error::INTERNAL, "Failed to allocate X.509 store"); } if (X509_STORE_add_cert(openssl_store_, const_cast(ca_cert_->openssl_cert())) == 0) { X509_STORE_free(openssl_store_); openssl_store_ = NULL; return util::Status(util::error::INTERNAL, "Failed to add X.509 CA certificate to store"); } } return util::OkStatus(); } util::Status X509CA::VerifyCert(const X509Cert& cert) { return OpenSslX509Verify(cert.openssl_cert(), nullptr); } util::Status X509CA::VerifyCertChain(const X509CertChain& cert_chain) { if (cert_chain.GetNumCerts() < 1) { return util::Status(util::error::INVALID_ARGUMENT, "Cannot verify empty certificate chain"); } ScopedX509StackOnly intermediates(sk_X509_new_null()); if (!intermediates) { return util::Status( util::Status::canonical_space(), util::error::INTERNAL, "Failed to allocate X.509 intermediate certificate stack"); } const X509Cert* leaf_cert(nullptr); for (size_t idx = 0; idx < cert_chain.GetNumCerts(); ++idx) { if (cert_chain.GetCert(idx)->IsCaCertificate()) { sk_X509_push(intermediates.get(), const_cast(cert_chain.GetCert(idx)->openssl_cert())); } else { leaf_cert = cert_chain.GetCert(idx); } } if (!leaf_cert) { return util::Status(util::Status::canonical_space(), util::error::INVALID_ARGUMENT, "X.509 certificate chain without leaf certificate."); } return OpenSslX509Verify(leaf_cert->openssl_cert(), intermediates.get()); } util::Status X509CA::VerifyCertWithChain(const X509Cert& cert, const X509CertChain& cert_chain) { ScopedX509StackOnly intermediates(sk_X509_new_null()); if (!intermediates) { // MakeStatus is now preferred. But we don't support it in the exported // version, yet. So, ignore lint here. // NOLINTNEXTLINE return util::Status( util::Status::canonical_space(), util::error::INTERNAL, "Failed to allocate X.509 intermediate certificate stack"); } for (size_t idx = 0; idx < cert_chain.GetNumCerts(); ++idx) { sk_X509_push(intermediates.get(), const_cast(cert_chain.GetCert(idx)->openssl_cert())); } return OpenSslX509Verify(cert.openssl_cert(), intermediates.get()); } util::Status X509CA::OpenSslX509Verify(const X509* cert, STACK_OF(X509) * intermediates) { DCHECK(cert); absl::ReaderMutexLock lock(&openssl_store_mutex_); if (openssl_store_ == NULL) { openssl_store_mutex_.ReaderUnlock(); util::Status status = InitializeStore(); if (!status.ok()) { return status; } openssl_store_mutex_.ReaderLock(); } ScopedX509StoreCtx store_ctx(X509_STORE_CTX_new()); if (!store_ctx) { return util::Status(util::Status::canonical_space(), util::error::INTERNAL, "Failed to allocate X.509 store context"); } if (X509_STORE_CTX_init(store_ctx.get(), openssl_store_, const_cast(cert), intermediates) == 0) { return util::Status(util::Status::canonical_space(), util::error::INTERNAL, "Failed to initialize X.509 store context"); } int x509_status = X509_verify_cert(store_ctx.get()); if (x509_status != 1) { return util::Status(util::Status::canonical_space(), util::error::INTERNAL, std::string("X.509 certificate chain validation failed: ") + X509_verify_cert_error_string( X509_STORE_CTX_get_error(store_ctx.get()))); } return util::OkStatus(); } } // namespace widevine