responder.go 14 KB

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