blob: 67605dcbd3bb5f77ae4488a2ad700f77ef635054 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/network/onc/onc_parsed_certificates.h"
#include <string>
#include <vector>
#include "base/base64.h"
#include "base/optional.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chromeos/network/onc/onc_utils.h"
#include "components/onc/onc_constants.h"
#include "net/cert/x509_certificate.h"
namespace chromeos {
namespace onc {
namespace {
enum class CertificateType { kServer, kAuthority, kClient };
// Returns true if the certificate described by |onc_certificate| requests web
// trust.
bool HasWebTrustFlag(const base::Value& onc_certificate) {
DCHECK(onc_certificate.is_dict());
bool web_trust_flag = false;
const base::Value* trust_list = onc_certificate.FindKeyOfType(
::onc::certificate::kTrustBits, base::Value::Type::LIST);
if (!trust_list)
return false;
for (const base::Value& trust_entry : trust_list->GetList()) {
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 |base::nullopt| if the certificate type was not understood.
base::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 base::nullopt;
}
} // namespace
OncParsedCertificates::ServerOrAuthorityCertificate::
ServerOrAuthorityCertificate(
Type type,
const std::string& guid,
const scoped_refptr<net::X509Certificate>& certificate,
bool web_trust_requested)
: 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 (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::ListValue()) {}
OncParsedCertificates::OncParsedCertificates(
const base::Value& onc_certificates) {
if (!onc_certificates.is_list()) {
LOG(WARNING) << "Value is not a list";
has_error_ = true;
return;
}
for (size_t i = 0; i < onc_certificates.GetList().size(); ++i) {
const base::Value& onc_certificate = onc_certificates.GetList().at(i);
DCHECK(onc_certificate.is_dict());
VLOG(2) << "Parsing certificate at index " << i << ": " << onc_certificate;
if (!ParseCertificate(onc_certificate)) {
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& onc_certificate) {
const base::Value* guid_key = onc_certificate.FindKeyOfType(
::onc::certificate::kGUID, base::Value::Type::STRING);
DCHECK(guid_key);
std::string guid = guid_key->GetString();
const base::Value* type_key = onc_certificate.FindKeyOfType(
::onc::certificate::kType, base::Value::Type::STRING);
DCHECK(type_key);
base::Optional<CertificateType> type_opt =
GetCertTypeAsEnum(type_key->GetString());
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);
default:
NOTREACHED();
return false;
}
return false;
}
bool OncParsedCertificates::ParseServerOrCaCertificate(
ServerOrAuthorityCertificate::Type type,
const std::string& guid,
const base::Value& onc_certificate) {
bool web_trust_requested = HasWebTrustFlag(onc_certificate);
const base::Value* x509_data_key = onc_certificate.FindKeyOfType(
::onc::certificate::kX509, base::Value::Type::STRING);
if (!x509_data_key || x509_data_key->GetString().empty()) {
LOG(ERROR) << "Certificate missing " << ::onc::certificate::kX509
<< " certificate data.";
return false;
}
std::string certificate_der_data = DecodePEM(x509_data_key->GetString());
if (certificate_der_data.empty()) {
LOG(ERROR) << "Unable to create certificate from PEM encoding.";
return false;
}
scoped_refptr<net::X509Certificate> certificate =
net::X509Certificate::CreateFromBytes(certificate_der_data.data(),
certificate_der_data.length());
if (!certificate) {
LOG(ERROR) << "Unable to create certificate from PEM encoding.";
return false;
}
server_or_authority_certificates_.push_back(ServerOrAuthorityCertificate(
type, guid, certificate, web_trust_requested));
return true;
}
bool OncParsedCertificates::ParseClientCertificate(
const std::string& guid,
const base::Value& onc_certificate) {
const base::Value* base64_pkcs12_data_key = onc_certificate.FindKeyOfType(
::onc::certificate::kPKCS12, base::Value::Type::STRING);
if (!base64_pkcs12_data_key || base64_pkcs12_data_key->GetString().empty()) {
LOG(ERROR) << "PKCS12 data is missing for client certificate.";
return false;
}
std::string base64_pkcs12_data;
base::RemoveChars(base64_pkcs12_data_key->GetString(), "\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->GetString() << "\".";
return false;
}
client_certificates_.push_back(ClientCertificate(guid, pkcs12_data));
return true;
}
} // namespace onc
} // namespace chromeos