diff options
author | nharper <nharper@chromium.org> | 2015-04-22 16:13:50 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-22 23:13:50 +0000 |
commit | 00e51a363a532f1e2c0d2defcf9035e7bf33ddc0 (patch) | |
tree | 5aa3debb016cdef1a3b771e7b4469fe352888393 /net | |
parent | 2d6f0f7d524088b0ea2577433d8b46fa020369ec (diff) | |
download | chromium_src-00e51a363a532f1e2c0d2defcf9035e7bf33ddc0.zip chromium_src-00e51a363a532f1e2c0d2defcf9035e7bf33ddc0.tar.gz chromium_src-00e51a363a532f1e2c0d2defcf9035e7bf33ddc0.tar.bz2 |
Add DER parser to //net
The DER parsing code is split into 2 main sections - the Parser class for parsing constructed values, and the ParseType free functions for parsing primitive values. The Parser class should be complete at this point (with the exception of adding more convenience methods to match free functions). This CL has only a very limited number of free functions for parsing primitive values; more will be added in later CLs based on the demand needed for other types.
Review URL: https://codereview.chromium.org/878813002
Cr-Commit-Position: refs/heads/master@{#326389}
Diffstat (limited to 'net')
-rw-r--r-- | net/der/input.cc | 90 | ||||
-rw-r--r-- | net/der/input.h | 152 | ||||
-rw-r--r-- | net/der/input_unittest.cc | 125 | ||||
-rw-r--r-- | net/der/parse_values.cc | 269 | ||||
-rw-r--r-- | net/der/parse_values.h | 69 | ||||
-rw-r--r-- | net/der/parse_values_unittest.cc | 201 | ||||
-rw-r--r-- | net/der/parser.cc | 192 | ||||
-rw-r--r-- | net/der/parser.h | 177 | ||||
-rw-r--r-- | net/der/parser_unittest.cc | 201 | ||||
-rw-r--r-- | net/der/tag.cc | 28 | ||||
-rw-r--r-- | net/der/tag.h | 63 | ||||
-rw-r--r-- | net/net.gypi | 11 |
12 files changed, 1578 insertions, 0 deletions
diff --git a/net/der/input.cc b/net/der/input.cc new file mode 100644 index 0000000..7c875a4 --- /dev/null +++ b/net/der/input.cc @@ -0,0 +1,90 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "net/der/input.h" + +namespace net { + +namespace der { + +Input::Input() : data_(nullptr), len_(0) { +} +Input::Input(const uint8_t* data, size_t len) : data_(data), len_(len) { +} +Input::Input(const std::string& s) + : data_(reinterpret_cast<const uint8_t*>(s.data())), len_(s.size()) { +} + +ByteReader::ByteReader(const Input& in) + : data_(in.UnsafeData()), len_(in.Length()) { +} + +bool ByteReader::ReadByte(uint8_t* byte_p) { + if (!HasMore()) + return false; + *byte_p = *data_; + Advance(1); + return true; +} + +bool ByteReader::ReadBytes(size_t len, Input* out) { + if (len > len_) + return false; + *out = Input(data_, len); + Advance(len); + return true; +} + +// Returns whether there is any more data to be read. +bool ByteReader::HasMore() { + return len_ > 0; +} + +Mark ByteReader::NewMark() { + return Mark(data_); +} + +bool ByteReader::AdvanceToMark(Mark mark) { + if (mark.ptr_ < data_) + return false; + // mark.ptr_ >= data_, so no concern of integer underflow here. + size_t advance_len = mark.ptr_ - data_; + if (advance_len > len_) + return false; + Advance(advance_len); + return true; +} + +bool ByteReader::ReadToMark(Mark mark, Input* out) { + if (mark.ptr_ < data_) + return false; + // mark.ptr_ >= data_, so no concern of integer underflow here. + size_t len = mark.ptr_ - data_; + return ReadBytes(len, out); +} + +void ByteReader::Advance(size_t len) { + CHECK_LE(len, len_); + data_ += len; + len_ -= len; +} + +Mark Mark::NullMark() { + return Mark(); +} + +bool Mark::IsEmpty() { + return ptr_ == nullptr; +} + +Mark::Mark(const uint8_t* ptr) : ptr_(ptr) { +} + +Mark::Mark() : ptr_(nullptr) { +} + +} // namespace der + +} // namespace net diff --git a/net/der/input.h b/net/der/input.h new file mode 100644 index 0000000..667bd89 --- /dev/null +++ b/net/der/input.h @@ -0,0 +1,152 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DER_INPUT_H_ +#define NET_DER_INPUT_H_ + +#include <stdint.h> +#include <string> + +#include "base/compiler_specific.h" +#include "net/base/net_export.h" + +namespace net { + +namespace der { + +class Mark; + +// An opaque class that represents a fixed buffer of data of a fixed length, +// to be used as an input to other operations. An Input object does not own +// the data it references, so callers are responsible for making sure that +// the data outlives the Input object and any other associated objects. +// +// All data access for an Input should be done through the ByteReader and Mark +// classes. This class and associated classes are designed with safety in mind +// to make it difficult to read memory outside of an Input. ByteReader provides +// a simple API for reading through the Input sequentially. For more +// complicated uses, multiple instances of a ByteReader for a particular Input +// can be created, and instances of Mark can be used to coordinate between the +// ByteReaders. +// +// One such use case of multiple ByteReaders would be looking ahead in an +// input: A ByteReader is copied and then is used to read some number of +// bytes into the input, based on the content it is reading. A Mark can then be +// set using the temporary ByteReader to indicate how far it read into the +// Input. The original ByteReader can then be synchronized with how far the +// temporary ByteReader read, by using either AdvanceToMark() or ReadToMark(). +class NET_EXPORT_PRIVATE Input { + public: + // Creates an empty Input, one from which no data can be read. + Input(); + + // Creates an Input from the given |data| and |len|. + Input(const uint8_t* data, size_t len); + + // Creates an Input from the given string |s|. + explicit Input(const std::string& s); + + // Returns the length in bytes of an Input's data. + size_t Length() const { return len_; } + + // Returns a pointer to the Input's data. This method is marked as "unsafe" + // because access to the Input's data should be done through ByteReader + // instead. This method should only be used where using a ByteReader truly + // is not an option. + const uint8_t* UnsafeData() const { return data_; } + + private: + const uint8_t* data_; + size_t len_; +}; + +// This class provides ways to read data from an Input in a bounds-checked way. +// The ByteReader is designed to read through the input sequentially. Once a +// byte has been read with a ByteReader, the caller can't go back and re-read +// that byte with the same reader. Of course, the caller can create multiple +// ByteReaders for the same input (or copy an existing ByteReader). +// +// For something simple like a single byte lookahead, the easiest way to do +// that is to copy the ByteReader and call ReadByte() on the copy - the original +// ByteReader will be unaffected and the peeked byte will be read through +// ReadByte(). For other read patterns, it can be useful to mark where one is +// in a ByteReader to be able to return to that spot. +// +// Some operations using Mark can also be done by creating a copy of the +// ByteReader. By using a Mark instead, you use less memory, but more +// importantly, you end up with an immutable object that matches the semantics +// of what is intended. +class NET_EXPORT_PRIVATE ByteReader { + public: + // Creates a ByteReader to read the data represented by an Input. + explicit ByteReader(const Input& in); + + // Reads a single byte from the input source, putting the byte read in + // |*byte_p|. If a byte cannot be read from the input (because there is + // no input left), then this method returns false. + bool ReadByte(uint8_t* out) WARN_UNUSED_RESULT; + + // Reads |len| bytes from the input source, and initializes an Input to + // point to that data. If there aren't enough bytes left in the input source, + // then this method returns false. + bool ReadBytes(size_t len, Input* out) WARN_UNUSED_RESULT; + + // Returns how many bytes are left to read. + size_t BytesLeft() const { return len_; } + + // Returns whether there is any more data to be read. + bool HasMore(); + + // Creates a new Mark at the current position of this ByteReader. If another + // ByteReader is advanced to this mark, the next byte read by the other + // ByteReader will be the same as the next byte read by this ByteReader. + Mark NewMark(); + + // Advances this ByteReader to the position marked by |mark|. Note that + // a ByteReader can only advance forward - it is not possible to "rewind" + // to a previous mark. To do this, one would need to create a new ByteReader + // (from the same input) and AdvanceToMark() on the new ByteReader. + // + // If it is not possible to advance this ByteReader to the mark, this method + // returns false and does nothing. + bool AdvanceToMark(Mark mark) WARN_UNUSED_RESULT; + + // Reads all data from the cursor of this ByteReader up to the mark, and + // initializes an Input to point to that data. If the Mark is not valid for + // this ByteReader, then this method returns false and does not modify |*out|. + bool ReadToMark(Mark mark, Input* out) WARN_UNUSED_RESULT; + + private: + void Advance(size_t len); + + const uint8_t* data_; + size_t len_; +}; + +// An immutable opaque pointer into the data represented by an Input. A Mark +// object is used to save and restore the state (position) of a ByteReader to +// allow for lookahead or backtracking. All interaction with a Mark object is +// done through the ByteReader class. +class NET_EXPORT_PRIVATE Mark { + public: + // Creates a null Mark. This Mark will not be usable with any ByteReader. + // This only exists so that a class can have a Mark member which may or may + // not be a valid Mark at any given time. + static Mark NullMark(); + + // Checks whether a given Mark is an empty Mark. + bool IsEmpty(); + friend class ByteReader; + + private: + explicit Mark(const uint8_t* ptr); + Mark(); + const uint8_t* ptr_; +}; + +} // namespace der + +} // namespace net + +#endif // NET_DER_INPUT_H_ diff --git a/net/der/input_unittest.cc b/net/der/input_unittest.cc new file mode 100644 index 0000000..a6a9b8c --- /dev/null +++ b/net/der/input_unittest.cc @@ -0,0 +1,125 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "net/der/input.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace der { +namespace test { + +const uint8_t kInput[] = {'t', 'e', 's', 't'}; + +TEST(ByteReaderTest, NoReadPastEnd) { + ByteReader reader(Input(nullptr, 0)); + uint8_t data; + EXPECT_FALSE(reader.ReadByte(&data)); +} + +TEST(ByteReaderTest, ReadToEnd) { + uint8_t out; + ByteReader reader(Input(kInput, arraysize(kInput))); + for (size_t i = 0; i < arraysize(kInput); ++i) { + ASSERT_TRUE(reader.ReadByte(&out)); + ASSERT_EQ(kInput[i], out); + } + EXPECT_FALSE(reader.ReadByte(&out)); +} + +TEST(ByteReaderTest, PartialReadFails) { + Input out; + ByteReader reader(Input(kInput, arraysize(kInput))); + EXPECT_FALSE(reader.ReadBytes(5, &out)); +} + +TEST(ByteReaderTest, HasMore) { + Input out; + ByteReader reader(Input(kInput, arraysize(kInput))); + + ASSERT_TRUE(reader.HasMore()); + ASSERT_TRUE(reader.ReadBytes(arraysize(kInput), &out)); + ASSERT_FALSE(reader.HasMore()); +} + +TEST(ByteReaderTest, ReadToMark) { + uint8_t out; + Input input(kInput, arraysize(kInput)); + ByteReader reader(input); + + // Read 2 bytes from the reader and then set a mark. + ASSERT_TRUE(reader.ReadByte(&out)); + ASSERT_TRUE(reader.ReadByte(&out)); + Mark mark = reader.NewMark(); + + // Reset the reader and check that we can read to a mark previously set. + reader = ByteReader(input); + Input marked_data; + ASSERT_TRUE(reader.ReadToMark(mark, &marked_data)); +} + +TEST(ByteReaderTest, CantReadToWrongMark) { + Input out; + Input in1(kInput, arraysize(kInput)); + Input in2("test"); + ByteReader reader1(in1); + ByteReader reader2(in2); + ASSERT_TRUE(reader1.ReadBytes(2, &out)); + ASSERT_TRUE(reader2.ReadBytes(2, &out)); + Mark mark1 = reader1.NewMark(); + Mark mark2 = reader2.NewMark(); + reader1 = ByteReader(in1); + reader2 = ByteReader(in2); + + // It is not possible to advance to a mark outside the underlying input. + ASSERT_FALSE(reader1.AdvanceToMark(mark2)); + ASSERT_FALSE(reader2.AdvanceToMark(mark1)); +} + +TEST(ByteReaderTest, MarksAreSharedBetweenSameInputs) { + Input out; + Input in1(kInput, arraysize(kInput)); + Input in2(kInput, 1); + ByteReader reader1(in1); + ByteReader reader2(in2); + ASSERT_TRUE(reader1.ReadBytes(2, &out)); + ASSERT_TRUE(reader2.ReadBytes(1, &out)); + Mark mark1 = reader1.NewMark(); + Mark mark2 = reader2.NewMark(); + reader1 = ByteReader(in1); + reader2 = ByteReader(in2); + + // If Marks are created on the same underlying data, they can be shared + // across ByteReaders and Inputs. However, they still must be inside the + // bounds for the ByteReader they are being used on. + + // mark1 is past the end of the input for reader2. + EXPECT_FALSE(reader2.AdvanceToMark(mark1)); + // mark2 is within the bounds of reader1. + EXPECT_TRUE(reader1.AdvanceToMark(mark2)); +} + +TEST(ByteReaderTest, CantReadToWrongMarkWithInputsOnStack) { + const uint8_t data1[] = "test"; + const uint8_t data2[] = "foo"; + Input out; + Input in1(data1, arraysize(data1)); + Input in2(data2, arraysize(data2)); + + ByteReader reader1(in1); + ByteReader reader2(in2); + ASSERT_TRUE(reader1.ReadBytes(2, &out)); + ASSERT_TRUE(reader2.ReadBytes(2, &out)); + Mark mark1 = reader1.NewMark(); + Mark mark2 = reader2.NewMark(); + reader1 = ByteReader(in1); + reader2 = ByteReader(in2); + + ASSERT_FALSE(reader1.AdvanceToMark(mark2)); + ASSERT_FALSE(reader2.AdvanceToMark(mark1)); +} + +} // namespace test +} // namespace der +} // namespace net diff --git a/net/der/parse_values.cc b/net/der/parse_values.cc new file mode 100644 index 0000000..bbf765f --- /dev/null +++ b/net/der/parse_values.cc @@ -0,0 +1,269 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "net/der/parse_values.h" + +namespace net { + +namespace der { + +namespace { + +bool ParseBoolInternal(const Input& in, bool* out, bool relaxed) { + // According to ITU-T X.690 section 8.2, a bool is encoded as a single octet + // where the octet of all zeroes is FALSE and a non-zero value for the octet + // is TRUE. + if (in.Length() != 1) + return false; + ByteReader data(in); + uint8_t byte; + if (!data.ReadByte(&byte)) + return false; + if (byte == 0) { + *out = false; + return true; + } + // ITU-T X.690 section 11.1 specifies that for DER, the TRUE value must be + // encoded as an octet of all ones. + if (byte == 0xff || relaxed) { + *out = true; + return true; + } + return false; +} + +// Reads a positive decimal number with |digits| digits and stores it in +// |*out|. This function does not check that the type of |*out| is large +// enough to hold 10^digits - 1; the caller must choose an appropriate type +// based on the number of digits they wish to parse. +template <typename UINT> +bool DecimalStringToUint(ByteReader& in, size_t digits, UINT* out) { + UINT value = 0; + for (size_t i = 0; i < digits; ++i) { + uint8_t digit; + if (!in.ReadByte(&digit)) { + return false; + } + if (digit < '0' || digit > '9') { + return false; + } + value = (value * 10) + (digit - '0'); + } + *out = value; + return true; +} + +// Checks that the values in a GeneralizedTime struct are valid. This involves +// checking that the year is 4 digits, the month is between 1 and 12, the day +// is a day that exists in that month (following current leap year rules), +// hours are between 0 and 23, minutes between 0 and 59, and seconds between +// 0 and 60 (to allow for leap seconds; no validation is done that a leap +// second is on a day that could be a leap second). +bool ValidateGeneralizedTime(const GeneralizedTime& time) { + CHECK(time.year > 0 && time.year < 9999); + if (time.month < 1 || time.month > 12) + return false; + if (time.day < 1) + return false; + if (time.hours < 0 || time.hours > 23) + return false; + if (time.minutes < 0 || time.minutes > 59) + return false; + // Leap seconds are allowed. + if (time.seconds < 0 || time.seconds > 60) + return false; + + // validate upper bound for day of month + switch (time.month) { + case 4: + case 6: + case 9: + case 11: + if (time.day > 30) + return false; + break; + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + if (time.day > 31) + return false; + break; + case 2: + if (time.year % 4 == 0 && + (time.year % 100 != 0 || time.year % 400 == 0)) { + if (time.day > 29) + return false; + } else { + if (time.day > 28) + return false; + } + break; + default: + NOTREACHED(); + return false; + } + return true; +} + +} // namespace + +bool ParseBool(const Input& in, bool* out) { + return ParseBoolInternal(in, out, false /* relaxed */); +} + +// BER interprets any non-zero value as true, while DER requires a bool to +// have either all bits zero (false) or all bits one (true). To support +// malformed certs, we recognized the BER encoding instead of failing to +// parse. +bool ParseBoolRelaxed(const Input& in, bool* out) { + return ParseBoolInternal(in, out, true /* relaxed */); +} + +bool ParseUint64(const Input& in, uint64_t* out) { + ByteReader reader(in); + size_t bytes_read = 0; + uint8_t data; + uint64_t value = 0; + // Note that for simplicity, this check only admits integers up to 2^63-1. + if (in.Length() > sizeof(uint64_t) || in.Length() == 0) + return false; + while (reader.ReadByte(&data)) { + if (bytes_read == 0 && (data & 0x80)) { + return false; + } + value <<= 8; + value |= data; + bytes_read++; + } + // ITU-T X.690 section 8.3.2 specifies that an integer value must be encoded + // in the smallest number of octets. If the encoding consists of more than + // one octet, then the bits of the first octet and the most significant bit + // of the second octet must not be all zeroes or all ones. + // Because this function only parses unsigned integers, there's no need to + // check for the all ones case. + if (bytes_read > 1) { + ByteReader first_bytes_reader(in); + uint8_t first_byte; + uint8_t second_byte; + if (!first_bytes_reader.ReadByte(&first_byte) || + !first_bytes_reader.ReadByte(&second_byte)) { + return false; + } + if (first_byte == 0 && !(second_byte & 0x80)) { + return false; + } + } + *out = value; + return true; +} + +bool operator<(const GeneralizedTime& lhs, const GeneralizedTime& rhs) { + if (lhs.year != rhs.year) + return lhs.year < rhs.year; + if (lhs.month != rhs.month) + return lhs.month < rhs.month; + if (lhs.day != rhs.day) + return lhs.day < rhs.day; + if (lhs.hours != rhs.hours) + return lhs.hours < rhs.hours; + if (lhs.minutes != rhs.minutes) + return lhs.minutes < rhs.minutes; + return lhs.seconds < rhs.seconds; +} + +// A UTC Time in DER encoding should be YYMMDDHHMMSSZ, but some CAs encode +// the time following BER rules, which allows for YYMMDDHHMMZ. If the length +// is 11, assume it's YYMMDDHHMMZ, and in converting it to a GeneralizedTime, +// add in the seconds (set to 0). +bool ParseUTCTimeRelaxed(const Input& in, GeneralizedTime* value) { + ByteReader reader(in); + GeneralizedTime time; + if (!DecimalStringToUint(reader, 2, &time.year) || + !DecimalStringToUint(reader, 2, &time.month) || + !DecimalStringToUint(reader, 2, &time.day) || + !DecimalStringToUint(reader, 2, &time.hours) || + !DecimalStringToUint(reader, 2, &time.minutes)) { + return false; + } + + // Try to read the 'Z' at the end. If we read something else, then for it to + // be valid the next bytes should be seconds (and then followed by 'Z'). + uint8_t zulu; + ByteReader zulu_reader = reader; + if (!zulu_reader.ReadByte(&zulu)) + return false; + if (zulu == 'Z' && !zulu_reader.HasMore()) { + time.seconds = 0; + *value = time; + return true; + } + if (!DecimalStringToUint(reader, 2, &time.seconds)) + return false; + if (!reader.ReadByte(&zulu) || zulu != 'Z' || reader.HasMore()) + return false; + if (!ValidateGeneralizedTime(time)) + return false; + if (time.year < 50) { + time.year += 2000; + } else { + time.year += 1900; + } + *value = time; + return true; +} + +bool ParseUTCTime(const Input& in, GeneralizedTime* value) { + ByteReader reader(in); + GeneralizedTime time; + if (!DecimalStringToUint(reader, 2, &time.year) || + !DecimalStringToUint(reader, 2, &time.month) || + !DecimalStringToUint(reader, 2, &time.day) || + !DecimalStringToUint(reader, 2, &time.hours) || + !DecimalStringToUint(reader, 2, &time.minutes) || + !DecimalStringToUint(reader, 2, &time.seconds)) { + return false; + } + uint8_t zulu; + if (!reader.ReadByte(&zulu) || zulu != 'Z' || reader.HasMore()) + return false; + if (!ValidateGeneralizedTime(time)) + return false; + if (time.year < 50) { + time.year += 2000; + } else { + time.year += 1900; + } + *value = time; + return true; +} + +bool ParseGeneralizedTime(const Input& in, GeneralizedTime* value) { + ByteReader reader(in); + GeneralizedTime time; + if (!DecimalStringToUint(reader, 4, &time.year) || + !DecimalStringToUint(reader, 2, &time.month) || + !DecimalStringToUint(reader, 2, &time.day) || + !DecimalStringToUint(reader, 2, &time.hours) || + !DecimalStringToUint(reader, 2, &time.minutes) || + !DecimalStringToUint(reader, 2, &time.seconds)) { + return false; + } + uint8_t zulu; + if (!reader.ReadByte(&zulu) || zulu != 'Z' || reader.HasMore()) + return false; + if (!ValidateGeneralizedTime(time)) + return false; + *value = time; + return true; +} + +} // namespace der + +} // namespace net diff --git a/net/der/parse_values.h b/net/der/parse_values.h new file mode 100644 index 0000000..23c534e --- /dev/null +++ b/net/der/parse_values.h @@ -0,0 +1,69 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DER_PARSE_TYPES_H_ +#define NET_DER_PARSE_TYPES_H_ + +#include "base/compiler_specific.h" +#include "net/base/net_export.h" +#include "net/der/input.h" + +namespace net { + +namespace der { + +// Reads a DER-encoded ASN.1 BOOLEAN value from |in| and puts the resulting +// value in |out|. Returns whether the encoded value could successfully be +// read. +NET_EXPORT bool ParseBool(const Input& in, bool* out) WARN_UNUSED_RESULT; + +// Like ParseBool, except it is more relaxed in what inputs it accepts: Any +// value that is a valid BER encoding will be parsed successfully. +NET_EXPORT bool ParseBoolRelaxed(const Input& in, bool* out) WARN_UNUSED_RESULT; + +// Reads a DER-encoded ASN.1 INTEGER value from |in| and puts the resulting +// value in |out|. ASN.1 INTEGERs are arbitrary precision; this function is +// provided as a convenience when the caller knows that the value is unsigned +// and is between 0 and 2^63-1. This function does not support the full range of +// uint64_t. This function returns false if the value is too big to fit in a +// uint64_t, is negative, or if there is an error reading the integer. +NET_EXPORT bool ParseUint64(const Input& in, uint64_t* out) WARN_UNUSED_RESULT; + +struct GeneralizedTime { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hours; + uint8_t minutes; + uint8_t seconds; +}; + +NET_EXPORT_PRIVATE bool operator<(const GeneralizedTime& lhs, + const GeneralizedTime& rhs); + +// Reads a DER-encoded ASN.1 UTCTime value from |in| and puts the resulting +// value in |out|, returning true if the UTCTime could be parsed successfully. +NET_EXPORT bool ParseUTCTime(const Input& in, + GeneralizedTime* out) WARN_UNUSED_RESULT; + +// Like ParseUTCTime, but it is more lenient in what is accepted. DER requires +// a UTCTime to be in the format YYMMDDhhmmssZ; this function will accept both +// that and YYMMDDhhmmZ, which is a valid BER encoding of a UTCTime which +// sometimes incorrectly appears in X.509 certificates. +NET_EXPORT bool ParseUTCTimeRelaxed(const Input& in, + GeneralizedTime* out) WARN_UNUSED_RESULT; + +// Reads a DER-encoded ASN.1 GeneralizedTime value from |in| and puts the +// resulting value in |out|, returning true if the GeneralizedTime could +// be parsed sucessfully. This function is even more restrictive than the +// DER rules - it follows the rules from RFC5280, which does not allow for +// fractional seconds. +NET_EXPORT bool ParseGeneralizedTime(const Input& in, + GeneralizedTime* out) WARN_UNUSED_RESULT; + +} // namespace der + +} // namespace net + +#endif // NET_DER_PARSE_TYPES_H_ diff --git a/net/der/parse_values_unittest.cc b/net/der/parse_values_unittest.cc new file mode 100644 index 0000000..8233042 --- /dev/null +++ b/net/der/parse_values_unittest.cc @@ -0,0 +1,201 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include "base/macros.h" +#include "net/der/parse_values.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace der { +namespace test { + +TEST(ParseValuesTest, ParseBool) { + uint8_t buf[] = {0xFF, 0x00}; + Input value(buf, 1); + bool out; + EXPECT_TRUE(ParseBool(value, &out)); + EXPECT_TRUE(out); + + buf[0] = 0; + EXPECT_TRUE(ParseBool(value, &out)); + EXPECT_FALSE(out); + + buf[0] = 1; + EXPECT_FALSE(ParseBool(value, &out)); + EXPECT_TRUE(ParseBoolRelaxed(value, &out)); + EXPECT_TRUE(out); + + buf[0] = 0xFF; + value = Input(buf, 2); + EXPECT_FALSE(ParseBool(value, &out)); + value = Input(buf, 0); + EXPECT_FALSE(ParseBool(value, &out)); +} + +TEST(ParseValuesTest, ParseTimes) { + GeneralizedTime out; + + EXPECT_TRUE(ParseUTCTime(Input("140218161200Z"), &out)); + + // DER-encoded UTCTime must end with 'Z'. + EXPECT_FALSE(ParseUTCTime(Input("140218161200X"), &out)); + + // Check that a negative number (-4 in this case) doesn't get parsed as + // a 2-digit number. + EXPECT_FALSE(ParseUTCTime(Input("-40218161200Z"), &out)); + + // Check that numbers with a leading 0 don't get parsed in octal by making + // the second digit an invalid octal digit (e.g. 09). + EXPECT_TRUE(ParseUTCTime(Input("090218161200Z"), &out)); + + // Check that the length is validated. + EXPECT_FALSE(ParseUTCTime(Input("140218161200"), &out)); + EXPECT_FALSE(ParseUTCTime(Input("140218161200Z0"), &out)); + EXPECT_FALSE(ParseUTCTimeRelaxed(Input("140218161200"), &out)); + EXPECT_FALSE(ParseUTCTimeRelaxed(Input("140218161200Z0"), &out)); + + // Check strictness of UTCTime parsers. + EXPECT_FALSE(ParseUTCTime(Input("1402181612Z"), &out)); + EXPECT_TRUE(ParseUTCTimeRelaxed(Input("1402181612Z"), &out)); + + // Check that the time ends in Z. + EXPECT_FALSE(ParseUTCTimeRelaxed(Input("1402181612Z0"), &out)); + + // Check format of GeneralizedTime. + + // Leap seconds are allowed. + EXPECT_TRUE(ParseGeneralizedTime(Input("20140218161260Z"), &out)); + + // But nothing larger than a leap second. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140218161261Z"), &out)); + + // Minutes only go up to 59. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140218166000Z"), &out)); + + // Hours only go up to 23. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140218240000Z"), &out)); + // The 0th day of a month is invalid. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140200161200Z"), &out)); + // The 0th month is invalid. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140018161200Z"), &out)); + // Months greater than 12 are invalid. + EXPECT_FALSE(ParseGeneralizedTime(Input("20141318161200Z"), &out)); + + // Some months have 31 days. + EXPECT_TRUE(ParseGeneralizedTime(Input("20140131000000Z"), &out)); + + // September has only 30 days. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140931000000Z"), &out)); + + // February has only 28 days... + EXPECT_FALSE(ParseGeneralizedTime(Input("20140229000000Z"), &out)); + + // ... unless it's a leap year. + EXPECT_TRUE(ParseGeneralizedTime(Input("20160229000000Z"), &out)); + + // There aren't any leap days in years divisible by 100... + EXPECT_FALSE(ParseGeneralizedTime(Input("21000229000000Z"), &out)); + + // ...unless it's also divisible by 400. + EXPECT_TRUE(ParseGeneralizedTime(Input("20000229000000Z"), &out)); + + // Check more perverse invalid inputs. + + const uint8_t trailing_null_bytes[] = {'2', + '0', + '0', + '0', + '1', + '2', + '3', + '1', + '0', + '1', + '0', + '2', + '0', + '3', + 'Z', + '\0'}; + Input trailing_null(trailing_null_bytes, sizeof(trailing_null_bytes)); + EXPECT_FALSE(ParseGeneralizedTime(trailing_null, &out)); + const uint8_t embedded_null_bytes[] = {'2', + '0', + '0', + '\0', + '1', + '2', + '3', + '1', + '0', + '1', + '0', + '2', + '0', + '3', + 'Z'}; + Input embedded_null(embedded_null_bytes, sizeof(embedded_null_bytes)); + EXPECT_FALSE(ParseGeneralizedTime(embedded_null, &out)); + + // The year can't be in hex. + EXPECT_FALSE(ParseGeneralizedTime(Input("0x201231000000Z"), &out)); + + // The last byte must be 'Z'. + EXPECT_FALSE(ParseGeneralizedTime(Input("20001231000000X"), &out)); + + // Check that the length is validated. + EXPECT_FALSE(ParseGeneralizedTime(Input("20140218161200"), &out)); + EXPECT_FALSE(ParseGeneralizedTime(Input("20140218161200Z0"), &out)); +} + +TEST(ParseValuesTest, TimesCompare) { + GeneralizedTime time1; + GeneralizedTime time2; + GeneralizedTime time3; + + ASSERT_TRUE(ParseGeneralizedTime(Input("20140218161200Z"), &time1)); + ASSERT_TRUE(ParseUTCTime(Input("150218161200Z"), &time2)); + ASSERT_TRUE(ParseGeneralizedTime(Input("20160218161200Z"), &time3)); + EXPECT_TRUE(time1 < time2); + EXPECT_TRUE(time2 < time3); + EXPECT_TRUE(time1 < time3); +} + +struct Uint64TestData { + bool should_pass; + const uint8_t input[9]; + size_t length; + uint64_t expected_value; +}; + +const Uint64TestData kUint64TestData[] = { + {true, {0x00}, 1, 0}, + {true, {0x01}, 1, 1}, + {false, {0xFF}, 1, 0}, + {true, {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 8, INT64_MAX}, + {false, {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8, 0}, + {false, {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 9, 0}, + {false, {0x00, 0x01}, 2, 1}, + {false, {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}, 9, 0}, + {false, {0}, 0, 0}, +}; + +TEST(ParseValuesTest, ParseUint64) { + for (size_t i = 0; i < arraysize(kUint64TestData); i++) { + Uint64TestData test_case = kUint64TestData[i]; + SCOPED_TRACE(i); + + uint64_t result; + EXPECT_EQ(test_case.should_pass, + ParseUint64(Input(test_case.input, test_case.length), &result)); + if (test_case.should_pass) + EXPECT_EQ(test_case.expected_value, result); + } +} + +} // namespace test +} // namespace der +} // namespace net diff --git a/net/der/parser.cc b/net/der/parser.cc new file mode 100644 index 0000000..7263fba --- /dev/null +++ b/net/der/parser.cc @@ -0,0 +1,192 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" + +namespace net { + +namespace der { + +Parser::Parser() : input_(Input()), advance_mark_(Mark::NullMark()) { +} +Parser::Parser(const Input& input) + : input_(input), advance_mark_(Mark::NullMark()) { +} + +// Reads the next TLV from the input and writes the tag and value to the +// output parameters |tag| and |out|. +bool Parser::PeekTagAndValue(Tag* tag, Input* out) { + ByteReader reader = input_; + + // Don't support tags > 30. + uint8_t tag_byte; + if (!reader.ReadByte(&tag_byte)) + return false; + + // ITU-T X.690 section 8.1.2.3 specifies the format for identifiers with a + // tag number no greater than 30. This parser only supports tag numbers up + // to 30. + // If the tag number is 31 (0x1F, the largest value that fits in the allotted + // bytes), then the tag is more than one byte long and the continuation bytes + // contain the real tag number. We only support tag numbers < 31 (and thus + // single-byte tags). + if ((tag_byte & kTagNumberMask) == 31) + return false; + + // Parse length. The format for the length encoding is specified in + // ITU-T X.690 section 8.1.3. + size_t value_len = 0; // Number of bytes used to encode just the value. + + uint8_t length_first_byte; + if (!reader.ReadByte(&length_first_byte)) + return false; + if ((length_first_byte & 0x80) == 0) { + // Short form for length - it's only one byte long. + value_len = length_first_byte & 0x7f; + } else { + // Long form for length - it's encoded across multiple bytes. + if (length_first_byte == 0xff) { + // ITU-T X.690 clause 8.1.3.5.c specifies the value 0xff shall not be + // used. + return false; + } + // The high bit indicated that this is the long form, while the next 7 bits + // encode the number of subsequent octets used to encode the length + // (ITU-T X.690 clause 8.1.3.5.b). + size_t length_len = length_first_byte & 0x7f; + if (length_len == 0) { + // ITU-T X.690 section 10.1 (DER length forms) requires encoding the + // length with the minimum number of octets. Besides, it makes no sense + // for the length to be encoded in 0 octets. + return false; + } + if (length_len > sizeof(value_len)) { + // The length is encoded in multiple octets, with the first octet + // indicating how many octets follow. Those octets need to be combined + // to form a size_t, so the number of octets to follow (length_len) + // must be small enough so that they fit in a size_t. + return false; + } + uint8_t length_byte; + for (size_t i = 0; i < length_len; i++) { + if (!reader.ReadByte(&length_byte)) + return false; + // A first length byte of all zeroes means the length was not encoded in + // minimum length. + if (i == 0 && length_byte == 0) + return false; + value_len <<= 8; + value_len += length_byte; + } + if (value_len < 0x80) { + // If value_len is < 0x80, then it could have been encoded in a single + // byte, meaning it was not encoded in minimum length. + return false; + } + } + + if (!reader.ReadBytes(value_len, out)) + return false; + advance_mark_ = reader.NewMark(); + *tag = tag_byte; + return true; +} + +bool Parser::Advance() { + if (advance_mark_.IsEmpty()) + return false; + if (!input_.AdvanceToMark(advance_mark_)) + return false; + advance_mark_ = Mark::NullMark(); + return true; +} + +bool Parser::HasMore() { + return input_.HasMore(); +} + +bool Parser::ReadRawTLV(Input* out) { + Tag tag; + Input value; + if (!PeekTagAndValue(&tag, &value)) + return false; + if (!input_.ReadToMark(advance_mark_, out)) + return false; + advance_mark_ = Mark::NullMark(); + + return true; +} + +bool Parser::ReadTagAndValue(Tag* tag, Input* out) { + if (!PeekTagAndValue(tag, out)) + return false; + CHECK(Advance()); + return true; +} + +bool Parser::ReadOptionalTag(Tag tag, Input* out, bool* present) { + if (!HasMore()) { + *present = false; + return true; + } + + Tag read_tag; + Input value; + if (!PeekTagAndValue(&read_tag, &value)) + return false; + *present = false; + if (read_tag == tag) { + *present = true; + *out = value; + CHECK(Advance()); + } else { + advance_mark_ = Mark::NullMark(); + } + return true; +} + +bool Parser::SkipOptionalTag(Tag tag, bool* present) { + Input out; + return ReadOptionalTag(tag, &out, present); +} + +bool Parser::ReadTag(Tag tag, Input* out) { + bool present; + return ReadOptionalTag(tag, out, &present) && present; +} + +bool Parser::SkipTag(Tag tag) { + Input out; + return ReadTag(tag, &out); +} + +// Type-specific variants of ReadTag + +bool Parser::ReadConstructed(Tag tag, Parser* out) { + if (!IsConstructed(tag)) + return false; + Input data; + if (!ReadTag(tag, &data)) + return false; + *out = Parser(data); + return true; +} + +bool Parser::ReadSequence(Parser* out) { + return ReadConstructed(kSequence, out); +} + +bool Parser::ReadUint64(uint64_t* out) { + Input encodedInt; + if (!ReadTag(kInteger, &encodedInt)) + return false; + return ParseUint64(encodedInt, out); +} + +} // namespace der + +} // namespace net diff --git a/net/der/parser.h b/net/der/parser.h new file mode 100644 index 0000000..4e60239 --- /dev/null +++ b/net/der/parser.h @@ -0,0 +1,177 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DER_PARSER_H_ +#define NET_DER_PARSER_H_ + +#include "base/compiler_specific.h" +#include "base/time/time.h" +#include "net/base/net_export.h" +#include "net/der/input.h" +#include "net/der/tag.h" + +namespace net { + +namespace der { + +// Parses a DER-encoded ASN.1 structure. DER (distinguished encoding rules) +// encodes each data value with a tag, length, and value (TLV). The tag +// indicates the type of the ASN.1 value. Depending on the type of the value, +// it could contain arbitrary bytes, so the length of the value is encoded +// after the tag and before the value to indicate how many bytes of value +// follow. DER also defines how the values are encoded for particular types. +// +// This Parser places a few restrictions on the DER encoding it can parse. The +// largest restriction is that it only supports tags which have a tag number +// no greater than 30 - these are the tags that fit in a single octet. The +// second restriction is that the maximum length for a value that can be parsed +// is 4GB. Both of these restrictions should be fine for any reasonable input. +// +// The Parser class is mainly focused on parsing the TLV structure of DER +// encoding, and does not directly handle parsing primitive values (other +// functions in the net::der namespace are provided for this.) When a Parser +// is created, it is passed in a reference to the encoded data. Because the +// encoded data is not owned by the Parser, the data cannot change during the +// lifespan of the Parser. The Parser functions by keeping a pointer to the +// current TLV which starts at the beginning of the input and advancing through +// the input as each TLV is read. As such, a Parser instance is thread-unsafe. +// +// Most methods for using the Parser write the current tag and/or value to +// the output parameters provided and then advance the input to the next TLV. +// None of the methods explicitly expose the length because it is part of the +// value. All methods return a boolean indicating whether there was a parsing +// error with the current TLV. +// +// Some methods are provided in the Parser class as convenience to both read +// the current TLV from the input and also parse the DER encoded value, +// converting it to a corresponding C++ type. These methods simply combine +// ReadTag() with the appropriate ParseType() free function. +// +// The design of DER encoding allows for nested data structures with +// constructed values, where the value is a series of TLVs. The Parser class +// is not designed to traverse through a nested encoding from a single object, +// but it does facilitate parsing nested data structures through the +// convenience methods ReadSequence() and the more general ReadConstructed(), +// which provide the user with another Parser object to traverse the next +// level of TLVs. +// +// For a brief example of how to use the Parser, suppose we have the following +// ASN.1 type definition: +// +// Foo ::= SEQUENCE { +// bar OCTET STRING OPTIONAL, +// quux OCTET STRING } +// +// If we have a DER-encoded Foo in an Input |encoded_value|, the +// following code shows an example of how to parse the quux field from the +// encoded data. +// +// bool ReadQuux(const Input& encoded_value, Input* quux_out) { +// Parser parser(encoded_value); +// Parser foo_parser; +// if (!parser.ReadSequence(&foo_parser)) +// return false; +// if (!foo_parser->SkipOptionalTag(kOctetString)) +// return false; +// if (!foo_parser->ReadTag(kOctetString, &quux)) +// return false; +// return true; +// } +class NET_EXPORT Parser { + public: + // Default constructor; equivalent to calling Parser(Input()). This only + // exists so that a Parser can be stack allocated and passed in to + // ReadConstructed() and similar methods. + Parser(); + + // Creates a parser to parse over the data represented by input. This class + // assumes that the underlying data will not change over the lifetime of + // the Parser object. + explicit Parser(const Input& input); + + // Returns whether there is any more data left in the input to parse. This + // does not guarantee that the data is parseable. + bool HasMore(); + + // Reads the current TLV from the input and advances. If the tag or length + // encoding for the current value is invalid, this method returns false and + // does not advance the input. Otherwise, it returns true, putting the + // read tag in |tag| and the value in |out|. + bool ReadTagAndValue(Tag* tag, Input* out) WARN_UNUSED_RESULT; + + // Reads the current TLV from the input and advances. Unlike ReadTagAndValue + // where only the value is put in |out|, this puts the raw bytes from the + // tag, length, and value in |out|. + bool ReadRawTLV(Input* out) WARN_UNUSED_RESULT; + + // Basic methods for reading or skipping the current TLV, with an + // expectation of what the current tag should be. It should be possible + // to parse any structure with these 4 methods; convenience methods are also + // provided to make some cases easier. + + // If the current tag in the input is |tag|, it puts the corresponding value + // in |out|, sets |was_present| to true, and advances the input to the next + // TLV. If the current tag is something else, then |was_present| is set to + // false and the input is not advanced. Like ReadTagAndValue, it returns + // false if the encoding is invalid and does not advance the input. + bool ReadOptionalTag(Tag tag, + Input* out, + bool* was_present) WARN_UNUSED_RESULT; + + // Like ReadOptionalTag, but the value is discarded. + bool SkipOptionalTag(Tag tag, bool* was_present) WARN_UNUSED_RESULT; + + // If the current tag matches |tag|, it puts the current value in |out|, + // advances the input, and returns true. Otherwise, it returns false. + bool ReadTag(Tag tag, Input* out) WARN_UNUSED_RESULT; + + // Advances the input and returns true if the current tag matches |tag|; + // otherwise it returns false. + bool SkipTag(Tag tag) WARN_UNUSED_RESULT; + + // Convenience methods to combine parsing the TLV with parsing the DER + // encoding for a specific type. + + // Reads the current TLV from the input, checks that the tag matches |tag| + // and is a constructed tag, and creates a new Parser from the value. + bool ReadConstructed(Tag tag, Parser* out) WARN_UNUSED_RESULT; + + // A more specific form of ReadConstructed that expects the current tag + // to be 0x30 (SEQUENCE). + bool ReadSequence(Parser* out) WARN_UNUSED_RESULT; + + // Expects the current tag to be kInteger, and calls ParseUint64 on the + // current value. Note that DER-encoded integers are arbitrary precision, + // so this method will fail for valid input that represents an integer + // outside the range of an int64. + bool ReadUint64(uint64_t* out) WARN_UNUSED_RESULT; + + // Lower level methods. The previous methods couple reading data from the + // input with advancing the Parser's internal pointer to the next TLV; these + // lower level methods decouple those two steps into methods that read from + // the current TLV and a method that advances the internal pointer to the + // next TLV. + + // Reads the current TLV from the input, putting the tag in |tag| and the raw + // value in |out|, but does not advance the input. Returns true if the tag + // and length are successfully read and the output exists. + bool PeekTagAndValue(Tag* tag, Input* out) WARN_UNUSED_RESULT; + + // Advances the input to the next TLV. This method only needs to be called + // after PeekTagAndValue; all other methods will advance the input if they + // read something. + bool Advance(); + + private: + ByteReader input_; + Mark advance_mark_; + + DISALLOW_COPY(Parser); +}; + +} // namespace der + +} // namespace net + +#endif // NET_DER_PARSER_H_ diff --git a/net/der/parser_unittest.cc b/net/der/parser_unittest.cc new file mode 100644 index 0000000..6c0e177 --- /dev/null +++ b/net/der/parser_unittest.cc @@ -0,0 +1,201 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "net/der/input.h" +#include "net/der/parse_values.h" +#include "net/der/parser.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace der { +namespace test { + +TEST(ParserTest, ConsumesAllBytesOfTLV) { + const uint8_t der[] = {0x04, 0x00}; + Parser parser(Input(der, sizeof(der))); + Tag tag; + Input value; + ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_EQ(0x04, tag); + ASSERT_FALSE(parser.HasMore()); +} + +TEST(ParserTest, CanReadRawTLV) { + const uint8_t der[] = {0x02, 0x01, 0x01}; + Parser parser(Input(der, sizeof(der))); + Input tlv; + ASSERT_TRUE(parser.ReadRawTLV(&tlv)); + ByteReader tlv_reader(tlv); + size_t tlv_len = tlv_reader.BytesLeft(); + ASSERT_EQ(3u, tlv_len); + Input tlv_data; + ASSERT_TRUE(tlv_reader.ReadBytes(tlv_len, &tlv_data)); + ASSERT_FALSE(parser.HasMore()); +} + +TEST(ParserTest, IgnoresContentsOfInnerValues) { + // This is a SEQUENCE which has one member. The member is another SEQUENCE + // with an invalid encoding - its length is too long. + const uint8_t der[] = {0x30, 0x02, 0x30, 0x7e}; + Parser parser(Input(der, sizeof(der))); + Tag tag; + Input value; + ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value)); +} + +TEST(ParserTest, FailsIfLengthOverlapsAnotherTLV) { + // This DER encoding has 2 top-level TLV tuples. The first is a SEQUENCE; + // the second is an INTEGER. The SEQUENCE contains an INTEGER, but its length + // is longer than what it has contents for. + const uint8_t der[] = {0x30, 0x02, 0x02, 0x01, 0x02, 0x01, 0x01}; + Parser parser(Input(der, sizeof(der))); + + Parser inner_sequence; + ASSERT_TRUE(parser.ReadSequence(&inner_sequence)); + uint64_t int_value; + ASSERT_TRUE(parser.ReadUint64(&int_value)); + ASSERT_EQ(1u, int_value); + ASSERT_FALSE(parser.HasMore()); + + // Try to read the INTEGER from the SEQUENCE, which should fail. + Tag tag; + Input value; + ASSERT_FALSE(inner_sequence.ReadTagAndValue(&tag, &value)); +} + +TEST(ParserTest, CanSkipOptionalTagAtEndOfInput) { + const uint8_t der[] = {0x02, 0x01, 0x01}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_TRUE(parser.ReadTagAndValue(&tag, &value)); + bool present; + ASSERT_TRUE(parser.ReadOptionalTag(0x02, &value, &present)); + ASSERT_FALSE(present); + ASSERT_FALSE(parser.HasMore()); +} + +TEST(ParserTest, SkipOptionalTagDoesntConsumePresentNonMatchingTLVs) { + const uint8_t der[] = {0x02, 0x01, 0x01}; + Parser parser(Input(der, sizeof(der))); + + bool present; + ASSERT_TRUE(parser.SkipOptionalTag(0x04, &present)); + ASSERT_FALSE(present); + ASSERT_TRUE(parser.SkipOptionalTag(0x02, &present)); + ASSERT_TRUE(present); + ASSERT_FALSE(parser.HasMore()); +} + +TEST(ParserTest, TagNumbersAboveThirtyUnsupported) { + // Context-specific class, tag number 31, length 0. + const uint8_t der[] = {0x9f, 0x1f, 0x00}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_TRUE(parser.HasMore()); +} + +TEST(ParserTest, IncompleteEncodingTagOnly) { + const uint8_t der[] = {0x01}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_TRUE(parser.HasMore()); +} + +TEST(ParserTest, IncompleteEncodingLengthTruncated) { + // Tag: octet string; length: long form, should have 2 total octets, but + // the last one is missing. (There's also no value.) + const uint8_t der[] = {0x04, 0x81}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_TRUE(parser.HasMore()); +} + +TEST(ParserTest, IncompleteEncodingValueShorterThanLength) { + // Tag: octet string; length: 2; value: first octet 'T', second octet missing. + const uint8_t der[] = {0x04, 0x02, 0x84}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_TRUE(parser.HasMore()); +} + +TEST(ParserTest, LengthMustBeEncodedWithMinimumNumberOfOctets) { + const uint8_t der[] = {0x01, 0x81, 0x01, 0x00}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_TRUE(parser.HasMore()); +} + +TEST(ParserTest, LengthMustNotHaveLeadingZeroes) { + // Tag: octet string; length: 3 bytes of length encoding a value of 128 + // (it should be encoded in only 2 bytes). Value: 128 bytes of 0. + const uint8_t der[] = { + 0x04, 0x83, 0x80, 0x81, 0x80, // group the 0s separately + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Parser parser(Input(der, sizeof(der))); + + Tag tag; + Input value; + ASSERT_FALSE(parser.ReadTagAndValue(&tag, &value)); + ASSERT_TRUE(parser.HasMore()); +} + +TEST(ParserTest, ReadConstructedFailsForNonConstructedTags) { + // Tag number is for SEQUENCE, but the constructed bit isn't set. + const uint8_t der[] = {0x10, 0x00}; + Parser parser(Input(der, sizeof(der))); + + Tag expected_tag = 0x10; + Parser sequence_parser; + ASSERT_FALSE(parser.ReadConstructed(expected_tag, &sequence_parser)); + + // Check that we didn't fail above because of a tag mismatch or an improperly + // encoded TLV. + Input value; + ASSERT_TRUE(parser.ReadTag(expected_tag, &value)); + ASSERT_FALSE(parser.HasMore()); +} + +TEST(ParserTest, CannotAdvanceAfterReadOptionalTag) { + const uint8_t der[] = {0x02, 0x01, 0x01}; + Parser parser(Input(der, sizeof(der))); + + Input value; + bool present; + ASSERT_TRUE(parser.ReadOptionalTag(0x04, &value, &present)); + ASSERT_FALSE(present); + ASSERT_FALSE(parser.Advance()); +} + +} // namespace test +} // namespace der +} // namespace net diff --git a/net/der/tag.cc b/net/der/tag.cc new file mode 100644 index 0000000..00ced68 --- /dev/null +++ b/net/der/tag.cc @@ -0,0 +1,28 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/logging.h" +#include "net/der/tag.h" + +namespace net { + +namespace der { + +Tag ContextSpecificConstructed(uint8_t base) { + DCHECK_EQ(base, base & kTagNumberMask); + return (base & kTagNumberMask) | kTagConstructed | kTagContextSpecific; +} + +Tag ContextSpecificPrimitive(uint8_t base) { + DCHECK_EQ(base, base & kTagNumberMask); + return (base & kTagNumberMask) | kTagPrimitive | kTagContextSpecific; +} + +bool IsConstructed(Tag tag) { + return (tag & kTagConstructionMask) == kTagConstructed; +} + +} // namespace der + +} // namespace net diff --git a/net/der/tag.h b/net/der/tag.h new file mode 100644 index 0000000..e9092cb --- /dev/null +++ b/net/der/tag.h @@ -0,0 +1,63 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_DER_TAG_H_ +#define NET_DER_TAG_H_ + +#include <stdint.h> + +#include "net/base/net_export.h" + +namespace net { + +namespace der { + +// This Tag type represents the identifier for an ASN.1 tag as encoded with DER. +// It follows the same bit-for-bit representation (including the class, tag +// number, and primitive/constructed bit) as DER. Constants are provided for +// universal class types, and functions are provided for building context +// specific tags. Tags can also be built from the provided constants and +// bitmasks. +using Tag = uint8_t; + +// Universal class primitive types +const Tag kBool = 0x01; +const Tag kInteger = 0x02; +const Tag kBitString = 0x03; +const Tag kOctetString = 0x04; +const Tag kNull = 0x05; +const Tag kOid = 0x06; +const Tag kUtf8String = 0x0C; +const Tag kPrintableString = 0x13; +const Tag kUtcTime = 0x17; +const Tag kGeneralizedTime = 0x18; + +// Universal class constructed types +const Tag kSequence = 0x30; +const Tag kSet = 0x31; + +// Primitive/constructed bits +const uint8_t kTagPrimitive = 0x00; +const uint8_t kTagConstructed = 0x20; + +// Tag classes +const uint8_t kTagUniversal = 0x00; +const uint8_t kTagApplication = 0x40; +const uint8_t kTagContextSpecific = 0x80; +const uint8_t kTagPrivate = 0xC0; + +// Masks for the 3 components of a tag (class, primitive/constructed, number) +const uint8_t kTagNumberMask = 0x1F; +const uint8_t kTagConstructionMask = 0x20; +const uint8_t kTagClassMask = 0xC0; + +NET_EXPORT Tag ContextSpecificConstructed(uint8_t base); +NET_EXPORT Tag ContextSpecificPrimitive(uint8_t base); +NET_EXPORT bool IsConstructed(Tag tag); + +} // namespace der + +} // namespace net + +#endif // NET_DER_TAG_H_ diff --git a/net/net.gypi b/net/net.gypi index 37a5d88..457cb24 100644 --- a/net/net.gypi +++ b/net/net.gypi @@ -93,6 +93,14 @@ 'cert/x509_util.h', 'cert/x509_util_openssl.cc', 'cert/x509_util_openssl.h', + 'der/input.cc', + 'der/input.h', + 'der/parse_values.cc', + 'der/parse_values.h', + 'der/parser.cc', + 'der/parser.h', + 'der/tag.cc', + 'der/tag.h', 'http/http_auth_challenge_tokenizer.cc', 'http/http_auth_challenge_tokenizer.h', 'http/http_byte_range.cc', @@ -1330,6 +1338,9 @@ 'cookies/cookie_store_unittest.h', 'cookies/cookie_util_unittest.cc', 'cookies/parsed_cookie_unittest.cc', + 'der/input_unittest.cc', + 'der/parser_unittest.cc', + 'der/parse_values_unittest.cc', 'disk_cache/backend_unittest.cc', 'disk_cache/blockfile/addr_unittest.cc', 'disk_cache/blockfile/bitmap_unittest.cc', |