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