responder.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. // Package ocsp implements an OCSP responder based on a generic storage backend.
  2. // It provides a couple of sample implementations.
  3. // Because OCSP responders handle high query volumes, we have to be careful
  4. // about how much logging we do. Error-level logs are reserved for problems
  5. // internal to the server, that can be fixed by an administrator. Any type of
  6. // incorrect input from a user should be logged and Info or below. For things
  7. // that are logged on every request, Debug is the appropriate level.
  8. package ocsp
  9. import (
  10. "crypto"
  11. "crypto/sha256"
  12. "encoding/base64"
  13. "encoding/hex"
  14. "encoding/json"
  15. "errors"
  16. "fmt"
  17. "io"
  18. "net/http"
  19. "net/url"
  20. "os"
  21. "regexp"
  22. "time"
  23. "github.com/cloudflare/cfssl/certdb"
  24. "github.com/cloudflare/cfssl/certdb/dbconf"
  25. "github.com/cloudflare/cfssl/certdb/sql"
  26. "github.com/cloudflare/cfssl/log"
  27. "github.com/jmhodges/clock"
  28. "golang.org/x/crypto/ocsp"
  29. )
  30. var (
  31. malformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
  32. internalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
  33. tryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
  34. sigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
  35. unauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
  36. // ErrNotFound indicates the request OCSP response was not found. It is used to
  37. // indicate that the responder should reply with unauthorizedErrorResponse.
  38. ErrNotFound = errors.New("Request OCSP Response not found")
  39. )
  40. // Source represents the logical source of OCSP responses, i.e.,
  41. // the logic that actually chooses a response based on a request. In
  42. // order to create an actual responder, wrap one of these in a Responder
  43. // object and pass it to http.Handle. By default the Responder will set
  44. // the headers Cache-Control to "max-age=(response.NextUpdate-now), public, no-transform, must-revalidate",
  45. // Last-Modified to response.ThisUpdate, Expires to response.NextUpdate,
  46. // ETag to the SHA256 hash of the response, and Content-Type to
  47. // application/ocsp-response. If you want to override these headers,
  48. // or set extra headers, your source should return a http.Header
  49. // with the headers you wish to set. If you don't want to set any
  50. // extra headers you may return nil instead.
  51. type Source interface {
  52. Response(*ocsp.Request) ([]byte, http.Header, error)
  53. }
  54. // An InMemorySource is a map from serialNumber -> der(response)
  55. type InMemorySource map[string][]byte
  56. // Response looks up an OCSP response to provide for a given request.
  57. // InMemorySource looks up a response purely based on serial number,
  58. // without regard to what issuer the request is asking for.
  59. func (src InMemorySource) Response(request *ocsp.Request) ([]byte, http.Header, error) {
  60. response, present := src[request.SerialNumber.String()]
  61. if !present {
  62. return nil, nil, ErrNotFound
  63. }
  64. return response, nil, nil
  65. }
  66. // DBSource represnts a source of OCSP responses backed by the certdb package.
  67. type DBSource struct {
  68. Accessor certdb.Accessor
  69. }
  70. // NewDBSource creates a new DBSource type with an associated dbAccessor.
  71. func NewDBSource(dbAccessor certdb.Accessor) Source {
  72. return DBSource{
  73. Accessor: dbAccessor,
  74. }
  75. }
  76. // Response implements cfssl.ocsp.responder.Source, which returns the
  77. // OCSP response in the Database for the given request with the expiration
  78. // date furthest in the future.
  79. func (src DBSource) Response(req *ocsp.Request) ([]byte, http.Header, error) {
  80. if req == nil {
  81. return nil, nil, errors.New("called with nil request")
  82. }
  83. aki := hex.EncodeToString(req.IssuerKeyHash)
  84. sn := req.SerialNumber
  85. if sn == nil {
  86. return nil, nil, errors.New("request contains no serial")
  87. }
  88. strSN := sn.String()
  89. if src.Accessor == nil {
  90. log.Errorf("No DB Accessor")
  91. return nil, nil, errors.New("called with nil DB accessor")
  92. }
  93. records, err := src.Accessor.GetOCSP(strSN, aki)
  94. // Response() logs when there are errors obtaining the OCSP response
  95. // and returns nil, false.
  96. if err != nil {
  97. log.Errorf("Error obtaining OCSP response: %s", err)
  98. return nil, nil, fmt.Errorf("failed to obtain OCSP response: %s", err)
  99. }
  100. if len(records) == 0 {
  101. return nil, nil, ErrNotFound
  102. }
  103. // Response() finds the OCSPRecord with the expiration date furthest in the future.
  104. cur := records[0]
  105. for _, rec := range records {
  106. if rec.Expiry.After(cur.Expiry) {
  107. cur = rec
  108. }
  109. }
  110. return []byte(cur.Body), nil, nil
  111. }
  112. // NewSourceFromFile reads the named file into an InMemorySource.
  113. // The file read by this function must contain whitespace-separated OCSP
  114. // responses. Each OCSP response must be in base64-encoded DER form (i.e.,
  115. // PEM without headers or whitespace). Invalid responses are ignored.
  116. // This function pulls the entire file into an InMemorySource.
  117. func NewSourceFromFile(responseFile string) (Source, error) {
  118. fileContents, err := os.ReadFile(responseFile)
  119. if err != nil {
  120. return nil, err
  121. }
  122. responsesB64 := regexp.MustCompile("\\s").Split(string(fileContents), -1)
  123. src := InMemorySource{}
  124. for _, b64 := range responsesB64 {
  125. // if the line/space is empty just skip
  126. if b64 == "" {
  127. continue
  128. }
  129. der, tmpErr := base64.StdEncoding.DecodeString(b64)
  130. if tmpErr != nil {
  131. log.Errorf("Base64 decode error %s on: %s", tmpErr, b64)
  132. continue
  133. }
  134. response, tmpErr := ocsp.ParseResponse(der, nil)
  135. if tmpErr != nil {
  136. log.Errorf("OCSP decode error %s on: %s", tmpErr, b64)
  137. continue
  138. }
  139. src[response.SerialNumber.String()] = der
  140. }
  141. log.Infof("Read %d OCSP responses", len(src))
  142. return src, nil
  143. }
  144. // NewSourceFromDB reads the given database configuration file
  145. // and creates a database data source for use with the OCSP responder
  146. func NewSourceFromDB(DBConfigFile string) (Source, error) {
  147. // Load DB from cofiguration file
  148. db, err := dbconf.DBFromConfig(DBConfigFile)
  149. if err != nil {
  150. return nil, err
  151. }
  152. // Create accesor
  153. accessor := sql.NewAccessor(db)
  154. src := NewDBSource(accessor)
  155. return src, nil
  156. }
  157. // Stats is a basic interface that allows users to record information
  158. // about returned responses
  159. type Stats interface {
  160. ResponseStatus(ocsp.ResponseStatus)
  161. }
  162. // A Responder object provides the HTTP logic to expose a
  163. // Source of OCSP responses.
  164. type Responder struct {
  165. Source Source
  166. stats Stats
  167. clk clock.Clock
  168. }
  169. // NewResponder instantiates a Responder with the give Source.
  170. func NewResponder(source Source, stats Stats) *Responder {
  171. return &Responder{
  172. Source: source,
  173. stats: stats,
  174. clk: clock.New(),
  175. }
  176. }
  177. func overrideHeaders(response http.ResponseWriter, headers http.Header) {
  178. for k, v := range headers {
  179. if len(v) == 1 {
  180. response.Header().Set(k, v[0])
  181. } else if len(v) > 1 {
  182. response.Header().Del(k)
  183. for _, e := range v {
  184. response.Header().Add(k, e)
  185. }
  186. }
  187. }
  188. }
  189. type logEvent struct {
  190. IP string `json:"ip,omitempty"`
  191. UA string `json:"ua,omitempty"`
  192. Method string `json:"method,omitempty"`
  193. Path string `json:"path,omitempty"`
  194. Body string `json:"body,omitempty"`
  195. Received time.Time `json:"received,omitempty"`
  196. Took time.Duration `json:"took,omitempty"`
  197. Headers http.Header `json:"headers,omitempty"`
  198. Serial string `json:"serial,omitempty"`
  199. IssuerKeyHash string `json:"issuerKeyHash,omitempty"`
  200. IssuerNameHash string `json:"issuerNameHash,omitempty"`
  201. HashAlg string `json:"hashAlg,omitempty"`
  202. }
  203. // hashToString contains mappings for the only hash functions
  204. // x/crypto/ocsp supports
  205. var hashToString = map[crypto.Hash]string{
  206. crypto.SHA1: "SHA1",
  207. crypto.SHA256: "SHA256",
  208. crypto.SHA384: "SHA384",
  209. crypto.SHA512: "SHA512",
  210. }
  211. // A Responder can process both GET and POST requests. The mapping
  212. // from an OCSP request to an OCSP response is done by the Source;
  213. // the Responder simply decodes the request, and passes back whatever
  214. // response is provided by the source.
  215. // Note: The caller must use http.StripPrefix to strip any path components
  216. // (including '/') on GET requests.
  217. // Do not use this responder in conjunction with http.NewServeMux, because the
  218. // default handler will try to canonicalize path components by changing any
  219. // strings of repeated '/' into a single '/', which will break the base64
  220. // encoding.
  221. func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) {
  222. le := logEvent{
  223. IP: request.RemoteAddr,
  224. UA: request.UserAgent(),
  225. Method: request.Method,
  226. Path: request.URL.Path,
  227. Received: time.Now(),
  228. }
  229. defer func() {
  230. le.Headers = response.Header()
  231. le.Took = time.Since(le.Received)
  232. jb, err := json.Marshal(le)
  233. if err != nil {
  234. // we log this error at the debug level as if we aren't at that level anyway
  235. // we shouldn't really care about marshalling the log event object
  236. log.Debugf("failed to marshal log event object: %s", err)
  237. return
  238. }
  239. log.Debugf("Received request: %s", string(jb))
  240. }()
  241. // By default we set a 'max-age=0, no-cache' Cache-Control header, this
  242. // is only returned to the client if a valid authorized OCSP response
  243. // is not found or an error is returned. If a response if found the header
  244. // will be altered to contain the proper max-age and modifiers.
  245. response.Header().Add("Cache-Control", "max-age=0, no-cache")
  246. // Read response from request
  247. var requestBody []byte
  248. var err error
  249. switch request.Method {
  250. case "GET":
  251. base64Request, err := url.QueryUnescape(request.URL.Path)
  252. if err != nil {
  253. log.Debugf("Error decoding URL: %s", request.URL.Path)
  254. response.WriteHeader(http.StatusBadRequest)
  255. return
  256. }
  257. // url.QueryUnescape not only unescapes %2B escaping, but it additionally
  258. // turns the resulting '+' into a space, which makes base64 decoding fail.
  259. // So we go back afterwards and turn ' ' back into '+'. This means we
  260. // accept some malformed input that includes ' ' or %20, but that's fine.
  261. base64RequestBytes := []byte(base64Request)
  262. for i := range base64RequestBytes {
  263. if base64RequestBytes[i] == ' ' {
  264. base64RequestBytes[i] = '+'
  265. }
  266. }
  267. // In certain situations a UA may construct a request that has a double
  268. // slash between the host name and the base64 request body due to naively
  269. // constructing the request URL. In that case strip the leading slash
  270. // so that we can still decode the request.
  271. if len(base64RequestBytes) > 0 && base64RequestBytes[0] == '/' {
  272. base64RequestBytes = base64RequestBytes[1:]
  273. }
  274. requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes))
  275. if err != nil {
  276. log.Debugf("Error decoding base64 from URL: %s", string(base64RequestBytes))
  277. response.WriteHeader(http.StatusBadRequest)
  278. return
  279. }
  280. case "POST":
  281. requestBody, err = io.ReadAll(request.Body)
  282. if err != nil {
  283. log.Errorf("Problem reading body of POST: %s", err)
  284. response.WriteHeader(http.StatusBadRequest)
  285. return
  286. }
  287. default:
  288. response.WriteHeader(http.StatusMethodNotAllowed)
  289. return
  290. }
  291. b64Body := base64.StdEncoding.EncodeToString(requestBody)
  292. log.Debugf("Received OCSP request: %s", b64Body)
  293. if request.Method == http.MethodPost {
  294. le.Body = b64Body
  295. }
  296. // All responses after this point will be OCSP.
  297. // We could check for the content type of the request, but that
  298. // seems unnecessariliy restrictive.
  299. response.Header().Add("Content-Type", "application/ocsp-response")
  300. // Parse response as an OCSP request
  301. // XXX: This fails if the request contains the nonce extension.
  302. // We don't intend to support nonces anyway, but maybe we
  303. // should return unauthorizedRequest instead of malformed.
  304. ocspRequest, err := ocsp.ParseRequest(requestBody)
  305. if err != nil {
  306. log.Debugf("Error decoding request body: %s", b64Body)
  307. response.WriteHeader(http.StatusBadRequest)
  308. response.Write(malformedRequestErrorResponse)
  309. if rs.stats != nil {
  310. rs.stats.ResponseStatus(ocsp.Malformed)
  311. }
  312. return
  313. }
  314. le.Serial = fmt.Sprintf("%x", ocspRequest.SerialNumber.Bytes())
  315. le.IssuerKeyHash = fmt.Sprintf("%x", ocspRequest.IssuerKeyHash)
  316. le.IssuerNameHash = fmt.Sprintf("%x", ocspRequest.IssuerNameHash)
  317. le.HashAlg = hashToString[ocspRequest.HashAlgorithm]
  318. // Look up OCSP response from source
  319. ocspResponse, headers, err := rs.Source.Response(ocspRequest)
  320. if err != nil {
  321. if err == ErrNotFound {
  322. log.Infof("No response found for request: serial %x, request body %s",
  323. ocspRequest.SerialNumber, b64Body)
  324. response.Write(unauthorizedErrorResponse)
  325. if rs.stats != nil {
  326. rs.stats.ResponseStatus(ocsp.Unauthorized)
  327. }
  328. return
  329. }
  330. log.Infof("Error retrieving response for request: serial %x, request body %s, error: %s",
  331. ocspRequest.SerialNumber, b64Body, err)
  332. response.WriteHeader(http.StatusInternalServerError)
  333. response.Write(internalErrorErrorResponse)
  334. if rs.stats != nil {
  335. rs.stats.ResponseStatus(ocsp.InternalError)
  336. }
  337. return
  338. }
  339. parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil)
  340. if err != nil {
  341. log.Errorf("Error parsing response for serial %x: %s",
  342. ocspRequest.SerialNumber, err)
  343. response.Write(internalErrorErrorResponse)
  344. if rs.stats != nil {
  345. rs.stats.ResponseStatus(ocsp.InternalError)
  346. }
  347. return
  348. }
  349. // Write OCSP response to response
  350. response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123))
  351. response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123))
  352. now := rs.clk.Now()
  353. maxAge := 0
  354. if now.Before(parsedResponse.NextUpdate) {
  355. maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second)
  356. } else {
  357. // TODO(#530): we want max-age=0 but this is technically an authorized OCSP response
  358. // (despite being stale) and 5019 forbids attaching no-cache
  359. maxAge = 0
  360. }
  361. response.Header().Set(
  362. "Cache-Control",
  363. fmt.Sprintf(
  364. "max-age=%d, public, no-transform, must-revalidate",
  365. maxAge,
  366. ),
  367. )
  368. responseHash := sha256.Sum256(ocspResponse)
  369. response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash))
  370. if headers != nil {
  371. overrideHeaders(response, headers)
  372. }
  373. // RFC 7232 says that a 304 response must contain the above
  374. // headers if they would also be sent for a 200 for the same
  375. // request, so we have to wait until here to do this
  376. if etag := request.Header.Get("If-None-Match"); etag != "" {
  377. if etag == fmt.Sprintf("\"%X\"", responseHash) {
  378. response.WriteHeader(http.StatusNotModified)
  379. return
  380. }
  381. }
  382. response.WriteHeader(http.StatusOK)
  383. response.Write(ocspResponse)
  384. if rs.stats != nil {
  385. rs.stats.ResponseStatus(ocsp.Success)
  386. }
  387. }