Add generic OpenID Connect provider (OAuth2)
This adds the oauth2 provider `oidc`. It needs an additional argument, the OIDC discovery endpoint to figure out where the auth and token URLs are. Configuration is similar to setting up the Google Authentication with these changes: * `OAUTH2_PROVIDER = oidc` * `OAUTH2_OIDC_DISCOVERY_ENDPOINT = https://auth.exampe.org/discovery`
This commit is contained in:
parent
54602b55bb
commit
3e1e0b604f
88 changed files with 15856 additions and 155 deletions
|
@ -832,6 +832,41 @@ func TestDefaultOAuth2RedirectURLValue(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOAuth2OidcDiscoveryEndpoint(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
os.Setenv("OAUTH2_OIDC_DISCOVERY_ENDPOINT", "http://example.org")
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
opts, err := parser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "http://example.org"
|
||||||
|
result := opts.OAuth2OidcDiscoveryEndpoint()
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf(`Unexpected OAUTH2_OIDC_DISCOVERY_ENDPOINT value, got %q instead of %q`, result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultOAuth2OidcDiscoveryEndpointValue(t *testing.T) {
|
||||||
|
os.Clearenv()
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
opts, err := parser.ParseEnvironmentVariables()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(`Parsing failure: %v`, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := defaultOAuth2OidcDiscoveryEndpoint
|
||||||
|
result := opts.OAuth2OidcDiscoveryEndpoint()
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOAuth2Provider(t *testing.T) {
|
func TestOAuth2Provider(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
os.Setenv("OAUTH2_PROVIDER", "google")
|
os.Setenv("OAUTH2_PROVIDER", "google")
|
||||||
|
|
|
@ -40,6 +40,7 @@ const (
|
||||||
defaultOAuth2ClientID = ""
|
defaultOAuth2ClientID = ""
|
||||||
defaultOAuth2ClientSecret = ""
|
defaultOAuth2ClientSecret = ""
|
||||||
defaultOAuth2RedirectURL = ""
|
defaultOAuth2RedirectURL = ""
|
||||||
|
defaultOAuth2OidcDiscoveryEndpoint = ""
|
||||||
defaultOAuth2Provider = ""
|
defaultOAuth2Provider = ""
|
||||||
defaultPocketConsumerKey = ""
|
defaultPocketConsumerKey = ""
|
||||||
defaultHTTPClientTimeout = 20
|
defaultHTTPClientTimeout = 20
|
||||||
|
@ -80,6 +81,7 @@ type Options struct {
|
||||||
oauth2ClientID string
|
oauth2ClientID string
|
||||||
oauth2ClientSecret string
|
oauth2ClientSecret string
|
||||||
oauth2RedirectURL string
|
oauth2RedirectURL string
|
||||||
|
oauth2OidcDiscoveryEndpoint string
|
||||||
oauth2Provider string
|
oauth2Provider string
|
||||||
pocketConsumerKey string
|
pocketConsumerKey string
|
||||||
httpClientTimeout int
|
httpClientTimeout int
|
||||||
|
@ -121,6 +123,7 @@ func NewOptions() *Options {
|
||||||
oauth2ClientID: defaultOAuth2ClientID,
|
oauth2ClientID: defaultOAuth2ClientID,
|
||||||
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
||||||
oauth2RedirectURL: defaultOAuth2RedirectURL,
|
oauth2RedirectURL: defaultOAuth2RedirectURL,
|
||||||
|
oauth2OidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint,
|
||||||
oauth2Provider: defaultOAuth2Provider,
|
oauth2Provider: defaultOAuth2Provider,
|
||||||
pocketConsumerKey: defaultPocketConsumerKey,
|
pocketConsumerKey: defaultPocketConsumerKey,
|
||||||
httpClientTimeout: defaultHTTPClientTimeout,
|
httpClientTimeout: defaultHTTPClientTimeout,
|
||||||
|
@ -250,6 +253,11 @@ func (o *Options) OAuth2RedirectURL() string {
|
||||||
return o.oauth2RedirectURL
|
return o.oauth2RedirectURL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuth2OidcDiscoveryEndpoint returns the OAuth2 OIDC discovery endpoint.
|
||||||
|
func (o *Options) OAuth2OidcDiscoveryEndpoint() string {
|
||||||
|
return o.oauth2OidcDiscoveryEndpoint
|
||||||
|
}
|
||||||
|
|
||||||
// OAuth2Provider returns the name of the OAuth2 provider configured.
|
// OAuth2Provider returns the name of the OAuth2 provider configured.
|
||||||
func (o *Options) OAuth2Provider() string {
|
func (o *Options) OAuth2Provider() string {
|
||||||
return o.oauth2Provider
|
return o.oauth2Provider
|
||||||
|
@ -348,6 +356,7 @@ func (o *Options) String() string {
|
||||||
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID))
|
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID))
|
||||||
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret))
|
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret))
|
||||||
builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL))
|
builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL))
|
||||||
|
builder.WriteString(fmt.Sprintf("OAUTH2_OIDC_DISCOVERY_ENDPOINT: %v\n", o.oauth2OidcDiscoveryEndpoint))
|
||||||
builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider))
|
builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider))
|
||||||
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout))
|
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout))
|
||||||
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize))
|
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize))
|
||||||
|
|
|
@ -152,6 +152,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||||
p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
|
p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret)
|
||||||
case "OAUTH2_REDIRECT_URL":
|
case "OAUTH2_REDIRECT_URL":
|
||||||
p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
|
p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL)
|
||||||
|
case "OAUTH2_OIDC_DISCOVERY_ENDPOINT":
|
||||||
|
p.opts.oauth2OidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint)
|
||||||
case "OAUTH2_PROVIDER":
|
case "OAUTH2_PROVIDER":
|
||||||
p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
|
p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider)
|
||||||
case "HTTP_CLIENT_TIMEOUT":
|
case "HTTP_CLIENT_TIMEOUT":
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -4,15 +4,21 @@ module miniflux.app
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.5.0
|
github.com/PuerkitoBio/goquery v1.5.0
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
github.com/gorilla/mux v1.7.3
|
github.com/gorilla/mux v1.7.3
|
||||||
github.com/lib/pq v1.2.0
|
github.com/lib/pq v1.2.0
|
||||||
github.com/tdewolff/minify/v2 v2.5.2 // indirect
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
|
||||||
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
|
github.com/tdewolff/minify/v2 v2.7.2 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708
|
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367 // indirect
|
||||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
|
||||||
|
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 // indirect
|
||||||
google.golang.org/appengine v1.6.2 // indirect
|
google.golang.org/appengine v1.6.2 // indirect
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.11
|
go 1.11
|
||||||
|
|
42
go.sum
42
go.sum
|
@ -4,6 +4,10 @@ github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8
|
||||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
||||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||||
|
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
@ -16,20 +20,28 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
|
||||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
|
||||||
|
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
||||||
github.com/tdewolff/minify/v2 v2.5.2 h1:If/q1brvT+91oWiWnIMEGuFcwWtpB6AtLTxba78tvMs=
|
github.com/tdewolff/minify/v2 v2.7.2 h1:XA92QuWsrKji+TlBv03mPuzUpSWz97mE5nyISY84wEY=
|
||||||
github.com/tdewolff/minify/v2 v2.5.2/go.mod h1:Q6mWHrmspbdRX0ZuUUoKIT8bDjVVXpIJ73ux7p7HZGg=
|
github.com/tdewolff/minify/v2 v2.7.2/go.mod h1:BkDSm8aMMT0ALGmpt7j3Ra7nLUgZL0qhyrAHXwxcy5w=
|
||||||
github.com/tdewolff/parse/v2 v2.3.9 h1:d8/K6XOLy5JVpLTG9Kx+SxA72rlm5OowFmVSVgtOlmM=
|
github.com/tdewolff/parse/v2 v2.4.2 h1:Bu2Qv6wepkc+Ou7iB/qHjAhEImlAP5vedzlQRUdj3BI=
|
||||||
github.com/tdewolff/parse/v2 v2.3.9/go.mod h1:HansaqmN4I/U7L6/tUp0NcwT2tFO0F4EAWYGSDzkYNk=
|
github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||||
github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4=
|
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
github.com/tdewolff/test v1.0.4/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
|
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
|
||||||
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -37,8 +49,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
|
||||||
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||||
|
@ -60,7 +71,18 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
|
||||||
|
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI=
|
google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI=
|
||||||
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -159,9 +159,12 @@ var translations = map[string]string{
|
||||||
"page.users.is_admin": "Administrator",
|
"page.users.is_admin": "Administrator",
|
||||||
"page.settings.title": "Einstellungen",
|
"page.settings.title": "Einstellungen",
|
||||||
"page.settings.link_google_account": "Google Konto verknüpfen",
|
"page.settings.link_google_account": "Google Konto verknüpfen",
|
||||||
"page.settings.unlink_google_account": "Diese Kategorie existiert nicht für diesen Benutzer",
|
"page.settings.unlink_google_account": "Google Konto Verknüpfung entfernen",
|
||||||
|
"page.settings.link_oidc_account": "OpenID Connect Konto verknüpfen",
|
||||||
|
"page.settings.unlink_oidc_account": "OpenID Connect Konto Verknüpfung entfernen",
|
||||||
"page.login.title": "Anmeldung",
|
"page.login.title": "Anmeldung",
|
||||||
"page.login.google_signin": "Anmeldung mit Google",
|
"page.login.google_signin": "Anmeldung mit Google",
|
||||||
|
"page.login.oidc_signin": "Anmeldung mit OpenID Connect",
|
||||||
"page.integrations.title": "Dienste",
|
"page.integrations.title": "Dienste",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpunkt",
|
"page.integration.miniflux_api_endpoint": "API Endpunkt",
|
||||||
|
@ -483,8 +486,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Settings",
|
"page.settings.title": "Settings",
|
||||||
"page.settings.link_google_account": "Link my Google account",
|
"page.settings.link_google_account": "Link my Google account",
|
||||||
"page.settings.unlink_google_account": "Unlink my Google account",
|
"page.settings.unlink_google_account": "Unlink my Google account",
|
||||||
|
"page.settings.link_oidc_account": "Link my OpenID Connect account",
|
||||||
|
"page.settings.unlink_oidc_account": "Unlink my OpenID Connect account",
|
||||||
"page.login.title": "Sign In",
|
"page.login.title": "Sign In",
|
||||||
"page.login.google_signin": "Sign in with Google",
|
"page.login.google_signin": "Sign in with Google",
|
||||||
|
"page.login.oidc_signin": "Sign in with OpenID Connect",
|
||||||
"page.integrations.title": "Integrations",
|
"page.integrations.title": "Integrations",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
||||||
|
@ -786,8 +792,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Ajustes",
|
"page.settings.title": "Ajustes",
|
||||||
"page.settings.link_google_account": "Vincular mi cuenta de Google",
|
"page.settings.link_google_account": "Vincular mi cuenta de Google",
|
||||||
"page.settings.unlink_google_account": "Desvincular mi cuenta de Google",
|
"page.settings.unlink_google_account": "Desvincular mi cuenta de Google",
|
||||||
|
"page.settings.link_oidc_account": "Vincular mi cuenta de OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Desvincular mi cuenta de OpenID Connect",
|
||||||
"page.login.title": "Iniciar sesión",
|
"page.login.title": "Iniciar sesión",
|
||||||
"page.login.google_signin": "Iniciar sesión con tu cuenta de Google",
|
"page.login.google_signin": "Iniciar sesión con tu cuenta de Google",
|
||||||
|
"page.login.oidc_signin": "Iniciar sesión con tu cuenta de OpenID Connect",
|
||||||
"page.integrations.title": "Integraciones",
|
"page.integrations.title": "Integraciones",
|
||||||
"page.integration.miniflux_api": "API de Miniflux",
|
"page.integration.miniflux_api": "API de Miniflux",
|
||||||
"page.integration.miniflux_api_endpoint": "Extremo de API",
|
"page.integration.miniflux_api_endpoint": "Extremo de API",
|
||||||
|
@ -1089,8 +1098,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Réglages",
|
"page.settings.title": "Réglages",
|
||||||
"page.settings.link_google_account": "Associer mon compte Google",
|
"page.settings.link_google_account": "Associer mon compte Google",
|
||||||
"page.settings.unlink_google_account": "Dissocier mon compte Google",
|
"page.settings.unlink_google_account": "Dissocier mon compte Google",
|
||||||
|
"page.settings.link_oidc_account": "Associer mon compte OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Dissocier mon compte OpenID Connect",
|
||||||
"page.login.title": "Connexion",
|
"page.login.title": "Connexion",
|
||||||
"page.login.google_signin": "Se connecter avec Google",
|
"page.login.google_signin": "Se connecter avec Google",
|
||||||
|
"page.login.oidc_signin": "Se connecter avec OpenID Connect",
|
||||||
"page.integrations.title": "Intégrations",
|
"page.integrations.title": "Intégrations",
|
||||||
"page.integration.miniflux_api": "API de Miniflux",
|
"page.integration.miniflux_api": "API de Miniflux",
|
||||||
"page.integration.miniflux_api_endpoint": "Point de terminaison de l'API",
|
"page.integration.miniflux_api_endpoint": "Point de terminaison de l'API",
|
||||||
|
@ -1412,8 +1424,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Impostazioni",
|
"page.settings.title": "Impostazioni",
|
||||||
"page.settings.link_google_account": "Collega il mio account Google",
|
"page.settings.link_google_account": "Collega il mio account Google",
|
||||||
"page.settings.unlink_google_account": "Scollega il mio account Google",
|
"page.settings.unlink_google_account": "Scollega il mio account Google",
|
||||||
|
"page.settings.link_oidc_account": "Collega il mio account OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Scollega il mio account OpenID Connect",
|
||||||
"page.login.title": "Accedi",
|
"page.login.title": "Accedi",
|
||||||
"page.login.google_signin": "Accedi tramite Google",
|
"page.login.google_signin": "Accedi tramite Google",
|
||||||
|
"page.login.oidc_signin": "Accedi tramite OpenID Connect",
|
||||||
"page.integrations.title": "Integrazioni",
|
"page.integrations.title": "Integrazioni",
|
||||||
"page.integration.miniflux_api": "API di Miniflux",
|
"page.integration.miniflux_api": "API di Miniflux",
|
||||||
"page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux",
|
"page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux",
|
||||||
|
@ -1715,8 +1730,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "設定",
|
"page.settings.title": "設定",
|
||||||
"page.settings.link_google_account": "Google アカウントと接続する",
|
"page.settings.link_google_account": "Google アカウントと接続する",
|
||||||
"page.settings.unlink_google_account": "Google アカウントと接続を解除する",
|
"page.settings.unlink_google_account": "Google アカウントと接続を解除する",
|
||||||
|
"page.settings.link_oidc_account": "OpenID Connect アカウントと接続する",
|
||||||
|
"page.settings.unlink_oidc_account": "OpenID Connect アカウントと接続を解除する",
|
||||||
"page.login.title": "ログイン",
|
"page.login.title": "ログイン",
|
||||||
"page.login.google_signin": "Google アカウントでログイン",
|
"page.login.google_signin": "Google アカウントでログイン",
|
||||||
|
"page.login.oidc_signin": "OpenID Connect アカウントでログイン",
|
||||||
"page.integrations.title": "関連付け",
|
"page.integrations.title": "関連付け",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
||||||
|
@ -2019,6 +2037,9 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Instellingen",
|
"page.settings.title": "Instellingen",
|
||||||
"page.settings.link_google_account": "Koppel mijn Google-account",
|
"page.settings.link_google_account": "Koppel mijn Google-account",
|
||||||
"page.settings.unlink_google_account": "Ontkoppel mijn Google-account",
|
"page.settings.unlink_google_account": "Ontkoppel mijn Google-account",
|
||||||
|
"page.settings.link_oidc_account": "Koppel mijn OpenID Connect-account",
|
||||||
|
"page.settings.unlink_oidc_account": "Ontkoppel mijn OpenID Connect-account",
|
||||||
|
"page.login.oidc_signin": "Inloggen via OpenID Connect",
|
||||||
"page.login.google_signin": "Inloggen via Google",
|
"page.login.google_signin": "Inloggen via Google",
|
||||||
"page.integrations.title": "Integraties",
|
"page.integrations.title": "Integraties",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
|
@ -2341,8 +2362,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Ustawienia",
|
"page.settings.title": "Ustawienia",
|
||||||
"page.settings.link_google_account": "Połącz z moim kontem Google",
|
"page.settings.link_google_account": "Połącz z moim kontem Google",
|
||||||
"page.settings.unlink_google_account": "Odłącz moje konto Google",
|
"page.settings.unlink_google_account": "Odłącz moje konto Google",
|
||||||
|
"page.settings.link_oidc_account": "Połącz z moim kontem OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Odłącz moje konto OpenID Connect",
|
||||||
"page.login.title": "Zaloguj się",
|
"page.login.title": "Zaloguj się",
|
||||||
"page.login.google_signin": "Zaloguj przez Google",
|
"page.login.google_signin": "Zaloguj przez Google",
|
||||||
|
"page.login.oidc_signin": "Zaloguj przez OpenID Connect",
|
||||||
"page.integrations.title": "Usługi",
|
"page.integrations.title": "Usługi",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "Punkt końcowy API",
|
"page.integration.miniflux_api_endpoint": "Punkt końcowy API",
|
||||||
|
@ -2670,8 +2694,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "Настройки",
|
"page.settings.title": "Настройки",
|
||||||
"page.settings.link_google_account": "Привязать мой Google аккаунт",
|
"page.settings.link_google_account": "Привязать мой Google аккаунт",
|
||||||
"page.settings.unlink_google_account": "Отвязать мой Google аккаунт",
|
"page.settings.unlink_google_account": "Отвязать мой Google аккаунт",
|
||||||
|
"page.settings.link_oidc_account": "Привязать мой OpenID Connect аккаунт",
|
||||||
|
"page.settings.unlink_oidc_account": "Отвязать мой OpenID Connect аккаунт",
|
||||||
"page.login.title": "Войти",
|
"page.login.title": "Войти",
|
||||||
"page.login.google_signin": "Войти с помощью Google",
|
"page.login.google_signin": "Войти с помощью Google",
|
||||||
|
"page.login.oidc_signin": "Войти с помощью OpenID Connect",
|
||||||
"page.integrations.title": "Интеграции",
|
"page.integrations.title": "Интеграции",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "Конечная точка API",
|
"page.integration.miniflux_api_endpoint": "Конечная точка API",
|
||||||
|
@ -2977,8 +3004,11 @@ var translations = map[string]string{
|
||||||
"page.settings.title": "设置",
|
"page.settings.title": "设置",
|
||||||
"page.settings.link_google_account": "关联我的 Google 账户",
|
"page.settings.link_google_account": "关联我的 Google 账户",
|
||||||
"page.settings.unlink_google_account": "解除 Google 账号关联",
|
"page.settings.unlink_google_account": "解除 Google 账号关联",
|
||||||
|
"page.settings.link_oidc_account": "关联我的 OpenID Connect 账户",
|
||||||
|
"page.settings.unlink_oidc_account": "解除 OpenID Connect 账号关联",
|
||||||
"page.login.title": "登陆",
|
"page.login.title": "登陆",
|
||||||
"page.login.google_signin": "使用 Google 登陆",
|
"page.login.google_signin": "使用 Google 登陆",
|
||||||
|
"page.login.oidc_signin": "使用 OpenID Connect 登陆",
|
||||||
"page.integrations.title": "集成",
|
"page.integrations.title": "集成",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
||||||
|
@ -3139,14 +3169,14 @@ var translations = map[string]string{
|
||||||
}
|
}
|
||||||
|
|
||||||
var translationsChecksums = map[string]string{
|
var translationsChecksums = map[string]string{
|
||||||
"de_DE": "75ccff01dcd27613e2d130c5b6abdb6bb2645029c93373c7b96d8754298002cd",
|
"de_DE": "cc826a57cf4bf789df38db4f50626ad8c1c2b84ce34075c2c04de3d1f0dcd2d5",
|
||||||
"en_US": "f6ac2959fbe86b273ca3cd95031741dbfc4db25e8b61d6b29b798a9faefae4c6",
|
"en_US": "f7e6db53cdbc2c0d959ac231dbacf0ef4d0ed81248944c4a4f8b83ef000f5349",
|
||||||
"es_ES": "a3a494acf1864b2cc6573f9627e5bd2f07fa96a14a39619f310e87e66a4f2c01",
|
"es_ES": "cc727f62eef3a6cba51b65253d70a50161af35bf9c5366281b7984b2fc189961",
|
||||||
"fr_FR": "9162d348af1c6d30bb6f16bb85468d394a353e9def08cf77adc47404889e6e78",
|
"fr_FR": "d3d1a4bf9aa8e4e24bae2f117507dcfc3cf00660a73b44a6c42356e8dbab8ae8",
|
||||||
"it_IT": "ad12b1282ed9b3d1a785f92af70c07f3d7aecf49e8a5d1f023742636b24a366b",
|
"it_IT": "5ded991f2c70ec2268e6053bd84a77cf4136ebaea42013d3e79d594f38abb1b3",
|
||||||
"ja_JP": "a9994611dc3b6a6dd763b6bd1c89bc7c5ec9985a04059f6c45342077d42a3e05",
|
"ja_JP": "110d7a7b1c888282b031de340e3318a62cdd62076b05a7fb49759f554c6dbe76",
|
||||||
"nl_NL": "54e9b6cd6758ee3e699028104f25704d6569e5ed8793ff17e817ad80f1ef7bd2",
|
"nl_NL": "a934ab4b1eff85580425a5859c31fcb227ae8926deba74df4e42b5d4feb67826",
|
||||||
"pl_PL": "6a95a4f7e8bce0d0d0e0f56d46e69b4577a44609d15511d9fa11c81cb981b5d7",
|
"pl_PL": "6e80c36788723b9a7ff3f372e13a55c68d153727ec0abb56663cadbf6d6e1d9f",
|
||||||
"ru_RU": "cb024cd742298206634be390a19b7371a797ab8484615a69af7d8fdbea9b58f8",
|
"ru_RU": "d56f9e31f63731d23ce1ea2a8a4cb019f3ab282b23a1f494c47061daea523587",
|
||||||
"zh_CN": "a5f32c5e4714bce8638f7fd19b6c3e54937d9ab00b08ab655076d7be35ef76bd",
|
"zh_CN": "4a5ca40790fceab88257f6742dc05294b79142bee8aad6fc87fbd479d1941292",
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,9 +154,12 @@
|
||||||
"page.users.is_admin": "Administrator",
|
"page.users.is_admin": "Administrator",
|
||||||
"page.settings.title": "Einstellungen",
|
"page.settings.title": "Einstellungen",
|
||||||
"page.settings.link_google_account": "Google Konto verknüpfen",
|
"page.settings.link_google_account": "Google Konto verknüpfen",
|
||||||
"page.settings.unlink_google_account": "Diese Kategorie existiert nicht für diesen Benutzer",
|
"page.settings.unlink_google_account": "Google Konto Verknüpfung entfernen",
|
||||||
|
"page.settings.link_oidc_account": "OpenID Connect Konto verknüpfen",
|
||||||
|
"page.settings.unlink_oidc_account": "OpenID Connect Konto Verknüpfung entfernen",
|
||||||
"page.login.title": "Anmeldung",
|
"page.login.title": "Anmeldung",
|
||||||
"page.login.google_signin": "Anmeldung mit Google",
|
"page.login.google_signin": "Anmeldung mit Google",
|
||||||
|
"page.login.oidc_signin": "Anmeldung mit OpenID Connect",
|
||||||
"page.integrations.title": "Dienste",
|
"page.integrations.title": "Dienste",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpunkt",
|
"page.integration.miniflux_api_endpoint": "API Endpunkt",
|
||||||
|
|
|
@ -155,8 +155,11 @@
|
||||||
"page.settings.title": "Settings",
|
"page.settings.title": "Settings",
|
||||||
"page.settings.link_google_account": "Link my Google account",
|
"page.settings.link_google_account": "Link my Google account",
|
||||||
"page.settings.unlink_google_account": "Unlink my Google account",
|
"page.settings.unlink_google_account": "Unlink my Google account",
|
||||||
|
"page.settings.link_oidc_account": "Link my OpenID Connect account",
|
||||||
|
"page.settings.unlink_oidc_account": "Unlink my OpenID Connect account",
|
||||||
"page.login.title": "Sign In",
|
"page.login.title": "Sign In",
|
||||||
"page.login.google_signin": "Sign in with Google",
|
"page.login.google_signin": "Sign in with Google",
|
||||||
|
"page.login.oidc_signin": "Sign in with OpenID Connect",
|
||||||
"page.integrations.title": "Integrations",
|
"page.integrations.title": "Integrations",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
||||||
|
|
|
@ -155,8 +155,11 @@
|
||||||
"page.settings.title": "Ajustes",
|
"page.settings.title": "Ajustes",
|
||||||
"page.settings.link_google_account": "Vincular mi cuenta de Google",
|
"page.settings.link_google_account": "Vincular mi cuenta de Google",
|
||||||
"page.settings.unlink_google_account": "Desvincular mi cuenta de Google",
|
"page.settings.unlink_google_account": "Desvincular mi cuenta de Google",
|
||||||
|
"page.settings.link_oidc_account": "Vincular mi cuenta de OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Desvincular mi cuenta de OpenID Connect",
|
||||||
"page.login.title": "Iniciar sesión",
|
"page.login.title": "Iniciar sesión",
|
||||||
"page.login.google_signin": "Iniciar sesión con tu cuenta de Google",
|
"page.login.google_signin": "Iniciar sesión con tu cuenta de Google",
|
||||||
|
"page.login.oidc_signin": "Iniciar sesión con tu cuenta de OpenID Connect",
|
||||||
"page.integrations.title": "Integraciones",
|
"page.integrations.title": "Integraciones",
|
||||||
"page.integration.miniflux_api": "API de Miniflux",
|
"page.integration.miniflux_api": "API de Miniflux",
|
||||||
"page.integration.miniflux_api_endpoint": "Extremo de API",
|
"page.integration.miniflux_api_endpoint": "Extremo de API",
|
||||||
|
|
|
@ -155,8 +155,11 @@
|
||||||
"page.settings.title": "Réglages",
|
"page.settings.title": "Réglages",
|
||||||
"page.settings.link_google_account": "Associer mon compte Google",
|
"page.settings.link_google_account": "Associer mon compte Google",
|
||||||
"page.settings.unlink_google_account": "Dissocier mon compte Google",
|
"page.settings.unlink_google_account": "Dissocier mon compte Google",
|
||||||
|
"page.settings.link_oidc_account": "Associer mon compte OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Dissocier mon compte OpenID Connect",
|
||||||
"page.login.title": "Connexion",
|
"page.login.title": "Connexion",
|
||||||
"page.login.google_signin": "Se connecter avec Google",
|
"page.login.google_signin": "Se connecter avec Google",
|
||||||
|
"page.login.oidc_signin": "Se connecter avec OpenID Connect",
|
||||||
"page.integrations.title": "Intégrations",
|
"page.integrations.title": "Intégrations",
|
||||||
"page.integration.miniflux_api": "API de Miniflux",
|
"page.integration.miniflux_api": "API de Miniflux",
|
||||||
"page.integration.miniflux_api_endpoint": "Point de terminaison de l'API",
|
"page.integration.miniflux_api_endpoint": "Point de terminaison de l'API",
|
||||||
|
|
|
@ -155,8 +155,11 @@
|
||||||
"page.settings.title": "Impostazioni",
|
"page.settings.title": "Impostazioni",
|
||||||
"page.settings.link_google_account": "Collega il mio account Google",
|
"page.settings.link_google_account": "Collega il mio account Google",
|
||||||
"page.settings.unlink_google_account": "Scollega il mio account Google",
|
"page.settings.unlink_google_account": "Scollega il mio account Google",
|
||||||
|
"page.settings.link_oidc_account": "Collega il mio account OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Scollega il mio account OpenID Connect",
|
||||||
"page.login.title": "Accedi",
|
"page.login.title": "Accedi",
|
||||||
"page.login.google_signin": "Accedi tramite Google",
|
"page.login.google_signin": "Accedi tramite Google",
|
||||||
|
"page.login.oidc_signin": "Accedi tramite OpenID Connect",
|
||||||
"page.integrations.title": "Integrazioni",
|
"page.integrations.title": "Integrazioni",
|
||||||
"page.integration.miniflux_api": "API di Miniflux",
|
"page.integration.miniflux_api": "API di Miniflux",
|
||||||
"page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux",
|
"page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux",
|
||||||
|
|
|
@ -155,8 +155,11 @@
|
||||||
"page.settings.title": "設定",
|
"page.settings.title": "設定",
|
||||||
"page.settings.link_google_account": "Google アカウントと接続する",
|
"page.settings.link_google_account": "Google アカウントと接続する",
|
||||||
"page.settings.unlink_google_account": "Google アカウントと接続を解除する",
|
"page.settings.unlink_google_account": "Google アカウントと接続を解除する",
|
||||||
|
"page.settings.link_oidc_account": "OpenID Connect アカウントと接続する",
|
||||||
|
"page.settings.unlink_oidc_account": "OpenID Connect アカウントと接続を解除する",
|
||||||
"page.login.title": "ログイン",
|
"page.login.title": "ログイン",
|
||||||
"page.login.google_signin": "Google アカウントでログイン",
|
"page.login.google_signin": "Google アカウントでログイン",
|
||||||
|
"page.login.oidc_signin": "OpenID Connect アカウントでログイン",
|
||||||
"page.integrations.title": "関連付け",
|
"page.integrations.title": "関連付け",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
||||||
|
|
|
@ -156,6 +156,9 @@
|
||||||
"page.settings.title": "Instellingen",
|
"page.settings.title": "Instellingen",
|
||||||
"page.settings.link_google_account": "Koppel mijn Google-account",
|
"page.settings.link_google_account": "Koppel mijn Google-account",
|
||||||
"page.settings.unlink_google_account": "Ontkoppel mijn Google-account",
|
"page.settings.unlink_google_account": "Ontkoppel mijn Google-account",
|
||||||
|
"page.settings.link_oidc_account": "Koppel mijn OpenID Connect-account",
|
||||||
|
"page.settings.unlink_oidc_account": "Ontkoppel mijn OpenID Connect-account",
|
||||||
|
"page.login.oidc_signin": "Inloggen via OpenID Connect",
|
||||||
"page.login.google_signin": "Inloggen via Google",
|
"page.login.google_signin": "Inloggen via Google",
|
||||||
"page.integrations.title": "Integraties",
|
"page.integrations.title": "Integraties",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
|
|
|
@ -157,8 +157,11 @@
|
||||||
"page.settings.title": "Ustawienia",
|
"page.settings.title": "Ustawienia",
|
||||||
"page.settings.link_google_account": "Połącz z moim kontem Google",
|
"page.settings.link_google_account": "Połącz z moim kontem Google",
|
||||||
"page.settings.unlink_google_account": "Odłącz moje konto Google",
|
"page.settings.unlink_google_account": "Odłącz moje konto Google",
|
||||||
|
"page.settings.link_oidc_account": "Połącz z moim kontem OpenID Connect",
|
||||||
|
"page.settings.unlink_oidc_account": "Odłącz moje konto OpenID Connect",
|
||||||
"page.login.title": "Zaloguj się",
|
"page.login.title": "Zaloguj się",
|
||||||
"page.login.google_signin": "Zaloguj przez Google",
|
"page.login.google_signin": "Zaloguj przez Google",
|
||||||
|
"page.login.oidc_signin": "Zaloguj przez OpenID Connect",
|
||||||
"page.integrations.title": "Usługi",
|
"page.integrations.title": "Usługi",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "Punkt końcowy API",
|
"page.integration.miniflux_api_endpoint": "Punkt końcowy API",
|
||||||
|
|
|
@ -157,8 +157,11 @@
|
||||||
"page.settings.title": "Настройки",
|
"page.settings.title": "Настройки",
|
||||||
"page.settings.link_google_account": "Привязать мой Google аккаунт",
|
"page.settings.link_google_account": "Привязать мой Google аккаунт",
|
||||||
"page.settings.unlink_google_account": "Отвязать мой Google аккаунт",
|
"page.settings.unlink_google_account": "Отвязать мой Google аккаунт",
|
||||||
|
"page.settings.link_oidc_account": "Привязать мой OpenID Connect аккаунт",
|
||||||
|
"page.settings.unlink_oidc_account": "Отвязать мой OpenID Connect аккаунт",
|
||||||
"page.login.title": "Войти",
|
"page.login.title": "Войти",
|
||||||
"page.login.google_signin": "Войти с помощью Google",
|
"page.login.google_signin": "Войти с помощью Google",
|
||||||
|
"page.login.oidc_signin": "Войти с помощью OpenID Connect",
|
||||||
"page.integrations.title": "Интеграции",
|
"page.integrations.title": "Интеграции",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "Конечная точка API",
|
"page.integration.miniflux_api_endpoint": "Конечная точка API",
|
||||||
|
|
|
@ -153,8 +153,11 @@
|
||||||
"page.settings.title": "设置",
|
"page.settings.title": "设置",
|
||||||
"page.settings.link_google_account": "关联我的 Google 账户",
|
"page.settings.link_google_account": "关联我的 Google 账户",
|
||||||
"page.settings.unlink_google_account": "解除 Google 账号关联",
|
"page.settings.unlink_google_account": "解除 Google 账号关联",
|
||||||
|
"page.settings.link_oidc_account": "关联我的 OpenID Connect 账户",
|
||||||
|
"page.settings.unlink_oidc_account": "解除 OpenID Connect 账号关联",
|
||||||
"page.login.title": "登陆",
|
"page.login.title": "登陆",
|
||||||
"page.login.google_signin": "使用 Google 登陆",
|
"page.login.google_signin": "使用 Google 登陆",
|
||||||
|
"page.login.oidc_signin": "使用 OpenID Connect 登陆",
|
||||||
"page.integrations.title": "集成",
|
"page.integrations.title": "集成",
|
||||||
"page.integration.miniflux_api": "Miniflux API",
|
"page.integration.miniflux_api": "Miniflux API",
|
||||||
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
"page.integration.miniflux_api_endpoint": "API Endpoint",
|
||||||
|
|
|
@ -31,9 +31,8 @@ func (g googleProvider) GetRedirectURL(state string) string {
|
||||||
return g.config().AuthCodeURL(state)
|
return g.config().AuthCodeURL(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g googleProvider) GetProfile(code string) (*Profile, error) {
|
func (g googleProvider) GetProfile(ctx context.Context, code string) (*Profile, error) {
|
||||||
conf := g.config()
|
conf := g.config()
|
||||||
ctx := context.Background()
|
|
||||||
token, err := conf.Exchange(ctx, code)
|
token, err := conf.Exchange(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
|
|
||||||
package oauth2 // import "miniflux.app/oauth2"
|
package oauth2 // import "miniflux.app/oauth2"
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"miniflux.app/logger"
|
||||||
|
)
|
||||||
|
|
||||||
// Manager handles OAuth2 providers.
|
// Manager handles OAuth2 providers.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
|
@ -26,8 +30,17 @@ func (m *Manager) AddProvider(name string, provider Provider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager returns a new Manager.
|
// NewManager returns a new Manager.
|
||||||
func NewManager(clientID, clientSecret, redirectURL string) *Manager {
|
func NewManager(ctx context.Context, clientID, clientSecret, redirectURL, oidcDiscoveryEndpoint string) *Manager {
|
||||||
m := &Manager{providers: make(map[string]Provider)}
|
m := &Manager{providers: make(map[string]Provider)}
|
||||||
m.AddProvider("google", newGoogleProvider(clientID, clientSecret, redirectURL))
|
m.AddProvider("google", newGoogleProvider(clientID, clientSecret, redirectURL))
|
||||||
|
|
||||||
|
if oidcDiscoveryEndpoint != "" {
|
||||||
|
if genericOidcProvider, err := newOidcProvider(ctx, clientID, clientSecret, redirectURL, oidcDiscoveryEndpoint); err != nil {
|
||||||
|
logger.Error("[OAuth2] failed to initialize OIDC provider: %v", err)
|
||||||
|
} else {
|
||||||
|
m.AddProvider("oidc", genericOidcProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
61
oauth2/oidc.go
Normal file
61
oauth2/oidc.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2017 Frédéric Guillot. All rights reserved.
|
||||||
|
// Use of this source code is governed by the Apache 2.0
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2 // import "miniflux.app/oauth2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/coreos/go-oidc"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type oidcProvider struct {
|
||||||
|
clientID string
|
||||||
|
clientSecret string
|
||||||
|
redirectURL string
|
||||||
|
provider *oidc.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o oidcProvider) GetUserExtraKey() string {
|
||||||
|
return "oidc_id" // FIXME? add extra options key to allow multiple OIDC providers each with their own extra key?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o oidcProvider) GetRedirectURL(state string) string {
|
||||||
|
return o.config().AuthCodeURL(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o oidcProvider) GetProfile(ctx context.Context, code string) (*Profile, error) {
|
||||||
|
conf := o.config()
|
||||||
|
token, err := conf.Exchange(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, err := o.provider.UserInfo(ctx, oauth2.StaticTokenSource(token))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
profile := &Profile{Key: o.GetUserExtraKey(), ID: userInfo.Subject, Username: userInfo.Email}
|
||||||
|
return profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o oidcProvider) config() *oauth2.Config {
|
||||||
|
return &oauth2.Config{
|
||||||
|
RedirectURL: o.redirectURL,
|
||||||
|
ClientID: o.clientID,
|
||||||
|
ClientSecret: o.clientSecret,
|
||||||
|
Scopes: []string{"openid", "email"},
|
||||||
|
Endpoint: o.provider.Endpoint(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOidcProvider(ctx context.Context, clientID, clientSecret, redirectURL, discoveryEndpoint string) (*oidcProvider, error) {
|
||||||
|
provider, err := oidc.NewProvider(ctx, discoveryEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oidcProvider{clientID: clientID, clientSecret: clientSecret, redirectURL: redirectURL, provider: provider}, nil
|
||||||
|
}
|
|
@ -3,10 +3,11 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package oauth2 // import "miniflux.app/oauth2"
|
package oauth2 // import "miniflux.app/oauth2"
|
||||||
|
import "context"
|
||||||
|
|
||||||
// Provider is an interface for OAuth2 providers.
|
// Provider is an interface for OAuth2 providers.
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
GetUserExtraKey() string
|
GetUserExtraKey() string
|
||||||
GetRedirectURL(state string) string
|
GetRedirectURL(state string) string
|
||||||
GetProfile(code string) (*Profile, error)
|
GetProfile(ctx context.Context, code string) (*Profile, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@
|
||||||
<div class="oauth2">
|
<div class="oauth2">
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.login.google_signin" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.login.google_signin" }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
{{ else if hasOAuth2Provider "oidc" }}
|
||||||
|
<div class="oauth2">
|
||||||
|
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.login.oidc_signin" }}</a>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</section>
|
</section>
|
||||||
<footer id="prompt-home-screen">
|
<footer id="prompt-home-screen">
|
||||||
|
|
|
@ -64,6 +64,14 @@
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
{{ else if hasOAuth2Provider "oidc" }}
|
||||||
|
<div class="panel">
|
||||||
|
{{ if hasKey .user.Extra "oidc_id" }}
|
||||||
|
<a href="{{ route "oauth2Unlink" "provider" "oidc" }}">{{ t "page.settings.unlink_oidc_account" }}</a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.settings.link_oidc_account" }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1127,6 +1127,10 @@ var templateViewsMap = map[string]string{
|
||||||
<div class="oauth2">
|
<div class="oauth2">
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.login.google_signin" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.login.google_signin" }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
{{ else if hasOAuth2Provider "oidc" }}
|
||||||
|
<div class="oauth2">
|
||||||
|
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.login.oidc_signin" }}</a>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</section>
|
</section>
|
||||||
<footer id="prompt-home-screen">
|
<footer id="prompt-home-screen">
|
||||||
|
@ -1270,6 +1274,14 @@ var templateViewsMap = map[string]string{
|
||||||
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
<a href="{{ route "oauth2Redirect" "provider" "google" }}">{{ t "page.settings.link_google_account" }}</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
{{ else if hasOAuth2Provider "oidc" }}
|
||||||
|
<div class="panel">
|
||||||
|
{{ if hasKey .user.Extra "oidc_id" }}
|
||||||
|
<a href="{{ route "oauth2Unlink" "provider" "oidc" }}">{{ t "page.settings.unlink_oidc_account" }}</a>
|
||||||
|
{{ else }}
|
||||||
|
<a href="{{ route "oauth2Redirect" "provider" "oidc" }}">{{ t "page.settings.link_oidc_account" }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -1416,10 +1428,10 @@ var templateViewsMapChecksums = map[string]string{
|
||||||
"history_entries": "87e17d39de70eb3fdbc4000326283be610928758eae7924e4b08dcb446f3b6a9",
|
"history_entries": "87e17d39de70eb3fdbc4000326283be610928758eae7924e4b08dcb446f3b6a9",
|
||||||
"import": "1b59b3bd55c59fcbc6fbb346b414dcdd26d1b4e0c307e437bb58b3f92ef01ad1",
|
"import": "1b59b3bd55c59fcbc6fbb346b414dcdd26d1b4e0c307e437bb58b3f92ef01ad1",
|
||||||
"integrations": "30329452743b35c668278f519245fd9be05c1726856e0384ba542f7c307f2788",
|
"integrations": "30329452743b35c668278f519245fd9be05c1726856e0384ba542f7c307f2788",
|
||||||
"login": "0657174d13229bb6d0bc470ccda06bb1f15c1af65c86b20b41ffa5c819eef0cc",
|
"login": "79ff2ca488c0a19b37c8fa227a21f73e94472eb357a51a077197c852f7713f11",
|
||||||
"search_entries": "274950d03298c24f3942e209c0faed580a6d57be9cf76a6c236175a7e766ac6a",
|
"search_entries": "274950d03298c24f3942e209c0faed580a6d57be9cf76a6c236175a7e766ac6a",
|
||||||
"sessions": "5d5c677bddbd027e0b0c9f7a0dd95b66d9d95b4e130959f31fb955b926c2201c",
|
"sessions": "5d5c677bddbd027e0b0c9f7a0dd95b66d9d95b4e130959f31fb955b926c2201c",
|
||||||
"settings": "56f7c06f24eef317353582b0191aa9a5985f46ed755accf97e723ceb4bba4469",
|
"settings": "d949ecdd28a33eadafaa3a727e548b3466c5aa44b0a4bbf86cc49784704ff7f6",
|
||||||
"unread_entries": "e38f7ffce17dfad3151b08cd33771a2cefe8ca9db42df04fc98bd1d675dd6075",
|
"unread_entries": "e38f7ffce17dfad3151b08cd33771a2cefe8ca9db42df04fc98bd1d675dd6075",
|
||||||
"users": "d7ff52efc582bbad10504f4a04fa3adcc12d15890e45dff51cac281e0c446e45",
|
"users": "d7ff52efc582bbad10504f4a04fa3adcc12d15890e45dff51cac281e0c446e45",
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,17 @@
|
||||||
package ui // import "miniflux.app/ui"
|
package ui // import "miniflux.app/ui"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"miniflux.app/config"
|
"miniflux.app/config"
|
||||||
"miniflux.app/oauth2"
|
"miniflux.app/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getOAuth2Manager() *oauth2.Manager {
|
func getOAuth2Manager(ctx context.Context) *oauth2.Manager {
|
||||||
return oauth2.NewManager(
|
return oauth2.NewManager(
|
||||||
|
ctx,
|
||||||
config.Opts.OAuth2ClientID(),
|
config.Opts.OAuth2ClientID(),
|
||||||
config.Opts.OAuth2ClientSecret(),
|
config.Opts.OAuth2ClientSecret(),
|
||||||
config.Opts.OAuth2RedirectURL(),
|
config.Opts.OAuth2RedirectURL(),
|
||||||
|
config.Opts.OAuth2OidcDiscoveryEndpoint(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,14 +44,14 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authProvider, err := getOAuth2Manager().Provider(provider)
|
authProvider, err := getOAuth2Manager(r.Context()).Provider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "login"))
|
html.Redirect(w, r, route.Path(h.router, "login"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile, err := authProvider.GetProfile(code)
|
profile, err := authProvider.GetProfile(r.Context(), code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "login"))
|
html.Redirect(w, r, route.Path(h.router, "login"))
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (h *handler) oauth2Redirect(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authProvider, err := getOAuth2Manager().Provider(provider)
|
authProvider, err := getOAuth2Manager(r.Context()).Provider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "login"))
|
html.Redirect(w, r, route.Path(h.router, "login"))
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authProvider, err := getOAuth2Manager().Provider(provider)
|
authProvider, err := getOAuth2Manager(r.Context()).Provider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[OAuth2] %v", err)
|
logger.Error("[OAuth2] %v", err)
|
||||||
html.Redirect(w, r, route.Path(h.router, "settings"))
|
html.Redirect(w, r, route.Path(h.router, "settings"))
|
||||||
|
|
|
@ -12,10 +12,10 @@ var Stylesheets = map[string]string{
|
||||||
}
|
}
|
||||||
|
|
||||||
var StylesheetsChecksums = map[string]string{
|
var StylesheetsChecksums = map[string]string{
|
||||||
"dark_sans_serif": "8e97eef07e3b3485c91c5ff20a66097c9bd19c3a88276292c4abe005cd124129",
|
"dark_sans_serif": "1a35631e5a9122fe56fcdbc9518d841a65867fa8c8e4e542a267efe0293c7d64",
|
||||||
"dark_serif": "cad3534b9c462c420c8bb49a5826535ce5c4289a425190be18755f45271aabec",
|
"dark_serif": "3ca862a6da6bd54060be6961d8cd300ac28527232ad8cde6b80d5c2038bfee3c",
|
||||||
"light_sans_serif": "f7ae30334715023100d836c7475864b1ba1f3e785ace032904f8970c7818990f",
|
"light_sans_serif": "034a35ad21066e76befe6411ad527255945ef5de717d46119c47616e38506291",
|
||||||
"light_serif": "cfd236de9e7801e68d1717a205ac554ecf903d4434ec2bab2c591afab5ebd881",
|
"light_serif": "df4bdba6508b874aae4ebe27f487b6938b7ed906ba9a0f0eb9f75a6130197ba8",
|
||||||
"system_sans_serif": "ae41c90c530ddbad31d5839f5030cfd1651a52be89b54f1dc37b528c23912a83",
|
"system_sans_serif": "6ad4ebfc205825448866dd5ae97bddc6be0517f03d0c6360fd17266dddf84897",
|
||||||
"system_serif": "62e54111ede72f03710f1c81bccfa56a7b16d4bc740e98a1a4292345db343757",
|
"system_serif": "44d72d100c0121f9cb95aae70923acd34960595a114aa697b14d07e31f5434ca",
|
||||||
}
|
}
|
||||||
|
|
2
vendor/github.com/coreos/go-oidc/.gitignore
generated
vendored
Normal file
2
vendor/github.com/coreos/go-oidc/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/bin
|
||||||
|
/gopath
|
16
vendor/github.com/coreos/go-oidc/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/coreos/go-oidc/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- "1.12"
|
||||||
|
- "1.13"
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -v -t github.com/coreos/go-oidc/...
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get golang.org/x/lint/golint
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./test
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
71
vendor/github.com/coreos/go-oidc/CONTRIBUTING.md
generated
vendored
Normal file
71
vendor/github.com/coreos/go-oidc/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# How to Contribute
|
||||||
|
|
||||||
|
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
|
||||||
|
GitHub pull requests. This document outlines some of the conventions on
|
||||||
|
development workflow, commit message formatting, contact points and other
|
||||||
|
resources to make it easier to get your contribution accepted.
|
||||||
|
|
||||||
|
# Certificate of Origin
|
||||||
|
|
||||||
|
By contributing to this project you agree to the Developer Certificate of
|
||||||
|
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||||
|
simple statement that you, as a contributor, have the legal right to make the
|
||||||
|
contribution. See the [DCO](DCO) file for details.
|
||||||
|
|
||||||
|
# Email and Chat
|
||||||
|
|
||||||
|
The project currently uses the general CoreOS email list and IRC channel:
|
||||||
|
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
|
||||||
|
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
|
||||||
|
|
||||||
|
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
|
||||||
|
are very busy and read the mailing lists.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
- Fork the repository on GitHub
|
||||||
|
- Read the [README](README.md) for build and test instructions
|
||||||
|
- Play with the project, submit bugs, submit patches!
|
||||||
|
|
||||||
|
## Contribution Flow
|
||||||
|
|
||||||
|
This is a rough outline of what a contributor's workflow looks like:
|
||||||
|
|
||||||
|
- Create a topic branch from where you want to base your work (usually master).
|
||||||
|
- Make commits of logical units.
|
||||||
|
- Make sure your commit messages are in the proper format (see below).
|
||||||
|
- Push your changes to a topic branch in your fork of the repository.
|
||||||
|
- Make sure the tests pass, and add any new tests as appropriate.
|
||||||
|
- Submit a pull request to the original repository.
|
||||||
|
|
||||||
|
Thanks for your contributions!
|
||||||
|
|
||||||
|
### Format of the Commit Message
|
||||||
|
|
||||||
|
We follow a rough convention for commit messages that is designed to answer two
|
||||||
|
questions: what changed and why. The subject line should feature the what and
|
||||||
|
the body of the commit should describe the why.
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts: add the test-cluster command
|
||||||
|
|
||||||
|
this uses tmux to setup a test cluster that you can easily kill and
|
||||||
|
start for debugging.
|
||||||
|
|
||||||
|
Fixes #38
|
||||||
|
```
|
||||||
|
|
||||||
|
The format can be described more formally as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<subsystem>: <what changed>
|
||||||
|
<BLANK LINE>
|
||||||
|
<why this change was made>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first line is the subject and should be no longer than 70 characters, the
|
||||||
|
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||||
|
This allows the message to be easier to read on GitHub as well as in various
|
||||||
|
git tools.
|
36
vendor/github.com/coreos/go-oidc/DCO
generated
vendored
Normal file
36
vendor/github.com/coreos/go-oidc/DCO
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
202
vendor/github.com/coreos/go-oidc/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/go-oidc/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
3
vendor/github.com/coreos/go-oidc/MAINTAINERS
generated
vendored
Normal file
3
vendor/github.com/coreos/go-oidc/MAINTAINERS
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Eric Chiang <ericchiang@google.com> (@ericchiang)
|
||||||
|
Mike Danese <mikedanese@google.com> (@mikedanese)
|
||||||
|
Rithu Leena John <rjohn@redhat.com> (@rithujohn191)
|
5
vendor/github.com/coreos/go-oidc/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/go-oidc/NOTICE
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CoreOS Project
|
||||||
|
Copyright 2014 CoreOS, Inc
|
||||||
|
|
||||||
|
This product includes software developed at CoreOS, Inc.
|
||||||
|
(http://www.coreos.com/).
|
72
vendor/github.com/coreos/go-oidc/README.md
generated
vendored
Normal file
72
vendor/github.com/coreos/go-oidc/README.md
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
# go-oidc
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/coreos/go-oidc?status.svg)](https://godoc.org/github.com/coreos/go-oidc)
|
||||||
|
[![Build Status](https://travis-ci.org/coreos/go-oidc.png?branch=master)](https://travis-ci.org/coreos/go-oidc)
|
||||||
|
|
||||||
|
## OpenID Connect support for Go
|
||||||
|
|
||||||
|
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
|
||||||
|
|
||||||
|
```go
|
||||||
|
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure an OpenID Connect aware OAuth2 client.
|
||||||
|
oauth2Config := oauth2.Config{
|
||||||
|
ClientID: clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
|
||||||
|
// Discovery returns the OAuth2 endpoints.
|
||||||
|
Endpoint: provider.Endpoint(),
|
||||||
|
|
||||||
|
// "openid" is a required scope for OpenID Connect flows.
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
OAuth2 redirects are unchanged.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The on responses, the provider can be used to verify ID Tokens.
|
||||||
|
|
||||||
|
```go
|
||||||
|
var verifier = provider.Verifier(&oidc.Config{ClientID: clientID})
|
||||||
|
|
||||||
|
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Verify state and errors.
|
||||||
|
|
||||||
|
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the ID Token from OAuth2 token.
|
||||||
|
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
// handle missing token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and verify ID Token payload.
|
||||||
|
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract custom claims
|
||||||
|
var claims struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Verified bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
if err := idToken.Claims(&claims); err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
61
vendor/github.com/coreos/go-oidc/code-of-conduct.md
generated
vendored
Normal file
61
vendor/github.com/coreos/go-oidc/code-of-conduct.md
generated
vendored
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
## CoreOS Community Code of Conduct
|
||||||
|
|
||||||
|
### Contributor Code of Conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of this project, and in the interest of
|
||||||
|
fostering an open and welcoming community, we pledge to respect all people who
|
||||||
|
contribute through reporting issues, posting feature requests, updating
|
||||||
|
documentation, submitting pull requests or patches, and other activities.
|
||||||
|
|
||||||
|
We are committed to making participation in this project a harassment-free
|
||||||
|
experience for everyone, regardless of level of experience, gender, gender
|
||||||
|
identity and expression, sexual orientation, disability, personal appearance,
|
||||||
|
body size, race, ethnicity, age, religion, or nationality.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery
|
||||||
|
* Personal attacks
|
||||||
|
* Trolling or insulting/derogatory comments
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as physical or electronic addresses, without explicit permission
|
||||||
|
* Other unethical or unprofessional conduct.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct. By adopting this Code of Conduct,
|
||||||
|
project maintainers commit themselves to fairly and consistently applying these
|
||||||
|
principles to every aspect of managing this project. Project maintainers who do
|
||||||
|
not follow or enforce the Code of Conduct may be permanently removed from the
|
||||||
|
project team.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting a project maintainer, Brandon Philips
|
||||||
|
<brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the Contributor Covenant
|
||||||
|
(http://contributor-covenant.org), version 1.2.0, available at
|
||||||
|
http://contributor-covenant.org/version/1/2/0/
|
||||||
|
|
||||||
|
### CoreOS Events Code of Conduct
|
||||||
|
|
||||||
|
CoreOS events are working conferences intended for professional networking and
|
||||||
|
collaboration in the CoreOS community. Attendees are expected to behave
|
||||||
|
according to professional standards and in accordance with their employer’s
|
||||||
|
policies on appropriate workplace behavior.
|
||||||
|
|
||||||
|
While at CoreOS events or related social networking opportunities, attendees
|
||||||
|
should not engage in discriminatory or offensive speech or actions including
|
||||||
|
but not limited to gender, sexuality, race, age, disability, or religion.
|
||||||
|
Speakers should be especially aware of these concerns.
|
||||||
|
|
||||||
|
CoreOS does not condone any statements by speakers contrary to these standards.
|
||||||
|
CoreOS reserves the right to deny entrance and/or eject from an event (without
|
||||||
|
refund) any individual found to be engaging in discriminatory or offensive
|
||||||
|
speech or actions.
|
||||||
|
|
||||||
|
Please bring any concerns to the immediate attention of designated on-site
|
||||||
|
staff, Brandon Philips <brandon.philips@coreos.com>, and/or Rithu John <rithu.john@coreos.com>.
|
20
vendor/github.com/coreos/go-oidc/jose.go
generated
vendored
Normal file
20
vendor/github.com/coreos/go-oidc/jose.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// +build !golint
|
||||||
|
|
||||||
|
// Don't lint this file. We don't want to have to add a comment to each constant.
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
const (
|
||||||
|
// JOSE asymmetric signing algorithm values as defined by RFC 7518
|
||||||
|
//
|
||||||
|
// see: https://tools.ietf.org/html/rfc7518#section-3.1
|
||||||
|
RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
|
||||||
|
RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
|
||||||
|
RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
|
||||||
|
ES256 = "ES256" // ECDSA using P-256 and SHA-256
|
||||||
|
ES384 = "ES384" // ECDSA using P-384 and SHA-384
|
||||||
|
ES512 = "ES512" // ECDSA using P-521 and SHA-512
|
||||||
|
PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||||
|
PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||||
|
PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||||
|
)
|
228
vendor/github.com/coreos/go-oidc/jwks.go
generated
vendored
Normal file
228
vendor/github.com/coreos/go-oidc/jwks.go
generated
vendored
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pquerna/cachecontrol"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keysExpiryDelta is the allowed clock skew between a client and the OpenID Connect
|
||||||
|
// server.
|
||||||
|
//
|
||||||
|
// When keys expire, they are valid for this amount of time after.
|
||||||
|
//
|
||||||
|
// If the keys have not expired, and an ID Token claims it was signed by a key not in
|
||||||
|
// the cache, if and only if the keys expire in this amount of time, the keys will be
|
||||||
|
// updated.
|
||||||
|
const keysExpiryDelta = 30 * time.Second
|
||||||
|
|
||||||
|
// NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
|
||||||
|
// GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
|
||||||
|
// used by NewProvider using the URLs returned by OpenID Connect discovery, but is
|
||||||
|
// exposed for providers that don't support discovery or to prevent round trips to the
|
||||||
|
// discovery URL.
|
||||||
|
//
|
||||||
|
// The returned KeySet is a long lived verifier that caches keys based on cache-control
|
||||||
|
// headers. Reuse a common remote key set instead of creating new ones as needed.
|
||||||
|
//
|
||||||
|
// The behavior of the returned KeySet is undefined once the context is canceled.
|
||||||
|
func NewRemoteKeySet(ctx context.Context, jwksURL string) KeySet {
|
||||||
|
return newRemoteKeySet(ctx, jwksURL, time.Now)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRemoteKeySet(ctx context.Context, jwksURL string, now func() time.Time) *remoteKeySet {
|
||||||
|
if now == nil {
|
||||||
|
now = time.Now
|
||||||
|
}
|
||||||
|
return &remoteKeySet{jwksURL: jwksURL, ctx: ctx, now: now}
|
||||||
|
}
|
||||||
|
|
||||||
|
type remoteKeySet struct {
|
||||||
|
jwksURL string
|
||||||
|
ctx context.Context
|
||||||
|
now func() time.Time
|
||||||
|
|
||||||
|
// guard all other fields
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
// inflight suppresses parallel execution of updateKeys and allows
|
||||||
|
// multiple goroutines to wait for its result.
|
||||||
|
inflight *inflight
|
||||||
|
|
||||||
|
// A set of cached keys and their expiry.
|
||||||
|
cachedKeys []jose.JSONWebKey
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// inflight is used to wait on some in-flight request from multiple goroutines.
|
||||||
|
type inflight struct {
|
||||||
|
doneCh chan struct{}
|
||||||
|
|
||||||
|
keys []jose.JSONWebKey
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInflight() *inflight {
|
||||||
|
return &inflight{doneCh: make(chan struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait returns a channel that multiple goroutines can receive on. Once it returns
|
||||||
|
// a value, the inflight request is done and result() can be inspected.
|
||||||
|
func (i *inflight) wait() <-chan struct{} {
|
||||||
|
return i.doneCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// done can only be called by a single goroutine. It records the result of the
|
||||||
|
// inflight request and signals other goroutines that the result is safe to
|
||||||
|
// inspect.
|
||||||
|
func (i *inflight) done(keys []jose.JSONWebKey, err error) {
|
||||||
|
i.keys = keys
|
||||||
|
i.err = err
|
||||||
|
close(i.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// result cannot be called until the wait() channel has returned a value.
|
||||||
|
func (i *inflight) result() ([]jose.JSONWebKey, error) {
|
||||||
|
return i.keys, i.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
|
||||||
|
jws, err := jose.ParseSigned(jwt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
return r.verify(ctx, jws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) verify(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
|
||||||
|
// We don't support JWTs signed with multiple signatures.
|
||||||
|
keyID := ""
|
||||||
|
for _, sig := range jws.Signatures {
|
||||||
|
keyID = sig.Header.KeyID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, expiry := r.keysFromCache()
|
||||||
|
|
||||||
|
// Don't check expiry yet. This optimizes for when the provider is unavailable.
|
||||||
|
for _, key := range keys {
|
||||||
|
if keyID == "" || key.KeyID == keyID {
|
||||||
|
if payload, err := jws.Verify(&key); err == nil {
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.now().Add(keysExpiryDelta).After(expiry) {
|
||||||
|
// Keys haven't expired, don't refresh.
|
||||||
|
return nil, errors.New("failed to verify id token signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := r.keysFromRemote(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetching keys %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
if keyID == "" || key.KeyID == keyID {
|
||||||
|
if payload, err := jws.Verify(&key); err == nil {
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to verify id token signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) keysFromCache() (keys []jose.JSONWebKey, expiry time.Time) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
return r.cachedKeys, r.expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// keysFromRemote syncs the key set from the remote set, records the values in the
|
||||||
|
// cache, and returns the key set.
|
||||||
|
func (r *remoteKeySet) keysFromRemote(ctx context.Context) ([]jose.JSONWebKey, error) {
|
||||||
|
// Need to lock to inspect the inflight request field.
|
||||||
|
r.mu.Lock()
|
||||||
|
// If there's not a current inflight request, create one.
|
||||||
|
if r.inflight == nil {
|
||||||
|
r.inflight = newInflight()
|
||||||
|
|
||||||
|
// This goroutine has exclusive ownership over the current inflight
|
||||||
|
// request. It releases the resource by nil'ing the inflight field
|
||||||
|
// once the goroutine is done.
|
||||||
|
go func() {
|
||||||
|
// Sync keys and finish inflight when that's done.
|
||||||
|
keys, expiry, err := r.updateKeys()
|
||||||
|
|
||||||
|
r.inflight.done(keys, err)
|
||||||
|
|
||||||
|
// Lock to update the keys and indicate that there is no longer an
|
||||||
|
// inflight request.
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
r.cachedKeys = keys
|
||||||
|
r.expiry = expiry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free inflight so a different request can run.
|
||||||
|
r.inflight = nil
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
inflight := r.inflight
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-inflight.wait():
|
||||||
|
return inflight.result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *remoteKeySet) updateKeys() ([]jose.JSONWebKey, time.Time, error) {
|
||||||
|
req, err := http.NewRequest("GET", r.jwksURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: can't create request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := doRequest(r.ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("unable to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: get keys failed: %s %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keySet jose.JSONWebKeySet
|
||||||
|
err = unmarshalResp(resp, body, &keySet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("oidc: failed to decode keys: %v %s", err, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the server doesn't provide cache control headers, assume the
|
||||||
|
// keys expire immediately.
|
||||||
|
expiry := r.now()
|
||||||
|
|
||||||
|
_, e, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{})
|
||||||
|
if err == nil && e.After(expiry) {
|
||||||
|
expiry = e
|
||||||
|
}
|
||||||
|
return keySet.Keys, expiry, nil
|
||||||
|
}
|
409
vendor/github.com/coreos/go-oidc/oidc.go
generated
vendored
Normal file
409
vendor/github.com/coreos/go-oidc/oidc.go
generated
vendored
Normal file
|
@ -0,0 +1,409 @@
|
||||||
|
// Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
|
||||||
|
ScopeOpenID = "openid"
|
||||||
|
|
||||||
|
// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
|
||||||
|
// OAuth2 refresh tokens.
|
||||||
|
//
|
||||||
|
// Support for this scope differs between OpenID Connect providers. For instance
|
||||||
|
// Google rejects it, favoring appending "access_type=offline" as part of the
|
||||||
|
// authorization request instead.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||||
|
ScopeOfflineAccess = "offline_access"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoAtHash = errors.New("id token did not have an access token hash")
|
||||||
|
errInvalidAtHash = errors.New("access token hash does not match value in ID token")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientContext returns a new Context that carries the provided HTTP client.
|
||||||
|
//
|
||||||
|
// This method sets the same context key used by the golang.org/x/oauth2 package,
|
||||||
|
// so the returned context works for that package too.
|
||||||
|
//
|
||||||
|
// myClient := &http.Client{}
|
||||||
|
// ctx := oidc.ClientContext(parentContext, myClient)
|
||||||
|
//
|
||||||
|
// // This will use the custom client
|
||||||
|
// provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
|
||||||
|
//
|
||||||
|
func ClientContext(ctx context.Context, client *http.Client) context.Context {
|
||||||
|
return context.WithValue(ctx, oauth2.HTTPClient, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doRequest(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||||
|
client := http.DefaultClient
|
||||||
|
if c, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
|
||||||
|
client = c
|
||||||
|
}
|
||||||
|
return client.Do(req.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider represents an OpenID Connect server's configuration.
|
||||||
|
type Provider struct {
|
||||||
|
issuer string
|
||||||
|
authURL string
|
||||||
|
tokenURL string
|
||||||
|
userInfoURL string
|
||||||
|
algorithms []string
|
||||||
|
|
||||||
|
// Raw claims returned by the server.
|
||||||
|
rawClaims []byte
|
||||||
|
|
||||||
|
remoteKeySet KeySet
|
||||||
|
}
|
||||||
|
|
||||||
|
type cachedKeys struct {
|
||||||
|
keys []jose.JSONWebKey
|
||||||
|
expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type providerJSON struct {
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
AuthURL string `json:"authorization_endpoint"`
|
||||||
|
TokenURL string `json:"token_endpoint"`
|
||||||
|
JWKSURL string `json:"jwks_uri"`
|
||||||
|
UserInfoURL string `json:"userinfo_endpoint"`
|
||||||
|
Algorithms []string `json:"id_token_signing_alg_values_supported"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// supportedAlgorithms is a list of algorithms explicitly supported by this
|
||||||
|
// package. If a provider supports other algorithms, such as HS256 or none,
|
||||||
|
// those values won't be passed to the IDTokenVerifier.
|
||||||
|
var supportedAlgorithms = map[string]bool{
|
||||||
|
RS256: true,
|
||||||
|
RS384: true,
|
||||||
|
RS512: true,
|
||||||
|
ES256: true,
|
||||||
|
ES384: true,
|
||||||
|
ES512: true,
|
||||||
|
PS256: true,
|
||||||
|
PS384: true,
|
||||||
|
PS512: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProvider uses the OpenID Connect discovery mechanism to construct a Provider.
|
||||||
|
//
|
||||||
|
// The issuer is the URL identifier for the service. For example: "https://accounts.google.com"
|
||||||
|
// or "https://login.salesforce.com".
|
||||||
|
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
|
||||||
|
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
|
||||||
|
req, err := http.NewRequest("GET", wellKnown, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := doRequest(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var p providerJSON
|
||||||
|
err = unmarshalResp(resp, body, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Issuer != issuer {
|
||||||
|
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
|
||||||
|
}
|
||||||
|
var algs []string
|
||||||
|
for _, a := range p.Algorithms {
|
||||||
|
if supportedAlgorithms[a] {
|
||||||
|
algs = append(algs, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Provider{
|
||||||
|
issuer: p.Issuer,
|
||||||
|
authURL: p.AuthURL,
|
||||||
|
tokenURL: p.TokenURL,
|
||||||
|
userInfoURL: p.UserInfoURL,
|
||||||
|
algorithms: algs,
|
||||||
|
rawClaims: body,
|
||||||
|
remoteKeySet: NewRemoteKeySet(ctx, p.JWKSURL),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims unmarshals raw fields returned by the server during discovery.
|
||||||
|
//
|
||||||
|
// var claims struct {
|
||||||
|
// ScopesSupported []string `json:"scopes_supported"`
|
||||||
|
// ClaimsSupported []string `json:"claims_supported"`
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if err := provider.Claims(&claims); err != nil {
|
||||||
|
// // handle unmarshaling error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// For a list of fields defined by the OpenID Connect spec see:
|
||||||
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||||
|
func (p *Provider) Claims(v interface{}) error {
|
||||||
|
if p.rawClaims == nil {
|
||||||
|
return errors.New("oidc: claims not set")
|
||||||
|
}
|
||||||
|
return json.Unmarshal(p.rawClaims, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
|
||||||
|
func (p *Provider) Endpoint() oauth2.Endpoint {
|
||||||
|
return oauth2.Endpoint{AuthURL: p.authURL, TokenURL: p.tokenURL}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo represents the OpenID Connect userinfo claims.
|
||||||
|
type UserInfo struct {
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Profile string `json:"profile"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
|
||||||
|
claims []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims unmarshals the raw JSON object claims into the provided object.
|
||||||
|
func (u *UserInfo) Claims(v interface{}) error {
|
||||||
|
if u.claims == nil {
|
||||||
|
return errors.New("oidc: claims not set")
|
||||||
|
}
|
||||||
|
return json.Unmarshal(u.claims, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserInfo uses the token source to query the provider's user info endpoint.
|
||||||
|
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
|
||||||
|
if p.userInfoURL == "" {
|
||||||
|
return nil, errors.New("oidc: user info endpoint is not supported by this provider")
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.userInfoURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: create GET request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := tokenSource.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: get access token: %v", err)
|
||||||
|
}
|
||||||
|
token.SetAuthHeader(req)
|
||||||
|
|
||||||
|
resp, err := doRequest(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("%s: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo UserInfo
|
||||||
|
if err := json.Unmarshal(body, &userInfo); err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
|
||||||
|
}
|
||||||
|
userInfo.claims = body
|
||||||
|
return &userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDToken is an OpenID Connect extension that provides a predictable representation
|
||||||
|
// of an authorization event.
|
||||||
|
//
|
||||||
|
// The ID Token only holds fields OpenID Connect requires. To access additional
|
||||||
|
// claims returned by the server, use the Claims method.
|
||||||
|
type IDToken struct {
|
||||||
|
// The URL of the server which issued this token. OpenID Connect
|
||||||
|
// requires this value always be identical to the URL used for
|
||||||
|
// initial discovery.
|
||||||
|
//
|
||||||
|
// Note: Because of a known issue with Google Accounts' implementation
|
||||||
|
// this value may differ when using Google.
|
||||||
|
//
|
||||||
|
// See: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
|
||||||
|
Issuer string
|
||||||
|
|
||||||
|
// The client ID, or set of client IDs, that this token is issued for. For
|
||||||
|
// common uses, this is the client that initialized the auth flow.
|
||||||
|
//
|
||||||
|
// This package ensures the audience contains an expected value.
|
||||||
|
Audience []string
|
||||||
|
|
||||||
|
// A unique string which identifies the end user.
|
||||||
|
Subject string
|
||||||
|
|
||||||
|
// Expiry of the token. Ths package will not process tokens that have
|
||||||
|
// expired unless that validation is explicitly turned off.
|
||||||
|
Expiry time.Time
|
||||||
|
// When the token was issued by the provider.
|
||||||
|
IssuedAt time.Time
|
||||||
|
|
||||||
|
// Initial nonce provided during the authentication redirect.
|
||||||
|
//
|
||||||
|
// This package does NOT provided verification on the value of this field
|
||||||
|
// and it's the user's responsibility to ensure it contains a valid value.
|
||||||
|
Nonce string
|
||||||
|
|
||||||
|
// at_hash claim, if set in the ID token. Callers can verify an access token
|
||||||
|
// that corresponds to the ID token using the VerifyAccessToken method.
|
||||||
|
AccessTokenHash string
|
||||||
|
|
||||||
|
// signature algorithm used for ID token, needed to compute a verification hash of an
|
||||||
|
// access token
|
||||||
|
sigAlgorithm string
|
||||||
|
|
||||||
|
// Raw payload of the id_token.
|
||||||
|
claims []byte
|
||||||
|
|
||||||
|
// Map of distributed claim names to claim sources
|
||||||
|
distributedClaims map[string]claimSource
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
|
||||||
|
//
|
||||||
|
// idToken, err := idTokenVerifier.Verify(rawIDToken)
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
// var claims struct {
|
||||||
|
// Email string `json:"email"`
|
||||||
|
// EmailVerified bool `json:"email_verified"`
|
||||||
|
// }
|
||||||
|
// if err := idToken.Claims(&claims); err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func (i *IDToken) Claims(v interface{}) error {
|
||||||
|
if i.claims == nil {
|
||||||
|
return errors.New("oidc: claims not set")
|
||||||
|
}
|
||||||
|
return json.Unmarshal(i.claims, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyAccessToken verifies that the hash of the access token that corresponds to the iD token
|
||||||
|
// matches the hash in the id token. It returns an error if the hashes don't match.
|
||||||
|
// It is the caller's responsibility to ensure that the optional access token hash is present for the ID token
|
||||||
|
// before calling this method. See https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||||
|
func (i *IDToken) VerifyAccessToken(accessToken string) error {
|
||||||
|
if i.AccessTokenHash == "" {
|
||||||
|
return errNoAtHash
|
||||||
|
}
|
||||||
|
var h hash.Hash
|
||||||
|
switch i.sigAlgorithm {
|
||||||
|
case RS256, ES256, PS256:
|
||||||
|
h = sha256.New()
|
||||||
|
case RS384, ES384, PS384:
|
||||||
|
h = sha512.New384()
|
||||||
|
case RS512, ES512, PS512:
|
||||||
|
h = sha512.New()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("oidc: unsupported signing algorithm %q", i.sigAlgorithm)
|
||||||
|
}
|
||||||
|
h.Write([]byte(accessToken)) // hash documents that Write will never return an error
|
||||||
|
sum := h.Sum(nil)[:h.Size()/2]
|
||||||
|
actual := base64.RawURLEncoding.EncodeToString(sum)
|
||||||
|
if actual != i.AccessTokenHash {
|
||||||
|
return errInvalidAtHash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type idToken struct {
|
||||||
|
Issuer string `json:"iss"`
|
||||||
|
Subject string `json:"sub"`
|
||||||
|
Audience audience `json:"aud"`
|
||||||
|
Expiry jsonTime `json:"exp"`
|
||||||
|
IssuedAt jsonTime `json:"iat"`
|
||||||
|
NotBefore *jsonTime `json:"nbf"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
AtHash string `json:"at_hash"`
|
||||||
|
ClaimNames map[string]string `json:"_claim_names"`
|
||||||
|
ClaimSources map[string]claimSource `json:"_claim_sources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type claimSource struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type audience []string
|
||||||
|
|
||||||
|
func (a *audience) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if json.Unmarshal(b, &s) == nil {
|
||||||
|
*a = audience{s}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var auds []string
|
||||||
|
if err := json.Unmarshal(b, &auds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*a = audience(auds)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonTime time.Time
|
||||||
|
|
||||||
|
func (j *jsonTime) UnmarshalJSON(b []byte) error {
|
||||||
|
var n json.Number
|
||||||
|
if err := json.Unmarshal(b, &n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var unix int64
|
||||||
|
|
||||||
|
if t, err := n.Int64(); err == nil {
|
||||||
|
unix = t
|
||||||
|
} else {
|
||||||
|
f, err := n.Float64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unix = int64(f)
|
||||||
|
}
|
||||||
|
*j = jsonTime(time.Unix(unix, 0))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalResp(r *http.Response, body []byte, v interface{}) error {
|
||||||
|
err := json.Unmarshal(body, &v)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ct := r.Header.Get("Content-Type")
|
||||||
|
mediaType, _, parseErr := mime.ParseMediaType(ct)
|
||||||
|
if parseErr == nil && mediaType == "application/json" {
|
||||||
|
return fmt.Errorf("got Content-Type = application/json, but could not unmarshal as JSON: %v", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("expected Content-Type = application/json, got %q: %v", ct, err)
|
||||||
|
}
|
16
vendor/github.com/coreos/go-oidc/test
generated
vendored
Normal file
16
vendor/github.com/coreos/go-oidc/test
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Filter out any files with a !golint build tag.
|
||||||
|
LINTABLE=$( go list -tags=golint -f '
|
||||||
|
{{- range $i, $file := .GoFiles -}}
|
||||||
|
{{ $file }} {{ end }}
|
||||||
|
{{ range $i, $file := .TestGoFiles -}}
|
||||||
|
{{ $file }} {{ end }}' github.com/coreos/go-oidc )
|
||||||
|
|
||||||
|
go test -v -i -race github.com/coreos/go-oidc/...
|
||||||
|
go test -v -race github.com/coreos/go-oidc/...
|
||||||
|
golint -set_exit_status $LINTABLE
|
||||||
|
go vet github.com/coreos/go-oidc/...
|
||||||
|
go build -v ./example/...
|
336
vendor/github.com/coreos/go-oidc/verify.go
generated
vendored
Normal file
336
vendor/github.com/coreos/go-oidc/verify.go
generated
vendored
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
issuerGoogleAccounts = "https://accounts.google.com"
|
||||||
|
issuerGoogleAccountsNoScheme = "accounts.google.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeySet is a set of publc JSON Web Keys that can be used to validate the signature
|
||||||
|
// of JSON web tokens. This is expected to be backed by a remote key set through
|
||||||
|
// provider metadata discovery or an in-memory set of keys delivered out-of-band.
|
||||||
|
type KeySet interface {
|
||||||
|
// VerifySignature parses the JSON web token, verifies the signature, and returns
|
||||||
|
// the raw payload. Header and claim fields are validated by other parts of the
|
||||||
|
// package. For example, the KeySet does not need to check values such as signature
|
||||||
|
// algorithm, issuer, and audience since the IDTokenVerifier validates these values
|
||||||
|
// independently.
|
||||||
|
//
|
||||||
|
// If VerifySignature makes HTTP requests to verify the token, it's expected to
|
||||||
|
// use any HTTP client associated with the context through ClientContext.
|
||||||
|
VerifySignature(ctx context.Context, jwt string) (payload []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDTokenVerifier provides verification for ID Tokens.
|
||||||
|
type IDTokenVerifier struct {
|
||||||
|
keySet KeySet
|
||||||
|
config *Config
|
||||||
|
issuer string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVerifier returns a verifier manually constructed from a key set and issuer URL.
|
||||||
|
//
|
||||||
|
// It's easier to use provider discovery to construct an IDTokenVerifier than creating
|
||||||
|
// one directly. This method is intended to be used with provider that don't support
|
||||||
|
// metadata discovery, or avoiding round trips when the key set URL is already known.
|
||||||
|
//
|
||||||
|
// This constructor can be used to create a verifier directly using the issuer URL and
|
||||||
|
// JSON Web Key Set URL without using discovery:
|
||||||
|
//
|
||||||
|
// keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
|
||||||
|
// verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
|
||||||
|
//
|
||||||
|
// Since KeySet is an interface, this constructor can also be used to supply custom
|
||||||
|
// public key sources. For example, if a user wanted to supply public keys out-of-band
|
||||||
|
// and hold them statically in-memory:
|
||||||
|
//
|
||||||
|
// // Custom KeySet implementation.
|
||||||
|
// keySet := newStatisKeySet(publicKeys...)
|
||||||
|
//
|
||||||
|
// // Verifier uses the custom KeySet implementation.
|
||||||
|
// verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
|
||||||
|
//
|
||||||
|
func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
|
||||||
|
return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration for an IDTokenVerifier.
|
||||||
|
type Config struct {
|
||||||
|
// Expected audience of the token. For a majority of the cases this is expected to be
|
||||||
|
// the ID of the client that initialized the login flow. It may occasionally differ if
|
||||||
|
// the provider supports the authorizing party (azp) claim.
|
||||||
|
//
|
||||||
|
// If not provided, users must explicitly set SkipClientIDCheck.
|
||||||
|
ClientID string
|
||||||
|
// If specified, only this set of algorithms may be used to sign the JWT.
|
||||||
|
//
|
||||||
|
// If the IDTokenVerifier is created from a provider with (*Provider).Verifier, this
|
||||||
|
// defaults to the set of algorithms the provider supports. Otherwise this values
|
||||||
|
// defaults to RS256.
|
||||||
|
SupportedSigningAlgs []string
|
||||||
|
|
||||||
|
// If true, no ClientID check performed. Must be true if ClientID field is empty.
|
||||||
|
SkipClientIDCheck bool
|
||||||
|
// If true, token expiry is not checked.
|
||||||
|
SkipExpiryCheck bool
|
||||||
|
|
||||||
|
// SkipIssuerCheck is intended for specialized cases where the the caller wishes to
|
||||||
|
// defer issuer validation. When enabled, callers MUST independently verify the Token's
|
||||||
|
// Issuer is a known good value.
|
||||||
|
//
|
||||||
|
// Mismatched issuers often indicate client mis-configuration. If mismatches are
|
||||||
|
// unexpected, evaluate if the provided issuer URL is incorrect instead of enabling
|
||||||
|
// this option.
|
||||||
|
SkipIssuerCheck bool
|
||||||
|
|
||||||
|
// Time function to check Token expiry. Defaults to time.Now
|
||||||
|
Now func() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
|
||||||
|
//
|
||||||
|
// The returned IDTokenVerifier is tied to the Provider's context and its behavior is
|
||||||
|
// undefined once the Provider's context is canceled.
|
||||||
|
func (p *Provider) Verifier(config *Config) *IDTokenVerifier {
|
||||||
|
if len(config.SupportedSigningAlgs) == 0 && len(p.algorithms) > 0 {
|
||||||
|
// Make a copy so we don't modify the config values.
|
||||||
|
cp := &Config{}
|
||||||
|
*cp = *config
|
||||||
|
cp.SupportedSigningAlgs = p.algorithms
|
||||||
|
config = cp
|
||||||
|
}
|
||||||
|
return NewVerifier(p.issuer, p.remoteKeySet, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseJWT(p string) ([]byte, error) {
|
||||||
|
parts := strings.Split(p, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
|
||||||
|
}
|
||||||
|
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(sli []string, ele string) bool {
|
||||||
|
for _, s := range sli {
|
||||||
|
if s == ele {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the Claims from the distributed JWT token
|
||||||
|
func resolveDistributedClaim(ctx context.Context, verifier *IDTokenVerifier, src claimSource) ([]byte, error) {
|
||||||
|
req, err := http.NewRequest("GET", src.Endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed request: %v", err)
|
||||||
|
}
|
||||||
|
if src.AccessToken != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+src.AccessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := doRequest(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: Request to endpoint failed: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("oidc: request failed: %v", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := verifier.Verify(ctx, string(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("malformed response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClaim(raw []byte, name string, v interface{}) error {
|
||||||
|
var parsed map[string]json.RawMessage
|
||||||
|
if err := json.Unmarshal(raw, &parsed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := parsed[name]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("claim doesn't exist: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal([]byte(val), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify parses a raw ID Token, verifies it's been signed by the provider, preforms
|
||||||
|
// any additional checks depending on the Config, and returns the payload.
|
||||||
|
//
|
||||||
|
// Verify does NOT do nonce validation, which is the callers responsibility.
|
||||||
|
//
|
||||||
|
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||||
|
//
|
||||||
|
// oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Extract the ID Token from oauth2 token.
|
||||||
|
// rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||||
|
// if !ok {
|
||||||
|
// // handle error
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// token, err := verifier.Verify(ctx, rawIDToken)
|
||||||
|
//
|
||||||
|
func (v *IDTokenVerifier) Verify(ctx context.Context, rawIDToken string) (*IDToken, error) {
|
||||||
|
jws, err := jose.ParseSigned(rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw out tokens with invalid claims before trying to verify the token. This lets
|
||||||
|
// us do cheap checks before possibly re-syncing keys.
|
||||||
|
payload, err := parseJWT(rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
|
||||||
|
}
|
||||||
|
var token idToken
|
||||||
|
if err := json.Unmarshal(payload, &token); err != nil {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
distributedClaims := make(map[string]claimSource)
|
||||||
|
|
||||||
|
//step through the token to map claim names to claim sources"
|
||||||
|
for cn, src := range token.ClaimNames {
|
||||||
|
if src == "" {
|
||||||
|
return nil, fmt.Errorf("oidc: failed to obtain source from claim name")
|
||||||
|
}
|
||||||
|
s, ok := token.ClaimSources[src]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("oidc: source does not exist")
|
||||||
|
}
|
||||||
|
distributedClaims[cn] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &IDToken{
|
||||||
|
Issuer: token.Issuer,
|
||||||
|
Subject: token.Subject,
|
||||||
|
Audience: []string(token.Audience),
|
||||||
|
Expiry: time.Time(token.Expiry),
|
||||||
|
IssuedAt: time.Time(token.IssuedAt),
|
||||||
|
Nonce: token.Nonce,
|
||||||
|
AccessTokenHash: token.AtHash,
|
||||||
|
claims: payload,
|
||||||
|
distributedClaims: distributedClaims,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check issuer.
|
||||||
|
if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
|
||||||
|
// Google sometimes returns "accounts.google.com" as the issuer claim instead of
|
||||||
|
// the required "https://accounts.google.com". Detect this case and allow it only
|
||||||
|
// for Google.
|
||||||
|
//
|
||||||
|
// We will not add hooks to let other providers go off spec like this.
|
||||||
|
if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
|
||||||
|
return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a client ID has been provided, make sure it's part of the audience. SkipClientIDCheck must be true if ClientID is empty.
|
||||||
|
//
|
||||||
|
// This check DOES NOT ensure that the ClientID is the party to which the ID Token was issued (i.e. Authorized party).
|
||||||
|
if !v.config.SkipClientIDCheck {
|
||||||
|
if v.config.ClientID != "" {
|
||||||
|
if !contains(t.Audience, v.config.ClientID) {
|
||||||
|
return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("oidc: invalid configuration, clientID must be provided or SkipClientIDCheck must be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a SkipExpiryCheck is false, make sure token is not expired.
|
||||||
|
if !v.config.SkipExpiryCheck {
|
||||||
|
now := time.Now
|
||||||
|
if v.config.Now != nil {
|
||||||
|
now = v.config.Now
|
||||||
|
}
|
||||||
|
nowTime := now()
|
||||||
|
|
||||||
|
if t.Expiry.Before(nowTime) {
|
||||||
|
return nil, fmt.Errorf("oidc: token is expired (Token Expiry: %v)", t.Expiry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nbf claim is provided in token, ensure that it is indeed in the past.
|
||||||
|
if token.NotBefore != nil {
|
||||||
|
nbfTime := time.Time(*token.NotBefore)
|
||||||
|
leeway := 1 * time.Minute
|
||||||
|
|
||||||
|
if nowTime.Add(leeway).Before(nbfTime) {
|
||||||
|
return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(jws.Signatures) {
|
||||||
|
case 0:
|
||||||
|
return nil, fmt.Errorf("oidc: id token not signed")
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := jws.Signatures[0]
|
||||||
|
supportedSigAlgs := v.config.SupportedSigningAlgs
|
||||||
|
if len(supportedSigAlgs) == 0 {
|
||||||
|
supportedSigAlgs = []string{RS256}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contains(supportedSigAlgs, sig.Header.Algorithm) {
|
||||||
|
return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.sigAlgorithm = sig.Header.Algorithm
|
||||||
|
|
||||||
|
gotPayload, err := v.keySet.VerifySignature(ctx, rawIDToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to verify signature: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the payload returned by the square actually matches the payload parsed earlier.
|
||||||
|
if !bytes.Equal(gotPayload, payload) {
|
||||||
|
return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nonce returns an auth code option which requires the ID Token created by the
|
||||||
|
// OpenID Connect provider to contain the specified nonce.
|
||||||
|
func Nonce(nonce string) oauth2.AuthCodeOption {
|
||||||
|
return oauth2.SetAuthURLParam("nonce", nonce)
|
||||||
|
}
|
10
vendor/github.com/pquerna/cachecontrol/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/pquerna/cachecontrol/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d -v ./...
|
||||||
|
- go get -u github.com/stretchr/testify/require
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.7
|
||||||
|
- 1.8
|
||||||
|
- tip
|
202
vendor/github.com/pquerna/cachecontrol/LICENSE
generated
vendored
Normal file
202
vendor/github.com/pquerna/cachecontrol/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
107
vendor/github.com/pquerna/cachecontrol/README.md
generated
vendored
Normal file
107
vendor/github.com/pquerna/cachecontrol/README.md
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# cachecontrol: HTTP Caching Parser and Interpretation
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/pquerna/cachecontrol?status.svg)](https://godoc.org/github.com/pquerna/cachecontrol)[![Build Status](https://travis-ci.org/pquerna/cachecontrol.svg?branch=master)](https://travis-ci.org/pquerna/cachecontrol)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`cachecontrol` implements [RFC 7234](http://tools.ietf.org/html/rfc7234) __Hypertext Transfer Protocol (HTTP/1.1): Caching__. It does this by parsing the `Cache-Control` and other headers, providing information about requests and responses -- but `cachecontrol` does not implement an actual cache backend, just the control plane to make decisions about if a particular response is cachable.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
`cachecontrol.CachableResponse` returns an array of [reasons](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Reason) why a response should not be cached and when it expires. In the case that `len(reasons) == 0`, the response is cachable according to the RFC. However, some people want non-compliant caches for various business use cases, so each reason is specifically named, so if your cache wants to cache `POST` requests, it can easily do that, but still be RFC compliant in other situations.
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
|
||||||
|
## Can you cache Example.com?
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pquerna/cachecontrol"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||||
|
|
||||||
|
res, _ := http.DefaultClient.Do(req)
|
||||||
|
_, _ = ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
reasons, expires, _ := cachecontrol.CachableResponse(req, res, cachecontrol.Options{})
|
||||||
|
|
||||||
|
fmt.Println("Reasons to not cache: ", reasons)
|
||||||
|
fmt.Println("Expiration: ", expires.String())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Can I use this in a high performance caching server?
|
||||||
|
|
||||||
|
`cachecontrol` is divided into two packages: `cachecontrol` with a high level API, and a lower level `cacheobject` package. Use [Object](https://godoc.org/github.com/pquerna/cachecontrol/cacheobject#Object) in a high performance use case where you have previously parsed headers containing dates or would like to avoid memory allocations.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pquerna/cachecontrol/cacheobject"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
req, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||||
|
|
||||||
|
res, _ := http.DefaultClient.Do(req)
|
||||||
|
_, _ = ioutil.ReadAll(res.Body)
|
||||||
|
|
||||||
|
reqDir, _ := cacheobject.ParseRequestCacheControl(req.Header.Get("Cache-Control"))
|
||||||
|
|
||||||
|
resDir, _ := cacheobject.ParseResponseCacheControl(res.Header.Get("Cache-Control"))
|
||||||
|
expiresHeader, _ := http.ParseTime(res.Header.Get("Expires"))
|
||||||
|
dateHeader, _ := http.ParseTime(res.Header.Get("Date"))
|
||||||
|
lastModifiedHeader, _ := http.ParseTime(res.Header.Get("Last-Modified"))
|
||||||
|
|
||||||
|
obj := cacheobject.Object{
|
||||||
|
RespDirectives: resDir,
|
||||||
|
RespHeaders: res.Header,
|
||||||
|
RespStatusCode: res.StatusCode,
|
||||||
|
RespExpiresHeader: expiresHeader,
|
||||||
|
RespDateHeader: dateHeader,
|
||||||
|
RespLastModifiedHeader: lastModifiedHeader,
|
||||||
|
|
||||||
|
ReqDirectives: reqDir,
|
||||||
|
ReqHeaders: req.Header,
|
||||||
|
ReqMethod: req.Method,
|
||||||
|
|
||||||
|
NowUTC: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
rv := cacheobject.ObjectResults{}
|
||||||
|
|
||||||
|
cacheobject.CachableObject(&obj, &rv)
|
||||||
|
cacheobject.ExpirationObject(&obj, &rv)
|
||||||
|
|
||||||
|
fmt.Println("Errors: ", rv.OutErr)
|
||||||
|
fmt.Println("Reasons to not cache: ", rv.OutReasons)
|
||||||
|
fmt.Println("Warning headers to add: ", rv.OutWarnings)
|
||||||
|
fmt.Println("Expiration: ", rv.OutExpirationTime.String())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Improvements, bugs, adding features, and taking cachecontrol new directions!
|
||||||
|
|
||||||
|
Please [open issues in Github](https://github.com/pquerna/cachecontrol/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :)
|
||||||
|
|
||||||
|
# Credits
|
||||||
|
|
||||||
|
`cachecontrol` has recieved significant contributions from:
|
||||||
|
|
||||||
|
* [Paul Querna](https://github.com/pquerna)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`cachecontrol` is licensed under the [Apache License, Version 2.0](./LICENSE)
|
48
vendor/github.com/pquerna/cachecontrol/api.go
generated
vendored
Normal file
48
vendor/github.com/pquerna/cachecontrol/api.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cachecontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pquerna/cachecontrol/cacheobject"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// Set to True for a prviate cache, which is not shared amoung users (eg, in a browser)
|
||||||
|
// Set to False for a "shared" cache, which is more common in a server context.
|
||||||
|
PrivateCache bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an HTTP Request, the future Status Code, and an ResponseWriter,
|
||||||
|
// determine the possible reasons a response SHOULD NOT be cached.
|
||||||
|
func CachableResponseWriter(req *http.Request,
|
||||||
|
statusCode int,
|
||||||
|
resp http.ResponseWriter,
|
||||||
|
opts Options) ([]cacheobject.Reason, time.Time, error) {
|
||||||
|
return cacheobject.UsingRequestResponse(req, statusCode, resp.Header(), opts.PrivateCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an HTTP Request and Response, determine the possible reasons a response SHOULD NOT
|
||||||
|
// be cached.
|
||||||
|
func CachableResponse(req *http.Request,
|
||||||
|
resp *http.Response,
|
||||||
|
opts Options) ([]cacheobject.Reason, time.Time, error) {
|
||||||
|
return cacheobject.UsingRequestResponse(req, resp.StatusCode, resp.Header, opts.PrivateCache)
|
||||||
|
}
|
546
vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go
generated
vendored
Normal file
546
vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go
generated
vendored
Normal file
|
@ -0,0 +1,546 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(pquerna): add extensions from here: http://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrQuoteMismatch = errors.New("Missing closing quote")
|
||||||
|
ErrMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `max-age`")
|
||||||
|
ErrSMaxAgeDeltaSeconds = errors.New("Failed to parse delta-seconds in `s-maxage`")
|
||||||
|
ErrMaxStaleDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
|
||||||
|
ErrMinFreshDeltaSeconds = errors.New("Failed to parse delta-seconds in `min-fresh`")
|
||||||
|
ErrNoCacheNoArgs = errors.New("Unexpected argument to `no-cache`")
|
||||||
|
ErrNoStoreNoArgs = errors.New("Unexpected argument to `no-store`")
|
||||||
|
ErrNoTransformNoArgs = errors.New("Unexpected argument to `no-transform`")
|
||||||
|
ErrOnlyIfCachedNoArgs = errors.New("Unexpected argument to `only-if-cached`")
|
||||||
|
ErrMustRevalidateNoArgs = errors.New("Unexpected argument to `must-revalidate`")
|
||||||
|
ErrPublicNoArgs = errors.New("Unexpected argument to `public`")
|
||||||
|
ErrProxyRevalidateNoArgs = errors.New("Unexpected argument to `proxy-revalidate`")
|
||||||
|
// Experimental
|
||||||
|
ErrImmutableNoArgs = errors.New("Unexpected argument to `immutable`")
|
||||||
|
ErrStaleIfErrorDeltaSeconds = errors.New("Failed to parse delta-seconds in `stale-if-error`")
|
||||||
|
ErrStaleWhileRevalidateDeltaSeconds = errors.New("Failed to parse delta-seconds in `stale-while-revalidate`")
|
||||||
|
)
|
||||||
|
|
||||||
|
func whitespace(b byte) bool {
|
||||||
|
if b == '\t' || b == ' ' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(value string, cd cacheDirective) error {
|
||||||
|
var err error = nil
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
for i < len(value) && err == nil {
|
||||||
|
// eat leading whitespace or commas
|
||||||
|
if whitespace(value[i]) || value[i] == ',' {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
j := i + 1
|
||||||
|
|
||||||
|
for j < len(value) {
|
||||||
|
if !isToken(value[j]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
|
||||||
|
token := strings.ToLower(value[i:j])
|
||||||
|
tokenHasFields := hasFieldNames(token)
|
||||||
|
/*
|
||||||
|
println("GOT TOKEN:")
|
||||||
|
println(" i -> ", i)
|
||||||
|
println(" j -> ", j)
|
||||||
|
println(" token -> ", token)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if j+1 < len(value) && value[j] == '=' {
|
||||||
|
k := j + 1
|
||||||
|
// minimum size two bytes of "", but we let httpUnquote handle it.
|
||||||
|
if k < len(value) && value[k] == '"' {
|
||||||
|
eaten, result := httpUnquote(value[k:])
|
||||||
|
if eaten == -1 {
|
||||||
|
return ErrQuoteMismatch
|
||||||
|
}
|
||||||
|
i = k + eaten
|
||||||
|
|
||||||
|
err = cd.addPair(token, result)
|
||||||
|
} else {
|
||||||
|
z := k
|
||||||
|
for z < len(value) {
|
||||||
|
if tokenHasFields {
|
||||||
|
if whitespace(value[z]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if whitespace(value[z]) || value[z] == ',' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
z++
|
||||||
|
}
|
||||||
|
i = z
|
||||||
|
|
||||||
|
result := value[k:z]
|
||||||
|
if result != "" && result[len(result)-1] == ',' {
|
||||||
|
result = result[:len(result)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cd.addPair(token, result)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if token != "," {
|
||||||
|
err = cd.addToken(token)
|
||||||
|
}
|
||||||
|
i = j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeltaSeconds specifies a non-negative integer, representing
|
||||||
|
// time in seconds: http://tools.ietf.org/html/rfc7234#section-1.2.1
|
||||||
|
//
|
||||||
|
// When set to -1, this means unset.
|
||||||
|
//
|
||||||
|
type DeltaSeconds int32
|
||||||
|
|
||||||
|
// Parser for delta-seconds, a uint31, more or less:
|
||||||
|
// http://tools.ietf.org/html/rfc7234#section-1.2.1
|
||||||
|
func parseDeltaSeconds(v string) (DeltaSeconds, error) {
|
||||||
|
n, err := strconv.ParseUint(v, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
if numError, ok := err.(*strconv.NumError); ok {
|
||||||
|
if numError.Err == strconv.ErrRange {
|
||||||
|
return DeltaSeconds(math.MaxInt32), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DeltaSeconds(-1), err
|
||||||
|
} else {
|
||||||
|
if n > math.MaxInt32 {
|
||||||
|
return DeltaSeconds(math.MaxInt32), nil
|
||||||
|
} else {
|
||||||
|
return DeltaSeconds(n), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields present in a header.
|
||||||
|
type FieldNames map[string]bool
|
||||||
|
|
||||||
|
// internal interface for shared methods of RequestCacheDirectives and ResponseCacheDirectives
|
||||||
|
type cacheDirective interface {
|
||||||
|
addToken(s string) error
|
||||||
|
addPair(s string, v string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersentation of possible request directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.1
|
||||||
|
//
|
||||||
|
// Note: Many fields will be `nil` in practice.
|
||||||
|
//
|
||||||
|
type RequestCacheDirectives struct {
|
||||||
|
|
||||||
|
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.1
|
||||||
|
//
|
||||||
|
// The "max-age" request directive indicates that the client is
|
||||||
|
// unwilling to accept a response whose age is greater than the
|
||||||
|
// specified number of seconds. Unless the max-stale request directive
|
||||||
|
// is also present, the client is not willing to accept a stale
|
||||||
|
// response.
|
||||||
|
MaxAge DeltaSeconds
|
||||||
|
|
||||||
|
// max-stale(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.2
|
||||||
|
//
|
||||||
|
// The "max-stale" request directive indicates that the client is
|
||||||
|
// willing to accept a response that has exceeded its freshness
|
||||||
|
// lifetime. If max-stale is assigned a value, then the client is
|
||||||
|
// willing to accept a response that has exceeded its freshness lifetime
|
||||||
|
// by no more than the specified number of seconds. If no value is
|
||||||
|
// assigned to max-stale, then the client is willing to accept a stale
|
||||||
|
// response of any age.
|
||||||
|
MaxStale DeltaSeconds
|
||||||
|
|
||||||
|
// min-fresh(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.1.3
|
||||||
|
//
|
||||||
|
// The "min-fresh" request directive indicates that the client is
|
||||||
|
// willing to accept a response whose freshness lifetime is no less than
|
||||||
|
// its current age plus the specified time in seconds. That is, the
|
||||||
|
// client wants a response that will still be fresh for at least the
|
||||||
|
// specified number of seconds.
|
||||||
|
MinFresh DeltaSeconds
|
||||||
|
|
||||||
|
// no-cache(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.4
|
||||||
|
//
|
||||||
|
// The "no-cache" request directive indicates that a cache MUST NOT use
|
||||||
|
// a stored response to satisfy the request without successful
|
||||||
|
// validation on the origin server.
|
||||||
|
NoCache bool
|
||||||
|
|
||||||
|
// no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.5
|
||||||
|
//
|
||||||
|
// The "no-store" request directive indicates that a cache MUST NOT
|
||||||
|
// store any part of either this request or any response to it. This
|
||||||
|
// directive applies to both private and shared caches.
|
||||||
|
NoStore bool
|
||||||
|
|
||||||
|
// no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.6
|
||||||
|
//
|
||||||
|
// The "no-transform" request directive indicates that an intermediary
|
||||||
|
// (whether or not it implements a cache) MUST NOT transform the
|
||||||
|
// payload, as defined in Section 5.7.2 of RFC7230.
|
||||||
|
NoTransform bool
|
||||||
|
|
||||||
|
// only-if-cached(bool): http://tools.ietf.org/html/rfc7234#section-5.2.1.7
|
||||||
|
//
|
||||||
|
// The "only-if-cached" request directive indicates that the client only
|
||||||
|
// wishes to obtain a stored response.
|
||||||
|
OnlyIfCached bool
|
||||||
|
|
||||||
|
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
|
||||||
|
//
|
||||||
|
// The Cache-Control header field can be extended through the use of one
|
||||||
|
// or more cache-extension tokens, each with an optional value. A cache
|
||||||
|
// MUST ignore unrecognized cache directives.
|
||||||
|
Extensions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *RequestCacheDirectives) addToken(token string) error {
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "max-age":
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
case "max-stale":
|
||||||
|
err = ErrMaxStaleDeltaSeconds
|
||||||
|
case "min-fresh":
|
||||||
|
err = ErrMinFreshDeltaSeconds
|
||||||
|
case "no-cache":
|
||||||
|
cd.NoCache = true
|
||||||
|
case "no-store":
|
||||||
|
cd.NoStore = true
|
||||||
|
case "no-transform":
|
||||||
|
cd.NoTransform = true
|
||||||
|
case "only-if-cached":
|
||||||
|
cd.OnlyIfCached = true
|
||||||
|
default:
|
||||||
|
cd.Extensions = append(cd.Extensions, token)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *RequestCacheDirectives) addPair(token string, v string) error {
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "max-age":
|
||||||
|
cd.MaxAge, err = parseDeltaSeconds(v)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
}
|
||||||
|
case "max-stale":
|
||||||
|
cd.MaxStale, err = parseDeltaSeconds(v)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrMaxStaleDeltaSeconds
|
||||||
|
}
|
||||||
|
case "min-fresh":
|
||||||
|
cd.MinFresh, err = parseDeltaSeconds(v)
|
||||||
|
if err != nil {
|
||||||
|
err = ErrMinFreshDeltaSeconds
|
||||||
|
}
|
||||||
|
case "no-cache":
|
||||||
|
err = ErrNoCacheNoArgs
|
||||||
|
case "no-store":
|
||||||
|
err = ErrNoStoreNoArgs
|
||||||
|
case "no-transform":
|
||||||
|
err = ErrNoTransformNoArgs
|
||||||
|
case "only-if-cached":
|
||||||
|
err = ErrOnlyIfCachedNoArgs
|
||||||
|
default:
|
||||||
|
// TODO(pquerna): this sucks, making user re-parse
|
||||||
|
cd.Extensions = append(cd.Extensions, token+"="+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Parses a Cache Control Header from a Request into a set of directives.
|
||||||
|
func ParseRequestCacheControl(value string) (*RequestCacheDirectives, error) {
|
||||||
|
cd := &RequestCacheDirectives{
|
||||||
|
MaxAge: -1,
|
||||||
|
MaxStale: -1,
|
||||||
|
MinFresh: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parse(value, cd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersentation of possible response directives in a `Cache-Control` header: http://tools.ietf.org/html/rfc7234#section-5.2.2
|
||||||
|
//
|
||||||
|
// Note: Many fields will be `nil` in practice.
|
||||||
|
//
|
||||||
|
type ResponseCacheDirectives struct {
|
||||||
|
|
||||||
|
// must-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.1
|
||||||
|
//
|
||||||
|
// The "must-revalidate" response directive indicates that once it has
|
||||||
|
// become stale, a cache MUST NOT use the response to satisfy subsequent
|
||||||
|
// requests without successful validation on the origin server.
|
||||||
|
MustRevalidate bool
|
||||||
|
|
||||||
|
// no-cache(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
|
||||||
|
//
|
||||||
|
// The "no-cache" response directive indicates that the response MUST
|
||||||
|
// NOT be used to satisfy a subsequent request without successful
|
||||||
|
// validation on the origin server.
|
||||||
|
//
|
||||||
|
// If the no-cache response directive specifies one or more field-names,
|
||||||
|
// then a cache MAY use the response to satisfy a subsequent request,
|
||||||
|
// subject to any other restrictions on caching. However, any header
|
||||||
|
// fields in the response that have the field-name(s) listed MUST NOT be
|
||||||
|
// sent in the response to a subsequent request without successful
|
||||||
|
// revalidation with the origin server.
|
||||||
|
NoCache FieldNames
|
||||||
|
|
||||||
|
// no-cache(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.2
|
||||||
|
//
|
||||||
|
// While the RFC defines optional field-names on a no-cache directive,
|
||||||
|
// many applications only want to know if any no-cache directives were
|
||||||
|
// present at all.
|
||||||
|
NoCachePresent bool
|
||||||
|
|
||||||
|
// no-store(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.3
|
||||||
|
//
|
||||||
|
// The "no-store" request directive indicates that a cache MUST NOT
|
||||||
|
// store any part of either this request or any response to it. This
|
||||||
|
// directive applies to both private and shared caches.
|
||||||
|
NoStore bool
|
||||||
|
|
||||||
|
// no-transform(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.4
|
||||||
|
//
|
||||||
|
// The "no-transform" response directive indicates that an intermediary
|
||||||
|
// (regardless of whether it implements a cache) MUST NOT transform the
|
||||||
|
// payload, as defined in Section 5.7.2 of RFC7230.
|
||||||
|
NoTransform bool
|
||||||
|
|
||||||
|
// public(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.5
|
||||||
|
//
|
||||||
|
// The "public" response directive indicates that any cache MAY store
|
||||||
|
// the response, even if the response would normally be non-cacheable or
|
||||||
|
// cacheable only within a private cache.
|
||||||
|
Public bool
|
||||||
|
|
||||||
|
// private(FieldName): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
|
||||||
|
//
|
||||||
|
// The "private" response directive indicates that the response message
|
||||||
|
// is intended for a single user and MUST NOT be stored by a shared
|
||||||
|
// cache. A private cache MAY store the response and reuse it for later
|
||||||
|
// requests, even if the response would normally be non-cacheable.
|
||||||
|
//
|
||||||
|
// If the private response directive specifies one or more field-names,
|
||||||
|
// this requirement is limited to the field-values associated with the
|
||||||
|
// listed response header fields. That is, a shared cache MUST NOT
|
||||||
|
// store the specified field-names(s), whereas it MAY store the
|
||||||
|
// remainder of the response message.
|
||||||
|
Private FieldNames
|
||||||
|
|
||||||
|
// private(cast-to-bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.6
|
||||||
|
//
|
||||||
|
// While the RFC defines optional field-names on a private directive,
|
||||||
|
// many applications only want to know if any private directives were
|
||||||
|
// present at all.
|
||||||
|
PrivatePresent bool
|
||||||
|
|
||||||
|
// proxy-revalidate(bool): http://tools.ietf.org/html/rfc7234#section-5.2.2.7
|
||||||
|
//
|
||||||
|
// The "proxy-revalidate" response directive has the same meaning as the
|
||||||
|
// must-revalidate response directive, except that it does not apply to
|
||||||
|
// private caches.
|
||||||
|
ProxyRevalidate bool
|
||||||
|
|
||||||
|
// max-age(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.8
|
||||||
|
//
|
||||||
|
// The "max-age" response directive indicates that the response is to be
|
||||||
|
// considered stale after its age is greater than the specified number
|
||||||
|
// of seconds.
|
||||||
|
MaxAge DeltaSeconds
|
||||||
|
|
||||||
|
// s-maxage(delta seconds): http://tools.ietf.org/html/rfc7234#section-5.2.2.9
|
||||||
|
//
|
||||||
|
// The "s-maxage" response directive indicates that, in shared caches,
|
||||||
|
// the maximum age specified by this directive overrides the maximum age
|
||||||
|
// specified by either the max-age directive or the Expires header
|
||||||
|
// field. The s-maxage directive also implies the semantics of the
|
||||||
|
// proxy-revalidate response directive.
|
||||||
|
SMaxAge DeltaSeconds
|
||||||
|
|
||||||
|
////
|
||||||
|
// Experimental features
|
||||||
|
// - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Extension_Cache-Control_directives
|
||||||
|
// - https://www.fastly.com/blog/stale-while-revalidate-stale-if-error-available-today
|
||||||
|
////
|
||||||
|
|
||||||
|
// immutable(cast-to-bool): experimental feature
|
||||||
|
Immutable bool
|
||||||
|
|
||||||
|
// stale-if-error(delta seconds): experimental feature
|
||||||
|
StaleIfError DeltaSeconds
|
||||||
|
|
||||||
|
// stale-while-revalidate(delta seconds): experimental feature
|
||||||
|
StaleWhileRevalidate DeltaSeconds
|
||||||
|
|
||||||
|
// Extensions: http://tools.ietf.org/html/rfc7234#section-5.2.3
|
||||||
|
//
|
||||||
|
// The Cache-Control header field can be extended through the use of one
|
||||||
|
// or more cache-extension tokens, each with an optional value. A cache
|
||||||
|
// MUST ignore unrecognized cache directives.
|
||||||
|
Extensions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Parses a Cache Control Header from a Response into a set of directives.
|
||||||
|
func ParseResponseCacheControl(value string) (*ResponseCacheDirectives, error) {
|
||||||
|
cd := &ResponseCacheDirectives{
|
||||||
|
MaxAge: -1,
|
||||||
|
SMaxAge: -1,
|
||||||
|
// Exerimantal stale timeouts
|
||||||
|
StaleIfError: -1,
|
||||||
|
StaleWhileRevalidate: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := parse(value, cd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ResponseCacheDirectives) addToken(token string) error {
|
||||||
|
var err error = nil
|
||||||
|
switch token {
|
||||||
|
case "must-revalidate":
|
||||||
|
cd.MustRevalidate = true
|
||||||
|
case "no-cache":
|
||||||
|
cd.NoCachePresent = true
|
||||||
|
case "no-store":
|
||||||
|
cd.NoStore = true
|
||||||
|
case "no-transform":
|
||||||
|
cd.NoTransform = true
|
||||||
|
case "public":
|
||||||
|
cd.Public = true
|
||||||
|
case "private":
|
||||||
|
cd.PrivatePresent = true
|
||||||
|
case "proxy-revalidate":
|
||||||
|
cd.ProxyRevalidate = true
|
||||||
|
case "max-age":
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
case "s-maxage":
|
||||||
|
err = ErrSMaxAgeDeltaSeconds
|
||||||
|
// Experimental
|
||||||
|
case "immutable":
|
||||||
|
cd.Immutable = true
|
||||||
|
case "stale-if-error":
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
case "stale-while-revalidate":
|
||||||
|
err = ErrMaxAgeDeltaSeconds
|
||||||
|
default:
|
||||||
|
cd.Extensions = append(cd.Extensions, token)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasFieldNames(token string) bool {
|
||||||
|
switch token {
|
||||||
|
case "no-cache":
|
||||||
|
return true
|
||||||
|
case "private":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd *ResponseCacheDirectives) addPair(token string, v string) error {
|
||||||
|
var err error = nil
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "must-revalidate":
|
||||||
|
err = ErrMustRevalidateNoArgs
|
||||||
|
case "no-cache":
|
||||||
|
cd.NoCachePresent = true
|
||||||
|
tokens := strings.Split(v, ",")
|
||||||
|
if cd.NoCache == nil {
|
||||||
|
cd.NoCache = make(FieldNames)
|
||||||
|
}
|
||||||
|
for _, t := range tokens {
|
||||||
|
k := http.CanonicalHeaderKey(textproto.TrimString(t))
|
||||||
|
cd.NoCache[k] = true
|
||||||
|
}
|
||||||
|
case "no-store":
|
||||||
|
err = ErrNoStoreNoArgs
|
||||||
|
case "no-transform":
|
||||||
|
err = ErrNoTransformNoArgs
|
||||||
|
case "public":
|
||||||
|
err = ErrPublicNoArgs
|
||||||
|
case "private":
|
||||||
|
cd.PrivatePresent = true
|
||||||
|
tokens := strings.Split(v, ",")
|
||||||
|
if cd.Private == nil {
|
||||||
|
cd.Private = make(FieldNames)
|
||||||
|
}
|
||||||
|
for _, t := range tokens {
|
||||||
|
k := http.CanonicalHeaderKey(textproto.TrimString(t))
|
||||||
|
cd.Private[k] = true
|
||||||
|
}
|
||||||
|
case "proxy-revalidate":
|
||||||
|
err = ErrProxyRevalidateNoArgs
|
||||||
|
case "max-age":
|
||||||
|
cd.MaxAge, err = parseDeltaSeconds(v)
|
||||||
|
case "s-maxage":
|
||||||
|
cd.SMaxAge, err = parseDeltaSeconds(v)
|
||||||
|
// Experimental
|
||||||
|
case "immutable":
|
||||||
|
err = ErrImmutableNoArgs
|
||||||
|
case "stale-if-error":
|
||||||
|
cd.StaleIfError, err = parseDeltaSeconds(v)
|
||||||
|
case "stale-while-revalidate":
|
||||||
|
cd.StaleWhileRevalidate, err = parseDeltaSeconds(v)
|
||||||
|
default:
|
||||||
|
// TODO(pquerna): this sucks, making user re-parse, and its technically not 'quoted' like the original,
|
||||||
|
// but this is still easier, just a SplitN on "="
|
||||||
|
cd.Extensions = append(cd.Extensions, token+"="+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
93
vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go
generated
vendored
Normal file
93
vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go
generated
vendored
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
// This file deals with lexical matters of HTTP
|
||||||
|
|
||||||
|
func isSeparator(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }
|
||||||
|
|
||||||
|
func isChar(c byte) bool { return 0 <= c && c <= 127 }
|
||||||
|
|
||||||
|
func isAnyText(c byte) bool { return !isCtl(c) }
|
||||||
|
|
||||||
|
func isQdText(c byte) bool { return isAnyText(c) && c != '"' }
|
||||||
|
|
||||||
|
func isToken(c byte) bool { return isChar(c) && !isCtl(c) && !isSeparator(c) }
|
||||||
|
|
||||||
|
// Valid escaped sequences are not specified in RFC 2616, so for now, we assume
|
||||||
|
// that they coincide with the common sense ones used by GO. Malformed
|
||||||
|
// characters should probably not be treated as errors by a robust (forgiving)
|
||||||
|
// parser, so we replace them with the '?' character.
|
||||||
|
func httpUnquotePair(b byte) byte {
|
||||||
|
// skip the first byte, which should always be '\'
|
||||||
|
switch b {
|
||||||
|
case 'a':
|
||||||
|
return '\a'
|
||||||
|
case 'b':
|
||||||
|
return '\b'
|
||||||
|
case 'f':
|
||||||
|
return '\f'
|
||||||
|
case 'n':
|
||||||
|
return '\n'
|
||||||
|
case 'r':
|
||||||
|
return '\r'
|
||||||
|
case 't':
|
||||||
|
return '\t'
|
||||||
|
case 'v':
|
||||||
|
return '\v'
|
||||||
|
case '\\':
|
||||||
|
return '\\'
|
||||||
|
case '\'':
|
||||||
|
return '\''
|
||||||
|
case '"':
|
||||||
|
return '"'
|
||||||
|
}
|
||||||
|
return '?'
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw must begin with a valid quoted string. Only the first quoted string is
|
||||||
|
// parsed and is unquoted in result. eaten is the number of bytes parsed, or -1
|
||||||
|
// upon failure.
|
||||||
|
func httpUnquote(raw string) (eaten int, result string) {
|
||||||
|
buf := make([]byte, len(raw))
|
||||||
|
if raw[0] != '"' {
|
||||||
|
return -1, ""
|
||||||
|
}
|
||||||
|
eaten = 1
|
||||||
|
j := 0 // # of bytes written in buf
|
||||||
|
for i := 1; i < len(raw); i++ {
|
||||||
|
switch b := raw[i]; b {
|
||||||
|
case '"':
|
||||||
|
eaten++
|
||||||
|
buf = buf[0:j]
|
||||||
|
return i + 1, string(buf)
|
||||||
|
case '\\':
|
||||||
|
if len(raw) < i+2 {
|
||||||
|
return -1, ""
|
||||||
|
}
|
||||||
|
buf[j] = httpUnquotePair(raw[i+1])
|
||||||
|
eaten += 2
|
||||||
|
j++
|
||||||
|
i++
|
||||||
|
default:
|
||||||
|
if isQdText(b) {
|
||||||
|
buf[j] = b
|
||||||
|
} else {
|
||||||
|
buf[j] = '?'
|
||||||
|
}
|
||||||
|
eaten++
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, ""
|
||||||
|
}
|
387
vendor/github.com/pquerna/cachecontrol/cacheobject/object.go
generated
vendored
Normal file
387
vendor/github.com/pquerna/cachecontrol/cacheobject/object.go
generated
vendored
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersents a potentially cachable HTTP object.
|
||||||
|
//
|
||||||
|
// This struct is designed to be serialized efficiently, so in a high
|
||||||
|
// performance caching server, things like Date-Strings don't need to be
|
||||||
|
// parsed for every use of a cached object.
|
||||||
|
type Object struct {
|
||||||
|
CacheIsPrivate bool
|
||||||
|
|
||||||
|
RespDirectives *ResponseCacheDirectives
|
||||||
|
RespHeaders http.Header
|
||||||
|
RespStatusCode int
|
||||||
|
RespExpiresHeader time.Time
|
||||||
|
RespDateHeader time.Time
|
||||||
|
RespLastModifiedHeader time.Time
|
||||||
|
|
||||||
|
ReqDirectives *RequestCacheDirectives
|
||||||
|
ReqHeaders http.Header
|
||||||
|
ReqMethod string
|
||||||
|
|
||||||
|
NowUTC time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Repersents the results of examinig an Object with
|
||||||
|
// CachableObject and ExpirationObject.
|
||||||
|
//
|
||||||
|
// TODO(pquerna): decide if this is a good idea or bad
|
||||||
|
type ObjectResults struct {
|
||||||
|
OutReasons []Reason
|
||||||
|
OutWarnings []Warning
|
||||||
|
OutExpirationTime time.Time
|
||||||
|
OutErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOW LEVEL API: Check if a object is cachable.
|
||||||
|
func CachableObject(obj *Object, rv *ObjectResults) {
|
||||||
|
rv.OutReasons = nil
|
||||||
|
rv.OutWarnings = nil
|
||||||
|
rv.OutErr = nil
|
||||||
|
|
||||||
|
switch obj.ReqMethod {
|
||||||
|
case "GET":
|
||||||
|
break
|
||||||
|
case "HEAD":
|
||||||
|
break
|
||||||
|
case "POST":
|
||||||
|
/**
|
||||||
|
POST: http://tools.ietf.org/html/rfc7231#section-4.3.3
|
||||||
|
|
||||||
|
Responses to POST requests are only cacheable when they include
|
||||||
|
explicit freshness information (see Section 4.2.1 of [RFC7234]).
|
||||||
|
However, POST caching is not widely implemented. For cases where an
|
||||||
|
origin server wishes the client to be able to cache the result of a
|
||||||
|
POST in a way that can be reused by a later GET, the origin server
|
||||||
|
MAY send a 200 (OK) response containing the result and a
|
||||||
|
Content-Location header field that has the same value as the POST's
|
||||||
|
effective request URI (Section 3.1.4.2).
|
||||||
|
*/
|
||||||
|
if !hasFreshness(obj.ReqDirectives, obj.RespDirectives, obj.RespHeaders, obj.RespExpiresHeader, obj.CacheIsPrivate) {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodPOST)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "PUT":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodPUT)
|
||||||
|
|
||||||
|
case "DELETE":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodDELETE)
|
||||||
|
|
||||||
|
case "CONNECT":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodCONNECT)
|
||||||
|
|
||||||
|
case "OPTIONS":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodOPTIONS)
|
||||||
|
|
||||||
|
case "TRACE":
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodTRACE)
|
||||||
|
|
||||||
|
// HTTP Extension Methods: http://www.iana.org/assignments/http-methods/http-methods.xhtml
|
||||||
|
//
|
||||||
|
// To my knowledge, none of them are cachable. Please open a ticket if this is not the case!
|
||||||
|
//
|
||||||
|
default:
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestMethodUnkown)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.ReqDirectives.NoStore {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestNoStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storing Responses to Authenticated Requests: http://tools.ietf.org/html/rfc7234#section-3.2
|
||||||
|
authz := obj.ReqHeaders.Get("Authorization")
|
||||||
|
if authz != "" {
|
||||||
|
if obj.RespDirectives.MustRevalidate ||
|
||||||
|
obj.RespDirectives.Public ||
|
||||||
|
obj.RespDirectives.SMaxAge != -1 {
|
||||||
|
// Expires of some kind present, this is potentially OK.
|
||||||
|
} else {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonRequestAuthorizationHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.RespDirectives.PrivatePresent && !obj.CacheIsPrivate {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonResponsePrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.RespDirectives.NoStore {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonResponseNoStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
the response either:
|
||||||
|
|
||||||
|
* contains an Expires header field (see Section 5.3), or
|
||||||
|
|
||||||
|
* contains a max-age response directive (see Section 5.2.2.8), or
|
||||||
|
|
||||||
|
* contains a s-maxage response directive (see Section 5.2.2.9)
|
||||||
|
and the cache is shared, or
|
||||||
|
|
||||||
|
* contains a Cache Control Extension (see Section 5.2.3) that
|
||||||
|
allows it to be cached, or
|
||||||
|
|
||||||
|
* has a status code that is defined as cacheable by default (see
|
||||||
|
Section 4.2.2), or
|
||||||
|
|
||||||
|
* contains a public response directive (see Section 5.2.2.5).
|
||||||
|
*/
|
||||||
|
|
||||||
|
expires := obj.RespHeaders.Get("Expires") != ""
|
||||||
|
statusCachable := cachableStatusCode(obj.RespStatusCode)
|
||||||
|
|
||||||
|
if expires ||
|
||||||
|
obj.RespDirectives.MaxAge != -1 ||
|
||||||
|
(obj.RespDirectives.SMaxAge != -1 && !obj.CacheIsPrivate) ||
|
||||||
|
statusCachable ||
|
||||||
|
obj.RespDirectives.Public {
|
||||||
|
/* cachable by default, at least one of the above conditions was true */
|
||||||
|
} else {
|
||||||
|
rv.OutReasons = append(rv.OutReasons, ReasonResponseUncachableByDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var twentyFourHours = time.Duration(24 * time.Hour)
|
||||||
|
|
||||||
|
const debug = false
|
||||||
|
|
||||||
|
// LOW LEVEL API: Update an objects expiration time.
|
||||||
|
func ExpirationObject(obj *Object, rv *ObjectResults) {
|
||||||
|
/**
|
||||||
|
* Okay, lets calculate Freshness/Expiration now. woo:
|
||||||
|
* http://tools.ietf.org/html/rfc7234#section-4.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
o If the cache is shared and the s-maxage response directive
|
||||||
|
(Section 5.2.2.9) is present, use its value, or
|
||||||
|
|
||||||
|
o If the max-age response directive (Section 5.2.2.8) is present,
|
||||||
|
use its value, or
|
||||||
|
|
||||||
|
o If the Expires response header field (Section 5.3) is present, use
|
||||||
|
its value minus the value of the Date response header field, or
|
||||||
|
|
||||||
|
o Otherwise, no explicit expiration time is present in the response.
|
||||||
|
A heuristic freshness lifetime might be applicable; see
|
||||||
|
Section 4.2.2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var expiresTime time.Time
|
||||||
|
|
||||||
|
if obj.RespDirectives.SMaxAge != -1 && !obj.CacheIsPrivate {
|
||||||
|
expiresTime = obj.NowUTC.Add(time.Second * time.Duration(obj.RespDirectives.SMaxAge))
|
||||||
|
} else if obj.RespDirectives.MaxAge != -1 {
|
||||||
|
expiresTime = obj.NowUTC.UTC().Add(time.Second * time.Duration(obj.RespDirectives.MaxAge))
|
||||||
|
} else if !obj.RespExpiresHeader.IsZero() {
|
||||||
|
serverDate := obj.RespDateHeader
|
||||||
|
if serverDate.IsZero() {
|
||||||
|
// common enough case when a Date: header has not yet been added to an
|
||||||
|
// active response.
|
||||||
|
serverDate = obj.NowUTC
|
||||||
|
}
|
||||||
|
expiresTime = obj.NowUTC.Add(obj.RespExpiresHeader.Sub(serverDate))
|
||||||
|
} else if !obj.RespLastModifiedHeader.IsZero() {
|
||||||
|
// heuristic freshness lifetime
|
||||||
|
rv.OutWarnings = append(rv.OutWarnings, WarningHeuristicExpiration)
|
||||||
|
|
||||||
|
// http://httpd.apache.org/docs/2.4/mod/mod_cache.html#cachelastmodifiedfactor
|
||||||
|
// CacheMaxExpire defaults to 24 hours
|
||||||
|
// CacheLastModifiedFactor: is 0.1
|
||||||
|
//
|
||||||
|
// expiry-period = MIN(time-since-last-modified-date * factor, 24 hours)
|
||||||
|
//
|
||||||
|
// obj.NowUTC
|
||||||
|
|
||||||
|
since := obj.RespLastModifiedHeader.Sub(obj.NowUTC)
|
||||||
|
since = time.Duration(float64(since) * -0.1)
|
||||||
|
|
||||||
|
if since > twentyFourHours {
|
||||||
|
expiresTime = obj.NowUTC.Add(twentyFourHours)
|
||||||
|
} else {
|
||||||
|
expiresTime = obj.NowUTC.Add(since)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
println("Now UTC: ", obj.NowUTC.String())
|
||||||
|
println("Last-Modified: ", obj.RespLastModifiedHeader.String())
|
||||||
|
println("Since: ", since.String())
|
||||||
|
println("TwentyFourHours: ", twentyFourHours.String())
|
||||||
|
println("Expiration: ", expiresTime.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO(pquerna): what should the default behavoir be for expiration time?
|
||||||
|
}
|
||||||
|
|
||||||
|
rv.OutExpirationTime = expiresTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate cachability based on an HTTP request, and parts of the response.
|
||||||
|
func UsingRequestResponse(req *http.Request,
|
||||||
|
statusCode int,
|
||||||
|
respHeaders http.Header,
|
||||||
|
privateCache bool) ([]Reason, time.Time, error) {
|
||||||
|
reasons, time, _, _, err := UsingRequestResponseWithObject(req, statusCode, respHeaders, privateCache)
|
||||||
|
return reasons, time, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate cachability based on an HTTP request, and parts of the response.
|
||||||
|
// Returns the parsed Object as well.
|
||||||
|
func UsingRequestResponseWithObject(req *http.Request,
|
||||||
|
statusCode int,
|
||||||
|
respHeaders http.Header,
|
||||||
|
privateCache bool) ([]Reason, time.Time, []Warning, *Object, error) {
|
||||||
|
var reqHeaders http.Header
|
||||||
|
var reqMethod string
|
||||||
|
|
||||||
|
var reqDir *RequestCacheDirectives = nil
|
||||||
|
respDir, err := ParseResponseCacheControl(respHeaders.Get("Cache-Control"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if req != nil {
|
||||||
|
reqDir, err = ParseRequestCacheControl(req.Header.Get("Cache-Control"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, nil, nil, err
|
||||||
|
}
|
||||||
|
reqHeaders = req.Header
|
||||||
|
reqMethod = req.Method
|
||||||
|
}
|
||||||
|
|
||||||
|
var expiresHeader time.Time
|
||||||
|
var dateHeader time.Time
|
||||||
|
var lastModifiedHeader time.Time
|
||||||
|
|
||||||
|
if respHeaders.Get("Expires") != "" {
|
||||||
|
expiresHeader, err = http.ParseTime(respHeaders.Get("Expires"))
|
||||||
|
if err != nil {
|
||||||
|
// sometimes servers will return `Expires: 0` or `Expires: -1` to
|
||||||
|
// indicate expired content
|
||||||
|
expiresHeader = time.Time{}
|
||||||
|
}
|
||||||
|
expiresHeader = expiresHeader.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if respHeaders.Get("Date") != "" {
|
||||||
|
dateHeader, err = http.ParseTime(respHeaders.Get("Date"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, nil, nil, err
|
||||||
|
}
|
||||||
|
dateHeader = dateHeader.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
if respHeaders.Get("Last-Modified") != "" {
|
||||||
|
lastModifiedHeader, err = http.ParseTime(respHeaders.Get("Last-Modified"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, nil, nil, err
|
||||||
|
}
|
||||||
|
lastModifiedHeader = lastModifiedHeader.UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := Object{
|
||||||
|
CacheIsPrivate: privateCache,
|
||||||
|
|
||||||
|
RespDirectives: respDir,
|
||||||
|
RespHeaders: respHeaders,
|
||||||
|
RespStatusCode: statusCode,
|
||||||
|
RespExpiresHeader: expiresHeader,
|
||||||
|
RespDateHeader: dateHeader,
|
||||||
|
RespLastModifiedHeader: lastModifiedHeader,
|
||||||
|
|
||||||
|
ReqDirectives: reqDir,
|
||||||
|
ReqHeaders: reqHeaders,
|
||||||
|
ReqMethod: reqMethod,
|
||||||
|
|
||||||
|
NowUTC: time.Now().UTC(),
|
||||||
|
}
|
||||||
|
rv := ObjectResults{}
|
||||||
|
|
||||||
|
CachableObject(&obj, &rv)
|
||||||
|
if rv.OutErr != nil {
|
||||||
|
return nil, time.Time{}, nil, nil, rv.OutErr
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpirationObject(&obj, &rv)
|
||||||
|
if rv.OutErr != nil {
|
||||||
|
return nil, time.Time{}, nil, nil, rv.OutErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv.OutReasons, rv.OutExpirationTime, rv.OutWarnings, &obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate if a freshness directive is present: http://tools.ietf.org/html/rfc7234#section-4.2.1
|
||||||
|
func hasFreshness(reqDir *RequestCacheDirectives, respDir *ResponseCacheDirectives, respHeaders http.Header, respExpires time.Time, privateCache bool) bool {
|
||||||
|
if !privateCache && respDir.SMaxAge != -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if respDir.MaxAge != -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !respExpires.IsZero() || respHeaders.Get("Expires") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func cachableStatusCode(statusCode int) bool {
|
||||||
|
/*
|
||||||
|
Responses with status codes that are defined as cacheable by default
|
||||||
|
(e.g., 200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501 in
|
||||||
|
this specification) can be reused by a cache with heuristic
|
||||||
|
expiration unless otherwise indicated by the method definition or
|
||||||
|
explicit cache controls [RFC7234]; all other status codes are not
|
||||||
|
cacheable by default.
|
||||||
|
*/
|
||||||
|
switch statusCode {
|
||||||
|
case 200:
|
||||||
|
return true
|
||||||
|
case 203:
|
||||||
|
return true
|
||||||
|
case 204:
|
||||||
|
return true
|
||||||
|
case 206:
|
||||||
|
return true
|
||||||
|
case 300:
|
||||||
|
return true
|
||||||
|
case 301:
|
||||||
|
return true
|
||||||
|
case 404:
|
||||||
|
return true
|
||||||
|
case 405:
|
||||||
|
return true
|
||||||
|
case 410:
|
||||||
|
return true
|
||||||
|
case 414:
|
||||||
|
return true
|
||||||
|
case 501:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
95
vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go
generated
vendored
Normal file
95
vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
// Repersents a potential Reason to not cache an object.
|
||||||
|
//
|
||||||
|
// Applications may wish to ignore specific reasons, which will make them non-RFC
|
||||||
|
// compliant, but this type gives them specific cases they can choose to ignore,
|
||||||
|
// making them compliant in as many cases as they can.
|
||||||
|
type Reason int
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// The request method was POST and an Expiration header was not supplied.
|
||||||
|
ReasonRequestMethodPOST Reason = iota
|
||||||
|
|
||||||
|
// The request method was PUT and PUTs are not cachable.
|
||||||
|
ReasonRequestMethodPUT
|
||||||
|
|
||||||
|
// The request method was DELETE and DELETEs are not cachable.
|
||||||
|
ReasonRequestMethodDELETE
|
||||||
|
|
||||||
|
// The request method was CONNECT and CONNECTs are not cachable.
|
||||||
|
ReasonRequestMethodCONNECT
|
||||||
|
|
||||||
|
// The request method was OPTIONS and OPTIONS are not cachable.
|
||||||
|
ReasonRequestMethodOPTIONS
|
||||||
|
|
||||||
|
// The request method was TRACE and TRACEs are not cachable.
|
||||||
|
ReasonRequestMethodTRACE
|
||||||
|
|
||||||
|
// The request method was not recognized by cachecontrol, and should not be cached.
|
||||||
|
ReasonRequestMethodUnkown
|
||||||
|
|
||||||
|
// The request included an Cache-Control: no-store header
|
||||||
|
ReasonRequestNoStore
|
||||||
|
|
||||||
|
// The request included an Authorization header without an explicit Public or Expiration time: http://tools.ietf.org/html/rfc7234#section-3.2
|
||||||
|
ReasonRequestAuthorizationHeader
|
||||||
|
|
||||||
|
// The response included an Cache-Control: no-store header
|
||||||
|
ReasonResponseNoStore
|
||||||
|
|
||||||
|
// The response included an Cache-Control: private header and this is not a Private cache
|
||||||
|
ReasonResponsePrivate
|
||||||
|
|
||||||
|
// The response failed to meet at least one of the conditions specified in RFC 7234 section 3: http://tools.ietf.org/html/rfc7234#section-3
|
||||||
|
ReasonResponseUncachableByDefault
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r Reason) String() string {
|
||||||
|
switch r {
|
||||||
|
case ReasonRequestMethodPOST:
|
||||||
|
return "ReasonRequestMethodPOST"
|
||||||
|
case ReasonRequestMethodPUT:
|
||||||
|
return "ReasonRequestMethodPUT"
|
||||||
|
case ReasonRequestMethodDELETE:
|
||||||
|
return "ReasonRequestMethodDELETE"
|
||||||
|
case ReasonRequestMethodCONNECT:
|
||||||
|
return "ReasonRequestMethodCONNECT"
|
||||||
|
case ReasonRequestMethodOPTIONS:
|
||||||
|
return "ReasonRequestMethodOPTIONS"
|
||||||
|
case ReasonRequestMethodTRACE:
|
||||||
|
return "ReasonRequestMethodTRACE"
|
||||||
|
case ReasonRequestMethodUnkown:
|
||||||
|
return "ReasonRequestMethodUnkown"
|
||||||
|
case ReasonRequestNoStore:
|
||||||
|
return "ReasonRequestNoStore"
|
||||||
|
case ReasonRequestAuthorizationHeader:
|
||||||
|
return "ReasonRequestAuthorizationHeader"
|
||||||
|
case ReasonResponseNoStore:
|
||||||
|
return "ReasonResponseNoStore"
|
||||||
|
case ReasonResponsePrivate:
|
||||||
|
return "ReasonResponsePrivate"
|
||||||
|
case ReasonResponseUncachableByDefault:
|
||||||
|
return "ReasonResponseUncachableByDefault"
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(r)
|
||||||
|
}
|
107
vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go
generated
vendored
Normal file
107
vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cacheobject
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repersents an HTTP Warning: http://tools.ietf.org/html/rfc7234#section-5.5
|
||||||
|
type Warning int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Response is Stale
|
||||||
|
// A cache SHOULD generate this whenever the sent response is stale.
|
||||||
|
WarningResponseIsStale Warning = 110
|
||||||
|
|
||||||
|
// Revalidation Failed
|
||||||
|
// A cache SHOULD generate this when sending a stale
|
||||||
|
// response because an attempt to validate the response failed, due to an
|
||||||
|
// inability to reach the server.
|
||||||
|
WarningRevalidationFailed Warning = 111
|
||||||
|
|
||||||
|
// Disconnected Operation
|
||||||
|
// A cache SHOULD generate this if it is intentionally disconnected from
|
||||||
|
// the rest of the network for a period of time.
|
||||||
|
WarningDisconnectedOperation Warning = 112
|
||||||
|
|
||||||
|
// Heuristic Expiration
|
||||||
|
//
|
||||||
|
// A cache SHOULD generate this if it heuristically chose a freshness
|
||||||
|
// lifetime greater than 24 hours and the response's age is greater than
|
||||||
|
// 24 hours.
|
||||||
|
WarningHeuristicExpiration Warning = 113
|
||||||
|
|
||||||
|
// Miscellaneous Warning
|
||||||
|
//
|
||||||
|
// The warning text can include arbitrary information to be presented to
|
||||||
|
// a human user or logged. A system receiving this warning MUST NOT
|
||||||
|
// take any automated action, besides presenting the warning to the
|
||||||
|
// user.
|
||||||
|
WarningMiscellaneousWarning Warning = 199
|
||||||
|
|
||||||
|
// Transformation Applied
|
||||||
|
//
|
||||||
|
// This Warning code MUST be added by a proxy if it applies any
|
||||||
|
// transformation to the representation, such as changing the
|
||||||
|
// content-coding, media-type, or modifying the representation data,
|
||||||
|
// unless this Warning code already appears in the response.
|
||||||
|
WarningTransformationApplied Warning = 214
|
||||||
|
|
||||||
|
// Miscellaneous Persistent Warning
|
||||||
|
//
|
||||||
|
// The warning text can include arbitrary information to be presented to
|
||||||
|
// a human user or logged. A system receiving this warning MUST NOT
|
||||||
|
// take any automated action.
|
||||||
|
WarningMiscellaneousPersistentWarning Warning = 299
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w Warning) HeaderString(agent string, date time.Time) string {
|
||||||
|
if agent == "" {
|
||||||
|
agent = "-"
|
||||||
|
} else {
|
||||||
|
// TODO(pquerna): this doesn't escape agent if it contains bad things.
|
||||||
|
agent = `"` + agent + `"`
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(`%d %s "%s" %s`, w, agent, w.String(), date.Format(http.TimeFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Warning) String() string {
|
||||||
|
switch w {
|
||||||
|
case WarningResponseIsStale:
|
||||||
|
return "Response is Stale"
|
||||||
|
case WarningRevalidationFailed:
|
||||||
|
return "Revalidation Failed"
|
||||||
|
case WarningDisconnectedOperation:
|
||||||
|
return "Disconnected Operation"
|
||||||
|
case WarningHeuristicExpiration:
|
||||||
|
return "Heuristic Expiration"
|
||||||
|
case WarningMiscellaneousWarning:
|
||||||
|
// TODO(pquerna): ideally had a better way to override this one code.
|
||||||
|
return "Miscellaneous Warning"
|
||||||
|
case WarningTransformationApplied:
|
||||||
|
return "Transformation Applied"
|
||||||
|
case WarningMiscellaneousPersistentWarning:
|
||||||
|
// TODO(pquerna): same as WarningMiscellaneousWarning
|
||||||
|
return "Miscellaneous Persistent Warning"
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(w)
|
||||||
|
}
|
25
vendor/github.com/pquerna/cachecontrol/doc.go
generated
vendored
Normal file
25
vendor/github.com/pquerna/cachecontrol/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2015 Paul Querna
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package cachecontrol implements the logic for HTTP Caching
|
||||||
|
//
|
||||||
|
// Deciding if an HTTP Response can be cached is often harder
|
||||||
|
// and more bug prone than an actual cache storage backend.
|
||||||
|
// cachecontrol provides a simple interface to determine if
|
||||||
|
// request and response pairs are cachable as defined under
|
||||||
|
// RFC 7234 http://tools.ietf.org/html/rfc7234
|
||||||
|
package cachecontrol
|
222
vendor/golang.org/x/crypto/ed25519/ed25519.go
generated
vendored
Normal file
222
vendor/golang.org/x/crypto/ed25519/ed25519.go
generated
vendored
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// In Go 1.13, the ed25519 package was promoted to the standard library as
|
||||||
|
// crypto/ed25519, and this package became a wrapper for the standard library one.
|
||||||
|
//
|
||||||
|
// +build !go1.13
|
||||||
|
|
||||||
|
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||||
|
// https://ed25519.cr.yp.to/.
|
||||||
|
//
|
||||||
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
|
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
|
||||||
|
// representation includes a public key suffix to make multiple signing
|
||||||
|
// operations with the same key more efficient. This package refers to the RFC
|
||||||
|
// 8032 private key as the “seed”.
|
||||||
|
package ed25519
|
||||||
|
|
||||||
|
// This code is a port of the public domain, “ref10” implementation of ed25519
|
||||||
|
// from SUPERCOP.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
cryptorand "crypto/rand"
|
||||||
|
"crypto/sha512"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519/internal/edwards25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||||
|
PublicKeySize = 32
|
||||||
|
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||||
|
PrivateKeySize = 64
|
||||||
|
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||||
|
SignatureSize = 64
|
||||||
|
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
|
||||||
|
SeedSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey is the type of Ed25519 public keys.
|
||||||
|
type PublicKey []byte
|
||||||
|
|
||||||
|
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||||
|
type PrivateKey []byte
|
||||||
|
|
||||||
|
// Public returns the PublicKey corresponding to priv.
|
||||||
|
func (priv PrivateKey) Public() crypto.PublicKey {
|
||||||
|
publicKey := make([]byte, PublicKeySize)
|
||||||
|
copy(publicKey, priv[32:])
|
||||||
|
return PublicKey(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed returns the private key seed corresponding to priv. It is provided for
|
||||||
|
// interoperability with RFC 8032. RFC 8032's private keys correspond to seeds
|
||||||
|
// in this package.
|
||||||
|
func (priv PrivateKey) Seed() []byte {
|
||||||
|
seed := make([]byte, SeedSize)
|
||||||
|
copy(seed, priv[:32])
|
||||||
|
return seed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the given message with priv.
|
||||||
|
// Ed25519 performs two passes over messages to be signed and therefore cannot
|
||||||
|
// handle pre-hashed messages. Thus opts.HashFunc() must return zero to
|
||||||
|
// indicate the message hasn't been hashed. This can be achieved by passing
|
||||||
|
// crypto.Hash(0) as the value for opts.
|
||||||
|
func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
|
if opts.HashFunc() != crypto.Hash(0) {
|
||||||
|
return nil, errors.New("ed25519: cannot sign hashed message")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sign(priv, message), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||||
|
// If rand is nil, crypto/rand.Reader will be used.
|
||||||
|
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||||
|
if rand == nil {
|
||||||
|
rand = cryptorand.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
seed := make([]byte, SeedSize)
|
||||||
|
if _, err := io.ReadFull(rand, seed); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey := NewKeyFromSeed(seed)
|
||||||
|
publicKey := make([]byte, PublicKeySize)
|
||||||
|
copy(publicKey, privateKey[32:])
|
||||||
|
|
||||||
|
return publicKey, privateKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyFromSeed calculates a private key from a seed. It will panic if
|
||||||
|
// len(seed) is not SeedSize. This function is provided for interoperability
|
||||||
|
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
|
||||||
|
// package.
|
||||||
|
func NewKeyFromSeed(seed []byte) PrivateKey {
|
||||||
|
if l := len(seed); l != SeedSize {
|
||||||
|
panic("ed25519: bad seed length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
digest := sha512.Sum512(seed)
|
||||||
|
digest[0] &= 248
|
||||||
|
digest[31] &= 127
|
||||||
|
digest[31] |= 64
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var hBytes [32]byte
|
||||||
|
copy(hBytes[:], digest[:])
|
||||||
|
edwards25519.GeScalarMultBase(&A, &hBytes)
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
A.ToBytes(&publicKeyBytes)
|
||||||
|
|
||||||
|
privateKey := make([]byte, PrivateKeySize)
|
||||||
|
copy(privateKey, seed)
|
||||||
|
copy(privateKey[32:], publicKeyBytes[:])
|
||||||
|
|
||||||
|
return privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the message with privateKey and returns a signature. It will
|
||||||
|
// panic if len(privateKey) is not PrivateKeySize.
|
||||||
|
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||||
|
if l := len(privateKey); l != PrivateKeySize {
|
||||||
|
panic("ed25519: bad private key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(privateKey[:32])
|
||||||
|
|
||||||
|
var digest1, messageDigest, hramDigest [64]byte
|
||||||
|
var expandedSecretKey [32]byte
|
||||||
|
h.Sum(digest1[:0])
|
||||||
|
copy(expandedSecretKey[:], digest1[:])
|
||||||
|
expandedSecretKey[0] &= 248
|
||||||
|
expandedSecretKey[31] &= 63
|
||||||
|
expandedSecretKey[31] |= 64
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(digest1[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(messageDigest[:0])
|
||||||
|
|
||||||
|
var messageDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
||||||
|
var R edwards25519.ExtendedGroupElement
|
||||||
|
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
||||||
|
|
||||||
|
var encodedR [32]byte
|
||||||
|
R.ToBytes(&encodedR)
|
||||||
|
|
||||||
|
h.Reset()
|
||||||
|
h.Write(encodedR[:])
|
||||||
|
h.Write(privateKey[32:])
|
||||||
|
h.Write(message)
|
||||||
|
h.Sum(hramDigest[:0])
|
||||||
|
var hramDigestReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
||||||
|
|
||||||
|
var s [32]byte
|
||||||
|
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
||||||
|
|
||||||
|
signature := make([]byte, SignatureSize)
|
||||||
|
copy(signature[:], encodedR[:])
|
||||||
|
copy(signature[32:], s[:])
|
||||||
|
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||||
|
// will panic if len(publicKey) is not PublicKeySize.
|
||||||
|
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
|
if l := len(publicKey); l != PublicKeySize {
|
||||||
|
panic("ed25519: bad public key length: " + strconv.Itoa(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sig) != SignatureSize || sig[63]&224 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var A edwards25519.ExtendedGroupElement
|
||||||
|
var publicKeyBytes [32]byte
|
||||||
|
copy(publicKeyBytes[:], publicKey)
|
||||||
|
if !A.FromBytes(&publicKeyBytes) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
edwards25519.FeNeg(&A.X, &A.X)
|
||||||
|
edwards25519.FeNeg(&A.T, &A.T)
|
||||||
|
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write(sig[:32])
|
||||||
|
h.Write(publicKey[:])
|
||||||
|
h.Write(message)
|
||||||
|
var digest [64]byte
|
||||||
|
h.Sum(digest[:0])
|
||||||
|
|
||||||
|
var hReduced [32]byte
|
||||||
|
edwards25519.ScReduce(&hReduced, &digest)
|
||||||
|
|
||||||
|
var R edwards25519.ProjectiveGroupElement
|
||||||
|
var s [32]byte
|
||||||
|
copy(s[:], sig[32:])
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc8032#section-5.1.7 requires that s be in
|
||||||
|
// the range [0, order) in order to prevent signature malleability.
|
||||||
|
if !edwards25519.ScMinimal(&s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &s)
|
||||||
|
|
||||||
|
var checkR [32]byte
|
||||||
|
R.ToBytes(&checkR)
|
||||||
|
return bytes.Equal(sig[:32], checkR[:])
|
||||||
|
}
|
73
vendor/golang.org/x/crypto/ed25519/ed25519_go113.go
generated
vendored
Normal file
73
vendor/golang.org/x/crypto/ed25519/ed25519_go113.go
generated
vendored
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
|
// Package ed25519 implements the Ed25519 signature algorithm. See
|
||||||
|
// https://ed25519.cr.yp.to/.
|
||||||
|
//
|
||||||
|
// These functions are also compatible with the “Ed25519” function defined in
|
||||||
|
// RFC 8032. However, unlike RFC 8032's formulation, this package's private key
|
||||||
|
// representation includes a public key suffix to make multiple signing
|
||||||
|
// operations with the same key more efficient. This package refers to the RFC
|
||||||
|
// 8032 private key as the “seed”.
|
||||||
|
//
|
||||||
|
// Beginning with Go 1.13, the functionality of this package was moved to the
|
||||||
|
// standard library as crypto/ed25519. This package only acts as a compatibility
|
||||||
|
// wrapper.
|
||||||
|
package ed25519
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PublicKeySize is the size, in bytes, of public keys as used in this package.
|
||||||
|
PublicKeySize = 32
|
||||||
|
// PrivateKeySize is the size, in bytes, of private keys as used in this package.
|
||||||
|
PrivateKeySize = 64
|
||||||
|
// SignatureSize is the size, in bytes, of signatures generated and verified by this package.
|
||||||
|
SignatureSize = 64
|
||||||
|
// SeedSize is the size, in bytes, of private key seeds. These are the private key representations used by RFC 8032.
|
||||||
|
SeedSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicKey is the type of Ed25519 public keys.
|
||||||
|
//
|
||||||
|
// This type is an alias for crypto/ed25519's PublicKey type.
|
||||||
|
// See the crypto/ed25519 package for the methods on this type.
|
||||||
|
type PublicKey = ed25519.PublicKey
|
||||||
|
|
||||||
|
// PrivateKey is the type of Ed25519 private keys. It implements crypto.Signer.
|
||||||
|
//
|
||||||
|
// This type is an alias for crypto/ed25519's PrivateKey type.
|
||||||
|
// See the crypto/ed25519 package for the methods on this type.
|
||||||
|
type PrivateKey = ed25519.PrivateKey
|
||||||
|
|
||||||
|
// GenerateKey generates a public/private key pair using entropy from rand.
|
||||||
|
// If rand is nil, crypto/rand.Reader will be used.
|
||||||
|
func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) {
|
||||||
|
return ed25519.GenerateKey(rand)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyFromSeed calculates a private key from a seed. It will panic if
|
||||||
|
// len(seed) is not SeedSize. This function is provided for interoperability
|
||||||
|
// with RFC 8032. RFC 8032's private keys correspond to seeds in this
|
||||||
|
// package.
|
||||||
|
func NewKeyFromSeed(seed []byte) PrivateKey {
|
||||||
|
return ed25519.NewKeyFromSeed(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the message with privateKey and returns a signature. It will
|
||||||
|
// panic if len(privateKey) is not PrivateKeySize.
|
||||||
|
func Sign(privateKey PrivateKey, message []byte) []byte {
|
||||||
|
return ed25519.Sign(privateKey, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify reports whether sig is a valid signature of message by publicKey. It
|
||||||
|
// will panic if len(publicKey) is not PublicKeySize.
|
||||||
|
func Verify(publicKey PublicKey, message, sig []byte) bool {
|
||||||
|
return ed25519.Verify(publicKey, message, sig)
|
||||||
|
}
|
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
1422
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1793
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
1793
vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||||
|
2898 / PKCS #5 v2.0.
|
||||||
|
|
||||||
|
A key derivation function is useful when encrypting data based on a password
|
||||||
|
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||||
|
a secure encryption key based on the password.
|
||||||
|
|
||||||
|
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||||
|
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||||
|
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||||
|
choose, you can pass the `New` functions from the different SHA packages to
|
||||||
|
pbkdf2.Key.
|
||||||
|
*/
|
||||||
|
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key derives a key from the password, salt and iteration count, returning a
|
||||||
|
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||||
|
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||||
|
// the supplied hash function.
|
||||||
|
//
|
||||||
|
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||||
|
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||||
|
// doing:
|
||||||
|
//
|
||||||
|
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||||
|
//
|
||||||
|
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||||
|
// RFC.
|
||||||
|
//
|
||||||
|
// Using a higher iteration count will increase the cost of an exhaustive
|
||||||
|
// search but will also make derivation proportionally slower.
|
||||||
|
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(h, password)
|
||||||
|
hashLen := prf.Size()
|
||||||
|
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
dk := make([]byte, 0, numBlocks*hashLen)
|
||||||
|
U := make([]byte, hashLen)
|
||||||
|
for block := 1; block <= numBlocks; block++ {
|
||||||
|
// N.B.: || means concatenation, ^ means XOR
|
||||||
|
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||||
|
// U_1 = PRF(password, salt || uint(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
buf[0] = byte(block >> 24)
|
||||||
|
buf[1] = byte(block >> 16)
|
||||||
|
buf[2] = byte(block >> 8)
|
||||||
|
buf[3] = byte(block)
|
||||||
|
prf.Write(buf[:4])
|
||||||
|
dk = prf.Sum(dk)
|
||||||
|
T := dk[len(dk)-hashLen:]
|
||||||
|
copy(U, T)
|
||||||
|
|
||||||
|
// U_n = PRF(password, U_(n-1))
|
||||||
|
for n := 2; n <= iter; n++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(U)
|
||||||
|
U = U[:0]
|
||||||
|
U = prf.Sum(U)
|
||||||
|
for x := range U {
|
||||||
|
T[x] ^= U[x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dk[:keyLen]
|
||||||
|
}
|
1
vendor/gopkg.in/square/go-jose.v2/.gitcookies.sh.enc
generated
vendored
Normal file
1
vendor/gopkg.in/square/go-jose.v2/.gitcookies.sh.enc
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
'|Ę&{tÄU|gGę(ěŹCy=+¨śňcű:u:/pś#~žü["±4¤!nŮAŞDK<Šuf˙hĹażÂ:şü¸ˇ´B/ŁŘ¤ą¤ň_<C588>hÎŰSăT*wĚxĽŻťą-ç|ťŕŔÓ<C594>ŃÄäóĚ㣗A$$â6ŁÁâG)8nĎpűĆˡ3ĚšśoďĎvŽB–3ż]xÝ“Ó2l§G•|qRŢŻ
ö2
5R–Ó×Ç$´ń˝YčˇŢÝ™l‘Ë«yAI"ŰŚ<C5B0>®íĂ»ąĽkÄ|Kĺţ[9ĆâŇĺ=°ú˙źń|@S•3ó#ćťx?ľV„,ľ‚SĆÝőśwPíogŇ6&V6 ©D.dBŠ7
|
7
vendor/gopkg.in/square/go-jose.v2/.gitignore
generated
vendored
Normal file
7
vendor/gopkg.in/square/go-jose.v2/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
*~
|
||||||
|
.*.swp
|
||||||
|
*.out
|
||||||
|
*.test
|
||||||
|
*.pem
|
||||||
|
*.cov
|
||||||
|
jose-util/jose-util
|
44
vendor/gopkg.in/square/go-jose.v2/.travis.yml
generated
vendored
Normal file
44
vendor/gopkg.in/square/go-jose.v2/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
go:
|
||||||
|
- '1.11.x'
|
||||||
|
- '1.12.x'
|
||||||
|
- tip
|
||||||
|
|
||||||
|
go_import_path: gopkg.in/square/go-jose.v2
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export PATH=$HOME/.local/bin:$PATH
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
# Install encrypted gitcookies to get around bandwidth-limits
|
||||||
|
# that is causing Travis-CI builds to fail. For more info, see
|
||||||
|
# https://github.com/golang/go/issues/12933
|
||||||
|
- openssl aes-256-cbc -K $encrypted_1528c3c2cafd_key -iv $encrypted_1528c3c2cafd_iv -in .gitcookies.sh.enc -out .gitcookies.sh -d || true
|
||||||
|
- bash .gitcookies.sh || true
|
||||||
|
- go get github.com/wadey/gocovmerge
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get github.com/stretchr/testify/assert
|
||||||
|
- go get golang.org/x/tools/cmd/cover || true
|
||||||
|
- go get code.google.com/p/go.tools/cmd/cover || true
|
||||||
|
- pip install cram --user
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test . -v -covermode=count -coverprofile=profile.cov
|
||||||
|
- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
|
||||||
|
- go test ./jwt -v -covermode=count -coverprofile=jwt/profile.cov
|
||||||
|
- go test ./json -v # no coverage for forked encoding/json package
|
||||||
|
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t
|
||||||
|
- cd ..
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- gocovmerge *.cov */*.cov > merged.coverprofile
|
||||||
|
- $HOME/gopath/bin/goveralls -coverprofile merged.coverprofile -service=travis-ci
|
||||||
|
|
10
vendor/gopkg.in/square/go-jose.v2/BUG-BOUNTY.md
generated
vendored
Normal file
10
vendor/gopkg.in/square/go-jose.v2/BUG-BOUNTY.md
generated
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Serious about security
|
||||||
|
======================
|
||||||
|
|
||||||
|
Square recognizes the important contributions the security research community
|
||||||
|
can make. We therefore encourage reporting security issues with the code
|
||||||
|
contained in this repository.
|
||||||
|
|
||||||
|
If you believe you have discovered a security vulnerability, please follow the
|
||||||
|
guidelines at <https://bugcrowd.com/squareopensource>.
|
||||||
|
|
14
vendor/gopkg.in/square/go-jose.v2/CONTRIBUTING.md
generated
vendored
Normal file
14
vendor/gopkg.in/square/go-jose.v2/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
If you would like to contribute code to go-jose you can do so through GitHub by
|
||||||
|
forking the repository and sending a pull request.
|
||||||
|
|
||||||
|
When submitting code, please make every effort to follow existing conventions
|
||||||
|
and style in order to keep the code as readable as possible. Please also make
|
||||||
|
sure all tests pass by running `go test`, and format your code with `go fmt`.
|
||||||
|
We also recommend using `golint` and `errcheck`.
|
||||||
|
|
||||||
|
Before your code can be accepted into the project you must also sign the
|
||||||
|
[Individual Contributor License Agreement][1].
|
||||||
|
|
||||||
|
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
|
202
vendor/gopkg.in/square/go-jose.v2/LICENSE
generated
vendored
Normal file
202
vendor/gopkg.in/square/go-jose.v2/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
118
vendor/gopkg.in/square/go-jose.v2/README.md
generated
vendored
Normal file
118
vendor/gopkg.in/square/go-jose.v2/README.md
generated
vendored
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
# Go JOSE
|
||||||
|
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
|
||||||
|
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
|
||||||
|
[![build](https://travis-ci.org/square/go-jose.svg?branch=v2)](https://travis-ci.org/square/go-jose)
|
||||||
|
[![coverage](https://coveralls.io/repos/github/square/go-jose/badge.svg?branch=v2)](https://coveralls.io/r/square/go-jose)
|
||||||
|
|
||||||
|
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||||
|
and Encryption set of standards. This includes support for JSON Web Encryption,
|
||||||
|
JSON Web Signature, and JSON Web Token standards.
|
||||||
|
|
||||||
|
**Disclaimer**: This library contains encryption software that is subject to
|
||||||
|
the U.S. Export Administration Regulations. You may not export, re-export,
|
||||||
|
transfer or download this code or any part of it in violation of any United
|
||||||
|
States law, directive or regulation. In particular this software may not be
|
||||||
|
exported or re-exported in any form or on any media to Iran, North Sudan,
|
||||||
|
Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
|
||||||
|
US maintained blocked list.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The implementation follows the
|
||||||
|
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
|
||||||
|
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
|
||||||
|
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
|
||||||
|
Tables of supported algorithms are shown below. The library supports both
|
||||||
|
the compact and full serialization formats, and has optional support for
|
||||||
|
multiple recipients. It also comes with a small command-line utility
|
||||||
|
([`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util))
|
||||||
|
for dealing with JOSE messages in a shell.
|
||||||
|
|
||||||
|
**Note**: We use a forked version of the `encoding/json` package from the Go
|
||||||
|
standard library which uses case-sensitive matching for member names (instead
|
||||||
|
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
|
||||||
|
This is to avoid differences in interpretation of messages between go-jose and
|
||||||
|
libraries in other languages.
|
||||||
|
|
||||||
|
### Versions
|
||||||
|
|
||||||
|
We use [gopkg.in](https://gopkg.in) for versioning.
|
||||||
|
|
||||||
|
[Version 2](https://gopkg.in/square/go-jose.v2)
|
||||||
|
([branch](https://github.com/square/go-jose/tree/v2),
|
||||||
|
[doc](https://godoc.org/gopkg.in/square/go-jose.v2)) is the current version:
|
||||||
|
|
||||||
|
import "gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
|
The old `v1` branch ([go-jose.v1](https://gopkg.in/square/go-jose.v1)) will
|
||||||
|
still receive backported bug fixes and security fixes, but otherwise
|
||||||
|
development is frozen. All new feature development takes place on the `v2`
|
||||||
|
branch. Version 2 also contains additional sub-packages such as the
|
||||||
|
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) implementation
|
||||||
|
contributed by [@shaxbee](https://github.com/shaxbee).
|
||||||
|
|
||||||
|
### Supported algorithms
|
||||||
|
|
||||||
|
See below for a table of supported algorithms. Algorithm identifiers match
|
||||||
|
the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
|
||||||
|
standard where possible. The Godoc reference has a list of constants.
|
||||||
|
|
||||||
|
Key encryption | Algorithm identifier(s)
|
||||||
|
:------------------------- | :------------------------------
|
||||||
|
RSA-PKCS#1v1.5 | RSA1_5
|
||||||
|
RSA-OAEP | RSA-OAEP, RSA-OAEP-256
|
||||||
|
AES key wrap | A128KW, A192KW, A256KW
|
||||||
|
AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW
|
||||||
|
ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
|
||||||
|
ECDH-ES (direct) | ECDH-ES<sup>1</sup>
|
||||||
|
Direct encryption | dir<sup>1</sup>
|
||||||
|
|
||||||
|
<sup>1. Not supported in multi-recipient mode</sup>
|
||||||
|
|
||||||
|
Signing / MAC | Algorithm identifier(s)
|
||||||
|
:------------------------- | :------------------------------
|
||||||
|
RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
|
||||||
|
RSASSA-PSS | PS256, PS384, PS512
|
||||||
|
HMAC | HS256, HS384, HS512
|
||||||
|
ECDSA | ES256, ES384, ES512
|
||||||
|
Ed25519 | EdDSA<sup>2</sup>
|
||||||
|
|
||||||
|
<sup>2. Only available in version 2 of the package</sup>
|
||||||
|
|
||||||
|
Content encryption | Algorithm identifier(s)
|
||||||
|
:------------------------- | :------------------------------
|
||||||
|
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
|
||||||
|
AES-GCM | A128GCM, A192GCM, A256GCM
|
||||||
|
|
||||||
|
Compression | Algorithm identifiers(s)
|
||||||
|
:------------------------- | -------------------------------
|
||||||
|
DEFLATE (RFC 1951) | DEF
|
||||||
|
|
||||||
|
### Supported key types
|
||||||
|
|
||||||
|
See below for a table of supported key types. These are understood by the
|
||||||
|
library, and can be passed to corresponding functions such as `NewEncrypter` or
|
||||||
|
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
|
||||||
|
allows attaching a key id.
|
||||||
|
|
||||||
|
Algorithm(s) | Corresponding types
|
||||||
|
:------------------------- | -------------------------------
|
||||||
|
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
|
||||||
|
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
|
||||||
|
EdDSA<sup>1</sup> | [ed25519.PublicKey](https://godoc.org/golang.org/x/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/golang.org/x/crypto/ed25519#PrivateKey)
|
||||||
|
AES, HMAC | []byte
|
||||||
|
|
||||||
|
<sup>1. Only available in version 2 of the package</sup>
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
|
||||||
|
|
||||||
|
Examples can be found in the Godoc
|
||||||
|
reference for this package. The
|
||||||
|
[`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util)
|
||||||
|
subdirectory also contains a small command-line utility which might be useful
|
||||||
|
as an example.
|
592
vendor/gopkg.in/square/go-jose.v2/asymmetric.go
generated
vendored
Normal file
592
vendor/gopkg.in/square/go-jose.v2/asymmetric.go
generated
vendored
Normal file
|
@ -0,0 +1,592 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
josecipher "gopkg.in/square/go-jose.v2/cipher"
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A generic RSA-based encrypter/verifier
|
||||||
|
type rsaEncrypterVerifier struct {
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic RSA-based decrypter/signer
|
||||||
|
type rsaDecrypterSigner struct {
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic EC-based encrypter/verifier
|
||||||
|
type ecEncrypterVerifier struct {
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type edEncrypterVerifier struct {
|
||||||
|
publicKey ed25519.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// A key generator for ECDH-ES
|
||||||
|
type ecKeyGenerator struct {
|
||||||
|
size int
|
||||||
|
algID string
|
||||||
|
publicKey *ecdsa.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic EC-based decrypter/signer
|
||||||
|
type ecDecrypterSigner struct {
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type edDecrypterSigner struct {
|
||||||
|
privateKey ed25519.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRSARecipient creates recipientKeyInfo based on the given key.
|
||||||
|
func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
|
||||||
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
switch keyAlg {
|
||||||
|
case RSA1_5, RSA_OAEP, RSA_OAEP_256:
|
||||||
|
default:
|
||||||
|
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKey == nil {
|
||||||
|
return recipientKeyInfo{}, errors.New("invalid public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientKeyInfo{
|
||||||
|
keyAlg: keyAlg,
|
||||||
|
keyEncrypter: &rsaEncrypterVerifier{
|
||||||
|
publicKey: publicKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newRSASigner creates a recipientSigInfo based on the given key.
|
||||||
|
func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) {
|
||||||
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
switch sigAlg {
|
||||||
|
case RS256, RS384, RS512, PS256, PS384, PS512:
|
||||||
|
default:
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == nil {
|
||||||
|
return recipientSigInfo{}, errors.New("invalid private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientSigInfo{
|
||||||
|
sigAlg: sigAlg,
|
||||||
|
publicKey: staticPublicKey(&JSONWebKey{
|
||||||
|
Key: privateKey.Public(),
|
||||||
|
}),
|
||||||
|
signer: &rsaDecrypterSigner{
|
||||||
|
privateKey: privateKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) {
|
||||||
|
if sigAlg != EdDSA {
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == nil {
|
||||||
|
return recipientSigInfo{}, errors.New("invalid private key")
|
||||||
|
}
|
||||||
|
return recipientSigInfo{
|
||||||
|
sigAlg: sigAlg,
|
||||||
|
publicKey: staticPublicKey(&JSONWebKey{
|
||||||
|
Key: privateKey.Public(),
|
||||||
|
}),
|
||||||
|
signer: &edDecrypterSigner{
|
||||||
|
privateKey: privateKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newECDHRecipient creates recipientKeyInfo based on the given key.
|
||||||
|
func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
|
||||||
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
switch keyAlg {
|
||||||
|
case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
|
||||||
|
default:
|
||||||
|
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
||||||
|
return recipientKeyInfo{}, errors.New("invalid public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientKeyInfo{
|
||||||
|
keyAlg: keyAlg,
|
||||||
|
keyEncrypter: &ecEncrypterVerifier{
|
||||||
|
publicKey: publicKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newECDSASigner creates a recipientSigInfo based on the given key.
|
||||||
|
func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) {
|
||||||
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
switch sigAlg {
|
||||||
|
case ES256, ES384, ES512:
|
||||||
|
default:
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey == nil {
|
||||||
|
return recipientSigInfo{}, errors.New("invalid private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientSigInfo{
|
||||||
|
sigAlg: sigAlg,
|
||||||
|
publicKey: staticPublicKey(&JSONWebKey{
|
||||||
|
Key: privateKey.Public(),
|
||||||
|
}),
|
||||||
|
signer: &ecDecrypterSigner{
|
||||||
|
privateKey: privateKey,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the given payload and update the object.
|
||||||
|
func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||||
|
encryptedKey, err := ctx.encrypt(cek, alg)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientInfo{
|
||||||
|
encryptedKey: encryptedKey,
|
||||||
|
header: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the given payload. Based on the key encryption algorithm,
|
||||||
|
// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
|
||||||
|
func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
|
||||||
|
switch alg {
|
||||||
|
case RSA1_5:
|
||||||
|
return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek)
|
||||||
|
case RSA_OAEP:
|
||||||
|
return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{})
|
||||||
|
case RSA_OAEP_256:
|
||||||
|
return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the given payload and return the content encryption key.
|
||||||
|
func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
|
return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the given payload. Based on the key encryption algorithm,
|
||||||
|
// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
|
||||||
|
func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) {
|
||||||
|
// Note: The random reader on decrypt operations is only used for blinding,
|
||||||
|
// so stubbing is meanlingless (hence the direct use of rand.Reader).
|
||||||
|
switch alg {
|
||||||
|
case RSA1_5:
|
||||||
|
defer func() {
|
||||||
|
// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
|
||||||
|
// because of an index out of bounds error, which we want to ignore.
|
||||||
|
// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
|
||||||
|
// only exists for preventing crashes with unpatched versions.
|
||||||
|
// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
|
||||||
|
// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
|
||||||
|
_ = recover()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Perform some input validation.
|
||||||
|
keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8
|
||||||
|
if keyBytes != len(jek) {
|
||||||
|
// Input size is incorrect, the encrypted payload should always match
|
||||||
|
// the size of the public modulus (e.g. using a 2048 bit key will
|
||||||
|
// produce 256 bytes of output). Reject this since it's invalid input.
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
cek, _, err := generator.genKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
|
||||||
|
// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
|
||||||
|
// the Million Message Attack on Cryptographic Message Syntax". We are
|
||||||
|
// therefore deliberately ignoring errors here.
|
||||||
|
_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek)
|
||||||
|
|
||||||
|
return cek, nil
|
||||||
|
case RSA_OAEP:
|
||||||
|
// Use rand.Reader for RSA blinding
|
||||||
|
return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{})
|
||||||
|
case RSA_OAEP_256:
|
||||||
|
// Use rand.Reader for RSA blinding
|
||||||
|
return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the given payload
|
||||||
|
func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
var hash crypto.Hash
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case RS256, PS256:
|
||||||
|
hash = crypto.SHA256
|
||||||
|
case RS384, PS384:
|
||||||
|
hash = crypto.SHA384
|
||||||
|
case RS512, PS512:
|
||||||
|
hash = crypto.SHA512
|
||||||
|
default:
|
||||||
|
return Signature{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := hash.New()
|
||||||
|
|
||||||
|
// According to documentation, Write() on hash never fails
|
||||||
|
_, _ = hasher.Write(payload)
|
||||||
|
hashed := hasher.Sum(nil)
|
||||||
|
|
||||||
|
var out []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case RS256, RS384, RS512:
|
||||||
|
out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
|
||||||
|
case PS256, PS384, PS512:
|
||||||
|
out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
|
||||||
|
SaltLength: rsa.PSSSaltLengthEqualsHash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signature{
|
||||||
|
Signature: out,
|
||||||
|
protected: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the given payload
|
||||||
|
func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||||
|
var hash crypto.Hash
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case RS256, PS256:
|
||||||
|
hash = crypto.SHA256
|
||||||
|
case RS384, PS384:
|
||||||
|
hash = crypto.SHA384
|
||||||
|
case RS512, PS512:
|
||||||
|
hash = crypto.SHA512
|
||||||
|
default:
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := hash.New()
|
||||||
|
|
||||||
|
// According to documentation, Write() on hash never fails
|
||||||
|
_, _ = hasher.Write(payload)
|
||||||
|
hashed := hasher.Sum(nil)
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case RS256, RS384, RS512:
|
||||||
|
return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature)
|
||||||
|
case PS256, PS384, PS512:
|
||||||
|
return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the given payload and update the object.
|
||||||
|
func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||||
|
switch alg {
|
||||||
|
case ECDH_ES:
|
||||||
|
// ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key.
|
||||||
|
return recipientInfo{
|
||||||
|
header: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
|
||||||
|
default:
|
||||||
|
return recipientInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
generator := ecKeyGenerator{
|
||||||
|
algID: string(alg),
|
||||||
|
publicKey: ctx.publicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case ECDH_ES_A128KW:
|
||||||
|
generator.size = 16
|
||||||
|
case ECDH_ES_A192KW:
|
||||||
|
generator.size = 24
|
||||||
|
case ECDH_ES_A256KW:
|
||||||
|
generator.size = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
kek, header, err := generator.genKey()
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(kek)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jek, err := josecipher.KeyWrap(block, cek)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientInfo{
|
||||||
|
encryptedKey: jek,
|
||||||
|
header: &header,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key size for EC key generator
|
||||||
|
func (ctx ecKeyGenerator) keySize() int {
|
||||||
|
return ctx.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a content encryption key for ECDH-ES
|
||||||
|
func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||||
|
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rawHeader{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
|
||||||
|
|
||||||
|
b, err := json.Marshal(&JSONWebKey{
|
||||||
|
Key: &priv.PublicKey,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := rawHeader{
|
||||||
|
headerEPK: makeRawMessage(b),
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, headers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the given payload and return the content encryption key.
|
||||||
|
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
|
epk, err := headers.getEPK()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid epk header")
|
||||||
|
}
|
||||||
|
if epk == nil {
|
||||||
|
return nil, errors.New("square/go-jose: missing epk header")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
|
||||||
|
if publicKey == nil || !ok {
|
||||||
|
return nil, errors.New("square/go-jose: invalid epk header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid public key in epk header")
|
||||||
|
}
|
||||||
|
|
||||||
|
apuData, err := headers.getAPU()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid apu header")
|
||||||
|
}
|
||||||
|
apvData, err := headers.getAPV()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid apv header")
|
||||||
|
}
|
||||||
|
|
||||||
|
deriveKey := func(algID string, size int) []byte {
|
||||||
|
return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keySize int
|
||||||
|
|
||||||
|
algorithm := headers.getAlgorithm()
|
||||||
|
switch algorithm {
|
||||||
|
case ECDH_ES:
|
||||||
|
// ECDH-ES uses direct key agreement, no key unwrapping necessary.
|
||||||
|
return deriveKey(string(headers.getEncryption()), generator.keySize()), nil
|
||||||
|
case ECDH_ES_A128KW:
|
||||||
|
keySize = 16
|
||||||
|
case ECDH_ES_A192KW:
|
||||||
|
keySize = 24
|
||||||
|
case ECDH_ES_A256KW:
|
||||||
|
keySize = 32
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
key := deriveKey(string(algorithm), keySize)
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
if alg != EdDSA {
|
||||||
|
return Signature{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0))
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signature{
|
||||||
|
Signature: sig,
|
||||||
|
protected: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||||
|
if alg != EdDSA {
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
ok := ed25519.Verify(ctx.publicKey, payload, signature)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("square/go-jose: ed25519 signature failed to verify")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the given payload
|
||||||
|
func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
var expectedBitSize int
|
||||||
|
var hash crypto.Hash
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case ES256:
|
||||||
|
expectedBitSize = 256
|
||||||
|
hash = crypto.SHA256
|
||||||
|
case ES384:
|
||||||
|
expectedBitSize = 384
|
||||||
|
hash = crypto.SHA384
|
||||||
|
case ES512:
|
||||||
|
expectedBitSize = 521
|
||||||
|
hash = crypto.SHA512
|
||||||
|
}
|
||||||
|
|
||||||
|
curveBits := ctx.privateKey.Curve.Params().BitSize
|
||||||
|
if expectedBitSize != curveBits {
|
||||||
|
return Signature{}, fmt.Errorf("square/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := hash.New()
|
||||||
|
|
||||||
|
// According to documentation, Write() on hash never fails
|
||||||
|
_, _ = hasher.Write(payload)
|
||||||
|
hashed := hasher.Sum(nil)
|
||||||
|
|
||||||
|
r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed)
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyBytes := curveBits / 8
|
||||||
|
if curveBits%8 > 0 {
|
||||||
|
keyBytes++
|
||||||
|
}
|
||||||
|
|
||||||
|
// We serialize the outputs (r and s) into big-endian byte arrays and pad
|
||||||
|
// them with zeros on the left to make sure the sizes work out. Both arrays
|
||||||
|
// must be keyBytes long, and the output must be 2*keyBytes long.
|
||||||
|
rBytes := r.Bytes()
|
||||||
|
rBytesPadded := make([]byte, keyBytes)
|
||||||
|
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||||
|
|
||||||
|
sBytes := s.Bytes()
|
||||||
|
sBytesPadded := make([]byte, keyBytes)
|
||||||
|
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||||
|
|
||||||
|
out := append(rBytesPadded, sBytesPadded...)
|
||||||
|
|
||||||
|
return Signature{
|
||||||
|
Signature: out,
|
||||||
|
protected: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the given payload
|
||||||
|
func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||||
|
var keySize int
|
||||||
|
var hash crypto.Hash
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case ES256:
|
||||||
|
keySize = 32
|
||||||
|
hash = crypto.SHA256
|
||||||
|
case ES384:
|
||||||
|
keySize = 48
|
||||||
|
hash = crypto.SHA384
|
||||||
|
case ES512:
|
||||||
|
keySize = 66
|
||||||
|
hash = crypto.SHA512
|
||||||
|
default:
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(signature) != 2*keySize {
|
||||||
|
return fmt.Errorf("square/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher := hash.New()
|
||||||
|
|
||||||
|
// According to documentation, Write() on hash never fails
|
||||||
|
_, _ = hasher.Write(payload)
|
||||||
|
hashed := hasher.Sum(nil)
|
||||||
|
|
||||||
|
r := big.NewInt(0).SetBytes(signature[:keySize])
|
||||||
|
s := big.NewInt(0).SetBytes(signature[keySize:])
|
||||||
|
|
||||||
|
match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
|
||||||
|
if !match {
|
||||||
|
return errors.New("square/go-jose: ecdsa signature failed to verify")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
196
vendor/gopkg.in/square/go-jose.v2/cipher/cbc_hmac.go
generated
vendored
Normal file
196
vendor/gopkg.in/square/go-jose.v2/cipher/cbc_hmac.go
generated
vendored
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package josecipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nonceBytes = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC.
|
||||||
|
func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) {
|
||||||
|
keySize := len(key) / 2
|
||||||
|
integrityKey := key[:keySize]
|
||||||
|
encryptionKey := key[keySize:]
|
||||||
|
|
||||||
|
blockCipher, err := newBlockCipher(encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash func() hash.Hash
|
||||||
|
switch keySize {
|
||||||
|
case 16:
|
||||||
|
hash = sha256.New
|
||||||
|
case 24:
|
||||||
|
hash = sha512.New384
|
||||||
|
case 32:
|
||||||
|
hash = sha512.New
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cbcAEAD{
|
||||||
|
hash: hash,
|
||||||
|
blockCipher: blockCipher,
|
||||||
|
authtagBytes: keySize,
|
||||||
|
integrityKey: integrityKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AEAD based on CBC+HMAC
|
||||||
|
type cbcAEAD struct {
|
||||||
|
hash func() hash.Hash
|
||||||
|
authtagBytes int
|
||||||
|
integrityKey []byte
|
||||||
|
blockCipher cipher.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *cbcAEAD) NonceSize() int {
|
||||||
|
return nonceBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *cbcAEAD) Overhead() int {
|
||||||
|
// Maximum overhead is block size (for padding) plus auth tag length, where
|
||||||
|
// the length of the auth tag is equivalent to the key size.
|
||||||
|
return ctx.blockCipher.BlockSize() + ctx.authtagBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seal encrypts and authenticates the plaintext.
|
||||||
|
func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||||
|
// Output buffer -- must take care not to mangle plaintext input.
|
||||||
|
ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
|
||||||
|
copy(ciphertext, plaintext)
|
||||||
|
ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
|
||||||
|
|
||||||
|
cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
|
||||||
|
|
||||||
|
cbc.CryptBlocks(ciphertext, ciphertext)
|
||||||
|
authtag := ctx.computeAuthTag(data, nonce, ciphertext)
|
||||||
|
|
||||||
|
ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
|
||||||
|
copy(out, ciphertext)
|
||||||
|
copy(out[len(ciphertext):], authtag)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open decrypts and authenticates the ciphertext.
|
||||||
|
func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||||
|
if len(ciphertext) < ctx.authtagBytes {
|
||||||
|
return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := len(ciphertext) - ctx.authtagBytes
|
||||||
|
expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
|
||||||
|
match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
|
||||||
|
if match != 1 {
|
||||||
|
return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
|
||||||
|
}
|
||||||
|
|
||||||
|
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
|
||||||
|
|
||||||
|
// Make copy of ciphertext buffer, don't want to modify in place
|
||||||
|
buffer := append([]byte{}, []byte(ciphertext[:offset])...)
|
||||||
|
|
||||||
|
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
|
||||||
|
return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
|
||||||
|
}
|
||||||
|
|
||||||
|
cbc.CryptBlocks(buffer, buffer)
|
||||||
|
|
||||||
|
// Remove padding
|
||||||
|
plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
|
||||||
|
copy(out, plaintext)
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute an authentication tag
|
||||||
|
func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
|
||||||
|
buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
|
||||||
|
n := 0
|
||||||
|
n += copy(buffer, aad)
|
||||||
|
n += copy(buffer[n:], nonce)
|
||||||
|
n += copy(buffer[n:], ciphertext)
|
||||||
|
binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
|
||||||
|
|
||||||
|
// According to documentation, Write() on hash.Hash never fails.
|
||||||
|
hmac := hmac.New(ctx.hash, ctx.integrityKey)
|
||||||
|
_, _ = hmac.Write(buffer)
|
||||||
|
|
||||||
|
return hmac.Sum(nil)[:ctx.authtagBytes]
|
||||||
|
}
|
||||||
|
|
||||||
|
// resize ensures the the given slice has a capacity of at least n bytes.
|
||||||
|
// If the capacity of the slice is less than n, a new slice is allocated
|
||||||
|
// and the existing data will be copied.
|
||||||
|
func resize(in []byte, n uint64) (head, tail []byte) {
|
||||||
|
if uint64(cap(in)) >= n {
|
||||||
|
head = in[:n]
|
||||||
|
} else {
|
||||||
|
head = make([]byte, n)
|
||||||
|
copy(head, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
tail = head[len(in):]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply padding
|
||||||
|
func padBuffer(buffer []byte, blockSize int) []byte {
|
||||||
|
missing := blockSize - (len(buffer) % blockSize)
|
||||||
|
ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
|
||||||
|
padding := bytes.Repeat([]byte{byte(missing)}, missing)
|
||||||
|
copy(out, padding)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove padding
|
||||||
|
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
|
||||||
|
if len(buffer)%blockSize != 0 {
|
||||||
|
return nil, errors.New("square/go-jose: invalid padding")
|
||||||
|
}
|
||||||
|
|
||||||
|
last := buffer[len(buffer)-1]
|
||||||
|
count := int(last)
|
||||||
|
|
||||||
|
if count == 0 || count > blockSize || count > len(buffer) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid padding")
|
||||||
|
}
|
||||||
|
|
||||||
|
padding := bytes.Repeat([]byte{last}, count)
|
||||||
|
if !bytes.HasSuffix(buffer, padding) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid padding")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[:len(buffer)-count], nil
|
||||||
|
}
|
75
vendor/gopkg.in/square/go-jose.v2/cipher/concat_kdf.go
generated
vendored
Normal file
75
vendor/gopkg.in/square/go-jose.v2/cipher/concat_kdf.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package josecipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"encoding/binary"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type concatKDF struct {
|
||||||
|
z, info []byte
|
||||||
|
i uint32
|
||||||
|
cache []byte
|
||||||
|
hasher hash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConcatKDF builds a KDF reader based on the given inputs.
|
||||||
|
func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
|
||||||
|
buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo)))
|
||||||
|
n := 0
|
||||||
|
n += copy(buffer, algID)
|
||||||
|
n += copy(buffer[n:], ptyUInfo)
|
||||||
|
n += copy(buffer[n:], ptyVInfo)
|
||||||
|
n += copy(buffer[n:], supPubInfo)
|
||||||
|
copy(buffer[n:], supPrivInfo)
|
||||||
|
|
||||||
|
hasher := hash.New()
|
||||||
|
|
||||||
|
return &concatKDF{
|
||||||
|
z: z,
|
||||||
|
info: buffer,
|
||||||
|
hasher: hasher,
|
||||||
|
cache: []byte{},
|
||||||
|
i: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *concatKDF) Read(out []byte) (int, error) {
|
||||||
|
copied := copy(out, ctx.cache)
|
||||||
|
ctx.cache = ctx.cache[copied:]
|
||||||
|
|
||||||
|
for copied < len(out) {
|
||||||
|
ctx.hasher.Reset()
|
||||||
|
|
||||||
|
// Write on a hash.Hash never fails
|
||||||
|
_ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i)
|
||||||
|
_, _ = ctx.hasher.Write(ctx.z)
|
||||||
|
_, _ = ctx.hasher.Write(ctx.info)
|
||||||
|
|
||||||
|
hash := ctx.hasher.Sum(nil)
|
||||||
|
chunkCopied := copy(out[copied:], hash)
|
||||||
|
copied += chunkCopied
|
||||||
|
ctx.cache = hash[chunkCopied:]
|
||||||
|
|
||||||
|
ctx.i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied, nil
|
||||||
|
}
|
86
vendor/gopkg.in/square/go-jose.v2/cipher/ecdh_es.go
generated
vendored
Normal file
86
vendor/gopkg.in/square/go-jose.v2/cipher/ecdh_es.go
generated
vendored
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package josecipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
|
||||||
|
// It is an error to call this function with a private/public key that are not on the same
|
||||||
|
// curve. Callers must ensure that the keys are valid before calling this function. Output
|
||||||
|
// size may be at most 1<<16 bytes (64 KiB).
|
||||||
|
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
|
||||||
|
if size > 1<<16 {
|
||||||
|
panic("ECDH-ES output size too large, must be less than or equal to 1<<16")
|
||||||
|
}
|
||||||
|
|
||||||
|
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
|
||||||
|
algID := lengthPrefixed([]byte(alg))
|
||||||
|
ptyUInfo := lengthPrefixed(apuData)
|
||||||
|
ptyVInfo := lengthPrefixed(apvData)
|
||||||
|
|
||||||
|
// suppPubInfo is the encoded length of the output size in bits
|
||||||
|
supPubInfo := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8)
|
||||||
|
|
||||||
|
if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) {
|
||||||
|
panic("public key not on same curve as private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
z, _ := priv.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
|
||||||
|
zBytes := z.Bytes()
|
||||||
|
|
||||||
|
// Note that calling z.Bytes() on a big.Int may strip leading zero bytes from
|
||||||
|
// the returned byte array. This can lead to a problem where zBytes will be
|
||||||
|
// shorter than expected which breaks the key derivation. Therefore we must pad
|
||||||
|
// to the full length of the expected coordinate here before calling the KDF.
|
||||||
|
octSize := dSize(priv.Curve)
|
||||||
|
if len(zBytes) != octSize {
|
||||||
|
zBytes = append(bytes.Repeat([]byte{0}, octSize-len(zBytes)), zBytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := NewConcatKDF(crypto.SHA256, zBytes, algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{})
|
||||||
|
key := make([]byte, size)
|
||||||
|
|
||||||
|
// Read on the KDF will never fail
|
||||||
|
_, _ = reader.Read(key)
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
// dSize returns the size in octets for a coordinate on a elliptic curve.
|
||||||
|
func dSize(curve elliptic.Curve) int {
|
||||||
|
order := curve.Params().P
|
||||||
|
bitLen := order.BitLen()
|
||||||
|
size := bitLen / 8
|
||||||
|
if bitLen%8 != 0 {
|
||||||
|
size++
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func lengthPrefixed(data []byte) []byte {
|
||||||
|
out := make([]byte, len(data)+4)
|
||||||
|
binary.BigEndian.PutUint32(out, uint32(len(data)))
|
||||||
|
copy(out[4:], data)
|
||||||
|
return out
|
||||||
|
}
|
109
vendor/gopkg.in/square/go-jose.v2/cipher/key_wrap.go
generated
vendored
Normal file
109
vendor/gopkg.in/square/go-jose.v2/cipher/key_wrap.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package josecipher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
|
||||||
|
|
||||||
|
// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
|
||||||
|
func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
|
||||||
|
if len(cek)%8 != 0 {
|
||||||
|
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := len(cek) / 8
|
||||||
|
r := make([][]byte, n)
|
||||||
|
|
||||||
|
for i := range r {
|
||||||
|
r[i] = make([]byte, 8)
|
||||||
|
copy(r[i], cek[i*8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, 16)
|
||||||
|
tBytes := make([]byte, 8)
|
||||||
|
copy(buffer, defaultIV)
|
||||||
|
|
||||||
|
for t := 0; t < 6*n; t++ {
|
||||||
|
copy(buffer[8:], r[t%n])
|
||||||
|
|
||||||
|
block.Encrypt(buffer, buffer)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||||
|
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
buffer[i] = buffer[i] ^ tBytes[i]
|
||||||
|
}
|
||||||
|
copy(r[t%n], buffer[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]byte, (n+1)*8)
|
||||||
|
copy(out, buffer[:8])
|
||||||
|
for i := range r {
|
||||||
|
copy(out[(i+1)*8:], r[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
|
||||||
|
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
|
||||||
|
if len(ciphertext)%8 != 0 {
|
||||||
|
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
|
||||||
|
}
|
||||||
|
|
||||||
|
n := (len(ciphertext) / 8) - 1
|
||||||
|
r := make([][]byte, n)
|
||||||
|
|
||||||
|
for i := range r {
|
||||||
|
r[i] = make([]byte, 8)
|
||||||
|
copy(r[i], ciphertext[(i+1)*8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, 16)
|
||||||
|
tBytes := make([]byte, 8)
|
||||||
|
copy(buffer[:8], ciphertext[:8])
|
||||||
|
|
||||||
|
for t := 6*n - 1; t >= 0; t-- {
|
||||||
|
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||||
|
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
buffer[i] = buffer[i] ^ tBytes[i]
|
||||||
|
}
|
||||||
|
copy(buffer[8:], r[t%n])
|
||||||
|
|
||||||
|
block.Decrypt(buffer, buffer)
|
||||||
|
|
||||||
|
copy(r[t%n], buffer[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
|
||||||
|
return nil, errors.New("square/go-jose: failed to unwrap key")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]byte, n*8)
|
||||||
|
for i := range r {
|
||||||
|
copy(out[i*8:], r[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
541
vendor/gopkg.in/square/go-jose.v2/crypter.go
generated
vendored
Normal file
541
vendor/gopkg.in/square/go-jose.v2/crypter.go
generated
vendored
Normal file
|
@ -0,0 +1,541 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encrypter represents an encrypter which produces an encrypted JWE object.
|
||||||
|
type Encrypter interface {
|
||||||
|
Encrypt(plaintext []byte) (*JSONWebEncryption, error)
|
||||||
|
EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error)
|
||||||
|
Options() EncrypterOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic content cipher
|
||||||
|
type contentCipher interface {
|
||||||
|
keySize() int
|
||||||
|
encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error)
|
||||||
|
decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A key generator (for generating/getting a CEK)
|
||||||
|
type keyGenerator interface {
|
||||||
|
keySize() int
|
||||||
|
genKey() ([]byte, rawHeader, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic key encrypter
|
||||||
|
type keyEncrypter interface {
|
||||||
|
encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic key decrypter
|
||||||
|
type keyDecrypter interface {
|
||||||
|
decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generic encrypter based on the given key encrypter and content cipher.
|
||||||
|
type genericEncrypter struct {
|
||||||
|
contentAlg ContentEncryption
|
||||||
|
compressionAlg CompressionAlgorithm
|
||||||
|
cipher contentCipher
|
||||||
|
recipients []recipientKeyInfo
|
||||||
|
keyGenerator keyGenerator
|
||||||
|
extraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type recipientKeyInfo struct {
|
||||||
|
keyID string
|
||||||
|
keyAlg KeyAlgorithm
|
||||||
|
keyEncrypter keyEncrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncrypterOptions represents options that can be set on new encrypters.
|
||||||
|
type EncrypterOptions struct {
|
||||||
|
Compression CompressionAlgorithm
|
||||||
|
|
||||||
|
// Optional map of additional keys to be inserted into the protected header
|
||||||
|
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||||
|
// additional values here. All values must be JSON-serializable.
|
||||||
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||||
|
// if necessary. It returns itself and so can be used in a fluent style.
|
||||||
|
func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions {
|
||||||
|
if eo.ExtraHeaders == nil {
|
||||||
|
eo.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
|
}
|
||||||
|
eo.ExtraHeaders[k] = v
|
||||||
|
return eo
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContentType adds a content type ("cty") header and returns the updated
|
||||||
|
// EncrypterOptions.
|
||||||
|
func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions {
|
||||||
|
return eo.WithHeader(HeaderContentType, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithType adds a type ("typ") header and returns the updated EncrypterOptions.
|
||||||
|
func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
|
||||||
|
return eo.WithHeader(HeaderType, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recipient represents an algorithm/key to encrypt messages to.
|
||||||
|
//
|
||||||
|
// PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used
|
||||||
|
// on the password-based encryption algorithms PBES2-HS256+A128KW,
|
||||||
|
// PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe
|
||||||
|
// default of 100000 will be used for the count and a 128-bit random salt will
|
||||||
|
// be generated.
|
||||||
|
type Recipient struct {
|
||||||
|
Algorithm KeyAlgorithm
|
||||||
|
Key interface{}
|
||||||
|
KeyID string
|
||||||
|
PBES2Count int
|
||||||
|
PBES2Salt []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncrypter creates an appropriate encrypter based on the key type
|
||||||
|
func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) {
|
||||||
|
encrypter := &genericEncrypter{
|
||||||
|
contentAlg: enc,
|
||||||
|
recipients: []recipientKeyInfo{},
|
||||||
|
cipher: getContentCipher(enc),
|
||||||
|
}
|
||||||
|
if opts != nil {
|
||||||
|
encrypter.compressionAlg = opts.Compression
|
||||||
|
encrypter.extraHeaders = opts.ExtraHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypter.cipher == nil {
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyID string
|
||||||
|
var rawKey interface{}
|
||||||
|
switch encryptionKey := rcpt.Key.(type) {
|
||||||
|
case JSONWebKey:
|
||||||
|
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
|
||||||
|
case *JSONWebKey:
|
||||||
|
keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key
|
||||||
|
case OpaqueKeyEncrypter:
|
||||||
|
keyID, rawKey = encryptionKey.KeyID(), encryptionKey
|
||||||
|
default:
|
||||||
|
rawKey = encryptionKey
|
||||||
|
}
|
||||||
|
|
||||||
|
switch rcpt.Algorithm {
|
||||||
|
case DIRECT:
|
||||||
|
// Direct encryption mode must be treated differently
|
||||||
|
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
if encrypter.cipher.keySize() != len(rawKey.([]byte)) {
|
||||||
|
return nil, ErrInvalidKeySize
|
||||||
|
}
|
||||||
|
encrypter.keyGenerator = staticKeyGenerator{
|
||||||
|
key: rawKey.([]byte),
|
||||||
|
}
|
||||||
|
recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, rawKey.([]byte))
|
||||||
|
recipientInfo.keyID = keyID
|
||||||
|
if rcpt.KeyID != "" {
|
||||||
|
recipientInfo.keyID = rcpt.KeyID
|
||||||
|
}
|
||||||
|
encrypter.recipients = []recipientKeyInfo{recipientInfo}
|
||||||
|
return encrypter, nil
|
||||||
|
case ECDH_ES:
|
||||||
|
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
|
||||||
|
typeOf := reflect.TypeOf(rawKey)
|
||||||
|
if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
encrypter.keyGenerator = ecKeyGenerator{
|
||||||
|
size: encrypter.cipher.keySize(),
|
||||||
|
algID: string(enc),
|
||||||
|
publicKey: rawKey.(*ecdsa.PublicKey),
|
||||||
|
}
|
||||||
|
recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, rawKey.(*ecdsa.PublicKey))
|
||||||
|
recipientInfo.keyID = keyID
|
||||||
|
if rcpt.KeyID != "" {
|
||||||
|
recipientInfo.keyID = rcpt.KeyID
|
||||||
|
}
|
||||||
|
encrypter.recipients = []recipientKeyInfo{recipientInfo}
|
||||||
|
return encrypter, nil
|
||||||
|
default:
|
||||||
|
// Can just add a standard recipient
|
||||||
|
encrypter.keyGenerator = randomKeyGenerator{
|
||||||
|
size: encrypter.cipher.keySize(),
|
||||||
|
}
|
||||||
|
err := encrypter.addRecipient(rcpt)
|
||||||
|
return encrypter, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiEncrypter creates a multi-encrypter based on the given parameters
|
||||||
|
func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) {
|
||||||
|
cipher := getContentCipher(enc)
|
||||||
|
|
||||||
|
if cipher == nil {
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
if rcpts == nil || len(rcpts) == 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: recipients is nil or empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypter := &genericEncrypter{
|
||||||
|
contentAlg: enc,
|
||||||
|
recipients: []recipientKeyInfo{},
|
||||||
|
cipher: cipher,
|
||||||
|
keyGenerator: randomKeyGenerator{
|
||||||
|
size: cipher.keySize(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
encrypter.compressionAlg = opts.Compression
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, recipient := range rcpts {
|
||||||
|
err := encrypter.addRecipient(recipient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return encrypter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
|
||||||
|
var recipientInfo recipientKeyInfo
|
||||||
|
|
||||||
|
switch recipient.Algorithm {
|
||||||
|
case DIRECT, ECDH_ES:
|
||||||
|
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
|
||||||
|
if recipient.KeyID != "" {
|
||||||
|
recipientInfo.keyID = recipient.KeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
switch recipient.Algorithm {
|
||||||
|
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||||
|
if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok {
|
||||||
|
sr.p2c = recipient.PBES2Count
|
||||||
|
sr.p2s = recipient.PBES2Salt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
ctx.recipients = append(ctx.recipients, recipientInfo)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
|
||||||
|
switch encryptionKey := encryptionKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return newRSARecipient(alg, encryptionKey)
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return newECDHRecipient(alg, encryptionKey)
|
||||||
|
case []byte:
|
||||||
|
return newSymmetricRecipient(alg, encryptionKey)
|
||||||
|
case string:
|
||||||
|
return newSymmetricRecipient(alg, []byte(encryptionKey))
|
||||||
|
case *JSONWebKey:
|
||||||
|
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
||||||
|
recipient.keyID = encryptionKey.KeyID
|
||||||
|
return recipient, err
|
||||||
|
}
|
||||||
|
if encrypter, ok := encryptionKey.(OpaqueKeyEncrypter); ok {
|
||||||
|
return newOpaqueKeyEncrypter(alg, encrypter)
|
||||||
|
}
|
||||||
|
return recipientKeyInfo{}, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDecrypter creates an appropriate decrypter based on the key type
|
||||||
|
func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
|
||||||
|
switch decryptionKey := decryptionKey.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return &rsaDecrypterSigner{
|
||||||
|
privateKey: decryptionKey,
|
||||||
|
}, nil
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return &ecDecrypterSigner{
|
||||||
|
privateKey: decryptionKey,
|
||||||
|
}, nil
|
||||||
|
case []byte:
|
||||||
|
return &symmetricKeyCipher{
|
||||||
|
key: decryptionKey,
|
||||||
|
}, nil
|
||||||
|
case string:
|
||||||
|
return &symmetricKeyCipher{
|
||||||
|
key: []byte(decryptionKey),
|
||||||
|
}, nil
|
||||||
|
case JSONWebKey:
|
||||||
|
return newDecrypter(decryptionKey.Key)
|
||||||
|
case *JSONWebKey:
|
||||||
|
return newDecrypter(decryptionKey.Key)
|
||||||
|
}
|
||||||
|
if okd, ok := decryptionKey.(OpaqueKeyDecrypter); ok {
|
||||||
|
return &opaqueKeyDecrypter{decrypter: okd}, nil
|
||||||
|
}
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of encrypt method producing a JWE object.
|
||||||
|
func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) {
|
||||||
|
return ctx.EncryptWithAuthData(plaintext, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of encrypt method producing a JWE object.
|
||||||
|
func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) {
|
||||||
|
obj := &JSONWebEncryption{}
|
||||||
|
obj.aad = aad
|
||||||
|
|
||||||
|
obj.protected = &rawHeader{}
|
||||||
|
err := obj.protected.set(headerEncryption, ctx.contentAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.recipients = make([]recipientInfo, len(ctx.recipients))
|
||||||
|
|
||||||
|
if len(ctx.recipients) == 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
|
||||||
|
}
|
||||||
|
|
||||||
|
cek, headers, err := ctx.keyGenerator.genKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.protected.merge(&headers)
|
||||||
|
|
||||||
|
for i, info := range ctx.recipients {
|
||||||
|
recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = recipient.header.set(headerAlgorithm, info.keyAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.keyID != "" {
|
||||||
|
err = recipient.header.set(headerKeyID, info.keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.recipients[i] = recipient
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ctx.recipients) == 1 {
|
||||||
|
// Move per-recipient headers into main protected header if there's
|
||||||
|
// only a single recipient.
|
||||||
|
obj.protected.merge(obj.recipients[0].header)
|
||||||
|
obj.recipients[0].header = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.compressionAlg != NONE {
|
||||||
|
plaintext, err = compress(ctx.compressionAlg, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = obj.protected.set(headerCompression, ctx.compressionAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range ctx.extraHeaders {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
(*obj.protected)[k] = makeRawMessage(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
authData := obj.computeAuthData()
|
||||||
|
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.iv = parts.iv
|
||||||
|
obj.ciphertext = parts.ciphertext
|
||||||
|
obj.tag = parts.tag
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericEncrypter) Options() EncrypterOptions {
|
||||||
|
return EncrypterOptions{
|
||||||
|
Compression: ctx.compressionAlg,
|
||||||
|
ExtraHeaders: ctx.extraHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt and validate the object and return the plaintext. Note that this
|
||||||
|
// function does not support multi-recipient, if you desire multi-recipient
|
||||||
|
// decryption use DecryptMulti instead.
|
||||||
|
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
|
||||||
|
headers := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
|
if len(obj.recipients) > 1 {
|
||||||
|
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
|
||||||
|
}
|
||||||
|
|
||||||
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(critical) > 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypter, err := newDecrypter(decryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher := getContentCipher(headers.getEncryption())
|
||||||
|
if cipher == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.getEncryption()))
|
||||||
|
}
|
||||||
|
|
||||||
|
generator := randomKeyGenerator{
|
||||||
|
size: cipher.keySize(),
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := &aeadParts{
|
||||||
|
iv: obj.iv,
|
||||||
|
ciphertext: obj.ciphertext,
|
||||||
|
tag: obj.tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
authData := obj.computeAuthData()
|
||||||
|
|
||||||
|
var plaintext []byte
|
||||||
|
recipient := obj.recipients[0]
|
||||||
|
recipientHeaders := obj.mergedHeaders(&recipient)
|
||||||
|
|
||||||
|
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
|
||||||
|
if err == nil {
|
||||||
|
// Found a valid CEK -- let's try to decrypt.
|
||||||
|
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if plaintext == nil {
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
|
plaintext, err = decompress(comp, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintext, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptMulti decrypts and validates the object and returns the plaintexts,
|
||||||
|
// with support for multiple recipients. It returns the index of the recipient
|
||||||
|
// for which the decryption was successful, the merged headers for that recipient,
|
||||||
|
// and the plaintext.
|
||||||
|
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
|
||||||
|
globalHeaders := obj.mergedHeaders(nil)
|
||||||
|
|
||||||
|
critical, err := globalHeaders.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: invalid crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(critical) > 0 {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypter, err := newDecrypter(decryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryption := globalHeaders.getEncryption()
|
||||||
|
cipher := getContentCipher(encryption)
|
||||||
|
if cipher == nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(encryption))
|
||||||
|
}
|
||||||
|
|
||||||
|
generator := randomKeyGenerator{
|
||||||
|
size: cipher.keySize(),
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := &aeadParts{
|
||||||
|
iv: obj.iv,
|
||||||
|
ciphertext: obj.ciphertext,
|
||||||
|
tag: obj.tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
authData := obj.computeAuthData()
|
||||||
|
|
||||||
|
index := -1
|
||||||
|
var plaintext []byte
|
||||||
|
var headers rawHeader
|
||||||
|
|
||||||
|
for i, recipient := range obj.recipients {
|
||||||
|
recipientHeaders := obj.mergedHeaders(&recipient)
|
||||||
|
|
||||||
|
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
|
||||||
|
if err == nil {
|
||||||
|
// Found a valid CEK -- let's try to decrypt.
|
||||||
|
plaintext, err = cipher.decrypt(cek, authData, parts)
|
||||||
|
if err == nil {
|
||||||
|
index = i
|
||||||
|
headers = recipientHeaders
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if plaintext == nil || err != nil {
|
||||||
|
return -1, Header{}, nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
|
plaintext, err = decompress(comp, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitized, err := headers.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("square/go-jose: failed to sanitize header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return index, sanitized, plaintext, err
|
||||||
|
}
|
27
vendor/gopkg.in/square/go-jose.v2/doc.go
generated
vendored
Normal file
27
vendor/gopkg.in/square/go-jose.v2/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||||
|
and Encryption set of standards. It implements encryption and signing based on
|
||||||
|
the JSON Web Encryption and JSON Web Signature standards, with optional JSON
|
||||||
|
Web Token support available in a sub-package. The library supports both the
|
||||||
|
compact and full serialization formats, and has optional support for multiple
|
||||||
|
recipients.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package jose
|
185
vendor/gopkg.in/square/go-jose.v2/encoding.go
generated
vendored
Normal file
185
vendor/gopkg.in/square/go-jose.v2/encoding.go
generated
vendored
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/flate"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper function to serialize known-good objects.
|
||||||
|
// Precondition: value is not a nil pointer.
|
||||||
|
func mustSerializeJSON(value interface{}) []byte {
|
||||||
|
out, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// We never want to serialize the top-level value "null," since it's not a
|
||||||
|
// valid JOSE message. But if a caller passes in a nil pointer to this method,
|
||||||
|
// MarshalJSON will happily serialize it as the top-level value "null". If
|
||||||
|
// that value is then embedded in another operation, for instance by being
|
||||||
|
// base64-encoded and fed as input to a signing algorithm
|
||||||
|
// (https://github.com/square/go-jose/issues/22), the result will be
|
||||||
|
// incorrect. Because this method is intended for known-good objects, and a nil
|
||||||
|
// pointer is not a known-good object, we are free to panic in this case.
|
||||||
|
// Note: It's not possible to directly check whether the data pointed at by an
|
||||||
|
// interface is a nil pointer, so we do this hacky workaround.
|
||||||
|
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
|
||||||
|
if string(out) == "null" {
|
||||||
|
panic("Tried to serialize a nil pointer.")
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip all newlines and whitespace
|
||||||
|
func stripWhitespace(data string) string {
|
||||||
|
buf := strings.Builder{}
|
||||||
|
buf.Grow(len(data))
|
||||||
|
for _, r := range data {
|
||||||
|
if !unicode.IsSpace(r) {
|
||||||
|
buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform compression based on algorithm
|
||||||
|
func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
|
||||||
|
switch algorithm {
|
||||||
|
case DEFLATE:
|
||||||
|
return deflate(input)
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform decompression based on algorithm
|
||||||
|
func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
|
||||||
|
switch algorithm {
|
||||||
|
case DEFLATE:
|
||||||
|
return inflate(input)
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress with DEFLATE
|
||||||
|
func deflate(input []byte) ([]byte, error) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// Writing to byte buffer, err is always nil
|
||||||
|
writer, _ := flate.NewWriter(output, 1)
|
||||||
|
_, _ = io.Copy(writer, bytes.NewBuffer(input))
|
||||||
|
|
||||||
|
err := writer.Close()
|
||||||
|
return output.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress with DEFLATE
|
||||||
|
func inflate(input []byte) ([]byte, error) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
reader := flate.NewReader(bytes.NewBuffer(input))
|
||||||
|
|
||||||
|
_, err := io.Copy(output, reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = reader.Close()
|
||||||
|
return output.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
|
||||||
|
type byteBuffer struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuffer(data []byte) *byteBuffer {
|
||||||
|
if data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &byteBuffer{
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
|
||||||
|
if len(data) > length {
|
||||||
|
panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
|
||||||
|
}
|
||||||
|
pad := make([]byte, length-len(data))
|
||||||
|
return newBuffer(append(pad, data...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufferFromInt(num uint64) *byteBuffer {
|
||||||
|
data := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(data, num)
|
||||||
|
return newBuffer(bytes.TrimLeft(data, "\x00"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *byteBuffer) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(b.base64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *byteBuffer) UnmarshalJSON(data []byte) error {
|
||||||
|
var encoded string
|
||||||
|
err := json.Unmarshal(data, &encoded)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*b = *newBuffer(decoded)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *byteBuffer) base64() string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString(b.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *byteBuffer) bytes() []byte {
|
||||||
|
// Handling nil here allows us to transparently handle nil slices when serializing.
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteBuffer) bigInt() *big.Int {
|
||||||
|
return new(big.Int).SetBytes(b.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteBuffer) toInt() int {
|
||||||
|
return int(b.bigInt().Int64())
|
||||||
|
}
|
27
vendor/gopkg.in/square/go-jose.v2/json/LICENSE
generated
vendored
Normal file
27
vendor/gopkg.in/square/go-jose.v2/json/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
13
vendor/gopkg.in/square/go-jose.v2/json/README.md
generated
vendored
Normal file
13
vendor/gopkg.in/square/go-jose.v2/json/README.md
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Safe JSON
|
||||||
|
|
||||||
|
This repository contains a fork of the `encoding/json` package from Go 1.6.
|
||||||
|
|
||||||
|
The following changes were made:
|
||||||
|
|
||||||
|
* Object deserialization uses case-sensitive member name matching instead of
|
||||||
|
[case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html).
|
||||||
|
This is to avoid differences in the interpretation of JOSE messages between
|
||||||
|
go-jose and libraries written in other languages.
|
||||||
|
* When deserializing a JSON object, we check for duplicate keys and reject the
|
||||||
|
input whenever we detect a duplicate. Rather than trying to work with malformed
|
||||||
|
data, we prefer to reject it right away.
|
1183
vendor/gopkg.in/square/go-jose.v2/json/decode.go
generated
vendored
Normal file
1183
vendor/gopkg.in/square/go-jose.v2/json/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1197
vendor/gopkg.in/square/go-jose.v2/json/encode.go
generated
vendored
Normal file
1197
vendor/gopkg.in/square/go-jose.v2/json/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
141
vendor/gopkg.in/square/go-jose.v2/json/indent.go
generated
vendored
Normal file
141
vendor/gopkg.in/square/go-jose.v2/json/indent.go
generated
vendored
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
// Compact appends to dst the JSON-encoded src with
|
||||||
|
// insignificant space characters elided.
|
||||||
|
func Compact(dst *bytes.Buffer, src []byte) error {
|
||||||
|
return compact(dst, src, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compact(dst *bytes.Buffer, src []byte, escape bool) error {
|
||||||
|
origLen := dst.Len()
|
||||||
|
var scan scanner
|
||||||
|
scan.reset()
|
||||||
|
start := 0
|
||||||
|
for i, c := range src {
|
||||||
|
if escape && (c == '<' || c == '>' || c == '&') {
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u00`)
|
||||||
|
dst.WriteByte(hex[c>>4])
|
||||||
|
dst.WriteByte(hex[c&0xF])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
|
||||||
|
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
dst.WriteString(`\u202`)
|
||||||
|
dst.WriteByte(hex[src[i+2]&0xF])
|
||||||
|
start = i + 3
|
||||||
|
}
|
||||||
|
v := scan.step(&scan, c)
|
||||||
|
if v >= scanSkipSpace {
|
||||||
|
if v == scanError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
dst.Write(src[start:i])
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
dst.Truncate(origLen)
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
if start < len(src) {
|
||||||
|
dst.Write(src[start:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newline(dst *bytes.Buffer, prefix, indent string, depth int) {
|
||||||
|
dst.WriteByte('\n')
|
||||||
|
dst.WriteString(prefix)
|
||||||
|
for i := 0; i < depth; i++ {
|
||||||
|
dst.WriteString(indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indent appends to dst an indented form of the JSON-encoded src.
|
||||||
|
// Each element in a JSON object or array begins on a new,
|
||||||
|
// indented line beginning with prefix followed by one or more
|
||||||
|
// copies of indent according to the indentation nesting.
|
||||||
|
// The data appended to dst does not begin with the prefix nor
|
||||||
|
// any indentation, to make it easier to embed inside other formatted JSON data.
|
||||||
|
// Although leading space characters (space, tab, carriage return, newline)
|
||||||
|
// at the beginning of src are dropped, trailing space characters
|
||||||
|
// at the end of src are preserved and copied to dst.
|
||||||
|
// For example, if src has no trailing spaces, neither will dst;
|
||||||
|
// if src ends in a trailing newline, so will dst.
|
||||||
|
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
|
||||||
|
origLen := dst.Len()
|
||||||
|
var scan scanner
|
||||||
|
scan.reset()
|
||||||
|
needIndent := false
|
||||||
|
depth := 0
|
||||||
|
for _, c := range src {
|
||||||
|
scan.bytes++
|
||||||
|
v := scan.step(&scan, c)
|
||||||
|
if v == scanSkipSpace {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v == scanError {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if needIndent && v != scanEndObject && v != scanEndArray {
|
||||||
|
needIndent = false
|
||||||
|
depth++
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit semantically uninteresting bytes
|
||||||
|
// (in particular, punctuation in strings) unmodified.
|
||||||
|
if v == scanContinue {
|
||||||
|
dst.WriteByte(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add spacing around real punctuation.
|
||||||
|
switch c {
|
||||||
|
case '{', '[':
|
||||||
|
// delay indent so that empty object and array are formatted as {} and [].
|
||||||
|
needIndent = true
|
||||||
|
dst.WriteByte(c)
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
dst.WriteByte(c)
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
dst.WriteByte(c)
|
||||||
|
dst.WriteByte(' ')
|
||||||
|
|
||||||
|
case '}', ']':
|
||||||
|
if needIndent {
|
||||||
|
// suppress indent in empty object/array
|
||||||
|
needIndent = false
|
||||||
|
} else {
|
||||||
|
depth--
|
||||||
|
newline(dst, prefix, indent, depth)
|
||||||
|
}
|
||||||
|
dst.WriteByte(c)
|
||||||
|
|
||||||
|
default:
|
||||||
|
dst.WriteByte(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
dst.Truncate(origLen)
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
623
vendor/gopkg.in/square/go-jose.v2/json/scanner.go
generated
vendored
Normal file
623
vendor/gopkg.in/square/go-jose.v2/json/scanner.go
generated
vendored
Normal file
|
@ -0,0 +1,623 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
// JSON value parser state machine.
|
||||||
|
// Just about at the limit of what is reasonable to write by hand.
|
||||||
|
// Some parts are a bit tedious, but overall it nicely factors out the
|
||||||
|
// otherwise common code from the multiple scanning functions
|
||||||
|
// in this package (Compact, Indent, checkValid, nextValue, etc).
|
||||||
|
//
|
||||||
|
// This file starts with two simple examples using the scanner
|
||||||
|
// before diving into the scanner itself.
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// checkValid verifies that data is valid JSON-encoded data.
|
||||||
|
// scan is passed in for use by checkValid to avoid an allocation.
|
||||||
|
func checkValid(data []byte, scan *scanner) error {
|
||||||
|
scan.reset()
|
||||||
|
for _, c := range data {
|
||||||
|
scan.bytes++
|
||||||
|
if scan.step(scan, c) == scanError {
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
return scan.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextValue splits data after the next whole JSON value,
|
||||||
|
// returning that value and the bytes that follow it as separate slices.
|
||||||
|
// scan is passed in for use by nextValue to avoid an allocation.
|
||||||
|
func nextValue(data []byte, scan *scanner) (value, rest []byte, err error) {
|
||||||
|
scan.reset()
|
||||||
|
for i, c := range data {
|
||||||
|
v := scan.step(scan, c)
|
||||||
|
if v >= scanEndObject {
|
||||||
|
switch v {
|
||||||
|
// probe the scanner with a space to determine whether we will
|
||||||
|
// get scanEnd on the next character. Otherwise, if the next character
|
||||||
|
// is not a space, scanEndTop allocates a needless error.
|
||||||
|
case scanEndObject, scanEndArray:
|
||||||
|
if scan.step(scan, ' ') == scanEnd {
|
||||||
|
return data[:i+1], data[i+1:], nil
|
||||||
|
}
|
||||||
|
case scanError:
|
||||||
|
return nil, nil, scan.err
|
||||||
|
case scanEnd:
|
||||||
|
return data[:i], data[i:], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scan.eof() == scanError {
|
||||||
|
return nil, nil, scan.err
|
||||||
|
}
|
||||||
|
return data, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SyntaxError is a description of a JSON syntax error.
|
||||||
|
type SyntaxError struct {
|
||||||
|
msg string // description of error
|
||||||
|
Offset int64 // error occurred after reading Offset bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SyntaxError) Error() string { return e.msg }
|
||||||
|
|
||||||
|
// A scanner is a JSON scanning state machine.
|
||||||
|
// Callers call scan.reset() and then pass bytes in one at a time
|
||||||
|
// by calling scan.step(&scan, c) for each byte.
|
||||||
|
// The return value, referred to as an opcode, tells the
|
||||||
|
// caller about significant parsing events like beginning
|
||||||
|
// and ending literals, objects, and arrays, so that the
|
||||||
|
// caller can follow along if it wishes.
|
||||||
|
// The return value scanEnd indicates that a single top-level
|
||||||
|
// JSON value has been completed, *before* the byte that
|
||||||
|
// just got passed in. (The indication must be delayed in order
|
||||||
|
// to recognize the end of numbers: is 123 a whole value or
|
||||||
|
// the beginning of 12345e+6?).
|
||||||
|
type scanner struct {
|
||||||
|
// The step is a func to be called to execute the next transition.
|
||||||
|
// Also tried using an integer constant and a single func
|
||||||
|
// with a switch, but using the func directly was 10% faster
|
||||||
|
// on a 64-bit Mac Mini, and it's nicer to read.
|
||||||
|
step func(*scanner, byte) int
|
||||||
|
|
||||||
|
// Reached end of top-level value.
|
||||||
|
endTop bool
|
||||||
|
|
||||||
|
// Stack of what we're in the middle of - array values, object keys, object values.
|
||||||
|
parseState []int
|
||||||
|
|
||||||
|
// Error that happened, if any.
|
||||||
|
err error
|
||||||
|
|
||||||
|
// 1-byte redo (see undo method)
|
||||||
|
redo bool
|
||||||
|
redoCode int
|
||||||
|
redoState func(*scanner, byte) int
|
||||||
|
|
||||||
|
// total bytes consumed, updated by decoder.Decode
|
||||||
|
bytes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// These values are returned by the state transition functions
|
||||||
|
// assigned to scanner.state and the method scanner.eof.
|
||||||
|
// They give details about the current state of the scan that
|
||||||
|
// callers might be interested to know about.
|
||||||
|
// It is okay to ignore the return value of any particular
|
||||||
|
// call to scanner.state: if one call returns scanError,
|
||||||
|
// every subsequent call will return scanError too.
|
||||||
|
const (
|
||||||
|
// Continue.
|
||||||
|
scanContinue = iota // uninteresting byte
|
||||||
|
scanBeginLiteral // end implied by next result != scanContinue
|
||||||
|
scanBeginObject // begin object
|
||||||
|
scanObjectKey // just finished object key (string)
|
||||||
|
scanObjectValue // just finished non-last object value
|
||||||
|
scanEndObject // end object (implies scanObjectValue if possible)
|
||||||
|
scanBeginArray // begin array
|
||||||
|
scanArrayValue // just finished array value
|
||||||
|
scanEndArray // end array (implies scanArrayValue if possible)
|
||||||
|
scanSkipSpace // space byte; can skip; known to be last "continue" result
|
||||||
|
|
||||||
|
// Stop.
|
||||||
|
scanEnd // top-level value ended *before* this byte; known to be first "stop" result
|
||||||
|
scanError // hit an error, scanner.err.
|
||||||
|
)
|
||||||
|
|
||||||
|
// These values are stored in the parseState stack.
|
||||||
|
// They give the current state of a composite value
|
||||||
|
// being scanned. If the parser is inside a nested value
|
||||||
|
// the parseState describes the nested state, outermost at entry 0.
|
||||||
|
const (
|
||||||
|
parseObjectKey = iota // parsing object key (before colon)
|
||||||
|
parseObjectValue // parsing object value (after colon)
|
||||||
|
parseArrayValue // parsing array value
|
||||||
|
)
|
||||||
|
|
||||||
|
// reset prepares the scanner for use.
|
||||||
|
// It must be called before calling s.step.
|
||||||
|
func (s *scanner) reset() {
|
||||||
|
s.step = stateBeginValue
|
||||||
|
s.parseState = s.parseState[0:0]
|
||||||
|
s.err = nil
|
||||||
|
s.redo = false
|
||||||
|
s.endTop = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// eof tells the scanner that the end of input has been reached.
|
||||||
|
// It returns a scan status just as s.step does.
|
||||||
|
func (s *scanner) eof() int {
|
||||||
|
if s.err != nil {
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
if s.endTop {
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
s.step(s, ' ')
|
||||||
|
if s.endTop {
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
if s.err == nil {
|
||||||
|
s.err = &SyntaxError{"unexpected end of JSON input", s.bytes}
|
||||||
|
}
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushParseState pushes a new parse state p onto the parse stack.
|
||||||
|
func (s *scanner) pushParseState(p int) {
|
||||||
|
s.parseState = append(s.parseState, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// popParseState pops a parse state (already obtained) off the stack
|
||||||
|
// and updates s.step accordingly.
|
||||||
|
func (s *scanner) popParseState() {
|
||||||
|
n := len(s.parseState) - 1
|
||||||
|
s.parseState = s.parseState[0:n]
|
||||||
|
s.redo = false
|
||||||
|
if n == 0 {
|
||||||
|
s.step = stateEndTop
|
||||||
|
s.endTop = true
|
||||||
|
} else {
|
||||||
|
s.step = stateEndValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSpace(c byte) bool {
|
||||||
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginValueOrEmpty is the state after reading `[`.
|
||||||
|
func stateBeginValueOrEmpty(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
return stateBeginValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginValue is the state at the beginning of the input.
|
||||||
|
func stateBeginValue(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '{':
|
||||||
|
s.step = stateBeginStringOrEmpty
|
||||||
|
s.pushParseState(parseObjectKey)
|
||||||
|
return scanBeginObject
|
||||||
|
case '[':
|
||||||
|
s.step = stateBeginValueOrEmpty
|
||||||
|
s.pushParseState(parseArrayValue)
|
||||||
|
return scanBeginArray
|
||||||
|
case '"':
|
||||||
|
s.step = stateInString
|
||||||
|
return scanBeginLiteral
|
||||||
|
case '-':
|
||||||
|
s.step = stateNeg
|
||||||
|
return scanBeginLiteral
|
||||||
|
case '0': // beginning of 0.123
|
||||||
|
s.step = state0
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 't': // beginning of true
|
||||||
|
s.step = stateT
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 'f': // beginning of false
|
||||||
|
s.step = stateF
|
||||||
|
return scanBeginLiteral
|
||||||
|
case 'n': // beginning of null
|
||||||
|
s.step = stateN
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
if '1' <= c && c <= '9' { // beginning of 1234.5
|
||||||
|
s.step = state1
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
return s.error(c, "looking for beginning of value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginStringOrEmpty is the state after reading `{`.
|
||||||
|
func stateBeginStringOrEmpty(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
n := len(s.parseState)
|
||||||
|
s.parseState[n-1] = parseObjectValue
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
return stateBeginString(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateBeginString is the state after reading `{"key": value,`.
|
||||||
|
func stateBeginString(s *scanner, c byte) int {
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
s.step = stateInString
|
||||||
|
return scanBeginLiteral
|
||||||
|
}
|
||||||
|
return s.error(c, "looking for beginning of object key string")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateEndValue is the state after completing a value,
|
||||||
|
// such as after reading `{}` or `true` or `["x"`.
|
||||||
|
func stateEndValue(s *scanner, c byte) int {
|
||||||
|
n := len(s.parseState)
|
||||||
|
if n == 0 {
|
||||||
|
// Completed top-level before the current byte.
|
||||||
|
s.step = stateEndTop
|
||||||
|
s.endTop = true
|
||||||
|
return stateEndTop(s, c)
|
||||||
|
}
|
||||||
|
if c <= ' ' && isSpace(c) {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanSkipSpace
|
||||||
|
}
|
||||||
|
ps := s.parseState[n-1]
|
||||||
|
switch ps {
|
||||||
|
case parseObjectKey:
|
||||||
|
if c == ':' {
|
||||||
|
s.parseState[n-1] = parseObjectValue
|
||||||
|
s.step = stateBeginValue
|
||||||
|
return scanObjectKey
|
||||||
|
}
|
||||||
|
return s.error(c, "after object key")
|
||||||
|
case parseObjectValue:
|
||||||
|
if c == ',' {
|
||||||
|
s.parseState[n-1] = parseObjectKey
|
||||||
|
s.step = stateBeginString
|
||||||
|
return scanObjectValue
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
s.popParseState()
|
||||||
|
return scanEndObject
|
||||||
|
}
|
||||||
|
return s.error(c, "after object key:value pair")
|
||||||
|
case parseArrayValue:
|
||||||
|
if c == ',' {
|
||||||
|
s.step = stateBeginValue
|
||||||
|
return scanArrayValue
|
||||||
|
}
|
||||||
|
if c == ']' {
|
||||||
|
s.popParseState()
|
||||||
|
return scanEndArray
|
||||||
|
}
|
||||||
|
return s.error(c, "after array element")
|
||||||
|
}
|
||||||
|
return s.error(c, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateEndTop is the state after finishing the top-level value,
|
||||||
|
// such as after reading `{}` or `[1,2,3]`.
|
||||||
|
// Only space characters should be seen now.
|
||||||
|
func stateEndTop(s *scanner, c byte) int {
|
||||||
|
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
|
||||||
|
// Complain about non-space byte on next call.
|
||||||
|
s.error(c, "after top-level value")
|
||||||
|
}
|
||||||
|
return scanEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInString is the state after reading `"`.
|
||||||
|
func stateInString(s *scanner, c byte) int {
|
||||||
|
if c == '"' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == '\\' {
|
||||||
|
s.step = stateInStringEsc
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c < 0x20 {
|
||||||
|
return s.error(c, "in string literal")
|
||||||
|
}
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEsc is the state after reading `"\` during a quoted string.
|
||||||
|
func stateInStringEsc(s *scanner, c byte) int {
|
||||||
|
switch c {
|
||||||
|
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
|
||||||
|
s.step = stateInString
|
||||||
|
return scanContinue
|
||||||
|
case 'u':
|
||||||
|
s.step = stateInStringEscU
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in string escape code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU is the state after reading `"\u` during a quoted string.
|
||||||
|
func stateInStringEscU(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU1 is the state after reading `"\u1` during a quoted string.
|
||||||
|
func stateInStringEscU1(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU12
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU12 is the state after reading `"\u12` during a quoted string.
|
||||||
|
func stateInStringEscU12(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInStringEscU123
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateInStringEscU123 is the state after reading `"\u123` during a quoted string.
|
||||||
|
func stateInStringEscU123(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' {
|
||||||
|
s.step = stateInString
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
// numbers
|
||||||
|
return s.error(c, "in \\u hexadecimal character escape")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNeg is the state after reading `-` during a number.
|
||||||
|
func stateNeg(s *scanner, c byte) int {
|
||||||
|
if c == '0' {
|
||||||
|
s.step = state0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if '1' <= c && c <= '9' {
|
||||||
|
s.step = state1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// state1 is the state after reading a non-zero integer during a number,
|
||||||
|
// such as after reading `1` or `100` but not `0`.
|
||||||
|
func state1(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = state1
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return state0(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// state0 is the state after reading `0` during a number.
|
||||||
|
func state0(s *scanner, c byte) int {
|
||||||
|
if c == '.' {
|
||||||
|
s.step = stateDot
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == 'e' || c == 'E' {
|
||||||
|
s.step = stateE
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateDot is the state after reading the integer and decimal point in a number,
|
||||||
|
// such as after reading `1.`.
|
||||||
|
func stateDot(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = stateDot0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "after decimal point in numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateDot0 is the state after reading the integer, decimal point, and subsequent
|
||||||
|
// digits of a number, such as after reading `3.14`.
|
||||||
|
func stateDot0(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
if c == 'e' || c == 'E' {
|
||||||
|
s.step = stateE
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateE is the state after reading the mantissa and e in a number,
|
||||||
|
// such as after reading `314e` or `0.314e`.
|
||||||
|
func stateE(s *scanner, c byte) int {
|
||||||
|
if c == '+' || c == '-' {
|
||||||
|
s.step = stateESign
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateESign(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateESign is the state after reading the mantissa, e, and sign in a number,
|
||||||
|
// such as after reading `314e-` or `0.314e+`.
|
||||||
|
func stateESign(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
s.step = stateE0
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in exponent of numeric literal")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateE0 is the state after reading the mantissa, e, optional sign,
|
||||||
|
// and at least one digit of the exponent in a number,
|
||||||
|
// such as after reading `314e-2` or `0.314e+1` or `3.14e0`.
|
||||||
|
func stateE0(s *scanner, c byte) int {
|
||||||
|
if '0' <= c && c <= '9' {
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return stateEndValue(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateT is the state after reading `t`.
|
||||||
|
func stateT(s *scanner, c byte) int {
|
||||||
|
if c == 'r' {
|
||||||
|
s.step = stateTr
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'r')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateTr is the state after reading `tr`.
|
||||||
|
func stateTr(s *scanner, c byte) int {
|
||||||
|
if c == 'u' {
|
||||||
|
s.step = stateTru
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'u')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateTru is the state after reading `tru`.
|
||||||
|
func stateTru(s *scanner, c byte) int {
|
||||||
|
if c == 'e' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal true (expecting 'e')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateF is the state after reading `f`.
|
||||||
|
func stateF(s *scanner, c byte) int {
|
||||||
|
if c == 'a' {
|
||||||
|
s.step = stateFa
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'a')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFa is the state after reading `fa`.
|
||||||
|
func stateFa(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateFal
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFal is the state after reading `fal`.
|
||||||
|
func stateFal(s *scanner, c byte) int {
|
||||||
|
if c == 's' {
|
||||||
|
s.step = stateFals
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 's')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateFals is the state after reading `fals`.
|
||||||
|
func stateFals(s *scanner, c byte) int {
|
||||||
|
if c == 'e' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal false (expecting 'e')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateN is the state after reading `n`.
|
||||||
|
func stateN(s *scanner, c byte) int {
|
||||||
|
if c == 'u' {
|
||||||
|
s.step = stateNu
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'u')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNu is the state after reading `nu`.
|
||||||
|
func stateNu(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateNul
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateNul is the state after reading `nul`.
|
||||||
|
func stateNul(s *scanner, c byte) int {
|
||||||
|
if c == 'l' {
|
||||||
|
s.step = stateEndValue
|
||||||
|
return scanContinue
|
||||||
|
}
|
||||||
|
return s.error(c, "in literal null (expecting 'l')")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateError is the state after reaching a syntax error,
|
||||||
|
// such as after reading `[1}` or `5.1.2`.
|
||||||
|
func stateError(s *scanner, c byte) int {
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// error records an error and switches to the error state.
|
||||||
|
func (s *scanner) error(c byte, context string) int {
|
||||||
|
s.step = stateError
|
||||||
|
s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes}
|
||||||
|
return scanError
|
||||||
|
}
|
||||||
|
|
||||||
|
// quoteChar formats c as a quoted character literal
|
||||||
|
func quoteChar(c byte) string {
|
||||||
|
// special cases - different from quoted strings
|
||||||
|
if c == '\'' {
|
||||||
|
return `'\''`
|
||||||
|
}
|
||||||
|
if c == '"' {
|
||||||
|
return `'"'`
|
||||||
|
}
|
||||||
|
|
||||||
|
// use quoted string with different quotation marks
|
||||||
|
s := strconv.Quote(string(c))
|
||||||
|
return "'" + s[1:len(s)-1] + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
// undo causes the scanner to return scanCode from the next state transition.
|
||||||
|
// This gives callers a simple 1-byte undo mechanism.
|
||||||
|
func (s *scanner) undo(scanCode int) {
|
||||||
|
if s.redo {
|
||||||
|
panic("json: invalid use of scanner")
|
||||||
|
}
|
||||||
|
s.redoCode = scanCode
|
||||||
|
s.redoState = s.step
|
||||||
|
s.step = stateRedo
|
||||||
|
s.redo = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateRedo helps implement the scanner's 1-byte undo.
|
||||||
|
func stateRedo(s *scanner, c byte) int {
|
||||||
|
s.redo = false
|
||||||
|
s.step = s.redoState
|
||||||
|
return s.redoCode
|
||||||
|
}
|
480
vendor/gopkg.in/square/go-jose.v2/json/stream.go
generated
vendored
Normal file
480
vendor/gopkg.in/square/go-jose.v2/json/stream.go
generated
vendored
Normal file
|
@ -0,0 +1,480 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Decoder reads and decodes JSON objects from an input stream.
|
||||||
|
type Decoder struct {
|
||||||
|
r io.Reader
|
||||||
|
buf []byte
|
||||||
|
d decodeState
|
||||||
|
scanp int // start of unread data in buf
|
||||||
|
scan scanner
|
||||||
|
err error
|
||||||
|
|
||||||
|
tokenState int
|
||||||
|
tokenStack []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new decoder that reads from r.
|
||||||
|
//
|
||||||
|
// The decoder introduces its own buffering and may
|
||||||
|
// read data from r beyond the JSON values requested.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder {
|
||||||
|
return &Decoder{r: r}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number instead of as a float64.
|
||||||
|
func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
|
||||||
|
|
||||||
|
// Decode reads the next JSON-encoded value from its
|
||||||
|
// input and stores it in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// See the documentation for Unmarshal for details about
|
||||||
|
// the conversion of JSON into a Go value.
|
||||||
|
func (dec *Decoder) Decode(v interface{}) error {
|
||||||
|
if dec.err != nil {
|
||||||
|
return dec.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dec.tokenPrepareForDecode(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return &SyntaxError{msg: "not at beginning of value"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read whole value into buffer.
|
||||||
|
n, err := dec.readValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dec.d.init(dec.buf[dec.scanp : dec.scanp+n])
|
||||||
|
dec.scanp += n
|
||||||
|
|
||||||
|
// Don't save err from unmarshal into dec.err:
|
||||||
|
// the connection is still usable since we read a complete JSON
|
||||||
|
// object from it before the error happened.
|
||||||
|
err = dec.d.unmarshal(v)
|
||||||
|
|
||||||
|
// fixup token streaming state
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered returns a reader of the data remaining in the Decoder's
|
||||||
|
// buffer. The reader is valid until the next call to Decode.
|
||||||
|
func (dec *Decoder) Buffered() io.Reader {
|
||||||
|
return bytes.NewReader(dec.buf[dec.scanp:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// readValue reads a JSON value into dec.buf.
|
||||||
|
// It returns the length of the encoding.
|
||||||
|
func (dec *Decoder) readValue() (int, error) {
|
||||||
|
dec.scan.reset()
|
||||||
|
|
||||||
|
scanp := dec.scanp
|
||||||
|
var err error
|
||||||
|
Input:
|
||||||
|
for {
|
||||||
|
// Look in the buffer for a new value.
|
||||||
|
for i, c := range dec.buf[scanp:] {
|
||||||
|
dec.scan.bytes++
|
||||||
|
v := dec.scan.step(&dec.scan, c)
|
||||||
|
if v == scanEnd {
|
||||||
|
scanp += i
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
// scanEnd is delayed one byte.
|
||||||
|
// We might block trying to get that byte from src,
|
||||||
|
// so instead invent a space byte.
|
||||||
|
if (v == scanEndObject || v == scanEndArray) && dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||||
|
scanp += i + 1
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
if v == scanError {
|
||||||
|
dec.err = dec.scan.err
|
||||||
|
return 0, dec.scan.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scanp = len(dec.buf)
|
||||||
|
|
||||||
|
// Did the last read have an error?
|
||||||
|
// Delayed until now to allow buffer scan.
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if dec.scan.step(&dec.scan, ' ') == scanEnd {
|
||||||
|
break Input
|
||||||
|
}
|
||||||
|
if nonSpace(dec.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dec.err = err
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := scanp - dec.scanp
|
||||||
|
err = dec.refill()
|
||||||
|
scanp = dec.scanp + n
|
||||||
|
}
|
||||||
|
return scanp - dec.scanp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) refill() error {
|
||||||
|
// Make room to read more into the buffer.
|
||||||
|
// First slide down data already consumed.
|
||||||
|
if dec.scanp > 0 {
|
||||||
|
n := copy(dec.buf, dec.buf[dec.scanp:])
|
||||||
|
dec.buf = dec.buf[:n]
|
||||||
|
dec.scanp = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow buffer if not large enough.
|
||||||
|
const minRead = 512
|
||||||
|
if cap(dec.buf)-len(dec.buf) < minRead {
|
||||||
|
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
|
||||||
|
copy(newBuf, dec.buf)
|
||||||
|
dec.buf = newBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read. Delay error for next iteration (after scan).
|
||||||
|
n, err := dec.r.Read(dec.buf[len(dec.buf):cap(dec.buf)])
|
||||||
|
dec.buf = dec.buf[0 : len(dec.buf)+n]
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func nonSpace(b []byte) bool {
|
||||||
|
for _, c := range b {
|
||||||
|
if !isSpace(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Encoder writes JSON objects to an output stream.
|
||||||
|
type Encoder struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new encoder that writes to w.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{w: w}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes the JSON encoding of v to the stream,
|
||||||
|
// followed by a newline character.
|
||||||
|
//
|
||||||
|
// See the documentation for Marshal for details about the
|
||||||
|
// conversion of Go values to JSON.
|
||||||
|
func (enc *Encoder) Encode(v interface{}) error {
|
||||||
|
if enc.err != nil {
|
||||||
|
return enc.err
|
||||||
|
}
|
||||||
|
e := newEncodeState()
|
||||||
|
err := e.marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate each value with a newline.
|
||||||
|
// This makes the output look a little nicer
|
||||||
|
// when debugging, and some kind of space
|
||||||
|
// is required if the encoded value was a number,
|
||||||
|
// so that the reader knows there aren't more
|
||||||
|
// digits coming.
|
||||||
|
e.WriteByte('\n')
|
||||||
|
|
||||||
|
if _, err = enc.w.Write(e.Bytes()); err != nil {
|
||||||
|
enc.err = err
|
||||||
|
}
|
||||||
|
encodeStatePool.Put(e)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawMessage is a raw encoded JSON object.
|
||||||
|
// It implements Marshaler and Unmarshaler and can
|
||||||
|
// be used to delay JSON decoding or precompute a JSON encoding.
|
||||||
|
type RawMessage []byte
|
||||||
|
|
||||||
|
// MarshalJSON returns *m as the JSON encoding of m.
|
||||||
|
func (m *RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
|
return *m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON sets *m to a copy of data.
|
||||||
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
|
if m == nil {
|
||||||
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||||
|
}
|
||||||
|
*m = append((*m)[0:0], data...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Marshaler = (*RawMessage)(nil)
|
||||||
|
var _ Unmarshaler = (*RawMessage)(nil)
|
||||||
|
|
||||||
|
// A Token holds a value of one of these types:
|
||||||
|
//
|
||||||
|
// Delim, for the four JSON delimiters [ ] { }
|
||||||
|
// bool, for JSON booleans
|
||||||
|
// float64, for JSON numbers
|
||||||
|
// Number, for JSON numbers
|
||||||
|
// string, for JSON string literals
|
||||||
|
// nil, for JSON null
|
||||||
|
//
|
||||||
|
type Token interface{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenTopValue = iota
|
||||||
|
tokenArrayStart
|
||||||
|
tokenArrayValue
|
||||||
|
tokenArrayComma
|
||||||
|
tokenObjectStart
|
||||||
|
tokenObjectKey
|
||||||
|
tokenObjectColon
|
||||||
|
tokenObjectValue
|
||||||
|
tokenObjectComma
|
||||||
|
)
|
||||||
|
|
||||||
|
// advance tokenstate from a separator state to a value state
|
||||||
|
func (dec *Decoder) tokenPrepareForDecode() error {
|
||||||
|
// Note: Not calling peek before switch, to avoid
|
||||||
|
// putting peek into the standard Decode path.
|
||||||
|
// peek is only called when using the Token API.
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenArrayComma:
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ',' {
|
||||||
|
return &SyntaxError{"expected comma after array element", 0}
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenArrayValue
|
||||||
|
case tokenObjectColon:
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c != ':' {
|
||||||
|
return &SyntaxError{"expected colon after object key", 0}
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenValueAllowed() bool {
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenTopValue, tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenValueEnd() {
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenArrayStart, tokenArrayValue:
|
||||||
|
dec.tokenState = tokenArrayComma
|
||||||
|
case tokenObjectValue:
|
||||||
|
dec.tokenState = tokenObjectComma
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
|
||||||
|
type Delim rune
|
||||||
|
|
||||||
|
func (d Delim) String() string {
|
||||||
|
return string(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the next JSON token in the input stream.
|
||||||
|
// At the end of the input stream, Token returns nil, io.EOF.
|
||||||
|
//
|
||||||
|
// Token guarantees that the delimiters [ ] { } it returns are
|
||||||
|
// properly nested and matched: if Token encounters an unexpected
|
||||||
|
// delimiter in the input, it will return an error.
|
||||||
|
//
|
||||||
|
// The input stream consists of basic JSON values—bool, string,
|
||||||
|
// number, and null—along with delimiters [ ] { } of type Delim
|
||||||
|
// to mark the start and end of arrays and objects.
|
||||||
|
// Commas and colons are elided.
|
||||||
|
func (dec *Decoder) Token() (Token, error) {
|
||||||
|
for {
|
||||||
|
c, err := dec.peek()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '[':
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||||
|
dec.tokenState = tokenArrayStart
|
||||||
|
return Delim('['), nil
|
||||||
|
|
||||||
|
case ']':
|
||||||
|
if dec.tokenState != tokenArrayStart && dec.tokenState != tokenArrayComma {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||||
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
return Delim(']'), nil
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenStack = append(dec.tokenStack, dec.tokenState)
|
||||||
|
dec.tokenState = tokenObjectStart
|
||||||
|
return Delim('{'), nil
|
||||||
|
|
||||||
|
case '}':
|
||||||
|
if dec.tokenState != tokenObjectStart && dec.tokenState != tokenObjectComma {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = dec.tokenStack[len(dec.tokenStack)-1]
|
||||||
|
dec.tokenStack = dec.tokenStack[:len(dec.tokenStack)-1]
|
||||||
|
dec.tokenValueEnd()
|
||||||
|
return Delim('}'), nil
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
if dec.tokenState != tokenObjectColon {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectValue
|
||||||
|
continue
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
if dec.tokenState == tokenArrayComma {
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenArrayValue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dec.tokenState == tokenObjectComma {
|
||||||
|
dec.scanp++
|
||||||
|
dec.tokenState = tokenObjectKey
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return dec.tokenError(c)
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
if dec.tokenState == tokenObjectStart || dec.tokenState == tokenObjectKey {
|
||||||
|
var x string
|
||||||
|
old := dec.tokenState
|
||||||
|
dec.tokenState = tokenTopValue
|
||||||
|
err := dec.Decode(&x)
|
||||||
|
dec.tokenState = old
|
||||||
|
if err != nil {
|
||||||
|
clearOffset(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dec.tokenState = tokenObjectColon
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
default:
|
||||||
|
if !dec.tokenValueAllowed() {
|
||||||
|
return dec.tokenError(c)
|
||||||
|
}
|
||||||
|
var x interface{}
|
||||||
|
if err := dec.Decode(&x); err != nil {
|
||||||
|
clearOffset(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearOffset(err error) {
|
||||||
|
if s, ok := err.(*SyntaxError); ok {
|
||||||
|
s.Offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) tokenError(c byte) (Token, error) {
|
||||||
|
var context string
|
||||||
|
switch dec.tokenState {
|
||||||
|
case tokenTopValue:
|
||||||
|
context = " looking for beginning of value"
|
||||||
|
case tokenArrayStart, tokenArrayValue, tokenObjectValue:
|
||||||
|
context = " looking for beginning of value"
|
||||||
|
case tokenArrayComma:
|
||||||
|
context = " after array element"
|
||||||
|
case tokenObjectKey:
|
||||||
|
context = " looking for beginning of object key string"
|
||||||
|
case tokenObjectColon:
|
||||||
|
context = " after object key"
|
||||||
|
case tokenObjectComma:
|
||||||
|
context = " after object key:value pair"
|
||||||
|
}
|
||||||
|
return nil, &SyntaxError{"invalid character " + quoteChar(c) + " " + context, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// More reports whether there is another element in the
|
||||||
|
// current array or object being parsed.
|
||||||
|
func (dec *Decoder) More() bool {
|
||||||
|
c, err := dec.peek()
|
||||||
|
return err == nil && c != ']' && c != '}'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *Decoder) peek() (byte, error) {
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
for i := dec.scanp; i < len(dec.buf); i++ {
|
||||||
|
c := dec.buf[i]
|
||||||
|
if isSpace(c) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dec.scanp = i
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
// buffer has been scanned, now report any error
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
err = dec.refill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO
|
||||||
|
|
||||||
|
// EncodeToken writes the given JSON token to the stream.
|
||||||
|
// It returns an error if the delimiters [ ] { } are not properly used.
|
||||||
|
//
|
||||||
|
// EncodeToken does not call Flush, because usually it is part of
|
||||||
|
// a larger operation such as Encode, and those will call Flush when finished.
|
||||||
|
// Callers that create an Encoder and then invoke EncodeToken directly,
|
||||||
|
// without using Encode, need to call Flush when finished to ensure that
|
||||||
|
// the JSON is written to the underlying writer.
|
||||||
|
func (e *Encoder) EncodeToken(t Token) error {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
44
vendor/gopkg.in/square/go-jose.v2/json/tags.go
generated
vendored
Normal file
44
vendor/gopkg.in/square/go-jose.v2/json/tags.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tagOptions is the string following a comma in a struct field's "json"
|
||||||
|
// tag, or the empty string. It does not include the leading comma.
|
||||||
|
type tagOptions string
|
||||||
|
|
||||||
|
// parseTag splits a struct field's json tag into its name and
|
||||||
|
// comma-separated options.
|
||||||
|
func parseTag(tag string) (string, tagOptions) {
|
||||||
|
if idx := strings.Index(tag, ","); idx != -1 {
|
||||||
|
return tag[:idx], tagOptions(tag[idx+1:])
|
||||||
|
}
|
||||||
|
return tag, tagOptions("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether a comma-separated list of options
|
||||||
|
// contains a particular substr flag. substr must be surrounded by a
|
||||||
|
// string boundary or commas.
|
||||||
|
func (o tagOptions) Contains(optionName string) bool {
|
||||||
|
if len(o) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s := string(o)
|
||||||
|
for s != "" {
|
||||||
|
var next string
|
||||||
|
i := strings.Index(s, ",")
|
||||||
|
if i >= 0 {
|
||||||
|
s, next = s[:i], s[i+1:]
|
||||||
|
}
|
||||||
|
if s == optionName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s = next
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
294
vendor/gopkg.in/square/go-jose.v2/jwe.go
generated
vendored
Normal file
294
vendor/gopkg.in/square/go-jose.v2/jwe.go
generated
vendored
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
||||||
|
type rawJSONWebEncryption struct {
|
||||||
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||||||
|
Unprotected *rawHeader `json:"unprotected,omitempty"`
|
||||||
|
Header *rawHeader `json:"header,omitempty"`
|
||||||
|
Recipients []rawRecipientInfo `json:"recipients,omitempty"`
|
||||||
|
Aad *byteBuffer `json:"aad,omitempty"`
|
||||||
|
EncryptedKey *byteBuffer `json:"encrypted_key,omitempty"`
|
||||||
|
Iv *byteBuffer `json:"iv,omitempty"`
|
||||||
|
Ciphertext *byteBuffer `json:"ciphertext,omitempty"`
|
||||||
|
Tag *byteBuffer `json:"tag,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawRecipientInfo represents a raw JWE Per-Recipient header JSON object. Used for parsing/serializing.
|
||||||
|
type rawRecipientInfo struct {
|
||||||
|
Header *rawHeader `json:"header,omitempty"`
|
||||||
|
EncryptedKey string `json:"encrypted_key,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWebEncryption represents an encrypted JWE object after parsing.
|
||||||
|
type JSONWebEncryption struct {
|
||||||
|
Header Header
|
||||||
|
protected, unprotected *rawHeader
|
||||||
|
recipients []recipientInfo
|
||||||
|
aad, iv, ciphertext, tag []byte
|
||||||
|
original *rawJSONWebEncryption
|
||||||
|
}
|
||||||
|
|
||||||
|
// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
|
||||||
|
type recipientInfo struct {
|
||||||
|
header *rawHeader
|
||||||
|
encryptedKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthData retrieves the (optional) authenticated data attached to the object.
|
||||||
|
func (obj JSONWebEncryption) GetAuthData() []byte {
|
||||||
|
if obj.aad != nil {
|
||||||
|
out := make([]byte, len(obj.aad))
|
||||||
|
copy(out, obj.aad)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the merged header values
|
||||||
|
func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
||||||
|
out := rawHeader{}
|
||||||
|
out.merge(obj.protected)
|
||||||
|
out.merge(obj.unprotected)
|
||||||
|
|
||||||
|
if recipient != nil {
|
||||||
|
out.merge(recipient.header)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the additional authenticated data from a JWE object.
|
||||||
|
func (obj JSONWebEncryption) computeAuthData() []byte {
|
||||||
|
var protected string
|
||||||
|
|
||||||
|
if obj.original != nil && obj.original.Protected != nil {
|
||||||
|
protected = obj.original.Protected.base64()
|
||||||
|
} else if obj.protected != nil {
|
||||||
|
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
||||||
|
} else {
|
||||||
|
protected = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
output := []byte(protected)
|
||||||
|
if obj.aad != nil {
|
||||||
|
output = append(output, '.')
|
||||||
|
output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncrypted parses an encrypted message in compact or full serialization format.
|
||||||
|
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
|
||||||
|
input = stripWhitespace(input)
|
||||||
|
if strings.HasPrefix(input, "{") {
|
||||||
|
return parseEncryptedFull(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseEncryptedCompact(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEncryptedFull parses a message in compact format.
|
||||||
|
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
|
||||||
|
var parsed rawJSONWebEncryption
|
||||||
|
err := json.Unmarshal([]byte(input), &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.sanitized()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitized produces a cleaned-up JWE object from the raw JSON.
|
||||||
|
func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
|
||||||
|
obj := &JSONWebEncryption{
|
||||||
|
original: parsed,
|
||||||
|
unprotected: parsed.Unprotected,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that there is not a nonce in the unprotected headers
|
||||||
|
if parsed.Unprotected != nil {
|
||||||
|
if nonce := parsed.Unprotected.getNonce(); nonce != "" {
|
||||||
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parsed.Header != nil {
|
||||||
|
if nonce := parsed.Header.getNonce(); nonce != "" {
|
||||||
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||||||
|
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: this must be called _after_ we parse the protected header,
|
||||||
|
// otherwise fields from the protected header will not get picked up.
|
||||||
|
var err error
|
||||||
|
mergedHeaders := obj.mergedHeaders(nil)
|
||||||
|
obj.Header, err = mergedHeaders.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: cannot sanitize merged headers: %v (%v)", err, mergedHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsed.Recipients) == 0 {
|
||||||
|
obj.recipients = []recipientInfo{
|
||||||
|
{
|
||||||
|
header: parsed.Header,
|
||||||
|
encryptedKey: parsed.EncryptedKey.bytes(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
|
||||||
|
for r := range parsed.Recipients {
|
||||||
|
encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that there is not a nonce in the unprotected header
|
||||||
|
if parsed.Recipients[r].Header != nil && parsed.Recipients[r].Header.getNonce() != "" {
|
||||||
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.recipients[r].header = parsed.Recipients[r].Header
|
||||||
|
obj.recipients[r].encryptedKey = encryptedKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, recipient := range obj.recipients {
|
||||||
|
headers := obj.mergedHeaders(&recipient)
|
||||||
|
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.iv = parsed.Iv.bytes()
|
||||||
|
obj.ciphertext = parsed.Ciphertext.bytes()
|
||||||
|
obj.tag = parsed.Tag.bytes()
|
||||||
|
obj.aad = parsed.Aad.bytes()
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEncryptedCompact parses a message in compact format.
|
||||||
|
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
|
||||||
|
parts := strings.Split(input, ".")
|
||||||
|
if len(parts) != 5 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tag, err := base64.RawURLEncoding.DecodeString(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := &rawJSONWebEncryption{
|
||||||
|
Protected: newBuffer(rawProtected),
|
||||||
|
EncryptedKey: newBuffer(encryptedKey),
|
||||||
|
Iv: newBuffer(iv),
|
||||||
|
Ciphertext: newBuffer(ciphertext),
|
||||||
|
Tag: newBuffer(tag),
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.sanitized()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompactSerialize serializes an object using the compact serialization format.
|
||||||
|
func (obj JSONWebEncryption) CompactSerialize() (string, error) {
|
||||||
|
if len(obj.recipients) != 1 || obj.unprotected != nil ||
|
||||||
|
obj.protected == nil || obj.recipients[0].header != nil {
|
||||||
|
return "", ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedProtected := mustSerializeJSON(obj.protected)
|
||||||
|
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s.%s.%s.%s.%s",
|
||||||
|
base64.RawURLEncoding.EncodeToString(serializedProtected),
|
||||||
|
base64.RawURLEncoding.EncodeToString(obj.recipients[0].encryptedKey),
|
||||||
|
base64.RawURLEncoding.EncodeToString(obj.iv),
|
||||||
|
base64.RawURLEncoding.EncodeToString(obj.ciphertext),
|
||||||
|
base64.RawURLEncoding.EncodeToString(obj.tag)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||||||
|
func (obj JSONWebEncryption) FullSerialize() string {
|
||||||
|
raw := rawJSONWebEncryption{
|
||||||
|
Unprotected: obj.unprotected,
|
||||||
|
Iv: newBuffer(obj.iv),
|
||||||
|
Ciphertext: newBuffer(obj.ciphertext),
|
||||||
|
EncryptedKey: newBuffer(obj.recipients[0].encryptedKey),
|
||||||
|
Tag: newBuffer(obj.tag),
|
||||||
|
Aad: newBuffer(obj.aad),
|
||||||
|
Recipients: []rawRecipientInfo{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.recipients) > 1 {
|
||||||
|
for _, recipient := range obj.recipients {
|
||||||
|
info := rawRecipientInfo{
|
||||||
|
Header: recipient.header,
|
||||||
|
EncryptedKey: base64.RawURLEncoding.EncodeToString(recipient.encryptedKey),
|
||||||
|
}
|
||||||
|
raw.Recipients = append(raw.Recipients, info)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use flattened serialization
|
||||||
|
raw.Header = obj.recipients[0].header
|
||||||
|
raw.EncryptedKey = newBuffer(obj.recipients[0].encryptedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.protected != nil {
|
||||||
|
raw.Protected = newBuffer(mustSerializeJSON(obj.protected))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(mustSerializeJSON(raw))
|
||||||
|
}
|
608
vendor/gopkg.in/square/go-jose.v2/jwk.go
generated
vendored
Normal file
608
vendor/gopkg.in/square/go-jose.v2/jwk.go
generated
vendored
Normal file
|
@ -0,0 +1,608 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
|
||||||
|
type rawJSONWebKey struct {
|
||||||
|
Use string `json:"use,omitempty"`
|
||||||
|
Kty string `json:"kty,omitempty"`
|
||||||
|
Kid string `json:"kid,omitempty"`
|
||||||
|
Crv string `json:"crv,omitempty"`
|
||||||
|
Alg string `json:"alg,omitempty"`
|
||||||
|
K *byteBuffer `json:"k,omitempty"`
|
||||||
|
X *byteBuffer `json:"x,omitempty"`
|
||||||
|
Y *byteBuffer `json:"y,omitempty"`
|
||||||
|
N *byteBuffer `json:"n,omitempty"`
|
||||||
|
E *byteBuffer `json:"e,omitempty"`
|
||||||
|
// -- Following fields are only used for private keys --
|
||||||
|
// RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are
|
||||||
|
// completely optional. Therefore for RSA/ECDSA, D != nil is a contract that
|
||||||
|
// we have a private key whereas D == nil means we have only a public key.
|
||||||
|
D *byteBuffer `json:"d,omitempty"`
|
||||||
|
P *byteBuffer `json:"p,omitempty"`
|
||||||
|
Q *byteBuffer `json:"q,omitempty"`
|
||||||
|
Dp *byteBuffer `json:"dp,omitempty"`
|
||||||
|
Dq *byteBuffer `json:"dq,omitempty"`
|
||||||
|
Qi *byteBuffer `json:"qi,omitempty"`
|
||||||
|
// Certificates
|
||||||
|
X5c []string `json:"x5c,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWebKey represents a public or private key in JWK format.
|
||||||
|
type JSONWebKey struct {
|
||||||
|
Key interface{}
|
||||||
|
Certificates []*x509.Certificate
|
||||||
|
KeyID string
|
||||||
|
Algorithm string
|
||||||
|
Use string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON serializes the given key to its JSON representation.
|
||||||
|
func (k JSONWebKey) MarshalJSON() ([]byte, error) {
|
||||||
|
var raw *rawJSONWebKey
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch key := k.Key.(type) {
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
raw = fromEdPublicKey(key)
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
raw, err = fromEcPublicKey(key)
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
raw = fromRsaPublicKey(key)
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
raw, err = fromEdPrivateKey(key)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
raw, err = fromEcPrivateKey(key)
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
raw, err = fromRsaPrivateKey(key)
|
||||||
|
case []byte:
|
||||||
|
raw, err = fromSymmetricKey(key)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.Kid = k.KeyID
|
||||||
|
raw.Alg = k.Algorithm
|
||||||
|
raw.Use = k.Use
|
||||||
|
|
||||||
|
for _, cert := range k.Certificates {
|
||||||
|
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON reads a key from its JSON representation.
|
||||||
|
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
var raw rawJSONWebKey
|
||||||
|
err = json.Unmarshal(data, &raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var key interface{}
|
||||||
|
switch raw.Kty {
|
||||||
|
case "EC":
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.ecPrivateKey()
|
||||||
|
} else {
|
||||||
|
key, err = raw.ecPublicKey()
|
||||||
|
}
|
||||||
|
case "RSA":
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.rsaPrivateKey()
|
||||||
|
} else {
|
||||||
|
key, err = raw.rsaPublicKey()
|
||||||
|
}
|
||||||
|
case "oct":
|
||||||
|
key, err = raw.symmetricKey()
|
||||||
|
case "OKP":
|
||||||
|
if raw.Crv == "Ed25519" && raw.X != nil {
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.edPrivateKey()
|
||||||
|
} else {
|
||||||
|
key, err = raw.edPublicKey()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
|
||||||
|
|
||||||
|
k.Certificates, err = parseCertificateChain(raw.X5c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal x5c field: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWebKeySet represents a JWK Set object.
|
||||||
|
type JSONWebKeySet struct {
|
||||||
|
Keys []JSONWebKey `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key convenience method returns keys by key ID. Specification states
|
||||||
|
// that a JWK Set "SHOULD" use distinct key IDs, but allows for some
|
||||||
|
// cases where they are not distinct. Hence method returns a slice
|
||||||
|
// of JSONWebKeys.
|
||||||
|
func (s *JSONWebKeySet) Key(kid string) []JSONWebKey {
|
||||||
|
var keys []JSONWebKey
|
||||||
|
for _, key := range s.Keys {
|
||||||
|
if key.KeyID == kid {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
|
||||||
|
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
|
||||||
|
const edThumbprintTemplate = `{"crv":"%s","kty":"OKP",x":"%s"}`
|
||||||
|
|
||||||
|
func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
||||||
|
coordLength := curveSize(curve)
|
||||||
|
crv, err := curveName(curve)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(x.Bytes()) > coordLength || len(y.Bytes()) > coordLength {
|
||||||
|
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(ecThumbprintTemplate, crv,
|
||||||
|
newFixedSizeBuffer(x.Bytes(), coordLength).base64(),
|
||||||
|
newFixedSizeBuffer(y.Bytes(), coordLength).base64()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
||||||
|
return fmt.Sprintf(rsaThumbprintTemplate,
|
||||||
|
newBufferFromInt(uint64(e)).base64(),
|
||||||
|
newBuffer(n.Bytes()).base64()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
|
||||||
|
crv := "Ed25519"
|
||||||
|
if len(ed) > 32 {
|
||||||
|
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(edThumbprintTemplate, crv,
|
||||||
|
newFixedSizeBuffer(ed, 32).base64()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbprint computes the JWK Thumbprint of a key using the
|
||||||
|
// indicated hash algorithm.
|
||||||
|
func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
||||||
|
var input string
|
||||||
|
var err error
|
||||||
|
switch key := k.Key.(type) {
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
input, err = edThumbprintInput(key)
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
input, err = ecThumbprintInput(key.Curve, key.X, key.Y)
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
input, err = rsaThumbprintInput(key.N, key.E)
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
input, err = rsaThumbprintInput(key.N, key.E)
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
input, err = edThumbprintInput(ed25519.PublicKey(key[32:]))
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := hash.New()
|
||||||
|
h.Write([]byte(input))
|
||||||
|
return h.Sum(nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPublic returns true if the JWK represents a public key (not symmetric, not private).
|
||||||
|
func (k *JSONWebKey) IsPublic() bool {
|
||||||
|
switch k.Key.(type) {
|
||||||
|
case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public creates JSONWebKey with corresponding publik key if JWK represents asymmetric private key.
|
||||||
|
func (k *JSONWebKey) Public() JSONWebKey {
|
||||||
|
if k.IsPublic() {
|
||||||
|
return *k
|
||||||
|
}
|
||||||
|
ret := *k
|
||||||
|
switch key := k.Key.(type) {
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
ret.Key = key.Public()
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
ret.Key = key.Public()
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
ret.Key = key.Public()
|
||||||
|
default:
|
||||||
|
return JSONWebKey{} // returning invalid key
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid checks that the key contains the expected parameters.
|
||||||
|
func (k *JSONWebKey) Valid() bool {
|
||||||
|
if k.Key == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch key := k.Key.(type) {
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
if key.Curve == nil || key.X == nil || key.Y == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
if key.Curve == nil || key.X == nil || key.Y == nil || key.D == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
if key.N == nil || key.E == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
if key.N == nil || key.E == 0 || key.D == nil || len(key.Primes) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
if len(key) != 32 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
if len(key) != 64 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
|
||||||
|
if key.N == nil || key.E == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rsa.PublicKey{
|
||||||
|
N: key.N.bigInt(),
|
||||||
|
E: key.E.toInt(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromEdPublicKey(pub ed25519.PublicKey) *rawJSONWebKey {
|
||||||
|
return &rawJSONWebKey{
|
||||||
|
Kty: "OKP",
|
||||||
|
Crv: "Ed25519",
|
||||||
|
X: newBuffer(pub),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJSONWebKey {
|
||||||
|
return &rawJSONWebKey{
|
||||||
|
Kty: "RSA",
|
||||||
|
N: newBuffer(pub.N.Bytes()),
|
||||||
|
E: newBufferFromInt(uint64(pub.E)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
||||||
|
var curve elliptic.Curve
|
||||||
|
switch key.Crv {
|
||||||
|
case "P-256":
|
||||||
|
curve = elliptic.P256()
|
||||||
|
case "P-384":
|
||||||
|
curve = elliptic.P384()
|
||||||
|
case "P-521":
|
||||||
|
curve = elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.X == nil || key.Y == nil {
|
||||||
|
return nil, errors.New("square/go-jose: invalid EC key, missing x/y values")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length of this octet string MUST be the full size of a coordinate for
|
||||||
|
// the curve specified in the "crv" parameter.
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||||
|
if curveSize(curve) != len(key.X.data) {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for x")
|
||||||
|
}
|
||||||
|
|
||||||
|
if curveSize(curve) != len(key.Y.data) {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for y")
|
||||||
|
}
|
||||||
|
|
||||||
|
x := key.X.bigInt()
|
||||||
|
y := key.Y.bigInt()
|
||||||
|
|
||||||
|
if !curve.IsOnCurve(x, y) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ecdsa.PublicKey{
|
||||||
|
Curve: curve,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJSONWebKey, error) {
|
||||||
|
if pub == nil || pub.X == nil || pub.Y == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := curveName(pub.Curve)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := curveSize(pub.Curve)
|
||||||
|
|
||||||
|
xBytes := pub.X.Bytes()
|
||||||
|
yBytes := pub.Y.Bytes()
|
||||||
|
|
||||||
|
if len(xBytes) > size || len(yBytes) > size {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := &rawJSONWebKey{
|
||||||
|
Kty: "EC",
|
||||||
|
Crv: name,
|
||||||
|
X: newFixedSizeBuffer(xBytes, size),
|
||||||
|
Y: newFixedSizeBuffer(yBytes, size),
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
|
||||||
|
var missing []string
|
||||||
|
switch {
|
||||||
|
case key.D == nil:
|
||||||
|
missing = append(missing, "D")
|
||||||
|
case key.X == nil:
|
||||||
|
missing = append(missing, "X")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
privateKey := make([]byte, ed25519.PrivateKeySize)
|
||||||
|
copy(privateKey[0:32], key.D.bytes())
|
||||||
|
copy(privateKey[32:], key.X.bytes())
|
||||||
|
rv := ed25519.PrivateKey(privateKey)
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
|
||||||
|
if key.X == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid Ed key, missing x value")
|
||||||
|
}
|
||||||
|
publicKey := make([]byte, ed25519.PublicKeySize)
|
||||||
|
copy(publicKey[0:32], key.X.bytes())
|
||||||
|
rv := ed25519.PublicKey(publicKey)
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
||||||
|
var missing []string
|
||||||
|
switch {
|
||||||
|
case key.N == nil:
|
||||||
|
missing = append(missing, "N")
|
||||||
|
case key.E == nil:
|
||||||
|
missing = append(missing, "E")
|
||||||
|
case key.D == nil:
|
||||||
|
missing = append(missing, "D")
|
||||||
|
case key.P == nil:
|
||||||
|
missing = append(missing, "P")
|
||||||
|
case key.Q == nil:
|
||||||
|
missing = append(missing, "Q")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := &rsa.PrivateKey{
|
||||||
|
PublicKey: rsa.PublicKey{
|
||||||
|
N: key.N.bigInt(),
|
||||||
|
E: key.E.toInt(),
|
||||||
|
},
|
||||||
|
D: key.D.bigInt(),
|
||||||
|
Primes: []*big.Int{
|
||||||
|
key.P.bigInt(),
|
||||||
|
key.Q.bigInt(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.Dp != nil {
|
||||||
|
rv.Precomputed.Dp = key.Dp.bigInt()
|
||||||
|
}
|
||||||
|
if key.Dq != nil {
|
||||||
|
rv.Precomputed.Dq = key.Dq.bigInt()
|
||||||
|
}
|
||||||
|
if key.Qi != nil {
|
||||||
|
rv.Precomputed.Qinv = key.Qi.bigInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := rv.Validate()
|
||||||
|
return rv, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) {
|
||||||
|
raw := fromEdPublicKey(ed25519.PublicKey(ed[32:]))
|
||||||
|
|
||||||
|
raw.D = newBuffer(ed[0:32])
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJSONWebKey, error) {
|
||||||
|
if len(rsa.Primes) != 2 {
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := fromRsaPublicKey(&rsa.PublicKey)
|
||||||
|
|
||||||
|
raw.D = newBuffer(rsa.D.Bytes())
|
||||||
|
raw.P = newBuffer(rsa.Primes[0].Bytes())
|
||||||
|
raw.Q = newBuffer(rsa.Primes[1].Bytes())
|
||||||
|
|
||||||
|
if rsa.Precomputed.Dp != nil {
|
||||||
|
raw.Dp = newBuffer(rsa.Precomputed.Dp.Bytes())
|
||||||
|
}
|
||||||
|
if rsa.Precomputed.Dq != nil {
|
||||||
|
raw.Dq = newBuffer(rsa.Precomputed.Dq.Bytes())
|
||||||
|
}
|
||||||
|
if rsa.Precomputed.Qinv != nil {
|
||||||
|
raw.Qi = newBuffer(rsa.Precomputed.Qinv.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||||
|
var curve elliptic.Curve
|
||||||
|
switch key.Crv {
|
||||||
|
case "P-256":
|
||||||
|
curve = elliptic.P256()
|
||||||
|
case "P-384":
|
||||||
|
curve = elliptic.P384()
|
||||||
|
case "P-521":
|
||||||
|
curve = elliptic.P521()
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.X == nil || key.Y == nil || key.D == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length of this octet string MUST be the full size of a coordinate for
|
||||||
|
// the curve specified in the "crv" parameter.
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||||
|
if curveSize(curve) != len(key.X.data) {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for x")
|
||||||
|
}
|
||||||
|
|
||||||
|
if curveSize(curve) != len(key.Y.data) {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for y")
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
||||||
|
if dSize(curve) != len(key.D.data) {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for d")
|
||||||
|
}
|
||||||
|
|
||||||
|
x := key.X.bigInt()
|
||||||
|
y := key.Y.bigInt()
|
||||||
|
|
||||||
|
if !curve.IsOnCurve(x, y) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ecdsa.PrivateKey{
|
||||||
|
PublicKey: ecdsa.PublicKey{
|
||||||
|
Curve: curve,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
},
|
||||||
|
D: key.D.bigInt(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJSONWebKey, error) {
|
||||||
|
raw, err := fromEcPublicKey(&ec.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ec.D == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid EC private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
raw.D = newFixedSizeBuffer(ec.D.Bytes(), dSize(ec.PublicKey.Curve))
|
||||||
|
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dSize returns the size in octets for the "d" member of an elliptic curve
|
||||||
|
// private key.
|
||||||
|
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
||||||
|
// octets (where n is the order of the curve).
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
||||||
|
func dSize(curve elliptic.Curve) int {
|
||||||
|
order := curve.Params().P
|
||||||
|
bitLen := order.BitLen()
|
||||||
|
size := bitLen / 8
|
||||||
|
if bitLen%8 != 0 {
|
||||||
|
size = size + 1
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromSymmetricKey(key []byte) (*rawJSONWebKey, error) {
|
||||||
|
return &rawJSONWebKey{
|
||||||
|
Kty: "oct",
|
||||||
|
K: newBuffer(key),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
|
||||||
|
if key.K == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid OCT (symmetric) key, missing k value")
|
||||||
|
}
|
||||||
|
return key.K.bytes(), nil
|
||||||
|
}
|
366
vendor/gopkg.in/square/go-jose.v2/jws.go
generated
vendored
Normal file
366
vendor/gopkg.in/square/go-jose.v2/jws.go
generated
vendored
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
|
||||||
|
type rawJSONWebSignature struct {
|
||||||
|
Payload *byteBuffer `json:"payload,omitempty"`
|
||||||
|
Signatures []rawSignatureInfo `json:"signatures,omitempty"`
|
||||||
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||||||
|
Header *rawHeader `json:"header,omitempty"`
|
||||||
|
Signature *byteBuffer `json:"signature,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawSignatureInfo represents a single JWS signature over the JWS payload and protected header.
|
||||||
|
type rawSignatureInfo struct {
|
||||||
|
Protected *byteBuffer `json:"protected,omitempty"`
|
||||||
|
Header *rawHeader `json:"header,omitempty"`
|
||||||
|
Signature *byteBuffer `json:"signature,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWebSignature represents a signed JWS object after parsing.
|
||||||
|
type JSONWebSignature struct {
|
||||||
|
payload []byte
|
||||||
|
// Signatures attached to this object (may be more than one for multi-sig).
|
||||||
|
// Be careful about accessing these directly, prefer to use Verify() or
|
||||||
|
// VerifyMulti() to ensure that the data you're getting is verified.
|
||||||
|
Signatures []Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature represents a single signature over the JWS payload and protected header.
|
||||||
|
type Signature struct {
|
||||||
|
// Merged header fields. Contains both protected and unprotected header
|
||||||
|
// values. Prefer using Protected and Unprotected fields instead of this.
|
||||||
|
// Values in this header may or may not have been signed and in general
|
||||||
|
// should not be trusted.
|
||||||
|
Header Header
|
||||||
|
|
||||||
|
// Protected header. Values in this header were signed and
|
||||||
|
// will be verified as part of the signature verification process.
|
||||||
|
Protected Header
|
||||||
|
|
||||||
|
// Unprotected header. Values in this header were not signed
|
||||||
|
// and in general should not be trusted.
|
||||||
|
Unprotected Header
|
||||||
|
|
||||||
|
// The actual signature value
|
||||||
|
Signature []byte
|
||||||
|
|
||||||
|
protected *rawHeader
|
||||||
|
header *rawHeader
|
||||||
|
original *rawSignatureInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSigned parses a signed message in compact or full serialization format.
|
||||||
|
func ParseSigned(signature string) (*JSONWebSignature, error) {
|
||||||
|
signature = stripWhitespace(signature)
|
||||||
|
if strings.HasPrefix(signature, "{") {
|
||||||
|
return parseSignedFull(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseSignedCompact(signature, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDetached parses a signed message in compact serialization format with detached payload.
|
||||||
|
func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
|
||||||
|
if payload == nil {
|
||||||
|
return nil, errors.New("square/go-jose: nil payload")
|
||||||
|
}
|
||||||
|
return parseSignedCompact(stripWhitespace(signature), payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a header value
|
||||||
|
func (sig Signature) mergedHeaders() rawHeader {
|
||||||
|
out := rawHeader{}
|
||||||
|
out.merge(sig.protected)
|
||||||
|
out.merge(sig.header)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute data to be signed
|
||||||
|
func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) ([]byte, error) {
|
||||||
|
var authData bytes.Buffer
|
||||||
|
|
||||||
|
protectedHeader := new(rawHeader)
|
||||||
|
|
||||||
|
if signature.original != nil && signature.original.Protected != nil {
|
||||||
|
if err := json.Unmarshal(signature.original.Protected.bytes(), protectedHeader); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authData.WriteString(signature.original.Protected.base64())
|
||||||
|
} else if signature.protected != nil {
|
||||||
|
protectedHeader = signature.protected
|
||||||
|
authData.WriteString(base64.RawURLEncoding.EncodeToString(mustSerializeJSON(protectedHeader)))
|
||||||
|
}
|
||||||
|
|
||||||
|
needsBase64 := true
|
||||||
|
|
||||||
|
if protectedHeader != nil {
|
||||||
|
var err error
|
||||||
|
if needsBase64, err = protectedHeader.getB64(); err != nil {
|
||||||
|
needsBase64 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authData.WriteByte('.')
|
||||||
|
|
||||||
|
if needsBase64 {
|
||||||
|
authData.WriteString(base64.RawURLEncoding.EncodeToString(payload))
|
||||||
|
} else {
|
||||||
|
authData.Write(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return authData.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSignedFull parses a message in full format.
|
||||||
|
func parseSignedFull(input string) (*JSONWebSignature, error) {
|
||||||
|
var parsed rawJSONWebSignature
|
||||||
|
err := json.Unmarshal([]byte(input), &parsed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.sanitized()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitized produces a cleaned-up JWS object from the raw JSON.
|
||||||
|
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
|
||||||
|
if parsed.Payload == nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := &JSONWebSignature{
|
||||||
|
payload: parsed.Payload.bytes(),
|
||||||
|
Signatures: make([]Signature, len(parsed.Signatures)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsed.Signatures) == 0 {
|
||||||
|
// No signatures array, must be flattened serialization
|
||||||
|
signature := Signature{}
|
||||||
|
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
|
||||||
|
signature.protected = &rawHeader{}
|
||||||
|
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that there is not a nonce in the unprotected header
|
||||||
|
if parsed.Header != nil && parsed.Header.getNonce() != "" {
|
||||||
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
signature.header = parsed.Header
|
||||||
|
signature.Signature = parsed.Signature.bytes()
|
||||||
|
// Make a fake "original" rawSignatureInfo to store the unprocessed
|
||||||
|
// Protected header. This is necessary because the Protected header can
|
||||||
|
// contain arbitrary fields not registered as part of the spec. See
|
||||||
|
// https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
|
||||||
|
// If we unmarshal Protected into a rawHeader with its explicit list of fields,
|
||||||
|
// we cannot marshal losslessly. So we have to keep around the original bytes.
|
||||||
|
// This is used in computeAuthData, which will first attempt to use
|
||||||
|
// the original bytes of a protected header, and fall back on marshaling the
|
||||||
|
// header struct only if those bytes are not available.
|
||||||
|
signature.original = &rawSignatureInfo{
|
||||||
|
Protected: parsed.Protected,
|
||||||
|
Header: parsed.Header,
|
||||||
|
Signature: parsed.Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
signature.Header, err = signature.mergedHeaders().sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if signature.header != nil {
|
||||||
|
signature.Unprotected, err = signature.header.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if signature.protected != nil {
|
||||||
|
signature.Protected, err = signature.protected.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||||
|
jwk := signature.Header.JSONWebKey
|
||||||
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Signatures = append(obj.Signatures, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sig := range parsed.Signatures {
|
||||||
|
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
|
||||||
|
obj.Signatures[i].protected = &rawHeader{}
|
||||||
|
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that there is not a nonce in the unprotected header
|
||||||
|
if sig.Header != nil && sig.Header.getNonce() != "" {
|
||||||
|
return nil, ErrUnprotectedNonce
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
obj.Signatures[i].Header, err = obj.Signatures[i].mergedHeaders().sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Signatures[i].header != nil {
|
||||||
|
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.Signatures[i].protected != nil {
|
||||||
|
obj.Signatures[i].Protected, err = obj.Signatures[i].protected.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.Signatures[i].Signature = sig.Signature.bytes()
|
||||||
|
|
||||||
|
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
|
||||||
|
jwk := obj.Signatures[i].Header.JSONWebKey
|
||||||
|
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
|
||||||
|
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy value of sig
|
||||||
|
original := sig
|
||||||
|
|
||||||
|
obj.Signatures[i].header = sig.Header
|
||||||
|
obj.Signatures[i].original = &original
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSignedCompact parses a message in compact format.
|
||||||
|
func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
|
||||||
|
parts := strings.Split(input, ".")
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[1] != "" && payload != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: payload is not detached")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload == nil {
|
||||||
|
payload, err = base64.RawURLEncoding.DecodeString(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := &rawJSONWebSignature{
|
||||||
|
Payload: newBuffer(payload),
|
||||||
|
Protected: newBuffer(rawProtected),
|
||||||
|
Signature: newBuffer(signature),
|
||||||
|
}
|
||||||
|
return raw.sanitized()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
|
||||||
|
if len(obj.Signatures) != 1 || obj.Signatures[0].header != nil || obj.Signatures[0].protected == nil {
|
||||||
|
return "", ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedProtected := base64.RawURLEncoding.EncodeToString(mustSerializeJSON(obj.Signatures[0].protected))
|
||||||
|
payload := ""
|
||||||
|
signature := base64.RawURLEncoding.EncodeToString(obj.Signatures[0].Signature)
|
||||||
|
|
||||||
|
if !detached {
|
||||||
|
payload = base64.RawURLEncoding.EncodeToString(obj.payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s.%s.%s", serializedProtected, payload, signature), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompactSerialize serializes an object using the compact serialization format.
|
||||||
|
func (obj JSONWebSignature) CompactSerialize() (string, error) {
|
||||||
|
return obj.compactSerialize(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachedCompactSerialize serializes an object using the compact serialization format with detached payload.
|
||||||
|
func (obj JSONWebSignature) DetachedCompactSerialize() (string, error) {
|
||||||
|
return obj.compactSerialize(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullSerialize serializes an object using the full JSON serialization format.
|
||||||
|
func (obj JSONWebSignature) FullSerialize() string {
|
||||||
|
raw := rawJSONWebSignature{
|
||||||
|
Payload: newBuffer(obj.payload),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.Signatures) == 1 {
|
||||||
|
if obj.Signatures[0].protected != nil {
|
||||||
|
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
|
||||||
|
raw.Protected = newBuffer(serializedProtected)
|
||||||
|
}
|
||||||
|
raw.Header = obj.Signatures[0].header
|
||||||
|
raw.Signature = newBuffer(obj.Signatures[0].Signature)
|
||||||
|
} else {
|
||||||
|
raw.Signatures = make([]rawSignatureInfo, len(obj.Signatures))
|
||||||
|
for i, signature := range obj.Signatures {
|
||||||
|
raw.Signatures[i] = rawSignatureInfo{
|
||||||
|
Header: signature.header,
|
||||||
|
Signature: newBuffer(signature.Signature),
|
||||||
|
}
|
||||||
|
|
||||||
|
if signature.protected != nil {
|
||||||
|
raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(mustSerializeJSON(raw))
|
||||||
|
}
|
144
vendor/gopkg.in/square/go-jose.v2/opaque.go
generated
vendored
Normal file
144
vendor/gopkg.in/square/go-jose.v2/opaque.go
generated
vendored
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2018 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
// OpaqueSigner is an interface that supports signing payloads with opaque
|
||||||
|
// private key(s). Private key operations preformed by implementors may, for
|
||||||
|
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
|
||||||
|
// transparently to the user of this interface.
|
||||||
|
type OpaqueSigner interface {
|
||||||
|
// Public returns the public key of the current signing key.
|
||||||
|
Public() *JSONWebKey
|
||||||
|
// Algs returns a list of supported signing algorithms.
|
||||||
|
Algs() []SignatureAlgorithm
|
||||||
|
// SignPayload signs a payload with the current signing key using the given
|
||||||
|
// algorithm.
|
||||||
|
SignPayload(payload []byte, alg SignatureAlgorithm) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type opaqueSigner struct {
|
||||||
|
signer OpaqueSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigInfo, error) {
|
||||||
|
var algSupported bool
|
||||||
|
for _, salg := range signer.Algs() {
|
||||||
|
if alg == salg {
|
||||||
|
algSupported = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !algSupported {
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientSigInfo{
|
||||||
|
sigAlg: alg,
|
||||||
|
publicKey: signer.Public,
|
||||||
|
signer: &opaqueSigner{
|
||||||
|
signer: signer,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opaqueSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
out, err := o.signer.SignPayload(payload, alg)
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signature{
|
||||||
|
Signature: out,
|
||||||
|
protected: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpaqueVerifier is an interface that supports verifying payloads with opaque
|
||||||
|
// public key(s). An OpaqueSigner may rotate signing keys transparently to the
|
||||||
|
// user of this interface.
|
||||||
|
type OpaqueVerifier interface {
|
||||||
|
VerifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type opaqueVerifier struct {
|
||||||
|
verifier OpaqueVerifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||||
|
return o.verifier.VerifyPayload(payload, signature, alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpaqueKeyEncrypter is an interface that supports encrypting keys with an opaque key.
|
||||||
|
type OpaqueKeyEncrypter interface {
|
||||||
|
// KeyID returns the kid
|
||||||
|
KeyID() string
|
||||||
|
// Algs returns a list of supported key encryption algorithms.
|
||||||
|
Algs() []KeyAlgorithm
|
||||||
|
// encryptKey encrypts the CEK using the given algorithm.
|
||||||
|
encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type opaqueKeyEncrypter struct {
|
||||||
|
encrypter OpaqueKeyEncrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOpaqueKeyEncrypter(alg KeyAlgorithm, encrypter OpaqueKeyEncrypter) (recipientKeyInfo, error) {
|
||||||
|
var algSupported bool
|
||||||
|
for _, salg := range encrypter.Algs() {
|
||||||
|
if alg == salg {
|
||||||
|
algSupported = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !algSupported {
|
||||||
|
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientKeyInfo{
|
||||||
|
keyID: encrypter.KeyID(),
|
||||||
|
keyAlg: alg,
|
||||||
|
keyEncrypter: &opaqueKeyEncrypter{
|
||||||
|
encrypter: encrypter,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (oke *opaqueKeyEncrypter) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||||
|
return oke.encrypter.encryptKey(cek, alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
//OpaqueKeyDecrypter is an interface that supports decrypting keys with an opaque key.
|
||||||
|
type OpaqueKeyDecrypter interface {
|
||||||
|
DecryptKey(encryptedKey []byte, header Header) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type opaqueKeyDecrypter struct {
|
||||||
|
decrypter OpaqueKeyDecrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (okd *opaqueKeyDecrypter) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
|
mergedHeaders := rawHeader{}
|
||||||
|
mergedHeaders.merge(&headers)
|
||||||
|
mergedHeaders.merge(recipient.header)
|
||||||
|
|
||||||
|
header, err := mergedHeaders.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return okd.decrypter.DecryptKey(recipient.encryptedKey, header)
|
||||||
|
}
|
520
vendor/gopkg.in/square/go-jose.v2/shared.go
generated
vendored
Normal file
520
vendor/gopkg.in/square/go-jose.v2/shared.go
generated
vendored
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyAlgorithm represents a key management algorithm.
|
||||||
|
type KeyAlgorithm string
|
||||||
|
|
||||||
|
// SignatureAlgorithm represents a signature (or MAC) algorithm.
|
||||||
|
type SignatureAlgorithm string
|
||||||
|
|
||||||
|
// ContentEncryption represents a content encryption algorithm.
|
||||||
|
type ContentEncryption string
|
||||||
|
|
||||||
|
// CompressionAlgorithm represents an algorithm used for plaintext compression.
|
||||||
|
type CompressionAlgorithm string
|
||||||
|
|
||||||
|
// ContentType represents type of the contained data.
|
||||||
|
type ContentType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCryptoFailure represents an error in cryptographic primitive. This
|
||||||
|
// occurs when, for example, a message had an invalid authentication tag or
|
||||||
|
// could not be decrypted.
|
||||||
|
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
|
||||||
|
|
||||||
|
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
|
||||||
|
// supported. This occurs when trying to instantiate an encrypter for an
|
||||||
|
// algorithm that is not yet implemented.
|
||||||
|
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
|
||||||
|
|
||||||
|
// ErrUnsupportedKeyType indicates that the given key type/format is not
|
||||||
|
// supported. This occurs when trying to instantiate an encrypter and passing
|
||||||
|
// it a key of an unrecognized type or with unsupported parameters, such as
|
||||||
|
// an RSA private key with more than two primes.
|
||||||
|
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
||||||
|
|
||||||
|
// ErrInvalidKeySize indicates that the given key is not the correct size
|
||||||
|
// for the selected algorithm. This can occur, for example, when trying to
|
||||||
|
// encrypt with AES-256 but passing only a 128-bit key as input.
|
||||||
|
ErrInvalidKeySize = errors.New("square/go-jose: invalid key size for algorithm")
|
||||||
|
|
||||||
|
// ErrNotSupported serialization of object is not supported. This occurs when
|
||||||
|
// trying to compact-serialize an object which can't be represented in
|
||||||
|
// compact form.
|
||||||
|
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
|
||||||
|
|
||||||
|
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
|
||||||
|
// nonce header parameter was included in an unprotected header object.
|
||||||
|
ErrUnprotectedNonce = errors.New("square/go-jose: Nonce parameter included in unprotected header")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key management algorithms
|
||||||
|
const (
|
||||||
|
ED25519 = KeyAlgorithm("ED25519")
|
||||||
|
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
|
||||||
|
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
|
||||||
|
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
|
||||||
|
A128KW = KeyAlgorithm("A128KW") // AES key wrap (128)
|
||||||
|
A192KW = KeyAlgorithm("A192KW") // AES key wrap (192)
|
||||||
|
A256KW = KeyAlgorithm("A256KW") // AES key wrap (256)
|
||||||
|
DIRECT = KeyAlgorithm("dir") // Direct encryption
|
||||||
|
ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES
|
||||||
|
ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128)
|
||||||
|
ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192)
|
||||||
|
ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256)
|
||||||
|
A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128)
|
||||||
|
A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192)
|
||||||
|
A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256)
|
||||||
|
PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
|
||||||
|
PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
|
||||||
|
PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Signature algorithms
|
||||||
|
const (
|
||||||
|
EdDSA = SignatureAlgorithm("EdDSA")
|
||||||
|
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
|
||||||
|
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
|
||||||
|
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
|
||||||
|
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
|
||||||
|
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
|
||||||
|
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
|
||||||
|
ES256 = SignatureAlgorithm("ES256") // ECDSA using P-256 and SHA-256
|
||||||
|
ES384 = SignatureAlgorithm("ES384") // ECDSA using P-384 and SHA-384
|
||||||
|
ES512 = SignatureAlgorithm("ES512") // ECDSA using P-521 and SHA-512
|
||||||
|
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
|
||||||
|
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
|
||||||
|
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
|
||||||
|
)
|
||||||
|
|
||||||
|
// Content encryption algorithms
|
||||||
|
const (
|
||||||
|
A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
|
||||||
|
A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
|
||||||
|
A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
|
||||||
|
A128GCM = ContentEncryption("A128GCM") // AES-GCM (128)
|
||||||
|
A192GCM = ContentEncryption("A192GCM") // AES-GCM (192)
|
||||||
|
A256GCM = ContentEncryption("A256GCM") // AES-GCM (256)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Compression algorithms
|
||||||
|
const (
|
||||||
|
NONE = CompressionAlgorithm("") // No compression
|
||||||
|
DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
|
||||||
|
)
|
||||||
|
|
||||||
|
// A key in the protected header of a JWS object. Use of the Header...
|
||||||
|
// constants is preferred to enhance type safety.
|
||||||
|
type HeaderKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderType HeaderKey = "typ" // string
|
||||||
|
HeaderContentType = "cty" // string
|
||||||
|
|
||||||
|
// These are set by go-jose and shouldn't need to be set by consumers of the
|
||||||
|
// library.
|
||||||
|
headerAlgorithm = "alg" // string
|
||||||
|
headerEncryption = "enc" // ContentEncryption
|
||||||
|
headerCompression = "zip" // CompressionAlgorithm
|
||||||
|
headerCritical = "crit" // []string
|
||||||
|
|
||||||
|
headerAPU = "apu" // *byteBuffer
|
||||||
|
headerAPV = "apv" // *byteBuffer
|
||||||
|
headerEPK = "epk" // *JSONWebKey
|
||||||
|
headerIV = "iv" // *byteBuffer
|
||||||
|
headerTag = "tag" // *byteBuffer
|
||||||
|
headerX5c = "x5c" // []*x509.Certificate
|
||||||
|
|
||||||
|
headerJWK = "jwk" // *JSONWebKey
|
||||||
|
headerKeyID = "kid" // string
|
||||||
|
headerNonce = "nonce" // string
|
||||||
|
headerB64 = "b64" // bool
|
||||||
|
|
||||||
|
headerP2C = "p2c" // *byteBuffer (int)
|
||||||
|
headerP2S = "p2s" // *byteBuffer ([]byte)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedCritical is the set of supported extensions that are understood and processed.
|
||||||
|
var supportedCritical = map[string]bool{
|
||||||
|
headerB64: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
|
||||||
|
//
|
||||||
|
// The decoding of the constituent items is deferred because we want to marshal
|
||||||
|
// some members into particular structs rather than generic maps, but at the
|
||||||
|
// same time we need to receive any extra fields unhandled by this library to
|
||||||
|
// pass through to consuming code in case it wants to examine them.
|
||||||
|
type rawHeader map[HeaderKey]*json.RawMessage
|
||||||
|
|
||||||
|
// Header represents the read-only JOSE header for JWE/JWS objects.
|
||||||
|
type Header struct {
|
||||||
|
KeyID string
|
||||||
|
JSONWebKey *JSONWebKey
|
||||||
|
Algorithm string
|
||||||
|
Nonce string
|
||||||
|
|
||||||
|
// Unverified certificate chain parsed from x5c header.
|
||||||
|
certificates []*x509.Certificate
|
||||||
|
|
||||||
|
// Any headers not recognised above get unmarshaled
|
||||||
|
// from JSON in a generic manner and placed in this map.
|
||||||
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificates verifies & returns the certificate chain present
|
||||||
|
// in the x5c header field of a message, if one was present. Returns
|
||||||
|
// an error if there was no x5c header present or the chain could
|
||||||
|
// not be validated with the given verify options.
|
||||||
|
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
||||||
|
if len(h.certificates) == 0 {
|
||||||
|
return nil, errors.New("square/go-jose: no x5c header present in message")
|
||||||
|
}
|
||||||
|
|
||||||
|
leaf := h.certificates[0]
|
||||||
|
if opts.Intermediates == nil {
|
||||||
|
opts.Intermediates = x509.NewCertPool()
|
||||||
|
for _, intermediate := range h.certificates[1:] {
|
||||||
|
opts.Intermediates.AddCert(intermediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return leaf.Verify(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed[k] = makeRawMessage(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getString gets a string from the raw JSON, defaulting to "".
|
||||||
|
func (parsed rawHeader) getString(k HeaderKey) string {
|
||||||
|
v, ok := parsed[k]
|
||||||
|
if !ok || v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
err := json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// getByteBuffer gets a byte buffer from the raw JSON. Returns (nil, nil) if
|
||||||
|
// not specified.
|
||||||
|
func (parsed rawHeader) getByteBuffer(k HeaderKey) (*byteBuffer, error) {
|
||||||
|
v := parsed[k]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var bb *byteBuffer
|
||||||
|
err := json.Unmarshal(*v, &bb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bb, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAlgorithm extracts parsed "alg" from the raw JSON as a KeyAlgorithm.
|
||||||
|
func (parsed rawHeader) getAlgorithm() KeyAlgorithm {
|
||||||
|
return KeyAlgorithm(parsed.getString(headerAlgorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignatureAlgorithm extracts parsed "alg" from the raw JSON as a SignatureAlgorithm.
|
||||||
|
func (parsed rawHeader) getSignatureAlgorithm() SignatureAlgorithm {
|
||||||
|
return SignatureAlgorithm(parsed.getString(headerAlgorithm))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEncryption extracts parsed "enc" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getEncryption() ContentEncryption {
|
||||||
|
return ContentEncryption(parsed.getString(headerEncryption))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCompression extracts parsed "zip" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getCompression() CompressionAlgorithm {
|
||||||
|
return CompressionAlgorithm(parsed.getString(headerCompression))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parsed rawHeader) getNonce() string {
|
||||||
|
return parsed.getString(headerNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEPK extracts parsed "epk" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getEPK() (*JSONWebKey, error) {
|
||||||
|
v := parsed[headerEPK]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var epk *JSONWebKey
|
||||||
|
err := json.Unmarshal(*v, &epk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return epk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPU extracts parsed "apu" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getAPU() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerAPU)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAPV extracts parsed "apv" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getAPV() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerAPV)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIV extracts parsed "iv" frpom the raw JSON.
|
||||||
|
func (parsed rawHeader) getIV() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerIV)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTag extracts parsed "tag" frpom the raw JSON.
|
||||||
|
func (parsed rawHeader) getTag() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getJWK extracts parsed "jwk" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getJWK() (*JSONWebKey, error) {
|
||||||
|
v := parsed[headerJWK]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var jwk *JSONWebKey
|
||||||
|
err := json.Unmarshal(*v, &jwk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return jwk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCritical extracts parsed "crit" from the raw JSON. If omitted, it
|
||||||
|
// returns an empty slice.
|
||||||
|
func (parsed rawHeader) getCritical() ([]string, error) {
|
||||||
|
v := parsed[headerCritical]
|
||||||
|
if v == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var q []string
|
||||||
|
err := json.Unmarshal(*v, &q)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getS2C extracts parsed "p2c" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getP2C() (int, error) {
|
||||||
|
v := parsed[headerP2C]
|
||||||
|
if v == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var p2c int
|
||||||
|
err := json.Unmarshal(*v, &p2c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return p2c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getS2S extracts parsed "p2s" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getP2S() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerP2S)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getB64 extracts parsed "b64" from the raw JSON, defaulting to true.
|
||||||
|
func (parsed rawHeader) getB64() (bool, error) {
|
||||||
|
v := parsed[headerB64]
|
||||||
|
if v == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 bool
|
||||||
|
err := json.Unmarshal(*v, &b64)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
return b64, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitized produces a cleaned-up header object from the raw JSON.
|
||||||
|
func (parsed rawHeader) sanitized() (h Header, err error) {
|
||||||
|
for k, v := range parsed {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case headerJWK:
|
||||||
|
var jwk *JSONWebKey
|
||||||
|
err = json.Unmarshal(*v, &jwk)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal JWK: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.JSONWebKey = jwk
|
||||||
|
case headerKeyID:
|
||||||
|
var s string
|
||||||
|
err = json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal key ID: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.KeyID = s
|
||||||
|
case headerAlgorithm:
|
||||||
|
var s string
|
||||||
|
err = json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal algorithm: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Algorithm = s
|
||||||
|
case headerNonce:
|
||||||
|
var s string
|
||||||
|
err = json.Unmarshal(*v, &s)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal nonce: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.Nonce = s
|
||||||
|
case headerX5c:
|
||||||
|
c := []string{}
|
||||||
|
err = json.Unmarshal(*v, &c)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.certificates, err = parseCertificateChain(c)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if h.ExtraHeaders == nil {
|
||||||
|
h.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
|
}
|
||||||
|
var v2 interface{}
|
||||||
|
err = json.Unmarshal(*v, &v2)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to unmarshal value: %v: %#v", err, string(*v))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ExtraHeaders[k] = v2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
|
||||||
|
out := make([]*x509.Certificate, len(chain))
|
||||||
|
for i, cert := range chain {
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(cert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[i], err = x509.ParseCertificate(raw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dst rawHeader) isSet(k HeaderKey) bool {
|
||||||
|
dvr := dst[k]
|
||||||
|
if dvr == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var dv interface{}
|
||||||
|
err := json.Unmarshal(*dvr, &dv)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if dvStr, ok := dv.(string); ok {
|
||||||
|
return dvStr != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge headers from src into dst, giving precedence to headers from l.
|
||||||
|
func (dst rawHeader) merge(src *rawHeader) {
|
||||||
|
if src == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range *src {
|
||||||
|
if dst.isSet(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get JOSE name of curve
|
||||||
|
func curveName(crv elliptic.Curve) (string, error) {
|
||||||
|
switch crv {
|
||||||
|
case elliptic.P256():
|
||||||
|
return "P-256", nil
|
||||||
|
case elliptic.P384():
|
||||||
|
return "P-384", nil
|
||||||
|
case elliptic.P521():
|
||||||
|
return "P-521", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get size of curve in bytes
|
||||||
|
func curveSize(crv elliptic.Curve) int {
|
||||||
|
bits := crv.Params().BitSize
|
||||||
|
|
||||||
|
div := bits / 8
|
||||||
|
mod := bits % 8
|
||||||
|
|
||||||
|
if mod == 0 {
|
||||||
|
return div
|
||||||
|
}
|
||||||
|
|
||||||
|
return div + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRawMessage(b []byte) *json.RawMessage {
|
||||||
|
rm := json.RawMessage(b)
|
||||||
|
return &rm
|
||||||
|
}
|
441
vendor/gopkg.in/square/go-jose.v2/signing.go
generated
vendored
Normal file
441
vendor/gopkg.in/square/go-jose.v2/signing.go
generated
vendored
Normal file
|
@ -0,0 +1,441 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NonceSource represents a source of random nonces to go into JWS objects
|
||||||
|
type NonceSource interface {
|
||||||
|
Nonce() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signer represents a signer which takes a payload and produces a signed JWS object.
|
||||||
|
type Signer interface {
|
||||||
|
Sign(payload []byte) (*JSONWebSignature, error)
|
||||||
|
Options() SignerOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// SigningKey represents an algorithm/key used to sign a message.
|
||||||
|
type SigningKey struct {
|
||||||
|
Algorithm SignatureAlgorithm
|
||||||
|
Key interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignerOptions represents options that can be set when creating signers.
|
||||||
|
type SignerOptions struct {
|
||||||
|
NonceSource NonceSource
|
||||||
|
EmbedJWK bool
|
||||||
|
|
||||||
|
// Optional map of additional keys to be inserted into the protected header
|
||||||
|
// of a JWS object. Some specifications which make use of JWS like to insert
|
||||||
|
// additional values here. All values must be JSON-serializable.
|
||||||
|
ExtraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it
|
||||||
|
// if necessary. It returns itself and so can be used in a fluent style.
|
||||||
|
func (so *SignerOptions) WithHeader(k HeaderKey, v interface{}) *SignerOptions {
|
||||||
|
if so.ExtraHeaders == nil {
|
||||||
|
so.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||||
|
}
|
||||||
|
so.ExtraHeaders[k] = v
|
||||||
|
return so
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContentType adds a content type ("cty") header and returns the updated
|
||||||
|
// SignerOptions.
|
||||||
|
func (so *SignerOptions) WithContentType(contentType ContentType) *SignerOptions {
|
||||||
|
return so.WithHeader(HeaderContentType, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithType adds a type ("typ") header and returns the updated SignerOptions.
|
||||||
|
func (so *SignerOptions) WithType(typ ContentType) *SignerOptions {
|
||||||
|
return so.WithHeader(HeaderType, typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCritical adds the given names to the critical ("crit") header and returns
|
||||||
|
// the updated SignerOptions.
|
||||||
|
func (so *SignerOptions) WithCritical(names ...string) *SignerOptions {
|
||||||
|
if so.ExtraHeaders[headerCritical] == nil {
|
||||||
|
so.WithHeader(headerCritical, make([]string, 0, len(names)))
|
||||||
|
}
|
||||||
|
crit := so.ExtraHeaders[headerCritical].([]string)
|
||||||
|
so.ExtraHeaders[headerCritical] = append(crit, names...)
|
||||||
|
return so
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBase64 adds a base64url-encode payload ("b64") header and returns the updated
|
||||||
|
// SignerOptions. When the "b64" value is "false", the payload is not base64 encoded.
|
||||||
|
func (so *SignerOptions) WithBase64(b64 bool) *SignerOptions {
|
||||||
|
if !b64 {
|
||||||
|
so.WithHeader(headerB64, b64)
|
||||||
|
so.WithCritical(headerB64)
|
||||||
|
}
|
||||||
|
return so
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadSigner interface {
|
||||||
|
signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadVerifier interface {
|
||||||
|
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type genericSigner struct {
|
||||||
|
recipients []recipientSigInfo
|
||||||
|
nonceSource NonceSource
|
||||||
|
embedJWK bool
|
||||||
|
extraHeaders map[HeaderKey]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type recipientSigInfo struct {
|
||||||
|
sigAlg SignatureAlgorithm
|
||||||
|
publicKey func() *JSONWebKey
|
||||||
|
signer payloadSigner
|
||||||
|
}
|
||||||
|
|
||||||
|
func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey {
|
||||||
|
return func() *JSONWebKey {
|
||||||
|
return jwk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSigner creates an appropriate signer based on the key type
|
||||||
|
func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) {
|
||||||
|
return NewMultiSigner([]SigningKey{sig}, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMultiSigner creates a signer for multiple recipients
|
||||||
|
func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) {
|
||||||
|
signer := &genericSigner{recipients: []recipientSigInfo{}}
|
||||||
|
|
||||||
|
if opts != nil {
|
||||||
|
signer.nonceSource = opts.NonceSource
|
||||||
|
signer.embedJWK = opts.EmbedJWK
|
||||||
|
signer.extraHeaders = opts.ExtraHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sig := range sigs {
|
||||||
|
err := signer.addRecipient(sig.Algorithm, sig.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newVerifier creates a verifier based on the key type
|
||||||
|
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
||||||
|
switch verificationKey := verificationKey.(type) {
|
||||||
|
case ed25519.PublicKey:
|
||||||
|
return &edEncrypterVerifier{
|
||||||
|
publicKey: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
return &rsaEncrypterVerifier{
|
||||||
|
publicKey: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case *ecdsa.PublicKey:
|
||||||
|
return &ecEncrypterVerifier{
|
||||||
|
publicKey: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case []byte:
|
||||||
|
return &symmetricMac{
|
||||||
|
key: verificationKey,
|
||||||
|
}, nil
|
||||||
|
case JSONWebKey:
|
||||||
|
return newVerifier(verificationKey.Key)
|
||||||
|
case *JSONWebKey:
|
||||||
|
return newVerifier(verificationKey.Key)
|
||||||
|
}
|
||||||
|
if ov, ok := verificationKey.(OpaqueVerifier); ok {
|
||||||
|
return &opaqueVerifier{verifier: ov}, nil
|
||||||
|
}
|
||||||
|
return nil, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
||||||
|
recipient, err := makeJWSRecipient(alg, signingKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.recipients = append(ctx.recipients, recipient)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
|
||||||
|
switch signingKey := signingKey.(type) {
|
||||||
|
case ed25519.PrivateKey:
|
||||||
|
return newEd25519Signer(alg, signingKey)
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return newRSASigner(alg, signingKey)
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return newECDSASigner(alg, signingKey)
|
||||||
|
case []byte:
|
||||||
|
return newSymmetricSigner(alg, signingKey)
|
||||||
|
case JSONWebKey:
|
||||||
|
return newJWKSigner(alg, signingKey)
|
||||||
|
case *JSONWebKey:
|
||||||
|
return newJWKSigner(alg, *signingKey)
|
||||||
|
}
|
||||||
|
if signer, ok := signingKey.(OpaqueSigner); ok {
|
||||||
|
return newOpaqueSigner(alg, signer)
|
||||||
|
}
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
|
||||||
|
recipient, err := makeJWSRecipient(alg, signingKey.Key)
|
||||||
|
if err != nil {
|
||||||
|
return recipientSigInfo{}, err
|
||||||
|
}
|
||||||
|
if recipient.publicKey != nil && recipient.publicKey() != nil {
|
||||||
|
// recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo
|
||||||
|
// was created for the inner key (such as a RSA or ECDSA public key). It contains
|
||||||
|
// the pub key for embedding, but doesn't have extra params like key id.
|
||||||
|
publicKey := signingKey
|
||||||
|
publicKey.Key = recipient.publicKey().Key
|
||||||
|
recipient.publicKey = staticPublicKey(&publicKey)
|
||||||
|
|
||||||
|
// This should be impossible, but let's check anyway.
|
||||||
|
if !recipient.publicKey().IsPublic() {
|
||||||
|
return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return recipient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
||||||
|
obj := &JSONWebSignature{}
|
||||||
|
obj.payload = payload
|
||||||
|
obj.Signatures = make([]Signature, len(ctx.recipients))
|
||||||
|
|
||||||
|
for i, recipient := range ctx.recipients {
|
||||||
|
protected := map[HeaderKey]interface{}{
|
||||||
|
headerAlgorithm: string(recipient.sigAlg),
|
||||||
|
}
|
||||||
|
|
||||||
|
if recipient.publicKey != nil && recipient.publicKey() != nil {
|
||||||
|
// We want to embed the JWK or set the kid header, but not both. Having a protected
|
||||||
|
// header that contains an embedded JWK while also simultaneously containing the kid
|
||||||
|
// header is confusing, and at least in ACME the two are considered to be mutually
|
||||||
|
// exclusive. The fact that both can exist at the same time is a somewhat unfortunate
|
||||||
|
// result of the JOSE spec. We've decided that this library will only include one or
|
||||||
|
// the other to avoid this confusion.
|
||||||
|
//
|
||||||
|
// See https://github.com/square/go-jose/issues/157 for more context.
|
||||||
|
if ctx.embedJWK {
|
||||||
|
protected[headerJWK] = recipient.publicKey()
|
||||||
|
} else {
|
||||||
|
keyID := recipient.publicKey().KeyID
|
||||||
|
if keyID != "" {
|
||||||
|
protected[headerKeyID] = keyID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.nonceSource != nil {
|
||||||
|
nonce, err := ctx.nonceSource.Nonce()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: Error generating nonce: %v", err)
|
||||||
|
}
|
||||||
|
protected[headerNonce] = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range ctx.extraHeaders {
|
||||||
|
protected[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedProtected := mustSerializeJSON(protected)
|
||||||
|
needsBase64 := true
|
||||||
|
|
||||||
|
if b64, ok := protected[headerB64]; ok {
|
||||||
|
if needsBase64, ok = b64.(bool); !ok {
|
||||||
|
return nil, errors.New("square/go-jose: Invalid b64 header parameter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var input bytes.Buffer
|
||||||
|
|
||||||
|
input.WriteString(base64.RawURLEncoding.EncodeToString(serializedProtected))
|
||||||
|
input.WriteByte('.')
|
||||||
|
|
||||||
|
if needsBase64 {
|
||||||
|
input.WriteString(base64.RawURLEncoding.EncodeToString(payload))
|
||||||
|
} else {
|
||||||
|
input.Write(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureInfo, err := recipient.signer.signPayload(input.Bytes(), recipient.sigAlg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureInfo.protected = &rawHeader{}
|
||||||
|
for k, v := range protected {
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: Error marshalling item %#v: %v", k, err)
|
||||||
|
}
|
||||||
|
(*signatureInfo.protected)[k] = makeRawMessage(b)
|
||||||
|
}
|
||||||
|
obj.Signatures[i] = signatureInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *genericSigner) Options() SignerOptions {
|
||||||
|
return SignerOptions{
|
||||||
|
NonceSource: ctx.nonceSource,
|
||||||
|
EmbedJWK: ctx.embedJWK,
|
||||||
|
ExtraHeaders: ctx.extraHeaders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify validates the signature on the object and returns the payload.
|
||||||
|
// This function does not support multi-signature, if you desire multi-sig
|
||||||
|
// verification use VerifyMulti instead.
|
||||||
|
//
|
||||||
|
// Be careful when verifying signatures based on embedded JWKs inside the
|
||||||
|
// payload header. You cannot assume that the key received in a payload is
|
||||||
|
// trusted.
|
||||||
|
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
|
||||||
|
err := obj.DetachedVerify(obj.payload, verificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return obj.payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsafePayloadWithoutVerification returns the payload without
|
||||||
|
// verifying it. The content returned from this function cannot be
|
||||||
|
// trusted.
|
||||||
|
func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
|
||||||
|
return obj.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachedVerify validates a detached signature on the given payload. In
|
||||||
|
// most cases, you will probably want to use Verify instead. DetachedVerify
|
||||||
|
// is only useful if you have a payload and signature that are separated from
|
||||||
|
// each other.
|
||||||
|
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
|
||||||
|
verifier, err := newVerifier(verificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.Signatures) > 1 {
|
||||||
|
return errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := obj.Signatures[0]
|
||||||
|
headers := signature.mergedHeaders()
|
||||||
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range critical {
|
||||||
|
if !supportedCritical[name] {
|
||||||
|
return ErrCryptoFailure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := obj.computeAuthData(payload, &signature)
|
||||||
|
if err != nil {
|
||||||
|
return ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
alg := headers.getSignatureAlgorithm()
|
||||||
|
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyMulti validates (one of the multiple) signatures on the object and
|
||||||
|
// returns the index of the signature that was verified, along with the signature
|
||||||
|
// object and the payload. We return the signature and index to guarantee that
|
||||||
|
// callers are getting the verified value.
|
||||||
|
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
|
||||||
|
idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Signature{}, nil, err
|
||||||
|
}
|
||||||
|
return idx, sig, obj.payload, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetachedVerifyMulti validates a detached signature on the given payload with
|
||||||
|
// a signature/object that has potentially multiple signers. This returns the index
|
||||||
|
// of the signature that was verified, along with the signature object. We return
|
||||||
|
// the signature and index to guarantee that callers are getting the verified value.
|
||||||
|
//
|
||||||
|
// In most cases, you will probably want to use Verify or VerifyMulti instead.
|
||||||
|
// DetachedVerifyMulti is only useful if you have a payload and signature that are
|
||||||
|
// separated from each other, and the signature can have multiple signers at the
|
||||||
|
// same time.
|
||||||
|
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
|
||||||
|
verifier, err := newVerifier(verificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Signature{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for i, signature := range obj.Signatures {
|
||||||
|
headers := signature.mergedHeaders()
|
||||||
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range critical {
|
||||||
|
if !supportedCritical[name] {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := obj.computeAuthData(payload, &signature)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
alg := headers.getSignatureAlgorithm()
|
||||||
|
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||||
|
if err == nil {
|
||||||
|
return i, signature, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, Signature{}, ErrCryptoFailure
|
||||||
|
}
|
482
vendor/gopkg.in/square/go-jose.v2/symmetric.go
generated
vendored
Normal file
482
vendor/gopkg.in/square/go-jose.v2/symmetric.go
generated
vendored
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2014 Square Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/subtle"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"gopkg.in/square/go-jose.v2/cipher"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Random reader (stubbed out in tests)
|
||||||
|
var RandReader = rand.Reader
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RFC7518 recommends a minimum of 1,000 iterations:
|
||||||
|
// https://tools.ietf.org/html/rfc7518#section-4.8.1.2
|
||||||
|
// NIST recommends a minimum of 10,000:
|
||||||
|
// https://pages.nist.gov/800-63-3/sp800-63b.html
|
||||||
|
// 1Password uses 100,000:
|
||||||
|
// https://support.1password.com/pbkdf2/
|
||||||
|
defaultP2C = 100000
|
||||||
|
// Default salt size: 128 bits
|
||||||
|
defaultP2SSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dummy key cipher for shared symmetric key mode
|
||||||
|
type symmetricKeyCipher struct {
|
||||||
|
key []byte // Pre-shared content-encryption key
|
||||||
|
p2c int // PBES2 Count
|
||||||
|
p2s []byte // PBES2 Salt Input
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signer/verifier for MAC modes
|
||||||
|
type symmetricMac struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input/output from an AEAD operation
|
||||||
|
type aeadParts struct {
|
||||||
|
iv, ciphertext, tag []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// A content cipher based on an AEAD construction
|
||||||
|
type aeadContentCipher struct {
|
||||||
|
keyBytes int
|
||||||
|
authtagBytes int
|
||||||
|
getAead func(key []byte) (cipher.AEAD, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random key generator
|
||||||
|
type randomKeyGenerator struct {
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static key generator
|
||||||
|
type staticKeyGenerator struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new content cipher based on AES-GCM
|
||||||
|
func newAESGCM(keySize int) contentCipher {
|
||||||
|
return &aeadContentCipher{
|
||||||
|
keyBytes: keySize,
|
||||||
|
authtagBytes: 16,
|
||||||
|
getAead: func(key []byte) (cipher.AEAD, error) {
|
||||||
|
aes, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cipher.NewGCM(aes)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new content cipher based on AES-CBC+HMAC
|
||||||
|
func newAESCBC(keySize int) contentCipher {
|
||||||
|
return &aeadContentCipher{
|
||||||
|
keyBytes: keySize * 2,
|
||||||
|
authtagBytes: keySize,
|
||||||
|
getAead: func(key []byte) (cipher.AEAD, error) {
|
||||||
|
return josecipher.NewCBCHMAC(key, aes.NewCipher)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an AEAD cipher object for the given content encryption algorithm
|
||||||
|
func getContentCipher(alg ContentEncryption) contentCipher {
|
||||||
|
switch alg {
|
||||||
|
case A128GCM:
|
||||||
|
return newAESGCM(16)
|
||||||
|
case A192GCM:
|
||||||
|
return newAESGCM(24)
|
||||||
|
case A256GCM:
|
||||||
|
return newAESGCM(32)
|
||||||
|
case A128CBC_HS256:
|
||||||
|
return newAESCBC(16)
|
||||||
|
case A192CBC_HS384:
|
||||||
|
return newAESCBC(24)
|
||||||
|
case A256CBC_HS512:
|
||||||
|
return newAESCBC(32)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPbkdf2Params returns the key length and hash function used in
|
||||||
|
// pbkdf2.Key.
|
||||||
|
func getPbkdf2Params(alg KeyAlgorithm) (int, func() hash.Hash) {
|
||||||
|
switch alg {
|
||||||
|
case PBES2_HS256_A128KW:
|
||||||
|
return 16, sha256.New
|
||||||
|
case PBES2_HS384_A192KW:
|
||||||
|
return 24, sha512.New384
|
||||||
|
case PBES2_HS512_A256KW:
|
||||||
|
return 32, sha512.New
|
||||||
|
default:
|
||||||
|
panic("invalid algorithm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRandomSalt generates a new salt of the given size.
|
||||||
|
func getRandomSalt(size int) ([]byte, error) {
|
||||||
|
salt := make([]byte, size)
|
||||||
|
_, err := io.ReadFull(RandReader, salt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return salt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSymmetricRecipient creates a JWE encrypter based on AES-GCM key wrap.
|
||||||
|
func newSymmetricRecipient(keyAlg KeyAlgorithm, key []byte) (recipientKeyInfo, error) {
|
||||||
|
switch keyAlg {
|
||||||
|
case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW:
|
||||||
|
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||||
|
default:
|
||||||
|
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientKeyInfo{
|
||||||
|
keyAlg: keyAlg,
|
||||||
|
keyEncrypter: &symmetricKeyCipher{
|
||||||
|
key: key,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSymmetricSigner creates a recipientSigInfo based on the given key.
|
||||||
|
func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo, error) {
|
||||||
|
// Verify that key management algorithm is supported by this encrypter
|
||||||
|
switch sigAlg {
|
||||||
|
case HS256, HS384, HS512:
|
||||||
|
default:
|
||||||
|
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientSigInfo{
|
||||||
|
sigAlg: sigAlg,
|
||||||
|
signer: &symmetricMac{
|
||||||
|
key: key,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a random key for the given content cipher
|
||||||
|
func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||||
|
key := make([]byte, ctx.size)
|
||||||
|
_, err := io.ReadFull(RandReader, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rawHeader{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return key, rawHeader{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key size for random generator
|
||||||
|
func (ctx randomKeyGenerator) keySize() int {
|
||||||
|
return ctx.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a static key (for direct mode)
|
||||||
|
func (ctx staticKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||||
|
cek := make([]byte, len(ctx.key))
|
||||||
|
copy(cek, ctx.key)
|
||||||
|
return cek, rawHeader{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key size for static generator
|
||||||
|
func (ctx staticKeyGenerator) keySize() int {
|
||||||
|
return len(ctx.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get key size for this cipher
|
||||||
|
func (ctx aeadContentCipher) keySize() int {
|
||||||
|
return ctx.keyBytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt some data
|
||||||
|
func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) {
|
||||||
|
// Get a new AEAD instance
|
||||||
|
aead, err := ctx.getAead(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a new nonce
|
||||||
|
iv := make([]byte, aead.NonceSize())
|
||||||
|
_, err = io.ReadFull(RandReader, iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertextAndTag := aead.Seal(nil, iv, pt, aad)
|
||||||
|
offset := len(ciphertextAndTag) - ctx.authtagBytes
|
||||||
|
|
||||||
|
return &aeadParts{
|
||||||
|
iv: iv,
|
||||||
|
ciphertext: ciphertextAndTag[:offset],
|
||||||
|
tag: ciphertextAndTag[offset:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt some data
|
||||||
|
func (ctx aeadContentCipher) decrypt(key, aad []byte, parts *aeadParts) ([]byte, error) {
|
||||||
|
aead, err := ctx.getAead(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts.iv) != aead.NonceSize() || len(parts.tag) < ctx.authtagBytes {
|
||||||
|
return nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
return aead.Open(nil, parts.iv, append(parts.ciphertext, parts.tag...), aad)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the content encryption key.
|
||||||
|
func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
|
||||||
|
switch alg {
|
||||||
|
case DIRECT:
|
||||||
|
return recipientInfo{
|
||||||
|
header: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
case A128GCMKW, A192GCMKW, A256GCMKW:
|
||||||
|
aead := newAESGCM(len(ctx.key))
|
||||||
|
|
||||||
|
parts, err := aead.encrypt(ctx.key, []byte{}, cek)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
header := &rawHeader{}
|
||||||
|
header.set(headerIV, newBuffer(parts.iv))
|
||||||
|
header.set(headerTag, newBuffer(parts.tag))
|
||||||
|
|
||||||
|
return recipientInfo{
|
||||||
|
header: header,
|
||||||
|
encryptedKey: parts.ciphertext,
|
||||||
|
}, nil
|
||||||
|
case A128KW, A192KW, A256KW:
|
||||||
|
block, err := aes.NewCipher(ctx.key)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jek, err := josecipher.KeyWrap(block, cek)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientInfo{
|
||||||
|
encryptedKey: jek,
|
||||||
|
header: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||||
|
if len(ctx.p2s) == 0 {
|
||||||
|
salt, err := getRandomSalt(defaultP2SSize)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
ctx.p2s = salt
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.p2c <= 0 {
|
||||||
|
ctx.p2c = defaultP2C
|
||||||
|
}
|
||||||
|
|
||||||
|
// salt is UTF8(Alg) || 0x00 || Salt Input
|
||||||
|
salt := bytes.Join([][]byte{[]byte(alg), ctx.p2s}, []byte{0x00})
|
||||||
|
|
||||||
|
// derive key
|
||||||
|
keyLen, h := getPbkdf2Params(alg)
|
||||||
|
key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h)
|
||||||
|
|
||||||
|
// use AES cipher with derived key
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jek, err := josecipher.KeyWrap(block, cek)
|
||||||
|
if err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
header := &rawHeader{}
|
||||||
|
header.set(headerP2C, ctx.p2c)
|
||||||
|
header.set(headerP2S, newBuffer(ctx.p2s))
|
||||||
|
|
||||||
|
return recipientInfo{
|
||||||
|
encryptedKey: jek,
|
||||||
|
header: header,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipientInfo{}, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt the content encryption key.
|
||||||
|
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
|
||||||
|
switch headers.getAlgorithm() {
|
||||||
|
case DIRECT:
|
||||||
|
cek := make([]byte, len(ctx.key))
|
||||||
|
copy(cek, ctx.key)
|
||||||
|
return cek, nil
|
||||||
|
case A128GCMKW, A192GCMKW, A256GCMKW:
|
||||||
|
aead := newAESGCM(len(ctx.key))
|
||||||
|
|
||||||
|
iv, err := headers.getIV()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid IV: %v", err)
|
||||||
|
}
|
||||||
|
tag, err := headers.getTag()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid tag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := &aeadParts{
|
||||||
|
iv: iv.bytes(),
|
||||||
|
ciphertext: recipient.encryptedKey,
|
||||||
|
tag: tag.bytes(),
|
||||||
|
}
|
||||||
|
|
||||||
|
cek, err := aead.decrypt(ctx.key, []byte{}, parts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cek, nil
|
||||||
|
case A128KW, A192KW, A256KW:
|
||||||
|
block, err := aes.NewCipher(ctx.key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cek, nil
|
||||||
|
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||||
|
p2s, err := headers.getP2S()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid P2S: %v", err)
|
||||||
|
}
|
||||||
|
if p2s == nil || len(p2s.data) == 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid P2S: must be present")
|
||||||
|
}
|
||||||
|
|
||||||
|
p2c, err := headers.getP2C()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid P2C: %v", err)
|
||||||
|
}
|
||||||
|
if p2c <= 0 {
|
||||||
|
return nil, fmt.Errorf("square/go-jose: invalid P2C: must be a positive integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// salt is UTF8(Alg) || 0x00 || Salt Input
|
||||||
|
alg := headers.getAlgorithm()
|
||||||
|
salt := bytes.Join([][]byte{[]byte(alg), p2s.bytes()}, []byte{0x00})
|
||||||
|
|
||||||
|
// derive key
|
||||||
|
keyLen, h := getPbkdf2Params(alg)
|
||||||
|
key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h)
|
||||||
|
|
||||||
|
// use AES cipher with derived key
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cek, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the given payload
|
||||||
|
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||||
|
mac, err := ctx.hmac(payload, alg)
|
||||||
|
if err != nil {
|
||||||
|
return Signature{}, errors.New("square/go-jose: failed to compute hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signature{
|
||||||
|
Signature: mac,
|
||||||
|
protected: &rawHeader{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the given payload
|
||||||
|
func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
|
||||||
|
expected, err := ctx.hmac(payload, alg)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("square/go-jose: failed to compute hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mac) != len(expected) {
|
||||||
|
return errors.New("square/go-jose: invalid hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
match := subtle.ConstantTimeCompare(mac, expected)
|
||||||
|
if match != 1 {
|
||||||
|
return errors.New("square/go-jose: invalid hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the HMAC based on the given alg value
|
||||||
|
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
|
||||||
|
var hash func() hash.Hash
|
||||||
|
|
||||||
|
switch alg {
|
||||||
|
case HS256:
|
||||||
|
hash = sha256.New
|
||||||
|
case HS384:
|
||||||
|
hash = sha512.New384
|
||||||
|
case HS512:
|
||||||
|
hash = sha512.New
|
||||||
|
default:
|
||||||
|
return nil, ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
hmac := hmac.New(hash, ctx.key)
|
||||||
|
|
||||||
|
// According to documentation, Write() on hash never fails
|
||||||
|
_, _ = hmac.Write(payload)
|
||||||
|
return hmac.Sum(nil), nil
|
||||||
|
}
|
12
vendor/modules.txt
vendored
12
vendor/modules.txt
vendored
|
@ -2,6 +2,8 @@
|
||||||
github.com/PuerkitoBio/goquery
|
github.com/PuerkitoBio/goquery
|
||||||
# github.com/andybalholm/cascadia v1.0.0
|
# github.com/andybalholm/cascadia v1.0.0
|
||||||
github.com/andybalholm/cascadia
|
github.com/andybalholm/cascadia
|
||||||
|
# github.com/coreos/go-oidc v2.2.1+incompatible
|
||||||
|
github.com/coreos/go-oidc
|
||||||
# github.com/golang/protobuf v1.3.2
|
# github.com/golang/protobuf v1.3.2
|
||||||
github.com/golang/protobuf/proto
|
github.com/golang/protobuf/proto
|
||||||
# github.com/gorilla/mux v1.7.3
|
# github.com/gorilla/mux v1.7.3
|
||||||
|
@ -11,11 +13,17 @@ github.com/lib/pq
|
||||||
github.com/lib/pq/hstore
|
github.com/lib/pq/hstore
|
||||||
github.com/lib/pq/oid
|
github.com/lib/pq/oid
|
||||||
github.com/lib/pq/scram
|
github.com/lib/pq/scram
|
||||||
|
# github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35
|
||||||
|
github.com/pquerna/cachecontrol
|
||||||
|
github.com/pquerna/cachecontrol/cacheobject
|
||||||
# golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708
|
# golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708
|
||||||
golang.org/x/crypto/acme
|
golang.org/x/crypto/acme
|
||||||
golang.org/x/crypto/acme/autocert
|
golang.org/x/crypto/acme/autocert
|
||||||
golang.org/x/crypto/bcrypt
|
golang.org/x/crypto/bcrypt
|
||||||
golang.org/x/crypto/blowfish
|
golang.org/x/crypto/blowfish
|
||||||
|
golang.org/x/crypto/ed25519
|
||||||
|
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||||
|
golang.org/x/crypto/pbkdf2
|
||||||
golang.org/x/crypto/ssh/terminal
|
golang.org/x/crypto/ssh/terminal
|
||||||
# golang.org/x/net v0.0.0-20191112182307-2180aed22343
|
# golang.org/x/net v0.0.0-20191112182307-2180aed22343
|
||||||
golang.org/x/net/context
|
golang.org/x/net/context
|
||||||
|
@ -59,3 +67,7 @@ google.golang.org/appengine/internal/log
|
||||||
google.golang.org/appengine/internal/remote_api
|
google.golang.org/appengine/internal/remote_api
|
||||||
google.golang.org/appengine/internal/urlfetch
|
google.golang.org/appengine/internal/urlfetch
|
||||||
google.golang.org/appengine/urlfetch
|
google.golang.org/appengine/urlfetch
|
||||||
|
# gopkg.in/square/go-jose.v2 v2.4.1
|
||||||
|
gopkg.in/square/go-jose.v2
|
||||||
|
gopkg.in/square/go-jose.v2/cipher
|
||||||
|
gopkg.in/square/go-jose.v2/json
|
||||||
|
|
Loading…
Reference in a new issue