import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,83 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef mozilla_pkix_ScopedPtr_h
#define mozilla_pkix_ScopedPtr_h
namespace mozilla { namespace pkix {
// A subset polyfill of std::unique_ptr that does not support move construction
// or move assignment. This is used instead of std::unique_ptr because some
// important toolchains still don't provide std::unique_ptr, including in
// particular Android NDK projects with APP_STL=stlport_static or
// ALL_STL=stlport_shared.
template <typename T, void (&Destroyer)(T*)>
class ScopedPtr final
{
public:
explicit ScopedPtr(T* value = nullptr) : mValue(value) { }
ScopedPtr(const ScopedPtr&) = delete;
~ScopedPtr()
{
if (mValue) {
Destroyer(mValue);
}
}
void operator=(const ScopedPtr&) = delete;
T& operator*() const { return *mValue; }
T* operator->() const { return mValue; }
explicit operator bool() const { return mValue; }
T* get() const { return mValue; }
T* release()
{
T* result = mValue;
mValue = nullptr;
return result;
}
void reset(T* newValue = nullptr)
{
// The C++ standard requires std::unique_ptr to destroy the old value
// pointed to by mValue, if any, *after* assigning the new value to mValue.
T* oldValue = mValue;
mValue = newValue;
if (oldValue) {
Destroyer(oldValue);
}
}
private:
T* mValue;
};
} } // namespace mozilla::pkix
#endif // mozilla_pkix_ScopedPtr_h

View file

@ -0,0 +1,382 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkix/pkix.h"
#include "pkixcheck.h"
#include "pkixutil.h"
namespace mozilla { namespace pkix {
static Result BuildForward(TrustDomain& trustDomain,
const BackCert& subject,
Time time,
KeyUsage requiredKeyUsageIfPresent,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const Input* stapledOCSPResponse,
unsigned int subCACount);
TrustDomain::IssuerChecker::IssuerChecker() { }
TrustDomain::IssuerChecker::~IssuerChecker() { }
// The implementation of TrustDomain::IssuerTracker is in a subclass only to
// hide the implementation from external users.
class PathBuildingStep final : public TrustDomain::IssuerChecker
{
public:
PathBuildingStep(TrustDomain& trustDomain, const BackCert& subject,
Time time, KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const Input* stapledOCSPResponse,
unsigned int subCACount, Result deferredSubjectError)
: trustDomain(trustDomain)
, subject(subject)
, time(time)
, requiredEKUIfPresent(requiredEKUIfPresent)
, requiredPolicy(requiredPolicy)
, stapledOCSPResponse(stapledOCSPResponse)
, subCACount(subCACount)
, deferredSubjectError(deferredSubjectError)
, result(Result::FATAL_ERROR_LIBRARY_FAILURE)
, resultWasSet(false)
{
}
Result Check(Input potentialIssuerDER,
/*optional*/ const Input* additionalNameConstraints,
/*out*/ bool& keepGoing) override;
Result CheckResult() const;
private:
TrustDomain& trustDomain;
const BackCert& subject;
const Time time;
const KeyPurposeId requiredEKUIfPresent;
const CertPolicyId& requiredPolicy;
/*optional*/ Input const* const stapledOCSPResponse;
const unsigned int subCACount;
const Result deferredSubjectError;
// Initialized lazily.
uint8_t subjectSignatureDigestBuf[MAX_DIGEST_SIZE_IN_BYTES];
der::PublicKeyAlgorithm subjectSignaturePublicKeyAlg;
SignedDigest subjectSignature;
Result RecordResult(Result currentResult, /*out*/ bool& keepGoing);
Result result;
bool resultWasSet;
PathBuildingStep(const PathBuildingStep&) = delete;
void operator=(const PathBuildingStep&) = delete;
};
Result
PathBuildingStep::RecordResult(Result newResult, /*out*/ bool& keepGoing)
{
if (newResult == Result::ERROR_UNTRUSTED_CERT) {
newResult = Result::ERROR_UNTRUSTED_ISSUER;
} else if (newResult == Result::ERROR_EXPIRED_CERTIFICATE) {
newResult = Result::ERROR_EXPIRED_ISSUER_CERTIFICATE;
} else if (newResult == Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
newResult = Result::ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE;
}
if (resultWasSet) {
if (result == Success) {
return NotReached("RecordResult called after finding a chain",
Result::FATAL_ERROR_INVALID_STATE);
}
// If every potential issuer has the same problem (e.g. expired) and/or if
// there is only one bad potential issuer, then return a more specific
// error. Otherwise, punt on trying to decide which error should be
// returned by returning the generic Result::ERROR_UNKNOWN_ISSUER error.
if (newResult != Success && newResult != result) {
newResult = Result::ERROR_UNKNOWN_ISSUER;
}
}
result = newResult;
resultWasSet = true;
keepGoing = result != Success;
return Success;
}
Result
PathBuildingStep::CheckResult() const
{
if (!resultWasSet) {
return Result::ERROR_UNKNOWN_ISSUER;
}
return result;
}
// The code that executes in the inner loop of BuildForward
Result
PathBuildingStep::Check(Input potentialIssuerDER,
/*optional*/ const Input* additionalNameConstraints,
/*out*/ bool& keepGoing)
{
BackCert potentialIssuer(potentialIssuerDER, EndEntityOrCA::MustBeCA,
&subject);
Result rv = potentialIssuer.Init();
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
// Simple TrustDomain::FindIssuers implementations may pass in all possible
// CA certificates without any filtering. Because of this, we don't consider
// a mismatched name to be an error. Instead, we just pretend that any
// certificate without a matching name was never passed to us. In particular,
// we treat the case where the TrustDomain only asks us to check CA
// certificates with mismatched names as equivalent to the case where the
// TrustDomain never called Check() at all.
if (!InputsAreEqual(potentialIssuer.GetSubject(), subject.GetIssuer())) {
keepGoing = true;
return Success;
}
// Loop prevention, done as recommended by RFC4158 Section 5.2
// TODO: this doesn't account for subjectAltNames!
// TODO(perf): This probably can and should be optimized in some way.
bool loopDetected = false;
for (const BackCert* prev = potentialIssuer.childCert;
!loopDetected && prev != nullptr; prev = prev->childCert) {
if (InputsAreEqual(potentialIssuer.GetSubjectPublicKeyInfo(),
prev->GetSubjectPublicKeyInfo()) &&
InputsAreEqual(potentialIssuer.GetSubject(), prev->GetSubject())) {
// XXX: error code
return RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
}
}
if (potentialIssuer.GetNameConstraints()) {
rv = CheckNameConstraints(*potentialIssuer.GetNameConstraints(),
subject, requiredEKUIfPresent);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
}
if (additionalNameConstraints) {
rv = CheckNameConstraints(*additionalNameConstraints, subject,
requiredEKUIfPresent);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
}
rv = CheckTLSFeatures(subject, potentialIssuer);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
// RFC 5280, Section 4.2.1.3: "If the keyUsage extension is present, then the
// subject public key MUST NOT be used to verify signatures on certificates
// or CRLs unless the corresponding keyCertSign or cRLSign bit is set."
rv = BuildForward(trustDomain, potentialIssuer, time, KeyUsage::keyCertSign,
requiredEKUIfPresent, requiredPolicy, nullptr, subCACount);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
// Calculate the digest of the subject's signed data if we haven't already
// done so. We do this lazily to avoid doing it at all if we backtrack before
// getting to this point. We cache the result to avoid recalculating it if we
// backtrack after getting to this point.
if (subjectSignature.digest.GetLength() == 0) {
rv = DigestSignedData(trustDomain, subject.GetSignedData(),
subjectSignatureDigestBuf,
subjectSignaturePublicKeyAlg, subjectSignature);
if (rv != Success) {
return rv;
}
}
rv = VerifySignedDigest(trustDomain, subjectSignaturePublicKeyAlg,
subjectSignature,
potentialIssuer.GetSubjectPublicKeyInfo());
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
// We avoid doing revocation checking for expired certificates because OCSP
// responders are allowed to forget about expired certificates, and many OCSP
// responders return an error when asked for the status of an expired
// certificate.
if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) {
CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
subject.GetSerialNumber());
Time notBefore(Time::uninitialized);
Time notAfter(Time::uninitialized);
// This should never fail. If we're here, we've already parsed the validity
// and checked that the given time is in the certificate's validity period.
rv = ParseValidity(subject.GetValidity(), &notBefore, &notAfter);
if (rv != Success) {
return rv;
}
Duration validityDuration(notAfter, notBefore);
rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
validityDuration, stapledOCSPResponse,
subject.GetAuthorityInfoAccess());
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
const Input* sctExtension = subject.GetSignedCertificateTimestamps();
if (sctExtension) {
Input sctList;
rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
sctList);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
trustDomain.NoteAuxiliaryExtension(AuxiliaryExtension::EmbeddedSCTList,
sctList);
}
}
}
return RecordResult(Success, keepGoing);
}
// Recursively build the path from the given subject certificate to the root.
//
// Be very careful about changing the order of checks. The order is significant
// because it affects which error we return when a certificate or certificate
// chain has multiple problems. See the error ranking documentation in
// pkix/pkix.h.
static Result
BuildForward(TrustDomain& trustDomain,
const BackCert& subject,
Time time,
KeyUsage requiredKeyUsageIfPresent,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const Input* stapledOCSPResponse,
unsigned int subCACount)
{
Result rv;
TrustLevel trustLevel;
// If this is an end-entity and not a trust anchor, we defer reporting
// any error found here until after attempting to find a valid chain.
// See the explanation of error prioritization in pkix.h.
rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
requiredKeyUsageIfPresent,
requiredEKUIfPresent, requiredPolicy,
subCACount, trustLevel);
Result deferredEndEntityError = Success;
if (rv != Success) {
if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
trustLevel != TrustLevel::TrustAnchor) {
deferredEndEntityError = rv;
} else {
return rv;
}
}
if (trustLevel == TrustLevel::TrustAnchor) {
// End of the recursion.
NonOwningDERArray chain;
for (const BackCert* cert = &subject; cert; cert = cert->childCert) {
rv = chain.Append(cert->GetDER());
if (rv != Success) {
return NotReached("NonOwningDERArray::SetItem failed.", rv);
}
}
// This must be done here, after the chain is built but before any
// revocation checks have been done.
return trustDomain.IsChainValid(chain, time);
}
if (subject.endEntityOrCA == EndEntityOrCA::MustBeCA) {
// Avoid stack overflows and poor performance by limiting cert chain
// length.
static const unsigned int MAX_SUBCA_COUNT = 6;
static_assert(1/*end-entity*/ + MAX_SUBCA_COUNT + 1/*root*/ ==
NonOwningDERArray::MAX_LENGTH,
"MAX_SUBCA_COUNT and NonOwningDERArray::MAX_LENGTH mismatch.");
if (subCACount >= MAX_SUBCA_COUNT) {
return Result::ERROR_UNKNOWN_ISSUER;
}
++subCACount;
} else {
assert(subCACount == 0);
}
// Find a trusted issuer.
PathBuildingStep pathBuilder(trustDomain, subject, time,
requiredEKUIfPresent, requiredPolicy,
stapledOCSPResponse, subCACount,
deferredEndEntityError);
// TODO(bug 965136): Add SKI/AKI matching optimizations
rv = trustDomain.FindIssuer(subject.GetIssuer(), pathBuilder, time);
if (rv != Success) {
return rv;
}
rv = pathBuilder.CheckResult();
if (rv != Success) {
return rv;
}
// If we found a valid chain but deferred reporting an error with the
// end-entity certificate, report it now.
if (deferredEndEntityError != Success) {
return deferredEndEntityError;
}
// We've built a valid chain from the subject cert up to a trusted root.
return Success;
}
Result
BuildCertChain(TrustDomain& trustDomain, Input certDER,
Time time, EndEntityOrCA endEntityOrCA,
KeyUsage requiredKeyUsageIfPresent,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const Input* stapledOCSPResponse)
{
// XXX: Support the legacy use of the subject CN field for indicating the
// domain name the certificate is valid for.
BackCert cert(certDER, endEntityOrCA, nullptr);
Result rv = cert.Init();
if (rv != Success) {
return rv;
}
return BuildForward(trustDomain, cert, time, requiredKeyUsageIfPresent,
requiredEKUIfPresent, requiredPolicy, stapledOCSPResponse,
0/*subCACount*/);
}
} } // namespace mozilla::pkix

View file

@ -0,0 +1,323 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2014 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkixutil.h"
namespace mozilla { namespace pkix {
Result
BackCert::Init()
{
Result rv;
// Certificate ::= SEQUENCE {
// tbsCertificate TBSCertificate,
// signatureAlgorithm AlgorithmIdentifier,
// signatureValue BIT STRING }
Reader tbsCertificate;
// The scope of |input| and |certificate| are limited to this block so we
// don't accidentally confuse them for tbsCertificate later.
{
Reader certificate;
rv = der::ExpectTagAndGetValueAtEnd(der, der::SEQUENCE, certificate);
if (rv != Success) {
return rv;
}
rv = der::SignedData(certificate, tbsCertificate, signedData);
if (rv != Success) {
return rv;
}
rv = der::End(certificate);
if (rv != Success) {
return rv;
}
}
// TBSCertificate ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// serialNumber CertificateSerialNumber,
// signature AlgorithmIdentifier,
// issuer Name,
// validity Validity,
// subject Name,
// subjectPublicKeyInfo SubjectPublicKeyInfo,
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
// -- If present, version MUST be v2 or v3
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
// -- If present, version MUST be v2 or v3
// extensions [3] EXPLICIT Extensions OPTIONAL
// -- If present, version MUST be v3
// }
rv = der::OptionalVersion(tbsCertificate, version);
if (rv != Success) {
return rv;
}
rv = der::CertificateSerialNumber(tbsCertificate, serialNumber);
if (rv != Success) {
return rv;
}
rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, signature);
if (rv != Success) {
return rv;
}
rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, issuer);
if (rv != Success) {
return rv;
}
rv = der::ExpectTagAndGetValue(tbsCertificate, der::SEQUENCE, validity);
if (rv != Success) {
return rv;
}
// TODO(bug XXXXXXX): We rely on the the caller of mozilla::pkix to validate
// that the name is syntactically valid, if they care. In Gecko we do this
// implicitly by parsing the certificate into a CERTCertificate object.
// Instead of relying on the caller to do this, we should do it ourselves.
rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE, subject);
if (rv != Success) {
return rv;
}
rv = der::ExpectTagAndGetTLV(tbsCertificate, der::SEQUENCE,
subjectPublicKeyInfo);
if (rv != Success) {
return rv;
}
static const uint8_t CSC = der::CONTEXT_SPECIFIC | der::CONSTRUCTED;
// According to RFC 5280, all fields below this line are forbidden for
// certificate versions less than v3. However, for compatibility reasons,
// we parse v1/v2 certificates in the same way as v3 certificates. So if
// these fields appear in a v1 certificate, they will be used.
// Ignore issuerUniqueID if present.
if (tbsCertificate.Peek(CSC | 1)) {
rv = der::ExpectTagAndSkipValue(tbsCertificate, CSC | 1);
if (rv != Success) {
return rv;
}
}
// Ignore subjectUniqueID if present.
if (tbsCertificate.Peek(CSC | 2)) {
rv = der::ExpectTagAndSkipValue(tbsCertificate, CSC | 2);
if (rv != Success) {
return rv;
}
}
rv = der::OptionalExtensions(
tbsCertificate, CSC | 3,
[this](Reader& extnID, const Input& extnValue, bool critical,
/*out*/ bool& understood) {
return RememberExtension(extnID, extnValue, critical, understood);
});
if (rv != Success) {
return rv;
}
// The Netscape Certificate Type extension is an obsolete
// Netscape-proprietary mechanism that we ignore in favor of the standard
// extensions. However, some CAs have issued certificates with the Netscape
// Cert Type extension marked critical. Thus, for compatibility reasons, we
// "understand" this extension by ignoring it when it is not critical, and
// by ensuring that the equivalent standardized extensions are present when
// it is marked critical, based on the assumption that the information in
// the Netscape Cert Type extension is consistent with the information in
// the standard extensions.
//
// Here is a mapping between the Netscape Cert Type extension and the
// standard extensions:
//
// Netscape Cert Type | BasicConstraints.cA | Extended Key Usage
// --------------------+-----------------------+----------------------
// SSL Server | false | id_kp_serverAuth
// SSL Client | false | id_kp_clientAuth
// S/MIME Client | false | id_kp_emailProtection
// Object Signing | false | id_kp_codeSigning
// SSL Server CA | true | id_pk_serverAuth
// SSL Client CA | true | id_kp_clientAuth
// S/MIME CA | true | id_kp_emailProtection
// Object Signing CA | true | id_kp_codeSigning
if (criticalNetscapeCertificateType.GetLength() > 0 &&
(basicConstraints.GetLength() == 0 || extKeyUsage.GetLength() == 0)) {
return Result::ERROR_UNKNOWN_CRITICAL_EXTENSION;
}
return der::End(tbsCertificate);
}
Result
BackCert::RememberExtension(Reader& extnID, Input extnValue,
bool critical, /*out*/ bool& understood)
{
understood = false;
// python DottedOIDToCode.py id-ce-keyUsage 2.5.29.15
static const uint8_t id_ce_keyUsage[] = {
0x55, 0x1d, 0x0f
};
// python DottedOIDToCode.py id-ce-subjectAltName 2.5.29.17
static const uint8_t id_ce_subjectAltName[] = {
0x55, 0x1d, 0x11
};
// python DottedOIDToCode.py id-ce-basicConstraints 2.5.29.19
static const uint8_t id_ce_basicConstraints[] = {
0x55, 0x1d, 0x13
};
// python DottedOIDToCode.py id-ce-nameConstraints 2.5.29.30
static const uint8_t id_ce_nameConstraints[] = {
0x55, 0x1d, 0x1e
};
// python DottedOIDToCode.py id-ce-certificatePolicies 2.5.29.32
static const uint8_t id_ce_certificatePolicies[] = {
0x55, 0x1d, 0x20
};
// python DottedOIDToCode.py id-ce-policyConstraints 2.5.29.36
static const uint8_t id_ce_policyConstraints[] = {
0x55, 0x1d, 0x24
};
// python DottedOIDToCode.py id-ce-extKeyUsage 2.5.29.37
static const uint8_t id_ce_extKeyUsage[] = {
0x55, 0x1d, 0x25
};
// python DottedOIDToCode.py id-ce-inhibitAnyPolicy 2.5.29.54
static const uint8_t id_ce_inhibitAnyPolicy[] = {
0x55, 0x1d, 0x36
};
// python DottedOIDToCode.py id-pe-authorityInfoAccess 1.3.6.1.5.5.7.1.1
static const uint8_t id_pe_authorityInfoAccess[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
};
// python DottedOIDToCode.py id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
static const uint8_t id_pkix_ocsp_nocheck[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
};
// python DottedOIDToCode.py Netscape-certificate-type 2.16.840.1.113730.1.1
static const uint8_t Netscape_certificate_type[] = {
0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01
};
// python DottedOIDToCode.py id-pe-tlsfeature 1.3.6.1.5.5.7.1.24
static const uint8_t id_pe_tlsfeature[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
};
// python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
// See Section 3.3 of RFC 6962.
static const uint8_t id_embeddedSctList[] = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
};
Input* out = nullptr;
// We already enforce the maximum possible constraints for policies so we
// can safely ignore even critical policy constraint extensions.
//
// XXX: Doing it this way won't allow us to detect duplicate
// policyConstraints extensions, but that's OK because (and only because) we
// ignore the extension.
Input dummyPolicyConstraints;
// We don't need to save the contents of this extension if it is present. We
// just need to handle its presence (it is essentially ignored right now).
Input dummyOCSPNocheck;
// For compatibility reasons, for some extensions we have to allow empty
// extension values. This would normally interfere with our duplicate
// extension checking code. However, as long as the extensions we allow to
// have empty values are also the ones we implicitly allow duplicates of,
// this will work fine.
bool emptyValueAllowed = false;
// RFC says "Conforming CAs MUST mark this extension as non-critical" for
// both authorityKeyIdentifier and subjectKeyIdentifier, and we do not use
// them for anything, so we totally ignore them here.
if (extnID.MatchRest(id_ce_keyUsage)) {
out = &keyUsage;
} else if (extnID.MatchRest(id_ce_subjectAltName)) {
out = &subjectAltName;
} else if (extnID.MatchRest(id_ce_basicConstraints)) {
out = &basicConstraints;
} else if (extnID.MatchRest(id_ce_nameConstraints)) {
out = &nameConstraints;
} else if (extnID.MatchRest(id_ce_certificatePolicies)) {
out = &certificatePolicies;
} else if (extnID.MatchRest(id_ce_policyConstraints)) {
out = &dummyPolicyConstraints;
} else if (extnID.MatchRest(id_ce_extKeyUsage)) {
out = &extKeyUsage;
} else if (extnID.MatchRest(id_ce_inhibitAnyPolicy)) {
out = &inhibitAnyPolicy;
} else if (extnID.MatchRest(id_pe_authorityInfoAccess)) {
out = &authorityInfoAccess;
} else if (extnID.MatchRest(id_pe_tlsfeature)) {
out = &requiredTLSFeatures;
} else if (extnID.MatchRest(id_embeddedSctList)) {
out = &signedCertificateTimestamps;
} else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
// We need to make sure we don't reject delegated OCSP response signing
// certificates that contain the id-pkix-ocsp-nocheck extension marked as
// critical when validating OCSP responses. Without this, an application
// that implements soft-fail OCSP might ignore a valid Revoked or Unknown
// response, and an application that implements hard-fail OCSP might fail
// to connect to a server given a valid Good response.
out = &dummyOCSPNocheck;
// We allow this extension to have an empty value.
// See http://comments.gmane.org/gmane.ietf.x509/30947
emptyValueAllowed = true;
} else if (extnID.MatchRest(Netscape_certificate_type) && critical) {
out = &criticalNetscapeCertificateType;
}
if (out) {
// Don't allow an empty value for any extension we understand. This way, we
// can test out->GetLength() != 0 or out->Init() to check for duplicates.
if (extnValue.GetLength() == 0 && !emptyValueAllowed) {
return Result::ERROR_EXTENSION_VALUE_INVALID;
}
if (out->Init(extnValue) != Success) {
// Duplicate extension
return Result::ERROR_EXTENSION_VALUE_INVALID;
}
understood = true;
}
return Success;
}
Result
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
Input& sctList)
{
Reader decodedValue;
Result rv = der::ExpectTagAndGetValueAtEnd(extnValue, der::OCTET_STRING,
decodedValue);
if (rv != Success) {
return rv;
}
return decodedValue.SkipToEnd(sctList);
}
} } // namespace mozilla::pkix

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef mozilla_pkix_pkixcheck_h
#define mozilla_pkix_pkixcheck_h
#include "pkix/pkixtypes.h"
namespace mozilla { namespace pkix {
class BackCert;
Result CheckIssuerIndependentProperties(
TrustDomain& trustDomain,
const BackCert& cert,
Time time,
KeyUsage requiredKeyUsageIfPresent,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
unsigned int subCACount,
/*out*/ TrustLevel& trustLevel);
Result CheckNameConstraints(Input encodedNameConstraints,
const BackCert& firstChild,
KeyPurposeId requiredEKUIfPresent);
Result CheckIssuer(Input encodedIssuer);
// ParseValidity and CheckValidity are usually used together. First you parse
// the dates from the DER Validity sequence, then you compare them to the time
// at which you are validating. They are separate so that the notBefore and
// notAfter times can be used for other things before they are checked against
// the time of validation.
Result ParseValidity(Input encodedValidity,
/*optional out*/ Time* notBeforeOut = nullptr,
/*optional out*/ Time* notAfterOut = nullptr);
Result CheckValidity(Time time, Time notBefore, Time notAfter);
// Check that a subject has TLS Feature (rfc7633) requirements that match its
// potential issuer
Result CheckTLSFeatures(const BackCert& subject, BackCert& potentialIssuer);
} } // namespace mozilla::pkix
#endif // mozilla_pkix_pkixcheck_h

View file

@ -0,0 +1,612 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkixder.h"
#include "pkixutil.h"
namespace mozilla { namespace pkix { namespace der {
// Too complicated to be inline
Result
ReadTagAndGetValue(Reader& input, /*out*/ uint8_t& tag, /*out*/ Input& value)
{
Result rv;
rv = input.Read(tag);
if (rv != Success) {
return rv;
}
if ((tag & 0x1F) == 0x1F) {
return Result::ERROR_BAD_DER; // high tag number form not allowed
}
uint16_t length;
// The short form of length is a single byte with the high order bit set
// to zero. The long form of length is one byte with the high order bit
// set, followed by N bytes, where N is encoded in the lowest 7 bits of
// the first byte.
uint8_t length1;
rv = input.Read(length1);
if (rv != Success) {
return rv;
}
if (!(length1 & 0x80)) {
length = length1;
} else if (length1 == 0x81) {
uint8_t length2;
rv = input.Read(length2);
if (rv != Success) {
return rv;
}
if (length2 < 128) {
// Not shortest possible encoding
return Result::ERROR_BAD_DER;
}
length = length2;
} else if (length1 == 0x82) {
rv = input.Read(length);
if (rv != Success) {
return rv;
}
if (length < 256) {
// Not shortest possible encoding
return Result::ERROR_BAD_DER;
}
} else {
// We don't support lengths larger than 2^16 - 1.
return Result::ERROR_BAD_DER;
}
return input.Skip(length, value);
}
static Result
OptionalNull(Reader& input)
{
if (input.Peek(NULLTag)) {
return Null(input);
}
return Success;
}
namespace {
Result
AlgorithmIdentifierValue(Reader& input, /*out*/ Reader& algorithmOIDValue)
{
Result rv = ExpectTagAndGetValue(input, der::OIDTag, algorithmOIDValue);
if (rv != Success) {
return rv;
}
return OptionalNull(input);
}
} // namespace
Result
SignatureAlgorithmIdentifierValue(Reader& input,
/*out*/ PublicKeyAlgorithm& publicKeyAlgorithm,
/*out*/ DigestAlgorithm& digestAlgorithm)
{
// RFC 5758 Section 3.2 (ECDSA with SHA-2), and RFC 3279 Section 2.2.3
// (ECDSA with SHA-1) say that parameters must be omitted.
//
// RFC 4055 Section 5 and RFC 3279 Section 2.2.1 both say that parameters for
// RSA must be encoded as NULL; we relax that requirement by allowing the
// NULL to be omitted, to match all the other signature algorithms we support
// and for compatibility.
Reader algorithmID;
Result rv = AlgorithmIdentifierValue(input, algorithmID);
if (rv != Success) {
return rv;
}
// RFC 5758 Section 3.2 (ecdsa-with-SHA224 is intentionally excluded)
// python DottedOIDToCode.py ecdsa-with-SHA256 1.2.840.10045.4.3.2
static const uint8_t ecdsa_with_SHA256[] = {
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02
};
// python DottedOIDToCode.py ecdsa-with-SHA384 1.2.840.10045.4.3.3
static const uint8_t ecdsa_with_SHA384[] = {
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03
};
// python DottedOIDToCode.py ecdsa-with-SHA512 1.2.840.10045.4.3.4
static const uint8_t ecdsa_with_SHA512[] = {
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
};
// RFC 4055 Section 5 (sha224WithRSAEncryption is intentionally excluded)
// python DottedOIDToCode.py sha256WithRSAEncryption 1.2.840.113549.1.1.11
static const uint8_t sha256WithRSAEncryption[] = {
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
};
// python DottedOIDToCode.py sha384WithRSAEncryption 1.2.840.113549.1.1.12
static const uint8_t sha384WithRSAEncryption[] = {
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c
};
// python DottedOIDToCode.py sha512WithRSAEncryption 1.2.840.113549.1.1.13
static const uint8_t sha512WithRSAEncryption[] = {
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d
};
// RFC 3279 Section 2.2.1
// python DottedOIDToCode.py sha-1WithRSAEncryption 1.2.840.113549.1.1.5
static const uint8_t sha_1WithRSAEncryption[] = {
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
};
// NIST Open Systems Environment (OSE) Implementor's Workshop (OIW)
// http://www.oiw.org/agreements/stable/12s-9412.txt (no longer works).
// http://www.imc.org/ietf-pkix/old-archive-97/msg01166.html
// We need to support this this non-PKIX OID for compatibility.
// python DottedOIDToCode.py sha1WithRSASignature 1.3.14.3.2.29
static const uint8_t sha1WithRSASignature[] = {
0x2b, 0x0e, 0x03, 0x02, 0x1d
};
// RFC 3279 Section 2.2.3
// python DottedOIDToCode.py ecdsa-with-SHA1 1.2.840.10045.4.1
static const uint8_t ecdsa_with_SHA1[] = {
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01
};
// Matching is attempted based on a rough estimate of the commonality of the
// algorithm, to minimize the number of MatchRest calls.
if (algorithmID.MatchRest(sha256WithRSAEncryption)) {
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
digestAlgorithm = DigestAlgorithm::sha256;
} else if (algorithmID.MatchRest(ecdsa_with_SHA256)) {
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
digestAlgorithm = DigestAlgorithm::sha256;
} else if (algorithmID.MatchRest(sha_1WithRSAEncryption)) {
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
digestAlgorithm = DigestAlgorithm::sha1;
} else if (algorithmID.MatchRest(ecdsa_with_SHA1)) {
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
digestAlgorithm = DigestAlgorithm::sha1;
} else if (algorithmID.MatchRest(ecdsa_with_SHA384)) {
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
digestAlgorithm = DigestAlgorithm::sha384;
} else if (algorithmID.MatchRest(ecdsa_with_SHA512)) {
publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
digestAlgorithm = DigestAlgorithm::sha512;
} else if (algorithmID.MatchRest(sha384WithRSAEncryption)) {
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
digestAlgorithm = DigestAlgorithm::sha384;
} else if (algorithmID.MatchRest(sha512WithRSAEncryption)) {
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
digestAlgorithm = DigestAlgorithm::sha512;
} else if (algorithmID.MatchRest(sha1WithRSASignature)) {
// XXX(bug 1042479): recognize this old OID for compatibility.
publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
digestAlgorithm = DigestAlgorithm::sha1;
} else {
return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
}
return Success;
}
Result
DigestAlgorithmIdentifier(Reader& input, /*out*/ DigestAlgorithm& algorithm)
{
Reader r;
return der::Nested(input, SEQUENCE, [&algorithm](Reader& r) -> Result {
Reader algorithmID;
Result rv = AlgorithmIdentifierValue(r, algorithmID);
if (rv != Success) {
return rv;
}
// RFC 4055 Section 2.1
// python DottedOIDToCode.py id-sha1 1.3.14.3.2.26
static const uint8_t id_sha1[] = {
0x2b, 0x0e, 0x03, 0x02, 0x1a
};
// python DottedOIDToCode.py id-sha256 2.16.840.1.101.3.4.2.1
static const uint8_t id_sha256[] = {
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
};
// python DottedOIDToCode.py id-sha384 2.16.840.1.101.3.4.2.2
static const uint8_t id_sha384[] = {
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02
};
// python DottedOIDToCode.py id-sha512 2.16.840.1.101.3.4.2.3
static const uint8_t id_sha512[] = {
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03
};
// Matching is attempted based on a rough estimate of the commonality of the
// algorithm, to minimize the number of MatchRest calls.
if (algorithmID.MatchRest(id_sha1)) {
algorithm = DigestAlgorithm::sha1;
} else if (algorithmID.MatchRest(id_sha256)) {
algorithm = DigestAlgorithm::sha256;
} else if (algorithmID.MatchRest(id_sha384)) {
algorithm = DigestAlgorithm::sha384;
} else if (algorithmID.MatchRest(id_sha512)) {
algorithm = DigestAlgorithm::sha512;
} else {
return Result::ERROR_INVALID_ALGORITHM;
}
return Success;
});
}
Result
SignedData(Reader& input, /*out*/ Reader& tbs,
/*out*/ SignedDataWithSignature& signedData)
{
Reader::Mark mark(input.GetMark());
Result rv;
rv = ExpectTagAndGetValue(input, SEQUENCE, tbs);
if (rv != Success) {
return rv;
}
rv = input.GetInput(mark, signedData.data);
if (rv != Success) {
return rv;
}
rv = ExpectTagAndGetValue(input, der::SEQUENCE, signedData.algorithm);
if (rv != Success) {
return rv;
}
rv = BitStringWithNoUnusedBits(input, signedData.signature);
if (rv == Result::ERROR_BAD_DER) {
rv = Result::ERROR_BAD_SIGNATURE;
}
return rv;
}
Result
BitStringWithNoUnusedBits(Reader& input, /*out*/ Input& value)
{
Reader valueWithUnusedBits;
Result rv = ExpectTagAndGetValue(input, BIT_STRING, valueWithUnusedBits);
if (rv != Success) {
return rv;
}
uint8_t unusedBitsAtEnd;
if (valueWithUnusedBits.Read(unusedBitsAtEnd) != Success) {
return Result::ERROR_BAD_DER;
}
// XXX: Really the constraint should be that unusedBitsAtEnd must be less
// than 7. But, we suspect there are no real-world values in OCSP responses
// or certificates with non-zero unused bits. It seems like NSS assumes this
// in various places, so we enforce it too in order to simplify this code. If
// we find compatibility issues, we'll know we're wrong and we'll have to
// figure out how to shift the bits around.
if (unusedBitsAtEnd != 0) {
return Result::ERROR_BAD_DER;
}
return valueWithUnusedBits.SkipToEnd(value);
}
static inline Result
ReadDigit(Reader& input, /*out*/ unsigned int& value)
{
uint8_t b;
if (input.Read(b) != Success) {
return Result::ERROR_INVALID_DER_TIME;
}
if (b < '0' || b > '9') {
return Result::ERROR_INVALID_DER_TIME;
}
value = static_cast<unsigned int>(b - static_cast<uint8_t>('0'));
return Success;
}
static inline Result
ReadTwoDigits(Reader& input, unsigned int minValue, unsigned int maxValue,
/*out*/ unsigned int& value)
{
unsigned int hi;
Result rv = ReadDigit(input, hi);
if (rv != Success) {
return rv;
}
unsigned int lo;
rv = ReadDigit(input, lo);
if (rv != Success) {
return rv;
}
value = (hi * 10) + lo;
if (value < minValue || value > maxValue) {
return Result::ERROR_INVALID_DER_TIME;
}
return Success;
}
namespace internal {
// We parse GeneralizedTime and UTCTime according to RFC 5280 and we do not
// accept all time formats allowed in the ASN.1 spec. That is,
// GeneralizedTime must always be in the format YYYYMMDDHHMMSSZ and UTCTime
// must always be in the format YYMMDDHHMMSSZ. Timezone formats of the form
// +HH:MM or -HH:MM or NOT accepted.
Result
TimeChoice(Reader& tagged, uint8_t expectedTag, /*out*/ Time& time)
{
unsigned int days;
Reader input;
Result rv = ExpectTagAndGetValue(tagged, expectedTag, input);
if (rv != Success) {
return rv;
}
unsigned int yearHi;
unsigned int yearLo;
if (expectedTag == GENERALIZED_TIME) {
rv = ReadTwoDigits(input, 0, 99, yearHi);
if (rv != Success) {
return rv;
}
rv = ReadTwoDigits(input, 0, 99, yearLo);
if (rv != Success) {
return rv;
}
} else if (expectedTag == UTCTime) {
rv = ReadTwoDigits(input, 0, 99, yearLo);
if (rv != Success) {
return rv;
}
yearHi = yearLo >= 50u ? 19u : 20u;
} else {
return NotReached("invalid tag given to TimeChoice",
Result::ERROR_INVALID_DER_TIME);
}
unsigned int year = (yearHi * 100u) + yearLo;
if (year < 1970u) {
// We don't support dates before January 1, 1970 because that is the epoch.
return Result::ERROR_INVALID_DER_TIME;
}
days = DaysBeforeYear(year);
unsigned int month;
rv = ReadTwoDigits(input, 1u, 12u, month);
if (rv != Success) {
return rv;
}
unsigned int daysInMonth;
static const unsigned int jan = 31u;
const unsigned int feb = ((year % 4u == 0u) &&
((year % 100u != 0u) || (year % 400u == 0u)))
? 29u
: 28u;
static const unsigned int mar = 31u;
static const unsigned int apr = 30u;
static const unsigned int may = 31u;
static const unsigned int jun = 30u;
static const unsigned int jul = 31u;
static const unsigned int aug = 31u;
static const unsigned int sep = 30u;
static const unsigned int oct = 31u;
static const unsigned int nov = 30u;
static const unsigned int dec = 31u;
switch (month) {
case 1: daysInMonth = jan; break;
case 2: daysInMonth = feb; days += jan; break;
case 3: daysInMonth = mar; days += jan + feb; break;
case 4: daysInMonth = apr; days += jan + feb + mar; break;
case 5: daysInMonth = may; days += jan + feb + mar + apr; break;
case 6: daysInMonth = jun; days += jan + feb + mar + apr + may; break;
case 7: daysInMonth = jul; days += jan + feb + mar + apr + may + jun;
break;
case 8: daysInMonth = aug; days += jan + feb + mar + apr + may + jun +
jul;
break;
case 9: daysInMonth = sep; days += jan + feb + mar + apr + may + jun +
jul + aug;
break;
case 10: daysInMonth = oct; days += jan + feb + mar + apr + may + jun +
jul + aug + sep;
break;
case 11: daysInMonth = nov; days += jan + feb + mar + apr + may + jun +
jul + aug + sep + oct;
break;
case 12: daysInMonth = dec; days += jan + feb + mar + apr + may + jun +
jul + aug + sep + oct + nov;
break;
default:
return NotReached("month already bounds-checked by ReadTwoDigits",
Result::FATAL_ERROR_INVALID_STATE);
}
unsigned int dayOfMonth;
rv = ReadTwoDigits(input, 1u, daysInMonth, dayOfMonth);
if (rv != Success) {
return rv;
}
days += dayOfMonth - 1;
unsigned int hours;
rv = ReadTwoDigits(input, 0u, 23u, hours);
if (rv != Success) {
return rv;
}
unsigned int minutes;
rv = ReadTwoDigits(input, 0u, 59u, minutes);
if (rv != Success) {
return rv;
}
unsigned int seconds;
rv = ReadTwoDigits(input, 0u, 59u, seconds);
if (rv != Success) {
return rv;
}
uint8_t b;
if (input.Read(b) != Success) {
return Result::ERROR_INVALID_DER_TIME;
}
if (b != 'Z') {
return Result::ERROR_INVALID_DER_TIME;
}
if (End(input) != Success) {
return Result::ERROR_INVALID_DER_TIME;
}
uint64_t totalSeconds = (static_cast<uint64_t>(days) * 24u * 60u * 60u) +
(static_cast<uint64_t>(hours) * 60u * 60u) +
(static_cast<uint64_t>(minutes) * 60u) +
seconds;
time = TimeFromElapsedSecondsAD(totalSeconds);
return Success;
}
Result
IntegralBytes(Reader& input, uint8_t tag,
IntegralValueRestriction valueRestriction,
/*out*/ Input& value,
/*optional out*/ Input::size_type* significantBytes)
{
Result rv = ExpectTagAndGetValue(input, tag, value);
if (rv != Success) {
return rv;
}
Reader reader(value);
// There must be at least one byte in the value. (Zero is encoded with a
// single 0x00 value byte.)
uint8_t firstByte;
rv = reader.Read(firstByte);
if (rv != Success) {
if (rv == Result::ERROR_BAD_DER) {
return Result::ERROR_INVALID_INTEGER_ENCODING;
}
return rv;
}
// If there is a byte after an initial 0x00/0xFF, then the initial byte
// indicates a positive/negative integer value with its high bit set/unset.
bool prefixed = !reader.AtEnd() && (firstByte == 0 || firstByte == 0xff);
if (prefixed) {
uint8_t nextByte;
if (reader.Read(nextByte) != Success) {
return NotReached("Read of one byte failed but not at end.",
Result::FATAL_ERROR_LIBRARY_FAILURE);
}
if ((firstByte & 0x80) == (nextByte & 0x80)) {
return Result::ERROR_INVALID_INTEGER_ENCODING;
}
}
switch (valueRestriction) {
case IntegralValueRestriction::MustBe0To127:
if (value.GetLength() != 1 || (firstByte & 0x80) != 0) {
return Result::ERROR_INVALID_INTEGER_ENCODING;
}
break;
case IntegralValueRestriction::MustBePositive:
if ((value.GetLength() == 1 && firstByte == 0) ||
(firstByte & 0x80) != 0) {
return Result::ERROR_INVALID_INTEGER_ENCODING;
}
break;
case IntegralValueRestriction::NoRestriction:
break;
}
if (significantBytes) {
*significantBytes = value.GetLength();
if (prefixed) {
assert(*significantBytes > 1);
--*significantBytes;
}
assert(*significantBytes > 0);
}
return Success;
}
// This parser will only parse values between 0..127. If this range is
// increased then callers will need to be changed.
Result
IntegralValue(Reader& input, uint8_t tag, /*out*/ uint8_t& value)
{
// Conveniently, all the Integers that we actually have to be able to parse
// are positive and very small. Consequently, this parser is *much* simpler
// than a general Integer parser would need to be.
Input valueBytes;
Result rv = IntegralBytes(input, tag, IntegralValueRestriction::MustBe0To127,
valueBytes, nullptr);
if (rv != Success) {
return rv;
}
Reader valueReader(valueBytes);
rv = valueReader.Read(value);
if (rv != Success) {
return NotReached("IntegralBytes already validated the value.", rv);
}
rv = End(valueReader);
assert(rv == Success); // guaranteed by IntegralBytes's range checks.
return rv;
}
} // namespace internal
Result
OptionalVersion(Reader& input, /*out*/ Version& version)
{
static const uint8_t TAG = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
if (!input.Peek(TAG)) {
version = Version::v1;
return Success;
}
return Nested(input, TAG, [&version](Reader& value) -> Result {
uint8_t integerValue;
Result rv = Integer(value, integerValue);
if (rv != Success) {
return rv;
}
// XXX(bug 1031093): We shouldn't accept an explicit encoding of v1,
// but we do here for compatibility reasons.
switch (integerValue) {
case static_cast<uint8_t>(Version::v3): version = Version::v3; break;
case static_cast<uint8_t>(Version::v2): version = Version::v2; break;
case static_cast<uint8_t>(Version::v1): version = Version::v1; break;
case static_cast<uint8_t>(Version::v4): version = Version::v4; break;
default:
return Result::ERROR_BAD_DER;
}
return Success;
});
}
} } } // namespace mozilla::pkix::der

565
security/pkix/lib/pkixder.h Normal file
View file

@ -0,0 +1,565 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef mozilla_pkix_pkixder_h
#define mozilla_pkix_pkixder_h
// Expect* functions advance the input mark and return Success if the input
// matches the given criteria; they fail with the input mark in an undefined
// state if the input does not match the criteria.
//
// Match* functions advance the input mark and return true if the input matches
// the given criteria; they return false without changing the input mark if the
// input does not match the criteria.
//
// Skip* functions unconditionally advance the input mark and return Success if
// they are able to do so; otherwise they fail with the input mark in an
// undefined state.
#include "pkix/Input.h"
#include "pkix/pkixtypes.h"
namespace mozilla { namespace pkix { namespace der {
enum Class : uint8_t
{
UNIVERSAL = 0 << 6,
// APPLICATION = 1 << 6, // unused
CONTEXT_SPECIFIC = 2 << 6,
// PRIVATE = 3 << 6 // unused
};
enum Constructed
{
CONSTRUCTED = 1 << 5
};
enum Tag : uint8_t
{
BOOLEAN = UNIVERSAL | 0x01,
INTEGER = UNIVERSAL | 0x02,
BIT_STRING = UNIVERSAL | 0x03,
OCTET_STRING = UNIVERSAL | 0x04,
NULLTag = UNIVERSAL | 0x05,
OIDTag = UNIVERSAL | 0x06,
ENUMERATED = UNIVERSAL | 0x0a,
UTF8String = UNIVERSAL | 0x0c,
SEQUENCE = UNIVERSAL | CONSTRUCTED | 0x10, // 0x30
SET = UNIVERSAL | CONSTRUCTED | 0x11, // 0x31
PrintableString = UNIVERSAL | 0x13,
TeletexString = UNIVERSAL | 0x14,
IA5String = UNIVERSAL | 0x16,
UTCTime = UNIVERSAL | 0x17,
GENERALIZED_TIME = UNIVERSAL | 0x18,
};
enum class EmptyAllowed { No = 0, Yes = 1 };
Result ReadTagAndGetValue(Reader& input, /*out*/ uint8_t& tag,
/*out*/ Input& value);
Result End(Reader& input);
inline Result
ExpectTagAndGetValue(Reader& input, uint8_t tag, /*out*/ Input& value)
{
uint8_t actualTag;
Result rv = ReadTagAndGetValue(input, actualTag, value);
if (rv != Success) {
return rv;
}
if (tag != actualTag) {
return Result::ERROR_BAD_DER;
}
return Success;
}
inline Result
ExpectTagAndGetValue(Reader& input, uint8_t tag, /*out*/ Reader& value)
{
Input valueInput;
Result rv = ExpectTagAndGetValue(input, tag, valueInput);
if (rv != Success) {
return rv;
}
return value.Init(valueInput);
}
inline Result
ExpectTagAndEmptyValue(Reader& input, uint8_t tag)
{
Reader value;
Result rv = ExpectTagAndGetValue(input, tag, value);
if (rv != Success) {
return rv;
}
return End(value);
}
inline Result
ExpectTagAndSkipValue(Reader& input, uint8_t tag)
{
Input ignoredValue;
return ExpectTagAndGetValue(input, tag, ignoredValue);
}
// Like ExpectTagAndGetValue, except the output Input will contain the
// encoded tag and length along with the value.
inline Result
ExpectTagAndGetTLV(Reader& input, uint8_t tag, /*out*/ Input& tlv)
{
Reader::Mark mark(input.GetMark());
Result rv = ExpectTagAndSkipValue(input, tag);
if (rv != Success) {
return rv;
}
return input.GetInput(mark, tlv);
}
inline Result
End(Reader& input)
{
if (!input.AtEnd()) {
return Result::ERROR_BAD_DER;
}
return Success;
}
template <typename Decoder>
inline Result
Nested(Reader& input, uint8_t tag, Decoder decoder)
{
Reader nested;
Result rv = ExpectTagAndGetValue(input, tag, nested);
if (rv != Success) {
return rv;
}
rv = decoder(nested);
if (rv != Success) {
return rv;
}
return End(nested);
}
template <typename Decoder>
inline Result
Nested(Reader& input, uint8_t outerTag, uint8_t innerTag, Decoder decoder)
{
Reader nestedInput;
Result rv = ExpectTagAndGetValue(input, outerTag, nestedInput);
if (rv != Success) {
return rv;
}
rv = Nested(nestedInput, innerTag, decoder);
if (rv != Success) {
return rv;
}
return End(nestedInput);
}
// This can be used to decode constructs like this:
//
// ...
// foos SEQUENCE OF Foo,
// ...
// Foo ::= SEQUENCE {
// }
//
// using code like this:
//
// Result Foo(Reader& r) { /*...*/ }
//
// rv = der::NestedOf(input, der::SEQEUENCE, der::SEQUENCE, Foo);
//
// or:
//
// Result Bar(Reader& r, int value) { /*...*/ }
//
// int value = /*...*/;
//
// rv = der::NestedOf(input, der::SEQUENCE, [value](Reader& r) {
// return Bar(r, value);
// });
//
// In these examples the function will get called once for each element of
// foos.
//
template <typename Decoder>
inline Result
NestedOf(Reader& input, uint8_t outerTag, uint8_t innerTag,
EmptyAllowed mayBeEmpty, Decoder decoder)
{
Reader inner;
Result rv = ExpectTagAndGetValue(input, outerTag, inner);
if (rv != Success) {
return rv;
}
if (inner.AtEnd()) {
if (mayBeEmpty != EmptyAllowed::Yes) {
return Result::ERROR_BAD_DER;
}
return Success;
}
do {
rv = Nested(inner, innerTag, decoder);
if (rv != Success) {
return rv;
}
} while (!inner.AtEnd());
return Success;
}
// Often, a function will need to decode an Input or Reader that contains
// DER-encoded data wrapped in a SEQUENCE (or similar) with nothing after it.
// This function reduces the boilerplate necessary for stripping the outermost
// SEQUENCE (or similar) and ensuring that nothing follows it.
inline Result
ExpectTagAndGetValueAtEnd(Reader& outer, uint8_t expectedTag,
/*out*/ Reader& inner)
{
Result rv = der::ExpectTagAndGetValue(outer, expectedTag, inner);
if (rv != Success) {
return rv;
}
return der::End(outer);
}
// Similar to the above, but takes an Input instead of a Reader&.
inline Result
ExpectTagAndGetValueAtEnd(Input outer, uint8_t expectedTag,
/*out*/ Reader& inner)
{
Reader outerReader(outer);
return ExpectTagAndGetValueAtEnd(outerReader, expectedTag, inner);
}
// Universal types
namespace internal {
enum class IntegralValueRestriction
{
NoRestriction,
MustBePositive,
MustBe0To127,
};
Result IntegralBytes(Reader& input, uint8_t tag,
IntegralValueRestriction valueRestriction,
/*out*/ Input& value,
/*optional out*/ Input::size_type* significantBytes = nullptr);
// This parser will only parse values between 0..127. If this range is
// increased then callers will need to be changed.
Result IntegralValue(Reader& input, uint8_t tag, /*out*/ uint8_t& value);
} // namespace internal
Result
BitStringWithNoUnusedBits(Reader& input, /*out*/ Input& value);
inline Result
Boolean(Reader& input, /*out*/ bool& value)
{
Reader valueReader;
Result rv = ExpectTagAndGetValue(input, BOOLEAN, valueReader);
if (rv != Success) {
return rv;
}
uint8_t intValue;
rv = valueReader.Read(intValue);
if (rv != Success) {
return rv;
}
rv = End(valueReader);
if (rv != Success) {
return rv;
}
switch (intValue) {
case 0: value = false; return Success;
case 0xFF: value = true; return Success;
default:
return Result::ERROR_BAD_DER;
}
}
// This is for BOOLEAN DEFAULT FALSE.
// The standard stipulates that "The encoding of a set value or sequence value
// shall not include an encoding for any component value which is equal to its
// default value." However, it appears to be common that other libraries
// incorrectly include the value of a BOOLEAN even when it's equal to the
// default value, so we allow invalid explicit encodings here.
inline Result
OptionalBoolean(Reader& input, /*out*/ bool& value)
{
value = false;
if (input.Peek(BOOLEAN)) {
Result rv = Boolean(input, value);
if (rv != Success) {
return rv;
}
}
return Success;
}
// This parser will only parse values between 0..127. If this range is
// increased then callers will need to be changed.
inline Result
Enumerated(Reader& input, uint8_t& value)
{
return internal::IntegralValue(input, ENUMERATED | 0, value);
}
namespace internal {
// internal::TimeChoice implements the shared functionality of GeneralizedTime
// and TimeChoice. tag must be either UTCTime or GENERALIZED_TIME.
//
// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
// eliminate the chance for complications in converting times to traditional
// time formats that start at 1970.
Result TimeChoice(Reader& input, uint8_t tag, /*out*/ Time& time);
} // namespace internal
// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
// eliminate the chance for complications in converting times to traditional
// time formats that start at 1970.
inline Result
GeneralizedTime(Reader& input, /*out*/ Time& time)
{
return internal::TimeChoice(input, GENERALIZED_TIME, time);
}
// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
// eliminate the chance for complications in converting times to traditional
// time formats that start at 1970.
inline Result
TimeChoice(Reader& input, /*out*/ Time& time)
{
uint8_t expectedTag = input.Peek(UTCTime) ? UTCTime : GENERALIZED_TIME;
return internal::TimeChoice(input, expectedTag, time);
}
// Parse a DER integer value into value. Empty values, negative values, and
// zero are rejected. If significantBytes is not null, then it will be set to
// the number of significant bytes in the value (the length of the value, less
// the length of any leading padding), which is useful for key size checks.
inline Result
PositiveInteger(Reader& input, /*out*/ Input& value,
/*optional out*/ Input::size_type* significantBytes = nullptr)
{
return internal::IntegralBytes(
input, INTEGER, internal::IntegralValueRestriction::MustBePositive,
value, significantBytes);
}
// This parser will only parse values between 0..127. If this range is
// increased then callers will need to be changed.
inline Result
Integer(Reader& input, /*out*/ uint8_t& value)
{
return internal::IntegralValue(input, INTEGER, value);
}
// This parser will only parse values between 0..127. If this range is
// increased then callers will need to be changed. The default value must be
// -1; defaultValue is only a parameter to make it clear in the calling code
// what the default value is.
inline Result
OptionalInteger(Reader& input, long defaultValue, /*out*/ long& value)
{
// If we need to support a different default value in the future, we need to
// test that parsedValue != defaultValue.
if (defaultValue != -1) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
if (!input.Peek(INTEGER)) {
value = defaultValue;
return Success;
}
uint8_t parsedValue;
Result rv = Integer(input, parsedValue);
if (rv != Success) {
return rv;
}
value = parsedValue;
return Success;
}
inline Result
Null(Reader& input)
{
return ExpectTagAndEmptyValue(input, NULLTag);
}
template <uint8_t Len>
Result
OID(Reader& input, const uint8_t (&expectedOid)[Len])
{
Reader value;
Result rv = ExpectTagAndGetValue(input, OIDTag, value);
if (rv != Success) {
return rv;
}
if (!value.MatchRest(expectedOid)) {
return Result::ERROR_BAD_DER;
}
return Success;
}
// PKI-specific types
inline Result
CertificateSerialNumber(Reader& input, /*out*/ Input& value)
{
// http://tools.ietf.org/html/rfc5280#section-4.1.2.2:
//
// * "The serial number MUST be a positive integer assigned by the CA to
// each certificate."
// * "Certificate users MUST be able to handle serialNumber values up to 20
// octets. Conforming CAs MUST NOT use serialNumber values longer than 20
// octets."
// * "Note: Non-conforming CAs may issue certificates with serial numbers
// that are negative or zero. Certificate users SHOULD be prepared to
// gracefully handle such certificates."
return internal::IntegralBytes(
input, INTEGER, internal::IntegralValueRestriction::NoRestriction,
value);
}
// x.509 and OCSP both use this same version numbering scheme, though OCSP
// only supports v1.
enum class Version { v1 = 0, v2 = 1, v3 = 2, v4 = 3 };
// X.509 Certificate and OCSP ResponseData both use
// "[0] EXPLICIT Version DEFAULT v1". Although an explicit encoding of v1 is
// illegal, we support it because some real-world OCSP responses explicitly
// encode it.
Result OptionalVersion(Reader& input, /*out*/ Version& version);
template <typename ExtensionHandler>
inline Result
OptionalExtensions(Reader& input, uint8_t tag,
ExtensionHandler extensionHandler)
{
if (!input.Peek(tag)) {
return Success;
}
return Nested(input, tag, [extensionHandler](Reader& tagged) {
// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
//
// TODO(bug 997994): According to the specification, there should never be
// an empty sequence of extensions but we've found OCSP responses that have
// that (see bug 991898).
return NestedOf(tagged, SEQUENCE, SEQUENCE, EmptyAllowed::Yes,
[extensionHandler](Reader& extension) -> Result {
// Extension ::= SEQUENCE {
// extnID OBJECT IDENTIFIER,
// critical BOOLEAN DEFAULT FALSE,
// extnValue OCTET STRING
// }
Reader extnID;
Result rv = ExpectTagAndGetValue(extension, OIDTag, extnID);
if (rv != Success) {
return rv;
}
bool critical;
rv = OptionalBoolean(extension, critical);
if (rv != Success) {
return rv;
}
Input extnValue;
rv = ExpectTagAndGetValue(extension, OCTET_STRING, extnValue);
if (rv != Success) {
return rv;
}
bool understood = false;
rv = extensionHandler(extnID, extnValue, critical, understood);
if (rv != Success) {
return rv;
}
if (critical && !understood) {
return Result::ERROR_UNKNOWN_CRITICAL_EXTENSION;
}
return Success;
});
});
}
Result DigestAlgorithmIdentifier(Reader& input,
/*out*/ DigestAlgorithm& algorithm);
enum class PublicKeyAlgorithm
{
RSA_PKCS1,
ECDSA,
};
Result SignatureAlgorithmIdentifierValue(
Reader& input,
/*out*/ PublicKeyAlgorithm& publicKeyAlgorithm,
/*out*/ DigestAlgorithm& digestAlgorithm);
struct SignedDataWithSignature final
{
public:
Input data;
Input algorithm;
Input signature;
void operator=(const SignedDataWithSignature&) = delete;
};
// Parses a SEQUENCE into tbs and then parses an AlgorithmIdentifier followed
// by a BIT STRING into signedData. This handles the commonality between
// parsing the signed/signature fields of certificates and OCSP responses. In
// the case of an OCSP response, the caller needs to parse the certs
// separately.
//
// Note that signatureAlgorithm is NOT parsed or validated.
//
// Certificate ::= SEQUENCE {
// tbsCertificate TBSCertificate,
// signatureAlgorithm AlgorithmIdentifier,
// signatureValue BIT STRING }
//
// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
Result SignedData(Reader& input, /*out*/ Reader& tbs,
/*out*/ SignedDataWithSignature& signedDataWithSignature);
} } } // namespace mozilla::pkix::der
#endif // mozilla_pkix_pkixder_h

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,228 @@
/*- *- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkix/pkixnss.h"
#include <limits>
#include "cryptohi.h"
#include "keyhi.h"
#include "pk11pub.h"
#include "pkix/pkix.h"
#include "pkixutil.h"
#include "ScopedPtr.h"
#include "secerr.h"
#include "sslerr.h"
namespace mozilla { namespace pkix {
namespace {
Result
VerifySignedDigest(const SignedDigest& sd,
Input subjectPublicKeyInfo,
SECOidTag pubKeyAlg,
void* pkcs11PinArg)
{
SECOidTag digestAlg;
switch (sd.digestAlgorithm) {
case DigestAlgorithm::sha512: digestAlg = SEC_OID_SHA512; break;
case DigestAlgorithm::sha384: digestAlg = SEC_OID_SHA384; break;
case DigestAlgorithm::sha256: digestAlg = SEC_OID_SHA256; break;
case DigestAlgorithm::sha1: digestAlg = SEC_OID_SHA1; break;
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
SECItem subjectPublicKeyInfoSECItem =
UnsafeMapInputToSECItem(subjectPublicKeyInfo);
ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo>
spki(SECKEY_DecodeDERSubjectPublicKeyInfo(&subjectPublicKeyInfoSECItem));
if (!spki) {
return MapPRErrorCodeToResult(PR_GetError());
}
ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
pubKey(SECKEY_ExtractPublicKey(spki.get()));
if (!pubKey) {
return MapPRErrorCodeToResult(PR_GetError());
}
SECItem digestSECItem(UnsafeMapInputToSECItem(sd.digest));
SECItem signatureSECItem(UnsafeMapInputToSECItem(sd.signature));
SECStatus srv = VFY_VerifyDigestDirect(&digestSECItem, pubKey.get(),
&signatureSECItem, pubKeyAlg,
digestAlg, pkcs11PinArg);
if (srv != SECSuccess) {
return MapPRErrorCodeToResult(PR_GetError());
}
return Success;
}
} // namespace
Result
VerifyRSAPKCS1SignedDigestNSS(const SignedDigest& sd,
Input subjectPublicKeyInfo,
void* pkcs11PinArg)
{
return VerifySignedDigest(sd, subjectPublicKeyInfo,
SEC_OID_PKCS1_RSA_ENCRYPTION, pkcs11PinArg);
}
Result
VerifyECDSASignedDigestNSS(const SignedDigest& sd,
Input subjectPublicKeyInfo,
void* pkcs11PinArg)
{
return VerifySignedDigest(sd, subjectPublicKeyInfo,
SEC_OID_ANSIX962_EC_PUBLIC_KEY, pkcs11PinArg);
}
Result
DigestBufNSS(Input item,
DigestAlgorithm digestAlg,
/*out*/ uint8_t* digestBuf,
size_t digestBufLen)
{
SECOidTag oid;
size_t bits;
switch (digestAlg) {
case DigestAlgorithm::sha512: oid = SEC_OID_SHA512; bits = 512; break;
case DigestAlgorithm::sha384: oid = SEC_OID_SHA384; bits = 384; break;
case DigestAlgorithm::sha256: oid = SEC_OID_SHA256; bits = 256; break;
case DigestAlgorithm::sha1: oid = SEC_OID_SHA1; bits = 160; break;
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
if (digestBufLen != bits / 8) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
SECItem itemSECItem = UnsafeMapInputToSECItem(item);
if (itemSECItem.len >
static_cast<decltype(itemSECItem.len)>(
std::numeric_limits<int32_t>::max())) {
PR_NOT_REACHED("large items should not be possible here");
return Result::FATAL_ERROR_INVALID_ARGS;
}
SECStatus srv = PK11_HashBuf(oid, digestBuf, itemSECItem.data,
static_cast<int32_t>(itemSECItem.len));
if (srv != SECSuccess) {
return MapPRErrorCodeToResult(PR_GetError());
}
return Success;
}
Result
MapPRErrorCodeToResult(PRErrorCode error)
{
switch (error)
{
#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
case nss_result: return Result::mozilla_pkix_result;
MOZILLA_PKIX_MAP_LIST
#undef MOZILLA_PKIX_MAP
default:
return Result::ERROR_UNKNOWN_ERROR;
}
}
PRErrorCode
MapResultToPRErrorCode(Result result)
{
switch (result)
{
#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
case Result::mozilla_pkix_result: return nss_result;
MOZILLA_PKIX_MAP_LIST
#undef MOZILLA_PKIX_MAP
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
}
void
RegisterErrorTable()
{
// Note that these error strings are not localizable.
// When these strings change, update the localization information too.
static const PRErrorMessage ErrorTableText[] = {
{ "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE",
"The server uses key pinning (HPKP) but no trusted certificate chain "
"could be constructed that matches the pinset. Key pinning violations "
"cannot be overridden." },
{ "MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY",
"The server uses a certificate with a basic constraints extension "
"identifying it as a certificate authority. For a properly-issued "
"certificate, this should not be the case." },
{ "MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE",
"The server presented a certificate with a key size that is too small "
"to establish a secure connection." },
{ "MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA",
"An X.509 version 1 certificate that is not a trust anchor was used to "
"issue the server's certificate. X.509 version 1 certificates are "
"deprecated and should not be used to sign other certificates." },
{ "MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH",
"The certificate is not valid for the given email address." },
{ "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE",
"The server presented a certificate that is not yet valid." },
{ "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE",
"A certificate that is not yet valid was used to issue the server's "
"certificate." },
{ "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH",
"The signature algorithm in the signature field of the certificate does "
"not match the algorithm in its signatureAlgorithm field." },
{ "MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING",
"The OCSP response does not include a status for the certificate being "
"verified." },
{ "MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG",
"The server presented a certificate that is valid for too long." },
{ "MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING",
"A required TLS feature is missing." },
{ "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING",
"The server presented a certificate that contains an invalid encoding of "
"an integer. Common causes include negative serial numbers, negative RSA "
"moduli, and encodings that are longer than necessary." },
{ "MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME",
"The server presented a certificate with an empty issuer distinguished "
"name." },
};
// Note that these error strings are not localizable.
// When these strings change, update the localization information too.
static const PRErrorTable ErrorTable = {
ErrorTableText,
"pkixerrors",
ERROR_BASE,
PR_ARRAY_SIZE(ErrorTableText)
};
(void) PR_ErrorInstallTable(&ErrorTable);
}
} } // namespace mozilla::pkix

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
/*- *- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkix/Result.h"
#include "pkixutil.h"
namespace mozilla { namespace pkix {
const char*
MapResultToName(Result result)
{
switch (result)
{
#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
case Result::mozilla_pkix_result: return "Result::" #mozilla_pkix_result;
MOZILLA_PKIX_MAP_LIST
#undef MOZILLA_PKIX_MAP
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
}
} } // namespace mozilla::pkix

View file

@ -0,0 +1,78 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2014 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkix/Time.h"
#include "pkixutil.h"
#ifdef WIN32
#ifdef _MSC_VER
#pragma warning(push, 3)
#endif
#include "windows.h"
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#else
#include "sys/time.h"
#endif
namespace mozilla { namespace pkix {
Time
Now()
{
uint64_t seconds;
#ifdef WIN32
// "Contains a 64-bit value representing the number of 100-nanosecond
// intervals since January 1, 1601 (UTC)."
// - http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
uint64_t ft64 = (static_cast<uint64_t>(ft.dwHighDateTime) << 32) |
ft.dwLowDateTime;
seconds = (DaysBeforeYear(1601) * Time::ONE_DAY_IN_SECONDS) +
ft64 / (1000u * 1000u * 1000u / 100u);
#else
// "The gettimeofday() function shall obtain the current time, expressed as
// seconds and microseconds since the Epoch."
// - http://pubs.opengroup.org/onlinepubs/009695399/functions/gettimeofday.html
timeval tv;
(void) gettimeofday(&tv, nullptr);
seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) +
static_cast<uint64_t>(tv.tv_sec);
#endif
return TimeFromElapsedSecondsAD(seconds);
}
Time
TimeFromEpochInSeconds(uint64_t secondsSinceEpoch)
{
uint64_t seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) +
secondsSinceEpoch;
return TimeFromElapsedSecondsAD(seconds);
}
} } // namespace mozilla::pkix

View file

@ -0,0 +1,289 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2013 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef mozilla_pkix_pkixutil_h
#define mozilla_pkix_pkixutil_h
#include "pkixder.h"
namespace mozilla { namespace pkix {
// During path building and verification, we build a linked list of BackCerts
// from the current cert toward the end-entity certificate. The linked list
// is used to verify properties that aren't local to the current certificate
// and/or the direct link between the current certificate and its issuer,
// such as name constraints.
//
// Each BackCert contains pointers to all the given certificate's extensions
// so that we can parse the extension block once and then process the
// extensions in an order that may be different than they appear in the cert.
class BackCert final
{
public:
// certDER and childCert must be valid for the lifetime of BackCert.
BackCert(Input certDER, EndEntityOrCA endEntityOrCA,
const BackCert* childCert)
: der(certDER)
, endEntityOrCA(endEntityOrCA)
, childCert(childCert)
{
}
Result Init();
const Input GetDER() const { return der; }
const der::SignedDataWithSignature& GetSignedData() const {
return signedData;
}
der::Version GetVersion() const { return version; }
const Input GetSerialNumber() const { return serialNumber; }
const Input GetSignature() const { return signature; }
const Input GetIssuer() const { return issuer; }
// XXX: "validity" is a horrible name for the structure that holds
// notBefore & notAfter, but that is the name used in RFC 5280 and we use the
// RFC 5280 names for everything.
const Input GetValidity() const { return validity; }
const Input GetSubject() const { return subject; }
const Input GetSubjectPublicKeyInfo() const
{
return subjectPublicKeyInfo;
}
const Input* GetAuthorityInfoAccess() const
{
return MaybeInput(authorityInfoAccess);
}
const Input* GetBasicConstraints() const
{
return MaybeInput(basicConstraints);
}
const Input* GetCertificatePolicies() const
{
return MaybeInput(certificatePolicies);
}
const Input* GetExtKeyUsage() const
{
return MaybeInput(extKeyUsage);
}
const Input* GetKeyUsage() const
{
return MaybeInput(keyUsage);
}
const Input* GetInhibitAnyPolicy() const
{
return MaybeInput(inhibitAnyPolicy);
}
const Input* GetNameConstraints() const
{
return MaybeInput(nameConstraints);
}
const Input* GetSubjectAltName() const
{
return MaybeInput(subjectAltName);
}
const Input* GetRequiredTLSFeatures() const
{
return MaybeInput(requiredTLSFeatures);
}
const Input* GetSignedCertificateTimestamps() const
{
return MaybeInput(signedCertificateTimestamps);
}
private:
const Input der;
public:
const EndEntityOrCA endEntityOrCA;
BackCert const* const childCert;
private:
// When parsing certificates in BackCert::Init, we don't accept empty
// extensions. Consequently, we don't have to store a distinction between
// empty extensions and extensions that weren't included. However, when
// *processing* extensions, we distinguish between whether an extension was
// included or not based on whetehr the GetXXX function for the extension
// returns nullptr.
static inline const Input* MaybeInput(const Input& item)
{
return item.GetLength() > 0 ? &item : nullptr;
}
der::SignedDataWithSignature signedData;
der::Version version;
Input serialNumber;
Input signature;
Input issuer;
// XXX: "validity" is a horrible name for the structure that holds
// notBefore & notAfter, but that is the name used in RFC 5280 and we use the
// RFC 5280 names for everything.
Input validity;
Input subject;
Input subjectPublicKeyInfo;
Input authorityInfoAccess;
Input basicConstraints;
Input certificatePolicies;
Input extKeyUsage;
Input inhibitAnyPolicy;
Input keyUsage;
Input nameConstraints;
Input subjectAltName;
Input criticalNetscapeCertificateType;
Input requiredTLSFeatures;
Input signedCertificateTimestamps; // RFC 6962 (Certificate Transparency)
Result RememberExtension(Reader& extnID, Input extnValue, bool critical,
/*out*/ bool& understood);
BackCert(const BackCert&) = delete;
void operator=(const BackCert&) = delete;
};
class NonOwningDERArray final : public DERArray
{
public:
NonOwningDERArray()
: numItems(0)
{
// we don't need to initialize the items array because we always check
// numItems before accessing i.
}
size_t GetLength() const override { return numItems; }
const Input* GetDER(size_t i) const override
{
return i < numItems ? &items[i] : nullptr;
}
Result Append(Input der)
{
if (numItems >= MAX_LENGTH) {
return Result::FATAL_ERROR_INVALID_ARGS;
}
Result rv = items[numItems].Init(der); // structure assignment
if (rv != Success) {
return rv;
}
++numItems;
return Success;
}
// Public so we can static_assert on this. Keep in sync with MAX_SUBCA_COUNT.
static const size_t MAX_LENGTH = 8;
private:
Input items[MAX_LENGTH]; // avoids any heap allocations
size_t numItems;
NonOwningDERArray(const NonOwningDERArray&) = delete;
void operator=(const NonOwningDERArray&) = delete;
};
// Extracts the SignedCertificateTimestampList structure which is encoded as an
// OCTET STRING within the X.509v3 / OCSP extensions (see RFC 6962 section 3.3).
Result
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
Input& sctList);
inline unsigned int
DaysBeforeYear(unsigned int year)
{
assert(year <= 9999);
return ((year - 1u) * 365u)
+ ((year - 1u) / 4u) // leap years are every 4 years,
- ((year - 1u) / 100u) // except years divisible by 100,
+ ((year - 1u) / 400u); // except years divisible by 400.
}
static const size_t MAX_DIGEST_SIZE_IN_BYTES = 512 / 8; // sha-512
Result DigestSignedData(TrustDomain& trustDomain,
const der::SignedDataWithSignature& signedData,
/*out*/ uint8_t(&digestBuf)[MAX_DIGEST_SIZE_IN_BYTES],
/*out*/ der::PublicKeyAlgorithm& publicKeyAlg,
/*out*/ SignedDigest& signedDigest);
Result VerifySignedDigest(TrustDomain& trustDomain,
der::PublicKeyAlgorithm publicKeyAlg,
const SignedDigest& signedDigest,
Input signerSubjectPublicKeyInfo);
// Combines DigestSignedData and VerifySignedDigest
Result VerifySignedData(TrustDomain& trustDomain,
const der::SignedDataWithSignature& signedData,
Input signerSubjectPublicKeyInfo);
// Extracts the key parameters from |subjectPublicKeyInfo|, invoking
// the relevant methods of |trustDomain|.
Result
CheckSubjectPublicKeyInfo(Input subjectPublicKeyInfo, TrustDomain& trustDomain,
EndEntityOrCA endEntityOrCA);
// In a switch over an enum, sometimes some compilers are not satisfied that
// all control flow paths have been considered unless there is a default case.
// However, in our code, such a default case is almost always unreachable dead
// code. That can be particularly problematic when the compiler wants the code
// to choose a value, such as a return value, for the default case, but there's
// no appropriate "impossible case" value to choose.
//
// MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM accounts for this. Example:
//
// // In xy.cpp
// #include "xt.h"
//
// enum class XY { X, Y };
//
// int func(XY xy) {
// switch (xy) {
// case XY::X: return 1;
// case XY::Y; return 2;
// MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
// }
// }
#if defined(__clang__)
// Clang will warn if not all cases are covered (-Wswitch-enum) AND it will
// warn if a switch statement that covers every enum label has a default case
// (-W-covered-switch-default). Versions prior to 3.5 warned about unreachable
// code in such default cases (-Wunreachable-code) even when
// -W-covered-switch-default was disabled, but that changed in Clang 3.5.
#define MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM // empty
#elif defined(__GNUC__)
// GCC will warn if not all cases are covered (-Wswitch-enum). It does not
// assume that the default case is unreachable.
#define MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM \
default: assert(false); __builtin_unreachable();
#elif defined(_MSC_VER)
// MSVC will warn if not all cases are covered (C4061, level 4). It does not
// assume that the default case is unreachable.
#define MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM \
default: assert(false); __assume(0);
#else
#error Unsupported compiler for MOZILLA_PKIX_UNREACHABLE_DEFAULT.
#endif
} } // namespace mozilla::pkix
#endif // mozilla_pkix_pkixutil_h

View file

@ -0,0 +1,103 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* Copyright 2015 Mozilla Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pkixutil.h"
namespace mozilla { namespace pkix {
Result
DigestSignedData(TrustDomain& trustDomain,
const der::SignedDataWithSignature& signedData,
/*out*/ uint8_t(&digestBuf)[MAX_DIGEST_SIZE_IN_BYTES],
/*out*/ der::PublicKeyAlgorithm& publicKeyAlg,
/*out*/ SignedDigest& signedDigest)
{
Reader signatureAlg(signedData.algorithm);
Result rv = der::SignatureAlgorithmIdentifierValue(
signatureAlg, publicKeyAlg, signedDigest.digestAlgorithm);
if (rv != Success) {
return rv;
}
if (!signatureAlg.AtEnd()) {
return Result::ERROR_BAD_DER;
}
size_t digestLen;
switch (signedDigest.digestAlgorithm) {
case DigestAlgorithm::sha512: digestLen = 512 / 8; break;
case DigestAlgorithm::sha384: digestLen = 384 / 8; break;
case DigestAlgorithm::sha256: digestLen = 256 / 8; break;
case DigestAlgorithm::sha1: digestLen = 160 / 8; break;
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
assert(digestLen <= sizeof(digestBuf));
rv = trustDomain.DigestBuf(signedData.data, signedDigest.digestAlgorithm,
digestBuf, digestLen);
if (rv != Success) {
return rv;
}
rv = signedDigest.digest.Init(digestBuf, digestLen);
if (rv != Success) {
return rv;
}
return signedDigest.signature.Init(signedData.signature);
}
Result
VerifySignedDigest(TrustDomain& trustDomain,
der::PublicKeyAlgorithm publicKeyAlg,
const SignedDigest& signedDigest,
Input signerSubjectPublicKeyInfo)
{
switch (publicKeyAlg) {
case der::PublicKeyAlgorithm::ECDSA:
return trustDomain.VerifyECDSASignedDigest(signedDigest,
signerSubjectPublicKeyInfo);
case der::PublicKeyAlgorithm::RSA_PKCS1:
return trustDomain.VerifyRSAPKCS1SignedDigest(signedDigest,
signerSubjectPublicKeyInfo);
MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
}
}
Result
VerifySignedData(TrustDomain& trustDomain,
const der::SignedDataWithSignature& signedData,
Input signerSubjectPublicKeyInfo)
{
uint8_t digestBuf[MAX_DIGEST_SIZE_IN_BYTES];
der::PublicKeyAlgorithm publicKeyAlg;
SignedDigest signedDigest;
Result rv = DigestSignedData(trustDomain, signedData, digestBuf,
publicKeyAlg, signedDigest);
if (rv != Success) {
return rv;
}
return VerifySignedDigest(trustDomain, publicKeyAlg, signedDigest,
signerSubjectPublicKeyInfo);
}
} } // namespace mozilla::pkix