summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/mozpkix
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@gmail.com>2018-12-17 14:12:04 +0100
committerwolfbeast <mcwerewolf@gmail.com>2018-12-17 14:12:04 +0100
commit51b821b3fdc5a7eab2369cb6a6680598a6264b08 (patch)
treef3608a518bbb9e31b0a42b9a10742fb11ef5b39b /security/nss/lib/mozpkix
parent8e44bbb43789e585fab9fc1ce8becc94b45d566c (diff)
parent680c3eadb6aaec1f3653636db081a519e0f62ef5 (diff)
downloadUXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar
UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar.gz
UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar.lz
UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar.xz
UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.zip
Merge branch 'master' into Sync-weave
Diffstat (limited to 'security/nss/lib/mozpkix')
-rw-r--r--security/nss/lib/mozpkix/.clang-format4
-rw-r--r--security/nss/lib/mozpkix/exports.gyp47
-rw-r--r--security/nss/lib/mozpkix/include/pkix-test/pkixtestnss.h48
-rw-r--r--security/nss/lib/mozpkix/include/pkix-test/pkixtestutil.h406
-rw-r--r--security/nss/lib/mozpkix/include/pkix/Input.h310
-rw-r--r--security/nss/lib/mozpkix/include/pkix/Result.h219
-rw-r--r--security/nss/lib/mozpkix/include/pkix/Time.h137
-rw-r--r--security/nss/lib/mozpkix/include/pkix/pkix.h160
-rw-r--r--security/nss/lib/mozpkix/include/pkix/pkixcheck.h65
-rw-r--r--security/nss/lib/mozpkix/include/pkix/pkixder.h520
-rw-r--r--security/nss/lib/mozpkix/include/pkix/pkixnss.h106
-rw-r--r--security/nss/lib/mozpkix/include/pkix/pkixtypes.h400
-rw-r--r--security/nss/lib/mozpkix/include/pkix/pkixutil.h265
-rw-r--r--security/nss/lib/mozpkix/lib/pkixbuild.cpp418
-rw-r--r--security/nss/lib/mozpkix/lib/pkixcert.cpp323
-rw-r--r--security/nss/lib/mozpkix/lib/pkixcheck.cpp1100
-rw-r--r--security/nss/lib/mozpkix/lib/pkixder.cpp611
-rw-r--r--security/nss/lib/mozpkix/lib/pkixnames.cpp2050
-rw-r--r--security/nss/lib/mozpkix/lib/pkixnss.cpp236
-rw-r--r--security/nss/lib/mozpkix/lib/pkixocsp.cpp1012
-rw-r--r--security/nss/lib/mozpkix/lib/pkixresult.cpp46
-rw-r--r--security/nss/lib/mozpkix/lib/pkixtime.cpp78
-rw-r--r--security/nss/lib/mozpkix/lib/pkixverify.cpp106
-rw-r--r--security/nss/lib/mozpkix/mozpkix.gyp60
-rw-r--r--security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp211
-rw-r--r--security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp364
-rw-r--r--security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp1155
-rw-r--r--security/nss/lib/mozpkix/tools/DottedOIDToCode.py216
28 files changed, 10673 insertions, 0 deletions
diff --git a/security/nss/lib/mozpkix/.clang-format b/security/nss/lib/mozpkix/.clang-format
new file mode 100644
index 000000000..06e3c5115
--- /dev/null
+++ b/security/nss/lib/mozpkix/.clang-format
@@ -0,0 +1,4 @@
+---
+Language: Cpp
+BasedOnStyle: Google
+...
diff --git a/security/nss/lib/mozpkix/exports.gyp b/security/nss/lib/mozpkix/exports.gyp
new file mode 100644
index 000000000..248efc910
--- /dev/null
+++ b/security/nss/lib/mozpkix/exports.gyp
@@ -0,0 +1,47 @@
+# 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/.
+{
+ 'includes': [
+ '../../coreconf/config.gypi'
+ ],
+ 'targets': [
+ {
+ 'target_name': 'lib_mozpkix_exports',
+ 'type': 'none',
+ 'copies': [
+ {
+ 'files': [
+ '<(DEPTH)/cpputil/nss_scoped_ptrs.h',
+ 'include/pkix/Input.h',
+ 'include/pkix/Time.h',
+ 'include/pkix/Result.h',
+ 'include/pkix/pkix.h',
+ 'include/pkix/pkixnss.h',
+ 'include/pkix/pkixtypes.h',
+ 'include/pkix/pkixutil.h',
+ 'include/pkix/pkixcheck.h',
+ 'include/pkix/pkixder.h',
+ ],
+ 'destination': '<(nss_public_dist_dir)/<(module)/mozpkix'
+ },
+ ],
+ },
+ {
+ 'target_name': 'lib_mozpkix_test_exports',
+ 'type': 'none',
+ 'copies': [
+ {
+ 'files': [
+ 'include/pkix-test/pkixtestutil.h',
+ 'include/pkix-test/pkixtestnss.h',
+ ],
+ 'destination': '<(nss_public_dist_dir)/<(module)/mozpkix/test'
+ },
+ ],
+ }
+ ],
+ 'variables': {
+ 'module': 'nss'
+ }
+} \ No newline at end of file
diff --git a/security/nss/lib/mozpkix/include/pkix-test/pkixtestnss.h b/security/nss/lib/mozpkix/include/pkix-test/pkixtestnss.h
new file mode 100644
index 000000000..5ae776f6a
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix-test/pkixtestnss.h
@@ -0,0 +1,48 @@
+/* -*- 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 2018 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.
+ */
+
+// This file provides some implementation-specific test utilities. This is only
+// necessary because some PSM xpcshell test utilities overlap in functionality
+// with these test utilities, so the underlying implementation is shared.
+
+#ifndef mozilla_pkix_test_pkixtestnss_h
+#define mozilla_pkix_test_pkixtestnss_h
+
+#include <keyhi.h>
+#include <keythi.h>
+#include "mozpkix/nss_scoped_ptrs.h"
+#include "mozpkix/test/pkixtestutil.h"
+
+namespace mozilla {
+namespace pkix {
+namespace test {
+
+TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
+ const ScopedSECKEYPublicKey& publicKey,
+ const ScopedSECKEYPrivateKey& privateKey);
+}
+}
+} // namespace mozilla::pkix::test
+
+#endif // mozilla_pkix_test_pkixtestnss_h
diff --git a/security/nss/lib/mozpkix/include/pkix-test/pkixtestutil.h b/security/nss/lib/mozpkix/include/pkix-test/pkixtestutil.h
new file mode 100644
index 000000000..55c435419
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix-test/pkixtestutil.h
@@ -0,0 +1,406 @@
+/* -*- 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_test_pkixtestutil_h
+#define mozilla_pkix_test_pkixtestutil_h
+
+#include <cstdint>
+#include <cstring>
+#include <ctime>
+#include <string>
+
+#include "mozpkix/pkixtypes.h"
+
+namespace mozilla {
+namespace pkix {
+namespace test {
+
+typedef std::basic_string<uint8_t> ByteString;
+
+inline bool ENCODING_FAILED(const ByteString& bs) { return bs.empty(); }
+
+template <size_t L>
+inline ByteString BytesToByteString(const uint8_t (&bytes)[L]) {
+ return ByteString(bytes, L);
+}
+
+// XXX: Ideally, we should define this instead:
+//
+// template <typename T, std::size_t N>
+// constexpr inline std::size_t
+// ArrayLength(T (&)[N])
+// {
+// return N;
+// }
+//
+// However, we don't because not all supported compilers support constexpr,
+// and we need to calculate array lengths in static_assert sometimes.
+//
+// XXX: Evaluates its argument twice
+#define MOZILLA_PKIX_ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
+
+bool InputEqualsByteString(Input input, const ByteString& bs);
+ByteString InputToByteString(Input input);
+
+// python DottedOIDToCode.py --tlv id-kp-OCSPSigning 1.3.6.1.5.5.7.3.9
+static const uint8_t tlv_id_kp_OCSPSigning[] = {0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x09};
+
+// python DottedOIDToCode.py --tlv id-kp-serverAuth 1.3.6.1.5.5.7.3.1
+static const uint8_t tlv_id_kp_serverAuth[] = {0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x01};
+
+enum class TestDigestAlgorithmID {
+ MD2,
+ MD5,
+ SHA1,
+ SHA224,
+ SHA256,
+ SHA384,
+ SHA512,
+};
+
+struct TestPublicKeyAlgorithm {
+ explicit TestPublicKeyAlgorithm(const ByteString& aAlgorithmIdentifier)
+ : algorithmIdentifier(aAlgorithmIdentifier) {}
+ bool operator==(const TestPublicKeyAlgorithm& other) const {
+ return algorithmIdentifier == other.algorithmIdentifier;
+ }
+ ByteString algorithmIdentifier;
+};
+
+ByteString DSS_P();
+ByteString DSS_Q();
+ByteString DSS_G();
+
+TestPublicKeyAlgorithm DSS();
+TestPublicKeyAlgorithm RSA_PKCS1();
+
+struct TestSignatureAlgorithm {
+ TestSignatureAlgorithm(const TestPublicKeyAlgorithm& publicKeyAlg,
+ TestDigestAlgorithmID digestAlg,
+ const ByteString& algorithmIdentifier, bool accepted);
+
+ TestPublicKeyAlgorithm publicKeyAlg;
+ TestDigestAlgorithmID digestAlg;
+ ByteString algorithmIdentifier;
+ bool accepted;
+};
+
+TestSignatureAlgorithm md2WithRSAEncryption();
+TestSignatureAlgorithm md5WithRSAEncryption();
+TestSignatureAlgorithm sha1WithRSAEncryption();
+TestSignatureAlgorithm sha256WithRSAEncryption();
+
+// e.g. YMDHMS(2016, 12, 31, 1, 23, 45) => 2016-12-31:01:23:45 (GMT)
+mozilla::pkix::Time YMDHMS(uint16_t year, uint16_t month, uint16_t day,
+ uint16_t hour, uint16_t minutes, uint16_t seconds);
+
+ByteString TLV(uint8_t tag, size_t length, const ByteString& value);
+
+inline ByteString TLV(uint8_t tag, const ByteString& value) {
+ return TLV(tag, value.length(), value);
+}
+
+// Although we can't enforce it without relying on Cuser-defined literals,
+// which aren't supported by all of our compilers yet, you should only pass
+// string literals as the last parameter to the following two functions.
+
+template <size_t N>
+inline ByteString TLV(uint8_t tag, const char (&value)[N]) {
+ static_assert(N > 0, "cannot have string literal of size 0");
+ assert(value[N - 1] == 0);
+ return TLV(tag, ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1));
+}
+
+template <size_t N>
+inline ByteString TLV(uint8_t tag, size_t length, const char (&value)[N]) {
+ static_assert(N > 0, "cannot have string literal of size 0");
+ assert(value[N - 1] == 0);
+ return TLV(tag, length,
+ ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1));
+}
+
+ByteString Boolean(bool value);
+ByteString Integer(long value);
+
+ByteString CN(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/);
+
+inline ByteString CN(const char* value,
+ uint8_t encodingTag = 0x0c /*UTF8String*/) {
+ return CN(
+ ByteString(reinterpret_cast<const uint8_t*>(value), std::strlen(value)),
+ encodingTag);
+}
+
+ByteString OU(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/);
+
+inline ByteString OU(const char* value,
+ uint8_t encodingTag = 0x0c /*UTF8String*/) {
+ return OU(
+ ByteString(reinterpret_cast<const uint8_t*>(value), std::strlen(value)),
+ encodingTag);
+}
+
+ByteString emailAddress(const ByteString&);
+
+inline ByteString emailAddress(const char* value) {
+ return emailAddress(
+ ByteString(reinterpret_cast<const uint8_t*>(value), std::strlen(value)));
+}
+
+// RelativeDistinguishedName ::=
+// SET SIZE (1..MAX) OF AttributeTypeAndValue
+//
+ByteString RDN(const ByteString& avas);
+
+// Name ::= CHOICE { -- only one possibility for now --
+// rdnSequence RDNSequence }
+//
+// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+//
+ByteString Name(const ByteString& rdns);
+
+inline ByteString CNToDERName(const ByteString& cn) {
+ return Name(RDN(CN(cn)));
+}
+
+inline ByteString CNToDERName(const char* cn) { return Name(RDN(CN(cn))); }
+
+// GeneralName ::= CHOICE {
+// otherName [0] OtherName,
+// rfc822Name [1] IA5String,
+// dNSName [2] IA5String,
+// x400Address [3] ORAddress,
+// directoryName [4] Name,
+// ediPartyName [5] EDIPartyName,
+// uniformResourceIdentifier [6] IA5String,
+// iPAddress [7] OCTET STRING,
+// registeredID [8] OBJECT IDENTIFIER }
+
+inline ByteString RFC822Name(const ByteString& name) {
+ // (2 << 6) means "context-specific", 1 is the GeneralName tag.
+ return TLV((2 << 6) | 1, name);
+}
+
+template <size_t L>
+inline ByteString RFC822Name(const char (&bytes)[L]) {
+ return RFC822Name(
+ ByteString(reinterpret_cast<const uint8_t*>(&bytes), L - 1));
+}
+
+inline ByteString DNSName(const ByteString& name) {
+ // (2 << 6) means "context-specific", 2 is the GeneralName tag.
+ return TLV((2 << 6) | 2, name);
+}
+
+template <size_t L>
+inline ByteString DNSName(const char (&bytes)[L]) {
+ return DNSName(ByteString(reinterpret_cast<const uint8_t*>(&bytes), L - 1));
+}
+
+inline ByteString DirectoryName(const ByteString& name) {
+ // (2 << 6) means "context-specific", (1 << 5) means "constructed", and 4 is
+ // the DirectoryName tag.
+ return TLV((2 << 6) | (1 << 5) | 4, name);
+}
+
+inline ByteString IPAddress() {
+ // (2 << 6) means "context-specific", 7 is the GeneralName tag.
+ return TLV((2 << 6) | 7, ByteString());
+}
+
+template <size_t L>
+inline ByteString IPAddress(const uint8_t (&bytes)[L]) {
+ // (2 << 6) means "context-specific", 7 is the GeneralName tag.
+ return TLV((2 << 6) | 7, ByteString(bytes, L));
+}
+
+// Names should be zero or more GeneralNames, like DNSName and IPAddress return,
+// concatenated together.
+//
+// CreatedEncodedSubjectAltName(ByteString()) results in a SAN with an empty
+// sequence. CreateEmptyEncodedSubjectName() results in a SAN without any
+// sequence.
+ByteString CreateEncodedSubjectAltName(const ByteString& names);
+ByteString CreateEncodedEmptySubjectAltName();
+
+class TestKeyPair {
+ public:
+ virtual ~TestKeyPair() {}
+
+ const TestPublicKeyAlgorithm publicKeyAlg;
+
+ // The DER encoding of the entire SubjectPublicKeyInfo structure. This is
+ // what is encoded in certificates.
+ const ByteString subjectPublicKeyInfo;
+
+ // The DER encoding of subjectPublicKeyInfo.subjectPublicKey. This is what is
+ // hashed to create CertIDs for OCSP.
+ const ByteString subjectPublicKey;
+
+ virtual Result SignData(const ByteString& tbs,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*out*/ ByteString& signature) const = 0;
+
+ virtual TestKeyPair* Clone() const = 0;
+
+ protected:
+ TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg,
+ const ByteString& spk);
+ TestKeyPair(const TestKeyPair&) = delete;
+ void operator=(const TestKeyPair&) = delete;
+};
+
+TestKeyPair* CloneReusedKeyPair();
+TestKeyPair* GenerateKeyPair();
+TestKeyPair* GenerateDSSKeyPair();
+inline void DeleteTestKeyPair(TestKeyPair* keyPair) { delete keyPair; }
+typedef std::unique_ptr<TestKeyPair> ScopedTestKeyPair;
+
+Result TestVerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo);
+Result TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo);
+Result TestDigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf, size_t digestBufLen);
+
+// Replace one substring in item with another of the same length, but only if
+// the substring was found exactly once. The "same length" restriction is
+// useful for avoiding invalidating lengths encoded within the item. The
+// "only once" restriction is helpful for avoiding making accidental changes.
+//
+// The string to search for must be 8 or more bytes long so that it is
+// extremely unlikely that there will ever be any false positive matches
+// in digital signatures, keys, hashes, etc.
+Result TamperOnce(/*in/out*/ ByteString& item, const ByteString& from,
+ const ByteString& to);
+
+///////////////////////////////////////////////////////////////////////////////
+// Encode Certificates
+
+enum Version { v1 = 0, v2 = 1, v3 = 2 };
+
+// signature is assumed to be the DER encoding of an AlgorithmIdentifer. It is
+// put into the signature field of the TBSCertificate. In most cases, it will
+// be the same as signatureAlgorithm, which is the algorithm actually used
+// to sign the certificate.
+// serialNumber is assumed to be the DER encoding of an INTEGER.
+//
+// If extensions is null, then no extensions will be encoded. Otherwise,
+// extensions must point to an array of ByteStrings, terminated with an empty
+// ByteString. (If the first item of the array is empty then an empty
+// Extensions sequence will be encoded.)
+ByteString CreateEncodedCertificate(
+ long version, const TestSignatureAlgorithm& signature,
+ const ByteString& serialNumber, const ByteString& issuerNameDER,
+ time_t notBefore, time_t notAfter, const ByteString& subjectNameDER,
+ const TestKeyPair& subjectKeyPair,
+ /*optional*/ const ByteString* extensions, const TestKeyPair& issuerKeyPair,
+ const TestSignatureAlgorithm& signatureAlgorithm);
+
+ByteString CreateEncodedSerialNumber(long value);
+
+enum class Critical { No = 0, Yes = 1 };
+
+ByteString CreateEncodedBasicConstraints(
+ bool isCA,
+ /*optional in*/ const long* pathLenConstraint, Critical critical);
+
+// Creates a DER-encoded extKeyUsage extension with one EKU OID.
+ByteString CreateEncodedEKUExtension(Input eku, Critical critical);
+
+///////////////////////////////////////////////////////////////////////////////
+// Encode OCSP responses
+
+class OCSPResponseExtension final {
+ public:
+ OCSPResponseExtension();
+
+ ByteString id;
+ bool critical;
+ ByteString value;
+ OCSPResponseExtension* next;
+};
+
+class OCSPResponseContext final {
+ public:
+ OCSPResponseContext(const CertID& certID, std::time_t time);
+
+ const CertID& certID;
+ // TODO(bug 980538): add a way to specify what certificates are included.
+
+ // The fields below are in the order that they appear in an OCSP response.
+
+ enum OCSPResponseStatus {
+ successful = 0,
+ malformedRequest = 1,
+ internalError = 2,
+ tryLater = 3,
+ // 4 is not used
+ sigRequired = 5,
+ unauthorized = 6,
+ };
+ uint8_t responseStatus; // an OCSPResponseStatus or an invalid value
+ bool skipResponseBytes; // If true, don't include responseBytes
+
+ // responderID
+ ByteString signerNameDER; // If set, responderID will use the byName
+ // form; otherwise responderID will use the
+ // byKeyHash form.
+
+ std::time_t producedAt;
+
+ // SingleResponse extensions (for the certID given in the constructor).
+ OCSPResponseExtension* singleExtensions;
+ // ResponseData extensions.
+ OCSPResponseExtension* responseExtensions;
+ bool includeEmptyExtensions; // If true, include the extension wrapper
+ // regardless of if there are any actual
+ // extensions.
+ ScopedTestKeyPair signerKeyPair;
+ TestSignatureAlgorithm signatureAlgorithm;
+ bool badSignature; // If true, alter the signature to fail verification
+ const ByteString* certs; // optional; array terminated by an empty string
+
+ // The following fields are on a per-SingleResponse basis. In the future we
+ // may support including multiple SingleResponses per response.
+ enum CertStatus {
+ good = 0,
+ revoked = 1,
+ unknown = 2,
+ };
+ uint8_t certStatus; // CertStatus or an invalid value
+ std::time_t revocationTime; // For certStatus == revoked
+ std::time_t thisUpdate;
+ std::time_t nextUpdate;
+ bool includeNextUpdate;
+};
+
+ByteString CreateEncodedOCSPResponse(OCSPResponseContext& context);
+}
+}
+} // namespace mozilla::pkix::test
+
+#endif // mozilla_pkix_test_pkixtestutil_h
diff --git a/security/nss/lib/mozpkix/include/pkix/Input.h b/security/nss/lib/mozpkix/include/pkix/Input.h
new file mode 100644
index 000000000..11b2a0f7e
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/Input.h
@@ -0,0 +1,310 @@
+/* -*- 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_Input_h
+#define mozilla_pkix_Input_h
+
+#include <algorithm>
+
+#include "mozpkix/Result.h"
+#include "stdint.h"
+
+namespace mozilla {
+namespace pkix {
+
+class Reader;
+
+// An Input is a safety-oriented immutable weak reference to a array of bytes
+// of a known size. The data can only be legally accessed by constructing a
+// Reader object, which guarantees all accesses to the data are memory safe.
+// Neither Input not Reader provide any facilities for modifying the data
+// they reference.
+//
+// Inputs are small and should usually be passed by value, not by reference,
+// though for inline functions the distinction doesn't matter:
+//
+// Result GoodExample(Input input);
+// Result BadExample(const Input& input);
+// Result WorseExample(const uint8_t* input, size_t len);
+//
+// Note that in the example, GoodExample has the same performance
+// characteristics as WorseExample, but with much better safety guarantees.
+class Input final {
+ public:
+ typedef uint16_t size_type;
+
+ // This constructor is useful for inputs that are statically known to be of a
+ // fixed size, e.g.:
+ //
+ // static const uint8_t EXPECTED_BYTES[] = { 0x00, 0x01, 0x02 };
+ // const Input expected(EXPECTED_BYTES);
+ //
+ // This is equivalent to (and preferred over):
+ //
+ // static const uint8_t EXPECTED_BYTES[] = { 0x00, 0x01, 0x02 };
+ // Input expected;
+ // Result rv = expected.Init(EXPECTED_BYTES, sizeof EXPECTED_BYTES);
+ template <size_type N>
+ explicit Input(const uint8_t (&aData)[N]) : data(aData), len(N) {}
+
+ // Construct a valid, empty, Init-able Input.
+ Input() : data(nullptr), len(0u) {}
+
+ // This is intentionally not explicit in order to allow value semantics.
+ Input(const Input&) = default;
+
+ // Initialize the input. data must be non-null and len must be less than
+ // 65536. Init may not be called more than once.
+ Result Init(const uint8_t* aData, size_t aLen) {
+ if (this->data) {
+ // already initialized
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ if (!aData || aLen > 0xffffu) {
+ // input too large
+ return Result::ERROR_BAD_DER;
+ }
+
+ this->data = aData;
+ this->len = aLen;
+
+ return Success;
+ }
+
+ // Initialize the input to be equivalent to the given input. Init may not be
+ // called more than once.
+ //
+ // This is basically operator=, but it wasn't given that name because
+ // normally callers do not check the result of operator=, and normally
+ // operator= can be used multiple times.
+ Result Init(Input other) { return Init(other.data, other.len); }
+
+ // Returns the length of the input.
+ //
+ // Having the return type be size_type instead of size_t avoids the need for
+ // callers to ensure that the result is small enough.
+ size_type GetLength() const { return static_cast<size_type>(len); }
+
+ // Don't use this. It is here because we have some "friend" functions that we
+ // don't want to declare in this header file.
+ const uint8_t* UnsafeGetData() const { return data; }
+
+ private:
+ const uint8_t* data;
+ size_t len;
+
+ void operator=(const Input&) = delete; // Use Init instead.
+};
+
+inline bool InputsAreEqual(const Input& a, const Input& b) {
+ return a.GetLength() == b.GetLength() &&
+ std::equal(a.UnsafeGetData(), a.UnsafeGetData() + a.GetLength(),
+ b.UnsafeGetData());
+}
+
+// An Reader is a cursor/iterator through the contents of an Input, designed to
+// maximize safety during parsing while minimizing the performance cost of that
+// safety. In particular, all methods do strict bounds checking to ensure
+// buffer overflows are impossible, and they are all inline so that the
+// compiler can coalesce as many of those checks together as possible.
+//
+// In general, Reader allows for one byte of lookahead and no backtracking.
+// However, the Match* functions internally may have more lookahead.
+class Reader final {
+ public:
+ Reader() : input(nullptr), end(nullptr) {}
+
+ explicit Reader(Input aInput)
+ : input(aInput.UnsafeGetData()),
+ end(aInput.UnsafeGetData() + aInput.GetLength()) {}
+
+ Result Init(Input aInput) {
+ if (this->input) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ this->input = aInput.UnsafeGetData();
+ this->end = aInput.UnsafeGetData() + aInput.GetLength();
+ return Success;
+ }
+
+ bool Peek(uint8_t expectedByte) const {
+ return input < end && *input == expectedByte;
+ }
+
+ Result Read(uint8_t& out) {
+ Result rv = EnsureLength(1);
+ if (rv != Success) {
+ return rv;
+ }
+ out = *input++;
+ return Success;
+ }
+
+ Result Read(uint16_t& out) {
+ Result rv = EnsureLength(2);
+ if (rv != Success) {
+ return rv;
+ }
+ out = *input++;
+ out <<= 8u;
+ out |= *input++;
+ return Success;
+ }
+
+ template <Input::size_type N>
+ bool MatchRest(const uint8_t (&toMatch)[N]) {
+ // Normally we use EnsureLength which compares (input + len < end), but
+ // here we want to be sure that there is nothing following the matched
+ // bytes
+ if (static_cast<size_t>(end - input) != N) {
+ return false;
+ }
+ if (!std::equal(input, end, toMatch)) {
+ return false;
+ }
+ input = end;
+ return true;
+ }
+
+ bool MatchRest(Input toMatch) {
+ // Normally we use EnsureLength which compares (input + len < end), but
+ // here we want to be sure that there is nothing following the matched
+ // bytes
+ size_t remaining = static_cast<size_t>(end - input);
+ if (toMatch.GetLength() != remaining) {
+ return false;
+ }
+ if (!std::equal(input, end, toMatch.UnsafeGetData())) {
+ return false;
+ }
+ input = end;
+ return true;
+ }
+
+ Result Skip(Input::size_type len) {
+ Result rv = EnsureLength(len);
+ if (rv != Success) {
+ return rv;
+ }
+ input += len;
+ return Success;
+ }
+
+ Result Skip(Input::size_type len, Reader& skipped) {
+ Result rv = EnsureLength(len);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = skipped.Init(input, len);
+ if (rv != Success) {
+ return rv;
+ }
+ input += len;
+ return Success;
+ }
+
+ Result Skip(Input::size_type len, /*out*/ Input& skipped) {
+ Result rv = EnsureLength(len);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = skipped.Init(input, len);
+ if (rv != Success) {
+ return rv;
+ }
+ input += len;
+ return Success;
+ }
+
+ void SkipToEnd() { input = end; }
+
+ Result SkipToEnd(/*out*/ Input& skipped) {
+ return Skip(static_cast<Input::size_type>(end - input), skipped);
+ }
+
+ Result EnsureLength(Input::size_type len) {
+ if (static_cast<size_t>(end - input) < len) {
+ return Result::ERROR_BAD_DER;
+ }
+ return Success;
+ }
+
+ bool AtEnd() const { return input == end; }
+
+ class Mark final {
+ public:
+ Mark(const Mark&) = default; // Intentionally not explicit.
+ private:
+ friend class Reader;
+ Mark(const Reader& aInput, const uint8_t* aMark)
+ : input(aInput), mark(aMark) {}
+ const Reader& input;
+ const uint8_t* const mark;
+ void operator=(const Mark&) = delete;
+ };
+
+ Mark GetMark() const { return Mark(*this, input); }
+
+ Result GetInput(const Mark& mark, /*out*/ Input& item) {
+ if (&mark.input != this || mark.mark > input) {
+ return NotReached("invalid mark", Result::FATAL_ERROR_INVALID_ARGS);
+ }
+ return item.Init(mark.mark,
+ static_cast<Input::size_type>(input - mark.mark));
+ }
+
+ private:
+ Result Init(const uint8_t* data, Input::size_type len) {
+ if (input) {
+ // already initialized
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ input = data;
+ end = data + len;
+ return Success;
+ }
+
+ const uint8_t* input;
+ const uint8_t* end;
+
+ Reader(const Reader&) = delete;
+ void operator=(const Reader&) = delete;
+};
+
+inline bool InputContains(const Input& input, uint8_t toFind) {
+ Reader reader(input);
+ for (;;) {
+ uint8_t b;
+ if (reader.Read(b) != Success) {
+ return false;
+ }
+ if (b == toFind) {
+ return true;
+ }
+ }
+}
+}
+} // namespace mozilla::pkix
+
+#endif // mozilla_pkix_Input_h
diff --git a/security/nss/lib/mozpkix/include/pkix/Result.h b/security/nss/lib/mozpkix/include/pkix/Result.h
new file mode 100644
index 000000000..29461dc1a
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/Result.h
@@ -0,0 +1,219 @@
+/* -*- 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_Result_h
+#define mozilla_pkix_Result_h
+
+#include <cassert>
+
+namespace mozilla {
+namespace pkix {
+
+static const unsigned int FATAL_ERROR_FLAG = 0x800;
+
+// ----------------------------------------------------------------------------
+// SELECTED ERROR CODE EXPLANATIONS
+//
+// Result::ERROR_UNTRUSTED_CERT
+// means that the end-entity certificate was actively distrusted.
+// Result::ERROR_UNTRUSTED_ISSUER
+// means that path building failed because of active distrust.
+// Result::ERROR_INVALID_DER_TIME
+// means the DER-encoded time was unexpected, such as being before the
+// UNIX epoch (allowed by X500, but not valid here).
+// Result::ERROR_EXPIRED_CERTIFICATE
+// means the end entity certificate expired.
+// Result::ERROR_EXPIRED_ISSUER_CERTIFICATE
+// means the CA certificate expired.
+// Result::ERROR_UNKNOWN_ISSUER
+// means that the CA could not be found in the root store.
+// Result::ERROR_POLICY_VALIDATION_FAILED
+// means that an encoded policy could not be applied or wasn't present
+// when expected. Usually this is in the context of Extended Validation.
+// Result::ERROR_BAD_CERT_DOMAIN
+// means that the certificate's name couldn't be matched to the
+// reference identifier.
+// Result::ERROR_CERT_NOT_IN_NAME_SPACE
+// typically means the certificate violates name constraints applied
+// by the issuer.
+// Result::ERROR_BAD_DER
+// means the input was improperly encoded.
+// Result::ERROR_UNKNOWN_ERROR
+// means that an external library (NSS) provided an error we didn't
+// anticipate. See the map below in Result.h to add new ones.
+// Result::FATAL_ERROR_LIBRARY_FAILURE
+// is an unexpected fatal error indicating a library had an unexpected
+// failure, and we can't proceed.
+// Result::FATAL_ERROR_INVALID_ARGS
+// means that we violated our own expectations on inputs and there's a
+// bug somewhere.
+// Result::FATAL_ERROR_INVALID_STATE
+// means that we violated our own expectations on state and there's a
+// bug somewhere.
+// Result::FATAL_ERROR_NO_MEMORY
+// means a memory allocation failed, prohibiting validation.
+// ----------------------------------------------------------------------------
+
+// The first argument to MOZILLA_PKIX_MAP() is used for building the mapping
+// from error code to error name in MapResultToName.
+//
+// The second argument is for defining the value for the enum literal in the
+// Result enum class.
+//
+// The third argument to MOZILLA_PKIX_MAP() is used, along with the first
+// argument, for maintaining the mapping of mozilla::pkix error codes to
+// NSS/NSPR error codes in pkixnss.cpp.
+#define MOZILLA_PKIX_MAP_LIST \
+ MOZILLA_PKIX_MAP(Success, 0, 0) \
+ MOZILLA_PKIX_MAP(ERROR_BAD_DER, 1, SEC_ERROR_BAD_DER) \
+ MOZILLA_PKIX_MAP(ERROR_CA_CERT_INVALID, 2, SEC_ERROR_CA_CERT_INVALID) \
+ MOZILLA_PKIX_MAP(ERROR_BAD_SIGNATURE, 3, SEC_ERROR_BAD_SIGNATURE) \
+ MOZILLA_PKIX_MAP(ERROR_CERT_BAD_ACCESS_LOCATION, 4, \
+ SEC_ERROR_CERT_BAD_ACCESS_LOCATION) \
+ MOZILLA_PKIX_MAP(ERROR_CERT_NOT_IN_NAME_SPACE, 5, \
+ SEC_ERROR_CERT_NOT_IN_NAME_SPACE) \
+ MOZILLA_PKIX_MAP(ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, 6, \
+ SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED) \
+ MOZILLA_PKIX_MAP(ERROR_CONNECT_REFUSED, 7, PR_CONNECT_REFUSED_ERROR) \
+ MOZILLA_PKIX_MAP(ERROR_EXPIRED_CERTIFICATE, 8, \
+ SEC_ERROR_EXPIRED_CERTIFICATE) \
+ MOZILLA_PKIX_MAP(ERROR_EXTENSION_VALUE_INVALID, 9, \
+ SEC_ERROR_EXTENSION_VALUE_INVALID) \
+ MOZILLA_PKIX_MAP(ERROR_INADEQUATE_CERT_TYPE, 10, \
+ SEC_ERROR_INADEQUATE_CERT_TYPE) \
+ MOZILLA_PKIX_MAP(ERROR_INADEQUATE_KEY_USAGE, 11, \
+ SEC_ERROR_INADEQUATE_KEY_USAGE) \
+ MOZILLA_PKIX_MAP(ERROR_INVALID_ALGORITHM, 12, SEC_ERROR_INVALID_ALGORITHM) \
+ MOZILLA_PKIX_MAP(ERROR_INVALID_DER_TIME, 13, SEC_ERROR_INVALID_TIME) \
+ MOZILLA_PKIX_MAP(ERROR_KEY_PINNING_FAILURE, 14, \
+ MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) \
+ MOZILLA_PKIX_MAP(ERROR_PATH_LEN_CONSTRAINT_INVALID, 15, \
+ SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID) \
+ MOZILLA_PKIX_MAP(ERROR_POLICY_VALIDATION_FAILED, 16, \
+ SEC_ERROR_POLICY_VALIDATION_FAILED) \
+ MOZILLA_PKIX_MAP(ERROR_REVOKED_CERTIFICATE, 17, \
+ SEC_ERROR_REVOKED_CERTIFICATE) \
+ MOZILLA_PKIX_MAP(ERROR_UNKNOWN_CRITICAL_EXTENSION, 18, \
+ SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION) \
+ MOZILLA_PKIX_MAP(ERROR_UNKNOWN_ERROR, 19, PR_UNKNOWN_ERROR) \
+ MOZILLA_PKIX_MAP(ERROR_UNKNOWN_ISSUER, 20, SEC_ERROR_UNKNOWN_ISSUER) \
+ MOZILLA_PKIX_MAP(ERROR_UNTRUSTED_CERT, 21, SEC_ERROR_UNTRUSTED_CERT) \
+ MOZILLA_PKIX_MAP(ERROR_UNTRUSTED_ISSUER, 22, SEC_ERROR_UNTRUSTED_ISSUER) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_BAD_SIGNATURE, 23, SEC_ERROR_OCSP_BAD_SIGNATURE) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_INVALID_SIGNING_CERT, 24, \
+ SEC_ERROR_OCSP_INVALID_SIGNING_CERT) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_MALFORMED_REQUEST, 25, \
+ SEC_ERROR_OCSP_MALFORMED_REQUEST) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_MALFORMED_RESPONSE, 26, \
+ SEC_ERROR_OCSP_MALFORMED_RESPONSE) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_OLD_RESPONSE, 27, SEC_ERROR_OCSP_OLD_RESPONSE) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_REQUEST_NEEDS_SIG, 28, \
+ SEC_ERROR_OCSP_REQUEST_NEEDS_SIG) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_RESPONDER_CERT_INVALID, 29, \
+ SEC_ERROR_OCSP_RESPONDER_CERT_INVALID) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_SERVER_ERROR, 30, SEC_ERROR_OCSP_SERVER_ERROR) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_TRY_SERVER_LATER, 31, \
+ SEC_ERROR_OCSP_TRY_SERVER_LATER) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_UNAUTHORIZED_REQUEST, 32, \
+ SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_UNKNOWN_RESPONSE_STATUS, 33, \
+ SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_UNKNOWN_CERT, 34, SEC_ERROR_OCSP_UNKNOWN_CERT) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_FUTURE_RESPONSE, 35, \
+ SEC_ERROR_OCSP_FUTURE_RESPONSE) \
+ MOZILLA_PKIX_MAP(ERROR_INVALID_KEY, 36, SEC_ERROR_INVALID_KEY) \
+ MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_KEYALG, 37, SEC_ERROR_UNSUPPORTED_KEYALG) \
+ MOZILLA_PKIX_MAP(ERROR_EXPIRED_ISSUER_CERTIFICATE, 38, \
+ SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE) \
+ MOZILLA_PKIX_MAP(ERROR_CA_CERT_USED_AS_END_ENTITY, 39, \
+ MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY) \
+ MOZILLA_PKIX_MAP(ERROR_INADEQUATE_KEY_SIZE, 40, \
+ MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE) \
+ MOZILLA_PKIX_MAP(ERROR_V1_CERT_USED_AS_CA, 41, \
+ MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA) \
+ MOZILLA_PKIX_MAP(ERROR_BAD_CERT_DOMAIN, 42, SSL_ERROR_BAD_CERT_DOMAIN) \
+ MOZILLA_PKIX_MAP(ERROR_NO_RFC822NAME_MATCH, 43, \
+ MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH) \
+ MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_ELLIPTIC_CURVE, 44, \
+ SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE) \
+ MOZILLA_PKIX_MAP(ERROR_NOT_YET_VALID_CERTIFICATE, 45, \
+ MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE) \
+ MOZILLA_PKIX_MAP(ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE, 46, \
+ MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE) \
+ MOZILLA_PKIX_MAP(ERROR_UNSUPPORTED_EC_POINT_FORM, 47, \
+ SEC_ERROR_UNSUPPORTED_EC_POINT_FORM) \
+ MOZILLA_PKIX_MAP(ERROR_SIGNATURE_ALGORITHM_MISMATCH, 48, \
+ MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH) \
+ MOZILLA_PKIX_MAP(ERROR_OCSP_RESPONSE_FOR_CERT_MISSING, 49, \
+ MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING) \
+ MOZILLA_PKIX_MAP(ERROR_VALIDITY_TOO_LONG, 50, \
+ MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG) \
+ MOZILLA_PKIX_MAP(ERROR_REQUIRED_TLS_FEATURE_MISSING, 51, \
+ MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING) \
+ MOZILLA_PKIX_MAP(ERROR_INVALID_INTEGER_ENCODING, 52, \
+ MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING) \
+ MOZILLA_PKIX_MAP(ERROR_EMPTY_ISSUER_NAME, 53, \
+ MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME) \
+ MOZILLA_PKIX_MAP(ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED, 54, \
+ MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED) \
+ MOZILLA_PKIX_MAP(ERROR_SELF_SIGNED_CERT, 55, \
+ MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT) \
+ MOZILLA_PKIX_MAP(ERROR_MITM_DETECTED, 56, MOZILLA_PKIX_ERROR_MITM_DETECTED) \
+ MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_ARGS, FATAL_ERROR_FLAG | 1, \
+ SEC_ERROR_INVALID_ARGS) \
+ MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_STATE, FATAL_ERROR_FLAG | 2, \
+ PR_INVALID_STATE_ERROR) \
+ MOZILLA_PKIX_MAP(FATAL_ERROR_LIBRARY_FAILURE, FATAL_ERROR_FLAG | 3, \
+ SEC_ERROR_LIBRARY_FAILURE) \
+ MOZILLA_PKIX_MAP(FATAL_ERROR_NO_MEMORY, FATAL_ERROR_FLAG | 4, \
+ SEC_ERROR_NO_MEMORY) \
+/* nothing here */
+
+enum class Result {
+#define MOZILLA_PKIX_MAP(name, value, nss_name) name = value,
+ MOZILLA_PKIX_MAP_LIST
+#undef MOZILLA_PKIX_MAP
+};
+
+// Returns the stringified name of the given result, e.g. "Result::Success",
+// or nullptr if result is unknown (invalid).
+const char* MapResultToName(Result result);
+
+// We write many comparisons as (x != Success), and this shortened name makes
+// those comparisons clearer, especially because the shortened name often
+// results in less line wrapping.
+static const Result Success = Result::Success;
+
+inline bool IsFatalError(Result rv) {
+ return (static_cast<unsigned int>(rv) & FATAL_ERROR_FLAG) != 0;
+}
+
+inline Result NotReached(const char* /*explanation*/, Result result) {
+ assert(false);
+ return result;
+}
+}
+} // namespace mozilla::pkix
+
+#endif // mozilla_pkix_Result_h
diff --git a/security/nss/lib/mozpkix/include/pkix/Time.h b/security/nss/lib/mozpkix/include/pkix/Time.h
new file mode 100644
index 000000000..8aea5479b
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/Time.h
@@ -0,0 +1,137 @@
+/* -*- 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.
+ */
+
+#ifndef mozilla_pkix_Time_h
+#define mozilla_pkix_Time_h
+
+#include <stdint.h>
+#include <ctime>
+#include <limits>
+
+#include "mozpkix/Result.h"
+
+namespace mozilla {
+namespace pkix {
+
+// Time with a range from the first second of year 0 (AD) through at least the
+// last second of year 9999, which is the range of legal times in X.509 and
+// OCSP. This type has second-level precision. The time zone is always UTC.
+//
+// Pass by value, not by reference.
+class Time final {
+ public:
+ // Construct an uninitialized instance.
+ //
+ // This will fail to compile because there is no default constructor:
+ // Time x;
+ //
+ // This will succeed, leaving the time uninitialized:
+ // Time x(Time::uninitialized);
+ enum Uninitialized { uninitialized };
+ explicit Time(Uninitialized) {}
+
+ bool operator==(const Time& other) const {
+ return elapsedSecondsAD == other.elapsedSecondsAD;
+ }
+ bool operator>(const Time& other) const {
+ return elapsedSecondsAD > other.elapsedSecondsAD;
+ }
+ bool operator>=(const Time& other) const {
+ return elapsedSecondsAD >= other.elapsedSecondsAD;
+ }
+ bool operator<(const Time& other) const {
+ return elapsedSecondsAD < other.elapsedSecondsAD;
+ }
+ bool operator<=(const Time& other) const {
+ return elapsedSecondsAD <= other.elapsedSecondsAD;
+ }
+
+ Result AddSeconds(uint64_t seconds) {
+ if (std::numeric_limits<uint64_t>::max() - elapsedSecondsAD < seconds) {
+ return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow
+ }
+ elapsedSecondsAD += seconds;
+ return Success;
+ }
+
+ Result SubtractSeconds(uint64_t seconds) {
+ if (seconds > elapsedSecondsAD) {
+ return Result::FATAL_ERROR_INVALID_ARGS; // integer overflow
+ }
+ elapsedSecondsAD -= seconds;
+ return Success;
+ }
+
+ static const uint64_t ONE_DAY_IN_SECONDS =
+ UINT64_C(24) * UINT64_C(60) * UINT64_C(60);
+
+ private:
+ // This constructor is hidden to prevent accidents like this:
+ //
+ // Time foo(time_t t)
+ // {
+ // // WRONG! 1970-01-01-00:00:00 == time_t(0), but not Time(0)!
+ // return Time(t);
+ // }
+ explicit Time(uint64_t aElapsedSecondsAD)
+ : elapsedSecondsAD(aElapsedSecondsAD) {}
+ friend Time TimeFromElapsedSecondsAD(uint64_t);
+ friend class Duration;
+
+ uint64_t elapsedSecondsAD;
+};
+
+inline Time TimeFromElapsedSecondsAD(uint64_t aElapsedSecondsAD) {
+ return Time(aElapsedSecondsAD);
+}
+
+Time Now();
+
+// Note the epoch is the unix epoch (ie 00:00:00 UTC, 1 January 1970)
+Time TimeFromEpochInSeconds(uint64_t secondsSinceEpoch);
+
+class Duration final {
+ public:
+ Duration(Time timeA, Time timeB)
+ : durationInSeconds(
+ timeA < timeB ? timeB.elapsedSecondsAD - timeA.elapsedSecondsAD
+ : timeA.elapsedSecondsAD - timeB.elapsedSecondsAD) {}
+
+ explicit Duration(uint64_t aDurationInSeconds)
+ : durationInSeconds(aDurationInSeconds) {}
+
+ bool operator>(const Duration& other) const {
+ return durationInSeconds > other.durationInSeconds;
+ }
+ bool operator<(const Duration& other) const {
+ return durationInSeconds < other.durationInSeconds;
+ }
+
+ private:
+ uint64_t durationInSeconds;
+};
+}
+} // namespace mozilla::pkix
+
+#endif // mozilla_pkix_Time_h
diff --git a/security/nss/lib/mozpkix/include/pkix/pkix.h b/security/nss/lib/mozpkix/include/pkix/pkix.h
new file mode 100644
index 000000000..1cd6548e4
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/pkix.h
@@ -0,0 +1,160 @@
+/* -*- 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_pkix_h
+#define mozilla_pkix_pkix_h
+
+#include "mozpkix/pkixtypes.h"
+
+namespace mozilla {
+namespace pkix {
+
+// ----------------------------------------------------------------------------
+// LIMITED SUPPORT FOR CERTIFICATE POLICIES
+//
+// If SEC_OID_X509_ANY_POLICY is passed as the value of the requiredPolicy
+// parameter then all policy validation will be skipped. Otherwise, path
+// building and validation will be done for the given policy.
+//
+// In RFC 5280 terms:
+//
+// * user-initial-policy-set = { requiredPolicy }.
+// * initial-explicit-policy = true
+// * initial-any-policy-inhibit = false
+//
+// We allow intermediate cerificates to use this extension but since
+// we do not process the inhibit anyPolicy extesion we will fail if this
+// extension is present. TODO(bug 989051)
+// Because we force explicit policy and because we prohibit policy mapping, we
+// do not bother processing the policy mapping, or policy constraint.
+//
+// ----------------------------------------------------------------------------
+// ERROR RANKING
+//
+// BuildCertChain prioritizes certain checks ahead of others so that when a
+// certificate chain has multiple errors, the "most serious" error is
+// returned. In practice, this ranking of seriousness is tied directly to how
+// Firefox's certificate error override mechanism.
+//
+// The ranking is:
+//
+// 1. Active distrust (Result::ERROR_UNTRUSTED_CERT).
+// 2. Problems with issuer-independent properties for CA certificates.
+// 3. Unknown issuer (Result::ERROR_UNKNOWN_ISSUER).
+// 4. Problems with issuer-independent properties for EE certificates.
+// 5. Revocation.
+//
+// In particular, if BuildCertChain returns Result::ERROR_UNKNOWN_ISSUER then
+// the caller can call CERT_CheckCertValidTimes to determine if the certificate
+// is ALSO expired.
+//
+// It would be better if revocation were prioritized above expiration and
+// unknown issuer. However, it is impossible to do revocation checking without
+// knowing the issuer, since the issuer information is needed to validate the
+// revocation information. Also, generally revocation checking only works
+// during the validity period of the certificate.
+//
+// In general, when path building fails, BuildCertChain will return
+// Result::ERROR_UNKNOWN_ISSUER. However, if all attempted paths resulted in
+// the same error (which is trivially true when there is only one potential
+// path), more specific errors will be returned.
+//
+// ----------------------------------------------------------------------------
+// Meanings of specific error codes can be found in Result.h
+
+// This function attempts to find a trustworthy path from the supplied
+// certificate to a trust anchor. In the event that no trusted path is found,
+// the method returns an error result; the error ranking is described above.
+//
+// Parameters:
+// time:
+// Timestamp for which the chain should be valid; this is useful to
+// analyze whether a record was trustworthy when it was made.
+// requiredKeyUsageIfPresent:
+// What key usage bits must be set, if the extension is present at all,
+// to be considered a valid chain. Multiple values should be OR'd
+// together. If you don't want to specify anything, use
+// KeyUsage::noParticularKeyUsageRequired.
+// requiredEKUIfPresent:
+// What extended key usage bits must be set, if the EKU extension
+// exists, to be considered a valid chain. Multiple values should be
+// OR'd together. If you don't want to specify anything, use
+// KeyPurposeId::anyExtendedKeyUsage.
+// requiredPolicy:
+// This is the policy to apply; typically included in EV certificates.
+// If there is no policy, pass in CertPolicyId::anyPolicy.
+Result BuildCertChain(TrustDomain& trustDomain, Input cert, Time time,
+ EndEntityOrCA endEntityOrCA,
+ KeyUsage requiredKeyUsageIfPresent,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
+ /*optional*/ const Input* stapledOCSPResponse);
+
+// Verify that the given end-entity cert, which is assumed to have been already
+// validated with BuildCertChain, is valid for the given hostname. The matching
+// function attempts to implement RFC 6125 with a couple of differences:
+// - IP addresses are out of scope of RFC 6125, but this method accepts them for
+// backward compatibility (see SearchNames in pkixnames.cpp)
+// - A wildcard in a DNS-ID may only appear as the entirety of the first label.
+Result CheckCertHostname(Input cert, Input hostname,
+ NameMatchingPolicy& nameMatchingPolicy);
+
+// Construct an RFC-6960-encoded OCSP request, ready for submission to a
+// responder, for the provided CertID. The request has no extensions.
+static const size_t OCSP_REQUEST_MAX_LENGTH = 127;
+Result CreateEncodedOCSPRequest(TrustDomain& trustDomain, const CertID& certID,
+ /*out*/ uint8_t (&out)[OCSP_REQUEST_MAX_LENGTH],
+ /*out*/ size_t& outLen);
+
+// The out parameter expired will be true if the response has expired. If the
+// response also indicates a revoked or unknown certificate, that error
+// will be returned. Otherwise, Result::ERROR_OCSP_OLD_RESPONSE will be
+// returned for an expired response.
+//
+// The optional parameter thisUpdate will be the thisUpdate value of
+// the encoded response if it is considered trustworthy. Only
+// good, unknown, or revoked responses that verify correctly are considered
+// trustworthy. If the response is not trustworthy, thisUpdate will be 0.
+// Similarly, the optional parameter validThrough will be the time through
+// which the encoded response is considered trustworthy (that is, as long as
+// the given time at which to validate is less than or equal to validThrough,
+// the response will be considered trustworthy).
+Result VerifyEncodedOCSPResponse(
+ TrustDomain& trustDomain, const CertID& certID, Time time,
+ uint16_t maxLifetimeInDays, Input encodedResponse,
+ /* out */ bool& expired,
+ /* optional out */ Time* thisUpdate = nullptr,
+ /* optional out */ Time* validThrough = nullptr);
+
+// Check that the TLSFeature extensions in a given end-entity cert (which is
+// assumed to have been already validated with BuildCertChain) are satisfied.
+// The only feature which we cancurrently process a requirement for is
+// status_request (OCSP stapling) so we reject any extension that specifies a
+// requirement for another value. Empty extensions are also rejected.
+Result CheckTLSFeaturesAreSatisfied(Input& cert,
+ const Input* stapledOCSPResponse);
+}
+} // namespace mozilla::pkix
+
+#endif // mozilla_pkix_pkix_h
diff --git a/security/nss/lib/mozpkix/include/pkix/pkixcheck.h b/security/nss/lib/mozpkix/include/pkix/pkixcheck.h
new file mode 100644
index 000000000..e04780e57
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/pkixcheck.h
@@ -0,0 +1,65 @@
+/* -*- 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 "mozpkix/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
diff --git a/security/nss/lib/mozpkix/include/pkix/pkixder.h b/security/nss/lib/mozpkix/include/pkix/pkixder.h
new file mode 100644
index 000000000..3aae0ecf6
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/pkixder.h
@@ -0,0 +1,520 @@
+/* -*- 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 "mozpkix/Input.h"
+#include "mozpkix/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, Uninitialized = 255 };
+
+// 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, Uninitialized };
+
+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
diff --git a/security/nss/lib/mozpkix/include/pkix/pkixnss.h b/security/nss/lib/mozpkix/include/pkix/pkixnss.h
new file mode 100644
index 000000000..b181ca541
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/pkixnss.h
@@ -0,0 +1,106 @@
+/* -*- 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_pkixnss_h
+#define mozilla_pkix_pkixnss_h
+
+#include <seccomon.h>
+#include "mozpkix/pkixtypes.h"
+#include "prerror.h"
+
+namespace mozilla {
+namespace pkix {
+
+// Verifies the PKCS#1.5 signature on the given data using the given RSA public
+// key.
+Result VerifyRSAPKCS1SignedDigestNSS(const SignedDigest& sd,
+ Input subjectPublicKeyInfo,
+ void* pkcs11PinArg);
+
+// Verifies the ECDSA signature on the given data using the given ECC public
+// key.
+Result VerifyECDSASignedDigestNSS(const SignedDigest& sd,
+ Input subjectPublicKeyInfo,
+ void* pkcs11PinArg);
+
+// Computes the digest of the given data using the given digest algorithm.
+//
+// item contains the data to hash.
+// digestBuf must point to a buffer to where the digest will be written.
+// digestBufLen must be the size of the buffer, which must be exactly equal
+// to the size of the digest output (20 for SHA-1, 32 for SHA-256,
+// etc.)
+//
+// TODO: Taking the output buffer as (uint8_t*, size_t) is counter to our
+// other, extensive, memory safety efforts in mozilla::pkix, and we should find
+// a way to provide a more-obviously-safe interface.
+Result DigestBufNSS(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf, size_t digestBufLen);
+
+Result MapPRErrorCodeToResult(PRErrorCode errorCode);
+PRErrorCode MapResultToPRErrorCode(Result result);
+
+// The error codes within each module must fit in 16 bits. We want these
+// errors to fit in the same module as the NSS errors but not overlap with
+// any of them. Converting an NSS SEC, NSS SSL, or PSM error to an NS error
+// involves negating the value of the error and then synthesizing an error
+// in the NS_ERROR_MODULE_SECURITY module. Hence, PSM errors will start at
+// a negative value that both doesn't overlap with the current value
+// ranges for NSS errors and that will fit in 16 bits when negated.
+static const PRErrorCode ERROR_BASE = -0x4000;
+static const PRErrorCode ERROR_LIMIT = ERROR_BASE + 1000;
+
+enum ErrorCode {
+ MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = ERROR_BASE + 0,
+ MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY = ERROR_BASE + 1,
+ MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE = ERROR_BASE + 2,
+ MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA = ERROR_BASE + 3,
+ MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH = ERROR_BASE + 4,
+ MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE = ERROR_BASE + 5,
+ MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE = ERROR_BASE + 6,
+ MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH = ERROR_BASE + 7,
+ MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING = ERROR_BASE + 8,
+ MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG = ERROR_BASE + 9,
+ MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING = ERROR_BASE + 10,
+ MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING = ERROR_BASE + 11,
+ MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME = ERROR_BASE + 12,
+ MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = ERROR_BASE + 13,
+ MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = ERROR_BASE + 14,
+ MOZILLA_PKIX_ERROR_MITM_DETECTED = ERROR_BASE + 15,
+ END_OF_LIST
+};
+
+void RegisterErrorTable();
+
+inline SECItem UnsafeMapInputToSECItem(Input input) {
+ SECItem result = {siBuffer, const_cast<uint8_t*>(input.UnsafeGetData()),
+ input.GetLength()};
+ static_assert(sizeof(decltype(input.GetLength())) <= sizeof(result.len),
+ "input.GetLength() must fit in a SECItem");
+ return result;
+}
+}
+} // namespace mozilla::pkix
+
+#endif // mozilla_pkix_pkixnss_h
diff --git a/security/nss/lib/mozpkix/include/pkix/pkixtypes.h b/security/nss/lib/mozpkix/include/pkix/pkixtypes.h
new file mode 100644
index 000000000..6b12edbb1
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/pkixtypes.h
@@ -0,0 +1,400 @@
+/* -*- 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_pkixtypes_h
+#define mozilla_pkix_pkixtypes_h
+
+#include <memory>
+
+#include "mozpkix/Input.h"
+#include "mozpkix/Time.h"
+#include "stdint.h"
+
+namespace mozilla {
+namespace pkix {
+
+enum class DigestAlgorithm {
+ sha512 = 1,
+ sha384 = 2,
+ sha256 = 3,
+ sha1 = 4,
+};
+
+enum class NamedCurve {
+ // secp521r1 (OID 1.3.132.0.35, RFC 5480)
+ secp521r1 = 1,
+
+ // secp384r1 (OID 1.3.132.0.34, RFC 5480)
+ secp384r1 = 2,
+
+ // secp256r1 (OID 1.2.840.10045.3.1.7, RFC 5480)
+ secp256r1 = 3,
+};
+
+struct SignedDigest final {
+ Input digest;
+ DigestAlgorithm digestAlgorithm;
+ Input signature;
+
+ void operator=(const SignedDigest&) = delete;
+};
+
+enum class EndEntityOrCA { MustBeEndEntity = 0, MustBeCA = 1 };
+
+enum class KeyUsage : uint8_t {
+ digitalSignature = 0,
+ nonRepudiation = 1,
+ keyEncipherment = 2,
+ dataEncipherment = 3,
+ keyAgreement = 4,
+ keyCertSign = 5,
+ // cRLSign = 6,
+ // encipherOnly = 7,
+ // decipherOnly = 8,
+ noParticularKeyUsageRequired = 0xff,
+};
+
+enum class KeyPurposeId {
+ anyExtendedKeyUsage = 0,
+ id_kp_serverAuth = 1, // id-kp-serverAuth
+ id_kp_clientAuth = 2, // id-kp-clientAuth
+ id_kp_codeSigning = 3, // id-kp-codeSigning
+ id_kp_emailProtection = 4, // id-kp-emailProtection
+ id_kp_OCSPSigning = 9, // id-kp-OCSPSigning
+};
+
+struct CertPolicyId final {
+ uint16_t numBytes;
+ static const uint16_t MAX_BYTES = 24;
+ uint8_t bytes[MAX_BYTES];
+
+ bool IsAnyPolicy() const;
+ bool operator==(const CertPolicyId& other) const;
+
+ static const CertPolicyId anyPolicy;
+};
+
+enum class TrustLevel {
+ TrustAnchor = 1, // certificate is a trusted root CA certificate or
+ // equivalent *for the given policy*.
+ ActivelyDistrusted = 2, // certificate is known to be bad
+ InheritsTrust = 3 // certificate must chain to a trust anchor
+};
+
+// Extensions extracted during the verification flow.
+// See TrustDomain::NoteAuxiliaryExtension.
+enum class AuxiliaryExtension {
+ // Certificate Transparency data, specifically Signed Certificate
+ // Timestamps (SCTs). See RFC 6962.
+
+ // SCT list embedded in the end entity certificate. Called by BuildCertChain
+ // after the certificate containing the SCTs has passed the revocation checks.
+ EmbeddedSCTList = 1,
+ // SCT list from OCSP response. Called by VerifyEncodedOCSPResponse
+ // when its result is a success and the SCT list is present.
+ SCTListFromOCSPResponse = 2
+};
+
+// CertID references the information needed to do revocation checking for the
+// certificate issued by the given issuer with the given serial number.
+//
+// issuer must be the DER-encoded issuer field from the certificate for which
+// revocation checking is being done, **NOT** the subject field of the issuer
+// certificate. (Those two fields must be equal to each other, but they may not
+// be encoded exactly the same, and the encoding matters for OCSP.)
+// issuerSubjectPublicKeyInfo is the entire DER-encoded subjectPublicKeyInfo
+// field from the issuer's certificate. serialNumber is the entire DER-encoded
+// serial number from the subject certificate (the certificate for which we are
+// checking the revocation status).
+struct CertID final {
+ public:
+ CertID(Input aIssuer, Input aIssuerSubjectPublicKeyInfo, Input aSerialNumber)
+ : issuer(aIssuer),
+ issuerSubjectPublicKeyInfo(aIssuerSubjectPublicKeyInfo),
+ serialNumber(aSerialNumber) {}
+ const Input issuer;
+ const Input issuerSubjectPublicKeyInfo;
+ const Input serialNumber;
+
+ void operator=(const CertID&) = delete;
+};
+typedef std::unique_ptr<CertID> ScopedCertID;
+
+class DERArray {
+ public:
+ // Returns the number of DER-encoded items in the array.
+ virtual size_t GetLength() const = 0;
+
+ // Returns a weak (non-owning) pointer the ith DER-encoded item in the array
+ // (0-indexed). The result is guaranteed to be non-null if i < GetLength(),
+ // and the result is guaranteed to be nullptr if i >= GetLength().
+ virtual const Input* GetDER(size_t i) const = 0;
+
+ protected:
+ DERArray() {}
+ virtual ~DERArray() {}
+};
+
+// Applications control the behavior of path building and verification by
+// implementing the TrustDomain interface. The TrustDomain is used for all
+// cryptography and for determining which certificates are trusted or
+// distrusted.
+class TrustDomain {
+ public:
+ virtual ~TrustDomain() {}
+
+ // Determine the level of trust in the given certificate for the given role.
+ // This will be called for every certificate encountered during path
+ // building.
+ //
+ // When policy.IsAnyPolicy(), then no policy-related checking should be done.
+ // When !policy.IsAnyPolicy(), then GetCertTrust MUST NOT return with
+ // trustLevel == TrustAnchor unless the given cert is considered a trust
+ // anchor *for that policy*. In particular, if the user has marked an
+ // intermediate certificate as trusted, but that intermediate isn't in the
+ // list of EV roots, then GetCertTrust must result in
+ // trustLevel == InheritsTrust instead of trustLevel == TrustAnchor
+ // (assuming the candidate cert is not actively distrusted).
+ virtual Result GetCertTrust(EndEntityOrCA endEntityOrCA,
+ const CertPolicyId& policy,
+ Input candidateCertDER,
+ /*out*/ TrustLevel& trustLevel) = 0;
+
+ class IssuerChecker {
+ public:
+ // potentialIssuerDER is the complete DER encoding of the certificate to
+ // be checked as a potential issuer.
+ //
+ // If additionalNameConstraints is not nullptr then it must point to an
+ // encoded NameConstraints extension value; in that case, those name
+ // constraints will be checked in addition to any any name constraints
+ // contained in potentialIssuerDER.
+ virtual Result Check(Input potentialIssuerDER,
+ /*optional*/ const Input* additionalNameConstraints,
+ /*out*/ bool& keepGoing) = 0;
+
+ protected:
+ IssuerChecker();
+ virtual ~IssuerChecker();
+
+ IssuerChecker(const IssuerChecker&) = delete;
+ void operator=(const IssuerChecker&) = delete;
+ };
+
+ // Search for a CA certificate with the given name. The implementation must
+ // call checker.Check with the DER encoding of the potential issuer
+ // certificate. The implementation must follow these rules:
+ //
+ // * The implementation must be reentrant and must limit the amount of stack
+ // space it uses; see the note on reentrancy and stack usage below.
+ // * When checker.Check does not return Success then immediately return its
+ // return value.
+ // * When checker.Check returns Success and sets keepGoing = false, then
+ // immediately return Success.
+ // * When checker.Check returns Success and sets keepGoing = true, then
+ // call checker.Check again with a different potential issuer certificate,
+ // if any more are available.
+ // * When no more potential issuer certificates are available, return
+ // Success.
+ // * Don't call checker.Check with the same potential issuer certificate more
+ // than once in a given call of FindIssuer.
+ // * The given time parameter may be used to filter out certificates that are
+ // not valid at the given time, or it may be ignored.
+ //
+ // Note on reentrancy and stack usage: checker.Check will attempt to
+ // recursively build a certificate path from the potential issuer it is given
+ // to a trusted root, as determined by this TrustDomain. That means that
+ // checker.Check may call any/all of the methods on this TrustDomain. In
+ // particular, there will be call stacks that look like this:
+ //
+ // BuildCertChain
+ // [...]
+ // TrustDomain::FindIssuer
+ // [...]
+ // IssuerChecker::Check
+ // [...]
+ // TrustDomain::FindIssuer
+ // [...]
+ // IssuerChecker::Check
+ // [...]
+ //
+ // checker.Check is responsible for limiting the recursion to a reasonable
+ // limit.
+ //
+ // checker.Check will verify that the subject's issuer field matches the
+ // potential issuer's subject field. It will also check that the potential
+ // issuer is valid at the given time. However, if the FindIssuer
+ // implementation has an efficient way of filtering potential issuers by name
+ // and/or validity period itself, then it is probably better for performance
+ // for it to do so.
+ virtual Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker,
+ Time time) = 0;
+
+ // Called as soon as we think we have a valid chain but before revocation
+ // checks are done. This function can be used to compute additional checks,
+ // especially checks that require the entire certificate chain. This callback
+ // can also be used to save a copy of the built certificate chain for later
+ // use.
+ //
+ // This function may be called multiple times, regardless of whether it
+ // returns success or failure. It is guaranteed that BuildCertChain will not
+ // return Success unless the last call to IsChainValid returns Success.
+ // Further,
+ // it is guaranteed that when BuildCertChain returns Success the last chain
+ // passed to IsChainValid is the valid chain that should be used for further
+ // operations that require the whole chain.
+ //
+ // Keep in mind, in particular, that if the application saves a copy of the
+ // certificate chain the last invocation of IsChainValid during a validation,
+ // it is still possible for BuildCertChain to fail, in which case the
+ // application must not assume anything about the validity of the last
+ // certificate chain passed to IsChainValid; especially, it would be very
+ // wrong to assume that the certificate chain is valid.
+ //
+ // certChain.GetDER(0) is the trust anchor.
+ virtual Result IsChainValid(const DERArray& certChain, Time time,
+ const CertPolicyId& requiredPolicy) = 0;
+
+ virtual Result CheckRevocation(EndEntityOrCA endEntityOrCA,
+ const CertID& certID, Time time,
+ Duration validityDuration,
+ /*optional*/ const Input* stapledOCSPresponse,
+ /*optional*/ const Input* aiaExtension) = 0;
+
+ // Check that the given digest algorithm is acceptable for use in signatures.
+ //
+ // Return Success if the algorithm is acceptable,
+ // Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not
+ // acceptable, or another error code if another error occurred.
+ virtual Result CheckSignatureDigestAlgorithm(DigestAlgorithm digestAlg,
+ EndEntityOrCA endEntityOrCA,
+ Time notBefore) = 0;
+
+ // Check that the RSA public key size is acceptable.
+ //
+ // Return Success if the key size is acceptable,
+ // Result::ERROR_INADEQUATE_KEY_SIZE if the key size is not acceptable,
+ // or another error code if another error occurred.
+ virtual Result CheckRSAPublicKeyModulusSizeInBits(
+ EndEntityOrCA endEntityOrCA, unsigned int modulusSizeInBits) = 0;
+
+ // Verify the given RSA PKCS#1.5 signature on the given digest using the
+ // given RSA public key.
+ //
+ // CheckRSAPublicKeyModulusSizeInBits will be called before calling this
+ // function, so it is not necessary to repeat those checks here. However,
+ // VerifyRSAPKCS1SignedDigest *is* responsible for doing the mathematical
+ // verification of the public key validity as specified in NIST SP 800-56A.
+ virtual Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) = 0;
+
+ // Check that the given named ECC curve is acceptable for ECDSA signatures.
+ //
+ // Return Success if the curve is acceptable,
+ // Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE if the curve is not acceptable,
+ // or another error code if another error occurred.
+ virtual Result CheckECDSACurveIsAcceptable(EndEntityOrCA endEntityOrCA,
+ NamedCurve curve) = 0;
+
+ // Verify the given ECDSA signature on the given digest using the given ECC
+ // public key.
+ //
+ // CheckECDSACurveIsAcceptable will be called before calling this function,
+ // so it is not necessary to repeat that check here. However,
+ // VerifyECDSASignedDigest *is* responsible for doing the mathematical
+ // verification of the public key validity as specified in NIST SP 800-56A.
+ virtual Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) = 0;
+
+ // Check that the validity duration is acceptable.
+ //
+ // Return Success if the validity duration is acceptable,
+ // Result::ERROR_VALIDITY_TOO_LONG if the validity duration is not acceptable,
+ // or another error code if another error occurred.
+ virtual Result CheckValidityIsAcceptable(Time notBefore, Time notAfter,
+ EndEntityOrCA endEntityOrCA,
+ KeyPurposeId keyPurpose) = 0;
+
+ // For compatibility, a CA certificate with an extended key usage that
+ // contains the id-Netscape-stepUp OID but does not contain the
+ // id-kp-serverAuth OID may be considered valid for issuing server auth
+ // certificates. This function allows TrustDomain implementations to control
+ // this setting based on the start of the validity period of the certificate
+ // in question.
+ virtual Result NetscapeStepUpMatchesServerAuth(Time notBefore,
+ /*out*/ bool& matches) = 0;
+
+ // Some certificate or OCSP response extensions do not directly participate
+ // in the verification flow, but might still be of interest to the clients
+ // (notably Certificate Transparency data, RFC 6962). Such extensions are
+ // extracted and passed to this function for further processing.
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) = 0;
+
+ // Compute a digest of the data in item using the given digest algorithm.
+ //
+ // item contains the data to hash.
+ // digestBuf points to a buffer to where the digest will be written.
+ // digestBufLen will be the size of the digest output (20 for SHA-1,
+ // 32 for SHA-256, etc.).
+ //
+ // TODO: Taking the output buffer as (uint8_t*, size_t) is counter to our
+ // other, extensive, memory safety efforts in mozilla::pkix, and we should
+ // find a way to provide a more-obviously-safe interface.
+ virtual Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf, size_t digestBufLen) = 0;
+
+ protected:
+ TrustDomain() {}
+
+ TrustDomain(const TrustDomain&) = delete;
+ void operator=(const TrustDomain&) = delete;
+};
+
+enum class FallBackToSearchWithinSubject { No = 0, Yes = 1 };
+
+// Applications control the behavior of matching presented name information from
+// a certificate against a reference hostname by implementing the
+// NameMatchingPolicy interface. Used in concert with CheckCertHostname.
+class NameMatchingPolicy {
+ public:
+ virtual ~NameMatchingPolicy() {}
+
+ // Given that the certificate in question has a notBefore field with the given
+ // value, should name matching fall back to searching within the subject
+ // common name field?
+ virtual Result FallBackToCommonName(
+ Time notBefore,
+ /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) = 0;
+
+ protected:
+ NameMatchingPolicy() {}
+
+ NameMatchingPolicy(const NameMatchingPolicy&) = delete;
+ void operator=(const NameMatchingPolicy&) = delete;
+};
+}
+} // namespace mozilla::pkix
+
+#endif // mozilla_pkix_pkixtypes_h
diff --git a/security/nss/lib/mozpkix/include/pkix/pkixutil.h b/security/nss/lib/mozpkix/include/pkix/pkixutil.h
new file mode 100644
index 000000000..ca5b5a2d7
--- /dev/null
+++ b/security/nss/lib/mozpkix/include/pkix/pkixutil.h
@@ -0,0 +1,265 @@
+/* -*- 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 "mozpkix/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 aCertDER, EndEntityOrCA aEndEntityOrCA,
+ const BackCert* aChildCert)
+ : der(aCertDER),
+ endEntityOrCA(aEndEntityOrCA),
+ childCert(aChildCert),
+ version(der::Version::Uninitialized) {}
+
+ 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
diff --git a/security/nss/lib/mozpkix/lib/pkixbuild.cpp b/security/nss/lib/mozpkix/lib/pkixbuild.cpp
new file mode 100644
index 000000000..0ac2cb883
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixbuild.cpp
@@ -0,0 +1,418 @@
+/* -*- 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 "mozpkix/pkix.h"
+
+#include "mozpkix/pkixcheck.h"
+#include "mozpkix/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,
+ unsigned int& buildForwardCallBudget);
+
+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& aTrustDomain, const BackCert& aSubject,
+ Time aTime, KeyPurposeId aRequiredEKUIfPresent,
+ const CertPolicyId& aRequiredPolicy,
+ /*optional*/ const Input* aStapledOCSPResponse,
+ unsigned int aSubCACount, Result aDeferredSubjectError,
+ unsigned int& aBuildForwardCallBudget)
+ : trustDomain(aTrustDomain)
+ , subject(aSubject)
+ , time(aTime)
+ , requiredEKUIfPresent(aRequiredEKUIfPresent)
+ , requiredPolicy(aRequiredPolicy)
+ , stapledOCSPResponse(aStapledOCSPResponse)
+ , subCACount(aSubCACount)
+ , deferredSubjectError(aDeferredSubjectError)
+ , subjectSignaturePublicKeyAlg(der::PublicKeyAlgorithm::Uninitialized)
+ , result(Result::FATAL_ERROR_LIBRARY_FAILURE)
+ , resultWasSet(false)
+ , buildForwardCallBudget(aBuildForwardCallBudget)
+ {
+ }
+
+ 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;
+ unsigned int& buildForwardCallBudget;
+
+ 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.
+ for (const BackCert* prev = potentialIssuer.childCert; prev;
+ 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);
+ }
+
+ // If we've ran out of budget, stop searching.
+ if (buildForwardCallBudget == 0) {
+ Result savedRv = RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
+ keepGoing = false;
+ return savedRv;
+ }
+ buildForwardCallBudget--;
+
+ // 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,
+ buildForwardCallBudget);
+ 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) {
+ // Since this is actually a problem with the current subject certificate
+ // (rather than the issuer), it doesn't make sense to keep going; all
+ // paths through this certificate will fail.
+ Result savedRv = RecordResult(rv, keepGoing);
+ keepGoing = false;
+ return savedRv;
+ }
+
+ if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
+ const Input* sctExtension = subject.GetSignedCertificateTimestamps();
+ if (sctExtension) {
+ Input sctList;
+ rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
+ sctList);
+ if (rv != Success) {
+ // Again, the problem is with this certificate, and all paths through
+ // it will fail.
+ Result savedRv = RecordResult(rv, keepGoing);
+ keepGoing = false;
+ return savedRv;
+ }
+ 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,
+ unsigned int& buildForwardCallBudget)
+{
+ 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, requiredPolicy);
+ }
+
+ 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, buildForwardCallBudget);
+
+ // 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;
+ }
+
+ // See bug 1056341 for context. If mozilla::pkix is being used in an
+ // environment where there are many certificates that all have the same
+ // distinguished name as their subject and issuer (but different SPKIs - see
+ // the loop prevention as per RFC4158 Section 5.2 in PathBuildingStep::Check),
+ // the space to search becomes exponential. Because it would be prohibitively
+ // expensive to explore the entire space, we introduce a budget here that,
+ // when exhausted, terminates the search with the result
+ // Result::ERROR_UNKNOWN_ISSUER. Essentially, we limit the total number of
+ // times `BuildForward` can be called. The current value appears to be a good
+ // balance between finding a path when one exists (when the space isn't too
+ // large) and timing out quickly enough when the space is too large or there
+ // is no valid path to a trust anchor.
+ unsigned int buildForwardCallBudget = 200000;
+ return BuildForward(trustDomain, cert, time, requiredKeyUsageIfPresent,
+ requiredEKUIfPresent, requiredPolicy, stapledOCSPResponse,
+ 0/*subCACount*/, buildForwardCallBudget);
+}
+
+} } // namespace mozilla::pkix
diff --git a/security/nss/lib/mozpkix/lib/pkixcert.cpp b/security/nss/lib/mozpkix/lib/pkixcert.cpp
new file mode 100644
index 000000000..a30483738
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixcert.cpp
@@ -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 "mozpkix/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_kp_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
diff --git a/security/nss/lib/mozpkix/lib/pkixcheck.cpp b/security/nss/lib/mozpkix/lib/pkixcheck.cpp
new file mode 100644
index 000000000..317db01e2
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixcheck.cpp
@@ -0,0 +1,1100 @@
+/* -*- 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 "mozpkix/pkixcheck.h"
+
+#include "mozpkix/pkixder.h"
+#include "mozpkix/pkixutil.h"
+
+namespace mozilla { namespace pkix {
+
+// 4.1.1.2 signatureAlgorithm
+// 4.1.2.3 signature
+
+Result
+CheckSignatureAlgorithm(TrustDomain& trustDomain,
+ EndEntityOrCA endEntityOrCA,
+ Time notBefore,
+ const der::SignedDataWithSignature& signedData,
+ Input signatureValue)
+{
+ // 4.1.1.2. signatureAlgorithm
+ der::PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ Reader signatureAlgorithmReader(signedData.algorithm);
+ Result rv = der::SignatureAlgorithmIdentifierValue(signatureAlgorithmReader,
+ publicKeyAlg, digestAlg);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::End(signatureAlgorithmReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.1.2.3. Signature
+ der::PublicKeyAlgorithm signedPublicKeyAlg;
+ DigestAlgorithm signedDigestAlg;
+ Reader signedSignatureAlgorithmReader(signatureValue);
+ rv = der::SignatureAlgorithmIdentifierValue(signedSignatureAlgorithmReader,
+ signedPublicKeyAlg,
+ signedDigestAlg);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::End(signedSignatureAlgorithmReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // "This field MUST contain the same algorithm identifier as the
+ // signatureAlgorithm field in the sequence Certificate." However, it may
+ // be encoded differently. In particular, one of the fields may have a NULL
+ // parameter while the other one may omit the parameter field altogether, and
+ // these are considered equivalent. Some certificates generation software
+ // actually generates certificates like that, so we compare the parsed values
+ // instead of comparing the encoded values byte-for-byte.
+ //
+ // Along the same lines, we accept two different OIDs for RSA-with-SHA1, and
+ // we consider those OIDs to be equivalent here.
+ if (publicKeyAlg != signedPublicKeyAlg || digestAlg != signedDigestAlg) {
+ return Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH;
+ }
+
+ // During the time of the deprecation of SHA-1 and the deprecation of RSA
+ // keys of less than 2048 bits, we will encounter many certs signed using
+ // SHA-1 and/or too-small RSA keys. With this in mind, we ask the trust
+ // domain early on if it knows it will reject the signature purely based on
+ // the digest algorithm and/or the RSA key size (if an RSA signature). This
+ // is a good optimization because it completely avoids calling
+ // trustDomain.FindIssuers (which may be slow) for such rejected certs, and
+ // more generally it short-circuits any path building with them (which, of
+ // course, is even slower).
+
+ rv = trustDomain.CheckSignatureDigestAlgorithm(digestAlg, endEntityOrCA,
+ notBefore);
+ if (rv != Success) {
+ return rv;
+ }
+
+ switch (publicKeyAlg) {
+ case der::PublicKeyAlgorithm::RSA_PKCS1:
+ {
+ // The RSA computation may give a result that requires fewer bytes to
+ // encode than the public key (since it is modular arithmetic). However,
+ // the last step of generating a PKCS#1.5 signature is the I2OSP
+ // procedure, which pads any such shorter result with zeros so that it
+ // is exactly the same length as the public key.
+ unsigned int signatureSizeInBits = signedData.signature.GetLength() * 8u;
+ return trustDomain.CheckRSAPublicKeyModulusSizeInBits(
+ endEntityOrCA, signatureSizeInBits);
+ }
+
+ case der::PublicKeyAlgorithm::ECDSA:
+ // In theory, we could implement a similar early-pruning optimization for
+ // ECDSA curves. However, since there has been no similar deprecation for
+ // for any curve that we support, the chances of us encountering a curve
+ // during path building is too low to be worth bothering with.
+ break;
+ case der::PublicKeyAlgorithm::Uninitialized:
+ {
+ assert(false);
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+
+ return Success;
+}
+
+// 4.1.2.4 Issuer
+
+Result
+CheckIssuer(Input encodedIssuer)
+{
+ // "The issuer field MUST contain a non-empty distinguished name (DN)."
+ Reader issuer(encodedIssuer);
+ Input encodedRDNs;
+ ExpectTagAndGetValue(issuer, der::SEQUENCE, encodedRDNs);
+ Reader rdns(encodedRDNs);
+ // Check that the issuer name contains at least one RDN
+ // (Note: this does not check related grammar rules, such as there being one
+ // or more AVAs in each RDN, or the values in AVAs not being empty strings)
+ if (rdns.AtEnd()) {
+ return Result::ERROR_EMPTY_ISSUER_NAME;
+ }
+ return Success;
+}
+
+// 4.1.2.5 Validity
+
+Result
+ParseValidity(Input encodedValidity,
+ /*optional out*/ Time* notBeforeOut,
+ /*optional out*/ Time* notAfterOut)
+{
+ Reader validity(encodedValidity);
+ Time notBefore(Time::uninitialized);
+ if (der::TimeChoice(validity, notBefore) != Success) {
+ return Result::ERROR_INVALID_DER_TIME;
+ }
+
+ Time notAfter(Time::uninitialized);
+ if (der::TimeChoice(validity, notAfter) != Success) {
+ return Result::ERROR_INVALID_DER_TIME;
+ }
+
+ if (der::End(validity) != Success) {
+ return Result::ERROR_INVALID_DER_TIME;
+ }
+
+ if (notBefore > notAfter) {
+ return Result::ERROR_INVALID_DER_TIME;
+ }
+
+ if (notBeforeOut) {
+ *notBeforeOut = notBefore;
+ }
+ if (notAfterOut) {
+ *notAfterOut = notAfter;
+ }
+
+ return Success;
+}
+
+Result
+CheckValidity(Time time, Time notBefore, Time notAfter)
+{
+ if (time < notBefore) {
+ return Result::ERROR_NOT_YET_VALID_CERTIFICATE;
+ }
+
+ if (time > notAfter) {
+ return Result::ERROR_EXPIRED_CERTIFICATE;
+ }
+
+ return Success;
+}
+
+// 4.1.2.7 Subject Public Key Info
+
+Result
+CheckSubjectPublicKeyInfoContents(Reader& input, TrustDomain& trustDomain,
+ EndEntityOrCA endEntityOrCA)
+{
+ // Here, we validate the syntax and do very basic semantic validation of the
+ // public key of the certificate. The intention here is to filter out the
+ // types of bad inputs that are most likely to trigger non-mathematical
+ // security vulnerabilities in the TrustDomain, like buffer overflows or the
+ // use of unsafe elliptic curves.
+ //
+ // We don't check (all of) the mathematical properties of the public key here
+ // because it is more efficient for the TrustDomain to do it during signature
+ // verification and/or other use of the public key. In particular, we
+ // delegate the arithmetic validation of the public key, as specified in
+ // NIST SP800-56A section 5.6.2, to the TrustDomain, at least for now.
+
+ Reader algorithm;
+ Input subjectPublicKey;
+ Result rv = der::ExpectTagAndGetValue(input, der::SEQUENCE, algorithm);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::BitStringWithNoUnusedBits(input, subjectPublicKey);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::End(input);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Reader subjectPublicKeyReader(subjectPublicKey);
+
+ Reader algorithmOID;
+ rv = der::ExpectTagAndGetValue(algorithm, der::OIDTag, algorithmOID);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // RFC 3279 Section 2.3.1
+ // python DottedOIDToCode.py rsaEncryption 1.2.840.113549.1.1.1
+ static const uint8_t rsaEncryption[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01
+ };
+
+ // RFC 3279 Section 2.3.5 and RFC 5480 Section 2.1.1
+ // python DottedOIDToCode.py id-ecPublicKey 1.2.840.10045.2.1
+ static const uint8_t id_ecPublicKey[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01
+ };
+
+ if (algorithmOID.MatchRest(id_ecPublicKey)) {
+ // An id-ecPublicKey AlgorithmIdentifier has a parameter that identifes
+ // the curve being used. Although RFC 5480 specifies multiple forms, we
+ // only supported the NamedCurve form, where the curve is identified by an
+ // OID.
+
+ Reader namedCurveOIDValue;
+ rv = der::ExpectTagAndGetValue(algorithm, der::OIDTag,
+ namedCurveOIDValue);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // RFC 5480
+ // python DottedOIDToCode.py secp256r1 1.2.840.10045.3.1.7
+ static const uint8_t secp256r1[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07
+ };
+
+ // RFC 5480
+ // python DottedOIDToCode.py secp384r1 1.3.132.0.34
+ static const uint8_t secp384r1[] = {
+ 0x2b, 0x81, 0x04, 0x00, 0x22
+ };
+
+ // RFC 5480
+ // python DottedOIDToCode.py secp521r1 1.3.132.0.35
+ static const uint8_t secp521r1[] = {
+ 0x2b, 0x81, 0x04, 0x00, 0x23
+ };
+
+ // Matching is attempted based on a rough estimate of the commonality of the
+ // elliptic curve, to minimize the number of MatchRest calls.
+ NamedCurve curve;
+ unsigned int bits;
+ if (namedCurveOIDValue.MatchRest(secp256r1)) {
+ curve = NamedCurve::secp256r1;
+ bits = 256;
+ } else if (namedCurveOIDValue.MatchRest(secp384r1)) {
+ curve = NamedCurve::secp384r1;
+ bits = 384;
+ } else if (namedCurveOIDValue.MatchRest(secp521r1)) {
+ curve = NamedCurve::secp521r1;
+ bits = 521;
+ } else {
+ return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
+ }
+
+ rv = trustDomain.CheckECDSACurveIsAcceptable(endEntityOrCA, curve);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // RFC 5480 Section 2.2 says that the first octet will be 0x04 to indicate
+ // an uncompressed point, which is the only encoding we support.
+ uint8_t compressedOrUncompressed;
+ rv = subjectPublicKeyReader.Read(compressedOrUncompressed);
+ if (rv != Success) {
+ return rv;
+ }
+ if (compressedOrUncompressed != 0x04) {
+ return Result::ERROR_UNSUPPORTED_EC_POINT_FORM;
+ }
+
+ // The point is encoded as two raw (not DER-encoded) integers, each padded
+ // to the bit length (rounded up to the nearest byte).
+ Input point;
+ rv = subjectPublicKeyReader.SkipToEnd(point);
+ if (rv != Success) {
+ return rv;
+ }
+ if (point.GetLength() != ((bits + 7) / 8u) * 2u) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ // XXX: We defer the mathematical verification of the validity of the point
+ // until signature verification. This means that if we never verify a
+ // signature, we'll never fully check whether the public key is valid.
+ } else if (algorithmOID.MatchRest(rsaEncryption)) {
+ // RFC 3279 Section 2.3.1 says "The parameters field MUST have ASN.1 type
+ // NULL for this algorithm identifier."
+ rv = der::ExpectTagAndEmptyValue(algorithm, der::NULLTag);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // RSAPublicKey :: = SEQUENCE{
+ // modulus INTEGER, --n
+ // publicExponent INTEGER } --e
+ rv = der::Nested(subjectPublicKeyReader, der::SEQUENCE,
+ [&trustDomain, endEntityOrCA](Reader& r) {
+ Input modulus;
+ Input::size_type modulusSignificantBytes;
+ Result nestedRv =
+ der::PositiveInteger(r, modulus, &modulusSignificantBytes);
+ if (nestedRv != Success) {
+ return nestedRv;
+ }
+ // XXX: Should we do additional checks of the modulus?
+ nestedRv = trustDomain.CheckRSAPublicKeyModulusSizeInBits(
+ endEntityOrCA, modulusSignificantBytes * 8u);
+ if (nestedRv != Success) {
+ return nestedRv;
+ }
+
+ // XXX: We don't allow the TrustDomain to validate the exponent.
+ // XXX: We don't do our own sanity checking of the exponent.
+ Input exponent;
+ return der::PositiveInteger(r, exponent);
+ });
+ if (rv != Success) {
+ return rv;
+ }
+ } else {
+ return Result::ERROR_UNSUPPORTED_KEYALG;
+ }
+
+ rv = der::End(algorithm);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::End(subjectPublicKeyReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ return Success;
+}
+
+Result
+CheckSubjectPublicKeyInfo(Input subjectPublicKeyInfo, TrustDomain& trustDomain,
+ EndEntityOrCA endEntityOrCA)
+{
+ Reader spkiReader(subjectPublicKeyInfo);
+ Result rv = der::Nested(spkiReader, der::SEQUENCE, [&](Reader& r) {
+ return CheckSubjectPublicKeyInfoContents(r, trustDomain, endEntityOrCA);
+ });
+ if (rv != Success) {
+ return rv;
+ }
+ return der::End(spkiReader);
+}
+
+// 4.2.1.3. Key Usage (id-ce-keyUsage)
+
+// As explained in the comment in CheckKeyUsage, bit 0 is the most significant
+// bit and bit 7 is the least significant bit.
+inline uint8_t KeyUsageToBitMask(KeyUsage keyUsage)
+{
+ assert(keyUsage != KeyUsage::noParticularKeyUsageRequired);
+ return 0x80u >> static_cast<uint8_t>(keyUsage);
+}
+
+Result
+CheckKeyUsage(EndEntityOrCA endEntityOrCA, const Input* encodedKeyUsage,
+ KeyUsage requiredKeyUsageIfPresent)
+{
+ if (!encodedKeyUsage) {
+ // TODO(bug 970196): Reject certificates that are being used to verify
+ // certificate signatures unless the certificate is a trust anchor, to
+ // reduce the chances of an end-entity certificate being abused as a CA
+ // certificate.
+ // if (endEntityOrCA == EndEntityOrCA::MustBeCA && !isTrustAnchor) {
+ // return Result::ERROR_INADEQUATE_KEY_USAGE;
+ // }
+ //
+ // TODO: Users may configure arbitrary certificates as trust anchors, not
+ // just roots. We should only allow a certificate without a key usage to be
+ // used as a CA when it is self-issued and self-signed.
+ return Success;
+ }
+
+ Reader input(*encodedKeyUsage);
+ Reader value;
+ if (der::ExpectTagAndGetValue(input, der::BIT_STRING, value) != Success) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+
+ uint8_t numberOfPaddingBits;
+ if (value.Read(numberOfPaddingBits) != Success) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+ if (numberOfPaddingBits > 7) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+
+ uint8_t bits;
+ if (value.Read(bits) != Success) {
+ // Reject empty bit masks.
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+
+ // The most significant bit is numbered 0 (digitalSignature) and the least
+ // significant bit is numbered 7 (encipherOnly), and the padding is in the
+ // least significant bits of the last byte. The numbering of bits in a byte
+ // is backwards from how we usually interpret them.
+ //
+ // For example, let's say bits is encoded in one byte with of value 0xB0 and
+ // numberOfPaddingBits == 4. Then, bits is 10110000 in binary:
+ //
+ // bit 0 bit 3
+ // | |
+ // v v
+ // 10110000
+ // ^^^^
+ // |
+ // 4 padding bits
+ //
+ // Since bits is the last byte, we have to consider the padding by ensuring
+ // that the least significant 4 bits are all zero, since DER rules require
+ // all padding bits to be zero. Then we have to look at the bit N bits to the
+ // right of the most significant bit, where N is a value from the KeyUsage
+ // enumeration.
+ //
+ // Let's say we're interested in the keyCertSign (5) bit. We'd need to look
+ // at bit 5, which is zero, so keyCertSign is not asserted. (Since we check
+ // that the padding is all zeros, it is OK to read from the padding bits.)
+ //
+ // Let's say we're interested in the digitalSignature (0) bit. We'd need to
+ // look at the bit 0 (the most significant bit), which is set, so that means
+ // digitalSignature is asserted. Similarly, keyEncipherment (2) and
+ // dataEncipherment (3) are asserted.
+ //
+ // Note that since the KeyUsage enumeration is limited to values 0-7, we
+ // only ever need to examine the first byte test for
+ // requiredKeyUsageIfPresent.
+
+ if (requiredKeyUsageIfPresent != KeyUsage::noParticularKeyUsageRequired) {
+ // Check that the required key usage bit is set.
+ if ((bits & KeyUsageToBitMask(requiredKeyUsageIfPresent)) == 0) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+ }
+
+ // RFC 5280 says "The keyCertSign bit is asserted when the subject public
+ // key is used for verifying signatures on public key certificates. If the
+ // keyCertSign bit is asserted, then the cA bit in the basic constraints
+ // extension (Section 4.2.1.9) MUST also be asserted."
+ // However, we allow end-entity certificates (i.e. certificates without
+ // basicConstraints.cA set to TRUE) to claim keyCertSign for compatibility
+ // reasons. This does not compromise security because we only allow
+ // certificates with basicConstraints.cA set to TRUE to act as CAs.
+ if (requiredKeyUsageIfPresent == KeyUsage::keyCertSign &&
+ endEntityOrCA != EndEntityOrCA::MustBeCA) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+
+ // The padding applies to the last byte, so skip to the last byte.
+ while (!value.AtEnd()) {
+ if (value.Read(bits) != Success) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+ }
+
+ // All of the padding bits must be zero, according to DER rules.
+ uint8_t paddingMask = static_cast<uint8_t>((1 << numberOfPaddingBits) - 1);
+ if ((bits & paddingMask) != 0) {
+ return Result::ERROR_INADEQUATE_KEY_USAGE;
+ }
+
+ return Success;
+}
+
+// RFC5820 4.2.1.4. Certificate Policies
+
+// "The user-initial-policy-set contains the special value any-policy if the
+// user is not concerned about certificate policy."
+//
+// python DottedOIDToCode.py anyPolicy 2.5.29.32.0
+
+static const uint8_t anyPolicy[] = {
+ 0x55, 0x1d, 0x20, 0x00
+};
+
+/*static*/ const CertPolicyId CertPolicyId::anyPolicy = {
+ 4, { 0x55, 0x1d, 0x20, 0x00 }
+};
+
+bool
+CertPolicyId::IsAnyPolicy() const {
+ if (this == &CertPolicyId::anyPolicy) {
+ return true;
+ }
+ return numBytes == sizeof(::mozilla::pkix::anyPolicy) &&
+ std::equal(bytes, bytes + numBytes, ::mozilla::pkix::anyPolicy);
+}
+
+bool
+CertPolicyId::operator==(const CertPolicyId& other) const
+{
+ return numBytes == other.numBytes &&
+ std::equal(bytes, bytes + numBytes, other.bytes);
+}
+
+// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
+Result
+CheckCertificatePolicies(EndEntityOrCA endEntityOrCA,
+ const Input* encodedCertificatePolicies,
+ const Input* encodedInhibitAnyPolicy,
+ TrustLevel trustLevel,
+ const CertPolicyId& requiredPolicy)
+{
+ if (requiredPolicy.numBytes == 0 ||
+ requiredPolicy.numBytes > sizeof requiredPolicy.bytes) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ bool requiredPolicyFound = requiredPolicy.IsAnyPolicy();
+ if (requiredPolicyFound) {
+ return Success;
+ }
+
+ // Bug 989051. Until we handle inhibitAnyPolicy we will fail close when
+ // inhibitAnyPolicy extension is present and we are validating for a policy.
+ if (!requiredPolicyFound && encodedInhibitAnyPolicy) {
+ return Result::ERROR_POLICY_VALIDATION_FAILED;
+ }
+
+ // The root CA certificate may omit the policies that it has been
+ // trusted for, so we cannot require the policies to be present in those
+ // certificates. Instead, the determination of which roots are trusted for
+ // which policies is made by the TrustDomain's GetCertTrust method.
+ if (trustLevel == TrustLevel::TrustAnchor &&
+ endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ requiredPolicyFound = true;
+ }
+
+ Input requiredPolicyDER;
+ if (requiredPolicyDER.Init(requiredPolicy.bytes, requiredPolicy.numBytes)
+ != Success) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ if (encodedCertificatePolicies) {
+ Reader extension(*encodedCertificatePolicies);
+ Reader certificatePolicies;
+ Result rv = der::ExpectTagAndGetValue(extension, der::SEQUENCE,
+ certificatePolicies);
+ if (rv != Success) {
+ return Result::ERROR_POLICY_VALIDATION_FAILED;
+ }
+ if (!extension.AtEnd()) {
+ return Result::ERROR_POLICY_VALIDATION_FAILED;
+ }
+
+ do {
+ // PolicyInformation ::= SEQUENCE {
+ // policyIdentifier CertPolicyId,
+ // policyQualifiers SEQUENCE SIZE (1..MAX) OF
+ // PolicyQualifierInfo OPTIONAL }
+ Reader policyInformation;
+ rv = der::ExpectTagAndGetValue(certificatePolicies, der::SEQUENCE,
+ policyInformation);
+ if (rv != Success) {
+ return Result::ERROR_POLICY_VALIDATION_FAILED;
+ }
+
+ Reader policyIdentifier;
+ rv = der::ExpectTagAndGetValue(policyInformation, der::OIDTag,
+ policyIdentifier);
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (policyIdentifier.MatchRest(requiredPolicyDER)) {
+ requiredPolicyFound = true;
+ } else if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+ policyIdentifier.MatchRest(anyPolicy)) {
+ requiredPolicyFound = true;
+ }
+
+ // RFC 5280 Section 4.2.1.4 says "Optional qualifiers, which MAY be
+ // present, are not expected to change the definition of the policy." Also,
+ // it seems that Section 6, which defines validation, does not require any
+ // matching of qualifiers. Thus, doing anything with the policy qualifiers
+ // would be a waste of time and a source of potential incompatibilities, so
+ // we just ignore them.
+ } while (!requiredPolicyFound && !certificatePolicies.AtEnd());
+ }
+
+ if (!requiredPolicyFound) {
+ return Result::ERROR_POLICY_VALIDATION_FAILED;
+ }
+
+ return Success;
+}
+
+static const long UNLIMITED_PATH_LEN = -1; // must be less than zero
+
+// BasicConstraints ::= SEQUENCE {
+// cA BOOLEAN DEFAULT FALSE,
+// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
+
+// RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints)
+Result
+CheckBasicConstraints(EndEntityOrCA endEntityOrCA,
+ const Input* encodedBasicConstraints,
+ const der::Version version, TrustLevel trustLevel,
+ unsigned int subCACount)
+{
+ bool isCA = false;
+ long pathLenConstraint = UNLIMITED_PATH_LEN;
+
+ if (encodedBasicConstraints) {
+ Reader input(*encodedBasicConstraints);
+ Result rv = der::Nested(input, der::SEQUENCE,
+ [&isCA, &pathLenConstraint](Reader& r) {
+ Result nestedRv = der::OptionalBoolean(r, isCA);
+ if (nestedRv != Success) {
+ return nestedRv;
+ }
+ // TODO(bug 985025): If isCA is false, pathLenConstraint
+ // MUST NOT be included (as per RFC 5280 section
+ // 4.2.1.9), but for compatibility reasons, we don't
+ // check this.
+ return der::OptionalInteger(r, UNLIMITED_PATH_LEN, pathLenConstraint);
+ });
+ if (rv != Success) {
+ return Result::ERROR_EXTENSION_VALUE_INVALID;
+ }
+ if (der::End(input) != Success) {
+ return Result::ERROR_EXTENSION_VALUE_INVALID;
+ }
+ } else {
+ // "If the basic constraints extension is not present in a version 3
+ // certificate, or the extension is present but the cA boolean is not
+ // asserted, then the certified public key MUST NOT be used to verify
+ // certificate signatures."
+ //
+ // For compatibility, we must accept v1 trust anchors without basic
+ // constraints as CAs.
+ //
+ // There are devices with v1 certificates that are unlikely to be trust
+ // anchors. In order to allow applications to treat this case differently
+ // from other basic constraints violations (e.g. allowing certificate error
+ // overrides for only this case), we return a different error code.
+ //
+ // TODO: add check for self-signedness?
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA && version == der::Version::v1) {
+ if (trustLevel == TrustLevel::TrustAnchor) {
+ isCA = true;
+ } else {
+ return Result::ERROR_V1_CERT_USED_AS_CA;
+ }
+ }
+ }
+
+ if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
+ // CA certificates are not trusted as EE certs.
+
+ if (isCA) {
+ // Note that this check prevents a delegated OCSP response signing
+ // certificate with the CA bit from successfully validating when we check
+ // it from pkixocsp.cpp, which is a good thing.
+ return Result::ERROR_CA_CERT_USED_AS_END_ENTITY;
+ }
+
+ return Success;
+ }
+
+ assert(endEntityOrCA == EndEntityOrCA::MustBeCA);
+
+ // End-entity certificates are not allowed to act as CA certs.
+ if (!isCA) {
+ return Result::ERROR_CA_CERT_INVALID;
+ }
+
+ if (pathLenConstraint >= 0 &&
+ static_cast<long>(subCACount) > pathLenConstraint) {
+ return Result::ERROR_PATH_LEN_CONSTRAINT_INVALID;
+ }
+
+ return Success;
+}
+
+// 4.2.1.12. Extended Key Usage (id-ce-extKeyUsage)
+
+static Result
+MatchEKU(Reader& value, KeyPurposeId requiredEKU,
+ EndEntityOrCA endEntityOrCA, TrustDomain& trustDomain,
+ Time notBefore, /*in/out*/ bool& found,
+ /*in/out*/ bool& foundOCSPSigning)
+{
+ // See Section 5.9 of "A Layman's Guide to a Subset of ASN.1, BER, and DER"
+ // for a description of ASN.1 DER encoding of OIDs.
+
+ // id-pkix OBJECT IDENTIFIER ::=
+ // { iso(1) identified-organization(3) dod(6) internet(1)
+ // security(5) mechanisms(5) pkix(7) }
+ // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
+ // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
+ // id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
+ // id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
+ // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
+ // id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
+ static const uint8_t server[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 1 };
+ static const uint8_t client[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 2 };
+ static const uint8_t code [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 3 };
+ static const uint8_t email [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 4 };
+ static const uint8_t ocsp [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 9 };
+
+ // id-Netscape OBJECT IDENTIFIER ::= { 2 16 840 1 113730 }
+ // id-Netscape-policy OBJECT IDENTIFIER ::= { id-Netscape 4 }
+ // id-Netscape-stepUp OBJECT IDENTIFIER ::= { id-Netscape-policy 1 }
+ static const uint8_t serverStepUp[] =
+ { (40*2)+16, 128+6,72, 1, 128+6,128+120,66, 4, 1 };
+
+ bool match = false;
+
+ if (!found) {
+ switch (requiredEKU) {
+ case KeyPurposeId::id_kp_serverAuth: {
+ if (value.MatchRest(server)) {
+ match = true;
+ break;
+ }
+ // Potentially treat CA certs with step-up OID as also having SSL server
+ // type. Comodo has issued certificates that require this behavior that
+ // don't expire until June 2020!
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA &&
+ value.MatchRest(serverStepUp)) {
+ Result rv = trustDomain.NetscapeStepUpMatchesServerAuth(notBefore,
+ match);
+ if (rv != Success) {
+ return rv;
+ }
+ }
+ break;
+ }
+
+ case KeyPurposeId::id_kp_clientAuth:
+ match = value.MatchRest(client);
+ break;
+
+ case KeyPurposeId::id_kp_codeSigning:
+ match = value.MatchRest(code);
+ break;
+
+ case KeyPurposeId::id_kp_emailProtection:
+ match = value.MatchRest(email);
+ break;
+
+ case KeyPurposeId::id_kp_OCSPSigning:
+ match = value.MatchRest(ocsp);
+ break;
+
+ case KeyPurposeId::anyExtendedKeyUsage:
+ return NotReached("anyExtendedKeyUsage should start with found==true",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+ }
+
+ if (match) {
+ found = true;
+ if (requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+ foundOCSPSigning = true;
+ }
+ } else if (value.MatchRest(ocsp)) {
+ foundOCSPSigning = true;
+ }
+
+ value.SkipToEnd(); // ignore unmatched OIDs.
+
+ return Success;
+}
+
+Result
+CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
+ const Input* encodedExtendedKeyUsage,
+ KeyPurposeId requiredEKU, TrustDomain& trustDomain,
+ Time notBefore)
+{
+ // XXX: We're using Result::ERROR_INADEQUATE_CERT_TYPE here so that callers
+ // can distinguish EKU mismatch from KU mismatch from basic constraints
+ // mismatch. We should probably add a new error code that is more clear for
+ // this type of problem.
+
+ bool foundOCSPSigning = false;
+
+ if (encodedExtendedKeyUsage) {
+ bool found = requiredEKU == KeyPurposeId::anyExtendedKeyUsage;
+
+ Reader input(*encodedExtendedKeyUsage);
+ Result rv = der::NestedOf(input, der::SEQUENCE, der::OIDTag,
+ der::EmptyAllowed::No, [&](Reader& r) {
+ return MatchEKU(r, requiredEKU, endEntityOrCA, trustDomain, notBefore,
+ found, foundOCSPSigning);
+ });
+ if (rv != Success) {
+ return Result::ERROR_INADEQUATE_CERT_TYPE;
+ }
+ if (der::End(input) != Success) {
+ return Result::ERROR_INADEQUATE_CERT_TYPE;
+ }
+
+ // If the EKU extension was included, then the required EKU must be in the
+ // list.
+ if (!found) {
+ return Result::ERROR_INADEQUATE_CERT_TYPE;
+ }
+ }
+
+ // pkixocsp.cpp depends on the following additional checks.
+
+ if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
+ // When validating anything other than an delegated OCSP signing cert,
+ // reject any cert that also claims to be an OCSP responder, because such
+ // a cert does not make sense. For example, if an SSL certificate were to
+ // assert id-kp-OCSPSigning then it could sign OCSP responses for itself,
+ // if not for this check.
+ // That said, we accept CA certificates with id-kp-OCSPSigning because
+ // some CAs in Mozilla's CA program have issued such intermediate
+ // certificates, and because some CAs have reported some Microsoft server
+ // software wrongly requires CA certificates to have id-kp-OCSPSigning.
+ // Allowing this exception does not cause any security issues because we
+ // require delegated OCSP response signing certificates to be end-entity
+ // certificates.
+ if (foundOCSPSigning && requiredEKU != KeyPurposeId::id_kp_OCSPSigning) {
+ return Result::ERROR_INADEQUATE_CERT_TYPE;
+ }
+ // http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
+ // "OCSP signing delegation SHALL be designated by the inclusion of
+ // id-kp-OCSPSigning in an extended key usage certificate extension
+ // included in the OCSP response signer's certificate."
+ //
+ // id-kp-OCSPSigning is the only EKU that isn't implicitly assumed when the
+ // EKU extension is missing from an end-entity certificate. However, any CA
+ // certificate can issue a delegated OCSP response signing certificate, so
+ // we can't require the EKU be explicitly included for CA certificates.
+ if (!foundOCSPSigning && requiredEKU == KeyPurposeId::id_kp_OCSPSigning) {
+ return Result::ERROR_INADEQUATE_CERT_TYPE;
+ }
+ }
+
+ return Success;
+}
+
+Result
+CheckTLSFeatures(const BackCert& subject, BackCert& potentialIssuer)
+{
+ const Input* issuerTLSFeatures = potentialIssuer.GetRequiredTLSFeatures();
+ if (!issuerTLSFeatures) {
+ return Success;
+ }
+
+ const Input* subjectTLSFeatures = subject.GetRequiredTLSFeatures();
+ if (issuerTLSFeatures->GetLength() == 0 ||
+ !subjectTLSFeatures ||
+ !InputsAreEqual(*issuerTLSFeatures, *subjectTLSFeatures)) {
+ return Result::ERROR_REQUIRED_TLS_FEATURE_MISSING;
+ }
+
+ return Success;
+}
+
+Result
+TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures,
+ const Input* stapledOCSPResponse)
+{
+ if (!requiredTLSFeatures) {
+ return Success;
+ }
+
+ // RFC 6066 10.2: ExtensionType status_request
+ const static uint8_t status_request = 5;
+ const static uint8_t status_request_bytes[] = { status_request };
+
+ Reader input(*requiredTLSFeatures);
+ return der::NestedOf(input, der::SEQUENCE, der::INTEGER,
+ der::EmptyAllowed::No, [&](Reader& r) {
+ if (!r.MatchRest(status_request_bytes)) {
+ return Result::ERROR_REQUIRED_TLS_FEATURE_MISSING;
+ }
+
+ if (!stapledOCSPResponse) {
+ return Result::ERROR_REQUIRED_TLS_FEATURE_MISSING;
+ }
+
+ return Result::Success;
+ });
+}
+
+Result
+CheckTLSFeaturesAreSatisfied(Input& cert,
+ const Input* stapledOCSPResponse)
+{
+ BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
+ Result rv = backCert.Init();
+ if (rv != Success) {
+ return rv;
+ }
+
+ return TLSFeaturesSatisfiedInternal(backCert.GetRequiredTLSFeatures(),
+ stapledOCSPResponse);
+}
+
+Result
+CheckIssuerIndependentProperties(TrustDomain& trustDomain,
+ const BackCert& cert,
+ Time time,
+ KeyUsage requiredKeyUsageIfPresent,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
+ unsigned int subCACount,
+ /*out*/ TrustLevel& trustLevel)
+{
+ Result rv;
+
+ const EndEntityOrCA endEntityOrCA = cert.endEntityOrCA;
+
+ // Check the cert's trust first, because we want to minimize the amount of
+ // processing we do on a distrusted cert, in case it is trying to exploit
+ // some bug in our processing.
+ rv = trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetDER(),
+ trustLevel);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // IMPORTANT: We parse the validity interval here, so that we can use the
+ // notBefore and notAfter values in checks for things that might be deprecated
+ // over time. However, we must not fail for semantic errors until the end of
+ // this method, in order to preserve error ranking.
+ Time notBefore(Time::uninitialized);
+ Time notAfter(Time::uninitialized);
+ rv = ParseValidity(cert.GetValidity(), &notBefore, &notAfter);
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (trustLevel == TrustLevel::TrustAnchor &&
+ endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
+ requiredEKUIfPresent == KeyPurposeId::id_kp_OCSPSigning) {
+ // OCSP signer certificates can never be trust anchors, especially
+ // since we don't support designated OCSP responders. All of the checks
+ // below that are dependent on trustLevel rely on this overriding of the
+ // trust level for OCSP signers.
+ trustLevel = TrustLevel::InheritsTrust;
+ }
+
+ switch (trustLevel) {
+ case TrustLevel::InheritsTrust:
+ rv = CheckSignatureAlgorithm(trustDomain, endEntityOrCA, notBefore,
+ cert.GetSignedData(), cert.GetSignature());
+ if (rv != Success) {
+ return rv;
+ }
+ break;
+
+ case TrustLevel::TrustAnchor:
+ // We don't even bother checking signatureAlgorithm or signature for
+ // syntactic validity for trust anchors, because we don't use those
+ // fields for anything, and because the trust anchor might be signed
+ // with a signature algorithm we don't actually support.
+ break;
+
+ case TrustLevel::ActivelyDistrusted:
+ return Result::ERROR_UNTRUSTED_CERT;
+ }
+
+ // Check the SPKI early, because it is one of the most selective properties
+ // of the certificate due to SHA-1 deprecation and the deprecation of
+ // certificates with keys weaker than RSA 2048.
+ rv = CheckSubjectPublicKeyInfo(cert.GetSubjectPublicKeyInfo(), trustDomain,
+ endEntityOrCA);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.1.2.4. Issuer
+ rv = CheckIssuer(cert.GetIssuer());
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136).
+
+ // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136).
+
+ // 4.2.1.3. Key Usage
+ rv = CheckKeyUsage(endEntityOrCA, cert.GetKeyUsage(),
+ requiredKeyUsageIfPresent);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.2.1.4. Certificate Policies
+ rv = CheckCertificatePolicies(endEntityOrCA, cert.GetCertificatePolicies(),
+ cert.GetInhibitAnyPolicy(), trustLevel,
+ requiredPolicy);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.2.1.5. Policy Mappings are not supported; see the documentation about
+ // policy enforcement in pkix.h.
+
+ // 4.2.1.6. Subject Alternative Name dealt with during name constraint
+ // checking and during name verification (CERT_VerifyCertName).
+
+ // 4.2.1.7. Issuer Alternative Name is not something that needs checking.
+
+ // 4.2.1.8. Subject Directory Attributes is not something that needs
+ // checking.
+
+ // 4.2.1.9. Basic Constraints.
+ rv = CheckBasicConstraints(endEntityOrCA, cert.GetBasicConstraints(),
+ cert.GetVersion(), trustLevel, subCACount);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.2.1.10. Name Constraints is dealt with in during path building.
+
+ // 4.2.1.11. Policy Constraints are implicitly supported; see the
+ // documentation about policy enforcement in pkix.h.
+
+ // 4.2.1.12. Extended Key Usage
+ rv = CheckExtendedKeyUsage(endEntityOrCA, cert.GetExtKeyUsage(),
+ requiredEKUIfPresent, trustDomain, notBefore);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // 4.2.1.13. CRL Distribution Points is not supported, though the
+ // TrustDomain's CheckRevocation method may parse it and process it
+ // on its own.
+
+ // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation
+ // about policy enforcement in pkix.h.
+
+ // IMPORTANT: Even though we parse validity above, we wait until this point to
+ // check it, so that error ranking works correctly.
+ rv = CheckValidity(time, notBefore, notAfter);
+ if (rv != Success) {
+ return rv;
+ }
+
+ rv = trustDomain.CheckValidityIsAcceptable(notBefore, notAfter, endEntityOrCA,
+ requiredEKUIfPresent);
+ if (rv != Success) {
+ return rv;
+ }
+
+ return Success;
+}
+
+} } // namespace mozilla::pkix
diff --git a/security/nss/lib/mozpkix/lib/pkixder.cpp b/security/nss/lib/mozpkix/lib/pkixder.cpp
new file mode 100644
index 000000000..152d11a23
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixder.cpp
@@ -0,0 +1,611 @@
+/* -*- 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 "mozpkix/pkixder.h"
+
+#include "mozpkix/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)
+{
+ 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
diff --git a/security/nss/lib/mozpkix/lib/pkixnames.cpp b/security/nss/lib/mozpkix/lib/pkixnames.cpp
new file mode 100644
index 000000000..6f40800d7
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixnames.cpp
@@ -0,0 +1,2050 @@
+/* -*- 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.
+ */
+
+// This code implements RFC6125-ish name matching, RFC5280-ish name constraint
+// checking, and related things.
+//
+// In this code, identifiers are classified as either "presented" or
+// "reference" identifiers are defined in
+// http://tools.ietf.org/html/rfc6125#section-1.8. A "presented identifier" is
+// one in the subjectAltName of the certificate, or sometimes within a CN of
+// the certificate's subject. The "reference identifier" is the one we are
+// being asked to match the certificate against. When checking name
+// constraints, the reference identifier is the entire encoded name constraint
+// extension value.
+
+#include <algorithm>
+
+#include "mozpkix/pkixcheck.h"
+#include "mozpkix/pkixutil.h"
+
+namespace mozilla { namespace pkix {
+
+namespace {
+
+// GeneralName ::= CHOICE {
+// otherName [0] OtherName,
+// rfc822Name [1] IA5String,
+// dNSName [2] IA5String,
+// x400Address [3] ORAddress,
+// directoryName [4] Name,
+// ediPartyName [5] EDIPartyName,
+// uniformResourceIdentifier [6] IA5String,
+// iPAddress [7] OCTET STRING,
+// registeredID [8] OBJECT IDENTIFIER }
+enum class GeneralNameType : uint8_t
+{
+ // Note that these values are NOT contiguous. Some values have the
+ // der::CONSTRUCTED bit set while others do not.
+ // (The der::CONSTRUCTED bit is for types where the value is a SEQUENCE.)
+ otherName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ rfc822Name = der::CONTEXT_SPECIFIC | 1,
+ dNSName = der::CONTEXT_SPECIFIC | 2,
+ x400Address = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3,
+ directoryName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 4,
+ ediPartyName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 5,
+ uniformResourceIdentifier = der::CONTEXT_SPECIFIC | 6,
+ iPAddress = der::CONTEXT_SPECIFIC | 7,
+ registeredID = der::CONTEXT_SPECIFIC | 8,
+ // nameConstraints is a pseudo-GeneralName used to signify that a
+ // reference ID is actually the entire name constraint extension.
+ nameConstraints = 0xff
+};
+
+inline Result
+ReadGeneralName(Reader& reader,
+ /*out*/ GeneralNameType& generalNameType,
+ /*out*/ Input& value)
+{
+ uint8_t tag;
+ Result rv = der::ReadTagAndGetValue(reader, tag, value);
+ if (rv != Success) {
+ return rv;
+ }
+ switch (tag) {
+ case static_cast<uint8_t>(GeneralNameType::otherName):
+ generalNameType = GeneralNameType::otherName;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::rfc822Name):
+ generalNameType = GeneralNameType::rfc822Name;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::dNSName):
+ generalNameType = GeneralNameType::dNSName;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::x400Address):
+ generalNameType = GeneralNameType::x400Address;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::directoryName):
+ generalNameType = GeneralNameType::directoryName;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::ediPartyName):
+ generalNameType = GeneralNameType::ediPartyName;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::uniformResourceIdentifier):
+ generalNameType = GeneralNameType::uniformResourceIdentifier;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::iPAddress):
+ generalNameType = GeneralNameType::iPAddress;
+ break;
+ case static_cast<uint8_t>(GeneralNameType::registeredID):
+ generalNameType = GeneralNameType::registeredID;
+ break;
+ default:
+ return Result::ERROR_BAD_DER;
+ }
+ return Success;
+}
+
+enum class MatchResult
+{
+ NoNamesOfGivenType = 0,
+ Mismatch = 1,
+ Match = 2
+};
+
+Result SearchNames(const Input* subjectAltName, Input subject,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ FallBackToSearchWithinSubject fallBackToCommonName,
+ /*out*/ MatchResult& match);
+Result SearchWithinRDN(Reader& rdn,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ FallBackToSearchWithinSubject fallBackToEmailAddress,
+ FallBackToSearchWithinSubject fallBackToCommonName,
+ /*in/out*/ MatchResult& match);
+Result MatchAVA(Input type,
+ uint8_t valueEncodingTag,
+ Input presentedID,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ FallBackToSearchWithinSubject fallBackToEmailAddress,
+ FallBackToSearchWithinSubject fallBackToCommonName,
+ /*in/out*/ MatchResult& match);
+Result ReadAVA(Reader& rdn,
+ /*out*/ Input& type,
+ /*out*/ uint8_t& valueTag,
+ /*out*/ Input& value);
+void MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
+ Input presentedID,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ /*in/out*/ MatchResult& match);
+
+Result MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
+ Input presentedID,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ /*in/out*/ MatchResult& matchResult);
+Result CheckPresentedIDConformsToConstraints(GeneralNameType referenceIDType,
+ Input presentedID,
+ Input nameConstraints);
+
+uint8_t LocaleInsensitveToLower(uint8_t a);
+bool StartsWithIDNALabel(Input id);
+
+enum class IDRole
+{
+ ReferenceID = 0,
+ PresentedID = 1,
+ NameConstraint = 2,
+};
+
+enum class AllowWildcards { No = 0, Yes = 1 };
+
+// DNSName constraints implicitly allow subdomain matching when there is no
+// leading dot ("foo.example.com" matches a constraint of "example.com"), but
+// RFC822Name constraints only allow subdomain matching when there is a leading
+// dot ("foo.example.com" does not match "example.com" but does match
+// ".example.com").
+enum class AllowDotlessSubdomainMatches { No = 0, Yes = 1 };
+
+bool IsValidDNSID(Input hostname, IDRole idRole,
+ AllowWildcards allowWildcards);
+
+Result MatchPresentedDNSIDWithReferenceDNSID(
+ Input presentedDNSID,
+ AllowWildcards allowWildcards,
+ AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
+ IDRole referenceDNSIDRole,
+ Input referenceDNSID,
+ /*out*/ bool& matches);
+
+Result MatchPresentedRFC822NameWithReferenceRFC822Name(
+ Input presentedRFC822Name, IDRole referenceRFC822NameRole,
+ Input referenceRFC822Name, /*out*/ bool& matches);
+
+} // namespace
+
+bool IsValidReferenceDNSID(Input hostname);
+bool IsValidPresentedDNSID(Input hostname);
+bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]);
+bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]);
+
+// This is used by the pkixnames_tests.cpp tests.
+Result
+MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
+ Input referenceDNSID,
+ /*out*/ bool& matches)
+{
+ return MatchPresentedDNSIDWithReferenceDNSID(
+ presentedDNSID, AllowWildcards::Yes,
+ AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
+ referenceDNSID, matches);
+}
+
+// Verify that the given end-entity cert, which is assumed to have been already
+// validated with BuildCertChain, is valid for the given hostname. hostname is
+// assumed to be a string representation of an IPv4 address, an IPv6 addresss,
+// or a normalized ASCII (possibly punycode) DNS name.
+Result
+CheckCertHostname(Input endEntityCertDER, Input hostname,
+ NameMatchingPolicy& nameMatchingPolicy)
+{
+ BackCert cert(endEntityCertDER, EndEntityOrCA::MustBeEndEntity, nullptr);
+ Result rv = cert.Init();
+ if (rv != Success) {
+ return rv;
+ }
+
+ Time notBefore(Time::uninitialized);
+ rv = ParseValidity(cert.GetValidity(), &notBefore);
+ if (rv != Success) {
+ return rv;
+ }
+ FallBackToSearchWithinSubject fallBackToSearchWithinSubject;
+ rv = nameMatchingPolicy.FallBackToCommonName(notBefore,
+ fallBackToSearchWithinSubject);
+ if (rv != Success) {
+ return rv;
+ }
+
+ const Input* subjectAltName(cert.GetSubjectAltName());
+ Input subject(cert.GetSubject());
+
+ // For backward compatibility with legacy certificates, we may fall back to
+ // searching for a name match in the subject common name for DNS names and
+ // IPv4 addresses. We don't do so for IPv6 addresses because we do not think
+ // there are many certificates that would need such fallback, and because
+ // comparisons of string representations of IPv6 addresses are particularly
+ // error prone due to the syntactic flexibility that IPv6 addresses have.
+ //
+ // IPv4 and IPv6 addresses are represented using the same type of GeneralName
+ // (iPAddress); they are differentiated by the lengths of the values.
+ MatchResult match;
+ uint8_t ipv6[16];
+ uint8_t ipv4[4];
+ if (IsValidReferenceDNSID(hostname)) {
+ rv = SearchNames(subjectAltName, subject, GeneralNameType::dNSName,
+ hostname, fallBackToSearchWithinSubject, match);
+ } else if (ParseIPv6Address(hostname, ipv6)) {
+ rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
+ Input(ipv6), FallBackToSearchWithinSubject::No, match);
+ } else if (ParseIPv4Address(hostname, ipv4)) {
+ rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
+ Input(ipv4), fallBackToSearchWithinSubject, match);
+ } else {
+ return Result::ERROR_BAD_CERT_DOMAIN;
+ }
+ if (rv != Success) {
+ return rv;
+ }
+ switch (match) {
+ case MatchResult::NoNamesOfGivenType: // fall through
+ case MatchResult::Mismatch:
+ return Result::ERROR_BAD_CERT_DOMAIN;
+ case MatchResult::Match:
+ return Success;
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+}
+
+// 4.2.1.10. Name Constraints
+Result
+CheckNameConstraints(Input encodedNameConstraints,
+ const BackCert& firstChild,
+ KeyPurposeId requiredEKUIfPresent)
+{
+ for (const BackCert* child = &firstChild; child; child = child->childCert) {
+ FallBackToSearchWithinSubject fallBackToCommonName
+ = (child->endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
+ requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth)
+ ? FallBackToSearchWithinSubject::Yes
+ : FallBackToSearchWithinSubject::No;
+
+ MatchResult match;
+ Result rv = SearchNames(child->GetSubjectAltName(), child->GetSubject(),
+ GeneralNameType::nameConstraints,
+ encodedNameConstraints, fallBackToCommonName,
+ match);
+ if (rv != Success) {
+ return rv;
+ }
+ switch (match) {
+ case MatchResult::Match: // fall through
+ case MatchResult::NoNamesOfGivenType:
+ break;
+ case MatchResult::Mismatch:
+ return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
+ }
+ }
+
+ return Success;
+}
+
+namespace {
+
+// SearchNames is used by CheckCertHostname and CheckNameConstraints.
+//
+// When called during name constraint checking, referenceIDType is
+// GeneralNameType::nameConstraints and referenceID is the entire encoded name
+// constraints extension value.
+//
+// The main benefit of using the exact same code paths for both is that we
+// ensure consistency between name validation and name constraint enforcement
+// regarding thing like "Which CN attributes should be considered as potential
+// CN-IDs" and "Which character sets are acceptable for CN-IDs?" If the name
+// matching and the name constraint enforcement logic were out of sync on these
+// issues (e.g. if name matching were to consider all subject CN attributes,
+// but name constraints were only enforced on the most specific subject CN),
+// trivial name constraint bypasses could result.
+
+Result
+SearchNames(/*optional*/ const Input* subjectAltName,
+ Input subject,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ FallBackToSearchWithinSubject fallBackToCommonName,
+ /*out*/ MatchResult& match)
+{
+ Result rv;
+
+ match = MatchResult::NoNamesOfGivenType;
+
+ // RFC 6125 says "A client MUST NOT seek a match for a reference identifier
+ // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or
+ // any application-specific identifier types supported by the client."
+ // Accordingly, we only consider CN-IDs if there are no DNS-IDs in the
+ // subjectAltName.
+ //
+ // RFC 6125 says that IP addresses are out of scope, but for backward
+ // compatibility we accept them, by considering IP addresses to be an
+ // "application-specific identifier type supported by the client."
+ //
+ // TODO(bug XXXXXXX): Consider strengthening this check to "A client MUST NOT
+ // seek a match for a reference identifier of CN-ID if the certificate
+ // contains a subjectAltName extension."
+ //
+ // TODO(bug XXXXXXX): Consider dropping support for IP addresses as
+ // identifiers completely.
+
+ if (subjectAltName) {
+ Reader altNames;
+ rv = der::ExpectTagAndGetValueAtEnd(*subjectAltName, der::SEQUENCE,
+ altNames);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // According to RFC 5280, "If the subjectAltName extension is present, the
+ // sequence MUST contain at least one entry." For compatibility reasons, we
+ // do not enforce this. See bug 1143085.
+ while (!altNames.AtEnd()) {
+ GeneralNameType presentedIDType;
+ Input presentedID;
+ rv = ReadGeneralName(altNames, presentedIDType, presentedID);
+ if (rv != Success) {
+ return rv;
+ }
+
+ rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
+ referenceIDType, referenceID,
+ match);
+ if (rv != Success) {
+ return rv;
+ }
+ if (referenceIDType != GeneralNameType::nameConstraints &&
+ match == MatchResult::Match) {
+ return Success;
+ }
+ if (presentedIDType == GeneralNameType::dNSName ||
+ presentedIDType == GeneralNameType::iPAddress) {
+ fallBackToCommonName = FallBackToSearchWithinSubject::No;
+ }
+ }
+ }
+
+ if (referenceIDType == GeneralNameType::nameConstraints) {
+ rv = CheckPresentedIDConformsToConstraints(GeneralNameType::directoryName,
+ subject, referenceID);
+ if (rv != Success) {
+ return rv;
+ }
+ }
+
+ FallBackToSearchWithinSubject fallBackToEmailAddress;
+ if (!subjectAltName &&
+ (referenceIDType == GeneralNameType::rfc822Name ||
+ referenceIDType == GeneralNameType::nameConstraints)) {
+ fallBackToEmailAddress = FallBackToSearchWithinSubject::Yes;
+ } else {
+ fallBackToEmailAddress = FallBackToSearchWithinSubject::No;
+ }
+
+ // Short-circuit the parsing of the subject name if we're not going to match
+ // any names in it
+ if (fallBackToEmailAddress == FallBackToSearchWithinSubject::No &&
+ fallBackToCommonName == FallBackToSearchWithinSubject::No) {
+ return Success;
+ }
+
+ // Attempt to match the reference ID against the CN-ID, which we consider to
+ // be the most-specific CN AVA in the subject field.
+ //
+ // https://tools.ietf.org/html/rfc6125#section-2.3.1 says:
+ //
+ // To reduce confusion, in this specification we avoid such terms and
+ // instead use the terms provided under Section 1.8; in particular, we
+ // do not use the term "(most specific) Common Name field in the subject
+ // field" from [HTTP-TLS] and instead state that a CN-ID is a Relative
+ // Distinguished Name (RDN) in the certificate subject containing one
+ // and only one attribute-type-and-value pair of type Common Name (thus
+ // removing the possibility that an RDN might contain multiple AVAs
+ // (Attribute Value Assertions) of type CN, one of which could be
+ // considered "most specific").
+ //
+ // https://tools.ietf.org/html/rfc6125#section-7.4 says:
+ //
+ // [...] Although it would be preferable to
+ // forbid multiple CN-IDs entirely, there are several reasons at this
+ // time why this specification states that they SHOULD NOT (instead of
+ // MUST NOT) be included [...]
+ //
+ // Consequently, it is unclear what to do when there are multiple CNs in the
+ // subject, regardless of whether there "SHOULD NOT" be.
+ //
+ // NSS's CERT_VerifyCertName mostly follows RFC2818 in this instance, which
+ // says:
+ //
+ // If a subjectAltName extension of type dNSName is present, that MUST
+ // be used as the identity. Otherwise, the (most specific) Common Name
+ // field in the Subject field of the certificate MUST be used.
+ //
+ // [...]
+ //
+ // In some cases, the URI is specified as an IP address rather than a
+ // hostname. In this case, the iPAddress subjectAltName must be present
+ // in the certificate and must exactly match the IP in the URI.
+ //
+ // (The main difference from RFC2818 is that NSS's CERT_VerifyCertName also
+ // matches IP addresses in the most-specific CN.)
+ //
+ // NSS's CERT_VerifyCertName finds the most specific CN via
+ // CERT_GetCommoName, which uses CERT_GetLastNameElement. Note that many
+ // NSS-based applications, including Gecko, also use CERT_GetCommonName. It
+ // is likely that other, non-NSS-based, applications also expect only the
+ // most specific CN to be matched against the reference ID.
+ //
+ // "A Layman's Guide to a Subset of ASN.1, BER, and DER" and other sources
+ // agree that an RDNSequence is ordered from most significant (least
+ // specific) to least significant (most specific), as do other references.
+ //
+ // However, Chromium appears to use the least-specific (first) CN instead of
+ // the most-specific; see https://crbug.com/366957. Also, MSIE and some other
+ // popular implementations apparently attempt to match the reference ID
+ // against any/all CNs in the subject. Since we're trying to phase out the
+ // use of CN-IDs, we intentionally avoid trying to match MSIE's more liberal
+ // behavior.
+
+ // Name ::= CHOICE { -- only one possibility for now --
+ // rdnSequence RDNSequence }
+ //
+ // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+ //
+ // RelativeDistinguishedName ::=
+ // SET SIZE (1..MAX) OF AttributeTypeAndValue
+ Reader subjectReader(subject);
+ return der::NestedOf(subjectReader, der::SEQUENCE, der::SET,
+ der::EmptyAllowed::Yes, [&](Reader& r) {
+ return SearchWithinRDN(r, referenceIDType, referenceID,
+ fallBackToEmailAddress, fallBackToCommonName, match);
+ });
+}
+
+// RelativeDistinguishedName ::=
+// SET SIZE (1..MAX) OF AttributeTypeAndValue
+//
+// AttributeTypeAndValue ::= SEQUENCE {
+// type AttributeType,
+// value AttributeValue }
+Result
+SearchWithinRDN(Reader& rdn,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ FallBackToSearchWithinSubject fallBackToEmailAddress,
+ FallBackToSearchWithinSubject fallBackToCommonName,
+ /*in/out*/ MatchResult& match)
+{
+ do {
+ Input type;
+ uint8_t valueTag;
+ Input value;
+ Result rv = ReadAVA(rdn, type, valueTag, value);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = MatchAVA(type, valueTag, value, referenceIDType, referenceID,
+ fallBackToEmailAddress, fallBackToCommonName, match);
+ if (rv != Success) {
+ return rv;
+ }
+ } while (!rdn.AtEnd());
+
+ return Success;
+}
+
+// AttributeTypeAndValue ::= SEQUENCE {
+// type AttributeType,
+// value AttributeValue }
+//
+// AttributeType ::= OBJECT IDENTIFIER
+//
+// AttributeValue ::= ANY -- DEFINED BY AttributeType
+//
+// DirectoryString ::= CHOICE {
+// teletexString TeletexString (SIZE (1..MAX)),
+// printableString PrintableString (SIZE (1..MAX)),
+// universalString UniversalString (SIZE (1..MAX)),
+// utf8String UTF8String (SIZE (1..MAX)),
+// bmpString BMPString (SIZE (1..MAX)) }
+Result
+MatchAVA(Input type, uint8_t valueEncodingTag, Input presentedID,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ FallBackToSearchWithinSubject fallBackToEmailAddress,
+ FallBackToSearchWithinSubject fallBackToCommonName,
+ /*in/out*/ MatchResult& match)
+{
+ // Try to match the CN as a DNSName or an IPAddress.
+ //
+ // id-at-commonName AttributeType ::= { id-at 3 }
+ //
+ // -- Naming attributes of type X520CommonName:
+ // -- X520CommonName ::= DirectoryName (SIZE (1..ub-common-name))
+ // --
+ // -- Expanded to avoid parameterized type:
+ // X520CommonName ::= CHOICE {
+ // teletexString TeletexString (SIZE (1..ub-common-name)),
+ // printableString PrintableString (SIZE (1..ub-common-name)),
+ // universalString UniversalString (SIZE (1..ub-common-name)),
+ // utf8String UTF8String (SIZE (1..ub-common-name)),
+ // bmpString BMPString (SIZE (1..ub-common-name)) }
+ //
+ // python DottedOIDToCode.py id-at-commonName 2.5.4.3
+ static const uint8_t id_at_commonName[] = {
+ 0x55, 0x04, 0x03
+ };
+ if (fallBackToCommonName == FallBackToSearchWithinSubject::Yes &&
+ InputsAreEqual(type, Input(id_at_commonName))) {
+ // We might have previously found a match. Now that we've found another CN,
+ // we no longer consider that previous match to be a match, so "forget" about
+ // it.
+ match = MatchResult::NoNamesOfGivenType;
+
+ // PrintableString is a subset of ASCII that contains all the characters
+ // allowed in CN-IDs except '*'. Although '*' is illegal, there are many
+ // real-world certificates that are encoded this way, so we accept it.
+ //
+ // In the case of UTF8String, we rely on the fact that in UTF-8 the octets in
+ // a multi-byte encoding of a code point are always distinct from ASCII. Any
+ // non-ASCII byte in a UTF-8 string causes us to fail to match. We make no
+ // attempt to detect or report malformed UTF-8 (e.g. incomplete or overlong
+ // encodings of code points, or encodings of invalid code points).
+ //
+ // TeletexString is supported as long as it does not contain any escape
+ // sequences, which are not supported. We'll reject escape sequences as
+ // invalid characters in names, which means we only accept strings that are
+ // in the default character set, which is a superset of ASCII. Note that NSS
+ // actually treats TeletexString as ISO-8859-1. Many certificates that have
+ // wildcard CN-IDs (e.g. "*.example.com") use TeletexString because
+ // PrintableString is defined to not allow '*' and because, at one point in
+ // history, UTF8String was too new to use for compatibility reasons.
+ //
+ // UniversalString and BMPString are also deprecated, and they are a little
+ // harder to support because they are not single-byte ASCII superset
+ // encodings, so we don't bother.
+ if (valueEncodingTag != der::PrintableString &&
+ valueEncodingTag != der::UTF8String &&
+ valueEncodingTag != der::TeletexString) {
+ return Success;
+ }
+
+ if (IsValidPresentedDNSID(presentedID)) {
+ MatchSubjectPresentedIDWithReferenceID(GeneralNameType::dNSName,
+ presentedID, referenceIDType,
+ referenceID, match);
+ } else {
+ // We don't match CN-IDs for IPv6 addresses.
+ // MatchSubjectPresentedIDWithReferenceID ensures that it won't match an
+ // IPv4 address with an IPv6 address, so we don't need to check that
+ // referenceID is an IPv4 address here.
+ uint8_t ipv4[4];
+ if (ParseIPv4Address(presentedID, ipv4)) {
+ MatchSubjectPresentedIDWithReferenceID(GeneralNameType::iPAddress,
+ Input(ipv4), referenceIDType,
+ referenceID, match);
+ }
+ }
+
+ // Regardless of whether there was a match, we keep going in case we find
+ // another CN later. If we do find another one, then this match/mismatch
+ // will be ignored, because we only care about the most specific CN.
+
+ return Success;
+ }
+
+ // Match an email address against an emailAddress attribute in the
+ // subject.
+ //
+ // id-emailAddress AttributeType ::= { pkcs-9 1 }
+ //
+ // EmailAddress ::= IA5String (SIZE (1..ub-emailaddress-length))
+ //
+ // python DottedOIDToCode.py id-emailAddress 1.2.840.113549.1.9.1
+ static const uint8_t id_emailAddress[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
+ };
+ if (fallBackToEmailAddress == FallBackToSearchWithinSubject::Yes &&
+ InputsAreEqual(type, Input(id_emailAddress))) {
+ if (referenceIDType == GeneralNameType::rfc822Name &&
+ match == MatchResult::Match) {
+ // We already found a match; we don't need to match another one
+ return Success;
+ }
+ if (valueEncodingTag != der::IA5String) {
+ return Result::ERROR_BAD_DER;
+ }
+ return MatchPresentedIDWithReferenceID(GeneralNameType::rfc822Name,
+ presentedID, referenceIDType,
+ referenceID, match);
+ }
+
+ return Success;
+}
+
+void
+MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
+ Input presentedID,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ /*in/out*/ MatchResult& match)
+{
+ Result rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
+ referenceIDType, referenceID,
+ match);
+ if (rv != Success) {
+ match = MatchResult::Mismatch;
+ }
+}
+
+Result
+MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
+ Input presentedID,
+ GeneralNameType referenceIDType,
+ Input referenceID,
+ /*out*/ MatchResult& matchResult)
+{
+ if (referenceIDType == GeneralNameType::nameConstraints) {
+ // matchResult is irrelevant when checking name constraints; only the
+ // pass/fail result of CheckPresentedIDConformsToConstraints matters.
+ return CheckPresentedIDConformsToConstraints(presentedIDType, presentedID,
+ referenceID);
+ }
+
+ if (presentedIDType != referenceIDType) {
+ matchResult = MatchResult::Mismatch;
+ return Success;
+ }
+
+ Result rv;
+ bool foundMatch;
+
+ switch (referenceIDType) {
+ case GeneralNameType::dNSName:
+ rv = MatchPresentedDNSIDWithReferenceDNSID(
+ presentedID, AllowWildcards::Yes,
+ AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
+ referenceID, foundMatch);
+ break;
+
+ case GeneralNameType::iPAddress:
+ foundMatch = InputsAreEqual(presentedID, referenceID);
+ rv = Success;
+ break;
+
+ case GeneralNameType::rfc822Name:
+ rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
+ presentedID, IDRole::ReferenceID, referenceID, foundMatch);
+ break;
+
+ case GeneralNameType::directoryName:
+ // TODO: At some point, we may add APIs for matching DirectoryNames.
+ // fall through
+
+ case GeneralNameType::otherName: // fall through
+ case GeneralNameType::x400Address: // fall through
+ case GeneralNameType::ediPartyName: // fall through
+ case GeneralNameType::uniformResourceIdentifier: // fall through
+ case GeneralNameType::registeredID: // fall through
+ case GeneralNameType::nameConstraints:
+ return NotReached("unexpected nameType for SearchType::Match",
+ Result::FATAL_ERROR_INVALID_ARGS);
+
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+
+ if (rv != Success) {
+ return rv;
+ }
+ matchResult = foundMatch ? MatchResult::Match : MatchResult::Mismatch;
+ return Success;
+}
+
+enum class NameConstraintsSubtrees : uint8_t
+{
+ permittedSubtrees = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
+ excludedSubtrees = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1
+};
+
+Result CheckPresentedIDConformsToNameConstraintsSubtrees(
+ GeneralNameType presentedIDType,
+ Input presentedID,
+ Reader& nameConstraints,
+ NameConstraintsSubtrees subtreesType);
+Result MatchPresentedIPAddressWithConstraint(Input presentedID,
+ Input iPAddressConstraint,
+ /*out*/ bool& foundMatch);
+Result MatchPresentedDirectoryNameWithConstraint(
+ NameConstraintsSubtrees subtreesType, Input presentedID,
+ Input directoryNameConstraint, /*out*/ bool& matches);
+
+Result
+CheckPresentedIDConformsToConstraints(
+ GeneralNameType presentedIDType,
+ Input presentedID,
+ Input encodedNameConstraints)
+{
+ // NameConstraints ::= SEQUENCE {
+ // permittedSubtrees [0] GeneralSubtrees OPTIONAL,
+ // excludedSubtrees [1] GeneralSubtrees OPTIONAL }
+ Reader nameConstraints;
+ Result rv = der::ExpectTagAndGetValueAtEnd(encodedNameConstraints,
+ der::SEQUENCE, nameConstraints);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // RFC 5280 says "Conforming CAs MUST NOT issue certificates where name
+ // constraints is an empty sequence. That is, either the permittedSubtrees
+ // field or the excludedSubtrees MUST be present."
+ if (nameConstraints.AtEnd()) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
+ presentedIDType, presentedID, nameConstraints,
+ NameConstraintsSubtrees::permittedSubtrees);
+ if (rv != Success) {
+ return rv;
+ }
+
+ rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
+ presentedIDType, presentedID, nameConstraints,
+ NameConstraintsSubtrees::excludedSubtrees);
+ if (rv != Success) {
+ return rv;
+ }
+
+ return der::End(nameConstraints);
+}
+
+Result
+CheckPresentedIDConformsToNameConstraintsSubtrees(
+ GeneralNameType presentedIDType,
+ Input presentedID,
+ Reader& nameConstraints,
+ NameConstraintsSubtrees subtreesType)
+{
+ if (!nameConstraints.Peek(static_cast<uint8_t>(subtreesType))) {
+ return Success;
+ }
+
+ Reader subtrees;
+ Result rv = der::ExpectTagAndGetValue(nameConstraints,
+ static_cast<uint8_t>(subtreesType),
+ subtrees);
+ if (rv != Success) {
+ return rv;
+ }
+
+ bool hasPermittedSubtreesMatch = false;
+ bool hasPermittedSubtreesMismatch = false;
+
+ // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
+ //
+ // do { ... } while(...) because subtrees isn't allowed to be empty.
+ do {
+ // GeneralSubtree ::= SEQUENCE {
+ // base GeneralName,
+ // minimum [0] BaseDistance DEFAULT 0,
+ // maximum [1] BaseDistance OPTIONAL }
+ Reader subtree;
+ rv = ExpectTagAndGetValue(subtrees, der::SEQUENCE, subtree);
+ if (rv != Success) {
+ return rv;
+ }
+ GeneralNameType nameConstraintType;
+ Input base;
+ rv = ReadGeneralName(subtree, nameConstraintType, base);
+ if (rv != Success) {
+ return rv;
+ }
+ // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
+ // profile, the minimum and maximum fields are not used with any name
+ // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
+ //
+ // Since the default value isn't allowed to be encoded according to the DER
+ // encoding rules for DEFAULT, this is equivalent to saying that neither
+ // minimum or maximum must be encoded.
+ rv = der::End(subtree);
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (presentedIDType == nameConstraintType) {
+ bool matches;
+
+ switch (presentedIDType) {
+ case GeneralNameType::dNSName:
+ rv = MatchPresentedDNSIDWithReferenceDNSID(
+ presentedID, AllowWildcards::Yes,
+ AllowDotlessSubdomainMatches::Yes, IDRole::NameConstraint,
+ base, matches);
+ if (rv != Success) {
+ return rv;
+ }
+ break;
+
+ case GeneralNameType::iPAddress:
+ rv = MatchPresentedIPAddressWithConstraint(presentedID, base,
+ matches);
+ if (rv != Success) {
+ return rv;
+ }
+ break;
+
+ case GeneralNameType::directoryName:
+ rv = MatchPresentedDirectoryNameWithConstraint(subtreesType,
+ presentedID, base,
+ matches);
+ if (rv != Success) {
+ return rv;
+ }
+ break;
+
+ case GeneralNameType::rfc822Name:
+ rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
+ presentedID, IDRole::NameConstraint, base, matches);
+ if (rv != Success) {
+ return rv;
+ }
+ break;
+
+ // RFC 5280 says "Conforming CAs [...] SHOULD NOT impose name
+ // constraints on the x400Address, ediPartyName, or registeredID
+ // name forms. It also says "Applications conforming to this profile
+ // [...] SHOULD be able to process name constraints that are imposed
+ // on [...] uniformResourceIdentifier [...]", but we don't bother.
+ //
+ // TODO: Ask to have spec updated to say ""Conforming CAs [...] SHOULD
+ // NOT impose name constraints on the otherName, x400Address,
+ // ediPartyName, uniformResourceIdentifier, or registeredID name
+ // forms."
+ case GeneralNameType::otherName: // fall through
+ case GeneralNameType::x400Address: // fall through
+ case GeneralNameType::ediPartyName: // fall through
+ case GeneralNameType::uniformResourceIdentifier: // fall through
+ case GeneralNameType::registeredID: // fall through
+ return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
+
+ case GeneralNameType::nameConstraints:
+ return NotReached("invalid presentedIDType",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+
+ switch (subtreesType) {
+ case NameConstraintsSubtrees::permittedSubtrees:
+ if (matches) {
+ hasPermittedSubtreesMatch = true;
+ } else {
+ hasPermittedSubtreesMismatch = true;
+ }
+ break;
+ case NameConstraintsSubtrees::excludedSubtrees:
+ if (matches) {
+ return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
+ }
+ break;
+ }
+ }
+ } while (!subtrees.AtEnd());
+
+ if (hasPermittedSubtreesMismatch && !hasPermittedSubtreesMatch) {
+ // If there was any entry of the given type in permittedSubtrees, then it
+ // required that at least one of them must match. Since none of them did,
+ // we have a failure.
+ return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
+ }
+
+ return Success;
+}
+
+// We do not distinguish between a syntactically-invalid presentedDNSID and one
+// that is syntactically valid but does not match referenceDNSID; in both
+// cases, the result is false.
+//
+// We assume that both presentedDNSID and referenceDNSID are encoded in such a
+// way that US-ASCII (7-bit) characters are encoded in one byte and no encoding
+// of a non-US-ASCII character contains a code point in the range 0-127. For
+// example, UTF-8 is OK but UTF-16 is not.
+//
+// RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
+// <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
+// follow NSS's stricter policy by accepting wildcards only of the form
+// <x>*.<DNSID>, where <x> may be empty.
+//
+// An relative presented DNS ID matches both an absolute reference ID and a
+// relative reference ID. Absolute presented DNS IDs are not supported:
+//
+// Presented ID Reference ID Result
+// -------------------------------------
+// example.com example.com Match
+// example.com. example.com Mismatch
+// example.com example.com. Match
+// example.com. example.com. Mismatch
+//
+// There are more subtleties documented inline in the code.
+//
+// Name constraints ///////////////////////////////////////////////////////////
+//
+// This is all RFC 5280 has to say about DNSName constraints:
+//
+// DNS name restrictions are expressed as host.example.com. Any DNS
+// name that can be constructed by simply adding zero or more labels to
+// the left-hand side of the name satisfies the name constraint. For
+// example, www.host.example.com would satisfy the constraint but
+// host1.example.com would not.
+//
+// This lack of specificity has lead to a lot of uncertainty regarding
+// subdomain matching. In particular, the following questions have been
+// raised and answered:
+//
+// Q: Does a presented identifier equal (case insensitive) to the name
+// constraint match the constraint? For example, does the presented
+// ID "host.example.com" match a "host.example.com" constraint?
+// A: Yes. RFC5280 says "by simply adding zero or more labels" and this
+// is the case of adding zero labels.
+//
+// Q: When the name constraint does not start with ".", do subdomain
+// presented identifiers match it? For example, does the presented
+// ID "www.host.example.com" match a "host.example.com" constraint?
+// A: Yes. RFC5280 says "by simply adding zero or more labels" and this
+// is the case of adding more than zero labels. The example is the
+// one from RFC 5280.
+//
+// Q: When the name constraint does not start with ".", does a
+// non-subdomain prefix match it? For example, does "bigfoo.bar.com"
+// match "foo.bar.com"? [4]
+// A: No. We interpret RFC 5280's language of "adding zero or more labels"
+// to mean that whole labels must be prefixed.
+//
+// (Note that the above three scenarios are the same as the RFC 6265
+// domain matching rules [0].)
+//
+// Q: Is a name constraint that starts with "." valid, and if so, what
+// semantics does it have? For example, does a presented ID of
+// "www.example.com" match a constraint of ".example.com"? Does a
+// presented ID of "example.com" match a constraint of ".example.com"?
+// A: This implementation, NSS[1], and SChannel[2] all support a
+// leading ".", but OpenSSL[3] does not yet. Amongst the
+// implementations that support it, a leading "." is legal and means
+// the same thing as when the "." is omitted, EXCEPT that a
+// presented identifier equal (case insensitive) to the name
+// constraint is not matched; i.e. presented DNSName identifiers
+// must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
+// have name constraints with the leading "." in their root
+// certificates. The name constraints imposed on DCISS by Mozilla also
+// have the it, so supporting this is a requirement for backward
+// compatibility, even if it is not yet standardized. So, for example, a
+// presented ID of "www.example.com" matches a constraint of
+// ".example.com" but a presented ID of "example.com" does not.
+//
+// Q: Is there a way to prevent subdomain matches?
+// A: Yes.
+//
+// Some people have proposed that dNSName constraints that do not
+// start with a "." should be restricted to exact (case insensitive)
+// matches. However, such a change of semantics from what RFC5280
+// specifies would be a non-backward-compatible change in the case of
+// permittedSubtrees constraints, and it would be a security issue for
+// excludedSubtrees constraints.
+//
+// However, it can be done with a combination of permittedSubtrees and
+// excludedSubtrees, e.g. "example.com" in permittedSubtrees and
+// ".example.com" in excudedSubtrees.
+//
+// Q: Are name constraints allowed to be specified as absolute names?
+// For example, does a presented ID of "example.com" match a name
+// constraint of "example.com." and vice versa.
+// A: Absolute names are not supported as presented IDs or name
+// constraints. Only reference IDs may be absolute.
+//
+// Q: Is "" a valid DNSName constraints? If so, what does it mean?
+// A: Yes. Any valid presented DNSName can be formed "by simply adding zero
+// or more labels to the left-hand side" of "". In particular, an
+// excludedSubtrees DNSName constraint of "" forbids all DNSNames.
+//
+// Q: Is "." a valid DNSName constraints? If so, what does it mean?
+// A: No, because absolute names are not allowed (see above).
+//
+// [0] RFC 6265 (Cookies) Domain Matching rules:
+// http://tools.ietf.org/html/rfc6265#section-5.1.3
+// [1] NSS source code:
+// https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
+// [2] Description of SChannel's behavior from Microsoft:
+// http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
+// [3] Proposal to add such support to OpenSSL:
+// http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
+// https://rt.openssl.org/Ticket/Display.html?id=3562
+// [4] Feedback on the lack of clarify in the definition that never got
+// incorporated into the spec:
+// https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
+Result
+MatchPresentedDNSIDWithReferenceDNSID(
+ Input presentedDNSID,
+ AllowWildcards allowWildcards,
+ AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
+ IDRole referenceDNSIDRole,
+ Input referenceDNSID,
+ /*out*/ bool& matches)
+{
+ if (!IsValidDNSID(presentedDNSID, IDRole::PresentedID, allowWildcards)) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ if (!IsValidDNSID(referenceDNSID, referenceDNSIDRole, AllowWildcards::No)) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ Reader presented(presentedDNSID);
+ Reader reference(referenceDNSID);
+
+ switch (referenceDNSIDRole)
+ {
+ case IDRole::ReferenceID:
+ break;
+
+ case IDRole::NameConstraint:
+ {
+ if (presentedDNSID.GetLength() > referenceDNSID.GetLength()) {
+ if (referenceDNSID.GetLength() == 0) {
+ // An empty constraint matches everything.
+ matches = true;
+ return Success;
+ }
+ // If the reference ID starts with a dot then skip the prefix of
+ // of the presented ID and start the comparison at the position of that
+ // dot. Examples:
+ //
+ // Matches Doesn't Match
+ // -----------------------------------------------------------
+ // original presented ID: www.example.com badexample.com
+ // skipped: www ba
+ // presented ID w/o prefix: .example.com dexample.com
+ // reference ID: .example.com .example.com
+ //
+ // If the reference ID does not start with a dot then we skip the
+ // prefix of the presented ID but also verify that the prefix ends with
+ // a dot. Examples:
+ //
+ // Matches Doesn't Match
+ // -----------------------------------------------------------
+ // original presented ID: www.example.com badexample.com
+ // skipped: www ba
+ // must be '.': . d
+ // presented ID w/o prefix: example.com example.com
+ // reference ID: example.com example.com
+ //
+ if (reference.Peek('.')) {
+ if (presented.Skip(static_cast<Input::size_type>(
+ presentedDNSID.GetLength() -
+ referenceDNSID.GetLength())) != Success) {
+ return NotReached("skipping subdomain failed",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+ } else if (allowDotlessSubdomainMatches ==
+ AllowDotlessSubdomainMatches::Yes) {
+ if (presented.Skip(static_cast<Input::size_type>(
+ presentedDNSID.GetLength() -
+ referenceDNSID.GetLength() - 1)) != Success) {
+ return NotReached("skipping subdomains failed",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+ uint8_t b;
+ if (presented.Read(b) != Success) {
+ return NotReached("reading from presentedDNSID failed",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+ if (b != '.') {
+ matches = false;
+ return Success;
+ }
+ }
+ }
+ break;
+ }
+
+ case IDRole::PresentedID: // fall through
+ return NotReached("IDRole::PresentedID is not a valid referenceDNSIDRole",
+ Result::FATAL_ERROR_INVALID_ARGS);
+ }
+
+ // We only allow wildcard labels that consist only of '*'.
+ if (presented.Peek('*')) {
+ if (presented.Skip(1) != Success) {
+ return NotReached("Skipping '*' failed",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+ do {
+ // This will happen if reference is a single, relative label
+ if (reference.AtEnd()) {
+ matches = false;
+ return Success;
+ }
+ uint8_t referenceByte;
+ if (reference.Read(referenceByte) != Success) {
+ return NotReached("invalid reference ID",
+ Result::FATAL_ERROR_INVALID_ARGS);
+ }
+ } while (!reference.Peek('.'));
+ }
+
+ for (;;) {
+ uint8_t presentedByte;
+ if (presented.Read(presentedByte) != Success) {
+ matches = false;
+ return Success;
+ }
+ uint8_t referenceByte;
+ if (reference.Read(referenceByte) != Success) {
+ matches = false;
+ return Success;
+ }
+ if (LocaleInsensitveToLower(presentedByte) !=
+ LocaleInsensitveToLower(referenceByte)) {
+ matches = false;
+ return Success;
+ }
+ if (presented.AtEnd()) {
+ // Don't allow presented IDs to be absolute.
+ if (presentedByte == '.') {
+ return Result::ERROR_BAD_DER;
+ }
+ break;
+ }
+ }
+
+ // Allow a relative presented DNS ID to match an absolute reference DNS ID,
+ // unless we're matching a name constraint.
+ if (!reference.AtEnd()) {
+ if (referenceDNSIDRole != IDRole::NameConstraint) {
+ uint8_t referenceByte;
+ if (reference.Read(referenceByte) != Success) {
+ return NotReached("read failed but not at end",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+ if (referenceByte != '.') {
+ matches = false;
+ return Success;
+ }
+ }
+ if (!reference.AtEnd()) {
+ matches = false;
+ return Success;
+ }
+ }
+
+ matches = true;
+ return Success;
+}
+
+// https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says:
+//
+// For IPv4 addresses, the iPAddress field of GeneralName MUST contain
+// eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent
+// an address range [RFC4632]. For IPv6 addresses, the iPAddress field
+// MUST contain 32 octets similarly encoded. For example, a name
+// constraint for "class C" subnet 192.0.2.0 is represented as the
+// octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
+// 192.0.2.0/24 (mask 255.255.255.0).
+Result
+MatchPresentedIPAddressWithConstraint(Input presentedID,
+ Input iPAddressConstraint,
+ /*out*/ bool& foundMatch)
+{
+ if (presentedID.GetLength() != 4 && presentedID.GetLength() != 16) {
+ return Result::ERROR_BAD_DER;
+ }
+ if (iPAddressConstraint.GetLength() != 8 &&
+ iPAddressConstraint.GetLength() != 32) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ // an IPv4 address never matches an IPv6 constraint, and vice versa.
+ if (presentedID.GetLength() * 2 != iPAddressConstraint.GetLength()) {
+ foundMatch = false;
+ return Success;
+ }
+
+ Reader constraint(iPAddressConstraint);
+ Reader constraintAddress;
+ Result rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u,
+ constraintAddress);
+ if (rv != Success) {
+ return rv;
+ }
+ Reader constraintMask;
+ rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u, constraintMask);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::End(constraint);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Reader presented(presentedID);
+ do {
+ uint8_t presentedByte;
+ rv = presented.Read(presentedByte);
+ if (rv != Success) {
+ return rv;
+ }
+ uint8_t constraintAddressByte;
+ rv = constraintAddress.Read(constraintAddressByte);
+ if (rv != Success) {
+ return rv;
+ }
+ uint8_t constraintMaskByte;
+ rv = constraintMask.Read(constraintMaskByte);
+ if (rv != Success) {
+ return rv;
+ }
+ foundMatch =
+ ((presentedByte ^ constraintAddressByte) & constraintMaskByte) == 0;
+ } while (foundMatch && !presented.AtEnd());
+
+ return Success;
+}
+
+// AttributeTypeAndValue ::= SEQUENCE {
+// type AttributeType,
+// value AttributeValue }
+//
+// AttributeType ::= OBJECT IDENTIFIER
+//
+// AttributeValue ::= ANY -- DEFINED BY AttributeType
+Result
+ReadAVA(Reader& rdn,
+ /*out*/ Input& type,
+ /*out*/ uint8_t& valueTag,
+ /*out*/ Input& value)
+{
+ return der::Nested(rdn, der::SEQUENCE, [&](Reader& ava) -> Result {
+ Result rv = der::ExpectTagAndGetValue(ava, der::OIDTag, type);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = der::ReadTagAndGetValue(ava, valueTag, value);
+ if (rv != Success) {
+ return rv;
+ }
+ return Success;
+ });
+}
+
+// Names are sequences of RDNs. RDNS are sets of AVAs. That means that RDNs are
+// unordered, so in theory we should match RDNs with equivalent AVAs that are
+// in different orders. Within the AVAs are DirectoryNames that are supposed to
+// be compared according to LDAP stringprep normalization rules (e.g.
+// normalizing whitespace), consideration of different character encodings,
+// etc. Indeed, RFC 5280 says we MUST deal with all of that.
+//
+// In practice, many implementations, including NSS, only match Names in a way
+// that only meets a subset of the requirements of RFC 5280. Those
+// normalization and character encoding conversion steps appear to be
+// unnecessary for processing real-world certificates, based on experience from
+// having used NSS in Firefox for many years.
+//
+// RFC 5280 also says "CAs issuing certificates with a restriction of the form
+// directoryName SHOULD NOT rely on implementation of the full
+// ISO DN name comparison algorithm. This implies name restrictions MUST
+// be stated identically to the encoding used in the subject field or
+// subjectAltName extension." It goes on to say, in the security
+// considerations:
+//
+// In addition, name constraints for distinguished names MUST be stated
+// identically to the encoding used in the subject field or
+// subjectAltName extension. If not, then name constraints stated as
+// excludedSubtrees will not match and invalid paths will be accepted
+// and name constraints expressed as permittedSubtrees will not match
+// and valid paths will be rejected. To avoid acceptance of invalid
+// paths, CAs SHOULD state name constraints for distinguished names as
+// permittedSubtrees wherever possible.
+//
+// For permittedSubtrees, the MUST-level requirement is relaxed for
+// compatibility in the case of PrintableString and UTF8String. That is, if a
+// name constraint has been encoded using UTF8String and the presented ID has
+// been encoded with a PrintableString (or vice-versa), they are considered to
+// match if they are equal everywhere except for the tag identifying the
+// encoding. See bug 1150114.
+//
+// For excludedSubtrees, we simply prohibit any non-empty directoryName
+// constraint to ensure we are not being too lenient. We support empty
+// DirectoryName constraints in excludedSubtrees so that a CA can say "Do not
+// allow any DirectoryNames in issued certificates."
+Result
+MatchPresentedDirectoryNameWithConstraint(NameConstraintsSubtrees subtreesType,
+ Input presentedID,
+ Input directoryNameConstraint,
+ /*out*/ bool& matches)
+{
+ Reader constraintRDNs;
+ Result rv = der::ExpectTagAndGetValueAtEnd(directoryNameConstraint,
+ der::SEQUENCE, constraintRDNs);
+ if (rv != Success) {
+ return rv;
+ }
+ Reader presentedRDNs;
+ rv = der::ExpectTagAndGetValueAtEnd(presentedID, der::SEQUENCE,
+ presentedRDNs);
+ if (rv != Success) {
+ return rv;
+ }
+
+ switch (subtreesType) {
+ case NameConstraintsSubtrees::permittedSubtrees:
+ break; // dealt with below
+ case NameConstraintsSubtrees::excludedSubtrees:
+ if (!constraintRDNs.AtEnd() || !presentedRDNs.AtEnd()) {
+ return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
+ }
+ matches = true;
+ return Success;
+ }
+
+ for (;;) {
+ // The AVAs have to be fully equal, but the constraint RDNs just need to be
+ // a prefix of the presented RDNs.
+ if (constraintRDNs.AtEnd()) {
+ matches = true;
+ return Success;
+ }
+ if (presentedRDNs.AtEnd()) {
+ matches = false;
+ return Success;
+ }
+ Reader constraintRDN;
+ rv = der::ExpectTagAndGetValue(constraintRDNs, der::SET, constraintRDN);
+ if (rv != Success) {
+ return rv;
+ }
+ Reader presentedRDN;
+ rv = der::ExpectTagAndGetValue(presentedRDNs, der::SET, presentedRDN);
+ if (rv != Success) {
+ return rv;
+ }
+ while (!constraintRDN.AtEnd() && !presentedRDN.AtEnd()) {
+ Input constraintType;
+ uint8_t constraintValueTag;
+ Input constraintValue;
+ rv = ReadAVA(constraintRDN, constraintType, constraintValueTag,
+ constraintValue);
+ if (rv != Success) {
+ return rv;
+ }
+ Input presentedType;
+ uint8_t presentedValueTag;
+ Input presentedValue;
+ rv = ReadAVA(presentedRDN, presentedType, presentedValueTag,
+ presentedValue);
+ if (rv != Success) {
+ return rv;
+ }
+ // TODO (bug 1155767): verify that if an AVA is a PrintableString it
+ // consists only of characters valid for PrintableStrings.
+ bool avasMatch =
+ InputsAreEqual(constraintType, presentedType) &&
+ InputsAreEqual(constraintValue, presentedValue) &&
+ (constraintValueTag == presentedValueTag ||
+ (constraintValueTag == der::Tag::UTF8String &&
+ presentedValueTag == der::Tag::PrintableString) ||
+ (constraintValueTag == der::Tag::PrintableString &&
+ presentedValueTag == der::Tag::UTF8String));
+ if (!avasMatch) {
+ matches = false;
+ return Success;
+ }
+ }
+ if (!constraintRDN.AtEnd() || !presentedRDN.AtEnd()) {
+ matches = false;
+ return Success;
+ }
+ }
+}
+
+// RFC 5280 says:
+//
+// The format of an rfc822Name is a "Mailbox" as defined in Section 4.1.2
+// of [RFC2821]. A Mailbox has the form "Local-part@Domain". Note that a
+// Mailbox has no phrase (such as a common name) before it, has no comment
+// (text surrounded in parentheses) after it, and is not surrounded by "<"
+// and ">". Rules for encoding Internet mail addresses that include
+// internationalized domain names are specified in Section 7.5.
+//
+// and:
+//
+// A name constraint for Internet mail addresses MAY specify a
+// particular mailbox, all addresses at a particular host, or all
+// mailboxes in a domain. To indicate a particular mailbox, the
+// constraint is the complete mail address. For example,
+// "root@example.com" indicates the root mailbox on the host
+// "example.com". To indicate all Internet mail addresses on a
+// particular host, the constraint is specified as the host name. For
+// example, the constraint "example.com" is satisfied by any mail
+// address at the host "example.com". To specify any address within a
+// domain, the constraint is specified with a leading period (as with
+// URIs). For example, ".example.com" indicates all the Internet mail
+// addresses in the domain "example.com", but not Internet mail
+// addresses on the host "example.com".
+
+bool
+IsValidRFC822Name(Input input)
+{
+ Reader reader(input);
+
+ // Local-part@.
+ bool startOfAtom = true;
+ for (;;) {
+ uint8_t presentedByte;
+ if (reader.Read(presentedByte) != Success) {
+ return false;
+ }
+ switch (presentedByte) {
+ // atext is defined in https://tools.ietf.org/html/rfc2822#section-3.2.4
+ case 'A': case 'a': case 'N': case 'n': case '0': case '!': case '#':
+ case 'B': case 'b': case 'O': case 'o': case '1': case '$': case '%':
+ case 'C': case 'c': case 'P': case 'p': case '2': case '&': case '\'':
+ case 'D': case 'd': case 'Q': case 'q': case '3': case '*': case '+':
+ case 'E': case 'e': case 'R': case 'r': case '4': case '-': case '/':
+ case 'F': case 'f': case 'S': case 's': case '5': case '=': case '?':
+ case 'G': case 'g': case 'T': case 't': case '6': case '^': case '_':
+ case 'H': case 'h': case 'U': case 'u': case '7': case '`': case '{':
+ case 'I': case 'i': case 'V': case 'v': case '8': case '|': case '}':
+ case 'J': case 'j': case 'W': case 'w': case '9': case '~':
+ case 'K': case 'k': case 'X': case 'x':
+ case 'L': case 'l': case 'Y': case 'y':
+ case 'M': case 'm': case 'Z': case 'z':
+ startOfAtom = false;
+ break;
+
+ case '.':
+ if (startOfAtom) {
+ return false;
+ }
+ startOfAtom = true;
+ break;
+
+ case '@':
+ {
+ if (startOfAtom) {
+ return false;
+ }
+ Input domain;
+ if (reader.SkipToEnd(domain) != Success) {
+ return false;
+ }
+ return IsValidDNSID(domain, IDRole::PresentedID, AllowWildcards::No);
+ }
+
+ default:
+ return false;
+ }
+ }
+}
+
+Result
+MatchPresentedRFC822NameWithReferenceRFC822Name(Input presentedRFC822Name,
+ IDRole referenceRFC822NameRole,
+ Input referenceRFC822Name,
+ /*out*/ bool& matches)
+{
+ if (!IsValidRFC822Name(presentedRFC822Name)) {
+ return Result::ERROR_BAD_DER;
+ }
+ Reader presented(presentedRFC822Name);
+
+ switch (referenceRFC822NameRole)
+ {
+ case IDRole::PresentedID:
+ return Result::FATAL_ERROR_INVALID_ARGS;
+
+ case IDRole::ReferenceID:
+ break;
+
+ case IDRole::NameConstraint:
+ {
+ if (InputContains(referenceRFC822Name, '@')) {
+ // The constraint is of the form "Local-part@Domain".
+ break;
+ }
+
+ // The constraint is of the form "example.com" or ".example.com".
+
+ // Skip past the '@' in the presented ID.
+ for (;;) {
+ uint8_t presentedByte;
+ if (presented.Read(presentedByte) != Success) {
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+ if (presentedByte == '@') {
+ break;
+ }
+ }
+
+ Input presentedDNSID;
+ if (presented.SkipToEnd(presentedDNSID) != Success) {
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ return MatchPresentedDNSIDWithReferenceDNSID(
+ presentedDNSID, AllowWildcards::No,
+ AllowDotlessSubdomainMatches::No, IDRole::NameConstraint,
+ referenceRFC822Name, matches);
+ }
+ }
+
+ if (!IsValidRFC822Name(referenceRFC822Name)) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ Reader reference(referenceRFC822Name);
+
+ for (;;) {
+ uint8_t presentedByte;
+ if (presented.Read(presentedByte) != Success) {
+ matches = reference.AtEnd();
+ return Success;
+ }
+ uint8_t referenceByte;
+ if (reference.Read(referenceByte) != Success) {
+ matches = false;
+ return Success;
+ }
+ if (LocaleInsensitveToLower(presentedByte) !=
+ LocaleInsensitveToLower(referenceByte)) {
+ matches = false;
+ return Success;
+ }
+ }
+}
+
+// We avoid isdigit because it is locale-sensitive. See
+// http://pubs.opengroup.org/onlinepubs/009695399/functions/tolower.html.
+inline uint8_t
+LocaleInsensitveToLower(uint8_t a)
+{
+ if (a >= 'A' && a <= 'Z') { // unlikely
+ return static_cast<uint8_t>(
+ static_cast<uint8_t>(a - static_cast<uint8_t>('A')) +
+ static_cast<uint8_t>('a'));
+ }
+ return a;
+}
+
+bool
+StartsWithIDNALabel(Input id)
+{
+ static const uint8_t IDN_ALABEL_PREFIX[4] = { 'x', 'n', '-', '-' };
+ Reader input(id);
+ for (const uint8_t prefixByte : IDN_ALABEL_PREFIX) {
+ uint8_t b;
+ if (input.Read(b) != Success) {
+ return false;
+ }
+ if (b != prefixByte) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+ReadIPv4AddressComponent(Reader& input, bool lastComponent,
+ /*out*/ uint8_t& valueOut)
+{
+ size_t length = 0;
+ unsigned int value = 0; // Must be larger than uint8_t.
+
+ for (;;) {
+ if (input.AtEnd() && lastComponent) {
+ break;
+ }
+
+ uint8_t b;
+ if (input.Read(b) != Success) {
+ return false;
+ }
+
+ if (b >= '0' && b <= '9') {
+ if (value == 0 && length > 0) {
+ return false; // Leading zeros are not allowed.
+ }
+ value = (value * 10) + (b - '0');
+ if (value > 255) {
+ return false; // Component's value is too large.
+ }
+ ++length;
+ } else if (!lastComponent && b == '.') {
+ break;
+ } else {
+ return false; // Invalid character.
+ }
+ }
+
+ if (length == 0) {
+ return false; // empty components not allowed
+ }
+
+ valueOut = static_cast<uint8_t>(value);
+ return true;
+}
+
+} // namespace
+
+// On Windows and maybe other platforms, OS-provided IP address parsing
+// functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
+// can't rely on them.
+bool
+ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4])
+{
+ Reader input(hostname);
+ return ReadIPv4AddressComponent(input, false, out[0]) &&
+ ReadIPv4AddressComponent(input, false, out[1]) &&
+ ReadIPv4AddressComponent(input, false, out[2]) &&
+ ReadIPv4AddressComponent(input, true, out[3]);
+}
+
+namespace {
+
+bool
+FinishIPv6Address(/*in/out*/ uint8_t (&address)[16], int numComponents,
+ int contractionIndex)
+{
+ assert(numComponents >= 0);
+ assert(numComponents <= 8);
+ assert(contractionIndex >= -1);
+ assert(contractionIndex <= 8);
+ assert(contractionIndex <= numComponents);
+ if (!(numComponents >= 0 &&
+ numComponents <= 8 &&
+ contractionIndex >= -1 &&
+ contractionIndex <= 8 &&
+ contractionIndex <= numComponents)) {
+ return false;
+ }
+
+ if (contractionIndex == -1) {
+ // no contraction
+ return numComponents == 8;
+ }
+
+ if (numComponents >= 8) {
+ return false; // no room left to expand the contraction.
+ }
+
+ // Shift components that occur after the contraction over.
+ std::copy_backward(address + (2u * static_cast<size_t>(contractionIndex)),
+ address + (2u * static_cast<size_t>(numComponents)),
+ address + (2u * 8u));
+ // Fill in the contracted area with zeros.
+ std::fill_n(address + 2u * static_cast<size_t>(contractionIndex),
+ (8u - static_cast<size_t>(numComponents)) * 2u, static_cast<uint8_t>(0u));
+
+ return true;
+}
+
+} // namespace
+
+// On Windows and maybe other platforms, OS-provided IP address parsing
+// functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
+// can't rely on them.
+bool
+ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16])
+{
+ Reader input(hostname);
+
+ int currentComponentIndex = 0;
+ int contractionIndex = -1;
+
+ if (input.Peek(':')) {
+ // A valid input can only start with ':' if there is a contraction at the
+ // beginning.
+ uint8_t b;
+ if (input.Read(b) != Success || b != ':') {
+ assert(false);
+ return false;
+ }
+ if (input.Read(b) != Success) {
+ return false;
+ }
+ if (b != ':') {
+ return false;
+ }
+ contractionIndex = 0;
+ }
+
+ for (;;) {
+ // If we encounter a '.' then we'll have to backtrack to parse the input
+ // from startOfComponent to the end of the input as an IPv4 address.
+ Reader::Mark startOfComponent(input.GetMark());
+ uint16_t componentValue = 0;
+ size_t componentLength = 0;
+ while (!input.AtEnd() && !input.Peek(':')) {
+ uint8_t value;
+ uint8_t b;
+ if (input.Read(b) != Success) {
+ assert(false);
+ return false;
+ }
+ switch (b) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ value = static_cast<uint8_t>(b - static_cast<uint8_t>('0'));
+ break;
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ value = static_cast<uint8_t>(b - static_cast<uint8_t>('a') +
+ UINT8_C(10));
+ break;
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ value = static_cast<uint8_t>(b - static_cast<uint8_t>('A') +
+ UINT8_C(10));
+ break;
+ case '.':
+ {
+ // A dot indicates we hit a IPv4-syntax component. Backtrack, parsing
+ // the input from startOfComponent to the end of the input as an IPv4
+ // address, and then combine it with the other components.
+
+ if (currentComponentIndex > 6) {
+ return false; // Too many components before the IPv4 component
+ }
+
+ input.SkipToEnd();
+ Input ipv4Component;
+ if (input.GetInput(startOfComponent, ipv4Component) != Success) {
+ return false;
+ }
+ uint8_t (*ipv4)[4] =
+ reinterpret_cast<uint8_t(*)[4]>(&out[2 * currentComponentIndex]);
+ if (!ParseIPv4Address(ipv4Component, *ipv4)) {
+ return false;
+ }
+ assert(input.AtEnd());
+ currentComponentIndex += 2;
+
+ return FinishIPv6Address(out, currentComponentIndex,
+ contractionIndex);
+ }
+ default:
+ return false;
+ }
+ if (componentLength >= 4) {
+ // component too long
+ return false;
+ }
+ ++componentLength;
+ componentValue = (componentValue * 0x10u) + value;
+ }
+
+ if (currentComponentIndex >= 8) {
+ return false; // too many components
+ }
+
+ if (componentLength == 0) {
+ if (input.AtEnd() && currentComponentIndex == contractionIndex) {
+ if (contractionIndex == 0) {
+ // don't accept "::"
+ return false;
+ }
+ return FinishIPv6Address(out, currentComponentIndex,
+ contractionIndex);
+ }
+ return false;
+ }
+
+ out[2 * currentComponentIndex] =
+ static_cast<uint8_t>(componentValue / 0x100);
+ out[(2 * currentComponentIndex) + 1] =
+ static_cast<uint8_t>(componentValue % 0x100);
+
+ ++currentComponentIndex;
+
+ if (input.AtEnd()) {
+ return FinishIPv6Address(out, currentComponentIndex,
+ contractionIndex);
+ }
+
+ uint8_t b;
+ if (input.Read(b) != Success || b != ':') {
+ assert(false);
+ return false;
+ }
+
+ if (input.Peek(':')) {
+ // Contraction
+ if (contractionIndex != -1) {
+ return false; // multiple contractions are not allowed.
+ }
+ if (input.Read(b) != Success || b != ':') {
+ assert(false);
+ return false;
+ }
+ contractionIndex = currentComponentIndex;
+ if (input.AtEnd()) {
+ // "::" at the end of the input.
+ return FinishIPv6Address(out, currentComponentIndex,
+ contractionIndex);
+ }
+ }
+ }
+}
+
+bool
+IsValidReferenceDNSID(Input hostname)
+{
+ return IsValidDNSID(hostname, IDRole::ReferenceID, AllowWildcards::No);
+}
+
+bool
+IsValidPresentedDNSID(Input hostname)
+{
+ return IsValidDNSID(hostname, IDRole::PresentedID, AllowWildcards::Yes);
+}
+
+namespace {
+
+// RFC 5280 Section 4.2.1.6 says that a dNSName "MUST be in the 'preferred name
+// syntax', as specified by Section 3.5 of [RFC1034] and as modified by Section
+// 2.1 of [RFC1123]" except "a dNSName of ' ' MUST NOT be used." Additionally,
+// we allow underscores for compatibility with existing practice.
+bool
+IsValidDNSID(Input hostname, IDRole idRole, AllowWildcards allowWildcards)
+{
+ if (hostname.GetLength() > 253) {
+ return false;
+ }
+
+ Reader input(hostname);
+
+ if (idRole == IDRole::NameConstraint && input.AtEnd()) {
+ return true;
+ }
+
+ size_t dotCount = 0;
+ size_t labelLength = 0;
+ bool labelIsAllNumeric = false;
+ bool labelEndsWithHyphen = false;
+
+ // Only presented IDs are allowed to have wildcard labels. And, like
+ // Chromium, be stricter than RFC 6125 requires by insisting that a
+ // wildcard label consist only of '*'.
+ bool isWildcard = allowWildcards == AllowWildcards::Yes && input.Peek('*');
+ bool isFirstByte = !isWildcard;
+ if (isWildcard) {
+ Result rv = input.Skip(1);
+ if (rv != Success) {
+ assert(false);
+ return false;
+ }
+
+ uint8_t b;
+ rv = input.Read(b);
+ if (rv != Success) {
+ return false;
+ }
+ if (b != '.') {
+ return false;
+ }
+ ++dotCount;
+ }
+
+ do {
+ static const size_t MAX_LABEL_LENGTH = 63;
+
+ uint8_t b;
+ if (input.Read(b) != Success) {
+ return false;
+ }
+ switch (b) {
+ case '-':
+ if (labelLength == 0) {
+ return false; // Labels must not start with a hyphen.
+ }
+ labelIsAllNumeric = false;
+ labelEndsWithHyphen = true;
+ ++labelLength;
+ if (labelLength > MAX_LABEL_LENGTH) {
+ return false;
+ }
+ break;
+
+ // We avoid isdigit because it is locale-sensitive. See
+ // http://pubs.opengroup.org/onlinepubs/009695399/functions/isdigit.html
+ case '0': case '5':
+ case '1': case '6':
+ case '2': case '7':
+ case '3': case '8':
+ case '4': case '9':
+ if (labelLength == 0) {
+ labelIsAllNumeric = true;
+ }
+ labelEndsWithHyphen = false;
+ ++labelLength;
+ if (labelLength > MAX_LABEL_LENGTH) {
+ return false;
+ }
+ break;
+
+ // We avoid using islower/isupper/tolower/toupper or similar things, to
+ // avoid any possibility of this code being locale-sensitive. See
+ // http://pubs.opengroup.org/onlinepubs/009695399/functions/isupper.html
+ case 'a': case 'A': case 'n': case 'N':
+ case 'b': case 'B': case 'o': case 'O':
+ case 'c': case 'C': case 'p': case 'P':
+ case 'd': case 'D': case 'q': case 'Q':
+ case 'e': case 'E': case 'r': case 'R':
+ case 'f': case 'F': case 's': case 'S':
+ case 'g': case 'G': case 't': case 'T':
+ case 'h': case 'H': case 'u': case 'U':
+ case 'i': case 'I': case 'v': case 'V':
+ case 'j': case 'J': case 'w': case 'W':
+ case 'k': case 'K': case 'x': case 'X':
+ case 'l': case 'L': case 'y': case 'Y':
+ case 'm': case 'M': case 'z': case 'Z':
+ // We allow underscores for compatibility with existing practices.
+ // See bug 1136616.
+ case '_':
+ labelIsAllNumeric = false;
+ labelEndsWithHyphen = false;
+ ++labelLength;
+ if (labelLength > MAX_LABEL_LENGTH) {
+ return false;
+ }
+ break;
+
+ case '.':
+ ++dotCount;
+ if (labelLength == 0 &&
+ (idRole != IDRole::NameConstraint || !isFirstByte)) {
+ return false;
+ }
+ if (labelEndsWithHyphen) {
+ return false; // Labels must not end with a hyphen.
+ }
+ labelLength = 0;
+ break;
+
+ default:
+ return false; // Invalid character.
+ }
+ isFirstByte = false;
+ } while (!input.AtEnd());
+
+ // Only reference IDs, not presented IDs or name constraints, may be
+ // absolute.
+ if (labelLength == 0 && idRole != IDRole::ReferenceID) {
+ return false;
+ }
+
+ if (labelEndsWithHyphen) {
+ return false; // Labels must not end with a hyphen.
+ }
+
+ if (labelIsAllNumeric) {
+ return false; // Last label must not be all numeric.
+ }
+
+ if (isWildcard) {
+ // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
+ size_t labelCount = (labelLength == 0) ? dotCount : (dotCount + 1);
+
+ // Like NSS, require at least two labels to follow the wildcard label.
+ //
+ // TODO(bug XXXXXXX): Allow the TrustDomain to control this on a
+ // per-eTLD+1 basis, similar to Chromium. Even then, it might be better to
+ // still enforce that there are at least two labels after the wildcard.
+ if (labelCount < 3) {
+ return false;
+ }
+ // XXX: RFC6125 says that we shouldn't accept wildcards within an IDN
+ // A-Label. The consequence of this is that we effectively discriminate
+ // against users of languages that cannot be encoded with ASCII.
+ if (StartsWithIDNALabel(hostname)) {
+ return false;
+ }
+
+ // TODO(bug XXXXXXX): Wildcards are not allowed for EV certificates.
+ // Provide an option to indicate whether wildcards should be matched, for
+ // the purpose of helping the application enforce this.
+ }
+
+ return true;
+}
+
+} // namespace
+
+} } // namespace mozilla::pkix
diff --git a/security/nss/lib/mozpkix/lib/pkixnss.cpp b/security/nss/lib/mozpkix/lib/pkixnss.cpp
new file mode 100644
index 000000000..9b293d5fd
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixnss.cpp
@@ -0,0 +1,236 @@
+/*- *- 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 "mozpkix/pkixnss.h"
+
+#include <limits>
+
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "pk11pub.h"
+#include "mozpkix/nss_scoped_ptrs.h"
+#include "mozpkix/pkix.h"
+#include "mozpkix/pkixutil.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);
+ ScopedCERTSubjectPublicKeyInfo
+ spki(SECKEY_DecodeDERSubjectPublicKeyInfo(&subjectPublicKeyInfoSECItem));
+ if (!spki) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ ScopedSECKEYPublicKey
+ 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." },
+ { "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED",
+ "An additional policy constraint failed when validating this "
+ "certificate." },
+ { "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT",
+ "The certificate is not trusted because it is self-signed." },
+ { "MOZILLA_PKIX_ERROR_MITM_DETECTED",
+ "Your connection is being intercepted by a TLS proxy. Uninstall it if "
+ "possible or configure your device to trust its root certificate." },
+ };
+ // 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
diff --git a/security/nss/lib/mozpkix/lib/pkixocsp.cpp b/security/nss/lib/mozpkix/lib/pkixocsp.cpp
new file mode 100644
index 000000000..a81154417
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixocsp.cpp
@@ -0,0 +1,1012 @@
+/* -*- 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 "mozpkix/pkix.h"
+#include "mozpkix/pkixcheck.h"
+#include "mozpkix/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& aTrustDomain, const CertID& aCertID, Time aTime,
+ uint16_t aMaxLifetimeInDays, /*optional out*/ Time* aThisUpdate,
+ /*optional out*/ Time* aValidThrough)
+ : trustDomain(aTrustDomain)
+ , certID(aCertID)
+ , time(aTime)
+ , maxLifetimeInDays(aMaxLifetimeInDays)
+ , certStatus(CertStatus::Unknown)
+ , thisUpdate(aThisUpdate)
+ , validThrough(aValidThrough)
+ , 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);
+// It is convention to name the function after the part of the data structure
+// we're parsing from the RFC (e.g. OCSPResponse, ResponseBytes).
+// But since we also have a C++ type called CertID, this function doesn't
+// follow the convention to prevent shadowing.
+static inline Result MatchCertID(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 nestedRv =
+ der::ExpectTagAndGetTLV(certsDER, der::SEQUENCE, cert);
+ if (nestedRv != Success) {
+ return nestedRv;
+ }
+ nestedRv = certs.Append(cert);
+ if (nestedRv != 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 MatchCertID(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
+MatchCertID(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 (const uint8_t hashAlgorithmByte : hashAlgorithm) {
+ *d++ = hashAlgorithmByte;
+ }
+
+ // 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
diff --git a/security/nss/lib/mozpkix/lib/pkixresult.cpp b/security/nss/lib/mozpkix/lib/pkixresult.cpp
new file mode 100644
index 000000000..871d9a0fe
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixresult.cpp
@@ -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 "mozpkix/Result.h"
+#include "mozpkix/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
diff --git a/security/nss/lib/mozpkix/lib/pkixtime.cpp b/security/nss/lib/mozpkix/lib/pkixtime.cpp
new file mode 100644
index 000000000..38e063804
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixtime.cpp
@@ -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 "mozpkix/Time.h"
+#include "mozpkix/pkixutil.h"
+
+#ifdef _WINDOWS
+#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 _WINDOWS
+ // "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
diff --git a/security/nss/lib/mozpkix/lib/pkixverify.cpp b/security/nss/lib/mozpkix/lib/pkixverify.cpp
new file mode 100644
index 000000000..8ceb2c184
--- /dev/null
+++ b/security/nss/lib/mozpkix/lib/pkixverify.cpp
@@ -0,0 +1,106 @@
+/* -*- 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 "mozpkix/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);
+ case der::PublicKeyAlgorithm::Uninitialized:
+ assert(false);
+ return Result::FATAL_ERROR_LIBRARY_FAILURE;
+ 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
diff --git a/security/nss/lib/mozpkix/mozpkix.gyp b/security/nss/lib/mozpkix/mozpkix.gyp
new file mode 100644
index 000000000..1c552ba5f
--- /dev/null
+++ b/security/nss/lib/mozpkix/mozpkix.gyp
@@ -0,0 +1,60 @@
+# 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/.
+{
+ 'includes': [
+ '../../coreconf/config.gypi'
+ ],
+ 'targets': [
+ {
+ 'target_name': 'mozpkix',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'sources': [
+ 'lib/pkixbuild.cpp',
+ 'lib/pkixcert.cpp',
+ 'lib/pkixcheck.cpp',
+ 'lib/pkixder.cpp',
+ 'lib/pkixnames.cpp',
+ 'lib/pkixnss.cpp',
+ 'lib/pkixocsp.cpp',
+ 'lib/pkixresult.cpp',
+ 'lib/pkixtime.cpp',
+ 'lib/pkixverify.cpp',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:nss_mozpkix_exports',
+ ],
+ 'conditions': [
+ [ 'mozpkix_only==0', {
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:nss_exports'
+ ],
+ }],
+ ],
+ },
+ {
+ 'target_name': 'mozpkix-testlib',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'sources': [
+ 'test-lib/pkixtestalg.cpp',
+ 'test-lib/pkixtestnss.cpp',
+ 'test-lib/pkixtestutil.cpp',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:nss_mozpkix_exports',
+ ],
+ 'conditions': [
+ [ 'mozpkix_only==0', {
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:nss_exports'
+ ],
+ }],
+ ],
+ },
+ ],
+ 'variables': {
+ 'module': 'nss',
+ }
+}
diff --git a/security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp b/security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp
new file mode 100644
index 000000000..304641e2f
--- /dev/null
+++ b/security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp
@@ -0,0 +1,211 @@
+/* -*- 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 "mozpkix/test/pkixtestutil.h"
+
+#include "mozpkix/pkixder.h"
+#include "mozpkix/nss_scoped_ptrs.h"
+
+// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10040 1.2.840.10040
+#define PREFIX_1_2_840_10040 0x2a, 0x86, 0x48, 0xce, 0x38
+
+// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045
+#define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d
+
+// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_113549 1.2.840.113549
+#define PREFIX_1_2_840_113549 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+enum class NULLParam { NO, YES };
+
+template <size_t SIZE>
+ByteString
+OID(const uint8_t (&rawValue)[SIZE])
+{
+ return TLV(der::OIDTag, ByteString(rawValue, SIZE));
+}
+
+template <size_t SIZE>
+ByteString
+SimpleAlgID(const uint8_t (&rawValue)[SIZE],
+ NULLParam nullParam = NULLParam::NO)
+{
+ ByteString sequenceValue(OID(rawValue));
+ if (nullParam == NULLParam::YES) {
+ sequenceValue.append(TLV(der::NULLTag, ByteString()));
+ }
+ return TLV(der::SEQUENCE, sequenceValue);
+}
+
+template <size_t SIZE>
+ByteString
+DERInteger(const uint8_t (&rawValue)[SIZE])
+{
+ ByteString value(rawValue, SIZE);
+ if (value[0] & 0x80u) {
+ // Prefix with a leading zero to disambiguate this from a negative value.
+ value.insert(value.begin(), 0x00);
+ }
+ return TLV(der::INTEGER, value);
+}
+
+// Generated with "openssl dsaparam -C -noout 2048" and reformatted.
+// openssl 1.0 or later must be used so that a 256-bit Q value is
+// generated.
+static const uint8_t DSS_P_RAW[] =
+{
+ 0xB3,0xCD,0x29,0x44,0xF0,0x25,0xA7,0x73,0xFC,0x86,0x70,0xA2,
+ 0x69,0x5A,0x97,0x3F,0xBD,0x1C,0x6F,0xAA,0x4A,0x40,0x42,0x8E,
+ 0xCF,0xAE,0x62,0x12,0xED,0xB4,0xFD,0x05,0xC2,0xAE,0xB1,0x8C,
+ 0xFC,0xBE,0x38,0x90,0xBB,0x7C,0xFF,0x16,0xF4,0xED,0xCE,0x72,
+ 0x12,0x93,0x83,0xF0,0xA4,0xA1,0x71,0xDC,0x4B,0xF0,0x4E,0x3A,
+ 0x2B,0xFA,0x17,0xB7,0xB3,0x2A,0xCC,0x2C,0xD3,0xC8,0x21,0x49,
+ 0x7A,0x83,0x71,0x8B,0x3D,0x62,0x96,0xDC,0xAD,0xA8,0x03,0xBE,
+ 0x1D,0x33,0x11,0xF3,0xEB,0xD8,0x1B,0x8D,0xDB,0x62,0x79,0x83,
+ 0xF8,0x67,0x4E,0x62,0x21,0x2C,0x81,0x59,0xE8,0x73,0xD7,0xAF,
+ 0xB9,0x63,0x60,0xEA,0xAE,0xEC,0x68,0x6A,0xB4,0xB0,0x65,0xBA,
+ 0xA3,0x4C,0x09,0x99,0x29,0x6A,0x2E,0x2B,0xFC,0x6D,0x51,0xCA,
+ 0x30,0xA2,0x2F,0x7A,0x65,0x76,0xA7,0x55,0x13,0x11,0xA0,0x02,
+ 0xA2,0x59,0x4B,0xCE,0xA7,0x05,0xF6,0x07,0x35,0x9B,0x41,0xD7,
+ 0x11,0x5A,0x18,0x57,0xA7,0x78,0x88,0xC3,0xA8,0xE3,0x39,0xF5,
+ 0x47,0x3D,0x2E,0x18,0x54,0xB0,0xF0,0xBF,0x65,0x3F,0x77,0xC7,
+ 0x11,0xB8,0x0D,0x52,0xAD,0xC8,0xE8,0x6D,0xF6,0x7E,0x88,0x65,
+ 0x84,0x2B,0xF7,0xEF,0x8E,0xB5,0x7C,0xBD,0x2E,0x0D,0xF3,0xC6,
+ 0xDD,0x0B,0xB4,0xF2,0x23,0x1F,0xDA,0x55,0x05,0xF5,0xDC,0x53,
+ 0xA6,0x83,0xDA,0x5C,0xEF,0x29,0x02,0x78,0x68,0xD0,0xA4,0x39,
+ 0x09,0x7F,0xFA,0x49,0x18,0xD0,0xB5,0x19,0x35,0x31,0x8E,0xDE,
+ 0x43,0x35,0xA3,0xB9,0x6D,0xC1,0x70,0xC6,0x0D,0x18,0x24,0xEB,
+ 0x1E,0x4D,0x52,0xB7,
+};
+
+static const uint8_t DSS_Q_RAW[] =
+{
+ 0x8D,0x6B,0x86,0x89,0x9C,0x8D,0x30,0x91,0xCC,0x6E,0x34,0xF1,
+ 0xE8,0x9C,0x8A,0x5C,0xD6,0xAB,0x01,0x1E,0xC4,0xDB,0xFD,0x07,
+ 0xEB,0x5F,0x4E,0xE8,0xFA,0xFC,0x98,0x2D,
+};
+
+static const uint8_t DSS_G_RAW[] =
+{
+ 0x0E,0x2C,0x34,0xB2,0xE1,0x66,0x49,0xB6,0x9A,0x7D,0x67,0x3E,
+ 0xEE,0x98,0x35,0x18,0x28,0x35,0xFC,0x05,0x36,0x3B,0x94,0xE6,
+ 0x1E,0x1C,0x5B,0x05,0x3E,0x86,0x1B,0xE3,0xED,0xD2,0xE1,0xF3,
+ 0xF7,0xF7,0x60,0x6D,0x7D,0xA1,0xAF,0x9A,0xD1,0xDF,0xA2,0x9C,
+ 0xFC,0xA2,0xEB,0x90,0x8B,0x1C,0x82,0x92,0x45,0x7B,0x30,0x2A,
+ 0xFD,0x7A,0xE6,0x68,0x8F,0xEC,0x89,0x3A,0x9A,0xAD,0xFE,0x25,
+ 0x5E,0x51,0xC5,0x29,0x45,0x7F,0xAC,0xDE,0xFC,0xB4,0x1B,0x3A,
+ 0xDA,0xC7,0x21,0x68,0x87,0x27,0x8D,0x7B,0xB2,0xBB,0x41,0x60,
+ 0x46,0x42,0x5B,0x6B,0xE8,0x80,0xD2,0xE4,0xA3,0x30,0x8F,0xD5,
+ 0x71,0x07,0x8A,0x7B,0x32,0x56,0x84,0x41,0x1C,0xDF,0x69,0xE9,
+ 0xFD,0xBA,0x48,0xE0,0x43,0xA0,0x38,0x92,0x12,0xF3,0x52,0xA5,
+ 0x40,0x87,0xCB,0x34,0xBB,0x3E,0x25,0x29,0x3C,0xC6,0xA5,0x17,
+ 0xFD,0x58,0x47,0x89,0xDB,0x9B,0xB9,0xCF,0xE9,0xA8,0xF2,0xEC,
+ 0x55,0x76,0xF5,0xF1,0x9C,0x6E,0x0A,0x3F,0x16,0x5F,0x49,0x31,
+ 0x31,0x1C,0x43,0xA2,0x83,0xDA,0xDD,0x7F,0x1C,0xEA,0x05,0x36,
+ 0x7B,0xED,0x09,0xFB,0x6F,0x8A,0x2B,0x55,0xB9,0xBC,0x4A,0x8C,
+ 0x28,0xC1,0x4D,0x13,0x6E,0x47,0xF4,0xAD,0x79,0x00,0xE9,0x5A,
+ 0xB6,0xC7,0x73,0x28,0xA9,0x89,0xAD,0xE8,0x6E,0xC6,0x54,0xA5,
+ 0x56,0x2D,0xAA,0x81,0x83,0x9E,0xC1,0x13,0x79,0xA4,0x12,0xE0,
+ 0x76,0x1F,0x25,0x43,0xB6,0xDE,0x56,0xF7,0x52,0xCC,0x07,0xB8,
+ 0x37,0xE2,0x8C,0xC5,0x56,0x8C,0xDD,0x63,0xF5,0xB6,0xA3,0x46,
+ 0x62,0xF6,0x35,0x76,
+};
+
+} // namespace
+
+TestSignatureAlgorithm::TestSignatureAlgorithm(
+ const TestPublicKeyAlgorithm& aPublicKeyAlg,
+ TestDigestAlgorithmID aDigestAlg,
+ const ByteString& aAlgorithmIdentifier,
+ bool aAccepted)
+ : publicKeyAlg(aPublicKeyAlg)
+ , digestAlg(aDigestAlg)
+ , algorithmIdentifier(aAlgorithmIdentifier)
+ , accepted(aAccepted)
+{
+}
+
+ByteString DSS_P() { return ByteString(DSS_P_RAW, sizeof(DSS_P_RAW)); }
+ByteString DSS_Q() { return ByteString(DSS_Q_RAW, sizeof(DSS_Q_RAW)); }
+ByteString DSS_G() { return ByteString(DSS_G_RAW, sizeof(DSS_G_RAW)); }
+
+TestPublicKeyAlgorithm
+DSS()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_10040, 4, 1 };
+
+ // RFC 3279 Section-2.3.2
+ return TestPublicKeyAlgorithm(
+ TLV(der::SEQUENCE,
+ OID(oidValue) +
+ TLV(der::SEQUENCE,
+ DERInteger(DSS_P_RAW) +
+ DERInteger(DSS_Q_RAW) +
+ DERInteger(DSS_G_RAW))));
+}
+
+// RFC 3279 Section 2.3.1
+TestPublicKeyAlgorithm
+RSA_PKCS1()
+{
+ static const uint8_t rsaEncryption[] = { PREFIX_1_2_840_113549, 1, 1, 1 };
+ return TestPublicKeyAlgorithm(SimpleAlgID(rsaEncryption, NULLParam::YES));
+}
+
+// RFC 3279 Section 2.2.1
+TestSignatureAlgorithm md2WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 2 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD2,
+ SimpleAlgID(oidValue), false);
+}
+
+// RFC 3279 Section 2.2.1
+TestSignatureAlgorithm md5WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 4 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD5,
+ SimpleAlgID(oidValue), false);
+}
+
+// RFC 3279 Section 2.2.1
+TestSignatureAlgorithm sha1WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 5 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA1,
+ SimpleAlgID(oidValue), true);
+}
+
+// RFC 4055 Section 5
+TestSignatureAlgorithm sha256WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 11 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA256,
+ SimpleAlgID(oidValue), true);
+}
+
+} } } // namespace mozilla::pkix
diff --git a/security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp b/security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp
new file mode 100644
index 000000000..ee59b1d97
--- /dev/null
+++ b/security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp
@@ -0,0 +1,364 @@
+/* -*- 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 "mozpkix/test/pkixtestutil.h"
+#include "mozpkix/test/pkixtestnss.h"
+
+#include <limits>
+
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "nss.h"
+#include "pk11pqg.h"
+#include "pk11pub.h"
+#include "mozpkix/nss_scoped_ptrs.h"
+#include "mozpkix/pkixnss.h"
+#include "mozpkix/pkixder.h"
+#include "mozpkix/pkixutil.h"
+#include "prinit.h"
+#include "secerr.h"
+#include "secitem.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+TestKeyPair* GenerateKeyPairInner();
+
+void
+InitNSSIfNeeded()
+{
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+}
+
+static ScopedTestKeyPair reusedKeyPair;
+
+PRStatus
+InitReusedKeyPair()
+{
+ InitNSSIfNeeded();
+ reusedKeyPair.reset(GenerateKeyPairInner());
+ return reusedKeyPair ? PR_SUCCESS : PR_FAILURE;
+}
+
+class NSSTestKeyPair final : public TestKeyPair
+{
+public:
+ NSSTestKeyPair(const TestPublicKeyAlgorithm& aPublicKeyAlg,
+ const ByteString& spk,
+ const ByteString& aEncryptedPrivateKey,
+ const ByteString& aEncryptionAlgorithm,
+ const ByteString& aEncryptionParams)
+ : TestKeyPair(aPublicKeyAlg, spk)
+ , encryptedPrivateKey(aEncryptedPrivateKey)
+ , encryptionAlgorithm(aEncryptionAlgorithm)
+ , encryptionParams(aEncryptionParams)
+ {
+ }
+
+ Result SignData(const ByteString& tbs,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*out*/ ByteString& signature) const override
+ {
+ SECOidTag oidTag;
+ if (signatureAlgorithm.publicKeyAlg == RSA_PKCS1()) {
+ switch (signatureAlgorithm.digestAlg) {
+ case TestDigestAlgorithmID::MD2:
+ oidTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::MD5:
+ oidTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA1:
+ oidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA224:
+ oidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA256:
+ oidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA384:
+ oidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA512:
+ oidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION;
+ break;
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+ } else {
+ abort();
+ }
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ SECItem encryptedPrivateKeyInfoItem = {
+ siBuffer,
+ const_cast<uint8_t*>(encryptedPrivateKey.data()),
+ static_cast<unsigned int>(encryptedPrivateKey.length())
+ };
+ SECItem encryptionAlgorithmItem = {
+ siBuffer,
+ const_cast<uint8_t*>(encryptionAlgorithm.data()),
+ static_cast<unsigned int>(encryptionAlgorithm.length())
+ };
+ SECItem encryptionParamsItem = {
+ siBuffer,
+ const_cast<uint8_t*>(encryptionParams.data()),
+ static_cast<unsigned int>(encryptionParams.length())
+ };
+ SECKEYEncryptedPrivateKeyInfo encryptedPrivateKeyInfo = {
+ nullptr,
+ { encryptionAlgorithmItem, encryptionParamsItem },
+ encryptedPrivateKeyInfoItem
+ };
+ SECItem passwordItem = { siBuffer, nullptr, 0 };
+ SECItem publicValueItem = {
+ siBuffer,
+ const_cast<uint8_t*>(subjectPublicKey.data()),
+ static_cast<unsigned int>(subjectPublicKey.length())
+ };
+ SECKEYPrivateKey* privateKey;
+ // This should always be an RSA key (we'll have aborted above if we're not
+ // doing an RSA signature).
+ if (PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(
+ slot.get(), &encryptedPrivateKeyInfo, &passwordItem, nullptr,
+ &publicValueItem, false, false, rsaKey, KU_ALL, &privateKey,
+ nullptr) != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ ScopedSECKEYPrivateKey scopedPrivateKey(privateKey);
+ SECItem signatureItem;
+ if (SEC_SignData(&signatureItem, tbs.data(),
+ static_cast<int>(tbs.length()),
+ scopedPrivateKey.get(), oidTag) != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ signature.assign(signatureItem.data, signatureItem.len);
+ SECITEM_FreeItem(&signatureItem, false);
+ return Success;
+ }
+
+ TestKeyPair* Clone() const override
+ {
+ return new (std::nothrow) NSSTestKeyPair(publicKeyAlg,
+ subjectPublicKey,
+ encryptedPrivateKey,
+ encryptionAlgorithm,
+ encryptionParams);
+ }
+
+private:
+ const ByteString encryptedPrivateKey;
+ const ByteString encryptionAlgorithm;
+ const ByteString encryptionParams;
+};
+
+} // namespace
+
+// This private function is also used by Gecko's PSM test framework
+// (OCSPCommon.cpp).
+TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
+ const ScopedSECKEYPublicKey& publicKey,
+ const ScopedSECKEYPrivateKey& privateKey)
+{
+ ScopedCERTSubjectPublicKeyInfo
+ spki(SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
+ if (!spki) {
+ return nullptr;
+ }
+ SECItem spkDER = spki->subjectPublicKey;
+ DER_ConvertBitString(&spkDER); // bits to bytes
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+ // Because NSSTestKeyPair isn't tracked by XPCOM and won't otherwise be aware
+ // of shutdown, we don't have a way to release NSS resources at the
+ // appropriate time. To work around this, NSSTestKeyPair doesn't hold on to
+ // NSS resources. Instead, we export the generated private key part as an
+ // encrypted blob (with an empty password and fairly lame encryption). When we
+ // need to use it (e.g. to sign something), we decrypt it and create a
+ // temporary key object.
+ SECItem passwordItem = { siBuffer, nullptr, 0 };
+ ScopedSECKEYEncryptedPrivateKeyInfo encryptedPrivateKey(
+ PK11_ExportEncryptedPrivKeyInfo(
+ slot.get(), SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC,
+ &passwordItem, privateKey.get(), 1, nullptr));
+ if (!encryptedPrivateKey) {
+ return nullptr;
+ }
+
+ return new (std::nothrow) NSSTestKeyPair(
+ publicKeyAlg,
+ ByteString(spkDER.data, spkDER.len),
+ ByteString(encryptedPrivateKey->encryptedData.data,
+ encryptedPrivateKey->encryptedData.len),
+ ByteString(encryptedPrivateKey->algorithm.algorithm.data,
+ encryptedPrivateKey->algorithm.algorithm.len),
+ ByteString(encryptedPrivateKey->algorithm.parameters.data,
+ encryptedPrivateKey->algorithm.parameters.len));
+}
+
+namespace {
+
+TestKeyPair*
+GenerateKeyPairInner()
+{
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ abort();
+ }
+
+ // Bug 1012786: PK11_GenerateKeyPair can fail if there is insufficient
+ // entropy to generate a random key. Attempting to add some entropy and
+ // retrying appears to solve this issue.
+ for (uint32_t retries = 0; retries < 10; retries++) {
+ PK11RSAGenParams params;
+ params.keySizeInBits = 2048;
+ params.pe = 3;
+ SECKEYPublicKey* publicKeyTemp = nullptr;
+ ScopedSECKEYPrivateKey
+ privateKey(PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN,
+ &params, &publicKeyTemp, false, true,
+ nullptr));
+ ScopedSECKEYPublicKey publicKey(publicKeyTemp);
+ if (privateKey) {
+ return CreateTestKeyPair(RSA_PKCS1(), publicKey, privateKey);
+ }
+
+ assert(!publicKeyTemp);
+
+ if (PR_GetError() != SEC_ERROR_PKCS11_FUNCTION_FAILED) {
+ break;
+ }
+
+ // Since these keys are only for testing, we don't need them to be good,
+ // random keys.
+ // https://xkcd.com/221/
+ static const uint8_t RANDOM_NUMBER[] = { 4, 4, 4, 4, 4, 4, 4, 4 };
+ if (PK11_RandomUpdate((void*) &RANDOM_NUMBER,
+ sizeof(RANDOM_NUMBER)) != SECSuccess) {
+ break;
+ }
+ }
+
+ abort();
+}
+
+} // namespace
+
+TestKeyPair*
+GenerateKeyPair()
+{
+ InitNSSIfNeeded();
+ return GenerateKeyPairInner();
+}
+
+TestKeyPair*
+CloneReusedKeyPair()
+{
+ static PRCallOnceType initCallOnce;
+ if (PR_CallOnce(&initCallOnce, InitReusedKeyPair) != PR_SUCCESS) {
+ abort();
+ }
+ assert(reusedKeyPair);
+ return reusedKeyPair->Clone();
+}
+
+TestKeyPair*
+GenerateDSSKeyPair()
+{
+ InitNSSIfNeeded();
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ ByteString p(DSS_P());
+ ByteString q(DSS_Q());
+ ByteString g(DSS_G());
+
+ static const PQGParams PARAMS = {
+ nullptr,
+ { siBuffer,
+ const_cast<uint8_t*>(p.data()),
+ static_cast<unsigned int>(p.length())
+ },
+ { siBuffer,
+ const_cast<uint8_t*>(q.data()),
+ static_cast<unsigned int>(q.length())
+ },
+ { siBuffer,
+ const_cast<uint8_t*>(g.data()),
+ static_cast<unsigned int>(g.length())
+ }
+ };
+
+ SECKEYPublicKey* publicKeyTemp = nullptr;
+ ScopedSECKEYPrivateKey
+ privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN,
+ const_cast<PQGParams*>(&PARAMS),
+ &publicKeyTemp, false, true, nullptr));
+ if (!privateKey) {
+ return nullptr;
+ }
+ ScopedSECKEYPublicKey publicKey(publicKeyTemp);
+ return CreateTestKeyPair(DSS(), publicKey, privateKey);
+}
+
+Result
+TestVerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo)
+{
+ InitNSSIfNeeded();
+ return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
+ nullptr);
+}
+
+Result
+TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo)
+{
+ InitNSSIfNeeded();
+ return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
+ nullptr);
+}
+
+Result
+TestDigestBuf(Input item,
+ DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf,
+ size_t digestBufLen)
+{
+ InitNSSIfNeeded();
+ return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
+}
+
+} } } // namespace mozilla::pkix::test
diff --git a/security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp b/security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp
new file mode 100644
index 000000000..b1b89c07e
--- /dev/null
+++ b/security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp
@@ -0,0 +1,1155 @@
+/* -*- 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 "mozpkix/test/pkixtestutil.h"
+
+#include <cerrno>
+#include <cstdio>
+#include <limits>
+#include <new>
+#include <sstream>
+#include <cstdlib>
+
+#include "mozpkix/pkixder.h"
+#include "mozpkix/pkixutil.h"
+
+using namespace std;
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+struct ScopedMaybeDeleteFile {
+ void operator()(FILE* f) {
+ if (f) {
+ (void)fclose(f);
+ }
+ }
+};
+typedef std::unique_ptr<FILE, ScopedMaybeDeleteFile> ScopedFILE;
+
+FILE*
+OpenFile(const string& dir, const string& filename, const string& mode)
+{
+ string path = dir + '/' + filename;
+
+ ScopedFILE file;
+#ifdef _MSC_VER
+ {
+ FILE* rawFile;
+ errno_t error = fopen_s(&rawFile, path.c_str(), mode.c_str());
+ if (error) {
+ // TODO: map error to NSPR error code
+ rawFile = nullptr;
+ }
+ file.reset(rawFile);
+ }
+#else
+ file.reset(fopen(path.c_str(), mode.c_str()));
+#endif
+ return file.release();
+}
+
+} // namespace
+
+bool
+InputEqualsByteString(Input input, const ByteString& bs)
+{
+ Input bsInput;
+ if (bsInput.Init(bs.data(), bs.length()) != Success) {
+ // Init can only fail if it is given a bad pointer or if the input is too
+ // long, which won't ever happen. Plus, if it does, it is ok to call abort
+ // since this is only test code.
+ abort();
+ }
+ return InputsAreEqual(input, bsInput);
+}
+
+ByteString
+InputToByteString(Input input)
+{
+ ByteString result;
+ Reader reader(input);
+ for (;;) {
+ uint8_t b;
+ if (reader.Read(b) != Success) {
+ return result;
+ }
+ result.push_back(b);
+ }
+}
+
+Result
+TamperOnce(/*in/out*/ ByteString& item, const ByteString& from,
+ const ByteString& to)
+{
+ if (from.length() < 8) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ if (from.length() != to.length()) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ size_t pos = item.find(from);
+ if (pos == string::npos) {
+ return Result::FATAL_ERROR_INVALID_ARGS; // No matches.
+ }
+ if (item.find(from, pos + from.length()) != string::npos) {
+ return Result::FATAL_ERROR_INVALID_ARGS; // More than once match.
+ }
+ item.replace(pos, from.length(), to);
+ return Success;
+}
+
+// Given a tag and a value, generates a DER-encoded tag-length-value item.
+ByteString
+TLV(uint8_t tag, size_t length, const ByteString& value)
+{
+ ByteString result;
+ result.push_back(tag);
+
+ if (value.length() < 128) {
+ result.push_back(static_cast<uint8_t>(length));
+ } else if (value.length() < 256) {
+ result.push_back(0x81u);
+ result.push_back(static_cast<uint8_t>(length));
+ } else if (value.length() < 65536) {
+ result.push_back(0x82u);
+ result.push_back(static_cast<uint8_t>(length / 256));
+ result.push_back(static_cast<uint8_t>(length % 256));
+ } else {
+ // It is MUCH more convenient for TLV to be infallible than for it to have
+ // "proper" error handling.
+ abort();
+ }
+ result.append(value);
+ return result;
+}
+
+OCSPResponseExtension::OCSPResponseExtension()
+ : id()
+ , critical(false)
+ , value()
+ , next(nullptr)
+{
+}
+
+OCSPResponseContext::OCSPResponseContext(const CertID& aCertID, time_t time)
+ : certID(aCertID)
+ , responseStatus(successful)
+ , skipResponseBytes(false)
+ , producedAt(time)
+ , singleExtensions(nullptr)
+ , responseExtensions(nullptr)
+ , includeEmptyExtensions(false)
+ , signatureAlgorithm(sha256WithRSAEncryption())
+ , badSignature(false)
+ , certs(nullptr)
+
+ , certStatus(good)
+ , revocationTime(0)
+ , thisUpdate(time)
+ , nextUpdate(time + static_cast<time_t>(Time::ONE_DAY_IN_SECONDS))
+ , includeNextUpdate(true)
+{
+}
+
+static ByteString ResponseBytes(OCSPResponseContext& context);
+static ByteString BasicOCSPResponse(OCSPResponseContext& context);
+static ByteString ResponseData(OCSPResponseContext& context);
+static ByteString ResponderID(OCSPResponseContext& context);
+static ByteString KeyHash(const ByteString& subjectPublicKeyInfo);
+static ByteString SingleResponse(OCSPResponseContext& context);
+static ByteString CertID(OCSPResponseContext& context);
+static ByteString CertStatus(OCSPResponseContext& context);
+
+static ByteString
+SHA1(const ByteString& toHash)
+{
+ uint8_t digestBuf[20];
+ Input input;
+ if (input.Init(toHash.data(), toHash.length()) != Success) {
+ abort();
+ }
+ Result rv = TestDigestBuf(input, DigestAlgorithm::sha1, digestBuf,
+ sizeof(digestBuf));
+ if (rv != Success) {
+ abort();
+ }
+ return ByteString(digestBuf, sizeof(digestBuf));
+}
+
+static ByteString
+HashedOctetString(const ByteString& bytes)
+{
+ ByteString digest(SHA1(bytes));
+ if (ENCODING_FAILED(digest)) {
+ return ByteString();
+ }
+ return TLV(der::OCTET_STRING, digest);
+}
+
+static ByteString
+BitString(const ByteString& rawBytes, bool corrupt)
+{
+ ByteString prefixed;
+ // We have to add a byte at the beginning indicating no unused bits.
+ // TODO: add ability to have bit strings of bit length not divisible by 8,
+ // resulting in unused bits in the bitstring encoding
+ prefixed.push_back(0);
+ prefixed.append(rawBytes);
+ if (corrupt) {
+ assert(prefixed.length() > 8);
+ prefixed[8]++;
+ }
+ return TLV(der::BIT_STRING, prefixed);
+}
+
+ByteString
+Boolean(bool value)
+{
+ ByteString encodedValue;
+ encodedValue.push_back(value ? 0xffu : 0x00u);
+ return TLV(der::BOOLEAN, encodedValue);
+}
+
+ByteString
+Integer(long value)
+{
+ if (value < 0 || value > 127) {
+ // TODO: add encoding of larger values
+ // It is MUCH more convenient for Integer to be infallible than for it to
+ // have "proper" error handling.
+ abort();
+ }
+
+ ByteString encodedValue;
+ encodedValue.push_back(static_cast<uint8_t>(value));
+ return TLV(der::INTEGER, encodedValue);
+}
+
+enum TimeEncoding { UTCTime = 0, GeneralizedTime = 1 };
+
+// Windows doesn't provide gmtime_r, but it provides something very similar.
+#if defined(_WINDOWS) && (!defined(_POSIX_C_SOURCE) || !defined(_POSIX_THREAD_SAFE_FUNCTIONS))
+static tm*
+gmtime_r(const time_t* t, /*out*/ tm* exploded)
+{
+ if (gmtime_s(exploded, t) != 0) {
+ return nullptr;
+ }
+ return exploded;
+}
+#endif
+
+// http://tools.ietf.org/html/rfc5280#section-4.1.2.5
+// UTCTime: YYMMDDHHMMSSZ (years 1950-2049 only)
+// GeneralizedTime: YYYYMMDDHHMMSSZ
+//
+// This assumes that time/time_t are POSIX-compliant in that time() returns
+// the number of seconds since the Unix epoch.
+static ByteString
+TimeToEncodedTime(time_t time, TimeEncoding encoding)
+{
+ assert(encoding == UTCTime || encoding == GeneralizedTime);
+
+ tm exploded;
+ if (!gmtime_r(&time, &exploded)) {
+ return ByteString();
+ }
+
+ if (exploded.tm_sec >= 60) {
+ // round down for leap seconds
+ exploded.tm_sec = 59;
+ }
+
+ // exploded.tm_year is the year offset by 1900.
+ int year = exploded.tm_year + 1900;
+
+ if (encoding == UTCTime && (year < 1950 || year >= 2050)) {
+ return ByteString();
+ }
+
+ ByteString value;
+
+ if (encoding == GeneralizedTime) {
+ value.push_back(static_cast<uint8_t>('0' + (year / 1000)));
+ value.push_back(static_cast<uint8_t>('0' + ((year % 1000) / 100)));
+ }
+
+ value.push_back(static_cast<uint8_t>('0' + ((year % 100) / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (year % 10)));
+ value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) / 10)));
+ value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec % 10)));
+ value.push_back('Z');
+
+ return TLV(encoding == GeneralizedTime ? der::GENERALIZED_TIME : der::UTCTime,
+ value);
+}
+
+static ByteString
+TimeToGeneralizedTime(time_t time)
+{
+ return TimeToEncodedTime(time, GeneralizedTime);
+}
+
+// http://tools.ietf.org/html/rfc5280#section-4.1.2.5: "CAs conforming to this
+// profile MUST always encode certificate validity dates through the year 2049
+// as UTCTime; certificate validity dates in 2050 or later MUST be encoded as
+// GeneralizedTime." (This is a special case of the rule that we must always
+// use the shortest possible encoding.)
+static ByteString
+TimeToTimeChoice(time_t time)
+{
+ tm exploded;
+ if (!gmtime_r(&time, &exploded)) {
+ return ByteString();
+ }
+ TimeEncoding encoding = (exploded.tm_year + 1900 >= 1950 &&
+ exploded.tm_year + 1900 < 2050)
+ ? UTCTime
+ : GeneralizedTime;
+
+ return TimeToEncodedTime(time, encoding);
+}
+
+Time
+YMDHMS(uint16_t year, uint16_t month, uint16_t day,
+ uint16_t hour, uint16_t minutes, uint16_t seconds)
+{
+ assert(year <= 9999);
+ assert(month >= 1);
+ assert(month <= 12);
+ assert(day >= 1);
+ assert(hour < 24);
+ assert(minutes < 60);
+ assert(seconds < 60);
+
+ uint64_t days = DaysBeforeYear(year);
+
+ {
+ static const int16_t DAYS_IN_MONTH[] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+
+ int16_t i = 1;
+ for (;;) {
+ int16_t daysInMonth = DAYS_IN_MONTH[i - 1];
+ if (i == 2 &&
+ ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))) {
+ // Add leap day
+ ++daysInMonth;
+ }
+ if (i == month) {
+ assert(day <= daysInMonth);
+ break;
+ }
+ days += daysInMonth;
+ ++i;
+ }
+ }
+
+ days += (day - 1);
+
+ uint64_t totalSeconds = days * Time::ONE_DAY_IN_SECONDS;
+ totalSeconds += hour * 60 * 60;
+ totalSeconds += minutes * 60;
+ totalSeconds += seconds;
+ return TimeFromElapsedSecondsAD(totalSeconds);
+}
+
+static ByteString
+SignedData(const ByteString& tbsData,
+ const TestKeyPair& keyPair,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ bool corrupt, /*optional*/ const ByteString* certs)
+{
+ ByteString signature;
+ if (keyPair.SignData(tbsData, signatureAlgorithm, signature) != Success) {
+ return ByteString();
+ }
+
+ // TODO: add ability to have signatures of bit length not divisible by 8,
+ // resulting in unused bits in the bitstring encoding
+ ByteString signatureNested(BitString(signature, corrupt));
+ if (ENCODING_FAILED(signatureNested)) {
+ return ByteString();
+ }
+
+ ByteString certsNested;
+ if (certs) {
+ ByteString certsSequenceValue;
+ while (!(*certs).empty()) {
+ certsSequenceValue.append(*certs);
+ ++certs;
+ }
+ ByteString certsSequence(TLV(der::SEQUENCE, certsSequenceValue));
+ certsNested = TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ certsSequence);
+ }
+
+ ByteString value;
+ value.append(tbsData);
+ value.append(signatureAlgorithm.algorithmIdentifier);
+ value.append(signatureNested);
+ value.append(certsNested);
+ return TLV(der::SEQUENCE, value);
+}
+
+// Extension ::= SEQUENCE {
+// extnID OBJECT IDENTIFIER,
+// critical BOOLEAN DEFAULT FALSE,
+// extnValue OCTET STRING
+// -- contains the DER encoding of an ASN.1 value
+// -- corresponding to the extension type identified
+// -- by extnID
+// }
+static ByteString
+Extension(Input extnID, Critical critical, const ByteString& extnValueBytes)
+{
+ ByteString encoded;
+
+ encoded.append(ByteString(extnID.UnsafeGetData(), extnID.GetLength()));
+
+ if (critical == Critical::Yes) {
+ encoded.append(Boolean(true));
+ }
+
+ ByteString extnValueSequence(TLV(der::SEQUENCE, extnValueBytes));
+ ByteString extnValue(TLV(der::OCTET_STRING, extnValueSequence));
+ encoded.append(extnValue);
+ return TLV(der::SEQUENCE, encoded);
+}
+
+static ByteString
+EmptyExtension(Input extnID, Critical critical)
+{
+ ByteString encoded(extnID.UnsafeGetData(), extnID.GetLength());
+
+ if (critical == Critical::Yes) {
+ encoded.append(Boolean(true));
+ }
+
+ ByteString extnValue(TLV(der::OCTET_STRING, ByteString()));
+ encoded.append(extnValue);
+ return TLV(der::SEQUENCE, encoded);
+}
+
+std::string
+GetEnv(const char* name)
+{
+ std::string result;
+
+#ifndef _MSC_VER
+ // XXX: Not thread safe.
+ const char* value = getenv(name);
+ if (value) {
+ result = value;
+ }
+#else
+ char* value = nullptr;
+ size_t valueLength = 0;
+ if (_dupenv_s(&value, &valueLength, name) != 0) {
+ abort();
+ }
+ if (value) {
+ result = value;
+ free(value);
+ }
+#endif
+ return result;
+}
+
+void
+MaybeLogOutput(const ByteString& result, const char* suffix)
+{
+ assert(suffix);
+
+ // This allows us to more easily debug the generated output, by creating a
+ // file in the directory given by MOZILLA_PKIX_TEST_LOG_DIR for each
+ // NOT THREAD-SAFE!!!
+ std::string logPath(GetEnv("MOZILLA_PKIX_TEST_LOG_DIR"));
+ if (!logPath.empty()) {
+ static int counter = 0;
+
+ std::ostringstream counterStream;
+ counterStream << counter;
+ if (!counterStream) {
+ assert(false);
+ return;
+ }
+ string filename = counterStream.str() + '-' + suffix + ".der";
+
+ ++counter;
+ ScopedFILE file(OpenFile(logPath, filename, "wb"));
+ if (file) {
+ (void) fwrite(result.data(), result.length(), 1, file.get());
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Certificates
+
+static ByteString TBSCertificate(long version, const ByteString& serialNumber,
+ const ByteString& signature,
+ const ByteString& issuer,
+ time_t notBefore, time_t notAfter,
+ const ByteString& subject,
+ const ByteString& subjectPublicKeyInfo,
+ /*optional*/ const ByteString* extensions);
+
+// Certificate ::= SEQUENCE {
+// tbsCertificate TBSCertificate,
+// signatureAlgorithm AlgorithmIdentifier,
+// signatureValue BIT STRING }
+ByteString
+CreateEncodedCertificate(long version,
+ const TestSignatureAlgorithm& signature,
+ const ByteString& serialNumber,
+ const ByteString& issuerNameDER,
+ time_t notBefore, time_t notAfter,
+ const ByteString& subjectNameDER,
+ const TestKeyPair& subjectKeyPair,
+ /*optional*/ const ByteString* extensions,
+ const TestKeyPair& issuerKeyPair,
+ const TestSignatureAlgorithm& signatureAlgorithm)
+{
+ ByteString tbsCertificate(TBSCertificate(version, serialNumber,
+ signature.algorithmIdentifier,
+ issuerNameDER, notBefore,
+ notAfter, subjectNameDER,
+ subjectKeyPair.subjectPublicKeyInfo,
+ extensions));
+ if (ENCODING_FAILED(tbsCertificate)) {
+ return ByteString();
+ }
+
+ ByteString result(SignedData(tbsCertificate, issuerKeyPair,
+ signatureAlgorithm, false, nullptr));
+ if (ENCODING_FAILED(result)) {
+ return ByteString();
+ }
+
+ MaybeLogOutput(result, "cert");
+
+ return result;
+}
+
+// TBSCertificate ::= SEQUENCE {
+// version [0] 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] Extensions OPTIONAL
+// -- If present, version MUST be v3 -- }
+static ByteString
+TBSCertificate(long versionValue,
+ const ByteString& serialNumber, const ByteString& signature,
+ const ByteString& issuer, time_t notBeforeTime,
+ time_t notAfterTime, const ByteString& subject,
+ const ByteString& subjectPublicKeyInfo,
+ /*optional*/ const ByteString* extensions)
+{
+ ByteString value;
+
+ if (versionValue != static_cast<long>(der::Version::v1)) {
+ ByteString versionInteger(Integer(versionValue));
+ ByteString version(TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ versionInteger));
+ value.append(version);
+ }
+
+ value.append(serialNumber);
+ value.append(signature);
+ value.append(issuer);
+
+ // Validity ::= SEQUENCE {
+ // notBefore Time,
+ // notAfter Time }
+ ByteString validity;
+ {
+ ByteString notBefore(TimeToTimeChoice(notBeforeTime));
+ if (ENCODING_FAILED(notBefore)) {
+ return ByteString();
+ }
+ ByteString notAfter(TimeToTimeChoice(notAfterTime));
+ if (ENCODING_FAILED(notAfter)) {
+ return ByteString();
+ }
+ ByteString validityValue;
+ validityValue.append(notBefore);
+ validityValue.append(notAfter);
+ validity = TLV(der::SEQUENCE, validityValue);
+ if (ENCODING_FAILED(validity)) {
+ return ByteString();
+ }
+ }
+ value.append(validity);
+
+ value.append(subject);
+
+ value.append(subjectPublicKeyInfo);
+
+ if (extensions) {
+ ByteString extensionsValue;
+ while (!(*extensions).empty()) {
+ extensionsValue.append(*extensions);
+ ++extensions;
+ }
+ ByteString extensionsSequence(TLV(der::SEQUENCE, extensionsValue));
+ if (ENCODING_FAILED(extensionsSequence)) {
+ return ByteString();
+ }
+ ByteString extensionsWrapped(
+ TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3, extensionsSequence));
+ if (ENCODING_FAILED(extensionsWrapped)) {
+ return ByteString();
+ }
+ value.append(extensionsWrapped);
+ }
+
+ return TLV(der::SEQUENCE, value);
+}
+
+// AttributeTypeAndValue ::= SEQUENCE {
+// type AttributeType,
+// value AttributeValue }
+//
+// AttributeType ::= OBJECT IDENTIFIER
+//
+// AttributeValue ::= ANY -- DEFINED BY AttributeType
+//
+// DirectoryString ::= CHOICE {
+// teletexString TeletexString (SIZE (1..MAX)),
+// printableString PrintableString (SIZE (1..MAX)),
+// universalString UniversalString (SIZE (1..MAX)),
+// utf8String UTF8String (SIZE (1..MAX)),
+// bmpString BMPString (SIZE (1..MAX)) }
+template <size_t N>
+static ByteString
+AVA(const uint8_t (&type)[N], uint8_t directoryStringType,
+ const ByteString& value)
+{
+ ByteString wrappedValue(TLV(directoryStringType, value));
+ ByteString ava;
+ ava.append(type, N);
+ ava.append(wrappedValue);
+ return TLV(der::SEQUENCE, ava);
+}
+
+ByteString
+CN(const ByteString& value, uint8_t encodingTag)
+{
+ // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
+ // id-at-commonName AttributeType ::= { id-at 3 }
+ // python DottedOIDToCode.py --tlv id-at-commonName 2.5.4.3
+ static const uint8_t tlv_id_at_commonName[] = {
+ 0x06, 0x03, 0x55, 0x04, 0x03
+ };
+ return AVA(tlv_id_at_commonName, encodingTag, value);
+}
+
+ByteString
+OU(const ByteString& value, uint8_t encodingTag)
+{
+ // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
+ // id-at-organizationalUnitName AttributeType ::= { id-at 11 }
+ // python DottedOIDToCode.py --tlv id-at-organizationalUnitName 2.5.4.11
+ static const uint8_t tlv_id_at_organizationalUnitName[] = {
+ 0x06, 0x03, 0x55, 0x04, 0x0b
+ };
+
+ return AVA(tlv_id_at_organizationalUnitName, encodingTag, value);
+}
+
+ByteString
+emailAddress(const ByteString& value)
+{
+ // id-emailAddress AttributeType ::= { pkcs-9 1 }
+ // python DottedOIDToCode.py --tlv id-emailAddress 1.2.840.113549.1.9.1
+ static const uint8_t tlv_id_emailAddress[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
+ };
+
+ return AVA(tlv_id_emailAddress, der::IA5String, value);
+}
+
+// RelativeDistinguishedName ::=
+// SET SIZE (1..MAX) OF AttributeTypeAndValue
+//
+ByteString
+RDN(const ByteString& avas)
+{
+ return TLV(der::SET, avas);
+}
+
+// Name ::= CHOICE { -- only one possibility for now --
+// rdnSequence RDNSequence }
+//
+// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+//
+ByteString
+Name(const ByteString& rdns)
+{
+ return TLV(der::SEQUENCE, rdns);
+}
+
+ByteString
+CreateEncodedSerialNumber(long serialNumberValue)
+{
+ return Integer(serialNumberValue);
+}
+
+// BasicConstraints ::= SEQUENCE {
+// cA BOOLEAN DEFAULT FALSE,
+// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
+ByteString
+CreateEncodedBasicConstraints(bool isCA,
+ /*optional in*/ const long* pathLenConstraintValue,
+ Critical critical)
+{
+ ByteString value;
+
+ if (isCA) {
+ ByteString cA(Boolean(true));
+ value.append(cA);
+ }
+
+ if (pathLenConstraintValue) {
+ ByteString pathLenConstraint(Integer(*pathLenConstraintValue));
+ value.append(pathLenConstraint);
+ }
+
+ // python DottedOIDToCode.py --tlv id-ce-basicConstraints 2.5.29.19
+ static const uint8_t tlv_id_ce_basicConstraints[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x13
+ };
+ return Extension(Input(tlv_id_ce_basicConstraints), critical, value);
+}
+
+// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
+// KeyPurposeId ::= OBJECT IDENTIFIER
+ByteString
+CreateEncodedEKUExtension(Input ekuOID, Critical critical)
+{
+ ByteString value(ekuOID.UnsafeGetData(), ekuOID.GetLength());
+
+ // python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
+ static const uint8_t tlv_id_ce_extKeyUsage[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x25
+ };
+
+ return Extension(Input(tlv_id_ce_extKeyUsage), critical, value);
+}
+
+// python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17
+static const uint8_t tlv_id_ce_subjectAltName[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x11
+};
+
+ByteString
+CreateEncodedSubjectAltName(const ByteString& names)
+{
+ return Extension(Input(tlv_id_ce_subjectAltName), Critical::No, names);
+}
+
+ByteString
+CreateEncodedEmptySubjectAltName()
+{
+ return EmptyExtension(Input(tlv_id_ce_subjectAltName), Critical::No);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OCSP responses
+
+ByteString
+CreateEncodedOCSPResponse(OCSPResponseContext& context)
+{
+ if (!context.skipResponseBytes) {
+ if (!context.signerKeyPair) {
+ return ByteString();
+ }
+ }
+
+ // OCSPResponse ::= SEQUENCE {
+ // responseStatus OCSPResponseStatus,
+ // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+
+ // 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
+ // }
+ ByteString reponseStatusValue;
+ reponseStatusValue.push_back(context.responseStatus);
+ ByteString responseStatus(TLV(der::ENUMERATED, reponseStatusValue));
+
+ ByteString responseBytesNested;
+ if (!context.skipResponseBytes) {
+ ByteString responseBytes(ResponseBytes(context));
+ if (ENCODING_FAILED(responseBytes)) {
+ return ByteString();
+ }
+
+ responseBytesNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC,
+ responseBytes);
+ }
+
+ ByteString value;
+ value.append(responseStatus);
+ value.append(responseBytesNested);
+ ByteString result(TLV(der::SEQUENCE, value));
+
+ MaybeLogOutput(result, "ocsp");
+
+ return result;
+}
+
+// ResponseBytes ::= SEQUENCE {
+// responseType OBJECT IDENTIFIER,
+// response OCTET STRING }
+ByteString
+ResponseBytes(OCSPResponseContext& context)
+{
+ // Includes tag and length
+ static const uint8_t id_pkix_ocsp_basic_encoded[] = {
+ 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+ ByteString response(BasicOCSPResponse(context));
+ if (ENCODING_FAILED(response)) {
+ return ByteString();
+ }
+ ByteString responseNested = TLV(der::OCTET_STRING, response);
+
+ ByteString value;
+ value.append(id_pkix_ocsp_basic_encoded,
+ sizeof(id_pkix_ocsp_basic_encoded));
+ value.append(responseNested);
+ return TLV(der::SEQUENCE, value);
+}
+
+// BasicOCSPResponse ::= SEQUENCE {
+// tbsResponseData ResponseData,
+// signatureAlgorithm AlgorithmIdentifier,
+// signature BIT STRING,
+// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ByteString
+BasicOCSPResponse(OCSPResponseContext& context)
+{
+ ByteString tbsResponseData(ResponseData(context));
+ if (ENCODING_FAILED(tbsResponseData)) {
+ return ByteString();
+ }
+
+ return SignedData(tbsResponseData, *context.signerKeyPair,
+ context.signatureAlgorithm, context.badSignature,
+ context.certs);
+}
+
+// Extension ::= SEQUENCE {
+// id OBJECT IDENTIFIER,
+// critical BOOLEAN DEFAULT FALSE
+// value OCTET STRING
+// }
+static ByteString
+OCSPExtension(OCSPResponseExtension& extension)
+{
+ ByteString encoded;
+ encoded.append(extension.id);
+ if (extension.critical) {
+ encoded.append(Boolean(true));
+ }
+ ByteString value(TLV(der::OCTET_STRING, extension.value));
+ encoded.append(value);
+ return TLV(der::SEQUENCE, encoded);
+}
+
+// Extensions ::= [1] {
+// SEQUENCE OF Extension
+// }
+static ByteString
+OCSPExtensions(OCSPResponseExtension* extensions)
+{
+ ByteString value;
+ for (OCSPResponseExtension* extension = extensions;
+ extension; extension = extension->next) {
+ ByteString extensionEncoded(OCSPExtension(*extension));
+ if (ENCODING_FAILED(extensionEncoded)) {
+ return ByteString();
+ }
+ value.append(extensionEncoded);
+ }
+ ByteString sequence(TLV(der::SEQUENCE, value));
+ return TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1, sequence);
+}
+
+// ResponseData ::= SEQUENCE {
+// version [0] EXPLICIT Version DEFAULT v1,
+// responderID ResponderID,
+// producedAt GeneralizedTime,
+// responses SEQUENCE OF SingleResponse,
+// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+ByteString
+ResponseData(OCSPResponseContext& context)
+{
+ ByteString responderID(ResponderID(context));
+ if (ENCODING_FAILED(responderID)) {
+ return ByteString();
+ }
+ ByteString producedAtEncoded(TimeToGeneralizedTime(context.producedAt));
+ if (ENCODING_FAILED(producedAtEncoded)) {
+ return ByteString();
+ }
+ ByteString response(SingleResponse(context));
+ if (ENCODING_FAILED(response)) {
+ return ByteString();
+ }
+ ByteString responses(TLV(der::SEQUENCE, response));
+ ByteString responseExtensions;
+ if (context.responseExtensions || context.includeEmptyExtensions) {
+ responseExtensions = OCSPExtensions(context.responseExtensions);
+ }
+
+ ByteString value;
+ value.append(responderID);
+ value.append(producedAtEncoded);
+ value.append(responses);
+ value.append(responseExtensions);
+ return TLV(der::SEQUENCE, value);
+}
+
+// ResponderID ::= CHOICE {
+// byName [1] Name,
+// byKey [2] KeyHash }
+// }
+ByteString
+ResponderID(OCSPResponseContext& context)
+{
+ ByteString contents;
+ uint8_t responderIDType;
+ if (!context.signerNameDER.empty()) {
+ contents = context.signerNameDER;
+ responderIDType = 1; // byName
+ } else {
+ contents = KeyHash(context.signerKeyPair->subjectPublicKey);
+ if (ENCODING_FAILED(contents)) {
+ return ByteString();
+ }
+ responderIDType = 2; // byKey
+ }
+
+ // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without the
+ // static_cast.
+ uint8_t tag = static_cast<uint8_t>(der::CONSTRUCTED | der::CONTEXT_SPECIFIC |
+ responderIDType);
+ return TLV(tag, contents);
+}
+
+// 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)
+ByteString
+KeyHash(const ByteString& subjectPublicKey)
+{
+ return HashedOctetString(subjectPublicKey);
+}
+
+// SingleResponse ::= SEQUENCE {
+// certID CertID,
+// certStatus CertStatus,
+// thisUpdate GeneralizedTime,
+// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+ByteString
+SingleResponse(OCSPResponseContext& context)
+{
+ ByteString certID(CertID(context));
+ if (ENCODING_FAILED(certID)) {
+ return ByteString();
+ }
+ ByteString certStatus(CertStatus(context));
+ if (ENCODING_FAILED(certStatus)) {
+ return ByteString();
+ }
+ ByteString thisUpdateEncoded(TimeToGeneralizedTime(context.thisUpdate));
+ if (ENCODING_FAILED(thisUpdateEncoded)) {
+ return ByteString();
+ }
+ ByteString nextUpdateEncodedNested;
+ if (context.includeNextUpdate) {
+ ByteString nextUpdateEncoded(TimeToGeneralizedTime(context.nextUpdate));
+ if (ENCODING_FAILED(nextUpdateEncoded)) {
+ return ByteString();
+ }
+ nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
+ nextUpdateEncoded);
+ }
+ ByteString singleExtensions;
+ if (context.singleExtensions || context.includeEmptyExtensions) {
+ singleExtensions = OCSPExtensions(context.singleExtensions);
+ }
+
+ ByteString value;
+ value.append(certID);
+ value.append(certStatus);
+ value.append(thisUpdateEncoded);
+ value.append(nextUpdateEncodedNested);
+ value.append(singleExtensions);
+ return TLV(der::SEQUENCE, value);
+}
+
+// CertID ::= SEQUENCE {
+// hashAlgorithm AlgorithmIdentifier,
+// issuerNameHash OCTET STRING, -- Hash of issuer's DN
+// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
+// serialNumber CertificateSerialNumber }
+ByteString
+CertID(OCSPResponseContext& context)
+{
+ ByteString issuerName(context.certID.issuer.UnsafeGetData(),
+ context.certID.issuer.GetLength());
+ ByteString issuerNameHash(HashedOctetString(issuerName));
+ if (ENCODING_FAILED(issuerNameHash)) {
+ return ByteString();
+ }
+
+ ByteString issuerKeyHash;
+ {
+ // context.certID.issuerSubjectPublicKeyInfo is the entire
+ // SubjectPublicKeyInfo structure, but we need just the subjectPublicKey
+ // part.
+ Reader input(context.certID.issuerSubjectPublicKeyInfo);
+ Reader contents;
+ if (der::ExpectTagAndGetValue(input, der::SEQUENCE, contents) != Success) {
+ return ByteString();
+ }
+ // Skip AlgorithmIdentifier
+ if (der::ExpectTagAndSkipValue(contents, der::SEQUENCE) != Success) {
+ return ByteString();
+ }
+ Input subjectPublicKey;
+ if (der::BitStringWithNoUnusedBits(contents, subjectPublicKey)
+ != Success) {
+ return ByteString();
+ }
+ issuerKeyHash = KeyHash(ByteString(subjectPublicKey.UnsafeGetData(),
+ subjectPublicKey.GetLength()));
+ if (ENCODING_FAILED(issuerKeyHash)) {
+ return ByteString();
+ }
+ }
+
+ ByteString serialNumberValue(context.certID.serialNumber.UnsafeGetData(),
+ context.certID.serialNumber.GetLength());
+ ByteString serialNumber(TLV(der::INTEGER, serialNumberValue));
+
+ // python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26
+ static const uint8_t alg_id_sha1[] = {
+ 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a
+ };
+
+ ByteString value;
+ value.append(alg_id_sha1, sizeof(alg_id_sha1));
+ value.append(issuerNameHash);
+ value.append(issuerKeyHash);
+ value.append(serialNumber);
+ return TLV(der::SEQUENCE, value);
+}
+
+// CertStatus ::= CHOICE {
+// good [0] IMPLICIT NULL,
+// revoked [1] IMPLICIT RevokedInfo,
+// unknown [2] IMPLICIT UnknownInfo }
+//
+// RevokedInfo ::= SEQUENCE {
+// revocationTime GeneralizedTime,
+// revocationReason [0] EXPLICIT CRLReason OPTIONAL }
+//
+// UnknownInfo ::= NULL
+//
+ByteString
+CertStatus(OCSPResponseContext& context)
+{
+ switch (context.certStatus) {
+ // Both good and unknown are ultimately represented as NULL - the only
+ // difference is in the tag that identifies them.
+ case 0:
+ case 2:
+ {
+ // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without
+ // the static cast.
+ return TLV(static_cast<uint8_t>(der::CONTEXT_SPECIFIC |
+ context.certStatus), ByteString());
+ }
+ case 1:
+ {
+ ByteString revocationTime(TimeToGeneralizedTime(context.revocationTime));
+ if (ENCODING_FAILED(revocationTime)) {
+ return ByteString();
+ }
+ // TODO(bug 980536): add support for revocationReason
+ return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, revocationTime);
+ }
+ default:
+ assert(false);
+ // fall through
+ }
+ return ByteString();
+}
+
+static const ByteString NO_UNUSED_BITS(1, 0x00);
+
+// The SubjectPublicKeyInfo syntax is specified in RFC 5280 Section 4.1.
+TestKeyPair::TestKeyPair(const TestPublicKeyAlgorithm& aPublicKeyAlg,
+ const ByteString& spk)
+ : publicKeyAlg(aPublicKeyAlg)
+ , subjectPublicKeyInfo(TLV(der::SEQUENCE,
+ aPublicKeyAlg.algorithmIdentifier +
+ TLV(der::BIT_STRING, NO_UNUSED_BITS + spk)))
+ , subjectPublicKey(spk)
+{
+}
+
+} } } // namespace mozilla::pkix::test
diff --git a/security/nss/lib/mozpkix/tools/DottedOIDToCode.py b/security/nss/lib/mozpkix/tools/DottedOIDToCode.py
new file mode 100644
index 000000000..dfd4ade07
--- /dev/null
+++ b/security/nss/lib/mozpkix/tools/DottedOIDToCode.py
@@ -0,0 +1,216 @@
+# 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.
+
+from __future__ import print_function
+import argparse
+import itertools
+import sys
+
+
+def base128(value):
+ """
+ Given an integral value, returns an array of the base-128 representation
+ of that value, with all but the last byte having the high bit set as
+ required by the DER rules for the nodes of an OID after the first two
+ bytes.
+
+ >>> base128(1)
+ [1]
+ >>> base128(10045)
+ [206, 61]
+ """
+
+ if value < 0:
+ raise ValueError("An OID must have only positive-value nodes.")
+
+ # least significant byte has highest bit unset
+ result = [value % 0x80]
+ value /= 0x80
+
+ while value != 0:
+ result = [0x80 | (value % 0x80)] + result
+ value /= 0x80
+
+ return result
+
+
+def dottedOIDToEncodedArray(dottedOID):
+ """
+ Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and
+ returns an array that contains the DER encoding of its value, without
+ the tag and length (e.g. [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04]).
+ """
+ nodes = [int(x) for x in dottedOID.strip().split(".")]
+ if len(nodes) < 2:
+ raise ValueError("An OID must have at least two nodes.")
+ if not (0 <= nodes[0] <= 2):
+ raise ValueError("The first node of an OID must be 0, 1, or 2.")
+ if not (0 <= nodes[1] <= 39):
+ # XXX: Does this restriction apply when the first part is 2?
+ raise ValueError("The second node of an OID must be 0-39.")
+ firstByte = (40 * nodes[0]) + nodes[1]
+ restBase128 = [base128(x) for x in nodes[2:]]
+ return [firstByte] + list(itertools.chain.from_iterable(restBase128))
+
+
+def dottedOIDToCArray(dottedOID, mode):
+ """
+ Takes a dotted OID string (e.g. '1.2.840.10045.4.3.4') as input, and
+ returns a string that contains the hex encoding of the OID in C++ literal
+ notation, e.g. '0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04'.
+ """
+ bytes = dottedOIDToEncodedArray(dottedOID)
+
+ if mode != "value" and mode != "prefixdefine":
+ bytes = [0x06, len(bytes)] + bytes
+
+ if mode == "alg":
+ # Wrap the DER-encoded OID in a SEQUENCE to create an
+ # AlgorithmIdentifier with no parameters.
+ bytes = [0x30, len(bytes)] + bytes
+
+ return ", ".join(["0x%.2x" % b for b in bytes])
+
+
+def specNameToCName(specName):
+ """
+ Given an string containing an ASN.1 name, returns a string that is a valid
+ C++ identifier that is as similar to that name as possible. Since most
+ ASN.1 identifiers used in PKIX specifications are legal C++ names except
+ for containing hyphens, this function just converts the hyphens to
+ underscores. This may need to be improved in the future if we encounter
+ names with other funny characters.
+ """
+ return specName.replace("-", "_")
+
+
+def toCode(programName, specName, dottedOID, mode):
+ """
+ Given an ASN.1 name and a string containing the dotted representation of an
+ OID, returns a string that contains a C++ declaration for a named constant
+ that contains that OID value. If mode is "value" then only the value of
+ the OID (without the tag or length) will be included in the output. If mode
+ is "tlv" then the value will be prefixed with the tag and length. If mode
+ is "alg" then the value will be a complete der-encoded AlgorithmIdentifier
+ with no parameters.
+
+ This:
+
+ toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4",
+ "value")
+
+ would result in a string like:
+
+ // 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
+ };
+
+ This:
+
+ toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4",
+ "tlv")
+
+ would result in a string like:
+
+ // python DottedOIDToCode.py --tlv ecdsa-with-SHA512 1.2.840.10045.4.3.4
+ static const uint8_t tlv_ecdsa_with_SHA512[] = {
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
+ };
+
+ This:
+
+ toCode("DottedOIDToCode.py", "ecdsa-with-SHA512", "1.2.840.10045.4.3.4",
+ "alg")
+
+ would result in a string like:
+
+ // python DottedOIDToCode.py --alg ecdsa-with-SHA512 1.2.840.10045.4.3.4
+ static const uint8_t alg_ecdsa_with_SHA512[] = {
+ 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
+ };
+
+ This:
+
+ toCode("DottedOIDToCode.py", "PREFIX_1_2_840_10045", "1.2.840.10045",
+ "prefixdefine")
+
+ would result in a string like this (note the lack of indention):
+
+ // python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045
+ #define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d
+ """
+ programNameWithOptions = programName
+
+ if mode == "prefixdefine":
+ programNameWithOptions += " --prefixdefine"
+ varName = specName
+ return ("// python %s %s %s\n" +
+ "#define %s %s\n") % (programNameWithOptions, specName,
+ dottedOID, varName,
+ dottedOIDToCArray(dottedOID, mode))
+
+ varName = specNameToCName(specName)
+ if mode == "tlv":
+ programNameWithOptions += " --tlv"
+ varName = "tlv_" + varName
+ elif mode == "alg":
+ programNameWithOptions += " --alg"
+ varName = "alg_" + varName
+ elif mode == "prefixdefine":
+ programNameWithOptions += " --alg"
+ varName = varName
+
+ return (" // python %s %s %s\n" +
+ " static const uint8_t %s[] = {\n" +
+ " %s\n" +
+ " };\n") % (programNameWithOptions, specName, dottedOID, varName,
+ dottedOIDToCArray(dottedOID, mode))
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Generate code snippets to handle OIDs in C++",
+ epilog="example: python %s ecdsa-with-SHA1 1.2.840.10045.4.1"
+ % sys.argv[0])
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument("--tlv", action='store_true',
+ help="Wrap the encoded OID value with the tag and length")
+ group.add_argument("--alg", action='store_true',
+ help="Wrap the encoded OID value in an encoded SignatureAlgorithm")
+ group.add_argument("--prefixdefine", action='store_true',
+ help="generate a OID prefix #define")
+ parser.add_argument("name",
+ help="The name given to the OID in the specification")
+ parser.add_argument("dottedOID", metavar="dotted-oid",
+ help="The OID value, in dotted notation")
+
+ args = parser.parse_args()
+ if args.alg:
+ mode = 'alg'
+ elif args.tlv:
+ mode = 'tlv'
+ elif args.prefixdefine:
+ mode = 'prefixdefine'
+ else:
+ mode = 'value'
+
+ print(toCode(sys.argv[0], args.name, args.dottedOID, mode))