1
#pragma once
2

            
3
#include <iomanip>
4
#include <sstream>
5
#include <vector>
6

            
7
#include "envoy/common/exception.h"
8
#include "envoy/common/time.h"
9

            
10
#include "source/common/common/assert.h"
11

            
12
#include "absl/status/statusor.h"
13
#include "absl/types/optional.h"
14
#include "absl/types/variant.h"
15
#include "openssl/bn.h"
16
#include "openssl/bytestring.h"
17
#include "openssl/ssl.h"
18

            
19
namespace Envoy {
20
namespace Extensions {
21
namespace TransportSockets {
22
namespace Tls {
23
namespace Ocsp {
24

            
25
constexpr absl::string_view GENERALIZED_TIME_FORMAT = "%E4Y%m%d%H%M%S";
26

            
27
/**
28
 * Construct a `T` from the data contained in the CBS&. Functions
29
 * of this type must advance the input CBS& over the element.
30
 */
31
template <typename T> using Asn1ParsingFunc = std::function<absl::StatusOr<T>(CBS&)>;
32

            
33
/**
34
 * Utility functions for parsing DER-encoded `ASN.1` objects.
35
 * This relies heavily on the 'openssl/bytestring' API which
36
 * is BoringSSL's recommended interface for parsing DER-encoded
37
 * `ASN.1` data when there is not an existing wrapper.
38
 * This is not a complete library for `ASN.1` parsing and primarily
39
 * serves as abstractions for the OCSP module, but can be
40
 * extended and moved into a general utility to support parsing of
41
 * additional `ASN.1` objects.
42
 *
43
 * Each function adheres to the invariant that given a reference
44
 * to a crypto `bytestring` (CBS&), it will parse the specified
45
 * `ASN.1` element and advance `cbs` over it.
46
 *
47
 * An exception is thrown if the `bytestring` is malformed or does
48
 * not match the specified `ASN.1` object. The position
49
 * of `cbs` is not reliable after an exception is thrown.
50
 */
51
class Asn1Utility {
52
public:
53
  ~Asn1Utility() = default;
54

            
55
  /**
56
   * Extracts the full contents of `cbs` as a string.
57
   *
58
   * @param `cbs` a CBS& that refers to the current document position
59
   * @returns absl::string_view containing the contents of `cbs`
60
   */
61
  static absl::string_view cbsToString(CBS& cbs);
62

            
63
  /**
64
   * Parses all elements of an `ASN.1` SEQUENCE OF. `parse_element` must
65
   * advance its input CBS& over the entire element.
66
   *
67
   * @param cbs a CBS& that refers to an `ASN.1` SEQUENCE OF object
68
   * @param parse_element an `Asn1ParsingFunc<T>` used to parse each element
69
   * @returns absl::StatusOr<std::vector<T>> containing the parsed elements of the sequence
70
   * or an error string if `cbs` does not point to a well-formed
71
   * SEQUENCE OF object.
72
   */
73
  template <typename T>
74
  static absl::StatusOr<std::vector<T>> parseSequenceOf(CBS& cbs, Asn1ParsingFunc<T> parse_element);
75

            
76
  /**
77
   * Checks if an explicitly tagged optional element of `tag` is present and
78
   * if so parses its value with `parse_data`. If the element is not present,
79
   * `cbs` is not advanced.
80
   *
81
   * @param cbs a CBS& that refers to the current document position
82
   * @param parse_data an `Asn1ParsingFunc<T>` used to parse the data if present
83
   * @return absl::StatusOr<absl::optional<T>> with a `T` if `cbs` is of the specified tag,
84
   * nullopt if the element has a different tag, or an error string if parsing fails.
85
   */
86
  template <typename T>
87
  static absl::StatusOr<absl::optional<T>> parseOptional(CBS& cbs, Asn1ParsingFunc<T> parse_data,
88
                                                         unsigned tag);
89

            
90
  /**
91
   * Returns whether or not an element explicitly tagged with `tag` is present
92
   * at `cbs`. If so, `cbs` is advanced over the optional and assigns
93
   * `data` to the inner element, if `data` is not nullptr.
94
   * If `cbs` does not contain `tag`, `cbs` remains at the same position.
95
   *
96
   * @param cbs a CBS& that refers to the current document position
97
   * @param an unsigned explicit tag indicating an optional value
98
   *
99
   * @returns absl::StatusOr<bool> whether `cbs` points to an element tagged with `tag` or
100
   * an error string if parsing fails.
101
   */
102
  static absl::StatusOr<absl::optional<CBS>> getOptional(CBS& cbs, unsigned tag);
103

            
104
  /**
105
   * @param cbs a CBS& that refers to an `ASN.1` OBJECT IDENTIFIER element
106
   * @returns absl::StatusOr<std::string> the `OID` encoded in `cbs` or an error
107
   * string if `cbs` does not point to a well-formed OBJECT IDENTIFIER
108
   */
109
  static absl::StatusOr<std::string> parseOid(CBS& cbs);
110

            
111
  /**
112
   * @param cbs a CBS& that refers to an `ASN.1` `GENERALIZEDTIME` element
113
   * @returns absl::StatusOr<Envoy::SystemTime> the UTC timestamp encoded in `cbs`
114
   * or an error string if `cbs` does not point to a well-formed
115
   * `GENERALIZEDTIME`
116
   */
117
  static absl::StatusOr<Envoy::SystemTime> parseGeneralizedTime(CBS& cbs);
118

            
119
  /**
120
   * Parses an `ASN.1` INTEGER type into its hex string representation.
121
   * `ASN.1` INTEGER types are arbitrary precision.
122
   * If you're SURE the integer fits into a fixed-size int,
123
   * use `CBS_get_asn1_*` functions for the given integer type instead.
124
   *
125
   * @param cbs a CBS& that refers to an `ASN.1` INTEGER element
126
   * @returns absl::StatusOr<std::string> a hex representation of the integer
127
   * or an error string if `cbs` does not point to a well-formed INTEGER
128
   */
129
  static absl::StatusOr<std::string> parseInteger(CBS& cbs);
130

            
131
  /**
132
   * @param cbs a CBS& that refers to an `ASN.1` `OCTETSTRING` element
133
   * @returns absl::StatusOr<std::vector<uint8_t>> the octets in `cbs` or
134
   * an error string if `cbs` does not point to a well-formed `OCTETSTRING`
135
   */
136
  static absl::StatusOr<std::vector<uint8_t>> parseOctetString(CBS& cbs);
137

            
138
  /**
139
   * Advance `cbs` over an `ASN.1` value of the class `tag` if that
140
   * value is present. Otherwise, `cbs` stays in the same position.
141
   *
142
   * @param cbs a CBS& that refers to the current document position
143
   * @param tag the tag of the value to skip
144
   * @returns `absl::StatusOr<absl::monostate>` a unit type denoting success
145
   * or an error string if parsing fails.
146
   */
147
  static absl::StatusOr<absl::monostate> skipOptional(CBS& cbs, unsigned tag);
148

            
149
  /**
150
   * Advance `cbs` over an `ASN.1` value of the class `tag`.
151
   *
152
   * @param cbs a CBS& that refers to the current document position
153
   * @param tag the tag of the value to skip
154
   * @returns `absl::StatusOr<absl::monostate>` a unit type denoting success
155
   * or an error string if parsing fails.
156
   */
157
  static absl::StatusOr<absl::monostate> skip(CBS& cbs, unsigned tag);
158
};
159

            
160
template <typename T>
161
absl::StatusOr<std::vector<T>> Asn1Utility::parseSequenceOf(CBS& cbs,
162
1197
                                                            Asn1ParsingFunc<T> parse_element) {
163
1197
  CBS seq_elem;
164
1197
  std::vector<T> vec;
165

            
166
  // Initialize seq_elem to first element in sequence.
167
1197
  if (!CBS_get_asn1(&cbs, &seq_elem, CBS_ASN1_SEQUENCE)) {
168
1
    return absl::InvalidArgumentError("Expected sequence of ASN.1 elements.");
169
1
  }
170

            
171
2393
  while (CBS_data(&seq_elem) < CBS_data(&cbs)) {
172
    // parse_element MUST advance seq_elem
173
1199
    auto elem_res = parse_element(seq_elem);
174
1199
    if (!elem_res.status().ok()) {
175
2
      return elem_res.status();
176
2
    }
177
1197
    vec.push_back(elem_res.value());
178
1197
  }
179

            
180
1194
  RELEASE_ASSERT(CBS_data(&cbs) == CBS_data(&seq_elem),
181
1194
                 "Sequence tag length must match actual length or element parsing would fail");
182

            
183
1194
  return vec;
184
1194
}
185

            
186
template <typename T>
187
absl::StatusOr<absl::optional<T>>
188
1198
Asn1Utility::parseOptional(CBS& cbs, Asn1ParsingFunc<T> parse_data, unsigned tag) {
189
1198
  auto maybe_data_res = getOptional(cbs, tag);
190

            
191
1198
  if (!maybe_data_res.status().ok()) {
192
1
    return maybe_data_res.status();
193
1
  }
194

            
195
1197
  auto maybe_data = maybe_data_res.value();
196
1197
  if (maybe_data) {
197
1183
    return parse_data(maybe_data.value());
198
1183
  }
199

            
200
14
  return absl::nullopt;
201
1197
}
202

            
203
} // namespace Ocsp
204
} // namespace Tls
205
} // namespace TransportSockets
206
} // namespace Extensions
207
} // namespace Envoy