Skip to content
Permalink
Browse files

gen-signedexchange: Add -ignoreErrors flag (#351)

This flag lets gen-signedexchange skip some input validations. This is
needed to create SXGs for web-platform-tests.

This also fixes a bug that Exchange.Verify() fails when
url.Parse(fallbackUrl).String() != fallbackUrl.
(Note: current Chromium impl has the same bug, see crbug.com/914247)
  • Loading branch information...
irori committed Dec 15, 2018
1 parent dcb188d commit d3352199e0a074ef0ce4390232b861560d061425
@@ -47,6 +47,8 @@ var (
flagDumpHeadersCbor = flag.String("dumpHeadersCbor", "", "Dump metadata and headers encoded as a canonical CBOR to a file for debugging.")
flagOutput = flag.String("o", "out.sxg", "Signed exchange output file. If value is '-', sxg is written to stdout.")

flagIgnoreErrors = flag.Bool("ignoreErrors", false, "Do not reject invalid input arguments")

flagRequestHeader = headerArgs{}
flagResponseHeader = headerArgs{}
)
@@ -138,11 +140,6 @@ func run() error {
defer f.Close()
}

parsedUrl, err := url.Parse(*flagUri)
if err != nil {
return fmt.Errorf("failed to parse URL %q. err: %v", *flagUri, err)
}

reqHeader := http.Header{}
for _, h := range flagRequestHeader {
chunks := strings.SplitN(h, ":", 2)
@@ -157,9 +154,19 @@ func run() error {
if resHeader.Get("content-type") == "" {
resHeader.Add("content-type", "text/html; charset=utf-8")
}
e, err := signedexchange.NewExchange(ver, parsedUrl, *flagMethod, reqHeader, *flagResponseStatus, resHeader, payload)
if err != nil {
return err

var e *signedexchange.Exchange
if *flagIgnoreErrors {
e = signedexchange.NewExchangeNoCheck(ver, *flagUri, *flagMethod, reqHeader, *flagResponseStatus, resHeader, payload)
} else {
parsedUrl, err := url.Parse(*flagUri)
if err != nil {
return fmt.Errorf("failed to parse URL %q. err: %v", *flagUri, err)
}
e, err = signedexchange.NewExchange(ver, parsedUrl, *flagMethod, reqHeader, *flagResponseStatus, resHeader, payload)
if err != nil {
return err
}
}
if err := e.MiEncodePayload(*flagMIRecordSize); err != nil {
return err
@@ -34,7 +34,7 @@ type Exchange struct {
Version version.Version

// Request
RequestURI *url.URL
RequestURI string
RequestMethod string
RequestHeaders http.Header

@@ -73,7 +73,7 @@ func NewExchange(ver version.Version, uri *url.URL, method string, requestHeader

return &Exchange{
Version: ver,
RequestURI: uri,
RequestURI: uri.String(),
RequestMethod: method,
ResponseStatus: status,
RequestHeaders: requestHeaders,
@@ -82,6 +82,18 @@ func NewExchange(ver version.Version, uri *url.URL, method string, requestHeader
}, nil
}

func NewExchangeNoCheck(ver version.Version, uri string, method string, requestHeaders http.Header, status int, responseHeaders http.Header, payload []byte) *Exchange {
return &Exchange{
Version: ver,
RequestURI: uri,
RequestMethod: method,
ResponseStatus: status,
RequestHeaders: requestHeaders,
ResponseHeaders: responseHeaders,
Payload: payload,
}
}

func (e *Exchange) MiEncodePayload(recordSize int) error {
var enc mice.Encoding
switch e.Version {
@@ -127,7 +139,7 @@ func (e *Exchange) encodeRequestMap(enc *cbor.Encoder) error {
mes = append(mes,
cbor.GenerateMapEntry(func(keyE *cbor.Encoder, valueE *cbor.Encoder) {
keyE.EncodeByteString(keyURL)
valueE.EncodeByteString([]byte(e.RequestURI.String()))
valueE.EncodeByteString([]byte(e.RequestURI))
}))
}
mes = encodeHeaders(mes, e.RequestHeaders)
@@ -173,7 +185,7 @@ func (e *Exchange) decodeRequestMap(dec *cbor.Decoder) error {
}
if bytes.Equal(key, keyURL) {
if e.Version == version.Version1b1 {
e.RequestURI, err = url.Parse(string(value))
e.RequestURI, err = validateFallbackURL(value)
if err != nil {
return err
}
@@ -330,8 +342,7 @@ func (e *Exchange) Write(w io.Writer) error {
}

// "2. 2 bytes storing a big-endian integer fallbackUrlLength." [spec text]
url := e.RequestURI.String()
urlLength, err := bigendian.EncodeBytesUint(int64(len(url)), 2)
urlLength, err := bigendian.EncodeBytesUint(int64(len(e.RequestURI)), 2)
if err != nil {
return err
}
@@ -341,7 +352,7 @@ func (e *Exchange) Write(w io.Writer) error {

// "3. fallbackUrlLength bytes holding a fallbackUrl, which MUST be an absolute URL with a scheme of “https”.
// Note: The byte location of the fallback URL is intended to remain invariant across versions of the application/signed-exchange format so that parsers encountering unknown versions can always find a URL to redirect to." [spec text]
if _, err := w.Write([]byte(url)); err != nil {
if _, err := w.Write([]byte(e.RequestURI)); err != nil {
return err
}

@@ -433,7 +444,7 @@ func ReadExchange(r io.Reader) (*Exchange, error) {
return nil, err
}
var err error
e.RequestURI, err = url.Parse(string(fallbackUrl))
e.RequestURI, err = validateFallbackURL(fallbackUrl)
if err != nil {
return nil, err
}
@@ -482,6 +493,20 @@ func ReadExchange(r io.Reader) (*Exchange, error) {
return e, nil
}

func validateFallbackURL(urlBytes []byte) (string, error) {
// draft-yasskin-http-origin-signed-responses.html#application-signed-exchange
// Step 3. "fallbackUrlLength bytes holding a fallbackUrl, which MUST be an absolute URL with a scheme of “https”. " [spec text]
urlStr := string(urlBytes)
parsedUrl, err := url.Parse(urlStr)
if err != nil {
return "", fmt.Errorf("signedexchange: cannot parse fallback URL %q: %v", urlStr, err)
}
if parsedUrl.Scheme != "https" { // This also ensures that parsedUrl is absolute.
return "", fmt.Errorf("signedexchange: non-https fallback URL: %q", urlStr)
}
return urlStr, nil
}

func (e *Exchange) DumpSignedMessage(w io.Writer, s *Signer) error {
bs, err := serializeSignedMessage(e, calculateCertSha256(s.Certs), s.ValidityUrl.String(), s.Date.Unix(), s.Expires.Unix())
if err != nil {
@@ -498,7 +523,7 @@ func (e *Exchange) PrettyPrintHeaders(w io.Writer) {
fmt.Fprintf(w, "format version: %s\n", e.Version)
fmt.Fprintln(w, "request:")
fmt.Fprintf(w, " method: %s\n", e.RequestMethod)
fmt.Fprintf(w, " uri: %s\n", e.RequestURI.String())
fmt.Fprintf(w, " uri: %s\n", e.RequestURI)
fmt.Fprintln(w, " headers:")
for k := range e.RequestHeaders {
fmt.Fprintf(w, " %s: %s\n", k, e.RequestHeaders.Get(k))
@@ -390,3 +390,18 @@ func TestVerifyBadSignature(t *testing.T) {
t.Errorf("Verification should fail")
}
}

func TestVerifyNonCanonicalURL(t *testing.T) {
e, s, c := createTestExchange(t)
// url.Parse() decodes "%73%78%67" to "sxg"
e.RequestURI = "https://example.com/%73%78%67"
if err := e.AddSignatureHeader(s); err != nil {
t.Fatal(err)
}
certFetcher := func(_ string) ([]byte, error) { return c, nil }

verificationTime := signatureDate
if !e.Verify(verificationTime, certFetcher, log.New(os.Stdout, "ERROR: ", 0)) {
t.Errorf("Verification failed")
}
}
@@ -156,7 +156,7 @@ func serializeSignedMessage(e *Exchange, certSha256 []byte, validityUrl string,
buf.Write(expiresBytes)

// "8. The 8-byte big-endian encoding of the length in bytes of requestUrl, followed by the bytes of requestUrl." [spec text]
rurl := []byte(e.RequestURI.String())
rurl := []byte(e.RequestURI)
rurlLenBytes, _ := bigendian.EncodeBytesUint(int64(len(rurl)), 8)
buf.Write(rurlLenBytes)
buf.Write(rurl)
@@ -111,7 +111,12 @@ func (e *Exchange) Verify(verificationTime time.Time, certFetcher CertFetcher, l
l.Printf("Cannot parse validity-url: %q", signature.ValidityUrl)
continue
}
if !isSameOrigin(validityUrl, e.RequestURI) {
requestURI, err := url.Parse(e.RequestURI)
if err != nil {
l.Printf("Cannot parse request URI: %q", e.RequestURI)
continue
}
if !isSameOrigin(validityUrl, requestURI) {
l.Printf("validity-url (%s) is not same-origin with request URL (%v)", signature.ValidityUrl, e.RequestURI)
continue
}

0 comments on commit d335219

Please sign in to comment.
You can’t perform that action at this time.