Skip to content

Commit 9c34d5e

Browse files
authored
Switch to hand-rolled http mux. (#313)
This fixes the error where `/priv/doc/https://` URLs were being improperly unescaped before fetching/signing. Also, verify no special URL chars get signed. This complies with the recommendation at https://wicg.github.io/webpackage/draft-yasskin-http-origin-signed-responses.html#seccons-content-sniffing.
1 parent ad5419b commit 9c34d5e

File tree

16 files changed

+242
-1232
lines changed

16 files changed

+242
-1232
lines changed

Gopkg.lock

Lines changed: 7 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/amppkg/main.go

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,17 @@ import (
2424
"log"
2525
"net/http"
2626
"net/url"
27-
"path"
2827
"time"
2928

3029
"github.com/WICG/webpackage/go/signedexchange"
31-
"github.com/julienschmidt/httprouter"
3230
"github.com/pkg/errors"
3331

3432
"github.com/ampproject/amppackager/packager/certcache"
33+
"github.com/ampproject/amppackager/packager/mux"
34+
"github.com/ampproject/amppackager/packager/rtv"
3535
"github.com/ampproject/amppackager/packager/signer"
3636
"github.com/ampproject/amppackager/packager/util"
3737
"github.com/ampproject/amppackager/packager/validitymap"
38-
"github.com/ampproject/amppackager/packager/rtv"
3938
)
4039

4140
var flagConfig = flag.String("config", "amppkg.toml", "Path to the config toml file.")
@@ -126,32 +125,13 @@ func main() {
126125
}
127126
}
128127

129-
packager, err := signer.New(certs[0], key, config.URLSet, rtvCache, certCache.IsHealthy,
128+
signer, err := signer.New(certs[0], key, config.URLSet, rtvCache, certCache.IsHealthy,
130129
overrideBaseURL, /*requireHeaders=*/!*flagDevelopment, config.ForwardedRequestHeaders)
131130
if err != nil {
132-
die(errors.Wrap(err, "building packager"))
131+
die(errors.Wrap(err, "building signer"))
133132
}
134133

135134
// TODO(twifkak): Make log output configurable.
136-
mux := httprouter.New()
137-
mux.RedirectTrailingSlash = false
138-
mux.RedirectFixedPath = false
139-
140-
var handlerConfigs = []struct {
141-
path string
142-
handler httprouter.Handle
143-
} {
144-
{util.ValidityMapPath, validityMap.ServeHTTP},
145-
{"/priv/doc", packager.ServeHTTP},
146-
{"/priv/doc/*signURL", packager.ServeHTTP},
147-
{path.Join(util.CertURLPrefix, ":certName"), certCache.ServeHTTP},
148-
}
149-
// GET and HEAD requests are handled identically. http.Server empties
150-
// the body before responding to HEAD requests.
151-
for _, handlerConfig := range handlerConfigs {
152-
mux.GET(handlerConfig.path, handlerConfig.handler)
153-
mux.HEAD(handlerConfig.path, handlerConfig.handler)
154-
}
155135

156136
addr := ""
157137
if config.LocalOnly {
@@ -162,7 +142,7 @@ func main() {
162142
Addr: addr,
163143
// Don't use DefaultServeMux, per
164144
// https://blog.cloudflare.com/exposing-go-on-the-internet/.
165-
Handler: logIntercept{mux},
145+
Handler: logIntercept{mux.New(certCache, signer, validityMap)},
166146
ReadTimeout: 10 * time.Second,
167147
ReadHeaderTimeout: 5 * time.Second,
168148
// If needing to stream the response, disable WriteTimeout and

cmd/gateway_server/server.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
"github.com/ampproject/amppackager/packager/rtv"
2424
"github.com/ampproject/amppackager/packager/signer"
2525
"github.com/ampproject/amppackager/packager/util"
26-
"github.com/julienschmidt/httprouter"
2726
"golang.org/x/crypto/ocsp"
2827
"google.golang.org/grpc"
2928
)
@@ -133,7 +132,7 @@ func (s *gatewayServer) GenerateSXG(ctx context.Context, request *pb.SXGRequest)
133132

134133
httpreq, err := http.NewRequest("GET", baseUrl.String(), nil)
135134
httpresp := httptest.NewRecorder()
136-
packager.ServeHTTP(httpresp, httpreq, httprouter.Params{})
135+
packager.ServeHTTP(httpresp, httpreq)
137136

138137
// TODO(amaltas): Capture error when signer returns unsigned document.
139138
if httpresp.Code != 200 {

packager/certcache/certcache.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import (
2929
"time"
3030

3131
"github.com/WICG/webpackage/go/signedexchange/certurl"
32+
"github.com/ampproject/amppackager/packager/mux"
3233
"github.com/ampproject/amppackager/packager/util"
33-
"github.com/julienschmidt/httprouter"
3434
"github.com/pkg/errors"
3535
"github.com/pquerna/cachecontrol"
3636
"golang.org/x/crypto/ocsp"
@@ -144,8 +144,9 @@ func (this *CertCache) ocspMidpoint(bytes []byte, issuer *x509.Certificate) (tim
144144
return resp.ThisUpdate.Add(resp.NextUpdate.Sub(resp.ThisUpdate) / 2), nil
145145
}
146146

147-
func (this *CertCache) ServeHTTP(resp http.ResponseWriter, req *http.Request, params httprouter.Params) {
148-
if params.ByName("certName") == this.certName {
147+
func (this *CertCache) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
148+
params := mux.Params(req)
149+
if params["certName"] == this.certName {
149150
// https://tools.ietf.org/html/draft-yasskin-httpbis-origin-signed-exchanges-impl-00#section-3.3
150151
// This content-type is not standard, but included to reduce
151152
// the chance that faulty user agents employ content sniffing.

packager/certcache/certcache_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import (
2929

3030
"github.com/WICG/webpackage/go/signedexchange"
3131
"github.com/WICG/webpackage/go/signedexchange/cbor"
32+
"github.com/ampproject/amppackager/packager/mux"
3233
pkgt "github.com/ampproject/amppackager/packager/testing"
3334
"github.com/ampproject/amppackager/packager/util"
34-
"github.com/julienschmidt/httprouter"
3535
"github.com/stretchr/testify/suite"
3636
"golang.org/x/crypto/ocsp"
3737
)
@@ -133,6 +133,10 @@ func (this *CertCacheSuite) TearDownTest() {
133133
}
134134
}
135135

136+
func (this *CertCacheSuite) mux() http.Handler {
137+
return mux.New(this.handler, nil, nil)
138+
}
139+
136140
func (this *CertCacheSuite) ocspServerCalled(f func()) bool {
137141
this.ocspServerWasCalled = false
138142
f()
@@ -168,7 +172,7 @@ func (this *CertCacheSuite) DecodeCBOR(r io.Reader) map[string][]byte {
168172
}
169173

170174
func (this *CertCacheSuite) TestServesCertificate() {
171-
resp := pkgt.GetP(this.T(), this.handler, "/amppkg/cert/"+pkgt.CertName, httprouter.Params{httprouter.Param{"certName", pkgt.CertName}})
175+
resp := pkgt.Get(this.T(), this.mux(), "/amppkg/cert/"+pkgt.CertName)
172176
this.Assert().Equal(http.StatusOK, resp.StatusCode, "incorrect status: %#v", resp)
173177
this.Assert().Equal("nosniff", resp.Header.Get("X-Content-Type-Options"))
174178
cbor := this.DecodeCBOR(resp.Body)
@@ -178,7 +182,7 @@ func (this *CertCacheSuite) TestServesCertificate() {
178182
}
179183

180184
func (this *CertCacheSuite) TestServes404OnMissingCertificate() {
181-
resp := pkgt.GetP(this.T(), this.handler, "/amppkg/cert/lalala", httprouter.Params{httprouter.Param{"certName", "lalala"}})
185+
resp := pkgt.Get(this.T(), this.mux(), "/amppkg/cert/lalala")
182186
this.Assert().Equal(http.StatusNotFound, resp.StatusCode, "incorrect status: %#v", resp)
183187
body, _ := ioutil.ReadAll(resp.Body)
184188
// Small enough not to fit a cert or key:
@@ -187,7 +191,7 @@ func (this *CertCacheSuite) TestServes404OnMissingCertificate() {
187191

188192
func (this *CertCacheSuite) TestOCSP() {
189193
// Verify it gets included in the cert-chain+cbor payload.
190-
resp := pkgt.GetP(this.T(), this.handler, "/amppkg/cert/"+pkgt.CertName, httprouter.Params{httprouter.Param{"certName", pkgt.CertName}})
194+
resp := pkgt.Get(this.T(), this.mux(), "/amppkg/cert/"+pkgt.CertName)
191195
this.Assert().Equal(http.StatusOK, resp.StatusCode, "incorrect status: %#v", resp)
192196
// 302400 is 3.5 days. max-age is slightly less because of the time between fake OCSP generation and cert-chain response.
193197
// TODO(twifkak): Make this less flaky, by injecting a fake clock.
@@ -222,7 +226,7 @@ func (this *CertCacheSuite) TestOCSPExpiry() {
222226
}))
223227

224228
// Verify HTTP response expires immediately:
225-
resp := pkgt.GetP(this.T(), this.handler, "/amppkg/cert/"+pkgt.CertName, httprouter.Params{httprouter.Param{"certName", pkgt.CertName}})
229+
resp := pkgt.Get(this.T(), this.mux(), "/amppkg/cert/"+pkgt.CertName)
226230
this.Assert().Equal("public, max-age=0", resp.Header.Get("Cache-Control"))
227231

228232
// On update, verify network is called:

0 commit comments

Comments
 (0)