michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* Copyright 2013 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #ifndef mozilla_pkix__pkixder_h michael@0: #define mozilla_pkix__pkixder_h michael@0: michael@0: #include "pkix/nullptr.h" michael@0: michael@0: #include "prerror.h" michael@0: #include "prlog.h" michael@0: #include "secder.h" michael@0: #include "secerr.h" michael@0: #include "secoidt.h" michael@0: #include "stdint.h" michael@0: michael@0: namespace mozilla { namespace pkix { namespace der { michael@0: michael@0: enum Class michael@0: { michael@0: UNIVERSAL = 0 << 6, michael@0: // APPLICATION = 1 << 6, // unused michael@0: CONTEXT_SPECIFIC = 2 << 6, michael@0: // PRIVATE = 3 << 6 // unused michael@0: }; michael@0: michael@0: enum Constructed michael@0: { michael@0: CONSTRUCTED = 1 << 5 michael@0: }; michael@0: michael@0: enum Tag michael@0: { michael@0: BOOLEAN = UNIVERSAL | 0x01, michael@0: INTEGER = UNIVERSAL | 0x02, michael@0: BIT_STRING = UNIVERSAL | 0x03, michael@0: OCTET_STRING = UNIVERSAL | 0x04, michael@0: NULLTag = UNIVERSAL | 0x05, michael@0: OIDTag = UNIVERSAL | 0x06, michael@0: ENUMERATED = UNIVERSAL | 0x0a, michael@0: GENERALIZED_TIME = UNIVERSAL | 0x18, michael@0: SEQUENCE = UNIVERSAL | CONSTRUCTED | 0x30, michael@0: }; michael@0: michael@0: enum Result michael@0: { michael@0: Failure = -1, michael@0: Success = 0 michael@0: }; michael@0: michael@0: enum EmptyAllowed { MayBeEmpty = 0, MustNotBeEmpty = 1 }; michael@0: michael@0: Result Fail(PRErrorCode errorCode); michael@0: michael@0: class Input michael@0: { michael@0: public: michael@0: Input() michael@0: : input(nullptr) michael@0: , end(nullptr) michael@0: { michael@0: } michael@0: michael@0: Result Init(const uint8_t* data, size_t len) michael@0: { michael@0: if (input) { michael@0: // already initialized michael@0: return Fail(SEC_ERROR_INVALID_ARGS); michael@0: } michael@0: if (!data || len > 0xffffu) { michael@0: // input too large michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: michael@0: // XXX: this->input = input bug was not caught by tests! Why not? michael@0: // this->end = end bug was not caught by tests! Why not? michael@0: this->input = data; michael@0: this->end = data + len; michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: Result Expect(const uint8_t* expected, uint16_t expectedLen) michael@0: { michael@0: if (EnsureLength(expectedLen) != Success) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: if (memcmp(input, expected, expectedLen)) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: input += expectedLen; michael@0: return Success; michael@0: } michael@0: michael@0: bool Peek(uint8_t expectedByte) const michael@0: { michael@0: return input < end && *input == expectedByte; michael@0: } michael@0: michael@0: Result Read(uint8_t& out) michael@0: { michael@0: if (input == end) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: out = *input++; michael@0: return Success; michael@0: } michael@0: michael@0: Result Read(uint16_t& out) michael@0: { michael@0: if (input == end || input + 1 == end) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: out = *input++; michael@0: out <<= 8u; michael@0: out |= *input++; michael@0: return Success; michael@0: } michael@0: michael@0: Result Skip(uint16_t len) michael@0: { michael@0: if (EnsureLength(len) != Success) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: input += len; michael@0: return Success; michael@0: } michael@0: michael@0: Result Skip(uint16_t len, Input& skippedInput) michael@0: { michael@0: if (EnsureLength(len) != Success) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: if (skippedInput.Init(input, len) != Success) { michael@0: return Failure; michael@0: } michael@0: input += len; michael@0: return Success; michael@0: } michael@0: michael@0: Result Skip(uint16_t len, SECItem& skippedItem) michael@0: { michael@0: if (EnsureLength(len) != Success) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: skippedItem.type = siBuffer; michael@0: skippedItem.data = const_cast(input); michael@0: skippedItem.len = len; michael@0: input += len; michael@0: return Success; michael@0: } michael@0: michael@0: void SkipToEnd() michael@0: { michael@0: input = end; michael@0: } michael@0: michael@0: Result EnsureLength(uint16_t len) michael@0: { michael@0: if (static_cast(end - input) < len) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: return Success; michael@0: } michael@0: michael@0: bool AtEnd() const { return input == end; } michael@0: michael@0: class Mark michael@0: { michael@0: private: michael@0: friend class Input; michael@0: explicit Mark(const uint8_t* mark) : mMark(mark) { } michael@0: const uint8_t* const mMark; michael@0: void operator=(const Mark&) /* = delete */; michael@0: }; michael@0: michael@0: Mark GetMark() const { return Mark(input); } michael@0: michael@0: bool GetSECItem(SECItemType type, const Mark& mark, /*out*/ SECItem& item) michael@0: { michael@0: PR_ASSERT(mark.mMark < input); michael@0: item.type = type; michael@0: item.data = const_cast(mark.mMark); michael@0: // TODO: Return false if bounds check fails michael@0: item.len = input - mark.mMark; michael@0: return true; michael@0: } michael@0: michael@0: private: michael@0: const uint8_t* input; michael@0: const uint8_t* end; michael@0: michael@0: Input(const Input&) /* = delete */; michael@0: void operator=(const Input&) /* = delete */; michael@0: }; michael@0: michael@0: inline Result michael@0: ExpectTagAndLength(Input& input, uint8_t expectedTag, uint8_t expectedLength) michael@0: { michael@0: PR_ASSERT((expectedTag & 0x1F) != 0x1F); // high tag number form not allowed michael@0: PR_ASSERT(expectedLength < 128); // must be a single-byte length michael@0: michael@0: uint16_t tagAndLength; michael@0: if (input.Read(tagAndLength) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: uint16_t expectedTagAndLength = static_cast(expectedTag << 8); michael@0: expectedTagAndLength |= expectedLength; michael@0: michael@0: if (tagAndLength != expectedTagAndLength) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: Result michael@0: ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length); michael@0: michael@0: inline Result michael@0: ExpectTagAndIgnoreLength(Input& input, uint8_t expectedTag) michael@0: { michael@0: uint16_t ignored; michael@0: return ExpectTagAndGetLength(input, expectedTag, ignored); michael@0: } michael@0: michael@0: inline Result michael@0: ExpectTagAndGetValue(Input& input, uint8_t tag, /*out*/ Input& value) michael@0: { michael@0: uint16_t length; michael@0: if (ExpectTagAndGetLength(input, tag, length) != Success) { michael@0: return Failure; michael@0: } michael@0: return input.Skip(length, value); michael@0: } michael@0: michael@0: inline Result michael@0: End(Input& input) michael@0: { michael@0: if (!input.AtEnd()) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: template michael@0: inline Result michael@0: Nested(Input& input, uint8_t tag, Decoder decoder) michael@0: { michael@0: uint16_t length; michael@0: if (ExpectTagAndGetLength(input, tag, length) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: Input nested; michael@0: if (input.Skip(length, nested) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: if (decoder(nested) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: return End(nested); michael@0: } michael@0: michael@0: template michael@0: inline Result michael@0: Nested(Input& input, uint8_t outerTag, uint8_t innerTag, Decoder decoder) michael@0: { michael@0: // XXX: This doesn't work (in VS2010): michael@0: // return Nested(input, outerTag, bind(Nested, _1, innerTag, decoder)); michael@0: michael@0: uint16_t length; michael@0: if (ExpectTagAndGetLength(input, outerTag, length) != Success) { michael@0: return Failure; michael@0: } michael@0: Input nestedInput; michael@0: if (input.Skip(length, nestedInput) != Success) { michael@0: return Failure; michael@0: } michael@0: if (Nested(nestedInput, innerTag, decoder) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: return End(nestedInput); michael@0: } michael@0: michael@0: // This can be used to decode constructs like this: michael@0: // michael@0: // ... michael@0: // foos SEQUENCE OF Foo, michael@0: // ... michael@0: // Foo ::= SEQUENCE { michael@0: // } michael@0: // michael@0: // using a call like this: michael@0: // michael@0: // rv = NestedOf(input, SEQEUENCE, SEQUENCE, bind(_1, Foo)); michael@0: // michael@0: // Result Foo(Input& input) { michael@0: // } michael@0: // michael@0: // In this example, Foo will get called once for each element of foos. michael@0: // michael@0: template michael@0: inline Result michael@0: NestedOf(Input& input, uint8_t outerTag, uint8_t innerTag, michael@0: EmptyAllowed mayBeEmpty, Decoder decoder) michael@0: { michael@0: uint16_t responsesLength; michael@0: if (ExpectTagAndGetLength(input, outerTag, responsesLength) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: Input inner; michael@0: if (input.Skip(responsesLength, inner) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: if (inner.AtEnd()) { michael@0: if (mayBeEmpty != MayBeEmpty) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: return Success; michael@0: } michael@0: michael@0: do { michael@0: if (Nested(inner, innerTag, decoder) != Success) { michael@0: return Failure; michael@0: } michael@0: } while (!inner.AtEnd()); michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: inline Result michael@0: Skip(Input& input, uint8_t tag) michael@0: { michael@0: uint16_t length; michael@0: if (ExpectTagAndGetLength(input, tag, length) != Success) { michael@0: return Failure; michael@0: } michael@0: return input.Skip(length); michael@0: } michael@0: michael@0: inline Result michael@0: Skip(Input& input, uint8_t tag, /*out*/ SECItem& value) michael@0: { michael@0: uint16_t length; michael@0: if (ExpectTagAndGetLength(input, tag, length) != Success) { michael@0: return Failure; michael@0: } michael@0: return input.Skip(length, value); michael@0: } michael@0: michael@0: // Universal types michael@0: michael@0: inline Result michael@0: Boolean(Input& input, /*out*/ bool& value) michael@0: { michael@0: if (ExpectTagAndLength(input, BOOLEAN, 1) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: uint8_t intValue; michael@0: if (input.Read(intValue) != Success) { michael@0: return Failure; michael@0: } michael@0: switch (intValue) { michael@0: case 0: value = false; return Success; michael@0: case 0xFF: value = true; return Success; michael@0: default: michael@0: PR_SetError(SEC_ERROR_BAD_DER, 0); michael@0: return Failure; michael@0: } michael@0: } michael@0: michael@0: // This is for any BOOLEAN DEFAULT FALSE. michael@0: // (If it is present and false, this is a bad encoding.) michael@0: // TODO(bug 989518): For compatibility reasons, in some places we allow michael@0: // invalid encodings with the explicit default value. michael@0: inline Result michael@0: OptionalBoolean(Input& input, bool allowInvalidExplicitEncoding, michael@0: /*out*/ bool& value) michael@0: { michael@0: value = false; michael@0: if (input.Peek(BOOLEAN)) { michael@0: if (Boolean(input, value) != Success) { michael@0: return Failure; michael@0: } michael@0: if (!allowInvalidExplicitEncoding && !value) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: } michael@0: return Success; michael@0: } michael@0: michael@0: inline Result michael@0: Enumerated(Input& input, uint8_t& value) michael@0: { michael@0: if (ExpectTagAndLength(input, ENUMERATED | 0, 1) != Success) { michael@0: return Failure; michael@0: } michael@0: return input.Read(value); michael@0: } michael@0: michael@0: inline Result michael@0: GeneralizedTime(Input& input, PRTime& time) michael@0: { michael@0: uint16_t length; michael@0: SECItem encoded; michael@0: if (ExpectTagAndGetLength(input, GENERALIZED_TIME, length) != Success) { michael@0: return Failure; michael@0: } michael@0: if (input.Skip(length, encoded)) { michael@0: return Failure; michael@0: } michael@0: if (DER_GeneralizedTimeToTime(&time, &encoded) != SECSuccess) { michael@0: return Failure; michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: inline Result michael@0: Integer(Input& input, /*out*/ SECItem& value) michael@0: { michael@0: uint16_t length; michael@0: if (ExpectTagAndGetLength(input, INTEGER, length) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: if (input.Skip(length, value) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: if (value.len == 0) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: michael@0: // Check for overly-long encodings. If the first byte is 0x00 then the high michael@0: // bit on the second byte must be 1; otherwise the same *positive* value michael@0: // could be encoded without the leading 0x00 byte. If the first byte is 0xFF michael@0: // then the second byte must NOT have its high bit set; otherwise the same michael@0: // *negative* value could be encoded without the leading 0xFF byte. michael@0: if (value.len > 1) { michael@0: if ((value.data[0] == 0x00 && (value.data[1] & 0x80) == 0) || michael@0: (value.data[0] == 0xff && (value.data[1] & 0x80) != 0)) { michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: } michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: inline Result michael@0: Null(Input& input) michael@0: { michael@0: return ExpectTagAndLength(input, NULLTag, 0); michael@0: } michael@0: michael@0: template michael@0: Result michael@0: OID(Input& input, const uint8_t (&expectedOid)[Len]) michael@0: { michael@0: if (ExpectTagAndLength(input, OIDTag, Len) != Success) { michael@0: return Failure; michael@0: } michael@0: michael@0: return input.Expect(expectedOid, Len); michael@0: } michael@0: michael@0: // PKI-specific types michael@0: michael@0: // AlgorithmIdentifier ::= SEQUENCE { michael@0: // algorithm OBJECT IDENTIFIER, michael@0: // parameters ANY DEFINED BY algorithm OPTIONAL } michael@0: inline Result michael@0: AlgorithmIdentifier(Input& input, SECAlgorithmID& algorithmID) michael@0: { michael@0: if (Skip(input, OIDTag, algorithmID.algorithm) != Success) { michael@0: return Failure; michael@0: } michael@0: algorithmID.parameters.data = nullptr; michael@0: algorithmID.parameters.len = 0; michael@0: if (input.AtEnd()) { michael@0: return Success; michael@0: } michael@0: return Null(input); michael@0: } michael@0: michael@0: inline Result michael@0: CertificateSerialNumber(Input& input, /*out*/ SECItem& serialNumber) michael@0: { michael@0: // http://tools.ietf.org/html/rfc5280#section-4.1.2.2: michael@0: // michael@0: // * "The serial number MUST be a positive integer assigned by the CA to michael@0: // each certificate." michael@0: // * "Certificate users MUST be able to handle serialNumber values up to 20 michael@0: // octets. Conforming CAs MUST NOT use serialNumber values longer than 20 michael@0: // octets." michael@0: // * "Note: Non-conforming CAs may issue certificates with serial numbers michael@0: // that are negative or zero. Certificate users SHOULD be prepared to michael@0: // gracefully handle such certificates." michael@0: michael@0: return Integer(input, serialNumber); michael@0: } michael@0: michael@0: // x.509 and OCSP both use this same version numbering scheme, though OCSP michael@0: // only supports v1. michael@0: enum Version { v1 = 0, v2 = 1, v3 = 2 }; michael@0: michael@0: // X.509 Certificate and OCSP ResponseData both use this michael@0: // "[0] EXPLICIT Version DEFAULT " construct, but with michael@0: // different default versions. michael@0: inline Result michael@0: OptionalVersion(Input& input, /*out*/ uint8_t& version) michael@0: { michael@0: const uint8_t tag = CONTEXT_SPECIFIC | CONSTRUCTED | 0; michael@0: if (!input.Peek(tag)) { michael@0: version = v1; michael@0: return Success; michael@0: } michael@0: if (ExpectTagAndLength(input, tag, 3) != Success) { michael@0: return Failure; michael@0: } michael@0: if (ExpectTagAndLength(input, INTEGER, 1) != Success) { michael@0: return Failure; michael@0: } michael@0: if (input.Read(version) != Success) { michael@0: return Failure; michael@0: } michael@0: if (version & 0x80) { // negative michael@0: return Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: return Success; michael@0: } michael@0: michael@0: } } } // namespace mozilla::pkix::der michael@0: michael@0: #endif // mozilla_pkix__pkixder_h