forked from cloudflare/cfssl
-
Notifications
You must be signed in to change notification settings - Fork 0
/
responder.go
141 lines (124 loc) · 4.38 KB
/
responder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package ocsp
import (
"encoding/base64"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"github.com/cloudflare/cfssl/log"
"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}
)
// 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.
type Source interface {
Response(*ocsp.Request) ([]byte, bool)
}
// 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) (response []byte, present bool) {
response, present = src[request.SerialNumber.String()]
return
}
// 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 {
der, tmpErr := base64.StdEncoding.DecodeString(b64)
if tmpErr != nil {
log.Errorf("Base64 decode error on: %s", b64)
continue
}
response, tmpErr := ocsp.ParseResponse(der, nil)
if tmpErr != nil {
log.Errorf("OCSP decode error on: %s", b64)
continue
}
src[response.SerialNumber.String()] = der
}
log.Infof("Read %d OCSP responses", len(src))
return src, nil
}
// A Responder object provides the HTTP logic to expose a
// Source of OCSP responses.
type Responder struct {
Source Source
}
// 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.
func (rs Responder) ServeHTTP(response http.ResponseWriter, request *http.Request) {
// Read response from request
var requestBody []byte
var err error
switch request.Method {
case "GET":
re := regexp.MustCompile("^.*/")
base64Request := re.ReplaceAllString(request.RequestURI, "")
base64Request, err = url.QueryUnescape(base64Request)
if err != nil {
return
}
requestBody, err = base64.StdEncoding.DecodeString(base64Request)
if err != nil {
return
}
case "POST":
requestBody, err = ioutil.ReadAll(request.Body)
if err != nil {
response.WriteHeader(http.StatusBadRequest)
return
}
default:
response.WriteHeader(http.StatusMethodNotAllowed)
return
}
// TODO log request
b64Body := base64.StdEncoding.EncodeToString(requestBody)
log.Infof("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.Errorf("Error decoding request body: %s", b64Body)
response.Write(malformedRequestErrorResponse)
return
}
// Look up OCSP response from source
ocspResponse, found := rs.Source.Response(ocspRequest)
if !found {
log.Errorf("No response found for request: %s", b64Body)
response.Write(unauthorizedErrorResponse)
return
}
// Write OCSP response to response
response.WriteHeader(http.StatusOK)
response.Write(ocspResponse)
}