summaryrefslogtreecommitdiffstats
path: root/security/pkix/lib/pkixocsp.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /security/pkix/lib/pkixocsp.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'security/pkix/lib/pkixocsp.cpp')
-rw-r--r--security/pkix/lib/pkixocsp.cpp1007
1 files changed, 1007 insertions, 0 deletions
diff --git a/security/pkix/lib/pkixocsp.cpp b/security/pkix/lib/pkixocsp.cpp
new file mode 100644
index 000000000..06cb53bc4
--- /dev/null
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -0,0 +1,1007 @@
+/* -*- 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 <limits>
+
+#include "pkix/pkix.h"
+#include "pkixcheck.h"
+#include "pkixutil.h"
+
+namespace {
+
+const size_t SHA1_DIGEST_LENGTH = 160 / 8;
+
+} // namespace
+
+namespace mozilla { namespace pkix {
+
+// These values correspond to the tag values in the ASN.1 CertStatus
+enum class CertStatus : uint8_t {
+ Good = der::CONTEXT_SPECIFIC | 0,
+ Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ Unknown = der::CONTEXT_SPECIFIC | 2
+};
+
+class Context final
+{
+public:
+ Context(TrustDomain& trustDomain, const CertID& certID, Time time,
+ uint16_t maxLifetimeInDays, /*optional out*/ Time* thisUpdate,
+ /*optional out*/ Time* validThrough)
+ : trustDomain(trustDomain)
+ , certID(certID)
+ , time(time)
+ , maxLifetimeInDays(maxLifetimeInDays)
+ , certStatus(CertStatus::Unknown)
+ , thisUpdate(thisUpdate)
+ , validThrough(validThrough)
+ , expired(false)
+ , matchFound(false)
+ {
+ if (thisUpdate) {
+ *thisUpdate = TimeFromElapsedSecondsAD(0);
+ }
+ if (validThrough) {
+ *validThrough = TimeFromElapsedSecondsAD(0);
+ }
+ }
+
+ TrustDomain& trustDomain;
+ const CertID& certID;
+ const Time time;
+ const uint16_t maxLifetimeInDays;
+ CertStatus certStatus;
+ Time* thisUpdate;
+ Time* validThrough;
+ bool expired;
+
+ Input signedCertificateTimestamps;
+
+ // Keep track of whether the OCSP response contains the status of the
+ // certificate we're interested in. Responders might reply without
+ // including the status of any of the requested certs, we should
+ // indicate a server failure in those cases.
+ bool matchFound;
+
+ Context(const Context&) = delete;
+ void operator=(const Context&) = delete;
+};
+
+// Verify that potentialSigner is a valid delegated OCSP response signing cert
+// according to RFC 6960 section 4.2.2.2.
+static Result
+CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
+ BackCert& potentialSigner,
+ Input issuerSubject,
+ Input issuerSubjectPublicKeyInfo,
+ Time time)
+{
+ Result rv;
+
+ // We don't need to do a complete verification of the signer (i.e. we don't
+ // have to call BuildCertChain to verify the entire chain) because we
+ // already know that the issuer is valid, since revocation checking is done
+ // from the root to the parent after we've built a complete chain that we
+ // know is otherwise valid. Rather, we just need to do a one-step validation
+ // from potentialSigner to the issuer.
+ //
+ // It seems reasonable to require the KU_DIGITAL_SIGNATURE key usage on the
+ // OCSP responder certificate if the OCSP responder certificate has a
+ // key usage extension. However, according to bug 240456, some OCSP responder
+ // certificates may have only the nonRepudiation bit set. Also, the OCSP
+ // specification (RFC 6960) does not mandate any particular key usage to be
+ // asserted for OCSP responde signers. Oddly, the CABForum Baseline
+ // Requirements v.1.1.5 do say "If the Root CA Private Key is used for
+ // signing OCSP responses, then the digitalSignature bit MUST be set."
+ //
+ // Note that CheckIssuerIndependentProperties processes
+ // SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us
+ // to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied
+ // by a missing EKU extension, unlike other EKUs.
+ //
+ // TODO(bug 926261): If we're validating for a policy then the policy OID we
+ // are validating for should be passed to CheckIssuerIndependentProperties.
+ TrustLevel unusedTrustLevel;
+ rv = CheckIssuerIndependentProperties(trustDomain, potentialSigner, time,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_OCSPSigning,
+ CertPolicyId::anyPolicy, 0,
+ unusedTrustLevel);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // It is possible that there exists a certificate with the same key as the
+ // issuer but with a different name, so we need to compare names
+ // XXX(bug 926270) XXX(bug 1008133) XXX(bug 980163): Improve name
+ // comparison.
+ // TODO: needs test
+ if (!InputsAreEqual(potentialSigner.GetIssuer(), issuerSubject)) {
+ return Result::ERROR_OCSP_RESPONDER_CERT_INVALID;
+ }
+
+ // TODO(bug 926260): check name constraints
+
+ rv = VerifySignedData(trustDomain, potentialSigner.GetSignedData(),
+ issuerSubjectPublicKeyInfo);
+
+ // TODO: check for revocation of the OCSP responder certificate unless no-check
+ // or the caller forcing no-check. To properly support the no-check policy, we'd
+ // need to enforce policy constraints from the issuerChain.
+
+ return rv;
+}
+
+enum class ResponderIDType : uint8_t
+{
+ byName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ byKey = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 2
+};
+
+static inline Result OCSPResponse(Reader&, Context&);
+static inline Result ResponseBytes(Reader&, Context&);
+static inline Result BasicResponse(Reader&, Context&);
+static inline Result ResponseData(
+ Reader& tbsResponseData,
+ Context& context,
+ const der::SignedDataWithSignature& signedResponseData,
+ const DERArray& certs);
+static inline Result SingleResponse(Reader& input, Context& context);
+static Result ExtensionNotUnderstood(Reader& extnID, Input extnValue,
+ bool critical, /*out*/ bool& understood);
+static Result RememberSingleExtension(Context& context, Reader& extnID,
+ Input extnValue, bool critical,
+ /*out*/ bool& understood);
+static inline Result CertID(Reader& input,
+ const Context& context,
+ /*out*/ bool& match);
+static Result MatchKeyHash(TrustDomain& trustDomain,
+ Input issuerKeyHash,
+ Input issuerSubjectPublicKeyInfo,
+ /*out*/ bool& match);
+static Result KeyHash(TrustDomain& trustDomain,
+ Input subjectPublicKeyInfo,
+ /*out*/ uint8_t* hashBuf, size_t hashBufSize);
+
+static Result
+MatchResponderID(TrustDomain& trustDomain,
+ ResponderIDType responderIDType,
+ Input responderID,
+ Input potentialSignerSubject,
+ Input potentialSignerSubjectPublicKeyInfo,
+ /*out*/ bool& match)
+{
+ match = false;
+
+ switch (responderIDType) {
+ case ResponderIDType::byName:
+ // XXX(bug 926270) XXX(bug 1008133) XXX(bug 980163): Improve name
+ // comparison.
+ match = InputsAreEqual(responderID, potentialSignerSubject);
+ return Success;
+
+ case ResponderIDType::byKey:
+ {
+ Reader input(responderID);
+ Input keyHash;
+ Result rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, keyHash);
+ if (rv != Success) {
+ return rv;
+ }
+ return MatchKeyHash(trustDomain, keyHash,
+ potentialSignerSubjectPublicKeyInfo, match);
+ }
+
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+}
+
+static Result
+VerifyOCSPSignedData(TrustDomain& trustDomain,
+ const der::SignedDataWithSignature& signedResponseData,
+ Input spki)
+{
+ Result rv = VerifySignedData(trustDomain, signedResponseData, spki);
+ if (rv == Result::ERROR_BAD_SIGNATURE) {
+ rv = Result::ERROR_OCSP_BAD_SIGNATURE;
+ }
+ return rv;
+}
+
+// RFC 6960 section 4.2.2.2: The OCSP responder must either be the issuer of
+// the cert or it must be a delegated OCSP response signing cert directly
+// issued by the issuer. If the OCSP responder is a delegated OCSP response
+// signer, then its certificate is (probably) embedded within the OCSP
+// response and we'll need to verify that it is a valid certificate that chains
+// *directly* to issuerCert.
+static Result
+VerifySignature(Context& context, ResponderIDType responderIDType,
+ Input responderID, const DERArray& certs,
+ const der::SignedDataWithSignature& signedResponseData)
+{
+ bool match;
+ Result rv = MatchResponderID(context.trustDomain, responderIDType,
+ responderID, context.certID.issuer,
+ context.certID.issuerSubjectPublicKeyInfo,
+ match);
+ if (rv != Success) {
+ return rv;
+ }
+ if (match) {
+ return VerifyOCSPSignedData(context.trustDomain, signedResponseData,
+ context.certID.issuerSubjectPublicKeyInfo);
+ }
+
+ size_t numCerts = certs.GetLength();
+ for (size_t i = 0; i < numCerts; ++i) {
+ BackCert cert(*certs.GetDER(i), EndEntityOrCA::MustBeEndEntity, nullptr);
+ rv = cert.Init();
+ if (rv != Success) {
+ return rv;
+ }
+ rv = MatchResponderID(context.trustDomain, responderIDType, responderID,
+ cert.GetSubject(), cert.GetSubjectPublicKeyInfo(),
+ match);
+ if (rv != Success) {
+ if (IsFatalError(rv)) {
+ return rv;
+ }
+ continue;
+ }
+
+ if (match) {
+ rv = CheckOCSPResponseSignerCert(context.trustDomain, cert,
+ context.certID.issuer,
+ context.certID.issuerSubjectPublicKeyInfo,
+ context.time);
+ if (rv != Success) {
+ if (IsFatalError(rv)) {
+ return rv;
+ }
+ continue;
+ }
+
+ return VerifyOCSPSignedData(context.trustDomain, signedResponseData,
+ cert.GetSubjectPublicKeyInfo());
+ }
+ }
+
+ return Result::ERROR_OCSP_INVALID_SIGNING_CERT;
+}
+
+static inline Result
+MapBadDERToMalformedOCSPResponse(Result rv)
+{
+ if (rv == Result::ERROR_BAD_DER) {
+ return Result::ERROR_OCSP_MALFORMED_RESPONSE;
+ }
+ return rv;
+}
+
+Result
+VerifyEncodedOCSPResponse(TrustDomain& trustDomain, const struct CertID& certID,
+ Time time, uint16_t maxOCSPLifetimeInDays,
+ Input encodedResponse,
+ /*out*/ bool& expired,
+ /*optional out*/ Time* thisUpdate,
+ /*optional out*/ Time* validThrough)
+{
+ // Always initialize this to something reasonable.
+ expired = false;
+
+ Context context(trustDomain, certID, time, maxOCSPLifetimeInDays,
+ thisUpdate, validThrough);
+
+ Reader input(encodedResponse);
+ Result rv = der::Nested(input, der::SEQUENCE, [&context](Reader& r) {
+ return OCSPResponse(r, context);
+ });
+ if (rv != Success) {
+ return MapBadDERToMalformedOCSPResponse(rv);
+ }
+ rv = der::End(input);
+ if (rv != Success) {
+ return MapBadDERToMalformedOCSPResponse(rv);
+ }
+ if (!context.matchFound) {
+ return Result::ERROR_OCSP_RESPONSE_FOR_CERT_MISSING;
+ }
+
+ expired = context.expired;
+
+ switch (context.certStatus) {
+ case CertStatus::Good:
+ if (expired) {
+ return Result::ERROR_OCSP_OLD_RESPONSE;
+ }
+ if (context.signedCertificateTimestamps.GetLength()) {
+ Input sctList;
+ rv = ExtractSignedCertificateTimestampListFromExtension(
+ context.signedCertificateTimestamps, sctList);
+ if (rv != Success) {
+ return MapBadDERToMalformedOCSPResponse(rv);
+ }
+ context.trustDomain.NoteAuxiliaryExtension(
+ AuxiliaryExtension::SCTListFromOCSPResponse, sctList);
+ }
+ return Success;
+ case CertStatus::Revoked:
+ return Result::ERROR_REVOKED_CERTIFICATE;
+ case CertStatus::Unknown:
+ return Result::ERROR_OCSP_UNKNOWN_CERT;
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+}
+
+// OCSPResponse ::= SEQUENCE {
+// responseStatus OCSPResponseStatus,
+// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+//
+static inline Result
+OCSPResponse(Reader& input, Context& context)
+{
+ // OCSPResponseStatus ::= ENUMERATED {
+ // successful (0), -- Response has valid confirmations
+ // malformedRequest (1), -- Illegal confirmation request
+ // internalError (2), -- Internal error in issuer
+ // tryLater (3), -- Try again later
+ // -- (4) is not used
+ // sigRequired (5), -- Must sign the request
+ // unauthorized (6) -- Request unauthorized
+ // }
+ uint8_t responseStatus;
+
+ Result rv = der::Enumerated(input, responseStatus);
+ if (rv != Success) {
+ return rv;
+ }
+ switch (responseStatus) {
+ case 0: break; // successful
+ case 1: return Result::ERROR_OCSP_MALFORMED_REQUEST;
+ case 2: return Result::ERROR_OCSP_SERVER_ERROR;
+ case 3: return Result::ERROR_OCSP_TRY_SERVER_LATER;
+ case 5: return Result::ERROR_OCSP_REQUEST_NEEDS_SIG;
+ case 6: return Result::ERROR_OCSP_UNAUTHORIZED_REQUEST;
+ default: return Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS;
+ }
+
+ return der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ der::SEQUENCE, [&context](Reader& r) {
+ return ResponseBytes(r, context);
+ });
+}
+
+// ResponseBytes ::= SEQUENCE {
+// responseType OBJECT IDENTIFIER,
+// response OCTET STRING }
+static inline Result
+ResponseBytes(Reader& input, Context& context)
+{
+ static const uint8_t id_pkix_ocsp_basic[] = {
+ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+
+ Result rv = der::OID(input, id_pkix_ocsp_basic);
+ if (rv != Success) {
+ return rv;
+ }
+
+ return der::Nested(input, der::OCTET_STRING, der::SEQUENCE,
+ [&context](Reader& r) {
+ return BasicResponse(r, context);
+ });
+}
+
+// BasicOCSPResponse ::= SEQUENCE {
+// tbsResponseData ResponseData,
+// signatureAlgorithm AlgorithmIdentifier,
+// signature BIT STRING,
+// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+Result
+BasicResponse(Reader& input, Context& context)
+{
+ Reader tbsResponseData;
+ der::SignedDataWithSignature signedData;
+ Result rv = der::SignedData(input, tbsResponseData, signedData);
+ if (rv != Success) {
+ if (rv == Result::ERROR_BAD_SIGNATURE) {
+ return Result::ERROR_OCSP_BAD_SIGNATURE;
+ }
+ return rv;
+ }
+
+ // Parse certificates, if any
+ NonOwningDERArray certs;
+ if (!input.AtEnd()) {
+ rv = der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ der::SEQUENCE, [&certs](Reader& certsDER) -> Result {
+ while (!certsDER.AtEnd()) {
+ Input cert;
+ Result rv = der::ExpectTagAndGetTLV(certsDER, der::SEQUENCE, cert);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = certs.Append(cert);
+ if (rv != Success) {
+ return Result::ERROR_BAD_DER; // Too many certs
+ }
+ }
+ return Success;
+ });
+ if (rv != Success) {
+ return rv;
+ }
+ }
+
+ return ResponseData(tbsResponseData, context, signedData, certs);
+}
+
+// ResponseData ::= SEQUENCE {
+// version [0] EXPLICIT Version DEFAULT v1,
+// responderID ResponderID,
+// producedAt GeneralizedTime,
+// responses SEQUENCE OF SingleResponse,
+// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+static inline Result
+ResponseData(Reader& input, Context& context,
+ const der::SignedDataWithSignature& signedResponseData,
+ const DERArray& certs)
+{
+ der::Version version;
+ Result rv = der::OptionalVersion(input, version);
+ if (rv != Success) {
+ return rv;
+ }
+ if (version != der::Version::v1) {
+ // TODO: more specific error code for bad version?
+ return Result::ERROR_BAD_DER;
+ }
+
+ // ResponderID ::= CHOICE {
+ // byName [1] Name,
+ // byKey [2] KeyHash }
+ Input responderID;
+ ResponderIDType responderIDType
+ = input.Peek(static_cast<uint8_t>(ResponderIDType::byName))
+ ? ResponderIDType::byName
+ : ResponderIDType::byKey;
+ rv = der::ExpectTagAndGetValue(input, static_cast<uint8_t>(responderIDType),
+ responderID);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // This is the soonest we can verify the signature. We verify the signature
+ // right away to follow the principal of minimizing the processing of data
+ // before verifying its signature.
+ rv = VerifySignature(context, responderIDType, responderID, certs,
+ signedResponseData);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // TODO: Do we even need to parse this? Should we just skip it?
+ Time producedAt(Time::uninitialized);
+ rv = der::GeneralizedTime(input, producedAt);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // We don't accept an empty sequence of responses. In practice, a legit OCSP
+ // responder will never return an empty response, and handling the case of an
+ // empty response makes things unnecessarily complicated.
+ rv = der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
+ der::EmptyAllowed::No, [&context](Reader& r) {
+ return SingleResponse(r, context);
+ });
+ if (rv != Success) {
+ return rv;
+ }
+
+ return der::OptionalExtensions(input,
+ der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ ExtensionNotUnderstood);
+}
+
+// SingleResponse ::= SEQUENCE {
+// certID CertID,
+// certStatus CertStatus,
+// thisUpdate GeneralizedTime,
+// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+// singleExtensions [1] EXPLICIT Extensions{{re-ocsp-crl |
+// re-ocsp-archive-cutoff |
+// CrlEntryExtensions, ...}
+// } OPTIONAL }
+static inline Result
+SingleResponse(Reader& input, Context& context)
+{
+ bool match = false;
+ Result rv = der::Nested(input, der::SEQUENCE, [&context, &match](Reader& r) {
+ return CertID(r, context, match);
+ });
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (!match) {
+ // This response does not reference the certificate we're interested in.
+ // By consuming the rest of our input and returning successfully, we can
+ // continue processing and examine another response that might have what
+ // we want.
+ input.SkipToEnd();
+ return Success;
+ }
+
+ // We found a response for the cert we're interested in.
+ context.matchFound = true;
+
+ // CertStatus ::= CHOICE {
+ // good [0] IMPLICIT NULL,
+ // revoked [1] IMPLICIT RevokedInfo,
+ // unknown [2] IMPLICIT UnknownInfo }
+ //
+ // In the event of multiple SingleResponses for a cert that have conflicting
+ // statuses, we use the following precedence rules:
+ //
+ // * revoked overrides good and unknown
+ // * good overrides unknown
+ if (input.Peek(static_cast<uint8_t>(CertStatus::Good))) {
+ rv = der::ExpectTagAndEmptyValue(input,
+ static_cast<uint8_t>(CertStatus::Good));
+ if (rv != Success) {
+ return rv;
+ }
+ if (context.certStatus != CertStatus::Revoked) {
+ context.certStatus = CertStatus::Good;
+ }
+ } else if (input.Peek(static_cast<uint8_t>(CertStatus::Revoked))) {
+ // We don't need any info from the RevokedInfo structure, so we don't even
+ // parse it. TODO: We should mention issues like this in the explanation of
+ // why we treat invalid OCSP responses equivalently to revoked for OCSP
+ // stapling.
+ rv = der::ExpectTagAndSkipValue(input,
+ static_cast<uint8_t>(CertStatus::Revoked));
+ if (rv != Success) {
+ return rv;
+ }
+ context.certStatus = CertStatus::Revoked;
+ } else {
+ rv = der::ExpectTagAndEmptyValue(input,
+ static_cast<uint8_t>(CertStatus::Unknown));
+ if (rv != Success) {
+ return rv;
+ }
+ }
+
+ // http://tools.ietf.org/html/rfc6960#section-3.2
+ // 5. The time at which the status being indicated is known to be
+ // correct (thisUpdate) is sufficiently recent;
+ // 6. When available, the time at or before which newer information will
+ // be available about the status of the certificate (nextUpdate) is
+ // greater than the current time.
+
+ Time thisUpdate(Time::uninitialized);
+ rv = der::GeneralizedTime(input, thisUpdate);
+ if (rv != Success) {
+ return rv;
+ }
+
+ static const uint64_t SLOP_SECONDS = Time::ONE_DAY_IN_SECONDS;
+
+ Time timePlusSlop(context.time);
+ rv = timePlusSlop.AddSeconds(SLOP_SECONDS);
+ if (rv != Success) {
+ return rv;
+ }
+ if (thisUpdate > timePlusSlop) {
+ return Result::ERROR_OCSP_FUTURE_RESPONSE;
+ }
+
+ Time notAfter(Time::uninitialized);
+ static const uint8_t NEXT_UPDATE_TAG =
+ der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
+ if (input.Peek(NEXT_UPDATE_TAG)) {
+ Time nextUpdate(Time::uninitialized);
+ rv = der::Nested(input, NEXT_UPDATE_TAG, [&nextUpdate](Reader& r) {
+ return der::GeneralizedTime(r, nextUpdate);
+ });
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (nextUpdate < thisUpdate) {
+ return Result::ERROR_OCSP_MALFORMED_RESPONSE;
+ }
+ notAfter = thisUpdate;
+ if (notAfter.AddSeconds(context.maxLifetimeInDays *
+ Time::ONE_DAY_IN_SECONDS) != Success) {
+ // This could only happen if we're dealing with times beyond the year
+ // 10,000AD.
+ return Result::ERROR_OCSP_FUTURE_RESPONSE;
+ }
+ if (nextUpdate <= notAfter) {
+ notAfter = nextUpdate;
+ }
+ } else {
+ // NSS requires all OCSP responses without a nextUpdate to be recent.
+ // Match that stricter behavior.
+ notAfter = thisUpdate;
+ if (notAfter.AddSeconds(Time::ONE_DAY_IN_SECONDS) != Success) {
+ // This could only happen if we're dealing with times beyond the year
+ // 10,000AD.
+ return Result::ERROR_OCSP_FUTURE_RESPONSE;
+ }
+ }
+
+ // Add some slop to hopefully handle clock-skew.
+ Time notAfterPlusSlop(notAfter);
+ rv = notAfterPlusSlop.AddSeconds(SLOP_SECONDS);
+ if (rv != Success) {
+ // This could only happen if we're dealing with times beyond the year
+ // 10,000AD.
+ return Result::ERROR_OCSP_FUTURE_RESPONSE;
+ }
+ if (context.time > notAfterPlusSlop) {
+ context.expired = true;
+ }
+
+ rv = der::OptionalExtensions(
+ input,
+ der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ [&context](Reader& extnID, const Input& extnValue, bool critical,
+ /*out*/ bool& understood) {
+ return RememberSingleExtension(context, extnID, extnValue, critical,
+ understood);
+ });
+
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (context.thisUpdate) {
+ *context.thisUpdate = thisUpdate;
+ }
+ if (context.validThrough) {
+ *context.validThrough = notAfterPlusSlop;
+ }
+
+ return Success;
+}
+
+// CertID ::= SEQUENCE {
+// hashAlgorithm AlgorithmIdentifier,
+// issuerNameHash OCTET STRING, -- Hash of issuer's DN
+// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
+// serialNumber CertificateSerialNumber }
+static inline Result
+CertID(Reader& input, const Context& context, /*out*/ bool& match)
+{
+ match = false;
+
+ DigestAlgorithm hashAlgorithm;
+ Result rv = der::DigestAlgorithmIdentifier(input, hashAlgorithm);
+ if (rv != Success) {
+ if (rv == Result::ERROR_INVALID_ALGORITHM) {
+ // Skip entries that are hashed with algorithms we don't support.
+ input.SkipToEnd();
+ return Success;
+ }
+ return rv;
+ }
+
+ Input issuerNameHash;
+ rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, issuerNameHash);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Input issuerKeyHash;
+ rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, issuerKeyHash);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Input serialNumber;
+ rv = der::CertificateSerialNumber(input, serialNumber);
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (!InputsAreEqual(serialNumber, context.certID.serialNumber)) {
+ // This does not reference the certificate we're interested in.
+ // Consume the rest of the input and return successfully to
+ // potentially continue processing other responses.
+ input.SkipToEnd();
+ return Success;
+ }
+
+ // TODO: support SHA-2 hashes.
+
+ if (hashAlgorithm != DigestAlgorithm::sha1) {
+ // Again, not interested in this response. Consume input, return success.
+ input.SkipToEnd();
+ return Success;
+ }
+
+ if (issuerNameHash.GetLength() != SHA1_DIGEST_LENGTH) {
+ return Result::ERROR_OCSP_MALFORMED_RESPONSE;
+ }
+
+ // From http://tools.ietf.org/html/rfc6960#section-4.1.1:
+ // "The hash shall be calculated over the DER encoding of the
+ // issuer's name field in the certificate being checked."
+ uint8_t hashBuf[SHA1_DIGEST_LENGTH];
+ rv = context.trustDomain.DigestBuf(context.certID.issuer,
+ DigestAlgorithm::sha1, hashBuf,
+ sizeof(hashBuf));
+ if (rv != Success) {
+ return rv;
+ }
+ Input computed(hashBuf);
+ if (!InputsAreEqual(computed, issuerNameHash)) {
+ // Again, not interested in this response. Consume input, return success.
+ input.SkipToEnd();
+ return Success;
+ }
+
+ return MatchKeyHash(context.trustDomain, issuerKeyHash,
+ context.certID.issuerSubjectPublicKeyInfo, match);
+}
+
+// From http://tools.ietf.org/html/rfc6960#section-4.1.1:
+// "The hash shall be calculated over the value (excluding tag and length) of
+// the subject public key field in the issuer's certificate."
+//
+// From http://tools.ietf.org/html/rfc6960#appendix-B.1:
+// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
+// -- (i.e., the SHA-1 hash of the value of the
+// -- BIT STRING subjectPublicKey [excluding
+// -- the tag, length, and number of unused
+// -- bits] in the responder's certificate)
+static Result
+MatchKeyHash(TrustDomain& trustDomain, Input keyHash,
+ const Input subjectPublicKeyInfo, /*out*/ bool& match)
+{
+ if (keyHash.GetLength() != SHA1_DIGEST_LENGTH) {
+ return Result::ERROR_OCSP_MALFORMED_RESPONSE;
+ }
+ uint8_t hashBuf[SHA1_DIGEST_LENGTH];
+ Result rv = KeyHash(trustDomain, subjectPublicKeyInfo, hashBuf,
+ sizeof hashBuf);
+ if (rv != Success) {
+ return rv;
+ }
+ Input computed(hashBuf);
+ match = InputsAreEqual(computed, keyHash);
+ return Success;
+}
+
+// TODO(bug 966856): support SHA-2 hashes
+Result
+KeyHash(TrustDomain& trustDomain, const Input subjectPublicKeyInfo,
+ /*out*/ uint8_t* hashBuf, size_t hashBufSize)
+{
+ if (!hashBuf || hashBufSize != SHA1_DIGEST_LENGTH) {
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ // RFC 5280 Section 4.1
+ //
+ // SubjectPublicKeyInfo ::= SEQUENCE {
+ // algorithm AlgorithmIdentifier,
+ // subjectPublicKey BIT STRING }
+
+ Reader spki;
+ Result rv = der::ExpectTagAndGetValueAtEnd(subjectPublicKeyInfo,
+ der::SEQUENCE, spki);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // Skip AlgorithmIdentifier
+ rv = der::ExpectTagAndSkipValue(spki, der::SEQUENCE);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Input subjectPublicKey;
+ rv = der::BitStringWithNoUnusedBits(spki, subjectPublicKey);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::End(spki);
+ if (rv != Success) {
+ return rv;
+ }
+
+ return trustDomain.DigestBuf(subjectPublicKey, DigestAlgorithm::sha1,
+ hashBuf, hashBufSize);
+}
+
+Result
+ExtensionNotUnderstood(Reader& /*extnID*/, Input /*extnValue*/,
+ bool /*critical*/, /*out*/ bool& understood)
+{
+ understood = false;
+ return Success;
+}
+
+Result
+RememberSingleExtension(Context& context, Reader& extnID, Input extnValue,
+ bool /*critical*/, /*out*/ bool& understood)
+{
+ understood = false;
+
+ // SingleExtension for Signed Certificate Timestamp List.
+ // See Section 3.3 of RFC 6962.
+ // python DottedOIDToCode.py
+ // id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5
+ static const uint8_t id_ocsp_singleExtensionSctList[] = {
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
+ };
+
+ if (extnID.MatchRest(id_ocsp_singleExtensionSctList)) {
+ // Empty values are not allowed for this extension. Note that
+ // we assume this later, when checking if the extension was present.
+ if (extnValue.GetLength() == 0) {
+ return Result::ERROR_EXTENSION_VALUE_INVALID;
+ }
+ if (context.signedCertificateTimestamps.Init(extnValue) != Success) {
+ // Duplicate extension.
+ return Result::ERROR_EXTENSION_VALUE_INVALID;
+ }
+ understood = true;
+ }
+
+ return Success;
+}
+
+// 1. The certificate identified in a received response corresponds to
+// the certificate that was identified in the corresponding request;
+// 2. The signature on the response is valid;
+// 3. The identity of the signer matches the intended recipient of the
+// request;
+// 4. The signer is currently authorized to provide a response for the
+// certificate in question;
+// 5. The time at which the status being indicated is known to be
+// correct (thisUpdate) is sufficiently recent;
+// 6. When available, the time at or before which newer information will
+// be available about the status of the certificate (nextUpdate) is
+// greater than the current time.
+//
+// Responses whose nextUpdate value is earlier than
+// the local system time value SHOULD be considered unreliable.
+// Responses whose thisUpdate time is later than the local system time
+// SHOULD be considered unreliable.
+//
+// If nextUpdate is not set, the responder is indicating that newer
+// revocation information is available all the time.
+//
+// http://tools.ietf.org/html/rfc5019#section-4
+
+Result
+CreateEncodedOCSPRequest(TrustDomain& trustDomain, const struct CertID& certID,
+ /*out*/ uint8_t (&out)[OCSP_REQUEST_MAX_LENGTH],
+ /*out*/ size_t& outLen)
+{
+ // We do not add any extensions to the request.
+
+ // RFC 6960 says "An OCSP client MAY wish to specify the kinds of response
+ // types it understands. To do so, it SHOULD use an extension with the OID
+ // id-pkix-ocsp-response." This use of MAY and SHOULD is unclear. MSIE11
+ // on Windows 8.1 does not include any extensions, whereas NSS has always
+ // included the id-pkix-ocsp-response extension. Avoiding the sending the
+ // extension is better for OCSP GET because it makes the request smaller,
+ // and thus more likely to fit within the 255 byte limit for OCSP GET that
+ // is specified in RFC 5019 Section 5.
+
+ // Bug 966856: Add the id-pkix-ocsp-pref-sig-algs extension.
+
+ // Since we don't know whether the OCSP responder supports anything other
+ // than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and
+ // issuerKeyHash.
+ static const uint8_t hashAlgorithm[11] = {
+ 0x30, 0x09, // SEQUENCE
+ 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, // OBJECT IDENTIFIER id-sha1
+ 0x05, 0x00, // NULL
+ };
+ static const uint8_t hashLen = 160 / 8;
+
+ static const unsigned int totalLenWithoutSerialNumberData
+ = 2 // OCSPRequest
+ + 2 // tbsRequest
+ + 2 // requestList
+ + 2 // Request
+ + 2 // reqCert (CertID)
+ + sizeof(hashAlgorithm) // hashAlgorithm
+ + 2 + hashLen // issuerNameHash
+ + 2 + hashLen // issuerKeyHash
+ + 2; // serialNumber (header)
+
+ // The only way we could have a request this large is if the serialNumber was
+ // ridiculously and unreasonably large. RFC 5280 says "Conforming CAs MUST
+ // NOT use serialNumber values longer than 20 octets." With this restriction,
+ // we allow for some amount of non-conformance with that requirement while
+ // still ensuring we can encode the length values in the ASN.1 TLV structures
+ // in a single byte.
+ static_assert(totalLenWithoutSerialNumberData < OCSP_REQUEST_MAX_LENGTH,
+ "totalLenWithoutSerialNumberData too big");
+ if (certID.serialNumber.GetLength() >
+ OCSP_REQUEST_MAX_LENGTH - totalLenWithoutSerialNumberData) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ outLen = totalLenWithoutSerialNumberData + certID.serialNumber.GetLength();
+
+ uint8_t totalLen = static_cast<uint8_t>(outLen);
+
+ uint8_t* d = out;
+ *d++ = 0x30; *d++ = totalLen - 2u; // OCSPRequest (SEQUENCE)
+ *d++ = 0x30; *d++ = totalLen - 4u; // tbsRequest (SEQUENCE)
+ *d++ = 0x30; *d++ = totalLen - 6u; // requestList (SEQUENCE OF)
+ *d++ = 0x30; *d++ = totalLen - 8u; // Request (SEQUENCE)
+ *d++ = 0x30; *d++ = totalLen - 10u; // reqCert (CertID SEQUENCE)
+
+ // reqCert.hashAlgorithm
+ for (size_t i = 0; i < sizeof(hashAlgorithm); ++i) {
+ *d++ = hashAlgorithm[i];
+ }
+
+ // reqCert.issuerNameHash (OCTET STRING)
+ *d++ = 0x04;
+ *d++ = hashLen;
+ Result rv = trustDomain.DigestBuf(certID.issuer, DigestAlgorithm::sha1, d,
+ hashLen);
+ if (rv != Success) {
+ return rv;
+ }
+ d += hashLen;
+
+ // reqCert.issuerKeyHash (OCTET STRING)
+ *d++ = 0x04;
+ *d++ = hashLen;
+ rv = KeyHash(trustDomain, certID.issuerSubjectPublicKeyInfo, d, hashLen);
+ if (rv != Success) {
+ return rv;
+ }
+ d += hashLen;
+
+ // reqCert.serialNumber (INTEGER)
+ *d++ = 0x02; // INTEGER
+ *d++ = static_cast<uint8_t>(certID.serialNumber.GetLength());
+ Reader serialNumber(certID.serialNumber);
+ do {
+ rv = serialNumber.Read(*d);
+ if (rv != Success) {
+ return rv;
+ }
+ ++d;
+ } while (!serialNumber.AtEnd());
+
+ assert(d == out + totalLen);
+
+ return Success;
+}
+
+} } // namespace mozilla::pkix