| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/components/onc/onc_parsed_certificates.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/containers/span.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "chromeos/components/onc/onc_utils.h" |
| #include "components/onc/onc_constants.h" |
| #include "net/cert/x509_certificate.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace chromeos::onc { |
| |
| namespace { |
| |
| enum class CertificateType { kServer, kAuthority, kClient }; |
| |
| // Parses the "Scope" of a policy-provided certificate. |
| // If a Scope element is not present, returns CertificateScope::Default(). |
| // If a Scope element is present but malformed, returns an empty absl::optional. |
| absl::optional<CertificateScope> ParseCertScope( |
| const base::Value::Dict& onc_certificate) { |
| const base::Value::Dict* scope_dict = |
| onc_certificate.FindDict(::onc::certificate::kScope); |
| if (!scope_dict) |
| return CertificateScope::Default(); |
| |
| return CertificateScope::ParseFromOncValue(*scope_dict); |
| } |
| |
| // Returns true if the certificate described by |onc_certificate| requests web |
| // trust. |
| bool HasWebTrustFlag(const base::Value::Dict& onc_certificate) { |
| bool web_trust_flag = false; |
| const base::Value::List* trust_list = |
| onc_certificate.FindList(::onc::certificate::kTrustBits); |
| if (!trust_list) |
| return false; |
| |
| for (const base::Value& trust_entry : *trust_list) { |
| DCHECK(trust_entry.is_string()); |
| |
| if (trust_entry.GetString() == ::onc::certificate::kWeb) { |
| // "Web" implies that the certificate is to be trusted for SSL |
| // identification. |
| web_trust_flag = true; |
| } else { |
| // Trust bits should only increase trust and never restrict. Thus, |
| // ignoring unknown bits should be safe. |
| LOG(WARNING) << "Certificate contains unknown trust type " |
| << trust_entry.GetString(); |
| } |
| } |
| |
| return web_trust_flag; |
| } |
| |
| // Converts the ONC string certificate type into the CertificateType enum. |
| // Returns |absl::nullopt| if the certificate type was not understood. |
| absl::optional<CertificateType> GetCertTypeAsEnum( |
| const std::string& cert_type) { |
| if (cert_type == ::onc::certificate::kServer) { |
| return CertificateType::kServer; |
| } |
| |
| if (cert_type == ::onc::certificate::kAuthority) { |
| return CertificateType::kAuthority; |
| } |
| |
| if (cert_type == ::onc::certificate::kClient) { |
| return CertificateType::kClient; |
| } |
| |
| return absl::nullopt; |
| } |
| |
| } // namespace |
| |
| OncParsedCertificates::ServerOrAuthorityCertificate:: |
| ServerOrAuthorityCertificate( |
| CertificateScope scope, |
| Type type, |
| const std::string& guid, |
| const scoped_refptr<net::X509Certificate>& certificate, |
| bool web_trust_requested) |
| : scope_(scope), |
| type_(type), |
| guid_(guid), |
| certificate_(certificate), |
| web_trust_requested_(web_trust_requested) {} |
| |
| OncParsedCertificates::ServerOrAuthorityCertificate:: |
| ServerOrAuthorityCertificate(const ServerOrAuthorityCertificate& other) = |
| default; |
| |
| OncParsedCertificates::ServerOrAuthorityCertificate& |
| OncParsedCertificates::ServerOrAuthorityCertificate::operator=( |
| const ServerOrAuthorityCertificate& other) = default; |
| |
| OncParsedCertificates::ServerOrAuthorityCertificate:: |
| ServerOrAuthorityCertificate(ServerOrAuthorityCertificate&& other) = |
| default; |
| |
| OncParsedCertificates::ServerOrAuthorityCertificate:: |
| ~ServerOrAuthorityCertificate() = default; |
| |
| bool OncParsedCertificates::ServerOrAuthorityCertificate::operator==( |
| const ServerOrAuthorityCertificate& other) const { |
| if (scope() != other.scope()) |
| return false; |
| |
| if (type() != other.type()) |
| return false; |
| |
| if (guid() != other.guid()) |
| return false; |
| |
| if (!certificate()->EqualsExcludingChain(other.certificate().get())) |
| return false; |
| |
| if (web_trust_requested() != other.web_trust_requested()) |
| return false; |
| |
| return true; |
| } |
| |
| bool OncParsedCertificates::ServerOrAuthorityCertificate::operator!=( |
| const ServerOrAuthorityCertificate& other) const { |
| return !(*this == other); |
| } |
| |
| OncParsedCertificates::ClientCertificate::ClientCertificate( |
| const std::string& guid, |
| const std::string& pkcs12_data) |
| : guid_(guid), pkcs12_data_(pkcs12_data) {} |
| |
| OncParsedCertificates::ClientCertificate::ClientCertificate( |
| const ClientCertificate& other) = default; |
| |
| OncParsedCertificates::ClientCertificate& |
| OncParsedCertificates::ClientCertificate::operator=( |
| const ClientCertificate& other) = default; |
| |
| OncParsedCertificates::ClientCertificate::ClientCertificate( |
| ClientCertificate&& other) = default; |
| |
| OncParsedCertificates::ClientCertificate::~ClientCertificate() = default; |
| |
| bool OncParsedCertificates::ClientCertificate::operator==( |
| const ClientCertificate& other) const { |
| if (guid() != other.guid()) |
| return false; |
| |
| if (pkcs12_data() != other.pkcs12_data()) |
| return false; |
| |
| return true; |
| } |
| |
| bool OncParsedCertificates::ClientCertificate::operator!=( |
| const ClientCertificate& other) const { |
| return !(*this == other); |
| } |
| |
| OncParsedCertificates::OncParsedCertificates() |
| : OncParsedCertificates(base::Value::List()) {} |
| |
| OncParsedCertificates::OncParsedCertificates( |
| const base::Value::List& onc_certificates) { |
| for (size_t i = 0; i < onc_certificates.size(); ++i) { |
| const base::Value& onc_certificate = onc_certificates[i]; |
| DCHECK(onc_certificate.is_dict()); |
| |
| VLOG(2) << "Parsing certificate at index " << i << ": " << onc_certificate; |
| |
| if (!ParseCertificate(onc_certificate.GetDict())) { |
| has_error_ = true; |
| LOG(ERROR) << "Cannot parse certificate at index " << i; |
| } else { |
| VLOG(2) << "Successfully parsed certificate at index " << i; |
| } |
| } |
| } |
| |
| OncParsedCertificates::~OncParsedCertificates() = default; |
| |
| bool OncParsedCertificates::ParseCertificate( |
| const base::Value::Dict& onc_certificate) { |
| const std::string* guid = |
| onc_certificate.FindString(::onc::certificate::kGUID); |
| DCHECK(guid); |
| |
| const std::string* type_key = |
| onc_certificate.FindString(::onc::certificate::kType); |
| DCHECK(type_key); |
| absl::optional<CertificateType> type_opt = GetCertTypeAsEnum(*type_key); |
| if (!type_opt) { |
| return false; |
| } |
| |
| switch (type_opt.value()) { |
| case CertificateType::kServer: |
| return ParseServerOrCaCertificate( |
| ServerOrAuthorityCertificate::Type::kServer, *guid, onc_certificate); |
| case CertificateType::kAuthority: |
| return ParseServerOrCaCertificate( |
| ServerOrAuthorityCertificate::Type::kAuthority, *guid, |
| onc_certificate); |
| case CertificateType::kClient: |
| return ParseClientCertificate(*guid, onc_certificate); |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool OncParsedCertificates::ParseServerOrCaCertificate( |
| ServerOrAuthorityCertificate::Type type, |
| const std::string& guid, |
| const base::Value::Dict& onc_certificate) { |
| absl::optional<CertificateScope> scope = ParseCertScope(onc_certificate); |
| if (!scope) { |
| LOG(ERROR) << "Certificate has malformed 'Scope'"; |
| return false; |
| } |
| |
| bool web_trust_requested = HasWebTrustFlag(onc_certificate); |
| const std::string* x509_data_key = |
| onc_certificate.FindString(::onc::certificate::kX509); |
| if (!x509_data_key || x509_data_key->empty()) { |
| LOG(ERROR) << "Certificate missing " << ::onc::certificate::kX509 |
| << " certificate data."; |
| return false; |
| } |
| |
| std::string certificate_der_data = DecodePEM(*x509_data_key); |
| if (certificate_der_data.empty()) { |
| LOG(ERROR) << "Unable to create certificate from PEM encoding."; |
| return false; |
| } |
| |
| scoped_refptr<net::X509Certificate> certificate = |
| net::X509Certificate::CreateFromBytes( |
| base::as_bytes(base::make_span(certificate_der_data))); |
| if (!certificate) { |
| LOG(ERROR) << "Unable to create certificate from PEM encoding."; |
| return false; |
| } |
| |
| server_or_authority_certificates_.emplace_back( |
| scope.value(), type, guid, certificate, web_trust_requested); |
| return true; |
| } |
| |
| bool OncParsedCertificates::ParseClientCertificate( |
| const std::string& guid, |
| const base::Value::Dict& onc_certificate) { |
| const std::string* base64_pkcs12_data_key = |
| onc_certificate.FindString(::onc::certificate::kPKCS12); |
| if (!base64_pkcs12_data_key || base64_pkcs12_data_key->empty()) { |
| LOG(ERROR) << "PKCS12 data is missing for client certificate."; |
| return false; |
| } |
| |
| std::string base64_pkcs12_data; |
| base::RemoveChars(*base64_pkcs12_data_key, "\n", &base64_pkcs12_data); |
| std::string pkcs12_data; |
| if (!base::Base64Decode(base64_pkcs12_data, &pkcs12_data)) { |
| LOG(ERROR) << "Unable to base64 decode PKCS#12 data: \"" |
| << base64_pkcs12_data_key << "\"."; |
| return false; |
| } |
| |
| client_certificates_.emplace_back(guid, pkcs12_data); |
| return true; |
| } |
| |
| } // namespace chromeos::onc |