summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFranziskus Kiefer <franziskuskiefer@gmail.com>2018-08-03 09:28:54 +0200
committerFranziskus Kiefer <franziskuskiefer@gmail.com>2018-08-03 09:28:54 +0200
commit67eda0f65557d253094329c74b30d11c32d77550 (patch)
tree05c19a507873f90dd433b7617028742da67dc3a8
parent288c01f2e525389939c32524cd08b7281aa59a5c (diff)
parenta6fa27bdd8bec089c84950f24c4875ed3e61ea57 (diff)
downloadnss-hg-67eda0f65557d253094329c74b30d11c32d77550.tar.gz
Bug 1479787 - merge mozpkix from mozilla-central to NSS
-rw-r--r--lib/mozpkix/include/pkix/Input.h348
-rw-r--r--lib/mozpkix/include/pkix/Result.h241
-rw-r--r--lib/mozpkix/include/pkix/Time.h155
-rw-r--r--lib/mozpkix/include/pkix/pkix.h161
-rw-r--r--lib/mozpkix/include/pkix/pkixnss.h112
-rw-r--r--lib/mozpkix/include/pkix/pkixtypes.h411
-rw-r--r--lib/mozpkix/lib/ScopedPtr.h83
-rw-r--r--lib/mozpkix/lib/pkixbuild.cpp418
-rw-r--r--lib/mozpkix/lib/pkixcert.cpp323
-rw-r--r--lib/mozpkix/lib/pkixcheck.cpp1100
-rw-r--r--lib/mozpkix/lib/pkixcheck.h66
-rw-r--r--lib/mozpkix/lib/pkixder.cpp611
-rw-r--r--lib/mozpkix/lib/pkixder.h566
-rw-r--r--lib/mozpkix/lib/pkixnames.cpp2050
-rw-r--r--lib/mozpkix/lib/pkixnss.cpp236
-rw-r--r--lib/mozpkix/lib/pkixocsp.cpp1012
-rw-r--r--lib/mozpkix/lib/pkixresult.cpp46
-rw-r--r--lib/mozpkix/lib/pkixtime.cpp78
-rw-r--r--lib/mozpkix/lib/pkixutil.h291
-rw-r--r--lib/mozpkix/lib/pkixverify.cpp106
-rw-r--r--lib/mozpkix/moz.build36
-rw-r--r--lib/mozpkix/test/gtest/README.txt61
-rw-r--r--lib/mozpkix/test/gtest/moz.build72
-rw-r--r--lib/mozpkix/test/gtest/pkixbuild_tests.cpp893
-rw-r--r--lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp275
-rw-r--r--lib/mozpkix/test/gtest/pkixcert_signature_algorithm_tests.cpp258
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp721
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp62
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp284
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp366
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp127
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_ParseValidity_tests.cpp83
-rw-r--r--lib/mozpkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp119
-rw-r--r--lib/mozpkix/test/gtest/pkixder_input_tests.cpp920
-rw-r--r--lib/mozpkix/test/gtest/pkixder_pki_types_tests.cpp479
-rw-r--r--lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp1225
-rw-r--r--lib/mozpkix/test/gtest/pkixgtest.cpp46
-rw-r--r--lib/mozpkix/test/gtest/pkixgtest.h255
-rw-r--r--lib/mozpkix/test/gtest/pkixnames_tests.cpp2837
-rw-r--r--lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp145
-rw-r--r--lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp1064
-rw-r--r--lib/mozpkix/test/lib/moz.build39
-rw-r--r--lib/mozpkix/test/lib/pkixtestalg.cpp210
-rw-r--r--lib/mozpkix/test/lib/pkixtestnss.cpp378
-rw-r--r--lib/mozpkix/test/lib/pkixtestnss.h49
-rw-r--r--lib/mozpkix/test/lib/pkixtestutil.cpp1155
-rw-r--r--lib/mozpkix/test/lib/pkixtestutil.h448
-rw-r--r--lib/mozpkix/tools/DottedOIDToCode.py216
-rw-r--r--lib/mozpkix/warnings.mozbuild52
49 files changed, 21289 insertions, 0 deletions
diff --git a/lib/mozpkix/include/pkix/Input.h b/lib/mozpkix/include/pkix/Input.h
new file mode 100644
index 000000000..d3aa38649
--- /dev/null
+++ b/lib/mozpkix/include/pkix/Input.h
@@ -0,0 +1,348 @@
+/* -*- 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 "pkix/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/lib/mozpkix/include/pkix/Result.h b/lib/mozpkix/include/pkix/Result.h
new file mode 100644
index 000000000..a4241b84b
--- /dev/null
+++ b/lib/mozpkix/include/pkix/Result.h
@@ -0,0 +1,241 @@
+/* -*- 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/lib/mozpkix/include/pkix/Time.h b/lib/mozpkix/include/pkix/Time.h
new file mode 100644
index 000000000..e997682b3
--- /dev/null
+++ b/lib/mozpkix/include/pkix/Time.h
@@ -0,0 +1,155 @@
+/* -*- 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 <ctime>
+#include <limits>
+#include <stdint.h>
+
+#include "pkix/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/lib/mozpkix/include/pkix/pkix.h b/lib/mozpkix/include/pkix/pkix.h
new file mode 100644
index 000000000..da4f63615
--- /dev/null
+++ b/lib/mozpkix/include/pkix/pkix.h
@@ -0,0 +1,161 @@
+/* -*- 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 "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/lib/mozpkix/include/pkix/pkixnss.h b/lib/mozpkix/include/pkix/pkixnss.h
new file mode 100644
index 000000000..39ba705a2
--- /dev/null
+++ b/lib/mozpkix/include/pkix/pkixnss.h
@@ -0,0 +1,112 @@
+/* -*- 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 "pkixtypes.h"
+#include "prerror.h"
+#include "seccomon.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/lib/mozpkix/include/pkix/pkixtypes.h b/lib/mozpkix/include/pkix/pkixtypes.h
new file mode 100644
index 000000000..e93723f97
--- /dev/null
+++ b/lib/mozpkix/include/pkix/pkixtypes.h
@@ -0,0 +1,411 @@
+/* -*- 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 "pkix/Input.h"
+#include "pkix/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;
+};
+
+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/lib/mozpkix/lib/ScopedPtr.h b/lib/mozpkix/lib/ScopedPtr.h
new file mode 100644
index 000000000..a9e18adc1
--- /dev/null
+++ b/lib/mozpkix/lib/ScopedPtr.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_pkix_ScopedPtr_h
+#define mozilla_pkix_ScopedPtr_h
+
+namespace mozilla { namespace pkix {
+
+// A subset polyfill of std::unique_ptr that does not support move construction
+// or move assignment. This is used instead of std::unique_ptr because some
+// important toolchains still don't provide std::unique_ptr, including in
+// particular Android NDK projects with APP_STL=stlport_static or
+// ALL_STL=stlport_shared.
+template <typename T, void (&Destroyer)(T*)>
+class ScopedPtr final
+{
+public:
+ explicit ScopedPtr(T* value = nullptr) : mValue(value) { }
+
+ ScopedPtr(const ScopedPtr&) = delete;
+
+ ~ScopedPtr()
+ {
+ if (mValue) {
+ Destroyer(mValue);
+ }
+ }
+
+ void operator=(const ScopedPtr&) = delete;
+
+ T& operator*() const { return *mValue; }
+ T* operator->() const { return mValue; }
+
+ explicit operator bool() const { return mValue; }
+
+ T* get() const { return mValue; }
+
+ T* release()
+ {
+ T* result = mValue;
+ mValue = nullptr;
+ return result;
+ }
+
+ void reset(T* newValue = nullptr)
+ {
+ // The C++ standard requires std::unique_ptr to destroy the old value
+ // pointed to by mValue, if any, *after* assigning the new value to mValue.
+ T* oldValue = mValue;
+ mValue = newValue;
+ if (oldValue) {
+ Destroyer(oldValue);
+ }
+ }
+
+private:
+ T* mValue;
+};
+
+} } // namespace mozilla::pkix
+
+#endif // mozilla_pkix_ScopedPtr_h
diff --git a/lib/mozpkix/lib/pkixbuild.cpp b/lib/mozpkix/lib/pkixbuild.cpp
new file mode 100644
index 000000000..d3202cd8e
--- /dev/null
+++ b/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 "pkix/pkix.h"
+
+#include "pkixcheck.h"
+#include "pkixutil.h"
+
+namespace mozilla { namespace pkix {
+
+static Result BuildForward(TrustDomain& trustDomain,
+ const BackCert& subject,
+ Time time,
+ KeyUsage requiredKeyUsageIfPresent,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
+ /*optional*/ const Input* stapledOCSPResponse,
+ unsigned int subCACount,
+ 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/lib/mozpkix/lib/pkixcert.cpp b/lib/mozpkix/lib/pkixcert.cpp
new file mode 100644
index 000000000..f6eb116b2
--- /dev/null
+++ b/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 "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/lib/mozpkix/lib/pkixcheck.cpp b/lib/mozpkix/lib/pkixcheck.cpp
new file mode 100644
index 000000000..705f7e7f0
--- /dev/null
+++ b/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 "pkixcheck.h"
+
+#include "pkixder.h"
+#include "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/lib/mozpkix/lib/pkixcheck.h b/lib/mozpkix/lib/pkixcheck.h
new file mode 100644
index 000000000..9ea205f3b
--- /dev/null
+++ b/lib/mozpkix/lib/pkixcheck.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_pkix_pkixcheck_h
+#define mozilla_pkix_pkixcheck_h
+
+#include "pkix/pkixtypes.h"
+
+namespace mozilla { namespace pkix {
+
+class BackCert;
+
+Result CheckIssuerIndependentProperties(
+ TrustDomain& trustDomain,
+ const BackCert& cert,
+ Time time,
+ KeyUsage requiredKeyUsageIfPresent,
+ KeyPurposeId requiredEKUIfPresent,
+ const CertPolicyId& requiredPolicy,
+ unsigned int subCACount,
+ /*out*/ TrustLevel& trustLevel);
+
+Result CheckNameConstraints(Input encodedNameConstraints,
+ const BackCert& firstChild,
+ KeyPurposeId requiredEKUIfPresent);
+
+Result CheckIssuer(Input encodedIssuer);
+
+// ParseValidity and CheckValidity are usually used together. First you parse
+// the dates from the DER Validity sequence, then you compare them to the time
+// at which you are validating. They are separate so that the notBefore and
+// notAfter times can be used for other things before they are checked against
+// the time of validation.
+Result ParseValidity(Input encodedValidity,
+ /*optional out*/ Time* notBeforeOut = nullptr,
+ /*optional out*/ Time* notAfterOut = nullptr);
+Result CheckValidity(Time time, Time notBefore, Time notAfter);
+
+// Check that a subject has TLS Feature (rfc7633) requirements that match its
+// potential issuer
+Result CheckTLSFeatures(const BackCert& subject, BackCert& potentialIssuer);
+
+} } // namespace mozilla::pkix
+
+#endif // mozilla_pkix_pkixcheck_h
diff --git a/lib/mozpkix/lib/pkixder.cpp b/lib/mozpkix/lib/pkixder.cpp
new file mode 100644
index 000000000..4f2647058
--- /dev/null
+++ b/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 "pkixder.h"
+
+#include "pkixutil.h"
+
+namespace mozilla { namespace pkix { namespace der {
+
+// Too complicated to be inline
+Result
+ReadTagAndGetValue(Reader& input, /*out*/ uint8_t& tag, /*out*/ Input& value)
+{
+ Result rv;
+
+ rv = input.Read(tag);
+ if (rv != Success) {
+ return rv;
+ }
+ if ((tag & 0x1F) == 0x1F) {
+ return Result::ERROR_BAD_DER; // high tag number form not allowed
+ }
+
+ uint16_t length;
+
+ // The short form of length is a single byte with the high order bit set
+ // to zero. The long form of length is one byte with the high order bit
+ // set, followed by N bytes, where N is encoded in the lowest 7 bits of
+ // the first byte.
+ uint8_t length1;
+ rv = input.Read(length1);
+ if (rv != Success) {
+ return rv;
+ }
+ if (!(length1 & 0x80)) {
+ length = length1;
+ } else if (length1 == 0x81) {
+ uint8_t length2;
+ rv = input.Read(length2);
+ if (rv != Success) {
+ return rv;
+ }
+ if (length2 < 128) {
+ // Not shortest possible encoding
+ return Result::ERROR_BAD_DER;
+ }
+ length = length2;
+ } else if (length1 == 0x82) {
+ rv = input.Read(length);
+ if (rv != Success) {
+ return rv;
+ }
+ if (length < 256) {
+ // Not shortest possible encoding
+ return Result::ERROR_BAD_DER;
+ }
+ } else {
+ // We don't support lengths larger than 2^16 - 1.
+ return Result::ERROR_BAD_DER;
+ }
+
+ return input.Skip(length, value);
+}
+
+static Result
+OptionalNull(Reader& input)
+{
+ if (input.Peek(NULLTag)) {
+ return Null(input);
+ }
+ return Success;
+}
+
+namespace {
+
+Result
+AlgorithmIdentifierValue(Reader& input, /*out*/ Reader& algorithmOIDValue)
+{
+ Result rv = ExpectTagAndGetValue(input, der::OIDTag, algorithmOIDValue);
+ if (rv != Success) {
+ return rv;
+ }
+ return OptionalNull(input);
+}
+
+} // namespace
+
+Result
+SignatureAlgorithmIdentifierValue(Reader& input,
+ /*out*/ PublicKeyAlgorithm& publicKeyAlgorithm,
+ /*out*/ DigestAlgorithm& digestAlgorithm)
+{
+ // RFC 5758 Section 3.2 (ECDSA with SHA-2), and RFC 3279 Section 2.2.3
+ // (ECDSA with SHA-1) say that parameters must be omitted.
+ //
+ // RFC 4055 Section 5 and RFC 3279 Section 2.2.1 both say that parameters for
+ // RSA must be encoded as NULL; we relax that requirement by allowing the
+ // NULL to be omitted, to match all the other signature algorithms we support
+ // and for compatibility.
+ Reader algorithmID;
+ Result rv = AlgorithmIdentifierValue(input, algorithmID);
+ if (rv != Success) {
+ return rv;
+ }
+
+ // RFC 5758 Section 3.2 (ecdsa-with-SHA224 is intentionally excluded)
+ // python DottedOIDToCode.py ecdsa-with-SHA256 1.2.840.10045.4.3.2
+ static const uint8_t ecdsa_with_SHA256[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02
+ };
+ // python DottedOIDToCode.py ecdsa-with-SHA384 1.2.840.10045.4.3.3
+ static const uint8_t ecdsa_with_SHA384[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03
+ };
+ // python DottedOIDToCode.py ecdsa-with-SHA512 1.2.840.10045.4.3.4
+ static const uint8_t ecdsa_with_SHA512[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04
+ };
+
+ // RFC 4055 Section 5 (sha224WithRSAEncryption is intentionally excluded)
+ // python DottedOIDToCode.py sha256WithRSAEncryption 1.2.840.113549.1.1.11
+ static const uint8_t sha256WithRSAEncryption[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
+ };
+ // python DottedOIDToCode.py sha384WithRSAEncryption 1.2.840.113549.1.1.12
+ static const uint8_t sha384WithRSAEncryption[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c
+ };
+ // python DottedOIDToCode.py sha512WithRSAEncryption 1.2.840.113549.1.1.13
+ static const uint8_t sha512WithRSAEncryption[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d
+ };
+
+ // RFC 3279 Section 2.2.1
+ // python DottedOIDToCode.py sha-1WithRSAEncryption 1.2.840.113549.1.1.5
+ static const uint8_t sha_1WithRSAEncryption[] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
+ };
+
+ // NIST Open Systems Environment (OSE) Implementor's Workshop (OIW)
+ // http://www.oiw.org/agreements/stable/12s-9412.txt (no longer works).
+ // http://www.imc.org/ietf-pkix/old-archive-97/msg01166.html
+ // We need to support this this non-PKIX OID for compatibility.
+ // python DottedOIDToCode.py sha1WithRSASignature 1.3.14.3.2.29
+ static const uint8_t sha1WithRSASignature[] = {
+ 0x2b, 0x0e, 0x03, 0x02, 0x1d
+ };
+
+ // RFC 3279 Section 2.2.3
+ // python DottedOIDToCode.py ecdsa-with-SHA1 1.2.840.10045.4.1
+ static const uint8_t ecdsa_with_SHA1[] = {
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01
+ };
+
+ // Matching is attempted based on a rough estimate of the commonality of the
+ // algorithm, to minimize the number of MatchRest calls.
+ if (algorithmID.MatchRest(sha256WithRSAEncryption)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
+ digestAlgorithm = DigestAlgorithm::sha256;
+ } else if (algorithmID.MatchRest(ecdsa_with_SHA256)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
+ digestAlgorithm = DigestAlgorithm::sha256;
+ } else if (algorithmID.MatchRest(sha_1WithRSAEncryption)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
+ digestAlgorithm = DigestAlgorithm::sha1;
+ } else if (algorithmID.MatchRest(ecdsa_with_SHA1)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
+ digestAlgorithm = DigestAlgorithm::sha1;
+ } else if (algorithmID.MatchRest(ecdsa_with_SHA384)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
+ digestAlgorithm = DigestAlgorithm::sha384;
+ } else if (algorithmID.MatchRest(ecdsa_with_SHA512)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::ECDSA;
+ digestAlgorithm = DigestAlgorithm::sha512;
+ } else if (algorithmID.MatchRest(sha384WithRSAEncryption)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
+ digestAlgorithm = DigestAlgorithm::sha384;
+ } else if (algorithmID.MatchRest(sha512WithRSAEncryption)) {
+ publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
+ digestAlgorithm = DigestAlgorithm::sha512;
+ } else if (algorithmID.MatchRest(sha1WithRSASignature)) {
+ // XXX(bug 1042479): recognize this old OID for compatibility.
+ publicKeyAlgorithm = PublicKeyAlgorithm::RSA_PKCS1;
+ digestAlgorithm = DigestAlgorithm::sha1;
+ } else {
+ return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+ }
+
+ return Success;
+}
+
+Result
+DigestAlgorithmIdentifier(Reader& input, /*out*/ DigestAlgorithm& algorithm)
+{
+ 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/lib/mozpkix/lib/pkixder.h b/lib/mozpkix/lib/pkixder.h
new file mode 100644
index 000000000..cdfde3eae
--- /dev/null
+++ b/lib/mozpkix/lib/pkixder.h
@@ -0,0 +1,566 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_pkix_pkixder_h
+#define mozilla_pkix_pkixder_h
+
+// Expect* functions advance the input mark and return Success if the input
+// matches the given criteria; they fail with the input mark in an undefined
+// state if the input does not match the criteria.
+//
+// Match* functions advance the input mark and return true if the input matches
+// the given criteria; they return false without changing the input mark if the
+// input does not match the criteria.
+//
+// Skip* functions unconditionally advance the input mark and return Success if
+// they are able to do so; otherwise they fail with the input mark in an
+// undefined state.
+
+#include "pkix/Input.h"
+#include "pkix/pkixtypes.h"
+
+namespace mozilla { namespace pkix { namespace der {
+
+enum Class : uint8_t
+{
+ UNIVERSAL = 0 << 6,
+// APPLICATION = 1 << 6, // unused
+ CONTEXT_SPECIFIC = 2 << 6,
+// PRIVATE = 3 << 6 // unused
+};
+
+enum Constructed
+{
+ CONSTRUCTED = 1 << 5
+};
+
+enum Tag : uint8_t
+{
+ BOOLEAN = UNIVERSAL | 0x01,
+ INTEGER = UNIVERSAL | 0x02,
+ BIT_STRING = UNIVERSAL | 0x03,
+ OCTET_STRING = UNIVERSAL | 0x04,
+ NULLTag = UNIVERSAL | 0x05,
+ OIDTag = UNIVERSAL | 0x06,
+ ENUMERATED = UNIVERSAL | 0x0a,
+ UTF8String = UNIVERSAL | 0x0c,
+ SEQUENCE = UNIVERSAL | CONSTRUCTED | 0x10, // 0x30
+ SET = UNIVERSAL | CONSTRUCTED | 0x11, // 0x31
+ PrintableString = UNIVERSAL | 0x13,
+ TeletexString = UNIVERSAL | 0x14,
+ IA5String = UNIVERSAL | 0x16,
+ UTCTime = UNIVERSAL | 0x17,
+ GENERALIZED_TIME = UNIVERSAL | 0x18,
+};
+
+enum class EmptyAllowed { No = 0, Yes = 1 };
+
+Result ReadTagAndGetValue(Reader& input, /*out*/ uint8_t& tag,
+ /*out*/ Input& value);
+Result End(Reader& input);
+
+inline Result
+ExpectTagAndGetValue(Reader& input, uint8_t tag, /*out*/ Input& value)
+{
+ uint8_t actualTag;
+ Result rv = ReadTagAndGetValue(input, actualTag, value);
+ if (rv != Success) {
+ return rv;
+ }
+ if (tag != actualTag) {
+ return Result::ERROR_BAD_DER;
+ }
+ return Success;
+}
+
+inline Result
+ExpectTagAndGetValue(Reader& input, uint8_t tag, /*out*/ Reader& value)
+{
+ Input valueInput;
+ Result rv = ExpectTagAndGetValue(input, tag, valueInput);
+ if (rv != Success) {
+ return rv;
+ }
+ return value.Init(valueInput);
+}
+
+inline Result
+ExpectTagAndEmptyValue(Reader& input, uint8_t tag)
+{
+ Reader value;
+ Result rv = ExpectTagAndGetValue(input, tag, value);
+ if (rv != Success) {
+ return rv;
+ }
+ return End(value);
+}
+
+inline Result
+ExpectTagAndSkipValue(Reader& input, uint8_t tag)
+{
+ Input ignoredValue;
+ return ExpectTagAndGetValue(input, tag, ignoredValue);
+}
+
+// Like ExpectTagAndGetValue, except the output Input will contain the
+// encoded tag and length along with the value.
+inline Result
+ExpectTagAndGetTLV(Reader& input, uint8_t tag, /*out*/ Input& tlv)
+{
+ Reader::Mark mark(input.GetMark());
+ Result rv = ExpectTagAndSkipValue(input, tag);
+ if (rv != Success) {
+ return rv;
+ }
+ return input.GetInput(mark, tlv);
+}
+
+inline Result
+End(Reader& input)
+{
+ if (!input.AtEnd()) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ return Success;
+}
+
+template <typename Decoder>
+inline Result
+Nested(Reader& input, uint8_t tag, Decoder decoder)
+{
+ Reader nested;
+ Result rv = ExpectTagAndGetValue(input, tag, nested);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = decoder(nested);
+ if (rv != Success) {
+ return rv;
+ }
+ return End(nested);
+}
+
+template <typename Decoder>
+inline Result
+Nested(Reader& input, uint8_t outerTag, uint8_t innerTag, Decoder decoder)
+{
+ Reader nestedInput;
+ Result rv = ExpectTagAndGetValue(input, outerTag, nestedInput);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = Nested(nestedInput, innerTag, decoder);
+ if (rv != Success) {
+ return rv;
+ }
+ return End(nestedInput);
+}
+
+// This can be used to decode constructs like this:
+//
+// ...
+// foos SEQUENCE OF Foo,
+// ...
+// Foo ::= SEQUENCE {
+// }
+//
+// using code like this:
+//
+// Result Foo(Reader& r) { /*...*/ }
+//
+// rv = der::NestedOf(input, der::SEQEUENCE, der::SEQUENCE, Foo);
+//
+// or:
+//
+// Result Bar(Reader& r, int value) { /*...*/ }
+//
+// int value = /*...*/;
+//
+// rv = der::NestedOf(input, der::SEQUENCE, [value](Reader& r) {
+// return Bar(r, value);
+// });
+//
+// In these examples the function will get called once for each element of
+// foos.
+//
+template <typename Decoder>
+inline Result
+NestedOf(Reader& input, uint8_t outerTag, uint8_t innerTag,
+ EmptyAllowed mayBeEmpty, Decoder decoder)
+{
+ Reader inner;
+ Result rv = ExpectTagAndGetValue(input, outerTag, inner);
+ if (rv != Success) {
+ return rv;
+ }
+
+ if (inner.AtEnd()) {
+ if (mayBeEmpty != EmptyAllowed::Yes) {
+ return Result::ERROR_BAD_DER;
+ }
+ return Success;
+ }
+
+ do {
+ rv = Nested(inner, innerTag, decoder);
+ if (rv != Success) {
+ return rv;
+ }
+ } while (!inner.AtEnd());
+
+ return Success;
+}
+
+// Often, a function will need to decode an Input or Reader that contains
+// DER-encoded data wrapped in a SEQUENCE (or similar) with nothing after it.
+// This function reduces the boilerplate necessary for stripping the outermost
+// SEQUENCE (or similar) and ensuring that nothing follows it.
+inline Result
+ExpectTagAndGetValueAtEnd(Reader& outer, uint8_t expectedTag,
+ /*out*/ Reader& inner)
+{
+ Result rv = der::ExpectTagAndGetValue(outer, expectedTag, inner);
+ if (rv != Success) {
+ return rv;
+ }
+ return der::End(outer);
+}
+
+// Similar to the above, but takes an Input instead of a Reader&.
+inline Result
+ExpectTagAndGetValueAtEnd(Input outer, uint8_t expectedTag,
+ /*out*/ Reader& inner)
+{
+ Reader outerReader(outer);
+ return ExpectTagAndGetValueAtEnd(outerReader, expectedTag, inner);
+}
+
+// Universal types
+
+namespace internal {
+
+enum class IntegralValueRestriction
+{
+ NoRestriction,
+ MustBePositive,
+ MustBe0To127,
+};
+
+Result IntegralBytes(Reader& input, uint8_t tag,
+ IntegralValueRestriction valueRestriction,
+ /*out*/ Input& value,
+ /*optional out*/ Input::size_type* significantBytes = nullptr);
+
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+Result IntegralValue(Reader& input, uint8_t tag, /*out*/ uint8_t& value);
+
+} // namespace internal
+
+Result
+BitStringWithNoUnusedBits(Reader& input, /*out*/ Input& value);
+
+inline Result
+Boolean(Reader& input, /*out*/ bool& value)
+{
+ Reader valueReader;
+ Result rv = ExpectTagAndGetValue(input, BOOLEAN, valueReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ uint8_t intValue;
+ rv = valueReader.Read(intValue);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = End(valueReader);
+ if (rv != Success) {
+ return rv;
+ }
+ switch (intValue) {
+ case 0: value = false; return Success;
+ case 0xFF: value = true; return Success;
+ default:
+ return Result::ERROR_BAD_DER;
+ }
+}
+
+// This is for BOOLEAN DEFAULT FALSE.
+// The standard stipulates that "The encoding of a set value or sequence value
+// shall not include an encoding for any component value which is equal to its
+// default value." However, it appears to be common that other libraries
+// incorrectly include the value of a BOOLEAN even when it's equal to the
+// default value, so we allow invalid explicit encodings here.
+inline Result
+OptionalBoolean(Reader& input, /*out*/ bool& value)
+{
+ value = false;
+ if (input.Peek(BOOLEAN)) {
+ Result rv = Boolean(input, value);
+ if (rv != Success) {
+ return rv;
+ }
+ }
+ return Success;
+}
+
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+inline Result
+Enumerated(Reader& input, uint8_t& value)
+{
+ return internal::IntegralValue(input, ENUMERATED | 0, value);
+}
+
+namespace internal {
+
+// internal::TimeChoice implements the shared functionality of GeneralizedTime
+// and TimeChoice. tag must be either UTCTime or GENERALIZED_TIME.
+//
+// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
+// eliminate the chance for complications in converting times to traditional
+// time formats that start at 1970.
+Result TimeChoice(Reader& input, uint8_t tag, /*out*/ Time& time);
+
+} // namespace internal
+
+// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
+// eliminate the chance for complications in converting times to traditional
+// time formats that start at 1970.
+inline Result
+GeneralizedTime(Reader& input, /*out*/ Time& time)
+{
+ return internal::TimeChoice(input, GENERALIZED_TIME, time);
+}
+
+// Only times from 1970-01-01-00:00:00 onward are accepted, in order to
+// eliminate the chance for complications in converting times to traditional
+// time formats that start at 1970.
+inline Result
+TimeChoice(Reader& input, /*out*/ Time& time)
+{
+ uint8_t expectedTag = input.Peek(UTCTime) ? UTCTime : GENERALIZED_TIME;
+ return internal::TimeChoice(input, expectedTag, time);
+}
+
+// Parse a DER integer value into value. Empty values, negative values, and
+// zero are rejected. If significantBytes is not null, then it will be set to
+// the number of significant bytes in the value (the length of the value, less
+// the length of any leading padding), which is useful for key size checks.
+inline Result
+PositiveInteger(Reader& input, /*out*/ Input& value,
+ /*optional out*/ Input::size_type* significantBytes = nullptr)
+{
+ return internal::IntegralBytes(
+ input, INTEGER, internal::IntegralValueRestriction::MustBePositive,
+ value, significantBytes);
+}
+
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed.
+inline Result
+Integer(Reader& input, /*out*/ uint8_t& value)
+{
+ return internal::IntegralValue(input, INTEGER, value);
+}
+
+// This parser will only parse values between 0..127. If this range is
+// increased then callers will need to be changed. The default value must be
+// -1; defaultValue is only a parameter to make it clear in the calling code
+// what the default value is.
+inline Result
+OptionalInteger(Reader& input, long defaultValue, /*out*/ long& value)
+{
+ // If we need to support a different default value in the future, we need to
+ // test that parsedValue != defaultValue.
+ if (defaultValue != -1) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ if (!input.Peek(INTEGER)) {
+ value = defaultValue;
+ return Success;
+ }
+
+ uint8_t parsedValue;
+ Result rv = Integer(input, parsedValue);
+ if (rv != Success) {
+ return rv;
+ }
+ value = parsedValue;
+ return Success;
+}
+
+inline Result
+Null(Reader& input)
+{
+ return ExpectTagAndEmptyValue(input, NULLTag);
+}
+
+template <uint8_t Len>
+Result
+OID(Reader& input, const uint8_t (&expectedOid)[Len])
+{
+ Reader value;
+ Result rv = ExpectTagAndGetValue(input, OIDTag, value);
+ if (rv != Success) {
+ return rv;
+ }
+ if (!value.MatchRest(expectedOid)) {
+ return Result::ERROR_BAD_DER;
+ }
+ return Success;
+}
+
+// PKI-specific types
+
+inline Result
+CertificateSerialNumber(Reader& input, /*out*/ Input& value)
+{
+ // http://tools.ietf.org/html/rfc5280#section-4.1.2.2:
+ //
+ // * "The serial number MUST be a positive integer assigned by the CA to
+ // each certificate."
+ // * "Certificate users MUST be able to handle serialNumber values up to 20
+ // octets. Conforming CAs MUST NOT use serialNumber values longer than 20
+ // octets."
+ // * "Note: Non-conforming CAs may issue certificates with serial numbers
+ // that are negative or zero. Certificate users SHOULD be prepared to
+ // gracefully handle such certificates."
+ return internal::IntegralBytes(
+ input, INTEGER, internal::IntegralValueRestriction::NoRestriction,
+ value);
+}
+
+// x.509 and OCSP both use this same version numbering scheme, though OCSP
+// only supports v1.
+enum class Version { v1 = 0, v2 = 1, v3 = 2, v4 = 3, 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/lib/mozpkix/lib/pkixnames.cpp b/lib/mozpkix/lib/pkixnames.cpp
new file mode 100644
index 000000000..1eceadd66
--- /dev/null
+++ b/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 "pkixcheck.h"
+#include "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/lib/mozpkix/lib/pkixnss.cpp b/lib/mozpkix/lib/pkixnss.cpp
new file mode 100644
index 000000000..b33f4fd03
--- /dev/null
+++ b/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 "pkix/pkixnss.h"
+
+#include <limits>
+
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "pk11pub.h"
+#include "pkix/pkix.h"
+#include "pkixutil.h"
+#include "ScopedPtr.h"
+#include "secerr.h"
+#include "sslerr.h"
+
+namespace mozilla { namespace pkix {
+
+namespace {
+
+Result
+VerifySignedDigest(const SignedDigest& sd,
+ Input subjectPublicKeyInfo,
+ SECOidTag pubKeyAlg,
+ void* pkcs11PinArg)
+{
+ SECOidTag digestAlg;
+ switch (sd.digestAlgorithm) {
+ case DigestAlgorithm::sha512: digestAlg = SEC_OID_SHA512; break;
+ case DigestAlgorithm::sha384: digestAlg = SEC_OID_SHA384; break;
+ case DigestAlgorithm::sha256: digestAlg = SEC_OID_SHA256; break;
+ case DigestAlgorithm::sha1: digestAlg = SEC_OID_SHA1; break;
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+
+ SECItem subjectPublicKeyInfoSECItem =
+ UnsafeMapInputToSECItem(subjectPublicKeyInfo);
+ ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo>
+ spki(SECKEY_DecodeDERSubjectPublicKeyInfo(&subjectPublicKeyInfoSECItem));
+ if (!spki) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
+ pubKey(SECKEY_ExtractPublicKey(spki.get()));
+ if (!pubKey) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+
+ SECItem digestSECItem(UnsafeMapInputToSECItem(sd.digest));
+ SECItem signatureSECItem(UnsafeMapInputToSECItem(sd.signature));
+ SECStatus srv = VFY_VerifyDigestDirect(&digestSECItem, pubKey.get(),
+ &signatureSECItem, pubKeyAlg,
+ digestAlg, pkcs11PinArg);
+ if (srv != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+
+ return Success;
+}
+
+} // namespace
+
+Result
+VerifyRSAPKCS1SignedDigestNSS(const SignedDigest& sd,
+ Input subjectPublicKeyInfo,
+ void* pkcs11PinArg)
+{
+ return VerifySignedDigest(sd, subjectPublicKeyInfo,
+ SEC_OID_PKCS1_RSA_ENCRYPTION, pkcs11PinArg);
+}
+
+Result
+VerifyECDSASignedDigestNSS(const SignedDigest& sd,
+ Input subjectPublicKeyInfo,
+ void* pkcs11PinArg)
+{
+ return VerifySignedDigest(sd, subjectPublicKeyInfo,
+ SEC_OID_ANSIX962_EC_PUBLIC_KEY, pkcs11PinArg);
+}
+
+Result
+DigestBufNSS(Input item,
+ DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf,
+ size_t digestBufLen)
+{
+ SECOidTag oid;
+ size_t bits;
+ switch (digestAlg) {
+ case DigestAlgorithm::sha512: oid = SEC_OID_SHA512; bits = 512; break;
+ case DigestAlgorithm::sha384: oid = SEC_OID_SHA384; bits = 384; break;
+ case DigestAlgorithm::sha256: oid = SEC_OID_SHA256; bits = 256; break;
+ case DigestAlgorithm::sha1: oid = SEC_OID_SHA1; bits = 160; break;
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+ if (digestBufLen != bits / 8) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ SECItem itemSECItem = UnsafeMapInputToSECItem(item);
+ if (itemSECItem.len >
+ static_cast<decltype(itemSECItem.len)>(
+ std::numeric_limits<int32_t>::max())) {
+ PR_NOT_REACHED("large items should not be possible here");
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ SECStatus srv = PK11_HashBuf(oid, digestBuf, itemSECItem.data,
+ static_cast<int32_t>(itemSECItem.len));
+ if (srv != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ return Success;
+}
+
+Result
+MapPRErrorCodeToResult(PRErrorCode error)
+{
+ switch (error)
+ {
+#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
+ case nss_result: return Result::mozilla_pkix_result;
+
+ MOZILLA_PKIX_MAP_LIST
+
+#undef MOZILLA_PKIX_MAP
+
+ default:
+ return Result::ERROR_UNKNOWN_ERROR;
+ }
+}
+
+PRErrorCode
+MapResultToPRErrorCode(Result result)
+{
+ switch (result)
+ {
+#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
+ case Result::mozilla_pkix_result: return nss_result;
+
+ MOZILLA_PKIX_MAP_LIST
+
+#undef MOZILLA_PKIX_MAP
+
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+}
+
+void
+RegisterErrorTable()
+{
+ // Note that these error strings are not localizable.
+ // When these strings change, update the localization information too.
+ static const PRErrorMessage ErrorTableText[] = {
+ { "MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE",
+ "The server uses key pinning (HPKP) but no trusted certificate chain "
+ "could be constructed that matches the pinset. Key pinning violations "
+ "cannot be overridden." },
+ { "MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY",
+ "The server uses a certificate with a basic constraints extension "
+ "identifying it as a certificate authority. For a properly-issued "
+ "certificate, this should not be the case." },
+ { "MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE",
+ "The server presented a certificate with a key size that is too small "
+ "to establish a secure connection." },
+ { "MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA",
+ "An X.509 version 1 certificate that is not a trust anchor was used to "
+ "issue the server's certificate. X.509 version 1 certificates are "
+ "deprecated and should not be used to sign other certificates." },
+ { "MOZILLA_PKIX_ERROR_NO_RFC822NAME_MATCH",
+ "The certificate is not valid for the given email address." },
+ { "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE",
+ "The server presented a certificate that is not yet valid." },
+ { "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE",
+ "A certificate that is not yet valid was used to issue the server's "
+ "certificate." },
+ { "MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH",
+ "The signature algorithm in the signature field of the certificate does "
+ "not match the algorithm in its signatureAlgorithm field." },
+ { "MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING",
+ "The OCSP response does not include a status for the certificate being "
+ "verified." },
+ { "MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG",
+ "The server presented a certificate that is valid for too long." },
+ { "MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING",
+ "A required TLS feature is missing." },
+ { "MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING",
+ "The server presented a certificate that contains an invalid encoding of "
+ "an integer. Common causes include negative serial numbers, negative RSA "
+ "moduli, and encodings that are longer than necessary." },
+ { "MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME",
+ "The server presented a certificate with an empty issuer distinguished "
+ "name." },
+ { "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/lib/mozpkix/lib/pkixocsp.cpp b/lib/mozpkix/lib/pkixocsp.cpp
new file mode 100644
index 000000000..db53c01b4
--- /dev/null
+++ b/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 "pkix/pkix.h"
+#include "pkixcheck.h"
+#include "pkixutil.h"
+
+namespace {
+
+const size_t SHA1_DIGEST_LENGTH = 160 / 8;
+
+} // namespace
+
+namespace mozilla { namespace pkix {
+
+// These values correspond to the tag values in the ASN.1 CertStatus
+enum class CertStatus : uint8_t {
+ Good = der::CONTEXT_SPECIFIC | 0,
+ Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ Unknown = der::CONTEXT_SPECIFIC | 2
+};
+
+class Context final
+{
+public:
+ Context(TrustDomain& 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/lib/mozpkix/lib/pkixresult.cpp b/lib/mozpkix/lib/pkixresult.cpp
new file mode 100644
index 000000000..670642de8
--- /dev/null
+++ b/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 "pkix/Result.h"
+#include "pkixutil.h"
+
+namespace mozilla { namespace pkix {
+
+const char*
+MapResultToName(Result result)
+{
+ switch (result)
+ {
+#define MOZILLA_PKIX_MAP(mozilla_pkix_result, value, nss_result) \
+ case Result::mozilla_pkix_result: return "Result::" #mozilla_pkix_result;
+
+ MOZILLA_PKIX_MAP_LIST
+
+#undef MOZILLA_PKIX_MAP
+
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+}
+
+} } // namespace mozilla::pkix
diff --git a/lib/mozpkix/lib/pkixtime.cpp b/lib/mozpkix/lib/pkixtime.cpp
new file mode 100644
index 000000000..ace23dd41
--- /dev/null
+++ b/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 "pkix/Time.h"
+#include "pkixutil.h"
+
+#ifdef WIN32
+#ifdef _MSC_VER
+#pragma warning(push, 3)
+#endif
+#include "windows.h"
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+#else
+#include "sys/time.h"
+#endif
+
+namespace mozilla { namespace pkix {
+
+Time
+Now()
+{
+ uint64_t seconds;
+
+#ifdef WIN32
+ // "Contains a 64-bit value representing the number of 100-nanosecond
+ // intervals since January 1, 1601 (UTC)."
+ // - http://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
+ FILETIME ft;
+ GetSystemTimeAsFileTime(&ft);
+ uint64_t ft64 = (static_cast<uint64_t>(ft.dwHighDateTime) << 32) |
+ ft.dwLowDateTime;
+ seconds = (DaysBeforeYear(1601) * Time::ONE_DAY_IN_SECONDS) +
+ ft64 / (1000u * 1000u * 1000u / 100u);
+#else
+ // "The gettimeofday() function shall obtain the current time, expressed as
+ // seconds and microseconds since the Epoch."
+ // - http://pubs.opengroup.org/onlinepubs/009695399/functions/gettimeofday.html
+ timeval tv;
+ (void) gettimeofday(&tv, nullptr);
+ seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) +
+ static_cast<uint64_t>(tv.tv_sec);
+#endif
+
+ return TimeFromElapsedSecondsAD(seconds);
+}
+
+Time
+TimeFromEpochInSeconds(uint64_t secondsSinceEpoch)
+{
+ uint64_t seconds = (DaysBeforeYear(1970) * Time::ONE_DAY_IN_SECONDS) +
+ secondsSinceEpoch;
+ return TimeFromElapsedSecondsAD(seconds);
+}
+
+} } // namespace mozilla::pkix
diff --git a/lib/mozpkix/lib/pkixutil.h b/lib/mozpkix/lib/pkixutil.h
new file mode 100644
index 000000000..0923802f7
--- /dev/null
+++ b/lib/mozpkix/lib/pkixutil.h
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_pkix_pkixutil_h
+#define mozilla_pkix_pkixutil_h
+
+#include "pkixder.h"
+
+namespace mozilla { namespace pkix {
+
+// During path building and verification, we build a linked list of BackCerts
+// from the current cert toward the end-entity certificate. The linked list
+// is used to verify properties that aren't local to the current certificate
+// and/or the direct link between the current certificate and its issuer,
+// such as name constraints.
+//
+// Each BackCert contains pointers to all the given certificate's extensions
+// so that we can parse the extension block once and then process the
+// extensions in an order that may be different than they appear in the cert.
+class BackCert final
+{
+public:
+ // certDER and childCert must be valid for the lifetime of BackCert.
+ BackCert(Input 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/lib/mozpkix/lib/pkixverify.cpp b/lib/mozpkix/lib/pkixverify.cpp
new file mode 100644
index 000000000..45e2f8b08
--- /dev/null
+++ b/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 "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/lib/mozpkix/moz.build b/lib/mozpkix/moz.build
new file mode 100644
index 000000000..ec02dcd01
--- /dev/null
+++ b/lib/mozpkix/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Security: PSM")
+
+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',
+]
+
+LOCAL_INCLUDES += [
+ 'include',
+]
+
+TEST_DIRS += [
+ 'test/gtest',
+ 'test/lib',
+]
+
+include('warnings.mozbuild')
+
+Library('mozillapkix')
+
+FINAL_LIBRARY = 'xul'
diff --git a/lib/mozpkix/test/gtest/README.txt b/lib/mozpkix/test/gtest/README.txt
new file mode 100644
index 000000000..5d3484a21
--- /dev/null
+++ b/lib/mozpkix/test/gtest/README.txt
@@ -0,0 +1,61 @@
+-------------
+Running Tests
+-------------
+
+Because of the rules below, you can run all the unit tests in this directory,
+and only these tests, with:
+
+ mach gtest "pkix*"
+
+You can run just the tests for functions defined in filename pkixfoo.cpp with:
+
+ mach gtest "pkixfoo*"
+
+If you run "mach gtest" then you'll end up running every gtest in Gecko.
+
+
+
+------------
+Naming Files
+------------
+
+Name files containing tests according to one of the following patterns:
+
+ * <filename>_tests.cpp
+ * <filename>_<Function>_tests.cpp
+ * <filename>_<category>_tests.cpp
+
+ <filename> is the name of the file containing the definitions of the
+ function(s) being tested by every test.
+ <Function> is the name of the function that is being tested by every
+ test.
+ <category> describes the group of related functions that are being
+ tested by every test.
+
+
+
+------------------------------------------------
+Always Use a Fixture Class: TEST_F(), not TEST()
+------------------------------------------------
+
+Many tests don't technically need a fixture, and so TEST() could technically
+be used to define the test. However, when you use TEST_F() instead of TEST(),
+the compiler will not allow you to make any typos in the test case name, but
+if you use TEST() then the name of the test case is not checked.
+
+See https://code.google.com/p/googletest/wiki/Primer#Test_Fixtures:_Using_the_Same_Data_Configuration_for_Multiple_Te
+to learn more about test fixtures.
+
+---------------
+Naming Fixtures
+---------------
+
+When all tests in a file use the same fixture, use the base name of the file
+without the "_tests" suffix as the name of the fixture class; e.g. tests in
+"pkixocsp.cpp" should use a fixture "class pkixocsp" by default.
+
+Sometimes tests in a file need separate fixtures. In this case, name the
+fixture class according to the pattern <fixture_base>_<fixture_suffix>, where
+<fixture_base> is the base name of the file without the "_tests" suffix, and
+<fixture_suffix> is a descriptive name for the fixture class, e.g.
+"class pkixocsp_DelegatedResponder".
diff --git a/lib/mozpkix/test/gtest/moz.build b/lib/mozpkix/test/gtest/moz.build
new file mode 100644
index 000000000..d9fa10e60
--- /dev/null
+++ b/lib/mozpkix/test/gtest/moz.build
@@ -0,0 +1,72 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+SOURCES += [
+ 'pkixbuild_tests.cpp',
+ 'pkixcert_extension_tests.cpp',
+ 'pkixcert_signature_algorithm_tests.cpp',
+ 'pkixcheck_CheckExtendedKeyUsage_tests.cpp',
+ 'pkixcheck_CheckIssuer_tests.cpp',
+ 'pkixcheck_CheckKeyUsage_tests.cpp',
+ 'pkixcheck_CheckSignatureAlgorithm_tests.cpp',
+ 'pkixcheck_CheckValidity_tests.cpp',
+ 'pkixcheck_ParseValidity_tests.cpp',
+ 'pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp',
+
+ # The naming conventions are described in ./README.txt.
+
+ 'pkixder_input_tests.cpp',
+ 'pkixder_pki_types_tests.cpp',
+ 'pkixder_universal_types_tests.cpp',
+ 'pkixgtest.cpp',
+ 'pkixnames_tests.cpp',
+ 'pkixocsp_CreateEncodedOCSPRequest_tests.cpp',
+ 'pkixocsp_VerifyEncodedOCSPResponse.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../../include',
+ '../../lib',
+ '../lib',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+include('../../warnings.mozbuild')
+
+# GTest uses a variadic macro in a questionable way and it doesn't seem to be
+# possible to selectively disable just that error when -pedantic-errors is set.
+if CONFIG['CC_TYPE'] == 'gcc':
+ CXXFLAGS.remove('-pedantic-errors')
+
+# These warnings are disabled in order to minimize the amount of boilerplate
+# required to implement tests, and/or because they originate in the GTest
+# framework in a way we cannot otherwise work around.
+if CONFIG['CC_TYPE'] in ('clang', 'clang-cl', 'gcc'):
+ CXXFLAGS += [
+ '-Wno-old-style-cast',
+ ]
+ if CONFIG['CC_TYPE'] in ('clang', 'clang-cl'):
+ CXXFLAGS += [
+ '-Wno-exit-time-destructors',
+ '-Wno-global-constructors',
+ '-Wno-thread-safety',
+ '-Wno-used-but-marked-unused',
+ '-Wno-zero-as-null-pointer-constant',
+ ]
+elif CONFIG['CC_TYPE'] == 'msvc':
+ CXXFLAGS += [
+ '-wd4350', # behavior change: 'std::_Wrap_alloc<std::allocator<_Ty>>::...
+ '-wd4275', # non dll-interface class used as base for dll-interface class
+ '-wd4548', # Expression before comma has no effect
+ '-wd4625', # copy constructor could not be generated.
+ '-wd4626', # assugment operator could not be generated.
+ '-wd4640', # construction of local static object is not thread safe.
+
+ # This is intended as a temporary hack to support building with VS2015.
+ # declaration of '*' hides class member
+ '-wd4458',
+ ]
diff --git a/lib/mozpkix/test/gtest/pkixbuild_tests.cpp b/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
new file mode 100644
index 000000000..866f97fed
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
@@ -0,0 +1,893 @@
+/* -*- 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+// When building with -D_HAS_EXCEPTIONS=0, MSVC's <xtree> header triggers
+// warning C4702: unreachable code.
+// https://connect.microsoft.com/VisualStudio/feedback/details/809962
+#pragma warning(push)
+#pragma warning(disable: 4702)
+#endif
+
+#include <map>
+#include <vector>
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#pragma warning(pop)
+#endif
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static ByteString
+CreateCert(const char* issuerCN, // null means "empty name"
+ const char* subjectCN, // null means "empty name"
+ EndEntityOrCA endEntityOrCA,
+ /*optional modified*/ std::map<ByteString, ByteString>*
+ subjectDERToCertDER = nullptr,
+ /*optional*/ const ByteString* extension = nullptr,
+ /*optional*/ const TestKeyPair* issuerKeyPair = nullptr,
+ /*optional*/ const TestKeyPair* subjectKeyPair = nullptr)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString()));
+ ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString()));
+
+ std::vector<ByteString> extensions;
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ ByteString basicConstraints =
+ CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(basicConstraints));
+ extensions.push_back(basicConstraints);
+ }
+ if (extension) {
+ extensions.push_back(*extension);
+ }
+ extensions.push_back(ByteString()); // marks the end of the list
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow, subjectDER,
+ subjectKeyPair ? *subjectKeyPair : *reusedKey,
+ extensions.data(),
+ issuerKeyPair ? *issuerKeyPair : *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ if (subjectDERToCertDER) {
+ (*subjectDERToCertDER)[subjectDER] = certDER;
+ }
+
+ return certDER;
+}
+
+class TestTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ // The "cert chain tail" is a longish chain of certificates that is used by
+ // all of the tests here. We share this chain across all the tests in order
+ // to speed up the tests (generating keypairs for the certs is very slow).
+ bool SetUpCertChainTail()
+ {
+ static char const* const names[] = {
+ "CA1 (Root)", "CA2", "CA3", "CA4", "CA5", "CA6", "CA7"
+ };
+
+ for (size_t i = 0; i < MOZILLA_PKIX_ARRAY_LENGTH(names); ++i) {
+ const char* issuerName = i == 0 ? names[0] : names[i-1];
+ CreateCACert(issuerName, names[i]);
+ if (i == 0) {
+ rootCACertDER = leafCACertDER;
+ }
+ }
+
+ return true;
+ }
+
+ void CreateCACert(const char* issuerName, const char* subjectName)
+ {
+ leafCACertDER = CreateCert(issuerName, subjectName,
+ EndEntityOrCA::MustBeCA, &subjectDERToCertDER);
+ assert(!ENCODING_FAILED(leafCACertDER));
+ }
+
+ ByteString GetLeafCACertDER() const { return leafCACertDER; }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
+ override
+ {
+ ByteString subjectDER(InputToByteString(encodedIssuerName));
+ ByteString certDER(subjectDERToCertDER[subjectDER]);
+ Input derCert;
+ Result rv = derCert.Init(certDER.data(), certDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/,
+ keepGoing);
+ if (rv != Success) {
+ return rv;
+ }
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ std::map<ByteString, ByteString> subjectDERToCertDER;
+ ByteString leafCACertDER;
+ ByteString rootCACertDER;
+};
+
+class pkixbuild : public ::testing::Test
+{
+public:
+ static void SetUpTestCase()
+ {
+ if (!trustDomain.SetUpCertChainTail()) {
+ abort();
+ }
+ }
+
+protected:
+
+ static TestTrustDomain trustDomain;
+};
+
+/*static*/ TestTrustDomain pkixbuild::trustDomain;
+
+TEST_F(pkixbuild, MaxAcceptableCertChainLength)
+{
+ {
+ ByteString leafCACert(trustDomain.GetLeafCACertDER());
+ Input certDER;
+ ASSERT_EQ(Success, certDER.Init(leafCACert.data(), leafCACert.length()));
+ ASSERT_EQ(Success,
+ BuildCertChain(trustDomain, certDER, Now(),
+ EndEntityOrCA::MustBeCA,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+
+ {
+ ByteString certDER(CreateCert("CA7", "Direct End-Entity",
+ EndEntityOrCA::MustBeEndEntity));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Success,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+}
+
+TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength)
+{
+ static char const* const caCertName = "CA Too Far";
+
+ trustDomain.CreateCACert("CA7", caCertName);
+
+ {
+ ByteString certDER(trustDomain.GetLeafCACertDER());
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeCA,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+
+ {
+ ByteString certDER(CreateCert(caCertName, "End-Entity Too Far",
+ EndEntityOrCA::MustBeEndEntity));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+}
+
+// A TrustDomain that checks certificates against a given root certificate.
+// It is initialized with the DER encoding of a root certificate that
+// is treated as a trust anchor and is assumed to have issued all certificates
+// (i.e. FindIssuer always attempts to build the next step in the chain with
+// it).
+class SingleRootTrustDomain : public DefaultCryptoTrustDomain
+{
+public:
+ explicit SingleRootTrustDomain(ByteString aRootDER)
+ : rootDER(aRootDER)
+ {
+ }
+
+ // The CertPolicyId argument is unused because we don't care about EV.
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ Input rootCert;
+ Result rv = rootCert.Init(rootDER.data(), rootDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ if (InputsAreEqual(candidateCert, rootCert)) {
+ trustLevel = TrustLevel::TrustAnchor;
+ } else {
+ trustLevel = TrustLevel::InheritsTrust;
+ }
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ // keepGoing is an out parameter from IssuerChecker.Check. It would tell us
+ // whether or not to continue attempting other potential issuers. We only
+ // know of one potential issuer, however, so we ignore it.
+ bool keepGoing;
+ Input rootCert;
+ Result rv = rootCert.Init(rootDER.data(), rootDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ return checker.Check(rootCert, nullptr, keepGoing);
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+private:
+ ByteString rootDER;
+};
+
+// A TrustDomain that explicitly fails if CheckRevocation is called.
+class ExpiredCertTrustDomain final : public SingleRootTrustDomain
+{
+public:
+ explicit ExpiredCertTrustDomain(ByteString aRootDER)
+ : SingleRootTrustDomain(aRootDER)
+ {
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRevocation should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+};
+
+TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert)
+{
+ const char* rootCN = "Root CA";
+ ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA,
+ nullptr));
+ EXPECT_FALSE(ENCODING_FAILED(rootDER));
+ ExpiredCertTrustDomain expiredCertTrustDomain(rootDER);
+
+ ByteString serialNumber(CreateEncodedSerialNumber(100));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+ ByteString issuerDER(CNToDERName(rootCN));
+ ByteString subjectDER(CNToDERName("Expired End-Entity Cert"));
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(),
+ serialNumber, issuerDER,
+ twoDaysBeforeNow,
+ oneDayBeforeNow,
+ subjectDER, *reusedKey, nullptr, *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ Input cert;
+ ASSERT_EQ(Success, cert.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE,
+ BuildCertChain(expiredCertTrustDomain, cert, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr));
+}
+
+class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain
+{
+public:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+ Input, /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = TrustLevel::TrustAnchor;
+ return Success;
+ }
+};
+
+class pkixbuild_DSS : public ::testing::Test { };
+
+TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted)
+{
+ DSSTrustDomain trustDomain;
+
+ ByteString serialNumber(CreateEncodedSerialNumber(1));
+ ASSERT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString subjectDER(CNToDERName("DSS"));
+ ASSERT_FALSE(ENCODING_FAILED(subjectDER));
+ ScopedTestKeyPair subjectKey(GenerateDSSKeyPair());
+ ASSERT_TRUE(subjectKey.get());
+
+ ByteString issuerDER(CNToDERName("RSA"));
+ ASSERT_FALSE(ENCODING_FAILED(issuerDER));
+ ScopedTestKeyPair issuerKey(CloneReusedKeyPair());
+ ASSERT_TRUE(issuerKey.get());
+
+ ByteString cert(CreateEncodedCertificate(v3, sha256WithRSAEncryption(),
+ serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow,
+ subjectDER, *subjectKey, nullptr,
+ *issuerKey, sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certDER;
+ ASSERT_EQ(Success, certDER.Init(cert.data(), cert.length()));
+
+ ASSERT_EQ(Result::ERROR_UNSUPPORTED_KEYALG,
+ BuildCertChain(trustDomain, certDER, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ IssuerNameCheckTrustDomain(const ByteString& aIssuer, bool aExpectedKeepGoing)
+ : issuer(aIssuer)
+ , expectedKeepGoing(aExpectedKeepGoing)
+ {
+ }
+
+ Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, Input,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = endEntityOrCA == EndEntityOrCA::MustBeCA
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ Input issuerInput;
+ EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
+ bool keepGoing;
+ EXPECT_EQ(Success,
+ checker.Check(issuerInput, nullptr /*additionalNameConstraints*/,
+ keepGoing));
+ EXPECT_EQ(expectedKeepGoing, keepGoing);
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+private:
+ const ByteString issuer;
+ const bool expectedKeepGoing;
+};
+
+struct IssuerNameCheckParams
+{
+ const char* subjectIssuerCN; // null means "empty name"
+ const char* issuerSubjectCN; // null means "empty name"
+ bool matches;
+ Result expectedError;
+};
+
+static const IssuerNameCheckParams ISSUER_NAME_CHECK_PARAMS[] =
+{
+ { "foo", "foo", true, Success },
+ { "foo", "bar", false, Result::ERROR_UNKNOWN_ISSUER },
+ { "f", "foo", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix
+ { "foo", "f", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix
+ { "foo", "Foo", false, Result::ERROR_UNKNOWN_ISSUER }, // case sensitive
+ { "", "", true, Success },
+ { nullptr, nullptr, false, Result::ERROR_EMPTY_ISSUER_NAME }, // empty issuer
+
+ // check that certificate-related errors are deferred and superseded by
+ // ERROR_UNKNOWN_ISSUER when a chain can't be built due to name mismatches
+ { "foo", nullptr, false, Result::ERROR_UNKNOWN_ISSUER },
+ { nullptr, "foo", false, Result::ERROR_UNKNOWN_ISSUER }
+};
+
+class pkixbuild_IssuerNameCheck
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IssuerNameCheckParams>
+{
+};
+
+TEST_P(pkixbuild_IssuerNameCheck, MatchingName)
+{
+ const IssuerNameCheckParams& params(GetParam());
+
+ ByteString issuerCertDER(CreateCert(params.issuerSubjectCN,
+ params.issuerSubjectCN,
+ EndEntityOrCA::MustBeCA, nullptr));
+ ASSERT_FALSE(ENCODING_FAILED(issuerCertDER));
+
+ ByteString subjectCertDER(CreateCert(params.subjectIssuerCN, "end-entity",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr));
+ ASSERT_FALSE(ENCODING_FAILED(subjectCertDER));
+
+ Input subjectCertDERInput;
+ ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(),
+ subjectCertDER.length()));
+
+ IssuerNameCheckTrustDomain trustDomain(issuerCertDER, !params.matches);
+ ASSERT_EQ(params.expectedError,
+ BuildCertChain(trustDomain, subjectCertDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck,
+ testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS));
+
+
+// Records the embedded SCT list extension for later examination.
+class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain
+{
+public:
+ explicit EmbeddedSCTListTestTrustDomain(ByteString aRootDER)
+ : SingleRootTrustDomain(aRootDER)
+ {
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) override
+ {
+ if (extension == AuxiliaryExtension::EmbeddedSCTList) {
+ signedCertificateTimestamps = InputToByteString(extensionData);
+ } else {
+ ADD_FAILURE();
+ }
+ }
+
+ ByteString signedCertificateTimestamps;
+};
+
+TEST_F(pkixbuild, CertificateTransparencyExtension)
+{
+ // python security/pkix/tools/DottedOIDToCode.py --tlv
+ // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
+ static const uint8_t tlv_id_embeddedSctList[] = {
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
+ };
+ static const uint8_t dummySctList[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+
+ ByteString ctExtension = TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_embeddedSctList) +
+ Boolean(false) +
+ TLV(der::OCTET_STRING,
+ // SignedCertificateTimestampList structure is encoded as an OCTET STRING
+ // within the X.509v3 extension (see RFC 6962 section 3.3).
+ // pkix decodes it internally and returns the actual structure.
+ TLV(der::OCTET_STRING, BytesToByteString(dummySctList))));
+
+ const char* rootCN = "Root CA";
+ ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA));
+ ASSERT_FALSE(ENCODING_FAILED(rootDER));
+
+ ByteString certDER(CreateCert(rootCN, "Cert with SCT list",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr, /*subjectDERToCertDER*/
+ &ctExtension));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+
+ EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER);
+ ASSERT_EQ(Success,
+ BuildCertChain(extTrustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr /*stapledOCSPResponse*/));
+ ASSERT_EQ(BytesToByteString(dummySctList),
+ extTrustDomain.signedCertificateTimestamps);
+}
+
+// This TrustDomain implements a hierarchy like so:
+//
+// A B
+// | |
+// C D
+// \ /
+// E
+//
+// where A is a trust anchor, B is not a trust anchor and has no known issuer, C
+// and D are intermediates with the same subject and subject public key, and E
+// is an end-entity (in practice, the end-entity will be generated by the test
+// functions using this trust domain).
+class MultiplePathTrustDomain: public DefaultCryptoTrustDomain
+{
+public:
+ void SetUpCerts()
+ {
+ ASSERT_FALSE(ENCODING_FAILED(CreateCert("UntrustedRoot", "UntrustedRoot",
+ EndEntityOrCA::MustBeCA,
+ &subjectDERToCertDER)));
+ // The subject DER -> cert DER mapping would be overwritten for subject
+ // "Intermediate" when we create the second "Intermediate" certificate, so
+ // we keep a copy of this "Intermediate".
+ intermediateSignedByUntrustedRootCertDER =
+ CreateCert("UntrustedRoot", "Intermediate", EndEntityOrCA::MustBeCA);
+ ASSERT_FALSE(ENCODING_FAILED(intermediateSignedByUntrustedRootCertDER));
+ rootCACertDER = CreateCert("TrustedRoot", "TrustedRoot",
+ EndEntityOrCA::MustBeCA, &subjectDERToCertDER);
+ ASSERT_FALSE(ENCODING_FAILED(rootCACertDER));
+ ASSERT_FALSE(ENCODING_FAILED(CreateCert("TrustedRoot", "Intermediate",
+ EndEntityOrCA::MustBeCA,
+ &subjectDERToCertDER)));
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result CheckCert(ByteString& certDER, IssuerChecker& checker, bool& keepGoing)
+ {
+ Input derCert;
+ Result rv = derCert.Init(certDER.data(), certDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ return checker.Check(derCert, nullptr/*additionalNameConstraints*/,
+ keepGoing);
+ }
+
+ Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
+ override
+ {
+ ByteString subjectDER(InputToByteString(encodedIssuerName));
+ ByteString certDER(subjectDERToCertDER[subjectDER]);
+ assert(!ENCODING_FAILED(certDER));
+ bool keepGoing;
+ Result rv = CheckCert(certDER, checker, keepGoing);
+ if (rv != Success) {
+ return rv;
+ }
+ // Also try the other intermediate.
+ if (keepGoing) {
+ rv = CheckCert(intermediateSignedByUntrustedRootCertDER, checker,
+ keepGoing);
+ if (rv != Success) {
+ return rv;
+ }
+ }
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ std::map<ByteString, ByteString> subjectDERToCertDER;
+ ByteString rootCACertDER;
+ ByteString intermediateSignedByUntrustedRootCertDER;
+};
+
+TEST_F(pkixbuild, BadEmbeddedSCTWithMultiplePaths)
+{
+ MultiplePathTrustDomain trustDomain;
+ trustDomain.SetUpCerts();
+
+ // python security/pkix/tools/DottedOIDToCode.py --tlv
+ // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
+ static const uint8_t tlv_id_embeddedSctList[] = {
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
+ };
+ static const uint8_t dummySctList[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+ ByteString ctExtension = TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_embeddedSctList) +
+ Boolean(false) +
+ // The contents of the OCTET STRING are supposed to consist of an OCTET
+ // STRING of useful data. We're testing what happens if it isn't, so shove
+ // some bogus (non-OCTET STRING) data in there.
+ TLV(der::OCTET_STRING, BytesToByteString(dummySctList)));
+ ByteString certDER(CreateCert("Intermediate", "Cert with bogus SCT list",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr, /*subjectDERToCertDER*/
+ &ctExtension));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+// Same as a MultiplePathTrustDomain, but the end-entity is revoked.
+class RevokedEndEntityTrustDomain final : public MultiplePathTrustDomain
+{
+public:
+ Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&, Time,
+ Duration, /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
+ return Result::ERROR_REVOKED_CERTIFICATE;
+ }
+ return Success;
+ }
+};
+
+TEST_F(pkixbuild, RevokedEndEntityWithMultiplePaths)
+{
+ RevokedEndEntityTrustDomain trustDomain;
+ trustDomain.SetUpCerts();
+ ByteString certDER(CreateCert("Intermediate", "RevokedEndEntity",
+ EndEntityOrCA::MustBeEndEntity));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+// This represents a collection of different certificates that all have the same
+// subject and issuer distinguished name.
+class SelfIssuedCertificatesTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ void SetUpCerts(size_t totalCerts)
+ {
+ ASSERT_TRUE(totalCerts > 0);
+ // First we generate a trust anchor.
+ ScopedTestKeyPair rootKeyPair(GenerateKeyPair());
+ rootCACertDER = CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
+ nullptr, rootKeyPair.get(), rootKeyPair.get());
+ ASSERT_FALSE(ENCODING_FAILED(rootCACertDER));
+ certs.push_back(rootCACertDER);
+ ScopedTestKeyPair issuerKeyPair(rootKeyPair.release());
+ size_t subCAsGenerated;
+ // Then we generate 6 sub-CAs (given that we were requested to generate at
+ // least that many).
+ for (subCAsGenerated = 0;
+ subCAsGenerated < totalCerts - 1 && subCAsGenerated < 6;
+ subCAsGenerated++) {
+ // Each certificate has to have a unique SPKI (mozilla::pkix does loop
+ // detection and stops searching if it encounters two certificates in a
+ // path with the same subject and SPKI).
+ ScopedTestKeyPair keyPair(GenerateKeyPair());
+ ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
+ nullptr, issuerKeyPair.get(), keyPair.get()));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ certs.push_back(cert);
+ issuerKeyPair.reset(keyPair.release());
+ }
+ // We set firstIssuerKey here because we can't end up with a path that has
+ // more than 7 CAs in it (because mozilla::pkix limits the path length).
+ firstIssuerKey.reset(issuerKeyPair.release());
+ // For any more sub CAs we generate, it doesn't matter what their keys are
+ // as long as they're different.
+ for (; subCAsGenerated < totalCerts - 1; subCAsGenerated++) {
+ ScopedTestKeyPair keyPair(GenerateKeyPair());
+ ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
+ nullptr, keyPair.get(), keyPair.get()));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ certs.insert(certs.begin(), cert);
+ }
+ }
+
+ const TestKeyPair* GetFirstIssuerKey()
+ {
+ return firstIssuerKey.get();
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ bool keepGoing;
+ for (auto& cert: certs) {
+ Input certInput;
+ Result rv = certInput.Init(cert.data(), cert.length());
+ if (rv != Success) {
+ return rv;
+ }
+ rv = checker.Check(certInput, nullptr, keepGoing);
+ if (rv != Success || !keepGoing) {
+ return rv;
+ }
+ }
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ std::vector<ByteString> certs;
+ ByteString rootCACertDER;
+ ScopedTestKeyPair firstIssuerKey;
+};
+
+TEST_F(pkixbuild, AvoidUnboundedPathSearchingFailure)
+{
+ SelfIssuedCertificatesTrustDomain trustDomain;
+ // This creates a few hundred million potential paths of length 8 (end entity
+ // + 6 sub-CAs + root). It would be prohibitively expensive to enumerate all
+ // of these, so we give mozilla::pkix a budget that is spent when searching
+ // paths. If the budget is exhausted, it simply returns an unknown issuer
+ // error. In the future it might be nice to return a specific error that would
+ // give the front-end a hint that maybe it shouldn't have so many certificates
+ // that all have the same subject and issuer DN but different SPKIs.
+ trustDomain.SetUpCerts(18);
+ ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity,
+ nullptr, nullptr,
+ trustDomain.GetFirstIssuerKey()));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+TEST_F(pkixbuild, AvoidUnboundedPathSearchingSuccess)
+{
+ SelfIssuedCertificatesTrustDomain trustDomain;
+ // This creates a few hundred thousand possible potential paths of length 8
+ // (end entity + 6 sub-CAs + root). This will nearly exhaust mozilla::pkix's
+ // search budget, so this should succeed.
+ trustDomain.SetUpCerts(10);
+ ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity,
+ nullptr, nullptr,
+ trustDomain.GetFirstIssuerKey()));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Success,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
diff --git a/lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp b/lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp
new file mode 100644
index 000000000..b2cdb296e
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcert_extension_tests.cpp
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+#include "pkixtestutil.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+// Creates a self-signed certificate with the given extension.
+static ByteString
+CreateCertWithExtensions(const char* subjectCN,
+ const ByteString* extensions)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+ ByteString issuerDER(CNToDERName(subjectCN));
+ EXPECT_FALSE(ENCODING_FAILED(issuerDER));
+ ByteString subjectDER(CNToDERName(subjectCN));
+ EXPECT_FALSE(ENCODING_FAILED(subjectDER));
+ ScopedTestKeyPair subjectKey(CloneReusedKeyPair());
+ return CreateEncodedCertificate(v3, sha256WithRSAEncryption(),
+ serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow,
+ subjectDER, *subjectKey, extensions,
+ *subjectKey,
+ sha256WithRSAEncryption());
+}
+
+// Creates a self-signed certificate with the given extension.
+static ByteString
+CreateCertWithOneExtension(const char* subjectStr, const ByteString& extension)
+{
+ const ByteString extensions[] = { extension, ByteString() };
+ return CreateCertWithExtensions(subjectStr, extensions);
+}
+
+class TrustEverythingTrustDomain final : public DefaultCryptoTrustDomain
+{
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = TrustLevel::TrustAnchor;
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+};
+
+// python DottedOIDToCode.py --tlv unknownExtensionOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
+static const uint8_t tlv_unknownExtensionOID[] = {
+ 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a,
+ 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03
+};
+
+// python DottedOIDToCode.py --tlv id-pe-authorityInformationAccess 1.3.6.1.5.5.7.1.1
+static const uint8_t tlv_id_pe_authorityInformationAccess[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
+};
+
+// python DottedOIDToCode.py --tlv wrongExtensionOID 1.3.6.6.1.5.5.7.1.1
+// (there is an extra "6" that shouldn't be in this OID)
+static const uint8_t tlv_wrongExtensionOID[] = {
+ 0x06, 0x09, 0x2b, 0x06, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
+};
+
+// python DottedOIDToCode.py --tlv id-ce-unknown 2.5.29.55
+// (this is a made-up OID for testing "id-ce"-prefixed OIDs that mozilla::pkix
+// doesn't handle)
+static const uint8_t tlv_id_ce_unknown[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x37
+};
+
+// python DottedOIDToCode.py --tlv id-ce-inhibitAnyPolicy 2.5.29.54
+static const uint8_t tlv_id_ce_inhibitAnyPolicy[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x36
+};
+
+// python DottedOIDToCode.py --tlv id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
+static const uint8_t tlv_id_pkix_ocsp_nocheck[] = {
+ 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
+};
+
+struct ExtensionTestcase
+{
+ ByteString extension;
+ Result expectedResult;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const ExtensionTestcase&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+static const ExtensionTestcase EXTENSION_TESTCASES[] =
+{
+ // Tests that a non-critical extension not in the id-ce or id-pe arcs (which
+ // is thus unknown to us) verifies successfully even if empty (extensions we
+ // know about aren't normally allowed to be empty).
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_unknownExtensionOID) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Success
+ },
+
+ // Tests that a critical extension not in the id-ce or id-pe arcs (which is
+ // thus unknown to us) is detected and that verification fails with the
+ // appropriate error.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_unknownExtensionOID) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
+ },
+
+ // Tests that a id-pe-authorityInformationAccess critical extension
+ // is detected and that verification succeeds.
+ // XXX: According to RFC 5280 an AIA that consists of an empty sequence is
+ // not legal, but we accept it and that is not what we're testing here.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_pe_authorityInformationAccess) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ByteString()))),
+ Success
+ },
+
+ // Tests that an incorrect OID for id-pe-authorityInformationAccess
+ // (when marked critical) is detected and that verification fails.
+ // (Until bug 1020993 was fixed, this wrong value was used for
+ // id-pe-authorityInformationAccess.)
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_wrongExtensionOID) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
+ },
+
+ // We know about some id-ce extensions (OID arc 2.5.29), but not all of them.
+ // Tests that an unknown id-ce extension is detected and that verification
+ // fails.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_unknown) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
+ },
+
+ // Tests that a certificate with a known critical id-ce extension (in this
+ // case, OID 2.5.29.54, which is id-ce-inhibitAnyPolicy), verifies
+ // successfully.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_inhibitAnyPolicy) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, Integer(0))),
+ Success
+ },
+
+ // Tests that a certificate with the id-pkix-ocsp-nocheck extension (marked
+ // critical) verifies successfully.
+ // RFC 6960:
+ // ext-ocsp-nocheck EXTENSION ::= { SYNTAX NULL IDENTIFIED
+ // BY id-pkix-ocsp-nocheck }
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_pkix_ocsp_nocheck) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, TLV(der::NULLTag, ByteString()))),
+ Success
+ },
+
+ // Tests that a certificate with another representation of the
+ // id-pkix-ocsp-nocheck extension (marked critical) verifies successfully.
+ // According to http://comments.gmane.org/gmane.ietf.x509/30947,
+ // some code creates certificates where value of the extension is
+ // an empty OCTET STRING.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_pkix_ocsp_nocheck) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Success
+ },
+};
+
+class pkixcert_extension
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ExtensionTestcase>
+{
+protected:
+ static TrustEverythingTrustDomain trustDomain;
+};
+
+/*static*/ TrustEverythingTrustDomain pkixcert_extension::trustDomain;
+
+TEST_P(pkixcert_extension, ExtensionHandledProperly)
+{
+ const ExtensionTestcase& testcase(GetParam());
+ const char* cn = "Cert Extension Test";
+ ByteString cert(CreateCertWithOneExtension(cn, testcase.extension));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+ ASSERT_EQ(testcase.expectedResult,
+ BuildCertChain(trustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixcert_extension,
+ pkixcert_extension,
+ testing::ValuesIn(EXTENSION_TESTCASES));
+
+// Two subjectAltNames must result in an error.
+TEST_F(pkixcert_extension, DuplicateSubjectAltName)
+{
+ // 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 subjectAltName(
+ TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_subjectAltName) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, DNSName("example.com")))));
+ static const ByteString extensions[] = { subjectAltName, subjectAltName,
+ ByteString() };
+ static const char* certCN = "Cert With Duplicate subjectAltName";
+ ByteString cert(CreateCertWithExtensions(certCN, extensions));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+ ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
+ BuildCertChain(trustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
diff --git a/lib/mozpkix/test/gtest/pkixcert_signature_algorithm_tests.cpp b/lib/mozpkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
new file mode 100644
index 000000000..c22b5e260
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
@@ -0,0 +1,258 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static ByteString
+CreateCert(const char* issuerCN,
+ const char* subjectCN,
+ EndEntityOrCA endEntityOrCA,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*out*/ ByteString& subjectDER)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(CNToDERName(issuerCN));
+ EXPECT_FALSE(ENCODING_FAILED(issuerDER));
+ subjectDER = CNToDERName(subjectCN);
+ EXPECT_FALSE(ENCODING_FAILED(subjectDER));
+
+ ByteString extensions[2];
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ extensions[0] =
+ CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ }
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(v3, signatureAlgorithm,
+ serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow,
+ subjectDER, *reusedKey,
+ extensions, *reusedKey,
+ signatureAlgorithm));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+ return certDER;
+}
+
+class AlgorithmTestsTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ AlgorithmTestsTrustDomain(const ByteString& aRootDER,
+ const ByteString& aRootSubjectDER,
+ /*optional*/ const ByteString& aIntDER,
+ /*optional*/ const ByteString& aIntSubjectDER)
+ : rootDER(aRootDER)
+ , rootSubjectDER(aRootSubjectDER)
+ , intDER(aIntDER)
+ , intSubjectDER(aIntSubjectDER)
+ {
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ if (InputEqualsByteString(candidateCert, rootDER)) {
+ trustLevel = TrustLevel::TrustAnchor;
+ } else {
+ trustLevel = TrustLevel::InheritsTrust;
+ }
+ return Success;
+ }
+
+ Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
+ override
+ {
+ ByteString* issuerDER = nullptr;
+ if (InputEqualsByteString(encodedIssuerName, rootSubjectDER)) {
+ issuerDER = &rootDER;
+ } else if (InputEqualsByteString(encodedIssuerName, intSubjectDER)) {
+ issuerDER = &intDER;
+ } else {
+ // FindIssuer just returns success if it can't find a potential issuer.
+ return Success;
+ }
+ Input issuerCert;
+ Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length());
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ return checker.Check(issuerCert, nullptr, keepGoing);
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ const Input*, const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ ByteString rootDER;
+ ByteString rootSubjectDER;
+ ByteString intDER;
+ ByteString intSubjectDER;
+};
+
+static const TestSignatureAlgorithm NO_INTERMEDIATE
+{
+ TestPublicKeyAlgorithm(ByteString()),
+ TestDigestAlgorithmID::MD2,
+ ByteString(),
+ false
+};
+
+struct ChainValidity final
+{
+ ChainValidity(const TestSignatureAlgorithm& aEndEntitySignatureAlgorithm,
+ const TestSignatureAlgorithm& aOptionalIntSignatureAlgorithm,
+ const TestSignatureAlgorithm& aRootSignatureAlgorithm,
+ bool aIsValid)
+ : endEntitySignatureAlgorithm(aEndEntitySignatureAlgorithm)
+ , optionalIntermediateSignatureAlgorithm(aOptionalIntSignatureAlgorithm)
+ , rootSignatureAlgorithm(aRootSignatureAlgorithm)
+ , isValid(aIsValid)
+ { }
+
+ // In general, a certificate is generated for each of these. However, if
+ // optionalIntermediateSignatureAlgorithm is NO_INTERMEDIATE, then only 2
+ // certificates are generated.
+ // The certificate generated for the given rootSignatureAlgorithm is the
+ // trust anchor.
+ TestSignatureAlgorithm endEntitySignatureAlgorithm;
+ TestSignatureAlgorithm optionalIntermediateSignatureAlgorithm;
+ TestSignatureAlgorithm rootSignatureAlgorithm;
+ bool isValid;
+};
+
+static const ChainValidity CHAIN_VALIDITY[] =
+{
+ // The trust anchor may have a signature with an unsupported signature
+ // algorithm.
+ ChainValidity(sha256WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ md5WithRSAEncryption(),
+ true),
+ ChainValidity(sha256WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ md2WithRSAEncryption(),
+ true),
+
+ // Certificates that are not trust anchors must not have a signature with an
+ // unsupported signature algorithm.
+ ChainValidity(md5WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(md2WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(md2WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ md5WithRSAEncryption(),
+ false),
+ ChainValidity(sha256WithRSAEncryption(),
+ md5WithRSAEncryption(),
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(sha256WithRSAEncryption(),
+ md2WithRSAEncryption(),
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(sha256WithRSAEncryption(),
+ md2WithRSAEncryption(),
+ md5WithRSAEncryption(),
+ false),
+};
+
+class pkixcert_IsValidChainForAlgorithm
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ChainValidity>
+{
+};
+
+::std::ostream& operator<<(::std::ostream& os,
+ const pkixcert_IsValidChainForAlgorithm&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+::std::ostream& operator<<(::std::ostream& os, const ChainValidity&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+TEST_P(pkixcert_IsValidChainForAlgorithm, IsValidChainForAlgorithm)
+{
+ const ChainValidity& chainValidity(GetParam());
+ const char* rootCN = "CN=Root";
+ ByteString rootSubjectDER;
+ ByteString rootEncoded(
+ CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA,
+ chainValidity.rootSignatureAlgorithm, rootSubjectDER));
+ EXPECT_FALSE(ENCODING_FAILED(rootEncoded));
+ EXPECT_FALSE(ENCODING_FAILED(rootSubjectDER));
+
+ const char* issuerCN = rootCN;
+
+ const char* intermediateCN = "CN=Intermediate";
+ ByteString intermediateSubjectDER;
+ ByteString intermediateEncoded;
+
+ // If the the algorithmIdentifier is empty, then it's NO_INTERMEDIATE.
+ if (!chainValidity.optionalIntermediateSignatureAlgorithm
+ .algorithmIdentifier.empty()) {
+ intermediateEncoded =
+ CreateCert(rootCN, intermediateCN, EndEntityOrCA::MustBeCA,
+ chainValidity.optionalIntermediateSignatureAlgorithm,
+ intermediateSubjectDER);
+ EXPECT_FALSE(ENCODING_FAILED(intermediateEncoded));
+ EXPECT_FALSE(ENCODING_FAILED(intermediateSubjectDER));
+ issuerCN = intermediateCN;
+ }
+
+ AlgorithmTestsTrustDomain trustDomain(rootEncoded, rootSubjectDER,
+ intermediateEncoded,
+ intermediateSubjectDER);
+
+ const char* endEntityCN = "CN=End Entity";
+ ByteString endEntitySubjectDER;
+ ByteString endEntityEncoded(
+ CreateCert(issuerCN, endEntityCN, EndEntityOrCA::MustBeEndEntity,
+ chainValidity.endEntitySignatureAlgorithm,
+ endEntitySubjectDER));
+ EXPECT_FALSE(ENCODING_FAILED(endEntityEncoded));
+ EXPECT_FALSE(ENCODING_FAILED(endEntitySubjectDER));
+
+ Input endEntity;
+ ASSERT_EQ(Success, endEntity.Init(endEntityEncoded.data(),
+ endEntityEncoded.length()));
+ Result expectedResult = chainValidity.isValid
+ ? Success
+ : Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+ ASSERT_EQ(expectedResult,
+ BuildCertChain(trustDomain, endEntity, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy, nullptr));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixcert_IsValidChainForAlgorithm,
+ pkixcert_IsValidChainForAlgorithm,
+ testing::ValuesIn(CHAIN_VALIDITY));
diff --git a/lib/mozpkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
new file mode 100644
index 000000000..39a21e4b7
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
@@ -0,0 +1,721 @@
+/* -*- 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 2016 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+#include "pkixutil.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+
+extern Result CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
+ const Input* encodedExtendedKeyUsage,
+ KeyPurposeId requiredEKU,
+ TrustDomain& trustDomain, Time notBefore);
+
+} } // namespace mozilla::pkix
+
+class pkixcheck_CheckExtendedKeyUsage : public ::testing::Test
+{
+protected:
+ DefaultCryptoTrustDomain mTrustDomain;
+};
+
+#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_CERT_TYPE, x)
+
+// tlv_id_kp_OCSPSigning and tlv_id_kp_serverAuth are defined in pkixtestutil.h
+
+// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2
+static const uint8_t tlv_id_kp_clientAuth[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
+};
+
+// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3
+static const uint8_t tlv_id_kp_codeSigning[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03
+};
+
+// python DottedOIDToCode.py --tlv id_kp_emailProtection 1.3.6.1.5.5.7.3.4
+static const uint8_t tlv_id_kp_emailProtection[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04
+};
+
+// python DottedOIDToCode.py --tlv id-Netscape-stepUp 2.16.840.1.113730.4.1
+static const uint8_t tlv_id_Netscape_stepUp[] = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01
+};
+
+// python DottedOIDToCode.py --tlv unknownOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
+static const uint8_t tlv_unknownOID[] = {
+ 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a,
+ 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03
+};
+
+// python DottedOIDToCode.py --tlv anyExtendedKeyUsage 2.5.29.37.0
+static const uint8_t tlv_anyExtendedKeyUsage[] = {
+ 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00
+};
+
+TEST_F(pkixcheck_CheckExtendedKeyUsage, none)
+{
+ // The input Input is nullptr. This means the cert had no extended key usage
+ // extension. This is always valid except for when the certificate is an
+ // end-entity and the required usage is id-kp-OCSPSigning.
+
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::anyExtendedKeyUsage,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::anyExtendedKeyUsage,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_clientAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_clientAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_codeSigning,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_codeSigning,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_emailProtection,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_emailProtection,
+ mTrustDomain, Now()));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyPurposeId::id_kp_OCSPSigning,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_OCSPSigning,
+ mTrustDomain, Now()));
+}
+
+static const Input empty_null;
+
+TEST_F(pkixcheck_CheckExtendedKeyUsage, empty)
+{
+ // The input Input is empty. The cert has an empty extended key usage
+ // extension, which is syntactically invalid.
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+
+ static const uint8_t dummy = 0x00;
+ Input empty_nonnull;
+ ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+}
+
+struct EKUTestcase
+{
+ ByteString ekuSEQUENCE;
+ KeyPurposeId keyPurposeId;
+ Result expectedResultEndEntity;
+ Result expectedResultCA;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const EKUTestcase&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+class CheckExtendedKeyUsageTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<EKUTestcase>
+{
+protected:
+ DefaultCryptoTrustDomain mTrustDomain;
+};
+
+TEST_P(CheckExtendedKeyUsageTest, EKUTestcase)
+{
+ const EKUTestcase& param(GetParam());
+ Input encodedEKU;
+ ASSERT_EQ(Success, encodedEKU.Init(param.ekuSEQUENCE.data(),
+ param.ekuSEQUENCE.length()));
+ ASSERT_EQ(param.expectedResultEndEntity,
+ CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &encodedEKU,
+ param.keyPurposeId,
+ mTrustDomain, Now()));
+ ASSERT_EQ(param.expectedResultCA,
+ CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &encodedEKU,
+ param.keyPurposeId,
+ mTrustDomain, Now()));
+}
+
+#define SINGLE_EKU_SUCCESS(oidBytes, keyPurposeId) \
+ { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
+ Success, Success }
+#define SINGLE_EKU_SUCCESS_CA(oidBytes, keyPurposeId) \
+ { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Success }
+#define SINGLE_EKU_FAILURE(oidBytes, keyPurposeId) \
+ { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE }
+#define DOUBLE_EKU_SUCCESS(oidBytes1, oidBytes2, keyPurposeId) \
+ { TLV(der::SEQUENCE, \
+ BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
+ keyPurposeId, \
+ Success, Success }
+#define DOUBLE_EKU_SUCCESS_CA(oidBytes1, oidBytes2, keyPurposeId) \
+ { TLV(der::SEQUENCE, \
+ BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
+ keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Success }
+#define DOUBLE_EKU_FAILURE(oidBytes1, oidBytes2, keyPurposeId) \
+ { TLV(der::SEQUENCE, \
+ BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
+ keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE }
+
+static const EKUTestcase EKU_TESTCASES[] =
+{
+ SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ // For end-entities, if id-kp-OCSPSigning is present, no usage is allowed
+ // except OCSPSigning.
+ SINGLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ // For compatibility, id-Netscape-stepUp is treated as equivalent to
+ // id-kp-serverAuth for CAs.
+ SINGLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+};
+
+INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage,
+ CheckExtendedKeyUsageTest,
+ ::testing::ValuesIn(EKU_TESTCASES));
+
+struct EKUChainTestcase
+{
+ ByteString ekuExtensionEE;
+ ByteString ekuExtensionCA;
+ KeyPurposeId keyPurposeId;
+ Result expectedResult;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const EKUChainTestcase&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+class CheckExtendedKeyUsageChainTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<EKUChainTestcase>
+{
+};
+
+static ByteString
+CreateCert(const char* issuerCN, const char* subjectCN,
+ EndEntityOrCA endEntityOrCA, ByteString encodedEKU)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(CNToDERName(issuerCN));
+ ByteString subjectDER(CNToDERName(subjectCN));
+
+ ByteString extensions[3];
+ extensions[0] =
+ CreateEncodedBasicConstraints(endEntityOrCA == EndEntityOrCA::MustBeCA,
+ nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ if (encodedEKU.length() > 0) {
+ extensions[1] = encodedEKU;
+ }
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow, subjectDER,
+ *reusedKey, extensions, *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ return certDER;
+}
+
+class EKUTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ explicit EKUTrustDomain(ByteString issuerCertDER)
+ : mIssuerCertDER(issuerCertDER)
+ {
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, mIssuerCertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ Input derCert;
+ Result rv = derCert.Init(mIssuerCertDER.data(), mIssuerCertDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ return checker.Check(derCert, nullptr, keepGoing);
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ const Input*, const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ ByteString mIssuerCertDER;
+};
+
+TEST_P(CheckExtendedKeyUsageChainTest, EKUChainTestcase)
+{
+ const EKUChainTestcase& param(GetParam());
+ ByteString issuerCertDER(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA,
+ param.ekuExtensionCA));
+ ByteString subjectCertDER(CreateCert("CA", "EE",
+ EndEntityOrCA::MustBeEndEntity,
+ param.ekuExtensionEE));
+
+ EKUTrustDomain trustDomain(issuerCertDER);
+
+ Input subjectCertDERInput;
+ ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(),
+ subjectCertDER.length()));
+ ASSERT_EQ(param.expectedResult,
+ BuildCertChain(trustDomain, subjectCertDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ param.keyPurposeId,
+ CertPolicyId::anyPolicy,
+ nullptr));
+}
+
+// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
+static const uint8_t tlv_id_ce_extKeyUsage[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x25
+};
+
+static inline ByteString
+CreateEKUExtension(ByteString ekuOIDs)
+{
+ return TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_extKeyUsage) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs)));
+}
+
+static const EKUChainTestcase EKU_CHAIN_TESTCASES[] =
+{
+ {
+ // Both end-entity and CA have id-kp-serverAuth => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // CA has no EKU extension => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ ByteString(),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // End-entity has no EKU extension => should succeed
+ ByteString(),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // No EKU extensions at all => should succeed
+ ByteString(),
+ ByteString(),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // CA has EKU without id-kp-serverAuth => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity has EKU without id-kp-serverAuth => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // Both end-entity and CA have EKU without id-kp-serverAuth => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity has no EKU, CA doesn't have id-kp-serverAuth => should fail
+ ByteString(),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity doesn't have id-kp-serverAuth, CA has no EKU => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ ByteString(),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // CA has id-Netscape-stepUp => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // End-entity has id-Netscape-stepUp => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity and CA have id-kp-serverAuth and id-kp-clientAuth => should
+ // succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // End-entity has id-kp-serverAuth and id-kp-OCSPSigning => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_OCSPSigning)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // CA has id-kp-serverAuth and id-kp-OCSPSigning => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_OCSPSigning)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+};
+
+INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage,
+ CheckExtendedKeyUsageChainTest,
+ ::testing::ValuesIn(EKU_CHAIN_TESTCASES));
diff --git a/lib/mozpkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp
new file mode 100644
index 000000000..d7fcfb210
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp
@@ -0,0 +1,62 @@
+/* -*- 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 2016 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 "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+class pkixcheck_CheckIssuer : public ::testing::Test { };
+
+static const uint8_t EMPTY_NAME_DATA[] = {
+ 0x30, 0x00 /* tag, length */
+};
+static const Input EMPTY_NAME(EMPTY_NAME_DATA);
+
+static const uint8_t VALID_NAME_DATA[] = {
+ /* From https://www.example.com/: C=US, O=DigiCert Inc, OU=www.digicert.com,
+ * CN=DigiCert SHA2 High Assurance Server CA */
+ 0x30, 0x70, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A,
+ 0x13, 0x0C, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49,
+ 0x6E, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13,
+ 0x10, 0x77, 0x77, 0x77, 0x2E, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
+ 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x31, 0x2F, 0x30, 0x2D, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x26, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+ 0x20, 0x53, 0x48, 0x41, 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
+ 0x73, 0x73, 0x75, 0x72, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x20, 0x43, 0x41
+};
+static const Input VALID_NAME(VALID_NAME_DATA);
+
+TEST_F(pkixcheck_CheckIssuer, ValidIssuer)
+{
+ ASSERT_EQ(Success, CheckIssuer(VALID_NAME));
+}
+
+TEST_F(pkixcheck_CheckIssuer, EmptyIssuer)
+{
+ ASSERT_EQ(Result::ERROR_EMPTY_ISSUER_NAME, CheckIssuer(EMPTY_NAME));
+}
diff --git a/lib/mozpkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp
new file mode 100644
index 000000000..136f8719a
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp
@@ -0,0 +1,284 @@
+/* -*- 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 "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+
+extern Result CheckKeyUsage(EndEntityOrCA endEntityOrCA,
+ const Input* encodedKeyUsage,
+ KeyUsage requiredKeyUsageIfPresent);
+
+} } // namespace mozilla::pkix
+
+class pkixcheck_CheckKeyUsage : public ::testing::Test { };
+
+#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_KEY_USAGE, x)
+
+// Make it easy to define test data for the common, simplest cases.
+#define NAMED_SIMPLE_KU(name, unusedBits, bits) \
+ const uint8_t name##_bytes[4] = { \
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, unusedBits, bits \
+ }; \
+ const Input name(name##_bytes);
+
+static const Input empty_null;
+
+// Note that keyCertSign is really the only interesting case for CA
+// certificates since we don't support cRLSign.
+
+TEST_F(pkixcheck_CheckKeyUsage, EE_none)
+{
+ // The input Input is nullptr. This means the cert had no keyUsage
+ // extension. This is always valid because no key usage in an end-entity
+ // means that there are no key usage restrictions.
+
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::noParticularKeyUsageRequired));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::digitalSignature));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::nonRepudiation));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::keyEncipherment));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::dataEncipherment));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::keyAgreement));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, EE_empty)
+{
+ // The input Input is empty. The cert had an empty keyUsage extension,
+ // which is syntactically invalid.
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
+ KeyUsage::digitalSignature));
+ static const uint8_t dummy = 0x00;
+ Input empty_nonnull;
+ ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
+ KeyUsage::digitalSignature));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, CA_none)
+{
+ // A CA certificate does not have a KU extension.
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, CA_empty)
+{
+ // A CA certificate has an empty KU extension.
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
+ KeyUsage::keyCertSign));
+ static const uint8_t dummy = 0x00;
+ Input empty_nonnull;
+ ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, maxUnusedBits)
+{
+ NAMED_SIMPLE_KU(encoded, 7, 0x80);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &encoded,
+ KeyUsage::digitalSignature));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, tooManyUnusedBits)
+{
+ static uint8_t oneValueByteData[] = {
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 8/*unused bits*/, 0x80
+ };
+ static const Input oneValueByte(oneValueByteData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
+ KeyUsage::digitalSignature));
+
+ static uint8_t twoValueBytesData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 8/*unused bits*/, 0x01, 0x00
+ };
+ static const Input twoValueBytes(twoValueBytesData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
+ KeyUsage::digitalSignature));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_NoPaddingBits)
+{
+ static const uint8_t DER_BYTES[] = {
+ 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 0/*unused bits*/
+ };
+ static const Input DER(DER_BYTES);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_7PaddingBits)
+{
+ static const uint8_t DER_BYTES[] = {
+ 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 7/*unused bits*/
+ };
+ static const Input DER(DER_BYTES);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER,
+ KeyUsage::keyCertSign));
+}
+
+void ASSERT_SimpleCase(uint8_t unusedBits, uint8_t bits, KeyUsage usage)
+{
+ // Test that only the right bit is accepted for the usage for both EE and CA
+ // certs.
+ NAMED_SIMPLE_KU(good, unusedBits, bits);
+ ASSERT_EQ(Success,
+ CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good, usage));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good, usage));
+
+ // We use (~bits >> unusedBits) << unusedBits) instead of using the same
+ // calculation that is in CheckKeyUsage to validate that the calculation in
+ // CheckKeyUsage is correct.
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the single-byte value case.
+ NAMED_SIMPLE_KU(notGood, unusedBits,
+ static_cast<uint8_t>((~bits >> unusedBits) << unusedBits));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &notGood, usage));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &notGood, usage));
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the two-byte value case.
+ const uint8_t twoByteNotGoodData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, unusedBits,
+ static_cast<uint8_t>(~bits),
+ static_cast<uint8_t>((0xFFu >> unusedBits) << unusedBits)
+ };
+ Input twoByteNotGood(twoByteNotGoodData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
+ usage));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood, usage));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, simpleCases)
+{
+ ASSERT_SimpleCase(7, 0x80, KeyUsage::digitalSignature);
+ ASSERT_SimpleCase(6, 0x40, KeyUsage::nonRepudiation);
+ ASSERT_SimpleCase(5, 0x20, KeyUsage::keyEncipherment);
+ ASSERT_SimpleCase(4, 0x10, KeyUsage::dataEncipherment);
+ ASSERT_SimpleCase(3, 0x08, KeyUsage::keyAgreement);
+}
+
+// Only CAs are allowed to assert keyCertSign.
+// End-entity certs may assert it along with other key usages if keyCertSign
+// isn't the required key usage. This is for compatibility.
+TEST_F(pkixcheck_CheckKeyUsage, keyCertSign)
+{
+ NAMED_SIMPLE_KU(good, 2, 0x04);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good,
+ KeyUsage::keyCertSign));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good,
+ KeyUsage::keyCertSign));
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the one-byte value case.
+ NAMED_SIMPLE_KU(notGood, 2, 0xFB);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &notGood,
+ KeyUsage::keyCertSign));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &notGood,
+ KeyUsage::keyCertSign));
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the two-byte value case.
+ static uint8_t twoByteNotGoodData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 2/*unused bits*/, 0xFBu, 0xFCu
+ };
+ static const Input twoByteNotGood(twoByteNotGoodData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
+ KeyUsage::keyCertSign));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood,
+ KeyUsage::keyCertSign));
+
+ // If an end-entity certificate does assert keyCertSign, this is allowed
+ // as long as that isn't the required key usage.
+ NAMED_SIMPLE_KU(digitalSignatureAndKeyCertSign, 2, 0x84);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &digitalSignatureAndKeyCertSign,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &digitalSignatureAndKeyCertSign,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, unusedBitNotZero)
+{
+ // single byte control case
+ static uint8_t controlOneValueByteData[] = {
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80
+ };
+ static const Input controlOneValueByte(controlOneValueByteData);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &controlOneValueByte,
+ KeyUsage::digitalSignature));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA,
+ &controlOneValueByte,
+ KeyUsage::digitalSignature));
+
+ // single-byte test case
+ static uint8_t oneValueByteData[] = {
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80 | 0x01
+ };
+ static const Input oneValueByte(oneValueByteData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &oneValueByte,
+ KeyUsage::digitalSignature));
+
+ // two-byte control case
+ static uint8_t controlTwoValueBytesData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
+ 0x80 | 0x01, 0x80
+ };
+ static const Input controlTwoValueBytes(controlTwoValueBytesData);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &controlTwoValueBytes,
+ KeyUsage::digitalSignature));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA,
+ &controlTwoValueBytes,
+ KeyUsage::digitalSignature));
+
+ // two-byte test case
+ static uint8_t twoValueBytesData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
+ 0x80 | 0x01, 0x80 | 0x01
+ };
+ static const Input twoValueBytes(twoValueBytesData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoValueBytes,
+ KeyUsage::digitalSignature));
+}
diff --git a/lib/mozpkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
new file mode 100644
index 000000000..9cf29896a
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
@@ -0,0 +1,366 @@
+/* -*- 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 "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+
+extern Result CheckSignatureAlgorithm(
+ TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA,
+ Time notBefore,
+ const der::SignedDataWithSignature& signedData,
+ Input signatureValue);
+
+} } // namespace mozilla::pkix
+
+struct CheckSignatureAlgorithmTestParams
+{
+ ByteString signatureAlgorithmValue;
+ ByteString signatureValue;
+ unsigned int signatureLengthInBytes;
+ Result expectedResult;
+};
+
+::std::ostream& operator<<(::std::ostream& os,
+ const CheckSignatureAlgorithmTestParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
+
+// python DottedOIDToCode.py --tlv sha256WithRSAEncryption 1.2.840.113549.1.1.11
+static const uint8_t tlv_sha256WithRSAEncryption[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
+};
+
+// Same as tlv_sha256WithRSAEncryption, except one without the "0x0b" and with
+// the DER length decreased accordingly.
+static const uint8_t tlv_sha256WithRSAEncryption_truncated[] = {
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01
+};
+
+// python DottedOIDToCode.py --tlv sha-1WithRSAEncryption 1.2.840.113549.1.1.5
+static const uint8_t tlv_sha_1WithRSAEncryption[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
+};
+
+// python DottedOIDToCode.py --tlv sha1WithRSASignature 1.3.14.3.2.29
+static const uint8_t tlv_sha1WithRSASignature[] = {
+ 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1d
+};
+
+// python DottedOIDToCode.py --tlv md5WithRSAEncryption 1.2.840.113549.1.1.4
+static const uint8_t tlv_md5WithRSAEncryption[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04
+};
+
+static const CheckSignatureAlgorithmTestParams
+ CHECKSIGNATUREALGORITHM_TEST_PARAMS[] =
+{
+ { // Both algorithm IDs are empty
+ ByteString(),
+ ByteString(),
+ 2048 / 8,
+ Result::ERROR_BAD_DER,
+ },
+ { // signatureAlgorithm is empty, signature is supported.
+ ByteString(),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_BAD_DER,
+ },
+ { // signatureAlgorithm is supported, signature is empty.
+ BS(tlv_sha256WithRSAEncryption),
+ ByteString(),
+ 2048 / 8,
+ Result::ERROR_BAD_DER,
+ },
+ { // Algorithms match, both are supported.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Success
+ },
+ { // Algorithms do not match because signatureAlgorithm is truncated.
+ BS(tlv_sha256WithRSAEncryption_truncated),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // Algorithms do not match because signature is truncated.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption_truncated),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // Algorithms do not match, both are supported.
+ BS(tlv_sha_1WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
+ },
+ { // Algorithms do not match, both are supported.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha_1WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
+ },
+ { // Algorithms match, both are unsupported.
+ BS(tlv_md5WithRSAEncryption),
+ BS(tlv_md5WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // signatureAlgorithm is unsupported, signature is supported.
+ BS(tlv_md5WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // signatureAlgorithm is supported, signature is unsupported.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_md5WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // Both have the optional NULL parameter.
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ 2048 / 8,
+ Success
+ },
+ { // signatureAlgorithm has the optional NULL parameter, signature doesn't.
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Success
+ },
+ { // signatureAlgorithm does not have the optional NULL parameter, signature
+ // does.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ 2048 / 8,
+ Success
+ },
+ { // The different OIDs for RSA-with-SHA1 we support are semantically
+ // equivalent.
+ BS(tlv_sha1WithRSASignature),
+ BS(tlv_sha_1WithRSAEncryption),
+ 2048 / 8,
+ Success,
+ },
+ { // The different OIDs for RSA-with-SHA1 we support are semantically
+ // equivalent (opposite order).
+ BS(tlv_sha_1WithRSAEncryption),
+ BS(tlv_sha1WithRSASignature),
+ 2048 / 8,
+ Success,
+ },
+ { // Algorithms match, both are supported, key size is not a multile of 128
+ // bits. This test verifies that we're not wrongly rounding up the
+ // signature size like we did in the original patch for bug 1131767.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ (2048 / 8) - 1,
+ Success
+ },
+};
+
+class pkixcheck_CheckSignatureAlgorithm
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<CheckSignatureAlgorithmTestParams>
+{
+};
+
+class pkixcheck_CheckSignatureAlgorithm_TrustDomain final
+ : public EverythingFailsByDefaultTrustDomain
+{
+public:
+ explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain(
+ unsigned int aPublicKeySizeInBits)
+ : publicKeySizeInBits(aPublicKeySizeInBits)
+ , checkedDigestAlgorithm(false)
+ , checkedModulusSizeInBits(false)
+ {
+ }
+
+ Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
+ override
+ {
+ checkedDigestAlgorithm = true;
+ return Success;
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA,
+ unsigned int modulusSizeInBits)
+ override
+ {
+ EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA);
+ EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits);
+ checkedModulusSizeInBits = true;
+ return Success;
+ }
+
+ const unsigned int publicKeySizeInBits;
+ bool checkedDigestAlgorithm;
+ bool checkedModulusSizeInBits;
+};
+
+TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
+{
+ const Time now(Now());
+ const CheckSignatureAlgorithmTestParams& params(GetParam());
+
+ Input signatureValueInput;
+ ASSERT_EQ(Success,
+ signatureValueInput.Init(params.signatureValue.data(),
+ params.signatureValue.length()));
+
+ pkixcheck_CheckSignatureAlgorithm_TrustDomain
+ trustDomain(params.signatureLengthInBytes * 8);
+
+ der::SignedDataWithSignature signedData;
+ ASSERT_EQ(Success,
+ signedData.algorithm.Init(params.signatureAlgorithmValue.data(),
+ params.signatureAlgorithmValue.length()));
+
+ ByteString dummySignature(params.signatureLengthInBytes, 0xDE);
+ ASSERT_EQ(Success,
+ signedData.signature.Init(dummySignature.data(),
+ dummySignature.length()));
+
+ ASSERT_EQ(params.expectedResult,
+ CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity,
+ now, signedData, signatureValueInput));
+ ASSERT_EQ(params.expectedResult == Success,
+ trustDomain.checkedDigestAlgorithm);
+ ASSERT_EQ(params.expectedResult == Success,
+ trustDomain.checkedModulusSizeInBits);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm,
+ testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS));
+
+class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
+ : public DefaultCryptoTrustDomain
+{
+public:
+ explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain(
+ const ByteString& aIssuer)
+ : issuer(aIssuer)
+ {
+ }
+
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+ Input cert, /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(cert, issuer)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ EXPECT_FALSE(ENCODING_FAILED(issuer));
+
+ Input issuerInput;
+ EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
+
+ bool keepGoing;
+ EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing));
+ EXPECT_FALSE(keepGoing);
+
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ ByteString issuer;
+};
+
+// Test that CheckSignatureAlgorithm actually gets called at some point when
+// BuildCertChain is called.
+TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain)
+{
+ ScopedTestKeyPair keyPair(CloneReusedKeyPair());
+ ASSERT_TRUE(keyPair.get());
+
+ ByteString issuerExtensions[2];
+ issuerExtensions[0] = CreateEncodedBasicConstraints(true, nullptr,
+ Critical::No);
+ ASSERT_FALSE(ENCODING_FAILED(issuerExtensions[0]));
+
+ ByteString issuer(CreateEncodedCertificate(3,
+ sha256WithRSAEncryption(),
+ CreateEncodedSerialNumber(1),
+ CNToDERName("issuer"),
+ oneDayBeforeNow, oneDayAfterNow,
+ CNToDERName("issuer"),
+ *keyPair,
+ issuerExtensions,
+ *keyPair,
+ sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(issuer));
+
+ ByteString subject(CreateEncodedCertificate(3,
+ sha1WithRSAEncryption(),
+ CreateEncodedSerialNumber(2),
+ CNToDERName("issuer"),
+ oneDayBeforeNow, oneDayAfterNow,
+ CNToDERName("subject"),
+ *keyPair,
+ nullptr,
+ *keyPair,
+ sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(subject));
+
+ Input subjectInput;
+ ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length()));
+ pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
+ trustDomain(issuer);
+ Result rv = BuildCertChain(trustDomain, subjectInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr);
+ ASSERT_EQ(Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, rv);
+}
diff --git a/lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
new file mode 100644
index 000000000..a77a2f47c
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
@@ -0,0 +1,127 @@
+/* -*- 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 "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56));
+
+#define OLDER_GENERALIZEDTIME \
+ 0x18, 15, /* tag, length */ \
+ '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+#define OLDER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+static const Time NOW(YMDHMS(2016, 12, 31, 12, 23, 56));
+
+#define NEWER_GENERALIZEDTIME \
+ 0x18, 15, /* tag, length */ \
+ '2', '0', '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+#define NEWER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
+
+class pkixcheck_CheckValidity : public ::testing::Test { };
+
+static const uint8_t OLDER_UTCTIME_NEWER_UTCTIME_DATA[] = {
+ OLDER_UTCTIME,
+ NEWER_UTCTIME,
+};
+static const Input
+OLDER_UTCTIME_NEWER_UTCTIME(OLDER_UTCTIME_NEWER_UTCTIME_DATA);
+
+TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_UTCTIME)
+{
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_GENERALIZEDTIME)
+{
+ static const uint8_t DER[] = {
+ OLDER_GENERALIZEDTIME,
+ NEWER_GENERALIZEDTIME,
+ };
+ static const Input validity(DER);
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_UTCTIME)
+{
+ static const uint8_t DER[] = {
+ OLDER_GENERALIZEDTIME,
+ NEWER_UTCTIME,
+ };
+ static const Input validity(DER);
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_GENERALIZEDTIME)
+{
+ static const uint8_t DER[] = {
+ OLDER_UTCTIME,
+ NEWER_GENERALIZEDTIME,
+ };
+ static const Input validity(DER);
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, InvalidBeforeNotBefore)
+{
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+ ASSERT_EQ(Result::ERROR_NOT_YET_VALID_CERTIFICATE, CheckValidity(PAST_TIME, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, InvalidAfterNotAfter)
+{
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+ ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, CheckValidity(FUTURE_TIME, notBefore, notAfter));
+}
diff --git a/lib/mozpkix/test/gtest/pkixcheck_ParseValidity_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_ParseValidity_tests.cpp
new file mode 100644
index 000000000..5206ce14f
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_ParseValidity_tests.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 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 "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+#define OLDER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+#define NEWER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
+
+class pkixcheck_ParseValidity : public ::testing::Test { };
+
+TEST_F(pkixcheck_ParseValidity, BothEmptyNull)
+{
+ static const uint8_t DER[] = {
+ 0x17/*UTCTime*/, 0/*length*/,
+ 0x17/*UTCTime*/, 0/*length*/,
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, NotBeforeEmptyNull)
+{
+ static const uint8_t DER[] = {
+ 0x17/*UTCTime*/, 0x00/*length*/,
+ NEWER_UTCTIME
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, NotAfterEmptyNull)
+{
+ static const uint8_t DER[] = {
+ NEWER_UTCTIME,
+ 0x17/*UTCTime*/, 0x00/*length*/,
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, InvalidNotAfterBeforeNotBefore)
+{
+ static const uint8_t DER[] = {
+ NEWER_UTCTIME,
+ OLDER_UTCTIME,
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
diff --git a/lib/mozpkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp b/lib/mozpkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp
new file mode 100644
index 000000000..6149286de
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp
@@ -0,0 +1,119 @@
+/* -*- 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 "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+ extern Result TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures,
+ const Input* stapledOCSPResponse);
+} } // namespace mozilla::pkix
+
+struct TLSFeaturesTestParams
+{
+ ByteString requiredTLSFeatures;
+ Result expectedResultWithResponse;
+ Result expectedResultWithoutResponse;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const TLSFeaturesTestParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
+static const uint8_t statusRequest[] = {
+ 0x30, 0x03, 0x02, 0x01, 0x05
+};
+
+static const uint8_t unknown[] = {
+ 0x30, 0x03, 0x02, 0x01, 0x06
+};
+
+static const uint8_t statusRequestAndUnknown[] = {
+ 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x06
+};
+
+static const uint8_t duplicateStatusRequest[] = {
+ 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x05
+};
+
+static const uint8_t twoByteUnknown[] = {
+ 0x30, 0x04, 0x02, 0x02, 0x05, 0x05
+};
+
+static const uint8_t zeroByteInteger[] = {
+ 0x30, 0x02, 0x02, 0x00
+};
+
+static const TLSFeaturesTestParams
+ TLSFEATURESSATISFIED_TEST_PARAMS[] =
+{
+ // some tests with checks enforced
+ { ByteString(), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER },
+ { BS(statusRequest), Success, Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(unknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(statusRequestAndUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(duplicateStatusRequest), Success,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(twoByteUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(zeroByteInteger), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+};
+
+class pkixcheck_TLSFeaturesSatisfiedInternal
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<TLSFeaturesTestParams>
+{
+};
+
+TEST_P(pkixcheck_TLSFeaturesSatisfiedInternal, TLSFeaturesSatisfiedInternal) {
+ const TLSFeaturesTestParams& params(GetParam());
+
+ Input featuresInput;
+ ASSERT_EQ(Success, featuresInput.Init(params.requiredTLSFeatures.data(),
+ params.requiredTLSFeatures.length()));
+ Input responseInput;
+ // just create an input with any data in it
+ ByteString stapledOCSPResponse = BS(statusRequest);
+ ASSERT_EQ(Success, responseInput.Init(stapledOCSPResponse.data(),
+ stapledOCSPResponse.length()));
+ // first we omit the response
+ ASSERT_EQ(params.expectedResultWithoutResponse,
+ TLSFeaturesSatisfiedInternal(&featuresInput, nullptr));
+ // then we try again with the response
+ ASSERT_EQ(params.expectedResultWithResponse,
+ TLSFeaturesSatisfiedInternal(&featuresInput, &responseInput));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixcheck_TLSFeaturesSatisfiedInternal,
+ pkixcheck_TLSFeaturesSatisfiedInternal,
+ testing::ValuesIn(TLSFEATURESSATISFIED_TEST_PARAMS));
diff --git a/lib/mozpkix/test/gtest/pkixder_input_tests.cpp b/lib/mozpkix/test/gtest/pkixder_input_tests.cpp
new file mode 100644
index 000000000..b0a6c8bf0
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixder_input_tests.cpp
@@ -0,0 +1,920 @@
+/* -*- 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 <functional>
+#include <vector>
+#include "pkixgtest.h"
+
+#include "pkixder.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::der;
+
+namespace {
+
+class pkixder_input_tests : public ::testing::Test { };
+
+static const uint8_t DER_SEQUENCE_EMPTY[] = {
+ 0x30, // SEQUENCE
+ 0x00, // length
+};
+
+static const uint8_t DER_SEQUENCE_NOT_EMPTY[] = {
+ 0x30, // SEQUENCE
+ 0x01, // length
+ 'X', // value
+};
+
+static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE[] = {
+ 'X', // value
+};
+
+static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED[] = {
+ 0x30, // SEQUENCE
+ 0x01, // length
+};
+
+const uint8_t DER_SEQUENCE_OF_INT8[] = {
+ 0x30, // SEQUENCE
+ 0x09, // length
+ 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
+ 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02
+ 0x02, 0x01, 0x03 // INTEGER length 1 value 0x03
+};
+
+const uint8_t DER_TRUNCATED_SEQUENCE_OF_INT8[] = {
+ 0x30, // SEQUENCE
+ 0x09, // length
+ 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
+ 0x02, 0x01, 0x02 // INTEGER length 1 value 0x02
+ // MISSING DATA HERE ON PURPOSE
+};
+
+const uint8_t DER_OVERRUN_SEQUENCE_OF_INT8[] = {
+ 0x30, // SEQUENCE
+ 0x09, // length
+ 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
+ 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02
+ 0x02, 0x02, 0xFF, 0x03 // INTEGER length 2 value 0xFF03
+};
+
+const uint8_t DER_INT16[] = {
+ 0x02, // INTEGER
+ 0x02, // length
+ 0x12, 0x34 // 0x1234
+};
+
+static const Input EMPTY_INPUT;
+
+TEST_F(pkixder_input_tests, InputInit)
+{
+ Input buf;
+ ASSERT_EQ(Success,
+ buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
+}
+
+TEST_F(pkixder_input_tests, InputInitWithNullPointerOrZeroLength)
+{
+ Input buf;
+ ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 0));
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 100));
+
+ // Though it seems odd to initialize with zero-length and non-null ptr, this
+ // is working as intended. The Reader class was intended to protect against
+ // buffer overflows, and there's no risk with the current behavior. See bug
+ // 1000354.
+ ASSERT_EQ(Success, buf.Init((const uint8_t*) "hello", 0));
+ ASSERT_TRUE(buf.GetLength() == 0);
+}
+
+TEST_F(pkixder_input_tests, InputInitWithLargeData)
+{
+ Input buf;
+ // Data argument length does not matter, it is not touched, just
+ // needs to be non-null
+ ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init((const uint8_t*) "", 0xffff+1));
+
+ ASSERT_EQ(Success, buf.Init((const uint8_t*) "", 0xffff));
+}
+
+TEST_F(pkixder_input_tests, InputInitMultipleTimes)
+{
+ Input buf;
+
+ ASSERT_EQ(Success,
+ buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
+
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS,
+ buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
+}
+
+TEST_F(pkixder_input_tests, PeekWithinBounds)
+{
+ const uint8_t der[] = { 0x11, 0x11 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_TRUE(input.Peek(0x11));
+ ASSERT_FALSE(input.Peek(0x22));
+}
+
+TEST_F(pkixder_input_tests, PeekPastBounds)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 1));
+ Reader input(buf);
+
+ uint8_t readByte;
+ ASSERT_EQ(Success, input.Read(readByte));
+ ASSERT_EQ(0x11, readByte);
+ ASSERT_FALSE(input.Peek(0x22));
+}
+
+TEST_F(pkixder_input_tests, ReadByte)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf(der);
+ Reader input(buf);
+
+ uint8_t readByte1;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x11, readByte1);
+
+ uint8_t readByte2;
+ ASSERT_EQ(Success, input.Read(readByte2));
+ ASSERT_EQ(0x22, readByte2);
+}
+
+TEST_F(pkixder_input_tests, ReadBytePastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 1));
+ Reader input(buf);
+
+ uint8_t readByte1 = 0;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x11, readByte1);
+
+ uint8_t readByte2 = 0;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readByte2));
+ ASSERT_NE(0x22, readByte2);
+}
+
+TEST_F(pkixder_input_tests, ReadByteWrapAroundPointer)
+{
+ // The original implementation of our buffer read overflow checks was
+ // susceptible to integer overflows which could make the checks ineffective.
+ // This attempts to verify that we've fixed that. Unfortunately, decrementing
+ // a null pointer is undefined behavior according to the C++ language spec.,
+ // but this should catch the problem on at least some compilers, if not all of
+ // them.
+ const uint8_t* der = nullptr;
+ --der;
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+
+ uint8_t b;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b));
+}
+
+TEST_F(pkixder_input_tests, ReadWord)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ uint16_t readWord1 = 0;
+ ASSERT_EQ(Success, input.Read(readWord1));
+ ASSERT_EQ(0x1122, readWord1);
+
+ uint16_t readWord2 = 0;
+ ASSERT_EQ(Success, input.Read(readWord2));
+ ASSERT_EQ(0x3344, readWord2);
+}
+
+TEST_F(pkixder_input_tests, ReadWordPastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 2)); // Initialize with too-short length
+ Reader input(buf);
+
+ uint16_t readWord1 = 0;
+ ASSERT_EQ(Success, input.Read(readWord1));
+ ASSERT_EQ(0x1122, readWord1);
+
+ uint16_t readWord2 = 0;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord2));
+ ASSERT_NE(0x3344, readWord2);
+}
+
+TEST_F(pkixder_input_tests, ReadWordWithInsufficentData)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 1));
+ Reader input(buf);
+
+ uint16_t readWord1 = 0;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord1));
+ ASSERT_NE(0x1122, readWord1);
+}
+
+TEST_F(pkixder_input_tests, ReadWordWrapAroundPointer)
+{
+ // The original implementation of our buffer read overflow checks was
+ // susceptible to integer overflows which could make the checks ineffective.
+ // This attempts to verify that we've fixed that. Unfortunately, decrementing
+ // a null pointer is undefined behavior according to the C++ language spec.,
+ // but this should catch the problem on at least some compilers, if not all of
+ // them.
+ const uint8_t* der = nullptr;
+ --der;
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+ uint16_t b;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b));
+}
+
+TEST_F(pkixder_input_tests, Skip)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ ASSERT_EQ(Success, input.Skip(1));
+
+ uint8_t readByte1 = 0;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x22, readByte1);
+
+ ASSERT_EQ(Success, input.Skip(1));
+
+ uint8_t readByte2 = 0;
+ ASSERT_EQ(Success, input.Read(readByte2));
+ ASSERT_EQ(0x44, readByte2);
+}
+
+TEST_F(pkixder_input_tests, Skip_ToEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(sizeof der));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, Skip_PastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1));
+}
+
+TEST_F(pkixder_input_tests, Skip_ToNewInput)
+{
+ const uint8_t der[] = { 0x01, 0x02, 0x03, 0x04 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader skippedInput;
+ ASSERT_EQ(Success, input.Skip(3, skippedInput));
+
+ uint8_t readByte1 = 0;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x04, readByte1);
+
+ ASSERT_TRUE(input.AtEnd());
+
+ // Reader has no Remaining() or Length() so we simply read the bytes
+ // and then expect to be at the end.
+
+ for (uint8_t i = 1; i <= 3; ++i) {
+ uint8_t readByte = 0;
+ ASSERT_EQ(Success, skippedInput.Read(readByte));
+ ASSERT_EQ(i, readByte);
+ }
+
+ ASSERT_TRUE(skippedInput.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, Skip_ToNewInputPastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader skippedInput;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der * 2, skippedInput));
+}
+
+TEST_F(pkixder_input_tests, Skip_ToInput)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 };
+
+ Input item;
+ ASSERT_EQ(Success, input.Skip(sizeof expectedItemData, item));
+
+ Input expected(expectedItemData);
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+TEST_F(pkixder_input_tests, Skip_WrapAroundPointer)
+{
+ // The original implementation of our buffer read overflow checks was
+ // susceptible to integer overflows which could make the checks ineffective.
+ // This attempts to verify that we've fixed that. Unfortunately, decrementing
+ // a null pointer is undefined behavior according to the C++ language spec.,
+ // but this should catch the problem on at least some compilers, if not all of
+ // them.
+ const uint8_t* der = nullptr;
+ --der;
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(1));
+}
+
+TEST_F(pkixder_input_tests, Skip_ToInputPastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Input skipped;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1, skipped));
+}
+
+TEST_F(pkixder_input_tests, SkipToEnd_ToInput)
+{
+ static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Input skipped;
+ ASSERT_EQ(Success, input.SkipToEnd(skipped));
+}
+
+TEST_F(pkixder_input_tests, SkipToEnd_ToInput_InputAlreadyInited)
+{
+ static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ static const uint8_t initialValue[] = { 0x01, 0x02, 0x03 };
+ Input x(initialValue);
+ // Fails because skipped was already initialized once, and Inputs are not
+ // allowed to be Init()d multiple times.
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.SkipToEnd(x));
+ ASSERT_TRUE(InputsAreEqual(x, Input(initialValue)));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndSkipValue)
+{
+ Input buf(DER_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE));
+ ASSERT_EQ(Success, End(input));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithTruncatedData)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndSkipValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithOverrunData)
+{
+ Input buf(DER_OVERRUN_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE));
+ ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
+}
+
+TEST_F(pkixder_input_tests, AtEndOnUnInitializedInput)
+{
+ Reader input;
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, AtEndAtBeginning)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_FALSE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, AtEndAtEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(sizeof der));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, MarkAndGetInput)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader::Mark mark = input.GetMark();
+
+ const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 };
+
+ ASSERT_EQ(Success, input.Skip(sizeof expectedItemData));
+
+ Input item;
+ ASSERT_EQ(Success, input.GetInput(mark, item));
+ Input expected(expectedItemData);
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+// Cannot run this test on debug builds because of the NotReached
+#ifdef NDEBUG
+TEST_F(pkixder_input_tests, MarkAndGetInputDifferentInput)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader another;
+ Reader::Mark mark = another.GetMark();
+
+ ASSERT_EQ(Success, input.Skip(3));
+
+ Input item;
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.GetInput(mark, item));
+}
+#endif
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_AtEnd)
+{
+ Reader input(EMPTY_INPUT);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_TruncatedAfterTag)
+{
+ static const uint8_t DER[] = { SEQUENCE };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ uint8_t tag = 0;
+ Input value;
+ ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value));
+ ASSERT_EQ(SEQUENCE, tag);
+ ASSERT_EQ(0u, value.GetLength());
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ uint8_t tag = 0;
+ Input value;
+ ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value));
+ ASSERT_EQ(SEQUENCE, tag);
+ Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE);
+ ASSERT_TRUE(InputsAreEqual(expected, value));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ReadTagAndGetValue_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm1)
+{
+ // High tag number form is not allowed (illegal 1 byte tag)
+ //
+ // If the decoder treats 0x1F as a valid low tag number tag, then it will
+ // treat the actual tag (1) as a length, and then it will return Success
+ // with value == { 0x00 } and tag == 0x1f.
+ //
+ // It is illegal to encode tag 1 in the high tag number form because it isn't
+ // the shortest encoding (the low tag number form is).
+ static const uint8_t DER[] = {
+ 0x1F, // high tag number form indicator
+ 1, // tag 1 (not legal!)
+ 0 // length zero
+ };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm2)
+{
+ // High tag number form is not allowed (legal 1 byte tag).
+ //
+ // ReadTagAndGetValue's check to prohibit the high tag number form has no
+ // effect on whether this test passes or fails, because ReadTagAndGetValue
+ // will interpret the second byte (31) as a length, and the input doesn't
+ // have 31 bytes following it. This test is here to guard against the case
+ // where somebody actually implements high tag number form parsing, to remind
+ // that person that they need to add tests here, including in particular
+ // tests for overly-long encodings.
+ static const uint8_t DER[] = {
+ 0x1F, // high tag number form indicator
+ 31, // tag 31
+ 0 // length zero
+ };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm3)
+{
+ // High tag number form is not allowed (2 byte legal tag)
+ //
+ // ReadTagAndGetValue's check to prohibit the high tag number form has no
+ // effect on whether this test passes or fails, because ReadTagAndGetValue
+ // will interpret the second byte as a length, and the input doesn't have
+ // that many bytes following it. This test is here to guard against the case
+ // where somebody actually implements high tag number form parsing, to remind
+ // that person that they need to add tests here, including in particular
+ // tests for overly-long encodings.
+ static const uint8_t DER[] = {
+ 0x1F, // high tag number form indicator
+ 0x80 | 0x01, 0x00, // tag 0x100 (256)
+ 0 // length zero
+ };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ ASSERT_TRUE(value.AtEnd());
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ ASSERT_TRUE(value.MatchRest(DER_SEQUENCE_NOT_EMPTY_VALUE));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndGetValue_Reader_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, INTEGER, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ ASSERT_EQ(0u, value.GetLength());
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE);
+ ASSERT_TRUE(InputsAreEqual(expected, value));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndGetValue_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, INTEGER, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ ASSERT_EQ(Success, ExpectTagAndEmptyValue(input, SEQUENCE));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndEmptyValue_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, INTEGER));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+ Input expected(DER_SEQUENCE_EMPTY);
+ ASSERT_TRUE(InputsAreEqual(expected, tlv));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+ Input expected(DER_SEQUENCE_NOT_EMPTY);
+ ASSERT_TRUE(InputsAreEqual(expected, tlv));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndGetTLV_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, INTEGER, tlv));
+}
+
+TEST_F(pkixder_input_tests, EndAtEnd)
+{
+ Input buf(DER_INT16);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(4));
+ ASSERT_EQ(Success, End(input));
+}
+
+TEST_F(pkixder_input_tests, EndBeforeEnd)
+{
+ Input buf(DER_INT16);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(2));
+ ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
+}
+
+TEST_F(pkixder_input_tests, EndAtBeginning)
+{
+ Input buf(DER_INT16);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
+}
+
+// TODO: Need tests for Nested too?
+
+Result NestedOfHelper(Reader& input, std::vector<uint8_t>& readValues)
+{
+ uint8_t value = 0;
+ Result rv = input.Read(value);
+ EXPECT_EQ(Success, rv);
+ if (rv != Success) {
+ return rv;
+ }
+ readValues.push_back(value);
+ return Success;
+}
+
+TEST_F(pkixder_input_tests, NestedOf)
+{
+ Input buf(DER_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ std::vector<uint8_t> readValues;
+ ASSERT_EQ(Success,
+ NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
+ [&readValues](Reader& r) {
+ return NestedOfHelper(r, readValues);
+ }));
+ ASSERT_EQ(3u, readValues.size());
+ ASSERT_EQ(0x01, readValues[0]);
+ ASSERT_EQ(0x02, readValues[1]);
+ ASSERT_EQ(0x03, readValues[2]);
+ ASSERT_EQ(Success, End(input));
+}
+
+TEST_F(pkixder_input_tests, NestedOfWithTruncatedData)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ std::vector<uint8_t> readValues;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
+ [&readValues](Reader& r) {
+ return NestedOfHelper(r, readValues);
+ }));
+ ASSERT_EQ(0u, readValues.size());
+}
+
+TEST_F(pkixder_input_tests, MatchRestAtEnd)
+{
+ static const uint8_t der[1] = { };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+ ASSERT_TRUE(input.AtEnd());
+ static const uint8_t toMatch[] = { 1 };
+ ASSERT_FALSE(input.MatchRest(toMatch));
+}
+
+TEST_F(pkixder_input_tests, MatchRest1Match)
+{
+ static const uint8_t der[] = { 1 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_FALSE(input.AtEnd());
+ ASSERT_TRUE(input.MatchRest(der));
+}
+
+TEST_F(pkixder_input_tests, MatchRest1Mismatch)
+{
+ static const uint8_t der[] = { 1 };
+ Input buf(der);
+ Reader input(buf);
+ static const uint8_t toMatch[] = { 2 };
+ ASSERT_FALSE(input.MatchRest(toMatch));
+ ASSERT_FALSE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, MatchRest2WithTrailingByte)
+{
+ static const uint8_t der[] = { 1, 2, 3 };
+ Input buf(der);
+ Reader input(buf);
+ static const uint8_t toMatch[] = { 1, 2 };
+ ASSERT_FALSE(input.MatchRest(toMatch));
+}
+
+TEST_F(pkixder_input_tests, MatchRest2Mismatch)
+{
+ static const uint8_t der[] = { 1, 2, 3 };
+ Input buf(der);
+ Reader input(buf);
+ static const uint8_t toMatchMismatch[] = { 1, 3 };
+ ASSERT_FALSE(input.MatchRest(toMatchMismatch));
+ ASSERT_TRUE(input.MatchRest(der));
+}
+
+} // namespace
diff --git a/lib/mozpkix/test/gtest/pkixder_pki_types_tests.cpp b/lib/mozpkix/test/gtest/pkixder_pki_types_tests.cpp
new file mode 100644
index 000000000..e40c2a4c3
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixder_pki_types_tests.cpp
@@ -0,0 +1,479 @@
+/* -*- 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 <functional>
+#include <vector>
+
+#include "pkixgtest.h"
+#include "pkix/pkixtypes.h"
+#include "pkixder.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::der;
+
+class pkixder_pki_types_tests : public ::testing::Test { };
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumber)
+{
+ const uint8_t DER_CERT_SERIAL[] = {
+ 0x02, // INTEGER
+ 8, // length
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
+ };
+ Input input(DER_CERT_SERIAL);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
+
+ Input expected;
+ ASSERT_EQ(Success,
+ expected.Init(DER_CERT_SERIAL + 2, sizeof DER_CERT_SERIAL - 2));
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumberLongest)
+{
+ const uint8_t DER_CERT_SERIAL_LONGEST[] = {
+ 0x02, // INTEGER
+ 20, // length
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+ };
+ Input input(DER_CERT_SERIAL_LONGEST);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
+
+ Input expected;
+ ASSERT_EQ(Success,
+ expected.Init(DER_CERT_SERIAL_LONGEST + 2,
+ sizeof DER_CERT_SERIAL_LONGEST - 2));
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumberCrazyLong)
+{
+ const uint8_t DER_CERT_SERIAL_CRAZY_LONG[] = {
+ 0x02, // INTEGER
+ 32, // length
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+ };
+ Input input(DER_CERT_SERIAL_CRAZY_LONG);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
+}
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumberZeroLength)
+{
+ const uint8_t DER_CERT_SERIAL_ZERO_LENGTH[] = {
+ 0x02, // INTEGER
+ 0x00 // length
+ };
+ Input input(DER_CERT_SERIAL_ZERO_LENGTH);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING,
+ CertificateSerialNumber(reader, item));
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionV1ExplicitEncodingAllowed)
+{
+ const uint8_t DER_OPTIONAL_VERSION_V1[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x00 // INTEGER(0)
+ };
+ Input input(DER_OPTIONAL_VERSION_V1);
+ Reader reader(input);
+
+ // XXX(bug 1031093): We shouldn't accept an explicit encoding of v1, but we
+ // do here for compatibility reasons.
+ // Version version;
+ // ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
+ der::Version version = der::Version::v3;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v1, version);
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionV2)
+{
+ const uint8_t DER_OPTIONAL_VERSION_V2[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x01 // INTEGER(1)
+ };
+ Input input(DER_OPTIONAL_VERSION_V2);
+ Reader reader(input);
+
+ der::Version version = der::Version::v1;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v2, version);
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionV3)
+{
+ const uint8_t DER_OPTIONAL_VERSION_V3[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x02 // INTEGER(2)
+ };
+ Input input(DER_OPTIONAL_VERSION_V3);
+ Reader reader(input);
+
+ der::Version version = der::Version::v1;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v3, version);
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionUnknown)
+{
+ const uint8_t DER_OPTIONAL_VERSION_INVALID[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x42 // INTEGER(0x42)
+ };
+ Input input(DER_OPTIONAL_VERSION_INVALID);
+ Reader reader(input);
+
+ der::Version version = der::Version::v1;
+ ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionInvalidTooLong)
+{
+ const uint8_t DER_OPTIONAL_VERSION_INVALID_TOO_LONG[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x02, 0x12, 0x34 // INTEGER(0x1234)
+ };
+ Input input(DER_OPTIONAL_VERSION_INVALID_TOO_LONG);
+ Reader reader(input);
+
+ der::Version version;
+ ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionMissing)
+{
+ const uint8_t DER_OPTIONAL_VERSION_MISSING[] = {
+ 0x02, 0x11, 0x22 // INTEGER
+ };
+ Input input(DER_OPTIONAL_VERSION_MISSING);
+ Reader reader(input);
+
+ der::Version version = der::Version::v3;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v1, version);
+}
+
+static const size_t MAX_ALGORITHM_OID_DER_LENGTH = 13;
+
+struct InvalidAlgorithmIdentifierTestInfo
+{
+ uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
+ size_t derLength;
+};
+
+struct ValidDigestAlgorithmIdentifierTestInfo
+{
+ DigestAlgorithm algorithm;
+ uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
+ size_t derLength;
+};
+
+class pkixder_DigestAlgorithmIdentifier_Valid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ValidDigestAlgorithmIdentifierTestInfo>
+{
+};
+
+static const ValidDigestAlgorithmIdentifierTestInfo
+ VALID_DIGEST_ALGORITHM_TEST_INFO[] =
+{
+ { DigestAlgorithm::sha512,
+ { 0x30, 0x0b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 },
+ 13
+ },
+ { DigestAlgorithm::sha384,
+ { 0x30, 0x0b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 },
+ 13
+ },
+ { DigestAlgorithm::sha256,
+ { 0x30, 0x0b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 },
+ 13
+ },
+ { DigestAlgorithm::sha1,
+ { 0x30, 0x07, 0x06, 0x05,
+ 0x2b, 0x0e, 0x03, 0x02, 0x1a },
+ 9
+ },
+};
+
+TEST_P(pkixder_DigestAlgorithmIdentifier_Valid, Valid)
+{
+ const ValidDigestAlgorithmIdentifierTestInfo& param(GetParam());
+
+ {
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ DigestAlgorithm alg;
+ ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg));
+ ASSERT_EQ(param.algorithm, alg);
+ ASSERT_EQ(Success, End(reader));
+ }
+
+ {
+ uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2];
+ memcpy(derWithNullParam, param.der, param.derLength);
+ derWithNullParam[1] += 2; // we're going to expand the value by 2 bytes
+ derWithNullParam[param.derLength] = 0x05; // NULL tag
+ derWithNullParam[param.derLength + 1] = 0x00; // length zero
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2));
+ Reader reader(input);
+ DigestAlgorithm alg;
+ ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg));
+ ASSERT_EQ(param.algorithm, alg);
+ ASSERT_EQ(Success, End(reader));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Valid,
+ pkixder_DigestAlgorithmIdentifier_Valid,
+ testing::ValuesIn(VALID_DIGEST_ALGORITHM_TEST_INFO));
+
+class pkixder_DigestAlgorithmIdentifier_Invalid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo>
+{
+};
+
+static const InvalidAlgorithmIdentifierTestInfo
+ INVALID_DIGEST_ALGORITHM_TEST_INFO[] =
+{
+ { // MD5
+ { 0x30, 0x0a, 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 },
+ 12,
+ },
+ { // ecdsa-with-SHA256 (1.2.840.10045.4.3.2) (not a hash algorithm)
+ { 0x30, 0x0a, 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 },
+ 12,
+ },
+};
+
+TEST_P(pkixder_DigestAlgorithmIdentifier_Invalid, Invalid)
+{
+ const InvalidAlgorithmIdentifierTestInfo& param(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ DigestAlgorithm alg;
+ ASSERT_EQ(Result::ERROR_INVALID_ALGORITHM,
+ DigestAlgorithmIdentifier(reader, alg));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Invalid,
+ pkixder_DigestAlgorithmIdentifier_Invalid,
+ testing::ValuesIn(INVALID_DIGEST_ALGORITHM_TEST_INFO));
+
+struct ValidSignatureAlgorithmIdentifierValueTestInfo
+{
+ PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
+ size_t derLength;
+};
+
+static const ValidSignatureAlgorithmIdentifierValueTestInfo
+ VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] =
+{
+ // ECDSA
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha512,
+ { 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 },
+ 10,
+ },
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha384,
+ { 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 },
+ 10,
+ },
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha256,
+ { 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 },
+ 10,
+ },
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha1,
+ { 0x06, 0x07,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01 },
+ 9,
+ },
+
+ // RSA
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha512,
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha384,
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha256,
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha1,
+ // IETF Standard OID
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha1,
+ // Legacy OIW OID (bug 1042479)
+ { 0x06, 0x05,
+ 0x2b, 0x0e, 0x03, 0x02, 0x1d },
+ 7,
+ },
+};
+
+class pkixder_SignatureAlgorithmIdentifierValue_Valid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<
+ ValidSignatureAlgorithmIdentifierValueTestInfo>
+{
+};
+
+TEST_P(pkixder_SignatureAlgorithmIdentifierValue_Valid, Valid)
+{
+ const ValidSignatureAlgorithmIdentifierValueTestInfo& param(GetParam());
+
+ {
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ ASSERT_EQ(Success,
+ SignatureAlgorithmIdentifierValue(reader, publicKeyAlg,
+ digestAlg));
+ ASSERT_EQ(param.publicKeyAlg, publicKeyAlg);
+ ASSERT_EQ(param.digestAlg, digestAlg);
+ ASSERT_EQ(Success, End(reader));
+ }
+
+ {
+ uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2];
+ memcpy(derWithNullParam, param.der, param.derLength);
+ derWithNullParam[param.derLength] = 0x05; // NULL tag
+ derWithNullParam[param.derLength + 1] = 0x00; // length zero
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2));
+ Reader reader(input);
+ PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ ASSERT_EQ(Success,
+ SignatureAlgorithmIdentifierValue(reader, publicKeyAlg,
+ digestAlg));
+ ASSERT_EQ(param.publicKeyAlg, publicKeyAlg);
+ ASSERT_EQ(param.digestAlg, digestAlg);
+ ASSERT_EQ(Success, End(reader));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixder_SignatureAlgorithmIdentifierValue_Valid,
+ pkixder_SignatureAlgorithmIdentifierValue_Valid,
+ testing::ValuesIn(VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO));
+
+static const InvalidAlgorithmIdentifierTestInfo
+ INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] =
+{
+ // id-dsa-with-sha256 (2.16.840.1.101.3.4.3.2)
+ { { 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02 },
+ 11,
+ },
+
+ // id-dsa-with-sha1 (1.2.840.10040.4.3)
+ { { 0x06, 0x07,
+ 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03 },
+ 9,
+ },
+
+ // RSA-with-MD5 (1.2.840.113549.1.1.4)
+ { { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 },
+ 11,
+ },
+
+ // id-sha256 (2.16.840.1.101.3.4.2.1). It is invalid because SHA-256 is not
+ // a signature algorithm.
+ { { 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 },
+ 11,
+ },
+};
+
+class pkixder_SignatureAlgorithmIdentifier_Invalid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo>
+{
+};
+
+TEST_P(pkixder_SignatureAlgorithmIdentifier_Invalid, Invalid)
+{
+ const InvalidAlgorithmIdentifierTestInfo& param(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ der::PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
+ SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, digestAlg));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixder_SignatureAlgorithmIdentifier_Invalid,
+ pkixder_SignatureAlgorithmIdentifier_Invalid,
+ testing::ValuesIn(INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO));
diff --git a/lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp b/lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp
new file mode 100644
index 000000000..8e21fc50d
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixder_universal_types_tests.cpp
@@ -0,0 +1,1225 @@
+/* -*- 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 <stdint.h>
+#include <vector>
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::der;
+using namespace mozilla::pkix::test;
+using namespace std;
+
+class pkixder_universal_types_tests : public ::testing::Test { };
+
+TEST_F(pkixder_universal_types_tests, BooleanTrue01)
+{
+ const uint8_t DER_BOOLEAN_TRUE_01[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x01 // invalid
+ };
+ Input input(DER_BOOLEAN_TRUE_01);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanTrue42)
+{
+ const uint8_t DER_BOOLEAN_TRUE_42[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x42 // invalid
+ };
+ Input input(DER_BOOLEAN_TRUE_42);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+static const uint8_t DER_BOOLEAN_TRUE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0xff // true
+};
+
+TEST_F(pkixder_universal_types_tests, BooleanTrueFF)
+{
+ Input input(DER_BOOLEAN_TRUE);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Success, Boolean(reader, value));
+ ASSERT_TRUE(value);
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanFalse)
+{
+ const uint8_t DER_BOOLEAN_FALSE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x00 // false
+ };
+ Input input(DER_BOOLEAN_FALSE);
+ Reader reader(input);
+
+ bool value = true;
+ ASSERT_EQ(Success, Boolean(reader, value));
+ ASSERT_FALSE(value);
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanInvalidLength)
+{
+ const uint8_t DER_BOOLEAN_INVALID_LENGTH[] = {
+ 0x01, // BOOLEAN
+ 0x02, // length
+ 0x42, 0x42 // invalid
+ };
+ Input input(DER_BOOLEAN_INVALID_LENGTH);
+ Reader reader(input);
+
+ bool value = true;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanInvalidZeroLength)
+{
+ const uint8_t DER_BOOLEAN_INVALID_ZERO_LENGTH[] = {
+ 0x01, // BOOLEAN
+ 0x00 // length
+ };
+ Input input(DER_BOOLEAN_INVALID_ZERO_LENGTH);
+ Reader reader(input);
+
+ bool value = true;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+// OptionalBoolean implements decoding of OPTIONAL BOOLEAN DEFAULT FALSE.
+// If the field is present, it must be a valid encoding of a BOOLEAN with
+// value TRUE. If the field is not present, it defaults to FALSE. For
+// compatibility reasons, OptionalBoolean also accepts encodings where the field
+// is present with value FALSE (this is technically not a valid DER encoding).
+TEST_F(pkixder_universal_types_tests, OptionalBooleanValidEncodings)
+{
+ {
+ const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_TRUE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0xff // true
+ };
+ Input input(DER_OPTIONAL_BOOLEAN_PRESENT_TRUE);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept the only valid encoding of a present OPTIONAL BOOLEAN";
+ ASSERT_TRUE(value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+
+ {
+ // The OPTIONAL BOOLEAN is omitted in this data.
+ const uint8_t DER_INTEGER_05[] = {
+ 0x02, // INTEGER
+ 0x01, // length
+ 0x05
+ };
+ Input input(DER_INTEGER_05);
+ Reader reader(input);
+ bool value = true;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept a valid encoding of an omitted OPTIONAL BOOLEAN";
+ ASSERT_FALSE(value);
+ ASSERT_FALSE(reader.AtEnd());
+ }
+
+ {
+ Input input;
+ ASSERT_EQ(Success, input.Init(reinterpret_cast<const uint8_t*>(""), 0));
+ Reader reader(input);
+ bool value = true;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept another valid encoding of an omitted OPTIONAL BOOLEAN";
+ ASSERT_FALSE(value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalBooleanInvalidEncodings)
+{
+ const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_FALSE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x00 // false
+ };
+
+ {
+ Input input(DER_OPTIONAL_BOOLEAN_PRESENT_FALSE);
+ Reader reader(input);
+ bool value = true;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept an invalid, default-value encoding of OPTIONAL BOOLEAN";
+ ASSERT_FALSE(value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+
+ const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_42[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x42 // (invalid value for a BOOLEAN)
+ };
+
+ {
+ Input input(DER_OPTIONAL_BOOLEAN_PRESENT_42);
+ Reader reader(input);
+ bool value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, OptionalBoolean(reader, value)) <<
+ "Should reject an invalid-valued encoding of OPTIONAL BOOLEAN";
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, Enumerated)
+{
+ const uint8_t DER_ENUMERATED[] = {
+ 0x0a, // ENUMERATED
+ 0x01, // length
+ 0x42 // value
+ };
+ Input input(DER_ENUMERATED);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Success, Enumerated(reader, value));
+ ASSERT_EQ(0x42, value);
+}
+
+TEST_F(pkixder_universal_types_tests, EnumeratedNotShortestPossibleDER)
+{
+ const uint8_t DER_ENUMERATED[] = {
+ 0x0a, // ENUMERATED
+ 0x02, // length
+ 0x00, 0x01 // value
+ };
+ Input input(DER_ENUMERATED);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, EnumeratedOutOfAcceptedRange)
+{
+ // Although this is a valid ENUMERATED value according to ASN.1, we
+ // intentionally don't support these large values because there are no
+ // ENUMERATED values in X.509 certs or OCSP this large, and we're trying to
+ // keep the parser simple and fast.
+ const uint8_t DER_ENUMERATED_INVALID_LENGTH[] = {
+ 0x0a, // ENUMERATED
+ 0x02, // length
+ 0x12, 0x34 // value
+ };
+ Input input(DER_ENUMERATED_INVALID_LENGTH);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, EnumeratedInvalidZeroLength)
+{
+ const uint8_t DER_ENUMERATED_INVALID_ZERO_LENGTH[] = {
+ 0x0a, // ENUMERATED
+ 0x00 // length
+ };
+ Input input(DER_ENUMERATED_INVALID_ZERO_LENGTH);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value));
+}
+
+////////////////////////////////////////
+// GeneralizedTime and TimeChoice
+//
+// From RFC 5280 section 4.1.2.5.2
+//
+// For the purposes of this profile, GeneralizedTime values MUST be
+// expressed in Greenwich Mean Time (Zulu) and MUST include seconds
+// (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds
+// is zero. GeneralizedTime values MUST NOT include fractional seconds.
+//
+// And from from RFC 6960 (OCSP) section 4.2.2.1:
+//
+// Responses can contain four times -- thisUpdate, nextUpdate,
+// producedAt, and revocationTime. The semantics of these fields are
+// defined in Section 2.4. The format for GeneralizedTime is as
+// specified in Section 4.1.2.5.2 of [RFC5280].
+//
+// So while we can could accept other ASN1 (ITU-T X.680) encodings for
+// GeneralizedTime we should not accept them, and breaking reading of these
+// other encodings is actually encouraged.
+
+// e.g. TWO_CHARS(53) => '5', '3'
+#define TWO_CHARS(t) \
+ static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) / 10u)), \
+ static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) % 10u))
+
+// Calls TimeChoice on the UTCTime variant of the given generalized time.
+template <uint16_t LENGTH>
+Result
+TimeChoiceForEquivalentUTCTime(const uint8_t (&generalizedTimeDER)[LENGTH],
+ /*out*/ Time& value)
+{
+ static_assert(LENGTH >= 4,
+ "TimeChoiceForEquivalentUTCTime input too small");
+ uint8_t utcTimeDER[LENGTH - 2];
+ utcTimeDER[0] = 0x17; // tag UTCTime
+ utcTimeDER[1] = LENGTH - 1/*tag*/ - 1/*value*/ - 2/*century*/;
+ // Copy the value except for the first two digits of the year
+ for (size_t i = 2; i < LENGTH - 2; ++i) {
+ utcTimeDER[i] = generalizedTimeDER[i + 2];
+ }
+
+ Input input(utcTimeDER);
+ Reader reader(input);
+ return TimeChoice(reader, value);
+}
+
+template <uint16_t LENGTH>
+void
+ExpectGoodTime(Time expectedValue,
+ const uint8_t (&generalizedTimeDER)[LENGTH])
+{
+ // GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, GeneralizedTime(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoice(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: UTCTime
+ {
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success,
+ TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+}
+
+template <uint16_t LENGTH>
+void
+ExpectBadTime(const uint8_t (&generalizedTimeDER)[LENGTH])
+{
+ // GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value));
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value));
+ }
+
+ // TimeChoice: UTCTime
+ {
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME,
+ TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value));
+ }
+}
+
+// Control value: a valid time
+TEST_F(pkixder_universal_types_tests, ValidControl)
+{
+ const uint8_t GT_DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', 'Z'
+ };
+ ExpectGoodTime(YMDHMS(1991, 5, 6, 16, 45, 40), GT_DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeTimeZoneOffset)
+{
+ const uint8_t DER_GENERALIZED_TIME_OFFSET[] = {
+ 0x18, // Generalized Time
+ 19, // Length = 19
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', '-',
+ '0', '7', '0', '0'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_OFFSET);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidZeroLength)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH[] = {
+ 0x18, // GeneralizedTime
+ 0x00 // Length = 0
+ };
+
+ Time value(Time::uninitialized);
+
+ // GeneralizedTime
+ Input gtBuf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH);
+ Reader gt(gtBuf);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(gt, value));
+
+ // TimeChoice: GeneralizedTime
+ Input tc_gt_buf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH);
+ Reader tc_gt(tc_gt_buf);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_gt, value));
+
+ // TimeChoice: UTCTime
+ const uint8_t DER_UTCTIME_INVALID_ZERO_LENGTH[] = {
+ 0x17, // UTCTime
+ 0x00 // Length = 0
+ };
+ Input tc_utc_buf(DER_UTCTIME_INVALID_ZERO_LENGTH);
+ Reader tc_utc(tc_utc_buf);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_utc, value));
+}
+
+// A non zulu time should fail
+TEST_F(pkixder_universal_types_tests, TimeInvalidLocal)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_LOCAL[] = {
+ 0x18, // Generalized Time
+ 14, // Length = 14
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_LOCAL);
+}
+
+// A time missing seconds and zulu should fail
+TEST_F(pkixder_universal_types_tests, TimeInvalidTruncated)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_TRUNCATED[] = {
+ 0x18, // Generalized Time
+ 12, // Length = 12
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_TRUNCATED);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeNoSeconds)
+{
+ const uint8_t DER_GENERALIZED_TIME_NO_SECONDS[] = {
+ 0x18, // Generalized Time
+ 13, // Length = 13
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_NO_SECONDS);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidPrefixedYear)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR[] = {
+ 0x18, // Generalized Time
+ 16, // Length = 16
+ ' ', '1', '9', '9', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeTooManyDigits)
+{
+ const uint8_t DER_GENERALIZED_TIME_TOO_MANY_DIGITS[] = {
+ 0x18, // Generalized Time
+ 16, // Length = 16
+ '1', '1', '1', '1', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_TOO_MANY_DIGITS);
+}
+
+// In order to ensure we we don't run into any trouble with conversions to and
+// from time_t we only accept times from 1970 onwards.
+TEST_F(pkixder_universal_types_tests, GeneralizedTimeYearValidRange)
+{
+ // Note that by using the last second of the last day of the year, we're also
+ // effectively testing all the accumulated conversions from Gregorian to to
+ // Julian time, including in particular the effects of leap years.
+
+ for (uint16_t i = 1970; i <= 9999; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ TWO_CHARS(i / 100), TWO_CHARS(i % 100), // YYYY
+ '1', '2', '3', '1', // 12-31
+ '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z
+ };
+
+ Time expectedValue = YMDHMS(i, 12, 31, 23, 59, 59);
+
+ // We have to test GeneralizedTime separately from UTCTime instead of using
+ // ExpectGooDtime because the range of UTCTime is less than the range of
+ // GeneralizedTime.
+
+ // GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, GeneralizedTime(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoice(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: UTCTime, which is limited to years less than 2049.
+ if (i <= 2049) {
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoiceForEquivalentUTCTime(DER, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+ }
+}
+
+// In order to ensure we we don't run into any trouble with conversions to and
+// from time_t we only accept times from 1970 onwards.
+TEST_F(pkixder_universal_types_tests, TimeYearInvalid1969)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '6', '9', '1', '2', '3', '1', // !!!1969!!!-12-31
+ '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z
+ };
+ ExpectBadTime(DER);
+}
+
+static const uint8_t DAYS_IN_MONTH[] = {
+ 0, // unused
+ 31, // January
+ 28, // February (leap years tested separately)
+ 31, // March
+ 30, // April
+ 31, // May
+ 30, // Jun
+ 31, // July
+ 31, // August
+ 30, // September
+ 31, // October
+ 30, // November
+ 31, // December
+};
+
+TEST_F(pkixder_universal_types_tests, TimeMonthDaysValidRange)
+{
+ for (uint16_t month = 1; month <= 12; ++month) {
+ for (uint8_t day = 1; day <= DAYS_IN_MONTH[month]; ++day) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '5', TWO_CHARS(month), TWO_CHARS(day), // (2015-mm-dd)
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectGoodTime(YMDHMS(2015, month, day, 16, 45, 40), DER);
+ }
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthInvalid0)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '5', '0', '0', '1', '5', // 2015-!!!00!!!-15
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthInvalid13)
+{
+ const uint8_t DER_GENERALIZED_TIME_13TH_MONTH[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', //YYYY (1991)
+ '1', '3', //MM 13th month of the year
+ '0', '6', '1', '6', '4', '5', '4', '0', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_13TH_MONTH);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeDayInvalid0)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '5', '0', '1', '0', '0', // 2015-01-!!!00!!!
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthDayInvalidPastEndOfMonth)
+{
+ for (int16_t month = 1; month <= 12; ++month) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', // YYYY 1991
+ TWO_CHARS(month), // MM
+ TWO_CHARS(1 + (month == 2 ? 29 : DAYS_IN_MONTH[month])), // !!!DD!!!
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2016)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '6', '0', '2', '2', '9', // 2016-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectGoodTime(YMDHMS(2016, 2, 29, 16, 45, 40), DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2000)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '0', '0', '0', '2', '2', '9', // 2000-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectGoodTime(YMDHMS(2000, 2, 29, 16, 45, 40), DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2400)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '4', '0', '0', '0', '2', '2', '9', // 2400-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+
+ // We don't use ExpectGoodTime here because UTCTime can't represent 2400.
+
+ Time expectedValue = YMDHMS(2400, 2, 29, 16, 45, 40);
+
+ // GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, GeneralizedTime(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoice(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2014)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '4', '0', '2', '2', '9', // 2014-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2100)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '1', '0', '0', '0', '2', '2', '9', // 2100-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+
+ // We don't use ExpectBadTime here because UTCTime can't represent 2100.
+
+ // GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value));
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value));
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeHoursValidRange)
+{
+ for (uint8_t i = 0; i <= 23; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ TWO_CHARS(i), '5', '9', '0', '1', 'Z' // HHMMSSZ (!!!!ii!!!!:59:01 Zulu)
+ };
+ ExpectGoodTime(YMDHMS(2012, 6, 30, i, 59, 1), DER);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeHoursInvalid_24_00_00)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '4', '0', '0', '0', '0', 'Z' // HHMMSSZ (!!24!!:00:00 Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMinutesValidRange)
+{
+ for (uint8_t i = 0; i <= 59; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', TWO_CHARS(i), '0', '1', 'Z' // HHMMSSZ (23:!!!!ii!!!!:01 Zulu)
+ };
+ ExpectGoodTime(YMDHMS(2012, 6, 30, 23, i, 1), DER);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMinutesInvalid60)
+{
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '6', '0', '5', '9', 'Z' // HHMMSSZ (23:!!!60!!!:01 Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeSecondsValidRange)
+{
+ for (uint8_t i = 0; i <= 59; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', TWO_CHARS(i), 'Z' // HHMMSSZ (23:59:!!!!ii!!!! Zulu)
+ };
+ ExpectGoodTime(YMDHMS(2012, 6, 30, 23, 59, i), DER);
+ }
+}
+
+// No Leap Seconds (60)
+TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid60)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '6', '0', 'Z' // HHMMSSZ (23:59:!!!!60!!!! Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+// No Leap Seconds (61)
+TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid61)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '6', '1', 'Z' // HHMMSSZ (23:59:!!!!61!!!! Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidZulu)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_ZULU[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '5', '9', 'z' // HHMMSSZ (23:59:59 !!!z!!!) should be Z
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_ZULU);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidExtraData)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_EXTRA_DATA[] = {
+ 0x18, // Generalized Time
+ 16, // Length = 16
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '5', '9', 'Z', // HHMMSSZ (23:59:59Z)
+ 0 // Extra null character
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_EXTRA_DATA);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidCenturyChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ 'X', '9', '9', '1', '1', '2', '0', '6', // YYYYMMDD (X991-12-06)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+
+ // We can't use ExpectBadTime here, because ExpectBadTime requires
+ // consistent results for GeneralizedTime and UTCTime, but the results
+ // for this input are different.
+
+ // GeneralizedTime
+ {
+ Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value));
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value));
+ }
+
+ // This test is not applicable to TimeChoice: UTCTime
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidYearChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_YEAR_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', 'I', '0', '1', '0', '6', // YYYYMMDD (199I-12-06)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_YEAR_CHAR);
+}
+
+TEST_F(pkixder_universal_types_tests, GeneralizedTimeInvalidMonthChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_MONTH_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', '0', 'I', '0', '6', // YYYYMMDD (1991-0I-06)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_MONTH_CHAR);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidDayChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_DAY_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', '0', '1', '0', 'S', // YYYYMMDD (1991-01-0S)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_DAY_CHAR);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidFractionalSeconds)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS[] = {
+ 0x18, // Generalized Time
+ 17, // Length = 17
+ '1', '9', '9', '1', '0', '1', '0', '1', // YYYYMMDD (1991-01-01)
+ '1', '6', '4', '5', '4', '0', '.', '3', 'Z' // HHMMSS.FFF (16:45:40.3Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS);
+}
+
+struct IntegerTestParams
+{
+ ByteString encoded;
+ struct PositiveIntegerParams
+ {
+ Result expectedResult;
+ Input::size_type significantBytesIfValid;
+ } positiveInteger;
+ struct SmallNonnegativeIntegerParams
+ {
+ Result expectedResult;
+ uint8_t valueIfValid;
+ } smallNonnegativeInteger;
+};
+
+class pkixder_universal_types_tests_Integer
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IntegerTestParams>
+{
+};
+
+::std::ostream& operator<<(::std::ostream& os, const IntegerTestParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#define INVALID 0xFF
+
+static const IntegerTestParams INTEGER_TEST_PARAMS[] =
+{
+ // Zero is encoded with one value byte of 0x00.
+ { TLV(2, ByteString()),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Success, 0 } },
+
+ // Positive single-byte values
+ { TLV(2, "\x01"), { Success, 1 }, { Success, 1} },
+ { TLV(2, "\x02"), { Success, 1 }, { Success, 2} },
+ { TLV(2, "\x7e"), { Success, 1 }, { Success, 0x7e} },
+ { TLV(2, "\x7f"), { Success, 1 }, { Success, 0x7f} },
+
+ // Negative single-byte values
+ { TLV(2, "\x80"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x81"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFE"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFF"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // Positive two-byte values not starting with 0x00
+ { TLV(2, "\x7F\x00"),
+ { Success, 2 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x01\x00"),
+ { Success, 2 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x01\x02"),
+ { Success, 2 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // Negative two-byte values not starting with 0xFF
+ { TLV(2, "\x80\x00"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x80\x7F"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x80\x80"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x80\xFF"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading zero is necessary.
+ { TLV(2, "\x00\x80"),
+ { Success, 1},
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00\x81"),
+ { Success, 1},
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00\xFF"),
+ { Success, 1},
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading zero is unnecessary.
+ { TLV(2, "\x00\x01"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00\x7F"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading 0xFF is necessary.
+ { TLV(2, "\xFF\x00"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFF\x7F"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading 0xFF is unnecessary.
+ { TLV(2, "\xFF\x80"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFF\xFF"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // Truncated values
+ { TLV(2, 1, ByteString(/*missing value*/)),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 3, "\x11\x22" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 4, "\x11\x22" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 2, "\x00" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 2, "\xFF" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 3, "\x00\x80" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 3, "\xFF\x00" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+
+ // Misc. larger values
+ { TLV(2, 4, "\x11\x22\x33\x44"),
+ { Success, 4 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2,
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"),
+ { Success, 256 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+};
+
+TEST_P(pkixder_universal_types_tests_Integer, Integer)
+{
+ const IntegerTestParams& params(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(params.encoded.data(),
+ params.encoded.length()));
+ Reader reader(input);
+ Result expectedResult = params.smallNonnegativeInteger.expectedResult;
+ uint8_t value;
+ ASSERT_EQ(expectedResult, der::Integer(reader, value));
+ if (expectedResult == Success) {
+ ASSERT_EQ(params.smallNonnegativeInteger.valueIfValid, value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+TEST_P(pkixder_universal_types_tests_Integer,
+ PositiveInteger_without_significantBytes)
+{
+ const IntegerTestParams& params(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(params.encoded.data(),
+ params.encoded.length()));
+ Reader reader(input);
+ Result expectedResult = params.positiveInteger.expectedResult;
+ Input value;
+ ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value));
+ if (expectedResult == Success) {
+ Reader anotherReader(input);
+ Input expectedValue;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader,
+ der::INTEGER, expectedValue));
+ ASSERT_TRUE(InputsAreEqual(expectedValue, value));
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+TEST_P(pkixder_universal_types_tests_Integer,
+ PositiveInteger_with_significantBytes)
+{
+ const IntegerTestParams& params(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(params.encoded.data(),
+ params.encoded.length()));
+ Reader reader(input);
+ Result expectedResult = params.positiveInteger.expectedResult;
+ Input value;
+ Input::size_type significantBytes = INVALID;
+ ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value,
+ &significantBytes));
+ if (expectedResult == Success) {
+ ASSERT_NE(INVALID, params.positiveInteger.significantBytesIfValid);
+ ASSERT_EQ(params.positiveInteger.significantBytesIfValid,
+ significantBytes);
+
+ Reader anotherReader(input);
+ Input expectedValue;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader,
+ der::INTEGER, expectedValue));
+ ASSERT_TRUE(InputsAreEqual(expectedValue, value));
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+#undef INVALID
+
+INSTANTIATE_TEST_CASE_P(pkixder_universal_types_tests_Integer,
+ pkixder_universal_types_tests_Integer,
+ testing::ValuesIn(INTEGER_TEST_PARAMS));
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefault)
+{
+ // The input is a BOOLEAN and not INTEGER for the input so we'll not parse
+ // anything and instead use the default value.
+ Input input(DER_BOOLEAN_TRUE);
+ Reader reader(input);
+
+ long value = 1;
+ ASSERT_EQ(Success, OptionalInteger(reader, -1, value));
+ ASSERT_EQ(-1, value);
+ bool boolValue;
+ ASSERT_EQ(Success, Boolean(reader, boolValue));
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerUnsupportedDefault)
+{
+ // The same as the previous test, except with an unsupported default value
+ // passed in.
+ Input input(DER_BOOLEAN_TRUE);
+ Reader reader(input);
+
+ long value;
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, OptionalInteger(reader, 0, value));
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefaultAtEnd)
+{
+ static const uint8_t dummy = 1;
+ Input input;
+ ASSERT_EQ(Success, input.Init(&dummy, 0));
+ Reader reader(input);
+
+ long value = 1;
+ ASSERT_EQ(Success, OptionalInteger(reader, -1, value));
+ ASSERT_EQ(-1, value);
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerNonDefaultValue)
+{
+ static const uint8_t DER[] = {
+ 0x02, // INTEGER
+ 0x01, // length
+ 0x00
+ };
+ Input input(DER);
+ Reader reader(input);
+
+ long value = 2;
+ ASSERT_EQ(Success, OptionalInteger(reader, -1, value));
+ ASSERT_EQ(0, value);
+ ASSERT_TRUE(reader.AtEnd());
+}
+
+TEST_F(pkixder_universal_types_tests, Null)
+{
+ const uint8_t DER_NUL[] = {
+ 0x05,
+ 0x00
+ };
+ Input input(DER_NUL);
+ Reader reader(input);
+
+ ASSERT_EQ(Success, Null(reader));
+}
+
+TEST_F(pkixder_universal_types_tests, NullWithBadLength)
+{
+ const uint8_t DER_NULL_BAD_LENGTH[] = {
+ 0x05,
+ 0x01,
+ 0x00
+ };
+ Input input(DER_NULL_BAD_LENGTH);
+ Reader reader(input);
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, Null(reader));
+}
+
+TEST_F(pkixder_universal_types_tests, OID)
+{
+ const uint8_t DER_VALID_OID[] = {
+ 0x06,
+ 0x09,
+ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+ Input input(DER_VALID_OID);
+ Reader reader(input);
+
+ const uint8_t expectedOID[] = {
+ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+
+ ASSERT_EQ(Success, OID(reader, expectedOID));
+}
diff --git a/lib/mozpkix/test/gtest/pkixgtest.cpp b/lib/mozpkix/test/gtest/pkixgtest.cpp
new file mode 100644
index 000000000..77baef857
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixgtest.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 "pkixgtest.h"
+
+#include <ctime>
+
+#include "pkix/Time.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+static const std::time_t ONE_DAY_IN_SECONDS_AS_TIME_T =
+ static_cast<std::time_t>(Time::ONE_DAY_IN_SECONDS);
+
+// This assumes that time/time_t are POSIX-compliant in that time() returns
+// the number of seconds since the Unix epoch.
+static const std::time_t now(time(nullptr));
+const std::time_t oneDayBeforeNow(now - ONE_DAY_IN_SECONDS_AS_TIME_T);
+const std::time_t oneDayAfterNow(now + ONE_DAY_IN_SECONDS_AS_TIME_T);
+const std::time_t twoDaysBeforeNow(now - (2 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+const std::time_t twoDaysAfterNow(now + (2 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+const std::time_t tenDaysBeforeNow(now - (10 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+const std::time_t tenDaysAfterNow(now + (10 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+
+} } } // namespace mozilla::pkix::test
diff --git a/lib/mozpkix/test/gtest/pkixgtest.h b/lib/mozpkix/test/gtest/pkixgtest.h
new file mode 100644
index 000000000..744465bf9
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixgtest.h
@@ -0,0 +1,255 @@
+/* -*- 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_pkixgtest_h
+#define mozilla_pkix_pkixgtest_h
+
+#include <ostream>
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated"
+#pragma clang diagnostic ignored "-Wmissing-noreturn"
+#pragma clang diagnostic ignored "-Wshift-sign-overflow"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wundef"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wextra"
+#elif defined(_MSC_VER)
+#pragma warning(push, 3)
+// C4224: Nonstandard extension used: formal parameter 'X' was previously
+// defined as a type.
+#pragma warning(disable: 4224)
+// C4826: Conversion from 'type1 ' to 'type_2' is sign - extended. This may
+// cause unexpected runtime behavior.
+#pragma warning(disable: 4826)
+#endif
+
+#include "gtest/gtest.h"
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#elif defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#include "pkix/pkix.h"
+#include "pkixtestutil.h"
+
+// PrintTo must be in the same namespace as the type we're overloading it for.
+namespace mozilla { namespace pkix {
+
+inline void
+PrintTo(const Result& result, ::std::ostream* os)
+{
+ const char* stringified = MapResultToName(result);
+ if (stringified) {
+ *os << stringified;
+ } else {
+ *os << "mozilla::pkix::Result(" << static_cast<unsigned int>(result) << ")";
+ }
+}
+
+} } // namespace mozilla::pkix
+
+namespace mozilla { namespace pkix { namespace test {
+
+extern const std::time_t oneDayBeforeNow;
+extern const std::time_t oneDayAfterNow;
+extern const std::time_t twoDaysBeforeNow;
+extern const std::time_t twoDaysAfterNow;
+extern const std::time_t tenDaysBeforeNow;
+extern const std::time_t tenDaysAfterNow;
+
+
+class EverythingFailsByDefaultTrustDomain : public TrustDomain
+{
+public:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+ Input, /*out*/ TrustLevel&) override
+ {
+ ADD_FAILURE();
+ return NotReached("GetCertTrust should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result FindIssuer(Input, IssuerChecker&, Time) override
+ {
+ ADD_FAILURE();
+ return NotReached("FindIssuer should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRevocation should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override
+ {
+ ADD_FAILURE();
+ return NotReached("IsChainValid should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override
+ {
+ ADD_FAILURE();
+ return NotReached("DigestBuf should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckSignatureDigestAlgorithm(DigestAlgorithm,
+ EndEntityOrCA,
+ Time) override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckSignatureDigestAlgorithm should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckECDSACurveIsAcceptable should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result VerifyECDSASignedDigest(const SignedDigest&, Input) override
+ {
+ ADD_FAILURE();
+ return NotReached("VerifyECDSASignedDigest should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRSAPublicKeyModulusSizeInBits should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override
+ {
+ ADD_FAILURE();
+ return NotReached("VerifyRSAPKCS1SignedDigest should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckValidityIsAcceptable should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result NetscapeStepUpMatchesServerAuth(Time, bool&) override
+ {
+ ADD_FAILURE();
+ return NotReached("NetscapeStepUpMatchesServerAuth should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
+ {
+ ADD_FAILURE();
+ }
+};
+
+class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
+{
+ Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf, size_t digestBufLen) override
+ {
+ return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
+ }
+
+ Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
+ override
+ {
+ return Success;
+ }
+
+ Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
+ {
+ return Success;
+ }
+
+ Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) override
+ {
+ return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
+ override
+ {
+ return Success;
+ }
+
+ Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) override
+ {
+ return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
+ }
+
+ Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId)
+ override
+ {
+ return Success;
+ }
+
+ Result NetscapeStepUpMatchesServerAuth(Time, /*out*/ bool& matches) override
+ {
+ matches = true;
+ return Success;
+ }
+
+ void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
+ {
+ }
+};
+
+class DefaultNameMatchingPolicy : public NameMatchingPolicy
+{
+public:
+ virtual Result FallBackToCommonName(
+ Time, /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override
+ {
+ fallBackToCommonName = FallBackToSearchWithinSubject::Yes;
+ return Success;
+ }
+};
+
+} } } // namespace mozilla::pkix::test
+
+#endif // mozilla_pkix_pkixgtest_h
diff --git a/lib/mozpkix/test/gtest/pkixnames_tests.cpp b/lib/mozpkix/test/gtest/pkixnames_tests.cpp
new file mode 100644
index 000000000..a328234af
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixnames_tests.cpp
@@ -0,0 +1,2837 @@
+/* -*- 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 "pkixcheck.h"
+#include "pkixder.h"
+#include "pkixgtest.h"
+#include "pkixutil.h"
+
+namespace mozilla { namespace pkix {
+
+Result MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
+ Input referenceDNSID,
+ /*out*/ bool& matches);
+
+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]);
+
+} } // namespace mozilla::pkix
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+struct PresentedMatchesReference
+{
+ ByteString presentedDNSID;
+ ByteString referenceDNSID;
+ Result expectedResult;
+ bool expectedMatches; // only valid when expectedResult == Success
+};
+
+::std::ostream& operator<<(::std::ostream& os, const PresentedMatchesReference&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#define DNS_ID_MATCH(a, b) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
+ ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
+ Success, \
+ true \
+ }
+
+#define DNS_ID_MISMATCH(a, b) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
+ ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
+ Success, \
+ false \
+ }
+
+#define DNS_ID_BAD_DER(a, b) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
+ ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
+ Result::ERROR_BAD_DER, \
+ false \
+ }
+
+static const PresentedMatchesReference DNSID_MATCH_PARAMS[] =
+{
+ DNS_ID_BAD_DER("", "a"),
+
+ DNS_ID_MATCH("a", "a"),
+ DNS_ID_MISMATCH("b", "a"),
+
+ DNS_ID_MATCH("*.b.a", "c.b.a"),
+ DNS_ID_MISMATCH("*.b.a", "b.a"),
+ DNS_ID_MISMATCH("*.b.a", "b.a."),
+
+ // We allow underscores for compatibility with existing practices.
+ DNS_ID_MATCH("a_b", "a_b"),
+ DNS_ID_MATCH("*.example.com", "uses_underscore.example.com"),
+ DNS_ID_MATCH("*.uses_underscore.example.com", "a.uses_underscore.example.com"),
+
+ // See bug 1139039
+ DNS_ID_MATCH("_.example.com", "_.example.com"),
+ DNS_ID_MATCH("*.example.com", "_.example.com"),
+ DNS_ID_MATCH("_", "_"),
+ DNS_ID_MATCH("___", "___"),
+ DNS_ID_MATCH("example_", "example_"),
+ DNS_ID_MATCH("_example", "_example"),
+ DNS_ID_MATCH("*._._", "x._._"),
+
+ // See bug 1139039
+ // A DNS-ID must not end in an all-numeric label. We don't consider
+ // underscores to be numeric.
+ DNS_ID_MATCH("_1", "_1"),
+ DNS_ID_MATCH("example._1", "example._1"),
+ DNS_ID_MATCH("example.1_", "example.1_"),
+
+ // Wildcard not in leftmost label
+ DNS_ID_MATCH("d.c.b.a", "d.c.b.a"),
+ DNS_ID_BAD_DER("d.*.b.a", "d.c.b.a"),
+ DNS_ID_BAD_DER("d.c*.b.a", "d.c.b.a"),
+ DNS_ID_BAD_DER("d.c*.b.a", "d.cc.b.a"),
+
+ // case sensitivity
+ DNS_ID_MATCH("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ DNS_ID_MATCH("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"),
+ DNS_ID_MATCH("aBc", "Abc"),
+
+ // digits
+ DNS_ID_MATCH("a1", "a1"),
+
+ // A trailing dot indicates an absolute name. Absolute presented names are
+ // not allowed, but absolute reference names are allowed.
+ DNS_ID_MATCH("example", "example"),
+ DNS_ID_BAD_DER("example.", "example."),
+ DNS_ID_MATCH("example", "example."),
+ DNS_ID_BAD_DER("example.", "example"),
+ DNS_ID_MATCH("example.com", "example.com"),
+ DNS_ID_BAD_DER("example.com.", "example.com."),
+ DNS_ID_MATCH("example.com", "example.com."),
+ DNS_ID_BAD_DER("example.com.", "example.com"),
+ DNS_ID_BAD_DER("example.com..", "example.com."),
+ DNS_ID_BAD_DER("example.com..", "example.com"),
+ DNS_ID_BAD_DER("example.com...", "example.com."),
+
+ // xn-- IDN prefix
+ DNS_ID_BAD_DER("x*.b.a", "xa.b.a"),
+ DNS_ID_BAD_DER("x*.b.a", "xna.b.a"),
+ DNS_ID_BAD_DER("x*.b.a", "xn-a.b.a"),
+ DNS_ID_BAD_DER("x*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn---*.b.a", "xn--a.b.a"),
+
+ // "*" cannot expand to nothing.
+ DNS_ID_BAD_DER("c*.b.a", "c.b.a"),
+
+ /////////////////////////////////////////////////////////////////////////////
+ // These are test cases adapted from Chromium's x509_certificate_unittest.cc.
+ // The parameter order is the opposite in Chromium's tests. Also, some tests
+ // were modified to fit into this framework or due to intentional differences
+ // between mozilla::pkix and Chromium.
+
+ DNS_ID_MATCH("foo.com", "foo.com"),
+ DNS_ID_MATCH("f", "f"),
+ DNS_ID_MISMATCH("i", "h"),
+ DNS_ID_MATCH("*.foo.com", "bar.foo.com"),
+ DNS_ID_MATCH("*.test.fr", "www.test.fr"),
+ DNS_ID_MATCH("*.test.FR", "wwW.tESt.fr"),
+ DNS_ID_BAD_DER(".uk", "f.uk"),
+ DNS_ID_BAD_DER("?.bar.foo.com", "w.bar.foo.com"),
+ DNS_ID_BAD_DER("(www|ftp).foo.com", "www.foo.com"), // regex!
+ DNS_ID_BAD_DER("www.foo.com\0", "www.foo.com"),
+ DNS_ID_BAD_DER("www.foo.com\0*.foo.com", "www.foo.com"),
+ DNS_ID_MISMATCH("ww.house.example", "www.house.example"),
+ DNS_ID_MISMATCH("www.test.org", "test.org"),
+ DNS_ID_MISMATCH("*.test.org", "test.org"),
+ DNS_ID_BAD_DER("*.org", "test.org"),
+ DNS_ID_BAD_DER("w*.bar.foo.com", "w.bar.foo.com"),
+ DNS_ID_BAD_DER("ww*ww.bar.foo.com", "www.bar.foo.com"),
+ DNS_ID_BAD_DER("ww*ww.bar.foo.com", "wwww.bar.foo.com"),
+
+ // Different than Chromium, matches NSS.
+ DNS_ID_BAD_DER("w*w.bar.foo.com", "wwww.bar.foo.com"),
+
+ DNS_ID_BAD_DER("w*w.bar.foo.c0m", "wwww.bar.foo.com"),
+
+ // '*' must be the only character in the wildcard label
+ DNS_ID_BAD_DER("wa*.bar.foo.com", "WALLY.bar.foo.com"),
+
+ // We require "*" to be the last character in a wildcard label, but
+ // Chromium does not.
+ DNS_ID_BAD_DER("*Ly.bar.foo.com", "wally.bar.foo.com"),
+
+ // Chromium does URL decoding of the reference ID, but we don't, and we also
+ // require that the reference ID is valid, so we can't test these two.
+ // DNS_ID_MATCH("www.foo.com", "ww%57.foo.com"),
+ // DNS_ID_MATCH("www&.foo.com", "www%26.foo.com"),
+
+ DNS_ID_MISMATCH("*.test.de", "www.test.co.jp"),
+ DNS_ID_BAD_DER("*.jp", "www.test.co.jp"),
+ DNS_ID_MISMATCH("www.test.co.uk", "www.test.co.jp"),
+ DNS_ID_BAD_DER("www.*.co.jp", "www.test.co.jp"),
+ DNS_ID_MATCH("www.bar.foo.com", "www.bar.foo.com"),
+ DNS_ID_MISMATCH("*.foo.com", "www.bar.foo.com"),
+ DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"),
+ DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"),
+
+ // Our matcher requires the reference ID to be a valid DNS name, so we cannot
+ // test this case.
+ //DNS_ID_BAD_DER("*.*.bar.foo.com", "*..bar.foo.com"),
+
+ DNS_ID_MATCH("www.bath.org", "www.bath.org"),
+
+ // Our matcher requires the reference ID to be a valid DNS name, so we cannot
+ // test these cases.
+ // DNS_ID_BAD_DER("www.bath.org", ""),
+ // DNS_ID_BAD_DER("www.bath.org", "20.30.40.50"),
+ // DNS_ID_BAD_DER("www.bath.org", "66.77.88.99"),
+
+ // IDN tests
+ DNS_ID_MATCH("xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"),
+ DNS_ID_MISMATCH("*.xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_BAD_DER("xn--poema-*.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_BAD_DER("xn--*-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_BAD_DER("*--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+
+ // The following are adapted from the examples quoted from
+ // http://tools.ietf.org/html/rfc6125#section-6.4.3
+ // (e.g., *.example.com would match foo.example.com but
+ // not bar.foo.example.com or example.com).
+ DNS_ID_MATCH("*.example.com", "foo.example.com"),
+ DNS_ID_MISMATCH("*.example.com", "bar.foo.example.com"),
+ DNS_ID_MISMATCH("*.example.com", "example.com"),
+ // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would
+ // be taken to match baz1.example.net and foobaz.example.net and
+ // buzz.example.net, respectively. However, we don't allow any characters
+ // other than '*' in the wildcard label.
+ DNS_ID_BAD_DER("baz*.example.net", "baz1.example.net"),
+
+ // Both of these are different from Chromium, but match NSS, becaues the
+ // wildcard character "*" is not the last character of the label.
+ DNS_ID_BAD_DER("*baz.example.net", "foobaz.example.net"),
+ DNS_ID_BAD_DER("b*z.example.net", "buzz.example.net"),
+
+ // Wildcards should not be valid for public registry controlled domains,
+ // and unknown/unrecognized domains, at least three domain components must
+ // be present. For mozilla::pkix and NSS, there must always be at least two
+ // labels after the wildcard label.
+ DNS_ID_MATCH("*.test.example", "www.test.example"),
+ DNS_ID_MATCH("*.example.co.uk", "test.example.co.uk"),
+ DNS_ID_BAD_DER("*.exmaple", "test.example"),
+
+ // The result is different than Chromium, because Chromium takes into account
+ // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does
+ // not know that.
+ DNS_ID_MATCH("*.co.uk", "example.co.uk"),
+
+ DNS_ID_BAD_DER("*.com", "foo.com"),
+ DNS_ID_BAD_DER("*.us", "foo.us"),
+ DNS_ID_BAD_DER("*", "foo"),
+
+ // IDN variants of wildcards and registry controlled domains.
+ DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"),
+ DNS_ID_MATCH("*.example.xn--mgbaam7a8h", "test.example.xn--mgbaam7a8h"),
+
+ // RFC6126 allows this, and NSS accepts it, but Chromium disallows it.
+ // TODO: File bug against Chromium.
+ DNS_ID_MATCH("*.com.br", "xn--poema-9qae5a.com.br"),
+
+ DNS_ID_BAD_DER("*.xn--mgbaam7a8h", "example.xn--mgbaam7a8h"),
+ // Wildcards should be permissible for 'private' registry-controlled
+ // domains. (In mozilla::pkix, we do not know if it is a private registry-
+ // controlled domain or not.)
+ DNS_ID_MATCH("*.appspot.com", "www.appspot.com"),
+ DNS_ID_MATCH("*.s3.amazonaws.com", "foo.s3.amazonaws.com"),
+
+ // Multiple wildcards are not valid.
+ DNS_ID_BAD_DER("*.*.com", "foo.example.com"),
+ DNS_ID_BAD_DER("*.bar.*.com", "foo.bar.example.com"),
+
+ // Absolute vs relative DNS name tests. Although not explicitly specified
+ // in RFC 6125, absolute reference names (those ending in a .) should
+ // match either absolute or relative presented names. We don't allow
+ // absolute presented names.
+ // TODO: File errata against RFC 6125 about this.
+ DNS_ID_BAD_DER("foo.com.", "foo.com"),
+ DNS_ID_MATCH("foo.com", "foo.com."),
+ DNS_ID_BAD_DER("foo.com.", "foo.com."),
+ DNS_ID_BAD_DER("f.", "f"),
+ DNS_ID_MATCH("f", "f."),
+ DNS_ID_BAD_DER("f.", "f."),
+ DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com"),
+ DNS_ID_MATCH("*.bar.foo.com", "www-3.bar.foo.com."),
+ DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com."),
+
+ // We require the reference ID to be a valid DNS name, so we cannot test this
+ // case.
+ // DNS_ID_MISMATCH(".", "."),
+
+ DNS_ID_BAD_DER("*.com.", "example.com"),
+ DNS_ID_BAD_DER("*.com", "example.com."),
+ DNS_ID_BAD_DER("*.com.", "example.com."),
+ DNS_ID_BAD_DER("*.", "foo."),
+ DNS_ID_BAD_DER("*.", "foo"),
+
+ // The result is different than Chromium because we don't know that co.uk is
+ // a TLD.
+ DNS_ID_MATCH("*.co.uk", "foo.co.uk"),
+ DNS_ID_MATCH("*.co.uk", "foo.co.uk."),
+ DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk"),
+ DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk."),
+
+ DNS_ID_MISMATCH("*.example.com", "localhost"),
+ DNS_ID_MISMATCH("*.example.com", "localhost."),
+ // Note that we already have the testcase DNS_ID_BAD_DER("*", "foo") above
+};
+
+struct InputValidity
+{
+ ByteString input;
+ bool isValidReferenceID;
+ bool isValidPresentedID;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const InputValidity&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+// str is null-terminated, which is why we subtract 1. str may contain embedded
+// nulls (including at the end) preceding the null terminator though.
+#define I(str, validReferenceID, validPresentedID) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ validReferenceID, \
+ validPresentedID, \
+ }
+
+static const InputValidity DNSNAMES_VALIDITY[] =
+{
+ I("a", true, true),
+ I("a.b", true, true),
+ I("a.b.c", true, true),
+ I("a.b.c.d", true, true),
+
+ // empty labels
+ I("", false, false),
+ I(".", false, false),
+ I("a", true, true),
+ I(".a", false, false),
+ I(".a.b", false, false),
+ I("..a", false, false),
+ I("a..b", false, false),
+ I("a...b", false, false),
+ I("a..b.c", false, false),
+ I("a.b..c", false, false),
+ I(".a.b.c.", false, false),
+
+ // absolute names (only allowed for reference names)
+ I("a.", true, false),
+ I("a.b.", true, false),
+ I("a.b.c.", true, false),
+
+ // absolute names with empty label at end
+ I("a..", false, false),
+ I("a.b..", false, false),
+ I("a.b.c..", false, false),
+ I("a...", false, false),
+
+ // Punycode
+ I("xn--", false, false),
+ I("xn--.", false, false),
+ I("xn--.a", false, false),
+ I("a.xn--", false, false),
+ I("a.xn--.", false, false),
+ I("a.xn--.b", false, false),
+ I("a.xn--.b", false, false),
+ I("a.xn--\0.b", false, false),
+ I("a.xn--a.b", true, true),
+ I("xn--a", true, true),
+ I("a.xn--a", true, true),
+ I("a.xn--a.a", true, true),
+ I("\xc4\x95.com", false, false), // UTF-8 ĕ
+ I("xn--jea.com", true, true), // punycode ĕ
+ I("xn--\xc4\x95.com", false, false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup
+
+ // Surprising punycode
+ I("xn--google.com", true, true), // 䕮䕵䕶䕱.com
+ I("xn--citibank.com", true, true), // 岍岊岊岅岉岎.com
+ I("xn--cnn.com", true, true), // 䁾.com
+ I("a.xn--cnn", true, true), // a.䁾
+ I("a.xn--cnn.com", true, true), // a.䁾.com
+
+ I("1.2.3.4", false, false), // IPv4 address
+ I("1::2", false, false), // IPV6 address
+
+ // whitespace not allowed anywhere.
+ I(" ", false, false),
+ I(" a", false, false),
+ I("a ", false, false),
+ I("a b", false, false),
+ I("a.b 1", false, false),
+ I("a\t", false, false),
+
+ // Nulls not allowed
+ I("\0", false, false),
+ I("a\0", false, false),
+ I("example.org\0.example.com", false, false), // Hi Moxie!
+ I("\0a", false, false),
+ I("xn--\0", false, false),
+
+ // Allowed character set
+ I("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true, true),
+ I("A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z", true, true),
+ I("0.1.2.3.4.5.6.7.8.9.a", true, true), // "a" needed to avoid numeric last label
+ I("a-b", true, true), // hyphen (a label cannot start or end with a hyphen)
+
+ // Underscores
+ I("a_b", true, true),
+ // See bug 1139039
+ I("_", true, true),
+ I("a_", true, true),
+ I("_a", true, true),
+ I("_1", true, true),
+ I("1_", true, true),
+ I("___", true, true),
+
+ // An invalid character in various positions
+ I("!", false, false),
+ I("!a", false, false),
+ I("a!", false, false),
+ I("a!b", false, false),
+ I("a.!", false, false),
+ I("a.a!", false, false),
+ I("a.!a", false, false),
+ I("a.a!a", false, false),
+ I("a.!a.a", false, false),
+ I("a.a!.a", false, false),
+ I("a.a!a.a", false, false),
+
+ // Various other invalid characters
+ I("a!", false, false),
+ I("a@", false, false),
+ I("a#", false, false),
+ I("a$", false, false),
+ I("a%", false, false),
+ I("a^", false, false),
+ I("a&", false, false),
+ I("a*", false, false),
+ I("a(", false, false),
+ I("a)", false, false),
+
+ // last label can't be fully numeric
+ I("1", false, false),
+ I("a.1", false, false),
+
+ // other labels can be fully numeric
+ I("1.a", true, true),
+ I("1.2.a", true, true),
+ I("1.2.3.a", true, true),
+
+ // last label can be *partly* numeric
+ I("1a", true, true),
+ I("1.1a", true, true),
+ I("1-1", true, true),
+ I("a.1-1", true, true),
+ I("a.1-a", true, true),
+
+ // labels cannot start with a hyphen
+ I("-", false, false),
+ I("-1", false, false),
+
+ // labels cannot end with a hyphen
+ I("1-", false, false),
+ I("1-.a", false, false),
+ I("a-", false, false),
+ I("a-.a", false, false),
+ I("a.1-.a", false, false),
+ I("a.a-.a", false, false),
+
+ // labels can contain a hyphen in the middle
+ I("a-b", true, true),
+ I("1-2", true, true),
+ I("a.a-1", true, true),
+
+ // multiple consecutive hyphens allowed
+ I("a--1", true, true),
+ I("1---a", true, true),
+ I("a-----------------b", true, true),
+
+ // Wildcard specifications are not valid reference names, but are valid
+ // presented names if there are enough labels and if '*' is the only
+ // character in the wildcard label.
+ I("*.a", false, false),
+ I("a*", false, false),
+ I("a*.", false, false),
+ I("a*.a", false, false),
+ I("a*.a.", false, false),
+ I("*.a.b", false, true),
+ I("*.a.b.", false, false),
+ I("a*.b.c", false, false),
+ I("*.a.b.c", false, true),
+ I("a*.b.c.d", false, false),
+
+ // Multiple wildcards are not allowed.
+ I("a**.b.c", false, false),
+ I("a*b*.c.d", false, false),
+ I("a*.b*.c", false, false),
+
+ // Wildcards are only allowed in the first label.
+ I("a.*", false, false),
+ I("a.*.b", false, false),
+ I("a.b.*", false, false),
+ I("a.b*.c", false, false),
+ I("*.b*.c", false, false),
+ I(".*.a.b", false, false),
+ I(".a*.b.c", false, false),
+
+ // Wildcards must be at the *end* of the first label.
+ I("*a.b.c", false, false),
+ I("a*b.c.d", false, false),
+
+ // Wildcards not allowed with IDNA prefix
+ I("x*.a.b", false, false),
+ I("xn*.a.b", false, false),
+ I("xn-*.a.b", false, false),
+ I("xn--*.a.b", false, false),
+ I("xn--w*.a.b", false, false),
+
+ // Redacted labels from RFC6962bis draft 4
+ // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-04#section-3.2.2
+ I("(PRIVATE).foo", false, false),
+
+ // maximum label length is 63 characters
+ I("1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "abc", true, true),
+ I("1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "abcd", false, false),
+
+ // maximum total length is 253 characters
+ I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "12345678" "a",
+ true, true),
+ I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "123456789" "a",
+ false, false),
+};
+
+static const InputValidity DNSNAMES_VALIDITY_TURKISH_I[] =
+{
+ // http://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing
+ // IDN registration rules disallow "latin capital letter i with dot above,"
+ // but our checks aren't intended to enforce those rules.
+ I("I", true, true), // ASCII capital I
+ I("i", true, true), // ASCII lowercase i
+ I("\xC4\xB0", false, false), // latin capital letter i with dot above
+ I("\xC4\xB1", false, false), // latin small letter dotless i
+ I("xn--i-9bb", true, true), // latin capital letter i with dot above, in punycode
+ I("xn--cfa", true, true), // latin small letter dotless i, in punycode
+ I("xn--\xC4\xB0", false, false), // latin capital letter i with dot above, mashup
+ I("xn--\xC4\xB1", false, false), // latin small letter dotless i, mashup
+};
+
+static const uint8_t LOWERCASE_I_VALUE[1] = { 'i' };
+static const uint8_t UPPERCASE_I_VALUE[1] = { 'I' };
+static const Input LOWERCASE_I(LOWERCASE_I_VALUE);
+static const Input UPPERCASE_I(UPPERCASE_I_VALUE);
+
+template <unsigned int L>
+struct IPAddressParams
+{
+ ByteString input;
+ bool isValid;
+ uint8_t expectedValueIfValid[L];
+};
+
+template <unsigned int L>
+::std::ostream& operator<<(::std::ostream& os, const IPAddressParams<L>&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#define IPV4_VALID(str, a, b, c, d) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ true, \
+ { a, b, c, d } \
+ }
+
+// The value of expectedValueIfValid must be ignored for invalid IP addresses.
+// The value { 73, 73, 73, 73 } is used because it is unlikely to result in an
+// accidental match, unlike { 0, 0, 0, 0 }, which is a value we actually test.
+#define IPV4_INVALID(str) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ false, \
+ { 73, 73, 73, 73 } \
+ }
+
+static const IPAddressParams<4> IPV4_ADDRESSES[] =
+{
+ IPV4_INVALID(""),
+ IPV4_INVALID("1"),
+ IPV4_INVALID("1.2"),
+ IPV4_INVALID("1.2.3"),
+ IPV4_VALID("1.2.3.4", 1, 2, 3, 4),
+ IPV4_INVALID("1.2.3.4.5"),
+
+ IPV4_INVALID("1.2.3.4a"), // a DNSName!
+ IPV4_INVALID("a.2.3.4"), // not even a DNSName!
+ IPV4_INVALID("1::2"), // IPv6 address
+
+ // Whitespace not allowed
+ IPV4_INVALID(" 1.2.3.4"),
+ IPV4_INVALID("1.2.3.4 "),
+ IPV4_INVALID("1 .2.3.4"),
+ IPV4_INVALID("\n1.2.3.4"),
+ IPV4_INVALID("1.2.3.4\n"),
+
+ // Nulls not allowed
+ IPV4_INVALID("\0"),
+ IPV4_INVALID("\0" "1.2.3.4"),
+ IPV4_INVALID("1.2.3.4\0"),
+ IPV4_INVALID("1.2.3.4\0.5"),
+
+ // Range
+ IPV4_VALID("0.0.0.0", 0, 0, 0, 0),
+ IPV4_VALID("255.255.255.255", 255, 255, 255, 255),
+ IPV4_INVALID("256.0.0.0"),
+ IPV4_INVALID("0.256.0.0"),
+ IPV4_INVALID("0.0.256.0"),
+ IPV4_INVALID("0.0.0.256"),
+ IPV4_INVALID("999.0.0.0"),
+ IPV4_INVALID("9999999999999999999.0.0.0"),
+
+ // All digits allowed
+ IPV4_VALID("0.1.2.3", 0, 1, 2, 3),
+ IPV4_VALID("4.5.6.7", 4, 5, 6, 7),
+ IPV4_VALID("8.9.0.1", 8, 9, 0, 1),
+
+ // Leading zeros not allowed
+ IPV4_INVALID("01.2.3.4"),
+ IPV4_INVALID("001.2.3.4"),
+ IPV4_INVALID("00000000001.2.3.4"),
+ IPV4_INVALID("010.2.3.4"),
+ IPV4_INVALID("1.02.3.4"),
+ IPV4_INVALID("1.2.03.4"),
+ IPV4_INVALID("1.2.3.04"),
+
+ // Empty components
+ IPV4_INVALID(".2.3.4"),
+ IPV4_INVALID("1..3.4"),
+ IPV4_INVALID("1.2..4"),
+ IPV4_INVALID("1.2.3."),
+
+ // Too many components
+ IPV4_INVALID("1.2.3.4.5"),
+ IPV4_INVALID("1.2.3.4.5.6"),
+ IPV4_INVALID("0.1.2.3.4"),
+ IPV4_INVALID("1.2.3.4.0"),
+
+ // Leading/trailing dot
+ IPV4_INVALID(".1.2.3.4"),
+ IPV4_INVALID("1.2.3.4."),
+
+ // Other common forms of IPv4 address
+ // http://en.wikipedia.org/wiki/IPv4#Address_representations
+ IPV4_VALID("192.0.2.235", 192, 0, 2, 235), // dotted decimal (control value)
+ IPV4_INVALID("0xC0.0x00.0x02.0xEB"), // dotted hex
+ IPV4_INVALID("0301.0000.0002.0353"), // dotted octal
+ IPV4_INVALID("0xC00002EB"), // non-dotted hex
+ IPV4_INVALID("3221226219"), // non-dotted decimal
+ IPV4_INVALID("030000001353"), // non-dotted octal
+ IPV4_INVALID("192.0.0002.0xEB"), // mixed
+};
+
+#define IPV6_VALID(str, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ true, \
+ { a, b, c, d, \
+ e, f, g, h, \
+ i, j, k, l, \
+ m, n, o, p } \
+ }
+
+#define IPV6_INVALID(str) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ false, \
+ { 73, 73, 73, 73, \
+ 73, 73, 73, 73, \
+ 73, 73, 73, 73, \
+ 73, 73, 73, 73 } \
+ }
+
+static const IPAddressParams<16> IPV6_ADDRESSES[] =
+{
+ IPV6_INVALID(""),
+ IPV6_INVALID("1234"),
+ IPV6_INVALID("1234:5678"),
+ IPV6_INVALID("1234:5678:9abc"),
+ IPV6_INVALID("1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:"),
+ IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc:def0",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:"),
+ IPV6_INVALID(":1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:0000"),
+
+ // Valid contractions
+ IPV6_VALID("::1",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::1234",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x34),
+ IPV6_VALID("1234::",
+ 0x12, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("1234::5678",
+ 0x12, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x56, 0x78),
+ IPV6_VALID("1234:5678::abcd",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xab, 0xcd),
+ IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc::",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0x00, 0x00),
+
+ // Contraction in full IPv6 addresses not allowed
+ IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), // start
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), // end
+ IPV6_INVALID("1234:5678::9abc:def0:1234:5678:9abc:def0"), // interior
+
+ // Multiple contractions not allowed
+ IPV6_INVALID("::1::"),
+ IPV6_INVALID("::1::2"),
+ IPV6_INVALID("1::2::"),
+
+ // Colon madness!
+ IPV6_INVALID(":"),
+ IPV6_INVALID("::"),
+ IPV6_INVALID(":::"),
+ IPV6_INVALID("::::"),
+ IPV6_INVALID(":::1"),
+ IPV6_INVALID("::::1"),
+ IPV6_INVALID("1:::2"),
+ IPV6_INVALID("1::::2"),
+ IPV6_INVALID("1:2:::"),
+ IPV6_INVALID("1:2::::"),
+ IPV6_INVALID("::1234:"),
+ IPV6_INVALID(":1234::"),
+
+ IPV6_INVALID("01234::"), // too many digits, even if zero
+ IPV6_INVALID("12345678::"), // too many digits or missing colon
+
+ // uppercase
+ IPV6_VALID("ABCD:EFAB::",
+ 0xab, 0xcd, 0xef, 0xab,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+
+ // miXeD CAse
+ IPV6_VALID("aBcd:eFAb::",
+ 0xab, 0xcd, 0xef, 0xab,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+
+ // IPv4-style
+ IPV6_VALID("::2.3.4.5",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x03, 0x04, 0x05),
+ IPV6_VALID("1234::2.3.4.5",
+ 0x12, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x03, 0x04, 0x05),
+ IPV6_VALID("::abcd:2.3.4.5",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xab, 0xcd,
+ 0x02, 0x03, 0x04, 0x05),
+ IPV6_VALID("1234:5678:9abc:def0:1234:5678:252.253.254.255",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78,
+ 252, 253, 254, 255),
+ IPV6_VALID("1234:5678:9abc:def0:1234::252.253.254.255",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x00, 0x00,
+ 252, 253, 254, 255),
+ IPV6_INVALID("1234::252.253.254"),
+ IPV6_INVALID("::252.253.254"),
+ IPV6_INVALID("::252.253.254.300"),
+ IPV6_INVALID("1234::252.253.254.255:"),
+ IPV6_INVALID("1234::252.253.254.255:5678"),
+
+ // Contractions that don't contract
+ IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"),
+ IPV6_INVALID("1234:5678:9abc:def0::1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678::252.253.254.255"),
+
+ // With and without leading zeros
+ IPV6_VALID("::123",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x23),
+ IPV6_VALID("::0123",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x23),
+ IPV6_VALID("::012",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x12),
+ IPV6_VALID("::0012",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x12),
+ IPV6_VALID("::01",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::001",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::0001",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::0",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("::00",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("::000",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("::0000",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_INVALID("::01234"),
+ IPV6_INVALID("::00123"),
+ IPV6_INVALID("::000123"),
+
+ // Trailing zero
+ IPV6_INVALID("::12340"),
+
+ // Whitespace
+ IPV6_INVALID(" 1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0\n"),
+ IPV6_INVALID("1234 :5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("1234: 5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID(":: 2.3.4.5"),
+ IPV6_INVALID("1234::252.253.254.255 "),
+ IPV6_INVALID("1234::252.253.254.255\n"),
+ IPV6_INVALID("1234::252.253. 254.255"),
+
+ // Nulls
+ IPV6_INVALID("\0"),
+ IPV6_INVALID("::1\0:2"),
+ IPV6_INVALID("::1\0"),
+ IPV6_INVALID("::1.2.3.4\0"),
+ IPV6_INVALID("::1.2\02.3.4"),
+};
+
+class pkixnames_MatchPresentedDNSIDWithReferenceDNSID
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<PresentedMatchesReference>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
+ MatchPresentedDNSIDWithReferenceDNSID)
+{
+ const PresentedMatchesReference& param(GetParam());
+ SCOPED_TRACE(param.presentedDNSID.c_str());
+ SCOPED_TRACE(param.referenceDNSID.c_str());
+ Input presented;
+ ASSERT_EQ(Success, presented.Init(param.presentedDNSID.data(),
+ param.presentedDNSID.length()));
+ Input reference;
+ ASSERT_EQ(Success, reference.Init(param.referenceDNSID.data(),
+ param.referenceDNSID.length()));
+
+ // sanity check that test makes sense
+ ASSERT_TRUE(IsValidReferenceDNSID(reference));
+
+ bool matches;
+ ASSERT_EQ(param.expectedResult,
+ MatchPresentedDNSIDWithReferenceDNSID(presented, reference,
+ matches));
+ if (param.expectedResult == Success) {
+ ASSERT_EQ(param.expectedMatches, matches);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
+ pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
+ testing::ValuesIn(DNSID_MATCH_PARAMS));
+
+class pkixnames_Turkish_I_Comparison
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InputValidity>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID)
+{
+ // Make sure we don't have the similar problems that strcasecmp and others
+ // have with the other kinds of "i" and "I" commonly used in Turkish locales.
+
+ const InputValidity& inputValidity(GetParam());
+ SCOPED_TRACE(inputValidity.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(inputValidity.input.data(),
+ inputValidity.input.length()));
+
+ bool isASCII = InputsAreEqual(LOWERCASE_I, input) ||
+ InputsAreEqual(UPPERCASE_I, input);
+ {
+ bool matches;
+ ASSERT_EQ(inputValidity.isValidPresentedID ? Success
+ : Result::ERROR_BAD_DER,
+ MatchPresentedDNSIDWithReferenceDNSID(input, LOWERCASE_I,
+ matches));
+ if (inputValidity.isValidPresentedID) {
+ ASSERT_EQ(isASCII, matches);
+ }
+ }
+ {
+ bool matches;
+ ASSERT_EQ(inputValidity.isValidPresentedID ? Success
+ : Result::ERROR_BAD_DER,
+ MatchPresentedDNSIDWithReferenceDNSID(input, UPPERCASE_I,
+ matches));
+ if (inputValidity.isValidPresentedID) {
+ ASSERT_EQ(isASCII, matches);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_Turkish_I_Comparison,
+ pkixnames_Turkish_I_Comparison,
+ testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));
+
+class pkixnames_IsValidReferenceDNSID
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InputValidity>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID)
+{
+ const InputValidity& inputValidity(GetParam());
+ SCOPED_TRACE(inputValidity.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(inputValidity.input.data(),
+ inputValidity.input.length()));
+ ASSERT_EQ(inputValidity.isValidReferenceID, IsValidReferenceDNSID(input));
+ ASSERT_EQ(inputValidity.isValidPresentedID, IsValidPresentedDNSID(input));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID,
+ pkixnames_IsValidReferenceDNSID,
+ testing::ValuesIn(DNSNAMES_VALIDITY));
+INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID_Turkish_I,
+ pkixnames_IsValidReferenceDNSID,
+ testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));
+
+class pkixnames_ParseIPv4Address
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IPAddressParams<4>>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address)
+{
+ const IPAddressParams<4>& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(),
+ param.input.length()));
+ uint8_t ipAddress[4];
+ ASSERT_EQ(param.isValid, ParseIPv4Address(input, ipAddress));
+ if (param.isValid) {
+ for (size_t i = 0; i < sizeof(ipAddress); ++i) {
+ ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv4Address,
+ pkixnames_ParseIPv4Address,
+ testing::ValuesIn(IPV4_ADDRESSES));
+
+class pkixnames_ParseIPv6Address
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IPAddressParams<16>>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address)
+{
+ const IPAddressParams<16>& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(),
+ param.input.length()));
+ uint8_t ipAddress[16];
+ ASSERT_EQ(param.isValid, ParseIPv6Address(input, ipAddress));
+ if (param.isValid) {
+ for (size_t i = 0; i < sizeof(ipAddress); ++i) {
+ ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv6Address,
+ pkixnames_ParseIPv6Address,
+ testing::ValuesIn(IPV6_ADDRESSES));
+
+// This is an arbitrary string that is used to indicate that no SAN extension
+// should be put into the generated certificate. It needs to be different from
+// "" or any other subjectAltName value that we actually want to test, but its
+// actual value does not matter. Note that this isn't a correctly-encoded SAN
+// extension value!
+static const ByteString
+ NO_SAN(reinterpret_cast<const uint8_t*>("I'm a bad, bad, certificate"));
+
+struct CheckCertHostnameParams
+{
+ ByteString hostname;
+ ByteString subject;
+ ByteString subjectAltName;
+ Result result;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const CheckCertHostnameParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+class pkixnames_CheckCertHostname
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<CheckCertHostnameParams>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+#define WITH_SAN(r, ps, psan, result) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \
+ ps, \
+ psan, \
+ result \
+ }
+
+#define WITHOUT_SAN(r, ps, result) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \
+ ps, \
+ NO_SAN, \
+ result \
+ }
+
+static const uint8_t example_com[] = {
+ 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm'
+};
+
+// Note that We avoid zero-valued bytes in these IP addresses so that we don't
+// get false negatives from anti-NULL-byte defenses in dNSName decoding.
+static const uint8_t ipv4_addr_bytes[] = {
+ 1, 2, 3, 4
+};
+static const uint8_t ipv4_addr_bytes_as_str[] = "\x01\x02\x03\x04";
+static const uint8_t ipv4_addr_str[] = "1.2.3.4";
+static const uint8_t ipv4_addr_bytes_FFFFFFFF[8] = {
+ 1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff
+};
+
+static const uint8_t ipv4_compatible_ipv6_addr_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 1, 2, 3, 4
+};
+static const uint8_t ipv4_compatible_ipv6_addr_str[] = "::1.2.3.4";
+
+static const uint8_t ipv4_mapped_ipv6_addr_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0xFF, 0xFF,
+ 1, 2, 3, 4
+};
+static const uint8_t ipv4_mapped_ipv6_addr_str[] = "::FFFF:1.2.3.4";
+
+static const uint8_t ipv6_addr_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66, 0x77, 0x88,
+ 0x99, 0xaa, 0xbb, 0xcc,
+ 0xdd, 0xee, 0xff, 0x11
+};
+static const uint8_t ipv6_addr_bytes_as_str[] =
+ "\x11\x22\x33\x44"
+ "\x55\x66\x77\x88"
+ "\x99\xaa\xbb\xcc"
+ "\xdd\xee\xff\x11";
+
+static const uint8_t ipv6_addr_str[] =
+ "1122:3344:5566:7788:99aa:bbcc:ddee:ff11";
+
+static const uint8_t ipv6_other_addr_bytes[] = {
+ 0xff, 0xee, 0xdd, 0xcc,
+ 0xbb, 0xaa, 0x99, 0x88,
+ 0x77, 0x66, 0x55, 0x44,
+ 0x33, 0x22, 0x11, 0x00,
+};
+
+static const uint8_t ipv4_other_addr_bytes[] = {
+ 5, 6, 7, 8
+};
+static const uint8_t ipv4_other_addr_bytes_FFFFFFFF[] = {
+ 5, 6, 7, 8, 0xff, 0xff, 0xff, 0xff
+};
+
+static const uint8_t ipv4_addr_00000000_bytes[] = {
+ 0, 0, 0, 0
+};
+static const uint8_t ipv4_addr_FFFFFFFF_bytes[] = {
+ 0, 0, 0, 0
+};
+
+static const uint8_t ipv4_constraint_all_zeros_bytes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const uint8_t ipv6_addr_all_zeros_bytes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const uint8_t ipv6_constraint_all_zeros_bytes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const uint8_t ipv4_constraint_CIDR_16_bytes[] = {
+ 1, 2, 0, 0, 0xff, 0xff, 0, 0
+};
+static const uint8_t ipv4_constraint_CIDR_17_bytes[] = {
+ 1, 2, 0, 0, 0xff, 0xff, 0x80, 0
+};
+
+// The subnet is 1.2.0.0/16 but it is specified as 1.2.3.0/16
+static const uint8_t ipv4_constraint_CIDR_16_bad_addr_bytes[] = {
+ 1, 2, 3, 0, 0xff, 0xff, 0, 0
+};
+
+// Masks are supposed to be of the form <ones><zeros>, but this one is of the
+// form <ones><zeros><ones><zeros>.
+static const uint8_t ipv4_constraint_bad_mask_bytes[] = {
+ 1, 2, 3, 0, 0xff, 0, 0xff, 0
+};
+
+static const uint8_t ipv6_constraint_CIDR_16_bytes[] = {
+ 0x11, 0x22, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// The subnet is 1122::/16 but it is specified as 1122:3344::/16
+static const uint8_t ipv6_constraint_CIDR_16_bad_addr_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// Masks are supposed to be of the form <ones><zeros>, but this one is of the
+// form <ones><zeros><ones><zeros>.
+static const uint8_t ipv6_constraint_bad_mask_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const uint8_t ipv4_addr_truncated_bytes[] = {
+ 1, 2, 3
+};
+static const uint8_t ipv4_addr_overlong_bytes[] = {
+ 1, 2, 3, 4, 5
+};
+static const uint8_t ipv4_constraint_truncated_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0,
+};
+static const uint8_t ipv4_constraint_overlong_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0, 0
+};
+
+static const uint8_t ipv6_addr_truncated_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66, 0x77, 0x88,
+ 0x99, 0xaa, 0xbb, 0xcc,
+ 0xdd, 0xee, 0xff
+};
+static const uint8_t ipv6_addr_overlong_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66, 0x77, 0x88,
+ 0x99, 0xaa, 0xbb, 0xcc,
+ 0xdd, 0xee, 0xff, 0x11, 0x00
+};
+static const uint8_t ipv6_constraint_truncated_bytes[] = {
+ 0x11, 0x22, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0
+};
+static const uint8_t ipv6_constraint_overlong_bytes[] = {
+ 0x11, 0x22, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// Note that, for DNSNames, these test cases in CHECK_CERT_HOSTNAME_PARAMS are
+// mostly about testing different scenerios regarding the structure of entries
+// in the subjectAltName and subject of the certificate, than about the how
+// specific presented identifier values are matched against the reference
+// identifier values. This is because we also use the test cases in
+// DNSNAMES_VALIDITY to test CheckCertHostname. Consequently, tests about
+// whether specific presented DNSNames (including wildcards, in particular) are
+// matched against a reference DNSName only need to be added to
+// DNSNAMES_VALIDITY, and not here.
+static const CheckCertHostnameParams CHECK_CERT_HOSTNAME_PARAMS[] =
+{
+ // This is technically illegal. PrintableString is defined in such a way that
+ // '*' is not an allowed character, but there are many real-world certificates
+ // that are encoded this way.
+ WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::PrintableString)),
+ Success),
+ WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::UTF8String)),
+ Success),
+
+ // Many certificates use TeletexString when encoding wildcards in CN-IDs
+ // because PrintableString is defined as not allowing '*' and UTF8String was,
+ // at one point in history, considered too new to depend on for compatibility.
+ // We accept TeletexString-encoded CN-IDs when they don't contain any escape
+ // sequences. The reference I used for the escape codes was
+ // https://tools.ietf.org/html/rfc1468. The escaping mechanism is actually
+ // pretty complex and these tests don't even come close to testing all the
+ // possibilities.
+ WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::TeletexString)),
+ Success),
+ // "ESC ( B" ({0x1B,0x50,0x42}) is the escape code to switch to ASCII, which
+ // is redundant because it already the default.
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("\x1B(B*.example.com", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("*.example\x1B(B.com", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("*.example.com\x1B(B", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // "ESC $ B" ({0x1B,0x24,0x42}) is the escape code to switch to
+ // JIS X 0208-1983 (a Japanese character set).
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("\x1B$B*.example.com", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("*.example.com\x1B$B", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Match a DNSName SAN entry with a redundant (ignored) matching CN-ID.
+ WITH_SAN("a", RDN(CN("a")), DNSName("a"), Success),
+ // Match a DNSName SAN entry when there is an CN-ID that doesn't match.
+ WITH_SAN("b", RDN(CN("a")), DNSName("b"), Success),
+ // Do not match a CN-ID when there is a valid DNSName SAN Entry.
+ WITH_SAN("a", RDN(CN("a")), DNSName("b"), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a CN-ID when there is a malformed DNSName SAN Entry.
+ WITH_SAN("a", RDN(CN("a")), DNSName("!"), Result::ERROR_BAD_DER),
+ // Do not match a matching CN-ID when there is a valid IPAddress SAN entry.
+ WITH_SAN("a", RDN(CN("a")), IPAddress(ipv4_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a matching CN-ID when there is a malformed IPAddress SAN entry.
+ WITH_SAN("a", RDN(CN("a")), IPAddress(example_com),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Match a DNSName against a matching CN-ID when there is a SAN, but the SAN
+ // does not contain an DNSName or IPAddress entry.
+ WITH_SAN("a", RDN(CN("a")), RFC822Name("foo@example.com"), Success),
+ // Match a matching CN-ID when there is no SAN.
+ WITHOUT_SAN("a", RDN(CN("a")), Success),
+ // Do not match a mismatching CN-ID when there is no SAN.
+ WITHOUT_SAN("a", RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // The first DNSName matches.
+ WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success),
+ // The last DNSName matches.
+ WITH_SAN("b", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success),
+ // The middle DNSName matches.
+ WITH_SAN("b", RDN(CN("foo")),
+ DNSName("a") + DNSName("b") + DNSName("c"), Success),
+ // After an IP address.
+ WITH_SAN("b", RDN(CN("foo")),
+ IPAddress(ipv4_addr_bytes) + DNSName("b"), Success),
+ // Before an IP address.
+ WITH_SAN("a", RDN(CN("foo")),
+ DNSName("a") + IPAddress(ipv4_addr_bytes), Success),
+ // Between an RFC822Name and an IP address.
+ WITH_SAN("b", RDN(CN("foo")),
+ RFC822Name("foo@example.com") + DNSName("b") +
+ IPAddress(ipv4_addr_bytes),
+ Success),
+ // Duplicate DNSName.
+ WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("a"), Success),
+ // After an invalid DNSName.
+ WITH_SAN("b", RDN(CN("foo")), DNSName("!") + DNSName("b"),
+ Result::ERROR_BAD_DER),
+
+ // http://tools.ietf.org/html/rfc5280#section-4.2.1.6: "If the subjectAltName
+ // extension is present, the sequence MUST contain at least one entry."
+ // However, for compatibility reasons, this is not enforced. See bug 1143085.
+ // This case is treated as if the extension is not present (i.e. name
+ // matching falls back to the subject CN).
+ WITH_SAN("a", RDN(CN("a")), ByteString(), Success),
+ WITH_SAN("a", RDN(CN("b")), ByteString(), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // http://tools.ietf.org/html/rfc5280#section-4.1.2.6 says "If subject naming
+ // information is present only in the subjectAltName extension (e.g., a key
+ // bound only to an email address or URI), then the subject name MUST be an
+ // empty sequence and the subjectAltName extension MUST be critical." So, we
+ // have to support an empty subject. We don't enforce that the SAN must be
+ // critical or even that there is a SAN when the subject is empty, though.
+ WITH_SAN("a", ByteString(), DNSName("a"), Success),
+ // Make sure we return ERROR_BAD_CERT_DOMAIN and not ERROR_BAD_DER.
+ WITHOUT_SAN("a", ByteString(), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Two CNs in the same RDN, both match.
+ WITHOUT_SAN("a", RDN(CN("a") + CN("a")), Success),
+ // Two CNs in the same RDN, both DNSNames, first one matches.
+ WITHOUT_SAN("a", RDN(CN("a") + CN("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in the same RDN, both DNSNames, last one matches.
+ WITHOUT_SAN("b", RDN(CN("a") + CN("b")), Success),
+ // Two CNs in the same RDN, first one matches, second isn't a DNSName.
+ WITHOUT_SAN("a", RDN(CN("a") + CN("Not a DNSName")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in the same RDN, first one not a DNSName, second matches.
+ WITHOUT_SAN("b", RDN(CN("Not a DNSName") + CN("b")), Success),
+
+ // Two CNs in separate RDNs, both match.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("a")), Success),
+ // Two CNs in separate RDNs, both DNSNames, first one matches.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in separate RDNs, both DNSNames, last one matches.
+ WITHOUT_SAN("b", RDN(CN("a")) + RDN(CN("b")), Success),
+ // Two CNs in separate RDNs, first one matches, second isn't a DNSName.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("Not a DNSName")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in separate RDNs, first one not a DNSName, second matches.
+ WITHOUT_SAN("b", RDN(CN("Not a DNSName")) + RDN(CN("b")), Success),
+
+ // One CN, one RDN, CN is the first AVA in the RDN, CN matches.
+ WITHOUT_SAN("a", RDN(CN("a") + OU("b")), Success),
+ // One CN, one RDN, CN is the first AVA in the RDN, CN does not match.
+ WITHOUT_SAN("b", RDN(CN("a") + OU("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // One CN, one RDN, CN is not the first AVA in the RDN, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a") + CN("b")), Success),
+ // One CN, one RDN, CN is not the first AVA in the RDN, CN does not match.
+ WITHOUT_SAN("a", RDN(OU("a") + CN("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // One CN, multiple RDNs, CN is in the first RDN, CN matches.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(OU("b")), Success),
+ // One CN, multiple RDNs, CN is in the first RDN, CN does not match.
+ WITHOUT_SAN("b", RDN(CN("a")) + RDN(OU("b")), Result::ERROR_BAD_CERT_DOMAIN),
+ // One CN, multiple RDNs, CN is not in the first RDN, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")), Success),
+ // One CN, multiple RDNs, CN is not in the first RDN, CN does not match.
+ WITHOUT_SAN("a", RDN(OU("a")) + RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // One CN, one RDN, CN is not in the first or last AVA, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a") + CN("b") + OU("c")), Success),
+ // One CN, multiple RDNs, CN is not in the first or last RDN, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")) + RDN(OU("c")), Success),
+
+ // Empty CN does not match.
+ WITHOUT_SAN("example.com", RDN(CN("")), Result::ERROR_BAD_CERT_DOMAIN),
+
+ WITHOUT_SAN("uses_underscore.example.com", RDN(CN("*.example.com")), Success),
+ WITHOUT_SAN("a.uses_underscore.example.com",
+ RDN(CN("*.uses_underscore.example.com")), Success),
+ WITH_SAN("uses_underscore.example.com", RDN(CN("foo")),
+ DNSName("*.example.com"), Success),
+ WITH_SAN("a.uses_underscore.example.com", RDN(CN("foo")),
+ DNSName("*.uses_underscore.example.com"), Success),
+
+ // Do not match a DNSName that is encoded in a malformed IPAddress.
+ WITH_SAN("example.com", RDN(CN("foo")), IPAddress(example_com),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // We skip over the malformed IPAddress and match the DNSName entry because
+ // we've heard reports of real-world certificates that have malformed
+ // IPAddress SANs.
+ WITH_SAN("example.org", RDN(CN("foo")),
+ IPAddress(example_com) + DNSName("example.org"), Success),
+
+ WITH_SAN("example.com", RDN(CN("foo")),
+ DNSName("!") + DNSName("example.com"), Result::ERROR_BAD_DER),
+
+ // Match a matching IPv4 address SAN entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes),
+ Success),
+ // Match a matching IPv4 addresses in the CN when there is no SAN
+ WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), Success),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with
+ // a DNSName entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with
+ // a non-matching IPAddress entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ IPAddress(ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN),
+ // Match a matching IPv4 address in the CN when there is a SAN with a
+ // non-IPAddress, non-DNSName entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ RFC822Name("foo@example.com"), Success),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with a
+ // malformed IPAddress entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with a
+ // malformed DNSName entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ DNSName("!"), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // We don't match IPv6 addresses in the CN, regardless of whether there is
+ // a SAN.
+ WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)),
+ DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)),
+ IPAddress(ipv6_addr_bytes), Success),
+ WITH_SAN(ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv6_addr_bytes),
+ Success),
+
+ // We don't match the binary encoding of the bytes of IP addresses in the
+ // CN.
+ WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_bytes_as_str)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_bytes_as_str)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // We don't match IP addresses with DNSName SANs.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")),
+ DNSName(ipv4_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")), DNSName(ipv4_addr_str),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN("foo")),
+ DNSName(ipv6_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN("foo")), DNSName(ipv6_addr_str),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Do not match an IPv4 reference ID against the equivalent IPv4-compatible
+ // IPv6 SAN entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_compatible_ipv6_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6
+ // SAN entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_mapped_ipv6_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match an IPv4-compatible IPv6 reference ID against the equivalent
+ // IPv4 SAN entry.
+ WITH_SAN(ipv4_compatible_ipv6_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6
+ // SAN entry.
+ WITH_SAN(ipv4_mapped_ipv6_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Test that the presence of an otherName entry is handled appropriately.
+ // (The actual value of the otherName entry isn't important - that's not what
+ // we're testing here.)
+ WITH_SAN("example.com", ByteString(),
+ // The tag for otherName is CONTEXT_SPECIFIC | CONSTRUCTED | 0
+ TLV((2 << 6) | (1 << 5) | 0, ByteString()) + DNSName("example.com"),
+ Success),
+ WITH_SAN("example.com", ByteString(),
+ TLV((2 << 6) | (1 << 5) | 0, ByteString()),
+ Result::ERROR_BAD_CERT_DOMAIN),
+};
+
+ByteString
+CreateCert(const ByteString& subject, const ByteString& subjectAltName,
+ EndEntityOrCA endEntityOrCA = EndEntityOrCA::MustBeEndEntity)
+{
+ ByteString serialNumber(CreateEncodedSerialNumber(1));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(Name(RDN(CN("issuer"))));
+ EXPECT_FALSE(ENCODING_FAILED(issuerDER));
+
+ ByteString extensions[2];
+ if (subjectAltName != NO_SAN) {
+ extensions[0] = CreateEncodedSubjectAltName(subjectAltName);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ }
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ // Currently, these tests assume that if we're creating a CA certificate, it
+ // will not have a subjectAlternativeName extension. If that assumption
+ // changes, this code will have to be updated. Ideally this would be
+ // ASSERT_EQ, but that inserts a 'return;', which doesn't match this
+ // function's return type.
+ EXPECT_EQ(subjectAltName, NO_SAN);
+ extensions[0] = CreateEncodedBasicConstraints(true, nullptr,
+ Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ }
+
+ ScopedTestKeyPair keyPair(CloneReusedKeyPair());
+ return CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow, Name(subject), *keyPair,
+ extensions, *keyPair, sha256WithRSAEncryption());
+}
+
+TEST_P(pkixnames_CheckCertHostname, CheckCertHostname)
+{
+ const CheckCertHostnameParams& param(GetParam());
+
+ ByteString cert(CreateCert(param.subject, param.subjectAltName));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.hostname.data(),
+ param.hostname.length()));
+
+ ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput,
+ mNameMatchingPolicy));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname,
+ pkixnames_CheckCertHostname,
+ testing::ValuesIn(CHECK_CERT_HOSTNAME_PARAMS));
+
+TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence)
+{
+ // A certificate with a truly empty SAN extension (one that doesn't even
+ // contain a SEQUENCE at all) is malformed. If we didn't treat this as
+ // malformed then we'd have to treat it like the CN_EmptySAN cases.
+
+ ByteString serialNumber(CreateEncodedSerialNumber(1));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString extensions[2];
+ extensions[0] = CreateEncodedEmptySubjectAltName();
+ ASSERT_FALSE(ENCODING_FAILED(extensions[0]));
+
+ ScopedTestKeyPair keyPair(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber,
+ Name(RDN(CN("issuer"))), oneDayBeforeNow, oneDayAfterNow,
+ Name(RDN(CN("a"))), *keyPair, extensions,
+ *keyPair, sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+
+ static const uint8_t a[] = { 'a' };
+ ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
+ CheckCertHostname(certInput, Input(a), mNameMatchingPolicy));
+}
+
+class pkixnames_CheckCertHostname_PresentedMatchesReference
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<PresentedMatchesReference>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN)
+{
+ // Since there is no SAN, a valid presented DNS ID in the subject CN field
+ // should result in a match.
+
+ const PresentedMatchesReference& param(GetParam());
+
+ ByteString cert(CreateCert(RDN(CN(param.presentedDNSID)), NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(),
+ param.referenceDNSID.length()));
+
+ ASSERT_EQ(param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN,
+ CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy));
+}
+
+TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference,
+ SubjectAltName_CNNotDNSName)
+{
+ // A DNSName SAN entry should match, regardless of the contents of the
+ // subject CN.
+
+ const PresentedMatchesReference& param(GetParam());
+
+ ByteString cert(CreateCert(RDN(CN("Common Name")),
+ DNSName(param.presentedDNSID)));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(),
+ param.referenceDNSID.length()));
+ Result expectedResult
+ = param.expectedResult != Success ? param.expectedResult
+ : param.expectedMatches ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput,
+ mNameMatchingPolicy));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_DNSID_MATCH_PARAMS,
+ pkixnames_CheckCertHostname_PresentedMatchesReference,
+ testing::ValuesIn(DNSID_MATCH_PARAMS));
+
+TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN)
+{
+ // Make sure we don't have the similar problems that strcasecmp and others
+ // have with the other kinds of "i" and "I" commonly used in Turkish locales,
+ // when we're matching a CN due to lack of subjectAltName.
+
+ const InputValidity& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length()));
+
+ ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Result expectedResult = (InputsAreEqual(LOWERCASE_I, input) ||
+ InputsAreEqual(UPPERCASE_I, input))
+ ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I,
+ mNameMatchingPolicy));
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I,
+ mNameMatchingPolicy));
+}
+
+TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN)
+{
+ // Make sure we don't have the similar problems that strcasecmp and others
+ // have with the other kinds of "i" and "I" commonly used in Turkish locales,
+ // when we're matching a dNSName in the SAN.
+
+ const InputValidity& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length()));
+
+ ByteString cert(CreateCert(RDN(CN("Common Name")), DNSName(param.input)));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Result expectedResult
+ = (!param.isValidPresentedID) ? Result::ERROR_BAD_DER
+ : (InputsAreEqual(LOWERCASE_I, input) ||
+ InputsAreEqual(UPPERCASE_I, input)) ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I,
+ mNameMatchingPolicy));
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I,
+ mNameMatchingPolicy));
+}
+
+class pkixnames_CheckCertHostname_IPV4_Addresses
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IPAddressParams<4>>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
+ ValidIPv4AddressInIPAddressSAN)
+{
+ // When the reference hostname is a valid IPv4 address, a correctly-formed
+ // IPv4 Address SAN matches it.
+
+ const IPAddressParams<4>& param(GetParam());
+
+ ByteString cert(CreateCert(RDN(CN("Common Name")),
+ IPAddress(param.expectedValueIfValid)));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.input.data(),
+ param.input.length()));
+
+ ASSERT_EQ(param.isValid ? Success : Result::ERROR_BAD_CERT_DOMAIN,
+ CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy));
+}
+
+TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
+ ValidIPv4AddressInCN_NoSAN)
+{
+ // When the reference hostname is a valid IPv4 address, a correctly-formed
+ // IPv4 Address in the CN matches it when there is no SAN.
+
+ const IPAddressParams<4>& param(GetParam());
+
+ SCOPED_TRACE(param.input.c_str());
+
+ ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.input.data(),
+ param.input.length()));
+
+ // Some of the invalid IPv4 addresses are valid DNS names!
+ Result expectedResult = (param.isValid || IsValidReferenceDNSID(hostnameInput))
+ ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput,
+ mNameMatchingPolicy));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_IPV4_ADDRESSES,
+ pkixnames_CheckCertHostname_IPV4_Addresses,
+ testing::ValuesIn(IPV4_ADDRESSES));
+
+struct NameConstraintParams
+{
+ ByteString subject;
+ ByteString subjectAltName;
+ ByteString subtrees;
+ Result expectedPermittedSubtreesResult;
+ Result expectedExcludedSubtreesResult;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const NameConstraintParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+static ByteString
+PermittedSubtrees(const ByteString& generalSubtrees)
+{
+ return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ generalSubtrees);
+}
+
+static ByteString
+ExcludedSubtrees(const ByteString& generalSubtrees)
+{
+ return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ generalSubtrees);
+}
+
+// Does not encode min or max.
+static ByteString
+GeneralSubtree(const ByteString& base)
+{
+ return TLV(der::SEQUENCE, base);
+}
+
+static const NameConstraintParams NAME_CONSTRAINT_PARAMS[] =
+{
+ /////////////////////////////////////////////////////////////////////////////
+ // XXX: Malformed name constraints for supported types of names are ignored
+ // when there are no names of that type to constrain.
+ { ByteString(), NO_SAN,
+ GeneralSubtree(DNSName("!")),
+ Success, Success
+ },
+ { // DirectoryName constraints are an exception, because *every* certificate
+ // has at least one DirectoryName (tbsCertificate.subject).
+ ByteString(), NO_SAN,
+ GeneralSubtree(Name(ByteString(reinterpret_cast<const uint8_t*>("!"), 1))),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(RFC822Name("!")),
+ Success, Success
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Edge cases of name constraint absolute vs. relative and subdomain matching
+ // that are not clearly explained in RFC 5280. (See the long comment above
+ // MatchPresentedDNSIDWithReferenceDNSID.)
+
+ // 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?
+ { ByteString(), DNSName("host.example.com"),
+ GeneralSubtree(DNSName("host.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // This test case is an example from RFC 5280.
+ ByteString(), DNSName("host1.example.com"),
+ GeneralSubtree(DNSName("host.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { ByteString(), RFC822Name("a@host.example.com"),
+ GeneralSubtree(RFC822Name("host.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // This test case is an example from RFC 5280.
+ ByteString(), RFC822Name("a@host1.example.com"),
+ GeneralSubtree(RFC822Name("host.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // 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?
+ { // This test case is an example from RFC 5280.
+ ByteString(), DNSName("www.host.example.com"),
+ GeneralSubtree(DNSName( "host.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The subdomain matching rule for host names that do not start with "." is
+ // different for RFC822Names than for DNSNames!
+ ByteString(), RFC822Name("a@www.host.example.com"),
+ GeneralSubtree(RFC822Name( "host.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE,
+ Success
+ },
+
+ // 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"?
+ { ByteString(), DNSName("bigfoo.bar.com"),
+ GeneralSubtree(DNSName( "foo.bar.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { ByteString(), RFC822Name("a@bigfoo.bar.com"),
+ GeneralSubtree(RFC822Name( "foo.bar.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // 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"?
+ { ByteString(), DNSName("www.example.com"),
+ GeneralSubtree(DNSName( ".example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // When there is no Local-part, an RFC822 name constraint's domain may
+ // start with '.', and the semantics are the same as for DNSNames.
+ ByteString(), RFC822Name("a@www.example.com"),
+ GeneralSubtree(RFC822Name( ".example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // When there is a Local-part, an RFC822 name constraint's domain must not
+ // start with '.'.
+ ByteString(), RFC822Name("a@www.example.com"),
+ GeneralSubtree(RFC822Name( "a@.example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // Check that we only allow subdomains to match.
+ ByteString(), DNSName( "example.com"),
+ GeneralSubtree(DNSName(".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Check that we only allow subdomains to match.
+ ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Check that we don't get confused and consider "b" == "."
+ ByteString(), DNSName("bexample.com"),
+ GeneralSubtree(DNSName(".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Check that we don't get confused and consider "b" == "."
+ ByteString(), RFC822Name("a@bexample.com"),
+ GeneralSubtree(RFC822Name( ".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // Q: Is there a way to prevent subdomain matches?
+ // (This is tested in a different set of tests because it requires a
+ // combination of permittedSubtrees and excludedSubtrees.)
+
+ // 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?
+ //
+ { // The DNSName in the constraint is not valid because constraint DNS IDs
+ // are not allowed to be absolute.
+ ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName("example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name( "example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // The DNSName in the SAN is not valid because presented DNS IDs are not
+ // allowed to be absolute.
+ ByteString(), DNSName("example.com."),
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { ByteString(), RFC822Name("a@example.com."),
+ GeneralSubtree(RFC822Name( "example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // The presented DNSName is the same length as the constraint, because the
+ // subdomain is only one character long and because the constraint both
+ // begins and ends with ".". But, it doesn't matter because absolute names
+ // are not allowed for DNSName constraints.
+ ByteString(), DNSName("p.example.com"),
+ GeneralSubtree(DNSName(".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // The presented DNSName is the same length as the constraint, because the
+ // subdomain is only one character long and because the constraint both
+ // begins and ends with ".".
+ ByteString(), RFC822Name("a@p.example.com"),
+ GeneralSubtree(RFC822Name( ".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // Same as previous test case, but using a wildcard presented ID.
+ ByteString(), DNSName("*.example.com"),
+ GeneralSubtree(DNSName(".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // Same as previous test case, but using a wildcard presented ID, which is
+ // invalid in an RFC822Name.
+ ByteString(), RFC822Name("a@*.example.com"),
+ GeneralSubtree(RFC822Name( ".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+
+ // Q: Are "" and "." valid DNSName constraints? If so, what do they mean?
+ { ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName("")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name("")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The malformed (absolute) presented ID does not match.
+ ByteString(), DNSName("example.com."),
+ GeneralSubtree(DNSName("")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@example.com."),
+ GeneralSubtree(RFC822Name("")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // Invalid syntax in name constraint
+ ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // Invalid syntax in name constraint
+ ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { ByteString(), DNSName("example.com."),
+ GeneralSubtree(DNSName(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@example.com."),
+ GeneralSubtree(RFC822Name(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Basic IP Address constraints (non-CN-ID)
+
+ // The Mozilla CA Policy says this means "no IPv4 addresses allowed."
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_addr_00000000_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_addr_FFFFFFFF_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ // The Mozilla CA Policy says this means "no IPv6 addresses allowed."
+ { ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv6_addr_all_zeros_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ // RFC 5280 doesn't partition IP address constraints into separate IPv4 and
+ // IPv6 categories, so a IPv4 permittedSubtrees constraint excludes all IPv6
+ // addresses, and vice versa.
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // IPv4 Subnets
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_17_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bad_addr_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_bad_mask_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // IPv6 Subnets
+ { ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv6_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bad_addr_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv6_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_bad_mask_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // Malformed presented IP addresses and constraints
+
+ { // The presented IPv4 address is empty
+ ByteString(), IPAddress(),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 address is truncated
+ ByteString(), IPAddress(ipv4_addr_truncated_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 address is too long
+ ByteString(), IPAddress(ipv4_addr_overlong_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 constraint is empty
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress()),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 constraint is truncated
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 constraint is too long
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 address is empty
+ ByteString(), IPAddress(),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 address is truncated
+ ByteString(), IPAddress(ipv6_addr_truncated_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 address is too long
+ ByteString(), IPAddress(ipv6_addr_overlong_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 constraint is empty
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress()),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 constraint is truncated
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 constraint is too long
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // XXX: We don't reject malformed name constraints when there are no names of
+ // that type.
+ { ByteString(), NO_SAN, GeneralSubtree(DNSName("!")),
+ Success, Success
+ },
+ { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_addr_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_addr_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN, GeneralSubtree(RFC822Name("\0")),
+ Success, Success
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Basic CN-ID DNSName constraint tests.
+
+ { // Empty Name is ignored for DNSName constraints.
+ ByteString(), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // Empty CN is ignored for DNSName constraints because it isn't a
+ // syntactically-valid DNSName.
+ //
+ // NSS gives different results.
+ RDN(CN("")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // IP Address is ignored for DNSName constraints.
+ //
+ // NSS gives different results.
+ RDN(CN("1.2.3.4")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // OU has something that looks like a dNSName that matches.
+ RDN(OU("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // OU has something that looks like a dNSName that does not match.
+ RDN(OU("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // NSS gives different results.
+ RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // DNSName CN-ID match is detected when there is a SAN w/o any DNSName or
+ // IPAddress
+ RDN(CN("a.example.com")), RFC822Name("foo@example.com"),
+ GeneralSubtree(DNSName("a.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // DNSName CN-ID mismatch is detected when there is a SAN w/o any DNSName
+ // or IPAddress
+ RDN(CN("a.example.com")), RFC822Name("foo@example.com"),
+ GeneralSubtree(DNSName("b.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // DNSName CN-ID match not reported when there is a DNSName SAN
+ RDN(CN("a.example.com")), DNSName("b.example.com"),
+ GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // DNSName CN-ID mismatch not reported when there is a DNSName SAN
+ RDN(CN("a.example.com")), DNSName("b.example.com"),
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE,
+ },
+ { // DNSName CN-ID match not reported when there is an IPAddress SAN
+ RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // DNSName CN-ID mismatch not reported when there is an IPAddress SAN
+ RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Success
+ },
+
+ { // IPAddress CN-ID match is detected when there is a SAN w/o any DNSName or
+ // IPAddress
+ RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // IPAddress CN-ID mismatch is detected when there is a SAN w/o any DNSName
+ // or IPAddress
+ RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"),
+ GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // IPAddress CN-ID match not reported when there is a DNSName SAN
+ RDN(CN(ipv4_addr_str)), DNSName("b.example.com"),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Success, Success
+ },
+ { // IPAddress CN-ID mismatch not reported when there is a DNSName SAN
+ RDN(CN(ipv4_addr_str)), DNSName("b.example.com"),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Success, Success
+ },
+ { // IPAddress CN-ID match not reported when there is an IPAddress SAN
+ RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // IPAddress CN-ID mismatch not reported when there is an IPAddress SAN
+ RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Test that constraints are applied to the most specific (last) CN, and only
+ // that CN-ID.
+
+ { // Name constraint only matches a.example.com, but the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.)
+ RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Name constraint only matches a.example.com, but the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.)
+ RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Name constraint only permits b.example.com, and the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.)
+ RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Name constraint only permits b.example.com, and the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.)
+ RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Additional RFC822 name constraint tests. There are more tests regarding
+ // the DNSName part of the constraint mixed into the DNSName constraint
+ // tests.
+
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name("a@example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ // Bug 1056773: name constraints that omit Local-part but include '@' are
+ // invalid.
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name("@example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("@example.com"),
+ GeneralSubtree(RFC822Name("@example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("example.com"),
+ GeneralSubtree(RFC822Name("@example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@mail.example.com"),
+ GeneralSubtree(RFC822Name("a@*.example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@*.example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("@example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("@a.example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Test name constraints with underscores.
+ //
+ { ByteString(), DNSName("uses_underscore.example.com"),
+ GeneralSubtree(DNSName("uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("uses_underscore.example.com"),
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("a.uses_underscore.example.com"),
+ GeneralSubtree(DNSName("uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("a@uses_underscore.example.com"),
+ GeneralSubtree(RFC822Name("uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("uses_underscore@example.com"),
+ GeneralSubtree(RFC822Name("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("a@a.uses_underscore.example.com"),
+ GeneralSubtree(RFC822Name(".uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Name constraint tests that relate to having an empty SAN. According to RFC
+ // 5280 this isn't valid, but we allow it for compatibility reasons (see bug
+ // 1143085).
+ { // For DNSNames, we fall back to the subject CN.
+ RDN(CN("a.example.com")), ByteString(),
+ GeneralSubtree(DNSName("a.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // For RFC822Names, we do not fall back to the subject emailAddress.
+ // This new implementation seems to conform better to the standards for
+ // RFC822 name constraints, by only applying the name constraints to
+ // emailAddress names in the certificate subject if there is no
+ // subjectAltName extension in the cert.
+ // In this case, the presence of the (empty) SAN extension means that RFC822
+ // name constraints are not enforced on the emailAddress attributes of the
+ // subject.
+ RDN(emailAddress("a@example.com")), ByteString(),
+ GeneralSubtree(RFC822Name("a@example.com")),
+ Success, Success
+ },
+ { // Compare this to the case where there is no SAN (i.e. the name
+ // constraints are enforced, because the extension is not present at all).
+ RDN(emailAddress("a@example.com")), NO_SAN,
+ GeneralSubtree(RFC822Name("a@example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // DirectoryName name constraint tests
+
+ { // One AVA per RDN
+ RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) +
+ RDN(CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // RDNs can have multiple AVAs.
+ RDN(OU("Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") +
+ CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The constraint is a prefix of the subject DN.
+ RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The name constraint is not a prefix of the subject DN.
+ // Note that for excludedSubtrees, we simply prohibit any non-empty
+ // directoryName constraint to ensure we are not being too lenient.
+ RDN(OU("Other Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Same as the previous one, but one RDN with multiple AVAs.
+ RDN(OU("Other Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") +
+ CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // With multiple AVAs per RDN in the subject DN, the constraint is not a
+ // prefix of the subject DN.
+ RDN(OU("Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The subject DN RDN has multiple AVAs, but the name constraint has only
+ // one AVA per RDN.
+ RDN(OU("Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The name constraint RDN has multiple AVAs, but the subject DN has only
+ // one AVA per RDN.
+ RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") +
+ CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // In this case, the constraint uses a different encoding from the subject.
+ // We consider them to match because we allow UTF8String and
+ // PrintableString to compare equal when their contents are equal.
+ RDN(OU("Example Organization", der::UTF8String)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::PrintableString)) +
+ RDN(CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Same as above, but with UTF8String/PrintableString switched.
+ RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::UTF8String)) +
+ RDN(CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // If the contents aren't the same, then they shouldn't match.
+ RDN(OU("Other Example Organization", der::UTF8String)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::PrintableString)) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Only UTF8String and PrintableString are considered equivalent.
+ RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::TeletexString)) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Some additional tests for completeness:
+ // Ensure that wildcards are handled:
+ { RDN(CN("*.example.com")), NO_SAN, GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("*.example.com"),
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("www.example.com"),
+ GeneralSubtree(DNSName("*.example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ // Handle multiple name constraint entries:
+ { RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DNSName("example.org")) +
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName("example.org")) +
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Handle multiple names in subject alternative name extension:
+ { ByteString(), DNSName("example.com") + DNSName("example.org"),
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Handle a mix of DNSName and DirectoryName:
+ { RDN(OU("Example Organization")), DNSName("example.com"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) +
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { RDN(OU("Other Example Organization")), DNSName("example.com"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) +
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { RDN(OU("Example Organization")), DNSName("example.org"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) +
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Handle a certificate with no DirectoryName:
+ { ByteString(), DNSName("example.com"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+};
+
+class pkixnames_CheckNameConstraints
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<NameConstraintParams>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_CheckNameConstraints,
+ NameConstraintsEnforcedForDirectlyIssuedEndEntity)
+{
+ // Test that name constraints are enforced on a certificate directly issued by
+ // a certificate with the given name constraints.
+
+ const NameConstraintParams& param(GetParam());
+
+ ByteString certDER(CreateCert(param.subject, param.subjectAltName));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+ BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
+ ASSERT_EQ(Success, cert.Init());
+
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedPermittedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees) +
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ((param.expectedPermittedSubtreesResult ==
+ param.expectedExcludedSubtreesResult)
+ ? param.expectedExcludedSubtreesResult
+ : Result::ERROR_CERT_NOT_IN_NAME_SPACE,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraints,
+ pkixnames_CheckNameConstraints,
+ testing::ValuesIn(NAME_CONSTRAINT_PARAMS));
+
+// The |subjectAltName| param is not used for these test cases (hence the use of
+// "NO_SAN").
+static const NameConstraintParams NO_FALLBACK_NAME_CONSTRAINT_PARAMS[] =
+{
+ // The only difference between end-entities being verified for serverAuth and
+ // intermediates or end-entities being verified for other uses is that for
+ // the latter cases, there is no fallback matching of DNSName entries to the
+ // subject common name.
+ { RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ // Sanity-check that name constraints are in fact enforced in these cases.
+ { RDN(CN("Example Name")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // (In this implementation, if a DirectoryName is in excludedSubtrees, nothing
+ // is considered to be in the name space.)
+ { RDN(CN("Other Example Name")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+};
+
+class pkixnames_CheckNameConstraintsOnIntermediate
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<NameConstraintParams>
+{
+};
+
+TEST_P(pkixnames_CheckNameConstraintsOnIntermediate,
+ NameConstraintsEnforcedOnIntermediate)
+{
+ // Test that name constraints are enforced on an intermediate certificate
+ // directly issued by a certificate with the given name constraints.
+
+ const NameConstraintParams& param(GetParam());
+
+ ByteString certDER(CreateCert(param.subject, NO_SAN,
+ EndEntityOrCA::MustBeCA));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+ BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr);
+ ASSERT_EQ(Success, cert.Init());
+
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedPermittedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees) +
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsOnIntermediate,
+ pkixnames_CheckNameConstraintsOnIntermediate,
+ testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS));
+
+class pkixnames_CheckNameConstraintsForNonServerAuthUsage
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<NameConstraintParams>
+{
+};
+
+TEST_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage,
+ NameConstraintsEnforcedForNonServerAuthUsage)
+{
+ // Test that for key purposes other than serverAuth, fallback to the subject
+ // common name does not occur.
+
+ const NameConstraintParams& param(GetParam());
+
+ ByteString certDER(CreateCert(param.subject, NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+ BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
+ ASSERT_EQ(Success, cert.Init());
+
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedPermittedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_clientAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_clientAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees) +
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_clientAuth));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage,
+ pkixnames_CheckNameConstraintsForNonServerAuthUsage,
+ testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS));
diff --git a/lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp b/lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
new file mode 100644
index 000000000..ffc987c86
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
@@ -0,0 +1,145 @@
+/* -*- 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 "pkixgtest.h"
+#include "pkixder.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+class CreateEncodedOCSPRequestTrustDomain final
+ : public EverythingFailsByDefaultTrustDomain
+{
+private:
+ Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t *digestBuf, size_t digestBufLen)
+ override
+ {
+ return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
+ override
+ {
+ return Success;
+ }
+};
+
+class pkixocsp_CreateEncodedOCSPRequest : public ::testing::Test
+{
+protected:
+ void MakeIssuerCertIDComponents(const char* issuerASCII,
+ /*out*/ ByteString& issuerDER,
+ /*out*/ ByteString& issuerSPKI)
+ {
+ issuerDER = CNToDERName(issuerASCII);
+ ASSERT_FALSE(ENCODING_FAILED(issuerDER));
+
+ ScopedTestKeyPair keyPair(GenerateKeyPair());
+ ASSERT_TRUE(keyPair.get());
+ issuerSPKI = keyPair->subjectPublicKeyInfo;
+ }
+
+ CreateEncodedOCSPRequestTrustDomain trustDomain;
+};
+
+// Test that the large length of the child serial number causes
+// CreateEncodedOCSPRequest to fail.
+TEST_F(pkixocsp_CreateEncodedOCSPRequest, ChildCertLongSerialNumberTest)
+{
+ static const uint8_t UNSUPPORTED_LEN = 128; // must be larger than 127
+
+ ByteString serialNumberString;
+ // tag + length + value is 1 + 2 + UNSUPPORTED_LEN
+ // Encoding the length takes two bytes: one byte to indicate that a
+ // second byte follows, and the second byte to indicate the length.
+ serialNumberString.push_back(0x80 + 1);
+ serialNumberString.push_back(UNSUPPORTED_LEN);
+ // value is 0x010000...00
+ serialNumberString.push_back(0x01);
+ for (size_t i = 1; i < UNSUPPORTED_LEN; ++i) {
+ serialNumberString.push_back(0x00);
+ }
+
+ ByteString issuerDER;
+ ByteString issuerSPKI;
+ ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER,
+ issuerSPKI));
+
+ Input issuer;
+ ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length()));
+
+ Input spki;
+ ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length()));
+
+ Input serialNumber;
+ ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(),
+ serialNumberString.length()));
+
+ uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH];
+ size_t ocspRequestLength;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ CreateEncodedOCSPRequest(trustDomain,
+ CertID(issuer, spki, serialNumber),
+ ocspRequest, ocspRequestLength));
+}
+
+// Test that CreateEncodedOCSPRequest handles the longest serial number that
+// it's required to support (i.e. 20 octets).
+TEST_F(pkixocsp_CreateEncodedOCSPRequest, LongestSupportedSerialNumberTest)
+{
+ static const uint8_t LONGEST_REQUIRED_LEN = 20;
+
+ ByteString serialNumberString;
+ // tag + length + value is 1 + 1 + LONGEST_REQUIRED_LEN
+ serialNumberString.push_back(der::INTEGER);
+ serialNumberString.push_back(LONGEST_REQUIRED_LEN);
+ serialNumberString.push_back(0x01);
+ // value is 0x010000...00
+ for (size_t i = 1; i < LONGEST_REQUIRED_LEN; ++i) {
+ serialNumberString.push_back(0x00);
+ }
+
+ ByteString issuerDER;
+ ByteString issuerSPKI;
+ ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER,
+ issuerSPKI));
+
+ Input issuer;
+ ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length()));
+
+ Input spki;
+ ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length()));
+
+ Input serialNumber;
+ ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(),
+ serialNumberString.length()));
+
+ uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH];
+ size_t ocspRequestLength;
+ ASSERT_EQ(Success,
+ CreateEncodedOCSPRequest(trustDomain,
+ CertID(issuer, spki, serialNumber),
+ ocspRequest, ocspRequestLength));
+}
diff --git a/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp b/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
new file mode 100644
index 000000000..2dd517505
--- /dev/null
+++ b/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -0,0 +1,1064 @@
+/* -*- 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 "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+const uint16_t END_ENTITY_MAX_LIFETIME_IN_DAYS = 10;
+
+// Note that CheckRevocation is never called for OCSP signing certificates.
+class OCSPTestTrustDomain : public DefaultCryptoTrustDomain
+{
+public:
+ OCSPTestTrustDomain() { }
+
+ Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&,
+ Input, /*out*/ TrustLevel& trustLevel)
+ /*non-final*/ override
+ {
+ EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
+ trustLevel = TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) override
+ {
+ if (extension == AuxiliaryExtension::SCTListFromOCSPResponse) {
+ signedCertificateTimestamps = InputToByteString(extensionData);
+ } else {
+ // We do not currently expect to receive any other extension here.
+ ADD_FAILURE();
+ }
+ }
+
+ ByteString signedCertificateTimestamps;
+};
+
+namespace {
+char const* const rootName = "Test CA 1";
+void deleteCertID(CertID* certID) { delete certID; }
+} // namespace
+
+class pkixocsp_VerifyEncodedResponse : public ::testing::Test
+{
+public:
+ static void SetUpTestCase()
+ {
+ rootKeyPair.reset(GenerateKeyPair());
+ if (!rootKeyPair) {
+ abort();
+ }
+ }
+
+ void SetUp()
+ {
+ rootNameDER = CNToDERName(rootName);
+ if (ENCODING_FAILED(rootNameDER)) {
+ abort();
+ }
+ Input rootNameDERInput;
+ if (rootNameDERInput.Init(rootNameDER.data(), rootNameDER.length())
+ != Success) {
+ abort();
+ }
+
+ serialNumberDER =
+ CreateEncodedSerialNumber(static_cast<long>(++rootIssuedCount));
+ if (ENCODING_FAILED(serialNumberDER)) {
+ abort();
+ }
+ Input serialNumberDERInput;
+ if (serialNumberDERInput.Init(serialNumberDER.data(),
+ serialNumberDER.length()) != Success) {
+ abort();
+ }
+
+ Input rootSPKIDER;
+ if (rootSPKIDER.Init(rootKeyPair->subjectPublicKeyInfo.data(),
+ rootKeyPair->subjectPublicKeyInfo.length())
+ != Success) {
+ abort();
+ }
+ endEntityCertID.reset(new (std::nothrow) CertID(rootNameDERInput, rootSPKIDER,
+ serialNumberDERInput));
+ if (!endEntityCertID) {
+ abort();
+ }
+ }
+
+ static ScopedTestKeyPair rootKeyPair;
+ static uint32_t rootIssuedCount;
+ OCSPTestTrustDomain trustDomain;
+
+ // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER.
+ ByteString rootNameDER;
+ ByteString serialNumberDER;
+ // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER.
+ ScopedPtr<CertID, deleteCertID> endEntityCertID;
+};
+
+/*static*/ ScopedTestKeyPair pkixocsp_VerifyEncodedResponse::rootKeyPair;
+/*static*/ uint32_t pkixocsp_VerifyEncodedResponse::rootIssuedCount = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+// responseStatus
+
+struct WithoutResponseBytes
+{
+ uint8_t responseStatus;
+ Result expectedError;
+};
+
+static const WithoutResponseBytes WITHOUT_RESPONSEBYTES[] = {
+ { OCSPResponseContext::successful, Result::ERROR_OCSP_MALFORMED_RESPONSE },
+ { OCSPResponseContext::malformedRequest, Result::ERROR_OCSP_MALFORMED_REQUEST },
+ { OCSPResponseContext::internalError, Result::ERROR_OCSP_SERVER_ERROR },
+ { OCSPResponseContext::tryLater, Result::ERROR_OCSP_TRY_SERVER_LATER },
+ { 4/*unused*/, Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS },
+ { OCSPResponseContext::sigRequired, Result::ERROR_OCSP_REQUEST_NEEDS_SIG },
+ { OCSPResponseContext::unauthorized, Result::ERROR_OCSP_UNAUTHORIZED_REQUEST },
+ { OCSPResponseContext::unauthorized + 1,
+ Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS
+ },
+};
+
+class pkixocsp_VerifyEncodedResponse_WithoutResponseBytes
+ : public pkixocsp_VerifyEncodedResponse
+ , public ::testing::WithParamInterface<WithoutResponseBytes>
+{
+protected:
+ ByteString CreateEncodedOCSPErrorResponse(uint8_t status)
+ {
+ static const Input EMPTY;
+ OCSPResponseContext context(CertID(EMPTY, EMPTY, EMPTY),
+ oneDayBeforeNow);
+ context.responseStatus = status;
+ context.skipResponseBytes = true;
+ return CreateEncodedOCSPResponse(context);
+ }
+};
+
+TEST_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, CorrectErrorCode)
+{
+ ByteString
+ responseString(CreateEncodedOCSPErrorResponse(GetParam().responseStatus));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(GetParam().expectedError,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes,
+ pkixocsp_VerifyEncodedResponse_WithoutResponseBytes,
+ testing::ValuesIn(WITHOUT_RESPONSEBYTES));
+
+///////////////////////////////////////////////////////////////////////////////
+// "successful" responses
+
+namespace {
+
+// Alias for nullptr to aid readability in the code below.
+static const char* byKey = nullptr;
+
+} // namespace
+
+class pkixocsp_VerifyEncodedResponse_successful
+ : public pkixocsp_VerifyEncodedResponse
+{
+public:
+ void SetUp()
+ {
+ pkixocsp_VerifyEncodedResponse::SetUp();
+ }
+
+ static void SetUpTestCase()
+ {
+ pkixocsp_VerifyEncodedResponse::SetUpTestCase();
+ }
+
+ ByteString CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::CertStatus certStatus,
+ const CertID& certID,
+ /*optional*/ const char* signerName,
+ const TestKeyPair& signerKeyPair,
+ time_t producedAt, time_t thisUpdate,
+ /*optional*/ const time_t* nextUpdate,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*optional*/ const ByteString* certs = nullptr,
+ /*optional*/ OCSPResponseExtension* singleExtensions = nullptr,
+ /*optional*/ OCSPResponseExtension* responseExtensions = nullptr)
+ {
+ OCSPResponseContext context(certID, producedAt);
+ if (signerName) {
+ context.signerNameDER = CNToDERName(signerName);
+ EXPECT_FALSE(ENCODING_FAILED(context.signerNameDER));
+ }
+ context.signerKeyPair.reset(signerKeyPair.Clone());
+ EXPECT_TRUE(context.signerKeyPair.get());
+ context.responseStatus = OCSPResponseContext::successful;
+ context.producedAt = producedAt;
+ context.signatureAlgorithm = signatureAlgorithm;
+ context.certs = certs;
+ context.singleExtensions = singleExtensions;
+ context.responseExtensions = responseExtensions;
+
+ context.certStatus = static_cast<uint8_t>(certStatus);
+ context.thisUpdate = thisUpdate;
+ context.nextUpdate = nextUpdate ? *nextUpdate : 0;
+ context.includeNextUpdate = nextUpdate != nullptr;
+
+ return CreateEncodedOCSPResponse(context);
+ }
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byName)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, rootName,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey_without_nextUpdate)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, nullptr,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, revoked)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::revoked, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, unknown)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::unknown, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful,
+ good_unsupportedSignatureAlgorithm)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ md5WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// Added for bug 1079436. The output variable validThrough represents the
+// latest time for which VerifyEncodedOCSPResponse will succeed, which is
+// different from the nextUpdate time in the OCSP response due to the slop we
+// add for time comparisons to deal with clock skew.
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, check_validThrough)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Time validThrough(Time::uninitialized);
+ {
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired, nullptr,
+ &validThrough));
+ ASSERT_FALSE(expired);
+ // The response was created to be valid until one day after now, so the
+ // value we got for validThrough should be after that.
+ Time oneDayAfterNowAsPKIXTime(
+ TimeFromEpochInSeconds(static_cast<uint64_t>(oneDayAfterNow)));
+ ASSERT_TRUE(validThrough > oneDayAfterNowAsPKIXTime);
+ }
+ {
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ // Given validThrough from a previous verification, this response should be
+ // valid through that time.
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ validThrough, END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+ }
+ {
+ Time noLongerValid(validThrough);
+ ASSERT_EQ(Success, noLongerValid.AddSeconds(1));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ // The verification time is now after when the response will be considered
+ // valid.
+ ASSERT_EQ(Result::ERROR_OCSP_OLD_RESPONSE,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ noLongerValid, END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_TRUE(expired);
+ }
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension)
+{
+ // python DottedOIDToCode.py --tlv
+ // id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5
+ static const uint8_t tlv_id_ocsp_singleExtensionSctList[] = {
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
+ };
+ static const uint8_t dummySctList[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+
+ OCSPResponseExtension ctExtension;
+ ctExtension.id = BytesToByteString(tlv_id_ocsp_singleExtensionSctList);
+ // SignedCertificateTimestampList structure is encoded as an OCTET STRING
+ // within the extension value (see RFC 6962 section 3.3).
+ // pkix decodes it internally and returns the actual structure.
+ ctExtension.value = TLV(der::OCTET_STRING, BytesToByteString(dummySctList));
+
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(),
+ /*certs*/ nullptr,
+ &ctExtension));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+ ASSERT_EQ(BytesToByteString(dummySctList),
+ trustDomain.signedCertificateTimestamps);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// indirect responses (signed by a delegated OCSP responder cert)
+
+class pkixocsp_VerifyEncodedResponse_DelegatedResponder
+ : public pkixocsp_VerifyEncodedResponse_successful
+{
+protected:
+ // certSubjectName should be unique for each call. This way, we avoid any
+ // issues with NSS caching the certificates internally. For the same reason,
+ // we generate a new keypair on each call. Either one of these should be
+ // sufficient to avoid issues with the NSS cache, but we do both to be
+ // cautious.
+ //
+ // signerName should be byKey to use the byKey ResponderID construction, or
+ // another value (usually equal to certSubjectName) to use the byName
+ // ResponderID construction.
+ //
+ // certSignatureAlgorithm specifies the signature algorithm that the
+ // certificate will be signed with, not the OCSP response.
+ //
+ // If signerEKU is omitted, then the certificate will have the
+ // id-kp-OCSPSigning EKU. If signerEKU is SEC_OID_UNKNOWN then it will not
+ // have any EKU extension. Otherwise, the certificate will have the given
+ // EKU.
+ ByteString CreateEncodedIndirectOCSPSuccessfulResponse(
+ const char* certSubjectName,
+ OCSPResponseContext::CertStatus certStatus,
+ const char* signerName,
+ const TestSignatureAlgorithm& certSignatureAlgorithm,
+ /*optional*/ const Input* signerEKUDER = &OCSPSigningEKUDER,
+ /*optional, out*/ ByteString* signerDEROut = nullptr)
+ {
+ assert(certSubjectName);
+
+ const ByteString extensions[] = {
+ signerEKUDER
+ ? CreateEncodedEKUExtension(*signerEKUDER, Critical::No)
+ : ByteString(),
+ ByteString()
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ ++rootIssuedCount, certSignatureAlgorithm,
+ rootName, oneDayBeforeNow, oneDayAfterNow,
+ certSubjectName, *signerKeyPair,
+ signerEKUDER ? extensions : nullptr,
+ *rootKeyPair));
+ EXPECT_FALSE(ENCODING_FAILED(signerDER));
+ if (signerDEROut) {
+ *signerDEROut = signerDER;
+ }
+
+ ByteString signerNameDER;
+ if (signerName) {
+ signerNameDER = CNToDERName(signerName);
+ EXPECT_FALSE(ENCODING_FAILED(signerNameDER));
+ }
+ ByteString certs[] = { signerDER, ByteString() };
+ return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID,
+ signerName, *signerKeyPair,
+ oneDayBeforeNow,
+ oneDayBeforeNow,
+ &oneDayAfterNow,
+ sha256WithRSAEncryption(),
+ certs);
+ }
+
+ static ByteString CreateEncodedCertificate(uint32_t serialNumber,
+ const TestSignatureAlgorithm& signatureAlg,
+ const char* issuer,
+ time_t notBefore,
+ time_t notAfter,
+ const char* subject,
+ const TestKeyPair& subjectKeyPair,
+ /*optional*/ const ByteString* extensions,
+ const TestKeyPair& signerKeyPair)
+ {
+ ByteString serialNumberDER(CreateEncodedSerialNumber(
+ static_cast<long>(serialNumber)));
+ if (ENCODING_FAILED(serialNumberDER)) {
+ return ByteString();
+ }
+ ByteString issuerDER(CNToDERName(issuer));
+ if (ENCODING_FAILED(issuerDER)) {
+ return ByteString();
+ }
+ ByteString subjectDER(CNToDERName(subject));
+ if (ENCODING_FAILED(subjectDER)) {
+ return ByteString();
+ }
+ return ::mozilla::pkix::test::CreateEncodedCertificate(
+ v3, signatureAlg, serialNumberDER,
+ issuerDER, notBefore, notAfter,
+ subjectDER, subjectKeyPair, extensions,
+ signerKeyPair, signatureAlg);
+ }
+
+ static const Input OCSPSigningEKUDER;
+};
+
+/*static*/ const Input pkixocsp_VerifyEncodedResponse_DelegatedResponder::
+ OCSPSigningEKUDER(tlv_id_kp_OCSPSigning);
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byKey)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_byKey", OCSPResponseContext::good,
+ byKey, sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byName)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_byName", OCSPResponseContext::good,
+ "good_indirect_byName", sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_byKey_missing_signer)
+{
+ ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair());
+ ASSERT_TRUE(missingSignerKeyPair.get());
+
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *missingSignerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, nullptr,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_byName_missing_signer)
+{
+ ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair());
+ ASSERT_TRUE(missingSignerKeyPair.get());
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ "missing", *missingSignerKeyPair,
+ oneDayBeforeNow, oneDayBeforeNow, nullptr,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_expired)
+{
+ static const char* signerName = "good_indirect_expired";
+
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ ++rootIssuedCount, sha256WithRSAEncryption(),
+ rootName,
+ tenDaysBeforeNow,
+ twoDaysBeforeNow,
+ signerName, *signerKeyPair, extensions,
+ *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ ByteString certs[] = { signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future)
+{
+ static const char* signerName = "good_indirect_future";
+
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ ++rootIssuedCount, sha256WithRSAEncryption(),
+ rootName,
+ twoDaysAfterNow,
+ tenDaysAfterNow,
+ signerName, *signerKeyPair, extensions,
+ *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ ByteString certs[] = { signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_no_eku)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_wrong_eku",
+ OCSPResponseContext::good, byKey,
+ sha256WithRSAEncryption(), nullptr));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+static const Input serverAuthEKUDER(tlv_id_kp_serverAuth);
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_indirect_wrong_eku)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_wrong_eku",
+ OCSPResponseContext::good, byKey,
+ sha256WithRSAEncryption(), &serverAuthEKUDER));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// Test that signature of OCSP response signer cert is verified
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_tampered_eku)
+{
+ ByteString tamperedResponse(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_tampered_eku",
+ OCSPResponseContext::good, byKey,
+ sha256WithRSAEncryption(), &serverAuthEKUDER));
+ ASSERT_EQ(Success,
+ TamperOnce(tamperedResponse,
+ ByteString(tlv_id_kp_serverAuth,
+ sizeof(tlv_id_kp_serverAuth)),
+ ByteString(tlv_id_kp_OCSPSigning,
+ sizeof(tlv_id_kp_OCSPSigning))));
+ Input tamperedResponseInput;
+ ASSERT_EQ(Success, tamperedResponseInput.Init(tamperedResponse.data(),
+ tamperedResponse.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ tamperedResponseInput, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_unknown_issuer)
+{
+ static const char* subCAName = "good_indirect_unknown_issuer sub-CA";
+ static const char* signerName = "good_indirect_unknown_issuer OCSP signer";
+
+ // unknown issuer
+ ScopedTestKeyPair unknownKeyPair(GenerateKeyPair());
+ ASSERT_TRUE(unknownKeyPair.get());
+
+ // Delegated responder cert signed by unknown issuer
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ 1, sha256WithRSAEncryption(), subCAName,
+ oneDayBeforeNow, oneDayAfterNow, signerName,
+ *signerKeyPair, extensions, *unknownKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ // OCSP response signed by that delegated responder
+ ByteString certs[] = { signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// The CA that issued the OCSP responder cert is a sub-CA of the issuer of
+// the certificate that the OCSP response is for. That sub-CA cert is included
+// in the OCSP response before the OCSP responder cert.
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_indirect_subca_1_first)
+{
+ static const char* subCAName = "good_indirect_subca_1_first sub-CA";
+ static const char* signerName = "good_indirect_subca_1_first OCSP signer";
+ static const long zero = 0;
+
+ // sub-CA of root (root is the direct issuer of endEntity)
+ const ByteString subCAExtensions[] = {
+ CreateEncodedBasicConstraints(true, &zero, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair subCAKeyPair(GenerateKeyPair());
+ ByteString subCADER(CreateEncodedCertificate(
+ ++rootIssuedCount, sha256WithRSAEncryption(), rootName,
+ oneDayBeforeNow, oneDayAfterNow, subCAName,
+ *subCAKeyPair, subCAExtensions, *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(subCADER));
+
+ // Delegated responder cert signed by that sub-CA
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString(),
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ 1, sha256WithRSAEncryption(), subCAName,
+ oneDayBeforeNow, oneDayAfterNow, signerName,
+ *signerKeyPair, extensions, *subCAKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ // OCSP response signed by the delegated responder issued by the sub-CA
+ // that is trying to impersonate the root.
+ ByteString certs[] = { subCADER, signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// The CA that issued the OCSP responder cert is a sub-CA of the issuer of
+// the certificate that the OCSP response is for. That sub-CA cert is included
+// in the OCSP response after the OCSP responder cert.
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_indirect_subca_1_second)
+{
+ static const char* subCAName = "good_indirect_subca_1_second sub-CA";
+ static const char* signerName = "good_indirect_subca_1_second OCSP signer";
+ static const long zero = 0;
+
+ // sub-CA of root (root is the direct issuer of endEntity)
+ const ByteString subCAExtensions[] = {
+ CreateEncodedBasicConstraints(true, &zero, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair subCAKeyPair(GenerateKeyPair());
+ ByteString subCADER(CreateEncodedCertificate(++rootIssuedCount,
+ sha256WithRSAEncryption(),
+ rootName,
+ oneDayBeforeNow, oneDayAfterNow,
+ subCAName, *subCAKeyPair,
+ subCAExtensions, *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(subCADER));
+
+ // Delegated responder cert signed by that sub-CA
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ 1, sha256WithRSAEncryption(), subCAName,
+ oneDayBeforeNow, oneDayAfterNow, signerName,
+ *signerKeyPair, extensions, *subCAKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ // OCSP response signed by the delegated responder issued by the sub-CA
+ // that is trying to impersonate the root.
+ ByteString certs[] = { signerDER, subCADER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_unsupportedSignatureAlgorithmOnResponder)
+{
+ // Note that the algorithm ID (md5WithRSAEncryption) identifies the signature
+ // algorithm that will be used to sign the certificate that issues the OCSP
+ // responses, not the responses themselves.
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_unsupportedSignatureAlgorithm",
+ OCSPResponseContext::good, byKey,
+ md5WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+}
+
+class pkixocsp_VerifyEncodedResponse_GetCertTrust
+ : public pkixocsp_VerifyEncodedResponse_DelegatedResponder {
+public:
+ void SetUp()
+ {
+ pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp();
+
+ responseString =
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "OCSPGetCertTrustTest Signer", OCSPResponseContext::good,
+ byKey, sha256WithRSAEncryption(), &OCSPSigningEKUDER,
+ &signerCertDER);
+ if (ENCODING_FAILED(responseString)) {
+ abort();
+ }
+ if (response.Init(responseString.data(), responseString.length())
+ != Success) {
+ abort();
+ }
+ if (signerCertDER.length() == 0) {
+ abort();
+ }
+ }
+
+ class TrustDomain final : public OCSPTestTrustDomain
+ {
+ public:
+ TrustDomain()
+ : certTrustLevel(TrustLevel::InheritsTrust)
+ {
+ }
+
+ bool SetCertTrust(const ByteString& aCertDER, TrustLevel aCertTrustLevel)
+ {
+ this->certDER = aCertDER;
+ this->certTrustLevel = aCertTrustLevel;
+ return true;
+ }
+ private:
+ Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&,
+ Input candidateCert, /*out*/ TrustLevel& trustLevel)
+ override
+ {
+ EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
+ EXPECT_FALSE(certDER.empty());
+ Input certDERInput;
+ EXPECT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ EXPECT_TRUE(InputsAreEqual(certDERInput, candidateCert));
+ trustLevel = certTrustLevel;
+ return Success;
+ }
+
+ ByteString certDER;
+ TrustLevel certTrustLevel;
+ };
+
+// trustDomain deliberately shadows the inherited field so that it isn't used
+// by accident. See bug 1339921.
+// Unfortunately GCC can't parse __has_warning("-Wshadow-field") even if it's
+// the latter part of a conjunction that would evaluate to false, so we have to
+// wrap it in a separate preprocessor conditional rather than using &&.
+#if defined(__clang__)
+ #if __has_warning("-Wshadow-field")
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wshadow-field"
+ #endif
+#endif
+ TrustDomain trustDomain;
+#if defined(__clang__)
+ #if __has_warning("-Wshadow-field")
+ #pragma clang diagnostic pop
+ #endif
+#endif
+ ByteString signerCertDER;
+ ByteString responseString;
+ Input response; // references data in responseString
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, InheritTrust)
+{
+ ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+ TrustLevel::InheritsTrust));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, TrustAnchor)
+{
+ ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+ TrustLevel::TrustAnchor));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, ActivelyDistrusted)
+{
+ ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+ TrustLevel::ActivelyDistrusted));
+ Input responseInput;
+ ASSERT_EQ(Success,
+ responseInput.Init(responseString.data(),
+ responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ responseInput, expired));
+ ASSERT_FALSE(expired);
+}
diff --git a/lib/mozpkix/test/lib/moz.build b/lib/mozpkix/test/lib/moz.build
new file mode 100644
index 000000000..bc9bf50b9
--- /dev/null
+++ b/lib/mozpkix/test/lib/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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.
+
+SOURCES += [
+ 'pkixtestalg.cpp',
+ 'pkixtestnss.cpp',
+ 'pkixtestutil.cpp',
+]
+
+Library('pkixtestutil')
+
+LOCAL_INCLUDES += [
+ '../../include',
+ '../../lib',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/lib/mozpkix/test/lib/pkixtestalg.cpp b/lib/mozpkix/test/lib/pkixtestalg.cpp
new file mode 100644
index 000000000..d4ef88f8f
--- /dev/null
+++ b/lib/mozpkix/test/lib/pkixtestalg.cpp
@@ -0,0 +1,210 @@
+/* -*- 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 "pkixtestutil.h"
+
+#include "pkixder.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/lib/mozpkix/test/lib/pkixtestnss.cpp b/lib/mozpkix/test/lib/pkixtestnss.cpp
new file mode 100644
index 000000000..7f12700d2
--- /dev/null
+++ b/lib/mozpkix/test/lib/pkixtestnss.cpp
@@ -0,0 +1,378 @@
+/* -*- 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 "pkixtestutil.h"
+#include "pkixtestnss.h"
+
+#include <limits>
+
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "nss.h"
+#include "pk11pqg.h"
+#include "pk11pub.h"
+#include "pkix/pkixnss.h"
+#include "pkixder.h"
+#include "pkixutil.h"
+#include "prinit.h"
+#include "secerr.h"
+#include "secitem.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+inline void
+SECITEM_FreeItem_true(SECItem* item)
+{
+ SECITEM_FreeItem(item, true);
+}
+
+inline void
+SECKEY_DestroyEncryptedPrivateKeyInfo_true(SECKEYEncryptedPrivateKeyInfo* e)
+{
+ SECKEY_DestroyEncryptedPrivateKeyInfo(e, true);
+}
+
+typedef mozilla::pkix::ScopedPtr<SECItem, SECITEM_FreeItem_true> ScopedSECItem;
+
+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();
+ }
+
+ ScopedPtr<PK11SlotInfo, PK11_FreeSlot> 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)
+{
+ ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo>
+ spki(SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
+ if (!spki) {
+ return nullptr;
+ }
+ SECItem spkDER = spki->subjectPublicKey;
+ DER_ConvertBitString(&spkDER); // bits to bytes
+ ScopedPtr<PK11SlotInfo, PK11_FreeSlot> 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 };
+ ScopedPtr<SECKEYEncryptedPrivateKeyInfo,
+ SECKEY_DestroyEncryptedPrivateKeyInfo_true> 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()
+{
+ ScopedPtr<PK11SlotInfo, PK11_FreeSlot> 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();
+
+ ScopedPtr<PK11SlotInfo, PK11_FreeSlot> 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/lib/mozpkix/test/lib/pkixtestnss.h b/lib/mozpkix/test/lib/pkixtestnss.h
new file mode 100644
index 000000000..18a7e1086
--- /dev/null
+++ b/lib/mozpkix/test/lib/pkixtestnss.h
@@ -0,0 +1,49 @@
+/* -*- 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 "pkixtestutil.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
+ ScopedSECKEYPublicKey;
+typedef ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey>
+ ScopedSECKEYPrivateKey;
+
+TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
+ const ScopedSECKEYPublicKey& publicKey,
+ const ScopedSECKEYPrivateKey& privateKey);
+
+} } } // namespace mozilla::pkix::test
+
+#endif // mozilla_pkix_test_pkixtestnss_h
diff --git a/lib/mozpkix/test/lib/pkixtestutil.cpp b/lib/mozpkix/test/lib/pkixtestutil.cpp
new file mode 100644
index 000000000..20b17418e
--- /dev/null
+++ b/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 "pkixtestutil.h"
+
+#include <cerrno>
+#include <cstdio>
+#include <limits>
+#include <new>
+#include <sstream>
+#include <cstdlib>
+
+#include "pkixder.h"
+#include "pkixutil.h"
+
+#include "mozilla/Unused.h"
+
+using namespace std;
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+inline void
+fclose_void(FILE* file) {
+ (void) fclose(file);
+}
+
+typedef mozilla::pkix::ScopedPtr<FILE, fclose_void> 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(WIN32) && (!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) {
+ Unused << 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/lib/mozpkix/test/lib/pkixtestutil.h b/lib/mozpkix/test/lib/pkixtestutil.h
new file mode 100644
index 000000000..e5da442a7
--- /dev/null
+++ b/lib/mozpkix/test/lib/pkixtestutil.h
@@ -0,0 +1,448 @@
+/* -*- 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 <ctime>
+#include <stdint.h> // Some Mozilla-supported compilers lack <cstdint>
+#include <string>
+#include <cstring>
+
+#include "pkix/pkixtypes.h"
+#include "../../lib/ScopedPtr.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 ScopedPtr<TestKeyPair, DeleteTestKeyPair> 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/lib/mozpkix/tools/DottedOIDToCode.py b/lib/mozpkix/tools/DottedOIDToCode.py
new file mode 100644
index 000000000..dfd4ade07
--- /dev/null
+++ b/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))
diff --git a/lib/mozpkix/warnings.mozbuild b/lib/mozpkix/warnings.mozbuild
new file mode 100644
index 000000000..f89d7fb74
--- /dev/null
+++ b/lib/mozpkix/warnings.mozbuild
@@ -0,0 +1,52 @@
+if CONFIG['CC_TYPE'] in ('clang', 'clang-cl'):
+ CXXFLAGS += [
+ '-Weverything',
+
+ '-Wno-c++98-compat',
+ '-Wno-c++98-compat-pedantic',
+ '-Wno-missing-prototypes',
+ '-Wno-missing-variable-declarations',
+ '-Wno-padded',
+ '-Wno-reserved-id-macro', # NSPR and NSS use reserved IDs in their include guards.
+ '-Wno-weak-vtables', # We rely on the linker to merge the duplicate vtables.
+ ]
+elif CONFIG['CC_TYPE'] == 'msvc':
+ CXXFLAGS += [
+ '-sdl', # Enable additional security checks based on Microsoft's SDL.
+
+ '-Wall',
+
+ '-wd4464', # relative include path contains '..'
+ '-wd4514', # 'function': unreferenced inline function has been removed
+ '-wd4668', # warning C4668: 'X' is not defined as a preprocessor macro,
+ # replacing with '0' for '#if/#elif'.
+ '-wd4710', # 'function': function not inlined
+ '-wd4711', # function 'function' selected for inline expansion
+ '-wd4800', # forcing value to bool 'true' or 'false'
+ '-wd4820', # 'bytes' bytes padding added after construct 'member_name'
+
+ # The following warnings are disabled because MSVC 2017 headers aren't
+ # warning free at the -Wall level.
+ '-wd4365', # 'action' : conversion from 'type_1' to 'type_2',
+ # signed/unsigned mismatch
+ '-wd4619', # #pragma warning : there is no warning number 'number'
+ '-wd4623', # 'derived class' : default constructor was implicitly defined as
+ # deleted because a base class default constructor is
+ # inaccessible or deleted
+ '-wd4774', # '<function>' : format string expected in argument <position> is
+ # not a string literal
+ '-wd4987', # nonstandard extension used: 'throw (...)'
+
+ # XXX: We cannot use /Za (Disable Microsoft Extensions) because windows.h
+ # won't copmile with it.
+ '-Zc:forScope', # Standard C++ rules for variable scope in for loops.
+ '-Zc:inline', # Standard C++ rules requiring definition inline functions.
+ '-Zc:rvalueCast', # Standard C++ rules for result of cast being an rvalue.
+ '-Zc:strictStrings', # Standard C++ rule that string literals are const.
+ ]
+else:
+ CXXFLAGS += [
+ '-Wall',
+ '-Wextra',
+ '-pedantic-errors',
+ ]