Line data Source code
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/types/optional.h" 13 : #include "absl/types/variant.h" 14 : #include "openssl/bn.h" 15 : #include "openssl/bytestring.h" 16 : #include "openssl/ssl.h" 17 : 18 : namespace Envoy { 19 : namespace Extensions { 20 : namespace TransportSockets { 21 : namespace Tls { 22 : namespace Ocsp { 23 : 24 : constexpr absl::string_view GENERALIZED_TIME_FORMAT = "%E4Y%m%d%H%M%S"; 25 : 26 : /** 27 : * The result of parsing an `ASN.1` structure or an `absl::string_view` error 28 : * description. 29 : */ 30 : template <typename T> using ParsingResult = absl::variant<T, absl::string_view>; 31 : 32 : /** 33 : * Construct a `T` from the data contained in the CBS&. Functions 34 : * of this type must advance the input CBS& over the element. 35 : */ 36 : template <typename T> using Asn1ParsingFunc = std::function<ParsingResult<T>(CBS&)>; 37 : 38 : /** 39 : * Utility functions for parsing DER-encoded `ASN.1` objects. 40 : * This relies heavily on the 'openssl/bytestring' API which 41 : * is BoringSSL's recommended interface for parsing DER-encoded 42 : * `ASN.1` data when there is not an existing wrapper. 43 : * This is not a complete library for `ASN.1` parsing and primarily 44 : * serves as abstractions for the OCSP module, but can be 45 : * extended and moved into a general utility to support parsing of 46 : * additional `ASN.1` objects. 47 : * 48 : * Each function adheres to the invariant that given a reference 49 : * to a crypto `bytestring` (CBS&), it will parse the specified 50 : * `ASN.1` element and advance `cbs` over it. 51 : * 52 : * An exception is thrown if the `bytestring` is malformed or does 53 : * not match the specified `ASN.1` object. The position 54 : * of `cbs` is not reliable after an exception is thrown. 55 : */ 56 : class Asn1Utility { 57 : public: 58 : ~Asn1Utility() = default; 59 : 60 : /** 61 : * Extracts the full contents of `cbs` as a string. 62 : * 63 : * @param `cbs` a CBS& that refers to the current document position 64 : * @returns absl::string_view containing the contents of `cbs` 65 : */ 66 : static absl::string_view cbsToString(CBS& cbs); 67 : 68 : /** 69 : * Parses all elements of an `ASN.1` SEQUENCE OF. `parse_element` must 70 : * advance its input CBS& over the entire element. 71 : * 72 : * @param cbs a CBS& that refers to an `ASN.1` SEQUENCE OF object 73 : * @param parse_element an `Asn1ParsingFunc<T>` used to parse each element 74 : * @returns ParsingResult<std::vector<T>> containing the parsed elements of the sequence 75 : * or an error string if `cbs` does not point to a well-formed 76 : * SEQUENCE OF object. 77 : */ 78 : template <typename T> 79 : static ParsingResult<std::vector<T>> parseSequenceOf(CBS& cbs, Asn1ParsingFunc<T> parse_element); 80 : 81 : /** 82 : * Checks if an explicitly tagged optional element of `tag` is present and 83 : * if so parses its value with `parse_data`. If the element is not present, 84 : * `cbs` is not advanced. 85 : * 86 : * @param cbs a CBS& that refers to the current document position 87 : * @param parse_data an `Asn1ParsingFunc<T>` used to parse the data if present 88 : * @return ParsingResult<absl::optional<T>> with a `T` if `cbs` is of the specified tag, 89 : * nullopt if the element has a different tag, or an error string if parsing fails. 90 : */ 91 : template <typename T> 92 : static ParsingResult<absl::optional<T>> parseOptional(CBS& cbs, Asn1ParsingFunc<T> parse_data, 93 : unsigned tag); 94 : 95 : /** 96 : * Returns whether or not an element explicitly tagged with `tag` is present 97 : * at `cbs`. If so, `cbs` is advanced over the optional and assigns 98 : * `data` to the inner element, if `data` is not nullptr. 99 : * If `cbs` does not contain `tag`, `cbs` remains at the same position. 100 : * 101 : * @param cbs a CBS& that refers to the current document position 102 : * @param an unsigned explicit tag indicating an optional value 103 : * 104 : * @returns ParsingResult<bool> whether `cbs` points to an element tagged with `tag` or 105 : * an error string if parsing fails. 106 : */ 107 : static ParsingResult<absl::optional<CBS>> getOptional(CBS& cbs, unsigned tag); 108 : 109 : /** 110 : * @param cbs a CBS& that refers to an `ASN.1` OBJECT IDENTIFIER element 111 : * @returns ParsingResult<std::string> the `OID` encoded in `cbs` or an error 112 : * string if `cbs` does not point to a well-formed OBJECT IDENTIFIER 113 : */ 114 : static ParsingResult<std::string> parseOid(CBS& cbs); 115 : 116 : /** 117 : * @param cbs a CBS& that refers to an `ASN.1` `GENERALIZEDTIME` element 118 : * @returns ParsingResult<Envoy::SystemTime> the UTC timestamp encoded in `cbs` 119 : * or an error string if `cbs` does not point to a well-formed 120 : * `GENERALIZEDTIME` 121 : */ 122 : static ParsingResult<Envoy::SystemTime> parseGeneralizedTime(CBS& cbs); 123 : 124 : /** 125 : * Parses an `ASN.1` INTEGER type into its hex string representation. 126 : * `ASN.1` INTEGER types are arbitrary precision. 127 : * If you're SURE the integer fits into a fixed-size int, 128 : * use `CBS_get_asn1_*` functions for the given integer type instead. 129 : * 130 : * @param cbs a CBS& that refers to an `ASN.1` INTEGER element 131 : * @returns ParsingResult<std::string> a hex representation of the integer 132 : * or an error string if `cbs` does not point to a well-formed INTEGER 133 : */ 134 : static ParsingResult<std::string> parseInteger(CBS& cbs); 135 : 136 : /** 137 : * @param cbs a CBS& that refers to an `ASN.1` `OCTETSTRING` element 138 : * @returns ParsingResult<std::vector<uint8_t>> the octets in `cbs` or 139 : * an error string if `cbs` does not point to a well-formed `OCTETSTRING` 140 : */ 141 : static ParsingResult<std::vector<uint8_t>> parseOctetString(CBS& cbs); 142 : 143 : /** 144 : * Advance `cbs` over an `ASN.1` value of the class `tag` if that 145 : * value is present. Otherwise, `cbs` stays in the same position. 146 : * 147 : * @param cbs a CBS& that refers to the current document position 148 : * @param tag the tag of the value to skip 149 : * @returns `ParsingResult<absl::monostate>` a unit type denoting success 150 : * or an error string if parsing fails. 151 : */ 152 : static ParsingResult<absl::monostate> skipOptional(CBS& cbs, unsigned tag); 153 : 154 : /** 155 : * Advance `cbs` over an `ASN.1` value of the class `tag`. 156 : * 157 : * @param cbs a CBS& that refers to the current document position 158 : * @param tag the tag of the value to skip 159 : * @returns `ParsingResult<absl::monostate>` a unit type denoting success 160 : * or an error string if parsing fails. 161 : */ 162 : static ParsingResult<absl::monostate> skip(CBS& cbs, unsigned tag); 163 : }; 164 : 165 : template <typename T> 166 : ParsingResult<std::vector<T>> Asn1Utility::parseSequenceOf(CBS& cbs, 167 0 : Asn1ParsingFunc<T> parse_element) { 168 0 : CBS seq_elem; 169 0 : std::vector<T> vec; 170 : 171 : // Initialize seq_elem to first element in sequence. 172 0 : if (!CBS_get_asn1(&cbs, &seq_elem, CBS_ASN1_SEQUENCE)) { 173 0 : return "Expected sequence of ASN.1 elements."; 174 0 : } 175 : 176 0 : while (CBS_data(&seq_elem) < CBS_data(&cbs)) { 177 : // parse_element MUST advance seq_elem 178 0 : auto elem_res = parse_element(seq_elem); 179 0 : if (absl::holds_alternative<T>(elem_res)) { 180 0 : vec.push_back(absl::get<0>(elem_res)); 181 0 : } else { 182 0 : return absl::get<1>(elem_res); 183 0 : } 184 0 : } 185 : 186 0 : RELEASE_ASSERT(CBS_data(&cbs) == CBS_data(&seq_elem), 187 0 : "Sequence tag length must match actual length or element parsing would fail"); 188 : 189 0 : return vec; 190 0 : } 191 : 192 : template <typename T> 193 : ParsingResult<absl::optional<T>> Asn1Utility::parseOptional(CBS& cbs, Asn1ParsingFunc<T> parse_data, 194 0 : unsigned tag) { 195 0 : auto maybe_data_res = getOptional(cbs, tag); 196 : 197 0 : if (absl::holds_alternative<absl::string_view>(maybe_data_res)) { 198 0 : return absl::get<absl::string_view>(maybe_data_res); 199 0 : } 200 : 201 0 : auto maybe_data = absl::get<absl::optional<CBS>>(maybe_data_res); 202 0 : if (maybe_data) { 203 0 : auto res = parse_data(maybe_data.value()); 204 0 : if (absl::holds_alternative<T>(res)) { 205 0 : return absl::get<0>(res); 206 0 : } 207 0 : return absl::get<1>(res); 208 0 : } 209 : 210 0 : return absl::nullopt; 211 0 : } 212 : 213 : } // namespace Ocsp 214 : } // namespace Tls 215 : } // namespace TransportSockets 216 : } // namespace Extensions 217 : } // namespace Envoy