Vendor cfssl and cfssljson
This commit is contained in:
39
vendor/github.com/cloudflare/cfssl/ocsp/BUILD
generated
vendored
Normal file
39
vendor/github.com/cloudflare/cfssl/ocsp/BUILD
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"ocsp.go",
|
||||
"responder.go",
|
||||
],
|
||||
importmap = "k8s.io/kubernetes/vendor/github.com/cloudflare/cfssl/ocsp",
|
||||
importpath = "github.com/cloudflare/cfssl/ocsp",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/cloudflare/cfssl/certdb:go_default_library",
|
||||
"//vendor/github.com/cloudflare/cfssl/certdb/dbconf:go_default_library",
|
||||
"//vendor/github.com/cloudflare/cfssl/certdb/sql:go_default_library",
|
||||
"//vendor/github.com/cloudflare/cfssl/errors:go_default_library",
|
||||
"//vendor/github.com/cloudflare/cfssl/helpers:go_default_library",
|
||||
"//vendor/github.com/cloudflare/cfssl/log:go_default_library",
|
||||
"//vendor/github.com/jmhodges/clock:go_default_library",
|
||||
"//vendor/golang.org/x/crypto/ocsp:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//vendor/github.com/cloudflare/cfssl/ocsp/config:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
213
vendor/github.com/cloudflare/cfssl/ocsp/ocsp.go
generated
vendored
Normal file
213
vendor/github.com/cloudflare/cfssl/ocsp/ocsp.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
|
||||
Package ocsp exposes OCSP signing functionality, much like the signer
|
||||
package does for certificate signing. It also provies a basic OCSP
|
||||
responder stack for serving pre-signed OCSP responses.
|
||||
|
||||
*/
|
||||
package ocsp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cferr "github.com/cloudflare/cfssl/errors"
|
||||
"github.com/cloudflare/cfssl/helpers"
|
||||
"github.com/cloudflare/cfssl/log"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
// revocationReasonCodes is a map between string reason codes
|
||||
// to integers as defined in RFC 5280
|
||||
var revocationReasonCodes = map[string]int{
|
||||
"unspecified": ocsp.Unspecified,
|
||||
"keycompromise": ocsp.KeyCompromise,
|
||||
"cacompromise": ocsp.CACompromise,
|
||||
"affiliationchanged": ocsp.AffiliationChanged,
|
||||
"superseded": ocsp.Superseded,
|
||||
"cessationofoperation": ocsp.CessationOfOperation,
|
||||
"certificatehold": ocsp.CertificateHold,
|
||||
"removefromcrl": ocsp.RemoveFromCRL,
|
||||
"privilegewithdrawn": ocsp.PrivilegeWithdrawn,
|
||||
"aacompromise": ocsp.AACompromise,
|
||||
}
|
||||
|
||||
// StatusCode is a map between string statuses sent by cli/api
|
||||
// to ocsp int statuses
|
||||
var StatusCode = map[string]int{
|
||||
"good": ocsp.Good,
|
||||
"revoked": ocsp.Revoked,
|
||||
"unknown": ocsp.Unknown,
|
||||
}
|
||||
|
||||
// SignRequest represents the desired contents of a
|
||||
// specific OCSP response.
|
||||
type SignRequest struct {
|
||||
Certificate *x509.Certificate
|
||||
Status string
|
||||
Reason int
|
||||
RevokedAt time.Time
|
||||
Extensions []pkix.Extension
|
||||
// IssuerHash is the hashing function used to hash the issuer subject and public key
|
||||
// in the OCSP response. Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384,
|
||||
// and crypto.SHA512. If zero, the default is crypto.SHA1.
|
||||
IssuerHash crypto.Hash
|
||||
// If provided ThisUpdate will override the default usage of time.Now().Truncate(time.Hour)
|
||||
ThisUpdate *time.Time
|
||||
// If provided NextUpdate will override the default usage of ThisUpdate.Add(signerInterval)
|
||||
NextUpdate *time.Time
|
||||
}
|
||||
|
||||
// Signer represents a general signer of OCSP responses. It is
|
||||
// responsible for populating all fields in the OCSP response that
|
||||
// are not reflected in the SignRequest.
|
||||
type Signer interface {
|
||||
Sign(req SignRequest) ([]byte, error)
|
||||
}
|
||||
|
||||
// StandardSigner is the default concrete type of OCSP signer.
|
||||
// It represents a single responder (represented by a key and certificate)
|
||||
// speaking for a single issuer (certificate). It is assumed that OCSP
|
||||
// responses are issued at a regular interval, which is used to compute
|
||||
// the nextUpdate value based on the current time.
|
||||
type StandardSigner struct {
|
||||
issuer *x509.Certificate
|
||||
responder *x509.Certificate
|
||||
key crypto.Signer
|
||||
interval time.Duration
|
||||
}
|
||||
|
||||
// ReasonStringToCode tries to convert a reason string to an integer code
|
||||
func ReasonStringToCode(reason string) (reasonCode int, err error) {
|
||||
// default to 0
|
||||
if reason == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
reasonCode, present := revocationReasonCodes[strings.ToLower(reason)]
|
||||
if !present {
|
||||
reasonCode, err = strconv.Atoi(reason)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if reasonCode >= ocsp.AACompromise || reasonCode <= ocsp.Unspecified {
|
||||
return 0, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewSignerFromFile reads the issuer cert, the responder cert and the responder key
|
||||
// from PEM files, and takes an interval in seconds
|
||||
func NewSignerFromFile(issuerFile, responderFile, keyFile string, interval time.Duration) (Signer, error) {
|
||||
log.Debug("Loading issuer cert: ", issuerFile)
|
||||
issuerBytes, err := helpers.ReadBytes(issuerFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("Loading responder cert: ", responderFile)
|
||||
responderBytes, err := ioutil.ReadFile(responderFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("Loading responder key: ", keyFile)
|
||||
keyBytes, err := ioutil.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return nil, cferr.Wrap(cferr.CertificateError, cferr.ReadFailed, err)
|
||||
}
|
||||
|
||||
issuerCert, err := helpers.ParseCertificatePEM(issuerBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responderCert, err := helpers.ParseCertificatePEM(responderBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := helpers.ParsePrivateKeyPEM(keyBytes)
|
||||
if err != nil {
|
||||
log.Debug("Malformed private key %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSigner(issuerCert, responderCert, key, interval)
|
||||
}
|
||||
|
||||
// NewSigner simply constructs a new StandardSigner object from the inputs,
|
||||
// taking the interval in seconds
|
||||
func NewSigner(issuer, responder *x509.Certificate, key crypto.Signer, interval time.Duration) (Signer, error) {
|
||||
return &StandardSigner{
|
||||
issuer: issuer,
|
||||
responder: responder,
|
||||
key: key,
|
||||
interval: interval,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Sign is used with an OCSP signer to request the issuance of
|
||||
// an OCSP response.
|
||||
func (s StandardSigner) Sign(req SignRequest) ([]byte, error) {
|
||||
if req.Certificate == nil {
|
||||
return nil, cferr.New(cferr.OCSPError, cferr.ReadFailed)
|
||||
}
|
||||
|
||||
// Verify that req.Certificate is issued under s.issuer
|
||||
if bytes.Compare(req.Certificate.RawIssuer, s.issuer.RawSubject) != 0 {
|
||||
return nil, cferr.New(cferr.OCSPError, cferr.IssuerMismatch)
|
||||
}
|
||||
if req.Certificate.CheckSignatureFrom(s.issuer) != nil {
|
||||
return nil, cferr.New(cferr.OCSPError, cferr.IssuerMismatch)
|
||||
}
|
||||
|
||||
var thisUpdate, nextUpdate time.Time
|
||||
if req.ThisUpdate != nil {
|
||||
thisUpdate = *req.ThisUpdate
|
||||
} else {
|
||||
// Round thisUpdate times down to the nearest hour
|
||||
thisUpdate = time.Now().Truncate(time.Hour)
|
||||
}
|
||||
if req.NextUpdate != nil {
|
||||
nextUpdate = *req.NextUpdate
|
||||
} else {
|
||||
nextUpdate = thisUpdate.Add(s.interval)
|
||||
}
|
||||
|
||||
status, ok := StatusCode[req.Status]
|
||||
if !ok {
|
||||
return nil, cferr.New(cferr.OCSPError, cferr.InvalidStatus)
|
||||
}
|
||||
|
||||
// If the OCSP responder is the same as the issuer, there is no need to
|
||||
// include any certificate in the OCSP response, which decreases the byte size
|
||||
// of OCSP responses dramatically.
|
||||
certificate := s.responder
|
||||
if s.issuer == s.responder || bytes.Equal(s.issuer.Raw, s.responder.Raw) {
|
||||
certificate = nil
|
||||
}
|
||||
|
||||
template := ocsp.Response{
|
||||
Status: status,
|
||||
SerialNumber: req.Certificate.SerialNumber,
|
||||
ThisUpdate: thisUpdate,
|
||||
NextUpdate: nextUpdate,
|
||||
Certificate: certificate,
|
||||
ExtraExtensions: req.Extensions,
|
||||
IssuerHash: req.IssuerHash,
|
||||
}
|
||||
|
||||
if status == ocsp.Revoked {
|
||||
template.RevokedAt = req.RevokedAt
|
||||
template.RevocationReason = req.Reason
|
||||
}
|
||||
|
||||
return ocsp.CreateResponse(s.issuer, s.responder, template, s.key)
|
||||
}
|
349
vendor/github.com/cloudflare/cfssl/ocsp/responder.go
generated
vendored
Normal file
349
vendor/github.com/cloudflare/cfssl/ocsp/responder.go
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
// Package ocsp implements an OCSP responder based on a generic storage backend.
|
||||
// It provides a couple of sample implementations.
|
||||
// Because OCSP responders handle high query volumes, we have to be careful
|
||||
// about how much logging we do. Error-level logs are reserved for problems
|
||||
// internal to the server, that can be fixed by an administrator. Any type of
|
||||
// incorrect input from a user should be logged and Info or below. For things
|
||||
// that are logged on every request, Debug is the appropriate level.
|
||||
package ocsp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cfssl/certdb"
|
||||
"github.com/cloudflare/cfssl/certdb/dbconf"
|
||||
"github.com/cloudflare/cfssl/certdb/sql"
|
||||
"github.com/cloudflare/cfssl/log"
|
||||
"github.com/jmhodges/clock"
|
||||
"golang.org/x/crypto/ocsp"
|
||||
)
|
||||
|
||||
var (
|
||||
malformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01}
|
||||
internalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02}
|
||||
tryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03}
|
||||
sigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05}
|
||||
unauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06}
|
||||
|
||||
// ErrNotFound indicates the request OCSP response was not found. It is used to
|
||||
// indicate that the responder should reply with unauthorizedErrorResponse.
|
||||
ErrNotFound = errors.New("Request OCSP Response not found")
|
||||
)
|
||||
|
||||
// Source represents the logical source of OCSP responses, i.e.,
|
||||
// the logic that actually chooses a response based on a request. In
|
||||
// order to create an actual responder, wrap one of these in a Responder
|
||||
// object and pass it to http.Handle. By default the Responder will set
|
||||
// the headers Cache-Control to "max-age=(response.NextUpdate-now), public, no-transform, must-revalidate",
|
||||
// Last-Modified to response.ThisUpdate, Expires to response.NextUpdate,
|
||||
// ETag to the SHA256 hash of the response, and Content-Type to
|
||||
// application/ocsp-response. If you want to override these headers,
|
||||
// or set extra headers, your source should return a http.Header
|
||||
// with the headers you wish to set. If you don't want to set any
|
||||
// extra headers you may return nil instead.
|
||||
type Source interface {
|
||||
Response(*ocsp.Request) ([]byte, http.Header, error)
|
||||
}
|
||||
|
||||
// An InMemorySource is a map from serialNumber -> der(response)
|
||||
type InMemorySource map[string][]byte
|
||||
|
||||
// Response looks up an OCSP response to provide for a given request.
|
||||
// InMemorySource looks up a response purely based on serial number,
|
||||
// without regard to what issuer the request is asking for.
|
||||
func (src InMemorySource) Response(request *ocsp.Request) ([]byte, http.Header, error) {
|
||||
response, present := src[request.SerialNumber.String()]
|
||||
if !present {
|
||||
return nil, nil, ErrNotFound
|
||||
}
|
||||
return response, nil, nil
|
||||
}
|
||||
|
||||
// DBSource represnts a source of OCSP responses backed by the certdb package.
|
||||
type DBSource struct {
|
||||
Accessor certdb.Accessor
|
||||
}
|
||||
|
||||
// NewDBSource creates a new DBSource type with an associated dbAccessor.
|
||||
func NewDBSource(dbAccessor certdb.Accessor) Source {
|
||||
return DBSource{
|
||||
Accessor: dbAccessor,
|
||||
}
|
||||
}
|
||||
|
||||
// Response implements cfssl.ocsp.responder.Source, which returns the
|
||||
// OCSP response in the Database for the given request with the expiration
|
||||
// date furthest in the future.
|
||||
func (src DBSource) Response(req *ocsp.Request) ([]byte, http.Header, error) {
|
||||
if req == nil {
|
||||
return nil, nil, errors.New("called with nil request")
|
||||
}
|
||||
|
||||
aki := hex.EncodeToString(req.IssuerKeyHash)
|
||||
sn := req.SerialNumber
|
||||
|
||||
if sn == nil {
|
||||
return nil, nil, errors.New("request contains no serial")
|
||||
}
|
||||
strSN := sn.String()
|
||||
|
||||
if src.Accessor == nil {
|
||||
log.Errorf("No DB Accessor")
|
||||
return nil, nil, errors.New("called with nil DB accessor")
|
||||
}
|
||||
records, err := src.Accessor.GetOCSP(strSN, aki)
|
||||
|
||||
// Response() logs when there are errors obtaining the OCSP response
|
||||
// and returns nil, false.
|
||||
if err != nil {
|
||||
log.Errorf("Error obtaining OCSP response: %s", err)
|
||||
return nil, nil, fmt.Errorf("failed to obtain OCSP response: %s", err)
|
||||
}
|
||||
|
||||
if len(records) == 0 {
|
||||
return nil, nil, ErrNotFound
|
||||
}
|
||||
|
||||
// Response() finds the OCSPRecord with the expiration date furthest in the future.
|
||||
cur := records[0]
|
||||
for _, rec := range records {
|
||||
if rec.Expiry.After(cur.Expiry) {
|
||||
cur = rec
|
||||
}
|
||||
}
|
||||
return []byte(cur.Body), nil, nil
|
||||
}
|
||||
|
||||
// NewSourceFromFile reads the named file into an InMemorySource.
|
||||
// The file read by this function must contain whitespace-separated OCSP
|
||||
// responses. Each OCSP response must be in base64-encoded DER form (i.e.,
|
||||
// PEM without headers or whitespace). Invalid responses are ignored.
|
||||
// This function pulls the entire file into an InMemorySource.
|
||||
func NewSourceFromFile(responseFile string) (Source, error) {
|
||||
fileContents, err := ioutil.ReadFile(responseFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responsesB64 := regexp.MustCompile("\\s").Split(string(fileContents), -1)
|
||||
src := InMemorySource{}
|
||||
for _, b64 := range responsesB64 {
|
||||
// if the line/space is empty just skip
|
||||
if b64 == "" {
|
||||
continue
|
||||
}
|
||||
der, tmpErr := base64.StdEncoding.DecodeString(b64)
|
||||
if tmpErr != nil {
|
||||
log.Errorf("Base64 decode error %s on: %s", tmpErr, b64)
|
||||
continue
|
||||
}
|
||||
|
||||
response, tmpErr := ocsp.ParseResponse(der, nil)
|
||||
if tmpErr != nil {
|
||||
log.Errorf("OCSP decode error %s on: %s", tmpErr, b64)
|
||||
continue
|
||||
}
|
||||
|
||||
src[response.SerialNumber.String()] = der
|
||||
}
|
||||
|
||||
log.Infof("Read %d OCSP responses", len(src))
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// NewSourceFromDB reads the given database configuration file
|
||||
// and creates a database data source for use with the OCSP responder
|
||||
func NewSourceFromDB(DBConfigFile string) (Source, error) {
|
||||
// Load DB from cofiguration file
|
||||
db, err := dbconf.DBFromConfig(DBConfigFile)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create accesor
|
||||
accessor := sql.NewAccessor(db)
|
||||
src := NewDBSource(accessor)
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// A Responder object provides the HTTP logic to expose a
|
||||
// Source of OCSP responses.
|
||||
type Responder struct {
|
||||
Source Source
|
||||
clk clock.Clock
|
||||
}
|
||||
|
||||
// NewResponder instantiates a Responder with the give Source.
|
||||
func NewResponder(source Source) *Responder {
|
||||
return &Responder{
|
||||
Source: source,
|
||||
clk: clock.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func overrideHeaders(response http.ResponseWriter, headers http.Header) {
|
||||
for k, v := range headers {
|
||||
if len(v) == 1 {
|
||||
response.Header().Set(k, v[0])
|
||||
} else if len(v) > 1 {
|
||||
response.Header().Del(k)
|
||||
for _, e := range v {
|
||||
response.Header().Add(k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A Responder can process both GET and POST requests. The mapping
|
||||
// from an OCSP request to an OCSP response is done by the Source;
|
||||
// the Responder simply decodes the request, and passes back whatever
|
||||
// response is provided by the source.
|
||||
// Note: The caller must use http.StripPrefix to strip any path components
|
||||
// (including '/') on GET requests.
|
||||
// Do not use this responder in conjunction with http.NewServeMux, because the
|
||||
// default handler will try to canonicalize path components by changing any
|
||||
// strings of repeated '/' into a single '/', which will break the base64
|
||||
// encoding.
|
||||
func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) {
|
||||
// By default we set a 'max-age=0, no-cache' Cache-Control header, this
|
||||
// is only returned to the client if a valid authorized OCSP response
|
||||
// is not found or an error is returned. If a response if found the header
|
||||
// will be altered to contain the proper max-age and modifiers.
|
||||
response.Header().Add("Cache-Control", "max-age=0, no-cache")
|
||||
// Read response from request
|
||||
var requestBody []byte
|
||||
var err error
|
||||
switch request.Method {
|
||||
case "GET":
|
||||
base64Request, err := url.QueryUnescape(request.URL.Path)
|
||||
if err != nil {
|
||||
log.Debugf("Error decoding URL: %s", request.URL.Path)
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// url.QueryUnescape not only unescapes %2B escaping, but it additionally
|
||||
// turns the resulting '+' into a space, which makes base64 decoding fail.
|
||||
// So we go back afterwards and turn ' ' back into '+'. This means we
|
||||
// accept some malformed input that includes ' ' or %20, but that's fine.
|
||||
base64RequestBytes := []byte(base64Request)
|
||||
for i := range base64RequestBytes {
|
||||
if base64RequestBytes[i] == ' ' {
|
||||
base64RequestBytes[i] = '+'
|
||||
}
|
||||
}
|
||||
// In certain situations a UA may construct a request that has a double
|
||||
// slash between the host name and the base64 request body due to naively
|
||||
// constructing the request URL. In that case strip the leading slash
|
||||
// so that we can still decode the request.
|
||||
if len(base64RequestBytes) > 0 && base64RequestBytes[0] == '/' {
|
||||
base64RequestBytes = base64RequestBytes[1:]
|
||||
}
|
||||
requestBody, err = base64.StdEncoding.DecodeString(string(base64RequestBytes))
|
||||
if err != nil {
|
||||
log.Debugf("Error decoding base64 from URL: %s", string(base64RequestBytes))
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
case "POST":
|
||||
requestBody, err = ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
log.Errorf("Problem reading body of POST: %s", err)
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
default:
|
||||
response.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
b64Body := base64.StdEncoding.EncodeToString(requestBody)
|
||||
log.Debugf("Received OCSP request: %s", b64Body)
|
||||
|
||||
// All responses after this point will be OCSP.
|
||||
// We could check for the content type of the request, but that
|
||||
// seems unnecessariliy restrictive.
|
||||
response.Header().Add("Content-Type", "application/ocsp-response")
|
||||
|
||||
// Parse response as an OCSP request
|
||||
// XXX: This fails if the request contains the nonce extension.
|
||||
// We don't intend to support nonces anyway, but maybe we
|
||||
// should return unauthorizedRequest instead of malformed.
|
||||
ocspRequest, err := ocsp.ParseRequest(requestBody)
|
||||
if err != nil {
|
||||
log.Debugf("Error decoding request body: %s", b64Body)
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
response.Write(malformedRequestErrorResponse)
|
||||
return
|
||||
}
|
||||
|
||||
// Look up OCSP response from source
|
||||
ocspResponse, headers, err := rs.Source.Response(ocspRequest)
|
||||
if err != nil {
|
||||
if err == ErrNotFound {
|
||||
log.Infof("No response found for request: serial %x, request body %s",
|
||||
ocspRequest.SerialNumber, b64Body)
|
||||
response.Write(unauthorizedErrorResponse)
|
||||
return
|
||||
}
|
||||
log.Infof("Error retrieving response for request: serial %x, request body %s, error: %s",
|
||||
ocspRequest.SerialNumber, b64Body, err)
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
response.Write(internalErrorErrorResponse)
|
||||
return
|
||||
}
|
||||
|
||||
parsedResponse, err := ocsp.ParseResponse(ocspResponse, nil)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing response for serial %x: %s",
|
||||
ocspRequest.SerialNumber, err)
|
||||
response.Write(unauthorizedErrorResponse)
|
||||
return
|
||||
}
|
||||
|
||||
// Write OCSP response to response
|
||||
response.Header().Add("Last-Modified", parsedResponse.ThisUpdate.Format(time.RFC1123))
|
||||
response.Header().Add("Expires", parsedResponse.NextUpdate.Format(time.RFC1123))
|
||||
now := rs.clk.Now()
|
||||
maxAge := 0
|
||||
if now.Before(parsedResponse.NextUpdate) {
|
||||
maxAge = int(parsedResponse.NextUpdate.Sub(now) / time.Second)
|
||||
} else {
|
||||
// TODO(#530): we want max-age=0 but this is technically an authorized OCSP response
|
||||
// (despite being stale) and 5019 forbids attaching no-cache
|
||||
maxAge = 0
|
||||
}
|
||||
response.Header().Set(
|
||||
"Cache-Control",
|
||||
fmt.Sprintf(
|
||||
"max-age=%d, public, no-transform, must-revalidate",
|
||||
maxAge,
|
||||
),
|
||||
)
|
||||
responseHash := sha256.Sum256(ocspResponse)
|
||||
response.Header().Add("ETag", fmt.Sprintf("\"%X\"", responseHash))
|
||||
|
||||
if headers != nil {
|
||||
overrideHeaders(response, headers)
|
||||
}
|
||||
|
||||
// RFC 7232 says that a 304 response must contain the above
|
||||
// headers if they would also be sent for a 200 for the same
|
||||
// request, so we have to wait until here to do this
|
||||
if etag := request.Header.Get("If-None-Match"); etag != "" {
|
||||
if etag == fmt.Sprintf("\"%X\"", responseHash) {
|
||||
response.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
}
|
||||
response.WriteHeader(http.StatusOK)
|
||||
response.Write(ocspResponse)
|
||||
}
|
Reference in New Issue
Block a user