blob: 350aca6ef4d7b2a50d449aa4c62d767f7e809357 [file] [log] [blame]
// Copyright 2019 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 "cast/common/certificate/cast_cert_validator_internal.h"
#include <openssl/asn1.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <time.h>
#include <chrono>
#include <string>
#include <vector>
#include "cast/common/certificate/types.h"
#include "util/crypto/pem_helpers.h"
#include "util/osp_logging.h"
namespace openscreen {
namespace cast {
namespace {
constexpr static int32_t kMinRsaModulusLengthBits = 2048;
// Stores intermediate state while attempting to find a valid certificate chain
// from a set of trusted certificates to a target certificate. Together, a
// sequence of these forms a certificate chain to be verified as well as a stack
// that can be unwound for searching more potential paths.
struct CertPathStep {
X509* cert;
// The next index that can be checked in |trust_store| if the choice |cert| on
// the path needs to be reverted.
uint32_t trust_store_index;
// The next index that can be checked in |intermediate_certs| if the choice
// |cert| on the path needs to be reverted.
uint32_t intermediate_cert_index;
};
// These values are bit positions from RFC 5280 4.2.1.3 and will be passed to
// ASN1_BIT_STRING_get_bit.
enum KeyUsageBits {
kDigitalSignature = 0,
kKeyCertSign = 5,
};
bool CertInPath(X509_NAME* name,
const std::vector<CertPathStep>& steps,
uint32_t start,
uint32_t stop) {
for (uint32_t i = start; i < stop; ++i) {
if (X509_NAME_cmp(name, X509_get_subject_name(steps[i].cert)) == 0) {
return true;
}
}
return false;
}
// Parse the data in |time| at |index| as a two-digit ascii number.
uint8_t ParseAsn1TimeDoubleDigit(ASN1_GENERALIZEDTIME* time, int index) {
return (time->data[index] - '0') * 10 + (time->data[index + 1] - '0');
}
bssl::UniquePtr<BASIC_CONSTRAINTS> GetConstraints(X509* issuer) {
const int basic_constraints_index =
X509_get_ext_by_NID(issuer, NID_basic_constraints, -1);
if (basic_constraints_index == -1) {
return nullptr;
}
X509_EXTENSION* const basic_constraints_extension =
X509_get_ext(issuer, basic_constraints_index);
return bssl::UniquePtr<BASIC_CONSTRAINTS>{
reinterpret_cast<BASIC_CONSTRAINTS*>(
X509V3_EXT_d2i(basic_constraints_extension))};
}
Error::Code VerifyCertTime(X509* cert, const DateTime& time) {
DateTime not_before;
DateTime not_after;
if (!GetCertValidTimeRange(cert, &not_before, &not_after)) {
return Error::Code::kErrCertsVerifyGeneric;
}
if ((time < not_before) || (not_after < time)) {
return Error::Code::kErrCertsDateInvalid;
}
return Error::Code::kNone;
}
bool VerifyPublicKeyLength(EVP_PKEY* public_key) {
return EVP_PKEY_bits(public_key) >= kMinRsaModulusLengthBits;
}
bssl::UniquePtr<ASN1_BIT_STRING> GetKeyUsage(X509* cert) {
int pos = X509_get_ext_by_NID(cert, NID_key_usage, -1);
if (pos == -1) {
return nullptr;
}
X509_EXTENSION* key_usage = X509_get_ext(cert, pos);
const uint8_t* value = key_usage->value->data;
ASN1_BIT_STRING* key_usage_bit_string = nullptr;
if (!d2i_ASN1_BIT_STRING(&key_usage_bit_string, &value,
key_usage->value->length)) {
return nullptr;
}
return bssl::UniquePtr<ASN1_BIT_STRING>{key_usage_bit_string};
}
Error::Code VerifyCertificateChain(const std::vector<CertPathStep>& path,
uint32_t step_index,
const DateTime& time) {
// Default max path length is the number of intermediate certificates.
int max_pathlen = path.size() - 2;
std::vector<NAME_CONSTRAINTS*> path_name_constraints;
Error::Code error = Error::Code::kNone;
uint32_t i = step_index;
for (; i < path.size() - 1; ++i) {
X509* subject = path[i + 1].cert;
X509* issuer = path[i].cert;
bool is_root = (i == step_index);
if (!is_root) {
if ((error = VerifyCertTime(issuer, time)) != Error::Code::kNone) {
return error;
}
if (X509_NAME_cmp(X509_get_subject_name(issuer),
X509_get_issuer_name(issuer)) != 0) {
if (max_pathlen == 0) {
return Error::Code::kErrCertsPathlen;
}
--max_pathlen;
} else {
issuer->ex_flags |= EXFLAG_SI;
}
} else {
issuer->ex_flags |= EXFLAG_SI;
}
bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(issuer);
if (key_usage) {
const int bit =
ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kKeyCertSign);
if (bit == 0) {
return Error::Code::kErrCertsVerifyGeneric;
}
}
// Certificates issued by a valid CA authority shall have the
// basicConstraints property present with the CA bit set. Self-signed
// certificates do not have this property present.
bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints =
GetConstraints(issuer);
if (!basic_constraints || !basic_constraints->ca) {
return Error::Code::kErrCertsVerifyGeneric;
}
if (basic_constraints->pathlen) {
if (basic_constraints->pathlen->length != 1) {
return Error::Code::kErrCertsVerifyGeneric;
} else {
const int pathlen = *basic_constraints->pathlen->data;
if (pathlen < 0) {
return Error::Code::kErrCertsVerifyGeneric;
}
if (pathlen < max_pathlen) {
max_pathlen = pathlen;
}
}
}
if (X509_ALGOR_cmp(issuer->sig_alg, issuer->cert_info->signature) != 0) {
return Error::Code::kErrCertsVerifyGeneric;
}
bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(issuer)};
if (!VerifyPublicKeyLength(public_key.get())) {
return Error::Code::kErrCertsVerifyGeneric;
}
// NOTE: (!self-issued || target) -> verify name constraints. Target case
// is after the loop.
const bool is_self_issued = issuer->ex_flags & EXFLAG_SI;
if (!is_self_issued) {
for (NAME_CONSTRAINTS* name_constraints : path_name_constraints) {
if (NAME_CONSTRAINTS_check(subject, name_constraints) != X509_V_OK) {
return Error::Code::kErrCertsVerifyGeneric;
}
}
}
if (issuer->nc) {
path_name_constraints.push_back(issuer->nc);
} else {
const int index = X509_get_ext_by_NID(issuer, NID_name_constraints, -1);
if (index != -1) {
X509_EXTENSION* ext = X509_get_ext(issuer, index);
auto* nc = reinterpret_cast<NAME_CONSTRAINTS*>(X509V3_EXT_d2i(ext));
if (nc) {
issuer->nc = nc;
path_name_constraints.push_back(nc);
} else {
return Error::Code::kErrCertsVerifyGeneric;
}
}
}
// Check that any policy mappings present are _not_ the anyPolicy OID. Even
// though we don't otherwise handle policies, this is required by RFC 5280
// 6.1.4(a).
const int policy_mappings_index =
X509_get_ext_by_NID(issuer, NID_policy_mappings, -1);
if (policy_mappings_index != -1) {
X509_EXTENSION* policy_mappings_extension =
X509_get_ext(issuer, policy_mappings_index);
auto* policy_mappings = reinterpret_cast<POLICY_MAPPINGS*>(
X509V3_EXT_d2i(policy_mappings_extension));
const uint32_t policy_mapping_count =
sk_POLICY_MAPPING_num(policy_mappings);
const ASN1_OBJECT* any_policy = OBJ_nid2obj(NID_any_policy);
for (uint32_t i = 0; i < policy_mapping_count; ++i) {
POLICY_MAPPING* policy_mapping =
sk_POLICY_MAPPING_value(policy_mappings, i);
const bool either_matches =
((OBJ_cmp(policy_mapping->issuerDomainPolicy, any_policy) == 0) ||
(OBJ_cmp(policy_mapping->subjectDomainPolicy, any_policy) == 0));
if (either_matches) {
error = Error::Code::kErrCertsVerifyGeneric;
break;
}
}
sk_POLICY_MAPPING_free(policy_mappings);
if (error != Error::Code::kNone) {
return error;
}
}
// Check that we don't have any unhandled extensions marked as critical.
int extension_count = X509_get_ext_count(issuer);
for (int i = 0; i < extension_count; ++i) {
X509_EXTENSION* extension = X509_get_ext(issuer, i);
if (extension->critical > 0) {
const int nid = OBJ_obj2nid(extension->object);
if (nid != NID_name_constraints && nid != NID_basic_constraints &&
nid != NID_key_usage) {
return Error::Code::kErrCertsVerifyGeneric;
}
}
}
int nid = OBJ_obj2nid(subject->sig_alg->algorithm);
const EVP_MD* digest;
switch (nid) {
case NID_sha1WithRSAEncryption:
digest = EVP_sha1();
break;
case NID_sha256WithRSAEncryption:
digest = EVP_sha256();
break;
case NID_sha384WithRSAEncryption:
digest = EVP_sha384();
break;
case NID_sha512WithRSAEncryption:
digest = EVP_sha512();
break;
default:
return Error::Code::kErrCertsVerifyGeneric;
}
if (!VerifySignedData(
digest, public_key.get(),
{subject->cert_info->enc.enc,
static_cast<uint32_t>(subject->cert_info->enc.len)},
{subject->signature->data,
static_cast<uint32_t>(subject->signature->length)})) {
return Error::Code::kErrCertsVerifyGeneric;
}
}
// NOTE: Other half of ((!self-issued || target) -> check name constraints).
for (NAME_CONSTRAINTS* name_constraints : path_name_constraints) {
if (NAME_CONSTRAINTS_check(path.back().cert, name_constraints) !=
X509_V_OK) {
return Error::Code::kErrCertsVerifyGeneric;
}
}
return error;
}
X509* ParseX509Der(const std::string& der) {
const uint8_t* data = reinterpret_cast<const uint8_t*>(der.data());
return d2i_X509(nullptr, &data, der.size());
}
} // namespace
// Parses DateTime with additional restrictions laid out by RFC 5280
// 4.1.2.5.2.
bool ParseAsn1GeneralizedTime(ASN1_GENERALIZEDTIME* time, DateTime* out) {
static constexpr uint8_t kDaysPerMonth[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
};
if (time->length != 15) {
return false;
}
if (time->data[14] != 'Z') {
return false;
}
for (int i = 0; i < 14; ++i) {
if (time->data[i] < '0' || time->data[i] > '9') {
return false;
}
}
out->year = ParseAsn1TimeDoubleDigit(time, 0) * 100 +
ParseAsn1TimeDoubleDigit(time, 2);
out->month = ParseAsn1TimeDoubleDigit(time, 4);
out->day = ParseAsn1TimeDoubleDigit(time, 6);
out->hour = ParseAsn1TimeDoubleDigit(time, 8);
out->minute = ParseAsn1TimeDoubleDigit(time, 10);
out->second = ParseAsn1TimeDoubleDigit(time, 12);
if (out->month == 0 || out->month > 12) {
return false;
}
int days_per_month = kDaysPerMonth[out->month - 1];
if (out->month == 2) {
if (out->year % 4 == 0 && (out->year % 100 != 0 || out->year % 400 == 0)) {
days_per_month = 29;
} else {
days_per_month = 28;
}
}
if (out->day == 0 || out->day > days_per_month) {
return false;
}
if (out->hour > 23) {
return false;
}
if (out->minute > 59) {
return false;
}
// Leap seconds are allowed.
if (out->second > 60) {
return false;
}
return true;
}
bool GetCertValidTimeRange(X509* cert,
DateTime* not_before,
DateTime* not_after) {
ASN1_GENERALIZEDTIME* not_before_asn1 = ASN1_TIME_to_generalizedtime(
cert->cert_info->validity->notBefore, nullptr);
ASN1_GENERALIZEDTIME* not_after_asn1 = ASN1_TIME_to_generalizedtime(
cert->cert_info->validity->notAfter, nullptr);
if (!not_before_asn1 || !not_after_asn1) {
return false;
}
bool times_valid = ParseAsn1GeneralizedTime(not_before_asn1, not_before) &&
ParseAsn1GeneralizedTime(not_after_asn1, not_after);
ASN1_GENERALIZEDTIME_free(not_before_asn1);
ASN1_GENERALIZEDTIME_free(not_after_asn1);
return times_valid;
}
// static
TrustStore TrustStore::CreateInstanceFromPemFile(absl::string_view file_path) {
TrustStore store;
std::vector<std::string> certs = ReadCertificatesFromPemFile(file_path);
for (const auto& der_cert : certs) {
const uint8_t* data = (const uint8_t*)der_cert.data();
store.certs.emplace_back(d2i_X509(nullptr, &data, der_cert.size()));
}
return store;
}
bool VerifySignedData(const EVP_MD* digest,
EVP_PKEY* public_key,
const ConstDataSpan& data,
const ConstDataSpan& signature) {
// This code assumes the signature algorithm was RSASSA PKCS#1 v1.5 with
// |digest|.
bssl::ScopedEVP_MD_CTX ctx;
if (!EVP_DigestVerifyInit(ctx.get(), nullptr, digest, nullptr, public_key)) {
return false;
}
return (EVP_DigestVerify(ctx.get(), signature.data, signature.length,
data.data, data.length) == 1);
}
Error FindCertificatePath(const std::vector<std::string>& der_certs,
const DateTime& time,
CertificatePathResult* result_path,
TrustStore* trust_store) {
if (der_certs.empty()) {
return Error(Error::Code::kErrCertsMissing, "Missing DER certificates");
}
bssl::UniquePtr<X509>& target_cert = result_path->target_cert;
std::vector<bssl::UniquePtr<X509>>& intermediate_certs =
result_path->intermediate_certs;
target_cert.reset(ParseX509Der(der_certs[0]));
if (!target_cert) {
OSP_DVLOG << "FindCertificatePath: Invalid target certificate";
return Error::Code::kErrCertsParse;
}
for (size_t i = 1; i < der_certs.size(); ++i) {
intermediate_certs.emplace_back(ParseX509Der(der_certs[i]));
if (!intermediate_certs.back()) {
OSP_DVLOG
<< "FindCertificatePath: Failed to parse intermediate certificate "
<< i << " of " << der_certs.size();
return Error::Code::kErrCertsParse;
}
}
// Basic checks on the target certificate.
Error::Code error = VerifyCertTime(target_cert.get(), time);
if (error != Error::Code::kNone) {
OSP_DVLOG << "FindCertificatePath: Failed to verify certificate time";
return error;
}
bssl::UniquePtr<EVP_PKEY> public_key{X509_get_pubkey(target_cert.get())};
if (!VerifyPublicKeyLength(public_key.get())) {
OSP_DVLOG << "FindCertificatePath: Failed with invalid public key length";
return Error::Code::kErrCertsVerifyGeneric;
}
if (X509_ALGOR_cmp(target_cert.get()->sig_alg,
target_cert.get()->cert_info->signature) != 0) {
return Error::Code::kErrCertsVerifyGeneric;
}
bssl::UniquePtr<ASN1_BIT_STRING> key_usage = GetKeyUsage(target_cert.get());
if (!key_usage) {
OSP_DVLOG << "FindCertificatePath: Failed with no key usage";
return Error::Code::kErrCertsRestrictions;
}
int bit =
ASN1_BIT_STRING_get_bit(key_usage.get(), KeyUsageBits::kDigitalSignature);
if (bit == 0) {
OSP_DVLOG << "FindCertificatePath: Failed to get digital signature";
return Error::Code::kErrCertsRestrictions;
}
X509* path_head = target_cert.get();
std::vector<CertPathStep> path;
// This vector isn't used as resizable, so instead we allocate the largest
// possible single path up front. This would be a single trusted cert, all
// the intermediate certs used once, and the target cert.
path.resize(1 + intermediate_certs.size() + 1);
// Additionally, the path is slightly simpler to deal with if the list is
// sorted from trust->target, so the path is actually built starting from the
// end.
uint32_t first_index = path.size() - 1;
path[first_index].cert = path_head;
// Index into |path| of the current frontier of path construction.
uint32_t path_index = first_index;
// Whether |path| has reached a certificate in |trust_store| and is ready for
// verification.
bool path_cert_in_trust_store = false;
// Attempt to build a valid certificate chain from |target_cert| to a
// certificate in |trust_store|. This loop tries all possible paths in a
// depth-first-search fashion. If no valid paths are found, the error
// returned is whatever the last error was from the last path tried.
uint32_t trust_store_index = 0;
uint32_t intermediate_cert_index = 0;
Error::Code last_error = Error::Code::kNone;
for (;;) {
X509_NAME* target_issuer_name = X509_get_issuer_name(path_head);
OSP_DVLOG << "FindCertificatePath: Target certificate issuer name: "
<< X509_NAME_oneline(target_issuer_name, 0, 0);
// The next issuer certificate to add to the current path.
X509* next_issuer = nullptr;
for (uint32_t i = trust_store_index; i < trust_store->certs.size(); ++i) {
X509* trust_store_cert = trust_store->certs[i].get();
X509_NAME* trust_store_cert_name =
X509_get_subject_name(trust_store_cert);
OSP_DVLOG << "FindCertificatePath: Trust store certificate issuer name: "
<< X509_NAME_oneline(trust_store_cert_name, 0, 0);
if (X509_NAME_cmp(trust_store_cert_name, target_issuer_name) == 0) {
CertPathStep& next_step = path[--path_index];
next_step.cert = trust_store_cert;
next_step.trust_store_index = i + 1;
next_step.intermediate_cert_index = 0;
next_issuer = trust_store_cert;
path_cert_in_trust_store = true;
break;
}
}
trust_store_index = 0;
if (!next_issuer) {
for (uint32_t i = intermediate_cert_index; i < intermediate_certs.size();
++i) {
X509* intermediate_cert = intermediate_certs[i].get();
X509_NAME* intermediate_cert_name =
X509_get_subject_name(intermediate_cert);
if (X509_NAME_cmp(intermediate_cert_name, target_issuer_name) == 0 &&
!CertInPath(intermediate_cert_name, path, path_index,
first_index)) {
CertPathStep& next_step = path[--path_index];
next_step.cert = intermediate_cert;
next_step.trust_store_index = trust_store->certs.size();
next_step.intermediate_cert_index = i + 1;
next_issuer = intermediate_cert;
break;
}
}
}
intermediate_cert_index = 0;
if (!next_issuer) {
if (path_index == first_index) {
// There are no more paths to try. Ensure an error is returned.
if (last_error == Error::Code::kNone) {
OSP_DVLOG << "FindCertificatePath: Failed after trying all "
"certificate paths, no matches";
return Error::Code::kErrCertsVerifyUntrustedCert;
}
return last_error;
} else {
CertPathStep& last_step = path[path_index++];
trust_store_index = last_step.trust_store_index;
intermediate_cert_index = last_step.intermediate_cert_index;
continue;
}
}
if (path_cert_in_trust_store) {
last_error = VerifyCertificateChain(path, path_index, time);
if (last_error != Error::Code::kNone) {
CertPathStep& last_step = path[path_index++];
trust_store_index = last_step.trust_store_index;
intermediate_cert_index = last_step.intermediate_cert_index;
path_cert_in_trust_store = false;
} else {
break;
}
}
path_head = next_issuer;
}
result_path->path.reserve(path.size() - path_index);
for (uint32_t i = path_index; i < path.size(); ++i) {
result_path->path.push_back(path[i].cert);
}
OSP_DVLOG
<< "FindCertificatePath: Succeeded at validating receiver certificates";
return Error::Code::kNone;
}
} // namespace cast
} // namespace openscreen