#pragma once #include #include #include #include "envoy/common/exception.h" #include "envoy/common/time.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "openssl/bytestring.h" #include "openssl/ssl.h" /** * Data structures and functions for unmarshaling OCSP responses * according to the RFC6960 B.2 spec. See: https://tools.ietf.org/html/rfc6960#appendix-B * * WARNING: This module is meant to validate that OCSP responses are well-formed * and extract useful fields for OCSP stapling. This assumes that responses are * provided from configs or another trusted source and does not perform * checks necessary to verify responses coming from an upstream server. */ namespace Envoy { namespace Extensions { namespace TransportSockets { namespace Tls { namespace Ocsp { /** * Reflection of the `ASN.1` OcspResponseStatus enumeration. * The possible statuses that can accompany an OCSP response. */ enum class OcspResponseStatus { // OCSPResponseStatus ::= ENUMERATED { // successful (0), -- Response has valid confirmations // malformedRequest (1), -- Illegal confirmation request // internalError (2), -- Internal error in issuer // tryLater (3), -- Try again later // -- (4) is not used // sigRequired (5), -- Must sign the request // unauthorized (6) -- Request unauthorized // } Successful = 0, MalformedRequest = 1, InternalError = 2, TryLater = 3, SigRequired = 5, Unauthorized = 6 }; /** * Partial reflection of the `ASN.1` CertId structure. * Contains the information to identify an SSL Certificate. * Serial numbers are guaranteed to be * unique per issuer but not necessarily universally. */ struct CertId { CertId(std::string serial_number); std::string serial_number_; }; /** * Partial reflection of the `ASN.1` SingleResponse structure. * Contains information about the OCSP status of a single certificate. * An OCSP request may request the status of multiple certificates and * therefore responses may contain multiple SingleResponses. * * this_update_ and next_update_ reflect the validity period for this response. * If next_update_ is not present, the OCSP responder always has new information * available. In this case the response would be considered immediately expired * and invalid for stapling. */ struct SingleResponse { SingleResponse(CertId cert_id, Envoy::SystemTime this_update, absl::optional next_update); const CertId cert_id_; const Envoy::SystemTime this_update_; const absl::optional next_update_; }; /** * Partial reflection of the `ASN.1` ResponseData structure. * Contains an OCSP response for each certificate in a given request * as well as the time at which the response was produced. */ struct ResponseData { ResponseData(std::vector single_responses); const std::vector single_responses_; }; /** * An abstract type for OCSP response formats. Which variant of `Response` is * used in an `OcspResponse` is indicated by the structure's `OID`. * * Envoy enforces that OCSP responses must be for a single certificate * only. The methods on this class extract the relevant information for the * single certificate contained in the response. */ class Response { public: virtual ~Response() = default; /** * @return The number of certs reported on by this response. */ virtual size_t getNumCerts() PURE; /** * @return The serial number of the certificate. */ virtual const std::string& getCertSerialNumber() PURE; /** * @return The beginning of the validity window for this response. */ virtual const Envoy::SystemTime& getThisUpdate() PURE; /** * The time at which this response is considered to expire. If * `nullopt`, then there is assumed to always be more up-to-date * information available and the response is always considered expired. * * @return The end of the validity window for this response. */ virtual const absl::optional& getNextUpdate() PURE; }; using ResponsePtr = std::unique_ptr; /** * Reflection of the `ASN.1` BasicOcspResponse structure. * Contains the full data of an OCSP response. * Envoy enforces that OCSP responses contain a response for only * a single certificate. * * BasicOcspResponse is the only supported Response type in RFC 6960. */ class BasicOcspResponse : public Response { public: BasicOcspResponse(ResponseData data); // Response size_t getNumCerts() override { return data_.single_responses_.size(); } const std::string& getCertSerialNumber() override { return data_.single_responses_[0].cert_id_.serial_number_; } const Envoy::SystemTime& getThisUpdate() override { return data_.single_responses_[0].this_update_; } const absl::optional& getNextUpdate() override { return data_.single_responses_[0].next_update_; } // Identified as `id-pkix-ocsp-basic` in // https://tools.ietf.org/html/rfc6960#appendix-B.2 constexpr static absl::string_view OID = "1.3.6.1.5.5.7.48.1.1"; private: const ResponseData data_; }; /** * Reflection of the `ASN.1` OcspResponse structure. * This is the top-level data structure for OCSP responses. */ struct OcspResponse { OcspResponse(OcspResponseStatus status, ResponsePtr response); OcspResponseStatus status_; ResponsePtr response_; }; /** * A wrapper used to own and query an OCSP response in DER-encoded format. */ class OcspResponseWrapper { public: OcspResponseWrapper(std::vector der_response, TimeSource& time_source); /** * @return std::vector& a reference to the underlying bytestring representation * of the OCSP response */ const std::vector& rawBytes() const { return raw_bytes_; } /** * @return OcspResponseStatus whether the OCSP response was successfully created * or a status indicating an error in the OCSP process */ OcspResponseStatus getResponseStatus() const { return response_->status_; } /** * @param cert a X509& SSL certificate * @returns bool whether this OCSP response contains the revocation status of `cert` */ bool matchesCertificate(X509& cert) const; /** * Determines whether the OCSP response can no longer be considered valid. * This can be true if the nextUpdate field of the response has passed * or is not present, indicating that there is always more updated information * available. * * @returns bool if the OCSP response is expired. */ bool isExpired(); /** * @returns the seconds until this OCSP response expires. */ uint64_t secondsUntilExpiration() const; /** * @return The beginning of the validity window for this response. */ Envoy::SystemTime getThisUpdate() const; /** * The time at which this response is considered to expire. If * the underlying response does not have a value, then the current * time is returned. * * @return The end of the validity window for this response. */ Envoy::SystemTime getNextUpdate() const; private: const std::vector raw_bytes_; const std::unique_ptr response_; TimeSource& time_source_; }; using OcspResponseWrapperPtr = std::unique_ptr; /** * `ASN.1` DER-encoded parsing functions similar to `Asn1Utility` but specifically * for structures related to OCSP. * * Each function must advance `cbs` across the element it refers to. */ class Asn1OcspUtility { public: /** * @param `cbs` a CBS& that refers to an `ASN.1` OcspResponse element * @returns std::unique_ptr the OCSP response encoded in `cbs` * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed OcspResponse * element. */ static std::unique_ptr parseOcspResponse(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` OcspResponseStatus element * @returns OcspResponseStatus the OCSP response encoded in `cbs` * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed * OcspResponseStatus element. */ static OcspResponseStatus parseResponseStatus(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` Response element * @returns Response containing the content of an OCSP response * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed * structure that is a valid Response type. */ static ResponsePtr parseResponseBytes(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` BasicOcspResponse element * @returns BasicOcspResponse containing the content of an OCSP response * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed * BasicOcspResponse element. */ static std::unique_ptr parseBasicOcspResponse(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` ResponseData element * @returns ResponseData containing the content of an OCSP response relating * to certificate statuses. * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed * ResponseData element. */ static ResponseData parseResponseData(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` SingleResponse element * @returns SingleResponse containing the id and revocation status of * a single certificate. * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed * SingleResponse element. */ static SingleResponse parseSingleResponse(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` CertId element * @returns CertId containing the information necessary to uniquely identify * an SSL certificate. * @throws Envoy::EnvoyException if `cbs` does not contain a well-formed * CertId element. */ static CertId parseCertId(CBS& cbs); }; } // namespace Ocsp } // namespace Tls } // namespace TransportSockets } // namespace Extensions } // namespace Envoy