X Tutup
Skip to content

Commit bd045b9

Browse files
Roland Bracewell Shoemakercpu
authored andcommitted
Fix OCSP-Responder double slash collapsing (letsencrypt#2748)
Uses a special mux for the OCSP Responder so that we stop collapsing double slashes in GET requests which cause a small number of requests to be considered malformed.
1 parent dfd0857 commit bd045b9

File tree

4 files changed

+41
-8
lines changed

4 files changed

+41
-8
lines changed

cmd/ocsp-responder/main.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,19 @@ as generated by Boulder's single-ocsp command.
234234
<-forever
235235
}
236236

237+
// ocspMux partially implements the interface defined for http.ServeMux but doesn't implement
238+
// the path cleaning its Handler method does. Notably http.ServeMux will collapse repeated
239+
// slashes into a single slash which breaks the base64 encoding that is used in OCSP GET
240+
// requests. CFSSL explicitly recommends against using http.ServeMux for this reason:
241+
// https://github.com/cloudflare/cfssl/blob/6388e1ec18d2933c35f0f8dfbbe383713eb04b1e/ocsp/responder.go#L170
242+
type ocspMux struct {
243+
handler http.Handler
244+
}
245+
246+
func (om *ocspMux) Handler(_ *http.Request) (http.Handler, string) {
247+
return om.handler, "/"
248+
}
249+
237250
func mux(scope metrics.Scope, responderPath string, source cfocsp.Source) http.Handler {
238251
stripPrefix := http.StripPrefix(responderPath, cfocsp.NewResponder(source))
239252
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -244,7 +257,5 @@ func mux(scope metrics.Scope, responderPath string, source cfocsp.Source) http.H
244257
}
245258
stripPrefix.ServeHTTP(w, r)
246259
})
247-
m := http.NewServeMux()
248-
m.Handle("/", h)
249-
return measured_http.New(m, clock.Default())
260+
return measured_http.New(&ocspMux{h}, clock.Default())
250261
}

cmd/ocsp-responder/main_test.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"bytes"
5+
"encoding/base64"
56
"fmt"
67
"io/ioutil"
78
"net/http"
@@ -29,16 +30,29 @@ func TestMux(t *testing.T) {
2930
if err != nil {
3031
t.Fatalf("ocsp.ParseRequest: %s", err)
3132
}
33+
doubleSlashBytes, err := base64.StdEncoding.DecodeString("MFMwUTBPME0wSzAJBgUrDgMCGgUABBR+5mrncpqz/PiiIGRsFqEtYHEIXQQUqEpqYwR93brm0Tm3pkVl7/Oo7KECEgO/AC2R1FW8hePAj4xp//8Jhw==")
34+
if err != nil {
35+
t.Fatalf("failed to decode double slash OCSP request")
36+
}
37+
doubleSlashReq, err := ocsp.ParseRequest(doubleSlashBytes)
38+
if err != nil {
39+
t.Fatalf("failed to parse double slash OCSP request")
40+
}
3241
src := make(cfocsp.InMemorySource)
3342
src[ocspReq.SerialNumber.String()] = resp.OCSPResponse
43+
src[doubleSlashReq.SerialNumber.String()] = resp.OCSPResponse
3444
h := mux(stats, "/foobar/", src)
3545
type muxTest struct {
3646
method string
3747
path string
3848
reqBody []byte
3949
respBody []byte
4050
}
41-
mts := []muxTest{{"POST", "/foobar/", req, resp.OCSPResponse}, {"GET", "/", nil, nil}}
51+
mts := []muxTest{
52+
{"POST", "/foobar/", req, resp.OCSPResponse},
53+
{"GET", "/", nil, nil},
54+
{"GET", "/foobar/MFMwUTBPME0wSzAJBgUrDgMCGgUABBR+5mrncpqz/PiiIGRsFqEtYHEIXQQUqEpqYwR93brm0Tm3pkVl7/Oo7KECEgO/AC2R1FW8hePAj4xp//8Jhw==", nil, resp.OCSPResponse},
55+
}
4256
for i, mt := range mts {
4357
w := httptest.NewRecorder()
4458
r, err := http.NewRequest(mt.method, mt.path, bytes.NewReader(mt.reqBody))

metrics/measured_http/http.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,25 @@ func (r *responseWriterWithStatus) WriteHeader(code int) {
3434
r.ResponseWriter.WriteHeader(code)
3535
}
3636

37+
// serveMux is a partial interface wrapper for the method http.ServeMux
38+
// exposes that we use. This is needed so that we can replace the default
39+
// http.ServeMux in ocsp-responder where we don't want to use its path
40+
// canonicalization.
41+
type serveMux interface {
42+
Handler(*http.Request) (http.Handler, string)
43+
}
44+
3745
// MeasuredHandler wraps an http.Handler and records prometheus stats
3846
type MeasuredHandler struct {
39-
*http.ServeMux
47+
serveMux
4048
clk clock.Clock
4149
// Normally this is always responseTime, but we override it for testing.
4250
stat *prometheus.HistogramVec
4351
}
4452

45-
func New(m *http.ServeMux, clk clock.Clock) *MeasuredHandler {
53+
func New(m serveMux, clk clock.Clock) *MeasuredHandler {
4654
return &MeasuredHandler{
47-
ServeMux: m,
55+
serveMux: m,
4856
clk: clk,
4957
stat: responseTime,
5058
}

metrics/measured_http/http_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func TestMeasuring(t *testing.T) {
3636
mux := http.NewServeMux()
3737
mux.Handle("/foo", sleepyHandler{clk})
3838
mh := MeasuredHandler{
39-
ServeMux: mux,
39+
serveMux: mux,
4040
clk: clk,
4141
stat: stat,
4242
}

0 commit comments

Comments
 (0)
X Tutup