diff options
Diffstat (limited to 'security/pkix/lib/pkixder.cpp')
-rw-r--r-- | security/pkix/lib/pkixder.cpp | 612 |
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 |