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