forked from TrueCloudLab/distribution
Replace docker/libtrust with go-jose/go-jose (#4096)
This commit is contained in:
commit
dfd191e7d2
59 changed files with 10180 additions and 3279 deletions
|
@ -68,26 +68,6 @@ Token has 3 main parts:
|
||||||
signing algorithm used to produce the signature. It also must have a "kid"
|
signing algorithm used to produce the signature. It also must have a "kid"
|
||||||
field, representing the ID of the key which was used to sign the token.
|
field, representing the ID of the key which was used to sign the token.
|
||||||
|
|
||||||
The "kid" field has to be in a libtrust fingerprint compatible format.
|
|
||||||
Such a format can be generated by following steps:
|
|
||||||
|
|
||||||
1. Take the DER encoded public key which the JWT token was signed against.
|
|
||||||
|
|
||||||
2. Create a SHA256 hash out of it and truncate to 240bits.
|
|
||||||
|
|
||||||
3. Split the result into 12 base32 encoded groups with `:` as delimiter.
|
|
||||||
|
|
||||||
Here is an example JOSE Header for a JSON Web Token (formatted with
|
|
||||||
whitespace for readability):
|
|
||||||
|
|
||||||
```
|
|
||||||
{
|
|
||||||
"typ": "JWT",
|
|
||||||
"alg": "ES256",
|
|
||||||
"kid": "PYYO:TEWU:V7JH:26JV:AQTZ:LJC3:SXVJ:XGHA:34F2:2LAQ:ZRMK:Z7Q6"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
It specifies that this object is going to be a JSON Web token signed using
|
It specifies that this object is going to be a JSON Web token signed using
|
||||||
the key with the given ID using the Elliptic Curve signature algorithm
|
the key with the given ID using the Elliptic Curve signature algorithm
|
||||||
using a SHA256 hash.
|
using a SHA256 hash.
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -14,7 +14,7 @@ require (
|
||||||
github.com/distribution/reference v0.5.0
|
github.com/distribution/reference v0.5.0
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
|
||||||
github.com/docker/go-metrics v0.0.1
|
github.com/docker/go-metrics v0.0.1
|
||||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1
|
github.com/go-jose/go-jose/v3 v3.0.0
|
||||||
github.com/gorilla/handlers v1.5.1
|
github.com/gorilla/handlers v1.5.1
|
||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/hashicorp/golang-lru/arc/v2 v2.0.5
|
github.com/hashicorp/golang-lru/arc/v2 v2.0.5
|
||||||
|
|
6
go.sum
6
go.sum
|
@ -102,8 +102,6 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
|
||||||
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
|
||||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
|
|
||||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
@ -113,6 +111,8 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||||
|
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
|
@ -308,6 +308,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
@ -335,6 +336,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
|
||||||
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
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-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
|
|
||||||
dcontext "github.com/distribution/distribution/v3/context"
|
dcontext "github.com/distribution/distribution/v3/context"
|
||||||
"github.com/distribution/distribution/v3/registry/auth"
|
"github.com/distribution/distribution/v3/registry/auth"
|
||||||
"github.com/docker/libtrust"
|
"github.com/go-jose/go-jose/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// accessSet maps a typed, named resource to
|
// accessSet maps a typed, named resource to
|
||||||
|
@ -132,7 +133,7 @@ type accessController struct {
|
||||||
issuer string
|
issuer string
|
||||||
service string
|
service string
|
||||||
rootCerts *x509.CertPool
|
rootCerts *x509.CertPool
|
||||||
trustedKeys map[string]libtrust.PublicKey
|
trustedKeys map[string]crypto.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// tokenAccessOptions is a convenience type for handling
|
// tokenAccessOptions is a convenience type for handling
|
||||||
|
@ -143,6 +144,7 @@ type tokenAccessOptions struct {
|
||||||
issuer string
|
issuer string
|
||||||
service string
|
service string
|
||||||
rootCertBundle string
|
rootCertBundle string
|
||||||
|
jwks string
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkOptions gathers the necessary options
|
// checkOptions gathers the necessary options
|
||||||
|
@ -150,17 +152,26 @@ type tokenAccessOptions struct {
|
||||||
func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
|
func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
|
||||||
var opts tokenAccessOptions
|
var opts tokenAccessOptions
|
||||||
|
|
||||||
keys := []string{"realm", "issuer", "service", "rootcertbundle"}
|
keys := []string{"realm", "issuer", "service", "rootcertbundle", "jwks"}
|
||||||
vals := make([]string, 0, len(keys))
|
vals := make([]string, 0, len(keys))
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
val, ok := options[key].(string)
|
val, ok := options[key].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
// NOTE(milosgajdos): this func makes me intensely sad
|
||||||
|
// just like all the other weakly typed config options.
|
||||||
|
// Either of these config options may be missing, but
|
||||||
|
// at least one must be present: we handle those cases
|
||||||
|
// in newAccessController func which consumes this one.
|
||||||
|
if key == "rootcertbundle" || key == "jwks" {
|
||||||
|
vals = append(vals, "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
return opts, fmt.Errorf("token auth requires a valid option string: %q", key)
|
return opts, fmt.Errorf("token auth requires a valid option string: %q", key)
|
||||||
}
|
}
|
||||||
vals = append(vals, val)
|
vals = append(vals, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.realm, opts.issuer, opts.service, opts.rootCertBundle = vals[0], vals[1], vals[2], vals[3]
|
opts.realm, opts.issuer, opts.service, opts.rootCertBundle, opts.jwks = vals[0], vals[1], vals[2], vals[3], vals[4]
|
||||||
|
|
||||||
autoRedirectVal, ok := options["autoredirect"]
|
autoRedirectVal, ok := options["autoredirect"]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -174,22 +185,16 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newAccessController creates an accessController using the given options.
|
func getRootCerts(path string) ([]*x509.Certificate, error) {
|
||||||
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
|
fp, err := os.Open(path)
|
||||||
config, err := checkOptions(options)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to open token auth root certificate bundle file %q: %s", path, err)
|
||||||
}
|
|
||||||
|
|
||||||
fp, err := os.Open(config.rootCertBundle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to open token auth root certificate bundle file %q: %s", config.rootCertBundle, err)
|
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer fp.Close()
|
||||||
|
|
||||||
rawCertBundle, err := io.ReadAll(fp)
|
rawCertBundle, err := io.ReadAll(fp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to read token auth root certificate bundle file %q: %s", config.rootCertBundle, err)
|
return nil, fmt.Errorf("unable to read token auth root certificate bundle file %q: %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rootCerts []*x509.Certificate
|
var rootCerts []*x509.Certificate
|
||||||
|
@ -207,19 +212,72 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
|
||||||
pemBlock, rawCertBundle = pem.Decode(rawCertBundle)
|
pemBlock, rawCertBundle = pem.Decode(rawCertBundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rootCerts) == 0 {
|
return rootCerts, nil
|
||||||
return nil, errors.New("token auth requires at least one token signing root certificate")
|
}
|
||||||
|
|
||||||
|
func getJwks(path string) (*jose.JSONWebKeySet, error) {
|
||||||
|
// TODO(milosgajdos): we should consider providing a JWKS
|
||||||
|
// URL from which the JWKS could be fetched
|
||||||
|
jp, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to open jwks file %q: %s", path, err)
|
||||||
|
}
|
||||||
|
defer jp.Close()
|
||||||
|
|
||||||
|
rawJWKS, err := io.ReadAll(jp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read token jwks file %q: %s", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwks jose.JSONWebKeySet
|
||||||
|
if err := json.Unmarshal(rawJWKS, &jwks); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse jwks: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &jwks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAccessController creates an accessController using the given options.
|
||||||
|
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
|
||||||
|
config, err := checkOptions(options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rootCerts []*x509.Certificate
|
||||||
|
jwks *jose.JSONWebKeySet
|
||||||
|
)
|
||||||
|
|
||||||
|
if config.rootCertBundle != "" {
|
||||||
|
rootCerts, err = getRootCerts(config.rootCertBundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.jwks != "" {
|
||||||
|
jwks, err = getJwks(config.jwks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len(rootCerts) == 0 && jwks == nil) || // no certs bundle and no jwks
|
||||||
|
(len(rootCerts) == 0 && jwks != nil && len(jwks.Keys) == 0) { // no certs bundle and empty jwks
|
||||||
|
return nil, errors.New("token auth requires at least one token signing key")
|
||||||
}
|
}
|
||||||
|
|
||||||
rootPool := x509.NewCertPool()
|
rootPool := x509.NewCertPool()
|
||||||
trustedKeys := make(map[string]libtrust.PublicKey, len(rootCerts))
|
|
||||||
for _, rootCert := range rootCerts {
|
for _, rootCert := range rootCerts {
|
||||||
rootPool.AddCert(rootCert)
|
rootPool.AddCert(rootCert)
|
||||||
pubKey, err := libtrust.FromCryptoPublicKey(crypto.PublicKey(rootCert.PublicKey))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to get public key from token auth root certificate: %s", err)
|
|
||||||
}
|
}
|
||||||
trustedKeys[pubKey.KeyID()] = pubKey
|
|
||||||
|
trustedKeys := make(map[string]crypto.PublicKey)
|
||||||
|
if jwks != nil {
|
||||||
|
for _, key := range jwks.Keys {
|
||||||
|
trustedKeys[key.KeyID] = key.Public()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &accessController{
|
return &accessController{
|
||||||
|
@ -266,12 +324,13 @@ func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.
|
||||||
TrustedKeys: ac.trustedKeys,
|
TrustedKeys: ac.trustedKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = token.Verify(verifyOpts); err != nil {
|
claims, err := token.Verify(verifyOpts)
|
||||||
|
if err != nil {
|
||||||
challenge.err = err
|
challenge.err = err
|
||||||
return nil, challenge
|
return nil, challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
accessSet := token.accessSet()
|
accessSet := claims.accessSet()
|
||||||
for _, access := range accessItems {
|
for _, access := range accessItems {
|
||||||
if !accessSet.contains(access) {
|
if !accessSet.contains(access) {
|
||||||
challenge.err = ErrInsufficientScope
|
challenge.err = ErrInsufficientScope
|
||||||
|
@ -279,9 +338,9 @@ func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = auth.WithResources(ctx, token.resources())
|
ctx = auth.WithResources(ctx, claims.resources())
|
||||||
|
|
||||||
return auth.WithUser(ctx, auth.UserInfo{Name: token.Claims.Subject}), nil
|
return auth.WithUser(ctx, auth.UserInfo{Name: claims.Subject}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// init handles registering the token auth backend.
|
// init handles registering the token auth backend.
|
||||||
|
|
|
@ -3,14 +3,12 @@ package token
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/libtrust"
|
"github.com/go-jose/go-jose/v3"
|
||||||
|
"github.com/go-jose/go-jose/v3/jwt"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/registry/auth"
|
"github.com/distribution/distribution/v3/registry/auth"
|
||||||
|
@ -54,21 +52,10 @@ type ClaimSet struct {
|
||||||
Access []*ResourceActions `json:"access"`
|
Access []*ResourceActions `json:"access"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header describes the header section of a JSON Web Token.
|
// Token is a JSON Web Token.
|
||||||
type Header struct {
|
|
||||||
Type string `json:"typ"`
|
|
||||||
SigningAlg string `json:"alg"`
|
|
||||||
KeyID string `json:"kid,omitempty"`
|
|
||||||
X5c []string `json:"x5c,omitempty"`
|
|
||||||
RawJWK *json.RawMessage `json:"jwk,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Token describes a JSON Web Token.
|
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Raw string
|
Raw string
|
||||||
Header *Header
|
JWT *jwt.JSONWebToken
|
||||||
Claims *ClaimSet
|
|
||||||
Signature []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyOptions is used to specify
|
// VerifyOptions is used to specify
|
||||||
|
@ -77,264 +64,178 @@ type VerifyOptions struct {
|
||||||
TrustedIssuers []string
|
TrustedIssuers []string
|
||||||
AcceptedAudiences []string
|
AcceptedAudiences []string
|
||||||
Roots *x509.CertPool
|
Roots *x509.CertPool
|
||||||
TrustedKeys map[string]libtrust.PublicKey
|
TrustedKeys map[string]crypto.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewToken parses the given raw token string
|
// NewToken parses the given raw token string
|
||||||
// and constructs an unverified JSON Web Token.
|
// and constructs an unverified JSON Web Token.
|
||||||
func NewToken(rawToken string) (*Token, error) {
|
func NewToken(rawToken string) (*Token, error) {
|
||||||
// We expect 3 parts, but limit the split to 4 to detect cases where
|
token, err := jwt.ParseSigned(rawToken)
|
||||||
// the token contains too many (or too few) separators.
|
|
||||||
parts := strings.SplitN(rawToken, TokenSeparator, 4)
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return nil, ErrMalformedToken
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
rawHeader, rawClaims = parts[0], parts[1]
|
|
||||||
headerJSON, claimsJSON []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("error while unmarshalling raw token: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if headerJSON, err = joseBase64UrlDecode(rawHeader); err != nil {
|
|
||||||
err = fmt.Errorf("unable to decode header: %s", err)
|
|
||||||
return nil, ErrMalformedToken
|
return nil, ErrMalformedToken
|
||||||
}
|
}
|
||||||
|
|
||||||
if claimsJSON, err = joseBase64UrlDecode(rawClaims); err != nil {
|
return &Token{
|
||||||
err = fmt.Errorf("unable to decode claims: %s", err)
|
Raw: rawToken,
|
||||||
return nil, ErrMalformedToken
|
JWT: token,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
token := new(Token)
|
|
||||||
token.Header = new(Header)
|
|
||||||
token.Claims = new(ClaimSet)
|
|
||||||
|
|
||||||
token.Raw = strings.Join(parts[:2], TokenSeparator)
|
|
||||||
if token.Signature, err = joseBase64UrlDecode(parts[2]); err != nil {
|
|
||||||
err = fmt.Errorf("unable to decode signature: %s", err)
|
|
||||||
return nil, ErrMalformedToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(headerJSON, token.Header); err != nil {
|
|
||||||
return nil, ErrMalformedToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(claimsJSON, token.Claims); err != nil {
|
|
||||||
return nil, ErrMalformedToken
|
|
||||||
}
|
|
||||||
|
|
||||||
return token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify attempts to verify this token using the given options.
|
// Verify attempts to verify this token using the given options.
|
||||||
// Returns a nil error if the token is valid.
|
// Returns a nil error if the token is valid.
|
||||||
func (t *Token) Verify(verifyOpts VerifyOptions) error {
|
func (t *Token) Verify(verifyOpts VerifyOptions) (*ClaimSet, error) {
|
||||||
|
// Verify that the signing key is trusted.
|
||||||
|
signingKey, err := t.VerifySigningKey(verifyOpts)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("failed to verify token: %v", err)
|
||||||
|
return nil, ErrInvalidToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(milosgajdos): Claims both verifies the signature
|
||||||
|
// and returns the claims within the payload
|
||||||
|
var claims ClaimSet
|
||||||
|
err = t.JWT.Claims(signingKey, &claims)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the Issuer claim is a trusted authority.
|
// Verify that the Issuer claim is a trusted authority.
|
||||||
if !contains(verifyOpts.TrustedIssuers, t.Claims.Issuer) {
|
if !contains(verifyOpts.TrustedIssuers, claims.Issuer) {
|
||||||
log.Infof("token from untrusted issuer: %q", t.Claims.Issuer)
|
log.Infof("token from untrusted issuer: %q", claims.Issuer)
|
||||||
return ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the Audience claim is allowed.
|
// Verify that the Audience claim is allowed.
|
||||||
if !containsAny(verifyOpts.AcceptedAudiences, t.Claims.Audience) {
|
if !containsAny(verifyOpts.AcceptedAudiences, claims.Audience) {
|
||||||
log.Infof("token intended for another audience: %v", t.Claims.Audience)
|
log.Infof("token intended for another audience: %v", claims.Audience)
|
||||||
return ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the token is currently usable and not expired.
|
// Verify that the token is currently usable and not expired.
|
||||||
currentTime := time.Now()
|
currentTime := time.Now()
|
||||||
|
|
||||||
ExpWithLeeway := time.Unix(t.Claims.Expiration, 0).Add(Leeway)
|
ExpWithLeeway := time.Unix(claims.Expiration, 0).Add(Leeway)
|
||||||
if currentTime.After(ExpWithLeeway) {
|
if currentTime.After(ExpWithLeeway) {
|
||||||
log.Infof("token not to be used after %s - currently %s", ExpWithLeeway, currentTime)
|
log.Infof("token not to be used after %s - currently %s", ExpWithLeeway, currentTime)
|
||||||
return ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
NotBeforeWithLeeway := time.Unix(t.Claims.NotBefore, 0).Add(-Leeway)
|
NotBeforeWithLeeway := time.Unix(claims.NotBefore, 0).Add(-Leeway)
|
||||||
if currentTime.Before(NotBeforeWithLeeway) {
|
if currentTime.Before(NotBeforeWithLeeway) {
|
||||||
log.Infof("token not to be used before %s - currently %s", NotBeforeWithLeeway, currentTime)
|
log.Infof("token not to be used before %s - currently %s", NotBeforeWithLeeway, currentTime)
|
||||||
return ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the token signature.
|
return &claims, nil
|
||||||
if len(t.Signature) == 0 {
|
|
||||||
log.Info("token has no signature")
|
|
||||||
return ErrInvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the signing key is trusted.
|
|
||||||
signingKey, err := t.VerifySigningKey(verifyOpts)
|
|
||||||
if err != nil {
|
|
||||||
log.Info(err)
|
|
||||||
return ErrInvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, verify the signature of the token using the key which signed it.
|
|
||||||
if err := signingKey.Verify(strings.NewReader(t.Raw), t.Header.SigningAlg, t.Signature); err != nil {
|
|
||||||
log.Infof("unable to verify token signature: %s", err)
|
|
||||||
return ErrInvalidToken
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifySigningKey attempts to get the key which was used to sign this token.
|
// VerifySigningKey attempts to verify and return the signing key which was used to sign the token.
|
||||||
// The token header should contain either of these 3 fields:
|
func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey crypto.PublicKey, err error) {
|
||||||
//
|
if len(t.JWT.Headers) == 0 {
|
||||||
// `x5c` - The x509 certificate chain for the signing key. Needs to be
|
return nil, ErrInvalidToken
|
||||||
// verified.
|
}
|
||||||
// `jwk` - The JSON Web Key representation of the signing key.
|
|
||||||
// May contain its own `x5c` field which needs to be verified.
|
// NOTE(milosgajdos): docker auth spec does not seem to
|
||||||
// `kid` - The unique identifier for the key. This library interprets it
|
// support tokens signed by multiple signatures so we are
|
||||||
// as a libtrust fingerprint. The key itself can be looked up in
|
// verifying the first one in the list only at the moment.
|
||||||
// the trustedKeys field of the given verify options.
|
header := t.JWT.Headers[0]
|
||||||
//
|
|
||||||
// Each of these methods are tried in that order of preference until the
|
|
||||||
// signing key is found or an error is returned.
|
|
||||||
func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey libtrust.PublicKey, err error) {
|
|
||||||
// First attempt to get an x509 certificate chain from the header.
|
|
||||||
var (
|
|
||||||
x5c = t.Header.X5c
|
|
||||||
rawJWK = t.Header.RawJWK
|
|
||||||
keyID = t.Header.KeyID
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(x5c) > 0:
|
case header.JSONWebKey != nil:
|
||||||
signingKey, err = parseAndVerifyCertChain(x5c, verifyOpts.Roots)
|
signingKey, err = verifyJWK(header, verifyOpts)
|
||||||
case rawJWK != nil:
|
case len(header.KeyID) > 0:
|
||||||
signingKey, err = parseAndVerifyRawJWK(rawJWK, verifyOpts)
|
signingKey = verifyOpts.TrustedKeys[header.KeyID]
|
||||||
case len(keyID) > 0:
|
|
||||||
signingKey = verifyOpts.TrustedKeys[keyID]
|
|
||||||
if signingKey == nil {
|
if signingKey == nil {
|
||||||
err = fmt.Errorf("token signed by untrusted key with ID: %q", keyID)
|
err = fmt.Errorf("token signed by untrusted key with ID: %q", header.KeyID)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = errors.New("unable to get token signing key")
|
signingKey, err = verifyCertChain(header, verifyOpts.Roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAndVerifyCertChain(x5c []string, roots *x509.CertPool) (leafKey libtrust.PublicKey, err error) {
|
func verifyCertChain(header jose.Header, roots *x509.CertPool) (signingKey crypto.PublicKey, err error) {
|
||||||
if len(x5c) == 0 {
|
|
||||||
return nil, errors.New("empty x509 certificate chain")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the first element is encoded correctly.
|
|
||||||
leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode leaf certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// And that it is a valid x509 certificate.
|
|
||||||
leafCert, err := x509.ParseCertificate(leafCertDer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to parse leaf certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rest of the certificate chain are intermediate certificates.
|
|
||||||
intermediates := x509.NewCertPool()
|
|
||||||
for i := 1; i < len(x5c); i++ {
|
|
||||||
intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode intermediate certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
intermediateCert, err := x509.ParseCertificate(intermediateCertDer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to parse intermediate certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
intermediates.AddCert(intermediateCert)
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyOpts := x509.VerifyOptions{
|
verifyOpts := x509.VerifyOptions{
|
||||||
Intermediates: intermediates,
|
|
||||||
Roots: roots,
|
Roots: roots,
|
||||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this call returns certificate chains which we ignore for now, but
|
// TODO: this call returns certificate chains which we ignore for now, but
|
||||||
// we should check them for revocations if we have the ability later.
|
// we should check them for revocations if we have the ability later.
|
||||||
if _, err = leafCert.Verify(verifyOpts); err != nil {
|
chains, err := header.Certificates(verifyOpts)
|
||||||
return nil, fmt.Errorf("unable to verify certificate chain: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the public key from the leaf certificate.
|
|
||||||
leafCryptoKey, ok := leafCert.PublicKey.(crypto.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to get leaf cert public key value")
|
|
||||||
}
|
|
||||||
|
|
||||||
leafKey, err = libtrust.FromCryptoPublicKey(leafCryptoKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to make libtrust public key from leaf certificate: %s", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
signingKey = getCertPubKey(chains)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAndVerifyRawJWK(rawJWK *json.RawMessage, verifyOpts VerifyOptions) (pubKey libtrust.PublicKey, err error) {
|
func verifyJWK(header jose.Header, verifyOpts VerifyOptions) (signingKey crypto.PublicKey, err error) {
|
||||||
pubKey, err = libtrust.UnmarshalPublicKeyJWK([]byte(*rawJWK))
|
jwk := header.JSONWebKey
|
||||||
if err != nil {
|
signingKey = jwk.Key
|
||||||
return nil, fmt.Errorf("unable to decode raw JWK value: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if the key includes a certificate chain.
|
// Check to see if the key includes a certificate chain.
|
||||||
x5cVal, ok := pubKey.GetExtendedField("x5c").([]interface{})
|
if len(jwk.Certificates) == 0 {
|
||||||
if !ok {
|
|
||||||
// The JWK should be one of the trusted root keys.
|
// The JWK should be one of the trusted root keys.
|
||||||
if _, trusted := verifyOpts.TrustedKeys[pubKey.KeyID()]; !trusted {
|
if _, trusted := verifyOpts.TrustedKeys[jwk.KeyID]; !trusted {
|
||||||
return nil, errors.New("untrusted JWK with no certificate chain")
|
return nil, errors.New("untrusted JWK with no certificate chain")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The JWK is one of the trusted keys.
|
// The JWK is one of the trusted keys.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure each item in the chain is of the correct type.
|
opts := x509.VerifyOptions{
|
||||||
x5c := make([]string, len(x5cVal))
|
Roots: verifyOpts.Roots,
|
||||||
for i, val := range x5cVal {
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
||||||
certString, ok := val.(string)
|
|
||||||
if !ok || len(certString) == 0 {
|
|
||||||
return nil, errors.New("malformed certificate chain")
|
|
||||||
}
|
|
||||||
x5c[i] = certString
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the x509 certificate chain can
|
leaf := jwk.Certificates[0]
|
||||||
// be verified up to one of our trusted roots.
|
if opts.Intermediates == nil {
|
||||||
leafKey, err := parseAndVerifyCertChain(x5c, verifyOpts.Roots)
|
opts.Intermediates = x509.NewCertPool()
|
||||||
|
for _, intermediate := range jwk.Certificates[1:] {
|
||||||
|
opts.Intermediates.AddCert(intermediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this call returns certificate chains which we ignore for now, but
|
||||||
|
// we should check them for revocations if we have the ability later.
|
||||||
|
chains, err := leaf.Verify(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not verify JWK certificate chain: %s", err)
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the public key in the leaf cert *is* the signing key.
|
|
||||||
if pubKey.KeyID() != leafKey.KeyID() {
|
|
||||||
return nil, errors.New("leaf certificate public key ID does not match JWK key ID")
|
|
||||||
}
|
}
|
||||||
|
signingKey = getCertPubKey(chains)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCertPubKey(chains [][]*x509.Certificate) crypto.PublicKey {
|
||||||
|
// NOTE(milosgajdos): if there are no certificates
|
||||||
|
// header.Certificates call above returns error, so we are
|
||||||
|
// guaranteed to get at least one certificate chain.
|
||||||
|
// We pick the leaf certificate chain.
|
||||||
|
chain := chains[0]
|
||||||
|
|
||||||
|
// NOTE(milosgajdos): header.Certificates call returns the result
|
||||||
|
// of leafCert.Verify which is a call to x509.Certificate.Verify.
|
||||||
|
// If successful, it returns one or more chains where the first
|
||||||
|
// element of the chain is x5c and the last element is from opts.Roots.
|
||||||
|
// See: https://pkg.go.dev/crypto/x509#Certificate.Verify
|
||||||
|
cert := chain[0]
|
||||||
|
|
||||||
|
// NOTE: we dont have to verify that the public key in the leaf cert
|
||||||
|
// *is* the signing key: if it's not the signing then token claims
|
||||||
|
// verifcation with this key fails
|
||||||
|
return cert.PublicKey.(crypto.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
// accessSet returns a set of actions available for the resource
|
// accessSet returns a set of actions available for the resource
|
||||||
// actions listed in the `access` section of this token.
|
// actions listed in the `access` section of this token.
|
||||||
func (t *Token) accessSet() accessSet {
|
func (c *ClaimSet) accessSet() accessSet {
|
||||||
if t.Claims == nil {
|
accessSet := make(accessSet, len(c.Access))
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
accessSet := make(accessSet, len(t.Claims.Access))
|
for _, resourceActions := range c.Access {
|
||||||
|
|
||||||
for _, resourceActions := range t.Claims.Access {
|
|
||||||
resource := auth.Resource{
|
resource := auth.Resource{
|
||||||
Type: resourceActions.Type,
|
Type: resourceActions.Type,
|
||||||
Name: resourceActions.Name,
|
Name: resourceActions.Name,
|
||||||
|
@ -354,13 +255,10 @@ func (t *Token) accessSet() accessSet {
|
||||||
return accessSet
|
return accessSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) resources() []auth.Resource {
|
func (c *ClaimSet) resources() []auth.Resource {
|
||||||
if t.Claims == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceSet := map[auth.Resource]struct{}{}
|
resourceSet := map[auth.Resource]struct{}{}
|
||||||
for _, resourceActions := range t.Claims.Access {
|
|
||||||
|
for _, resourceActions := range c.Access {
|
||||||
resource := auth.Resource{
|
resource := auth.Resource{
|
||||||
Type: resourceActions.Type,
|
Type: resourceActions.Type,
|
||||||
Class: resourceActions.Class,
|
Class: resourceActions.Class,
|
||||||
|
@ -376,7 +274,3 @@ func (t *Token) resources() []auth.Resource {
|
||||||
|
|
||||||
return resources
|
return resources
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) compactRaw() string {
|
|
||||||
return fmt.Sprintf("%s.%s", t.Raw, joseBase64UrlEncode(t.Signature))
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,110 +2,110 @@ package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/distribution/distribution/v3/context"
|
"github.com/distribution/distribution/v3/context"
|
||||||
"github.com/distribution/distribution/v3/registry/auth"
|
"github.com/distribution/distribution/v3/registry/auth"
|
||||||
"github.com/docker/libtrust"
|
"github.com/go-jose/go-jose/v3"
|
||||||
|
"github.com/go-jose/go-jose/v3/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeRootKeys(numKeys int) ([]libtrust.PrivateKey, error) {
|
func makeRootKeys(numKeys int) ([]*ecdsa.PrivateKey, error) {
|
||||||
keys := make([]libtrust.PrivateKey, 0, numKeys)
|
rootKeys := make([]*ecdsa.PrivateKey, 0, numKeys)
|
||||||
|
|
||||||
for i := 0; i < numKeys; i++ {
|
for i := 0; i < numKeys; i++ {
|
||||||
key, err := libtrust.GenerateECP256PrivateKey()
|
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
keys = append(keys, key)
|
rootKeys = append(rootKeys, pk)
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys, nil
|
return rootKeys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSigningKeyWithChain(rootKey libtrust.PrivateKey, depth int) (libtrust.PrivateKey, error) {
|
func makeRootCerts(rootKeys []*ecdsa.PrivateKey) ([]*x509.Certificate, error) {
|
||||||
|
rootCerts := make([]*x509.Certificate, 0, len(rootKeys))
|
||||||
|
|
||||||
|
for _, rootKey := range rootKeys {
|
||||||
|
cert, err := generateCACert(rootKey, rootKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootCerts = append(rootCerts, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootCerts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSigningKeyWithChain(rootKey *ecdsa.PrivateKey, depth int) (*jose.JSONWebKey, error) {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
// Don't need to build a chain.
|
// Don't need to build a chain.
|
||||||
return rootKey, nil
|
return &jose.JSONWebKey{
|
||||||
|
Key: rootKey,
|
||||||
|
KeyID: rootKey.X.String(),
|
||||||
|
Algorithm: string(jose.ES256),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
x5c = make([]string, depth)
|
certs = make([]*x509.Certificate, depth)
|
||||||
parentKey = rootKey
|
parentKey = rootKey
|
||||||
key libtrust.PrivateKey
|
|
||||||
|
pk *ecdsa.PrivateKey
|
||||||
cert *x509.Certificate
|
cert *x509.Certificate
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
for depth > 0 {
|
for depth > 0 {
|
||||||
if key, err = libtrust.GenerateECP256PrivateKey(); err != nil {
|
if pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cert, err = libtrust.GenerateCACert(parentKey, key); err != nil {
|
if cert, err = generateCACert(parentKey, pk); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
depth--
|
depth--
|
||||||
x5c[depth] = base64.StdEncoding.EncodeToString(cert.Raw)
|
certs[depth] = cert
|
||||||
parentKey = key
|
parentKey = pk
|
||||||
}
|
}
|
||||||
|
|
||||||
key.AddExtendedField("x5c", x5c)
|
return &jose.JSONWebKey{
|
||||||
|
Key: parentKey,
|
||||||
return key, nil
|
KeyID: rootKey.X.String(),
|
||||||
|
Algorithm: string(jose.ES256),
|
||||||
|
Certificates: certs,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRootCerts(rootKeys []libtrust.PrivateKey) ([]*x509.Certificate, error) {
|
func makeTestToken(jwk *jose.JSONWebKey, issuer, audience string, access []*ResourceActions, now time.Time, exp time.Time) (*Token, error) {
|
||||||
certs := make([]*x509.Certificate, 0, len(rootKeys))
|
signingKey := jose.SigningKey{
|
||||||
|
Algorithm: jose.ES256,
|
||||||
|
Key: jwk,
|
||||||
|
}
|
||||||
|
signerOpts := jose.SignerOptions{
|
||||||
|
EmbedJWK: true,
|
||||||
|
}
|
||||||
|
signerOpts.WithType("JWT")
|
||||||
|
|
||||||
for _, key := range rootKeys {
|
signer, err := jose.NewSigner(signingKey, &signerOpts)
|
||||||
cert, err := libtrust.GenerateCACert(key, key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to create a signer: %s", err)
|
||||||
}
|
|
||||||
certs = append(certs, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
return certs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTrustedKeyMap(rootKeys []libtrust.PrivateKey) map[string]libtrust.PublicKey {
|
|
||||||
trustedKeys := make(map[string]libtrust.PublicKey, len(rootKeys))
|
|
||||||
|
|
||||||
for _, key := range rootKeys {
|
|
||||||
trustedKeys[key.KeyID()] = key.PublicKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
return trustedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestToken(issuer, audience string, access []*ResourceActions, rootKey libtrust.PrivateKey, depth int, now time.Time, exp time.Time) (*Token, error) {
|
|
||||||
signingKey, err := makeSigningKeyWithChain(rootKey, depth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to make signing key with chain: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawJWK json.RawMessage
|
|
||||||
rawJWK, err = signingKey.PublicKey().MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to marshal signing key to JSON: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
joseHeader := &Header{
|
|
||||||
Type: "JWT",
|
|
||||||
SigningAlg: "ES256",
|
|
||||||
RawJWK: &rawJWK,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
randomBytes := make([]byte, 15)
|
randomBytes := make([]byte, 15)
|
||||||
|
@ -124,30 +124,99 @@ func makeTestToken(issuer, audience string, access []*ResourceActions, rootKey l
|
||||||
Access: access,
|
Access: access,
|
||||||
}
|
}
|
||||||
|
|
||||||
var joseHeaderBytes, claimSetBytes []byte
|
tokenString, err := jwt.Signed(signer).Claims(claimSet).CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
if joseHeaderBytes, err = json.Marshal(joseHeader); err != nil {
|
return nil, fmt.Errorf("unable to build token string: %v", err)
|
||||||
return nil, fmt.Errorf("unable to marshal jose header: %s", err)
|
|
||||||
}
|
}
|
||||||
if claimSetBytes, err = json.Marshal(claimSet); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to marshal claim set: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedJoseHeader := joseBase64UrlEncode(joseHeaderBytes)
|
|
||||||
encodedClaimSet := joseBase64UrlEncode(claimSetBytes)
|
|
||||||
encodingToSign := fmt.Sprintf("%s.%s", encodedJoseHeader, encodedClaimSet)
|
|
||||||
|
|
||||||
var signatureBytes []byte
|
|
||||||
if signatureBytes, _, err = signingKey.Sign(strings.NewReader(encodingToSign), crypto.SHA256); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to sign jwt payload: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
signature := joseBase64UrlEncode(signatureBytes)
|
|
||||||
tokenString := fmt.Sprintf("%s.%s", encodingToSign, signature)
|
|
||||||
|
|
||||||
return NewToken(tokenString)
|
return NewToken(tokenString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE(milosgajdos): certTemplateInfo type as well
|
||||||
|
// as some of the functions in this file have been
|
||||||
|
// adopted from https://github.com/docker/libtrust
|
||||||
|
// and modiified to fit the purpose of the token package.
|
||||||
|
|
||||||
|
type certTemplateInfo struct {
|
||||||
|
commonName string
|
||||||
|
domains []string
|
||||||
|
ipAddresses []net.IP
|
||||||
|
isCA bool
|
||||||
|
clientAuth bool
|
||||||
|
serverAuth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCertTemplate(info *certTemplateInfo) *x509.Certificate {
|
||||||
|
// Generate a certificate template which is valid from the past week to
|
||||||
|
// 10 years from now. The usage of the certificate depends on the
|
||||||
|
// specified fields in the given certTempInfo object.
|
||||||
|
var (
|
||||||
|
keyUsage x509.KeyUsage
|
||||||
|
extKeyUsage []x509.ExtKeyUsage
|
||||||
|
)
|
||||||
|
|
||||||
|
if info.isCA {
|
||||||
|
keyUsage = x509.KeyUsageCertSign
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.clientAuth {
|
||||||
|
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageClientAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.serverAuth {
|
||||||
|
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(0),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: info.commonName,
|
||||||
|
},
|
||||||
|
NotBefore: time.Now().Add(-time.Hour * 24 * 7),
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
|
||||||
|
DNSNames: info.domains,
|
||||||
|
IPAddresses: info.ipAddresses,
|
||||||
|
IsCA: info.isCA,
|
||||||
|
KeyUsage: keyUsage,
|
||||||
|
ExtKeyUsage: extKeyUsage,
|
||||||
|
BasicConstraintsValid: info.isCA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCert(priv crypto.PrivateKey, pub crypto.PublicKey, subInfo, issInfo *certTemplateInfo) (*x509.Certificate, error) {
|
||||||
|
pubCertTemplate := generateCertTemplate(subInfo)
|
||||||
|
privCertTemplate := generateCertTemplate(issInfo)
|
||||||
|
|
||||||
|
certDER, err := x509.CreateCertificate(
|
||||||
|
rand.Reader, pubCertTemplate, privCertTemplate,
|
||||||
|
pub, priv,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create certificate: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := x509.ParseCertificate(certDER)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificate: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateCACert creates a certificate which can be used as a trusted
|
||||||
|
// certificate authority.
|
||||||
|
func generateCACert(signer *ecdsa.PrivateKey, trustedKey *ecdsa.PrivateKey) (*x509.Certificate, error) {
|
||||||
|
subjectInfo := &certTemplateInfo{
|
||||||
|
commonName: trustedKey.X.String(),
|
||||||
|
isCA: true,
|
||||||
|
}
|
||||||
|
issuerInfo := &certTemplateInfo{
|
||||||
|
commonName: signer.X.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return generateCert(signer, trustedKey.Public(), subjectInfo, issuerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
// This test makes 4 tokens with a varying number of intermediate
|
// This test makes 4 tokens with a varying number of intermediate
|
||||||
// certificates ranging from no intermediate chain to a length of 3
|
// certificates ranging from no intermediate chain to a length of 3
|
||||||
// intermediates.
|
// intermediates.
|
||||||
|
@ -180,12 +249,17 @@ func TestTokenVerify(t *testing.T) {
|
||||||
rootPool.AddCert(rootCert)
|
rootPool.AddCert(rootCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
trustedKeys := makeTrustedKeyMap(rootKeys)
|
|
||||||
|
|
||||||
tokens := make([]*Token, 0, numTokens)
|
tokens := make([]*Token, 0, numTokens)
|
||||||
|
trustedKeys := map[string]crypto.PublicKey{}
|
||||||
|
|
||||||
for i := 0; i < numTokens; i++ {
|
for i := 0; i < numTokens; i++ {
|
||||||
token, err := makeTestToken(issuer, audience, access, rootKeys[i], i, time.Now(), time.Now().Add(5*time.Minute))
|
jwk, err := makeSigningKeyWithChain(rootKeys[i], i)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// add to trusted keys
|
||||||
|
trustedKeys[jwk.KeyID] = jwk.Public()
|
||||||
|
token, err := makeTestToken(jwk, issuer, audience, access, time.Now(), time.Now().Add(5*time.Minute))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -200,7 +274,7 @@ func TestTokenVerify(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
if err := token.Verify(verifyOps); err != nil {
|
if _, err := token.Verify(verifyOps); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,7 +300,14 @@ func TestLeeway(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
trustedKeys := makeTrustedKeyMap(rootKeys)
|
jwk, err := makeSigningKeyWithChain(rootKeys[0], 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustedKeys := map[string]crypto.PublicKey{
|
||||||
|
jwk.KeyID: jwk.Public(),
|
||||||
|
}
|
||||||
|
|
||||||
verifyOps := VerifyOptions{
|
verifyOps := VerifyOptions{
|
||||||
TrustedIssuers: []string{issuer},
|
TrustedIssuers: []string{issuer},
|
||||||
|
@ -237,48 +318,48 @@ func TestLeeway(t *testing.T) {
|
||||||
|
|
||||||
// nbf verification should pass within leeway
|
// nbf verification should pass within leeway
|
||||||
futureNow := time.Now().Add(time.Duration(5) * time.Second)
|
futureNow := time.Now().Add(time.Duration(5) * time.Second)
|
||||||
token, err := makeTestToken(issuer, audience, access, rootKeys[0], 0, futureNow, futureNow.Add(5*time.Minute))
|
token, err := makeTestToken(jwk, issuer, audience, access, futureNow, futureNow.Add(5*time.Minute))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := token.Verify(verifyOps); err != nil {
|
if _, err := token.Verify(verifyOps); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nbf verification should fail with a skew larger than leeway
|
// nbf verification should fail with a skew larger than leeway
|
||||||
futureNow = time.Now().Add(time.Duration(61) * time.Second)
|
futureNow = time.Now().Add(time.Duration(61) * time.Second)
|
||||||
token, err = makeTestToken(issuer, audience, access, rootKeys[0], 0, futureNow, futureNow.Add(5*time.Minute))
|
token, err = makeTestToken(jwk, issuer, audience, access, futureNow, futureNow.Add(5*time.Minute))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = token.Verify(verifyOps); err == nil {
|
if _, err = token.Verify(verifyOps); err == nil {
|
||||||
t.Fatal("Verification should fail for token with nbf in the future outside leeway")
|
t.Fatal("Verification should fail for token with nbf in the future outside leeway")
|
||||||
}
|
}
|
||||||
|
|
||||||
// exp verification should pass within leeway
|
// exp verification should pass within leeway
|
||||||
token, err = makeTestToken(issuer, audience, access, rootKeys[0], 0, time.Now(), time.Now().Add(-59*time.Second))
|
token, err = makeTestToken(jwk, issuer, audience, access, time.Now(), time.Now().Add(-59*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = token.Verify(verifyOps); err != nil {
|
if _, err = token.Verify(verifyOps); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// exp verification should fail with a skew larger than leeway
|
// exp verification should fail with a skew larger than leeway
|
||||||
token, err = makeTestToken(issuer, audience, access, rootKeys[0], 0, time.Now(), time.Now().Add(-60*time.Second))
|
token, err = makeTestToken(jwk, issuer, audience, access, time.Now(), time.Now().Add(-60*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = token.Verify(verifyOps); err == nil {
|
if _, err = token.Verify(verifyOps); err == nil {
|
||||||
t.Fatal("Verification should fail for token with exp in the future outside leeway")
|
t.Fatal("Verification should fail for token with exp in the future outside leeway")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeTempRootCerts(rootKeys []libtrust.PrivateKey) (filename string, err error) {
|
func writeTempRootCerts(rootKeys []*ecdsa.PrivateKey) (filename string, err error) {
|
||||||
rootCerts, err := makeRootCerts(rootKeys)
|
rootCerts, err := makeRootCerts(rootKeys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -303,6 +384,31 @@ func writeTempRootCerts(rootKeys []libtrust.PrivateKey) (filename string, err er
|
||||||
return tempFile.Name(), nil
|
return tempFile.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeTempJWKS(rootKeys []*ecdsa.PrivateKey) (filename string, err error) {
|
||||||
|
keys := make([]jose.JSONWebKey, len(rootKeys))
|
||||||
|
for i := range rootKeys {
|
||||||
|
jwk, err := makeSigningKeyWithChain(rootKeys[i], i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
keys[i] = *jwk
|
||||||
|
}
|
||||||
|
jwks := jose.JSONWebKeySet{
|
||||||
|
Keys: keys,
|
||||||
|
}
|
||||||
|
tempFile, err := os.CreateTemp("", "jwksBundle")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
if err := json.NewEncoder(tempFile).Encode(jwks); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempFile.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TestAccessController tests complete integration of the token auth package.
|
// TestAccessController tests complete integration of the token auth package.
|
||||||
// It starts by mocking the options for a token auth accessController which
|
// It starts by mocking the options for a token auth accessController which
|
||||||
// it creates. It then tries a few mock requests:
|
// it creates. It then tries a few mock requests:
|
||||||
|
@ -323,6 +429,11 @@ func TestAccessController(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.Remove(rootCertBundleFilename)
|
defer os.Remove(rootCertBundleFilename)
|
||||||
|
|
||||||
|
jwksFilename, err := writeTempJWKS(rootKeys)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
realm := "https://auth.example.com/token/"
|
realm := "https://auth.example.com/token/"
|
||||||
issuer := "test-issuer.example.com"
|
issuer := "test-issuer.example.com"
|
||||||
service := "test-service.example.com"
|
service := "test-service.example.com"
|
||||||
|
@ -333,6 +444,7 @@ func TestAccessController(t *testing.T) {
|
||||||
"service": service,
|
"service": service,
|
||||||
"rootcertbundle": rootCertBundleFilename,
|
"rootcertbundle": rootCertBundleFilename,
|
||||||
"autoredirect": false,
|
"autoredirect": false,
|
||||||
|
"jwks": jwksFilename,
|
||||||
}
|
}
|
||||||
|
|
||||||
accessController, err := newAccessController(options)
|
accessController, err := newAccessController(options)
|
||||||
|
@ -370,20 +482,25 @@ func TestAccessController(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Supply an invalid token.
|
// 2. Supply an invalid token.
|
||||||
|
invalidJwk, err := makeSigningKeyWithChain(rootKeys[1], 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
token, err := makeTestToken(
|
token, err := makeTestToken(
|
||||||
issuer, service,
|
invalidJwk, issuer, service,
|
||||||
[]*ResourceActions{{
|
[]*ResourceActions{{
|
||||||
Type: testAccess.Type,
|
Type: testAccess.Type,
|
||||||
Name: testAccess.Name,
|
Name: testAccess.Name,
|
||||||
Actions: []string{testAccess.Action},
|
Actions: []string{testAccess.Action},
|
||||||
}},
|
}},
|
||||||
rootKeys[1], 1, time.Now(), time.Now().Add(5*time.Minute), // Everything is valid except the key which signed it.
|
time.Now(), time.Now().Add(5*time.Minute), // Everything is valid except the key which signed it.
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Raw))
|
||||||
|
|
||||||
authCtx, err = accessController.Authorized(ctx, testAccess)
|
authCtx, err = accessController.Authorized(ctx, testAccess)
|
||||||
challenge, ok = err.(auth.Challenge)
|
challenge, ok = err.(auth.Challenge)
|
||||||
|
@ -399,17 +516,23 @@ func TestAccessController(t *testing.T) {
|
||||||
t.Fatalf("expected nil auth context but got %s", authCtx)
|
t.Fatalf("expected nil auth context but got %s", authCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create a valid jwk
|
||||||
|
jwk, err := makeSigningKeyWithChain(rootKeys[0], 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Supply a token with insufficient access.
|
// 3. Supply a token with insufficient access.
|
||||||
token, err = makeTestToken(
|
token, err = makeTestToken(
|
||||||
issuer, service,
|
jwk, issuer, service,
|
||||||
[]*ResourceActions{}, // No access specified.
|
[]*ResourceActions{}, // No access specified.
|
||||||
rootKeys[0], 1, time.Now(), time.Now().Add(5*time.Minute),
|
time.Now(), time.Now().Add(5*time.Minute),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Raw))
|
||||||
|
|
||||||
authCtx, err = accessController.Authorized(ctx, testAccess)
|
authCtx, err = accessController.Authorized(ctx, testAccess)
|
||||||
challenge, ok = err.(auth.Challenge)
|
challenge, ok = err.(auth.Challenge)
|
||||||
|
@ -427,19 +550,19 @@ func TestAccessController(t *testing.T) {
|
||||||
|
|
||||||
// 4. Supply the token we need, or deserve, or whatever.
|
// 4. Supply the token we need, or deserve, or whatever.
|
||||||
token, err = makeTestToken(
|
token, err = makeTestToken(
|
||||||
issuer, service,
|
jwk, issuer, service,
|
||||||
[]*ResourceActions{{
|
[]*ResourceActions{{
|
||||||
Type: testAccess.Type,
|
Type: testAccess.Type,
|
||||||
Name: testAccess.Name,
|
Name: testAccess.Name,
|
||||||
Actions: []string{testAccess.Action},
|
Actions: []string{testAccess.Action},
|
||||||
}},
|
}},
|
||||||
rootKeys[0], 1, time.Now(), time.Now().Add(5*time.Minute),
|
time.Now(), time.Now().Add(5*time.Minute),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Raw))
|
||||||
|
|
||||||
authCtx, err = accessController.Authorized(ctx, testAccess)
|
authCtx, err = accessController.Authorized(ctx, testAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -457,19 +580,19 @@ func TestAccessController(t *testing.T) {
|
||||||
|
|
||||||
// 5. Supply a token with full admin rights, which is represented as "*".
|
// 5. Supply a token with full admin rights, which is represented as "*".
|
||||||
token, err = makeTestToken(
|
token, err = makeTestToken(
|
||||||
issuer, service,
|
jwk, issuer, service,
|
||||||
[]*ResourceActions{{
|
[]*ResourceActions{{
|
||||||
Type: testAccess.Type,
|
Type: testAccess.Type,
|
||||||
Name: testAccess.Name,
|
Name: testAccess.Name,
|
||||||
Actions: []string{"*"},
|
Actions: []string{"*"},
|
||||||
}},
|
}},
|
||||||
rootKeys[0], 1, time.Now(), time.Now().Add(5*time.Minute),
|
time.Now(), time.Now().Add(5*time.Minute),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Raw))
|
||||||
|
|
||||||
_, err = accessController.Authorized(ctx, testAccess)
|
_, err = accessController.Authorized(ctx, testAccess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -496,11 +619,11 @@ func TestNewAccessControllerPemBlock(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
keyBlock, err := rootKeys[0].PEMBlock()
|
bytes, err := x509.MarshalECPrivateKey(rootKeys[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = pem.Encode(file, keyBlock)
|
_, err = file.Write(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,5 @@
|
||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// joseBase64UrlEncode encodes the given data using the standard base64 url
|
|
||||||
// encoding format but with all trailing '=' characters omitted in accordance
|
|
||||||
// with the jose specification.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
|
||||||
func joseBase64UrlEncode(b []byte) string {
|
|
||||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
|
||||||
}
|
|
||||||
|
|
||||||
// joseBase64UrlDecode decodes the given string using the standard base64 url
|
|
||||||
// decoder but first adds the appropriate number of trailing '=' characters in
|
|
||||||
// accordance with the jose specification.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
|
||||||
func joseBase64UrlDecode(s string) ([]byte, error) {
|
|
||||||
switch len(s) % 4 {
|
|
||||||
case 0:
|
|
||||||
case 2:
|
|
||||||
s += "=="
|
|
||||||
case 3:
|
|
||||||
s += "="
|
|
||||||
default:
|
|
||||||
return nil, errors.New("illegal base64url string")
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// actionSet is a special type of stringSet.
|
// actionSet is a special type of stringSet.
|
||||||
type actionSet struct {
|
type actionSet struct {
|
||||||
stringSet
|
stringSet
|
||||||
|
|
13
vendor/github.com/docker/libtrust/CONTRIBUTING.md
generated
vendored
13
vendor/github.com/docker/libtrust/CONTRIBUTING.md
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
# Contributing to libtrust
|
|
||||||
|
|
||||||
Want to hack on libtrust? Awesome! Here are instructions to get you
|
|
||||||
started.
|
|
||||||
|
|
||||||
libtrust is a part of the [Docker](https://www.docker.com) project, and follows
|
|
||||||
the same rules and principles. If you're already familiar with the way
|
|
||||||
Docker does things, you'll feel right at home.
|
|
||||||
|
|
||||||
Otherwise, go read
|
|
||||||
[Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md).
|
|
||||||
|
|
||||||
Happy hacking!
|
|
3
vendor/github.com/docker/libtrust/MAINTAINERS
generated
vendored
3
vendor/github.com/docker/libtrust/MAINTAINERS
generated
vendored
|
@ -1,3 +0,0 @@
|
||||||
Solomon Hykes <solomon@docker.com>
|
|
||||||
Josh Hawn <josh@docker.com> (github: jlhawn)
|
|
||||||
Derek McGowan <derek@docker.com> (github: dmcgowan)
|
|
18
vendor/github.com/docker/libtrust/README.md
generated
vendored
18
vendor/github.com/docker/libtrust/README.md
generated
vendored
|
@ -1,18 +0,0 @@
|
||||||
# libtrust
|
|
||||||
|
|
||||||
Libtrust is library for managing authentication and authorization using public key cryptography.
|
|
||||||
|
|
||||||
Authentication is handled using the identity attached to the public key.
|
|
||||||
Libtrust provides multiple methods to prove possession of the private key associated with an identity.
|
|
||||||
- TLS x509 certificates
|
|
||||||
- Signature verification
|
|
||||||
- Key Challenge
|
|
||||||
|
|
||||||
Authorization and access control is managed through a distributed trust graph.
|
|
||||||
Trust servers are used as the authorities of the trust graph and allow caching portions of the graph for faster access.
|
|
||||||
|
|
||||||
## Copyright and license
|
|
||||||
|
|
||||||
Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license.
|
|
||||||
Docs released under Creative commons.
|
|
||||||
|
|
175
vendor/github.com/docker/libtrust/certificates.go
generated
vendored
175
vendor/github.com/docker/libtrust/certificates.go
generated
vendored
|
@ -1,175 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type certTemplateInfo struct {
|
|
||||||
commonName string
|
|
||||||
domains []string
|
|
||||||
ipAddresses []net.IP
|
|
||||||
isCA bool
|
|
||||||
clientAuth bool
|
|
||||||
serverAuth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCertTemplate(info *certTemplateInfo) *x509.Certificate {
|
|
||||||
// Generate a certificate template which is valid from the past week to
|
|
||||||
// 10 years from now. The usage of the certificate depends on the
|
|
||||||
// specified fields in the given certTempInfo object.
|
|
||||||
var (
|
|
||||||
keyUsage x509.KeyUsage
|
|
||||||
extKeyUsage []x509.ExtKeyUsage
|
|
||||||
)
|
|
||||||
|
|
||||||
if info.isCA {
|
|
||||||
keyUsage = x509.KeyUsageCertSign
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.clientAuth {
|
|
||||||
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageClientAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.serverAuth {
|
|
||||||
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &x509.Certificate{
|
|
||||||
SerialNumber: big.NewInt(0),
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: info.commonName,
|
|
||||||
},
|
|
||||||
NotBefore: time.Now().Add(-time.Hour * 24 * 7),
|
|
||||||
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
|
|
||||||
DNSNames: info.domains,
|
|
||||||
IPAddresses: info.ipAddresses,
|
|
||||||
IsCA: info.isCA,
|
|
||||||
KeyUsage: keyUsage,
|
|
||||||
ExtKeyUsage: extKeyUsage,
|
|
||||||
BasicConstraintsValid: info.isCA,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateCert(pub PublicKey, priv PrivateKey, subInfo, issInfo *certTemplateInfo) (cert *x509.Certificate, err error) {
|
|
||||||
pubCertTemplate := generateCertTemplate(subInfo)
|
|
||||||
privCertTemplate := generateCertTemplate(issInfo)
|
|
||||||
|
|
||||||
certDER, err := x509.CreateCertificate(
|
|
||||||
rand.Reader, pubCertTemplate, privCertTemplate,
|
|
||||||
pub.CryptoPublicKey(), priv.CryptoPrivateKey(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err = x509.ParseCertificate(certDER)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSelfSignedServerCert creates a self-signed certificate for the
|
|
||||||
// given key which is to be used for TLS servers with the given domains and
|
|
||||||
// IP addresses.
|
|
||||||
func GenerateSelfSignedServerCert(key PrivateKey, domains []string, ipAddresses []net.IP) (*x509.Certificate, error) {
|
|
||||||
info := &certTemplateInfo{
|
|
||||||
commonName: key.KeyID(),
|
|
||||||
domains: domains,
|
|
||||||
ipAddresses: ipAddresses,
|
|
||||||
serverAuth: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateCert(key.PublicKey(), key, info, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSelfSignedClientCert creates a self-signed certificate for the
|
|
||||||
// given key which is to be used for TLS clients.
|
|
||||||
func GenerateSelfSignedClientCert(key PrivateKey) (*x509.Certificate, error) {
|
|
||||||
info := &certTemplateInfo{
|
|
||||||
commonName: key.KeyID(),
|
|
||||||
clientAuth: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateCert(key.PublicKey(), key, info, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateCACert creates a certificate which can be used as a trusted
|
|
||||||
// certificate authority.
|
|
||||||
func GenerateCACert(signer PrivateKey, trustedKey PublicKey) (*x509.Certificate, error) {
|
|
||||||
subjectInfo := &certTemplateInfo{
|
|
||||||
commonName: trustedKey.KeyID(),
|
|
||||||
isCA: true,
|
|
||||||
}
|
|
||||||
issuerInfo := &certTemplateInfo{
|
|
||||||
commonName: signer.KeyID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
return generateCert(trustedKey, signer, subjectInfo, issuerInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateCACertPool creates a certificate authority pool to be used for a
|
|
||||||
// TLS configuration. Any self-signed certificates issued by the specified
|
|
||||||
// trusted keys will be verified during a TLS handshake
|
|
||||||
func GenerateCACertPool(signer PrivateKey, trustedKeys []PublicKey) (*x509.CertPool, error) {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
|
|
||||||
for _, trustedKey := range trustedKeys {
|
|
||||||
cert, err := GenerateCACert(signer, trustedKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to generate CA certificate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPool.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
return certPool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCertificateBundle loads certificates from the given file. The file should be pem encoded
|
|
||||||
// containing one or more certificates. The expected pem type is "CERTIFICATE".
|
|
||||||
func LoadCertificateBundle(filename string) ([]*x509.Certificate, error) {
|
|
||||||
b, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certificates := []*x509.Certificate{}
|
|
||||||
var block *pem.Block
|
|
||||||
block, b = pem.Decode(b)
|
|
||||||
for ; block != nil; block, b = pem.Decode(b) {
|
|
||||||
if block.Type == "CERTIFICATE" {
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certificates = append(certificates, cert)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("invalid pem block type: %s", block.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certificates, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadCertificatePool loads a CA pool from the given file. The file should be pem encoded
|
|
||||||
// containing one or more certificates. The expected pem type is "CERTIFICATE".
|
|
||||||
func LoadCertificatePool(filename string) (*x509.CertPool, error) {
|
|
||||||
certs, err := LoadCertificateBundle(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pool := x509.NewCertPool()
|
|
||||||
for _, cert := range certs {
|
|
||||||
pool.AddCert(cert)
|
|
||||||
}
|
|
||||||
return pool, nil
|
|
||||||
}
|
|
9
vendor/github.com/docker/libtrust/doc.go
generated
vendored
9
vendor/github.com/docker/libtrust/doc.go
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
/*
|
|
||||||
Package libtrust provides an interface for managing authentication and
|
|
||||||
authorization using public key cryptography. Authentication is handled
|
|
||||||
using the identity attached to the public key and verified through TLS
|
|
||||||
x509 certificates, a key challenge, or signature. Authorization and
|
|
||||||
access control is managed through a trust graph distributed between
|
|
||||||
both remote trust servers and locally cached and managed data.
|
|
||||||
*/
|
|
||||||
package libtrust
|
|
428
vendor/github.com/docker/libtrust/ec_key.go
generated
vendored
428
vendor/github.com/docker/libtrust/ec_key.go
generated
vendored
|
@ -1,428 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* EC DSA PUBLIC KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ecPublicKey implements a libtrust.PublicKey using elliptic curve digital
|
|
||||||
// signature algorithms.
|
|
||||||
type ecPublicKey struct {
|
|
||||||
*ecdsa.PublicKey
|
|
||||||
curveName string
|
|
||||||
signatureAlgorithm *signatureAlgorithm
|
|
||||||
extended map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromECPublicKey(cryptoPublicKey *ecdsa.PublicKey) (*ecPublicKey, error) {
|
|
||||||
curve := cryptoPublicKey.Curve
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case curve == elliptic.P256():
|
|
||||||
return &ecPublicKey{cryptoPublicKey, "P-256", es256, map[string]interface{}{}}, nil
|
|
||||||
case curve == elliptic.P384():
|
|
||||||
return &ecPublicKey{cryptoPublicKey, "P-384", es384, map[string]interface{}{}}, nil
|
|
||||||
case curve == elliptic.P521():
|
|
||||||
return &ecPublicKey{cryptoPublicKey, "P-521", es512, map[string]interface{}{}}, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unsupported elliptic curve")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyType returns the key type for elliptic curve keys, i.e., "EC".
|
|
||||||
func (k *ecPublicKey) KeyType() string {
|
|
||||||
return "EC"
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurveName returns the elliptic curve identifier.
|
|
||||||
// Possible values are "P-256", "P-384", and "P-521".
|
|
||||||
func (k *ecPublicKey) CurveName() string {
|
|
||||||
return k.curveName
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
||||||
func (k *ecPublicKey) KeyID() string {
|
|
||||||
return keyIDFromCryptoKey(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) String() string {
|
|
||||||
return fmt.Sprintf("EC Public Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifyies the signature of the data in the io.Reader using this
|
|
||||||
// PublicKey. The alg parameter should identify the digital signature
|
|
||||||
// algorithm which was used to produce the signature and should be supported
|
|
||||||
// by this public key. Returns a nil error if the signature is valid.
|
|
||||||
func (k *ecPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
|
|
||||||
// For EC keys there is only one supported signature algorithm depending
|
|
||||||
// on the curve parameters.
|
|
||||||
if k.signatureAlgorithm.HeaderParam() != alg {
|
|
||||||
return fmt.Errorf("unable to verify signature: EC Public Key with curve %q does not support signature algorithm %q", k.curveName, alg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// signature is the concatenation of (r, s), base64Url encoded.
|
|
||||||
sigLength := len(signature)
|
|
||||||
expectedOctetLength := 2 * ((k.Params().BitSize + 7) >> 3)
|
|
||||||
if sigLength != expectedOctetLength {
|
|
||||||
return fmt.Errorf("signature length is %d octets long, should be %d", sigLength, expectedOctetLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
rBytes, sBytes := signature[:sigLength/2], signature[sigLength/2:]
|
|
||||||
r := new(big.Int).SetBytes(rBytes)
|
|
||||||
s := new(big.Int).SetBytes(sBytes)
|
|
||||||
|
|
||||||
hasher := k.signatureAlgorithm.HashID().New()
|
|
||||||
_, err := io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
if !ecdsa.Verify(k.PublicKey, hash, r, s) {
|
|
||||||
return errors.New("invalid signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPublicKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *ecPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return k.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) toMap() map[string]interface{} {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
for k, v := range k.extended {
|
|
||||||
jwk[k] = v
|
|
||||||
}
|
|
||||||
jwk["kty"] = k.KeyType()
|
|
||||||
jwk["kid"] = k.KeyID()
|
|
||||||
jwk["crv"] = k.CurveName()
|
|
||||||
|
|
||||||
xBytes := k.X.Bytes()
|
|
||||||
yBytes := k.Y.Bytes()
|
|
||||||
octetLength := (k.Params().BitSize + 7) >> 3
|
|
||||||
// MUST include leading zeros in the output so that x, y are each
|
|
||||||
// *octetLength* bytes long.
|
|
||||||
xBuf := make([]byte, octetLength-len(xBytes), octetLength)
|
|
||||||
yBuf := make([]byte, octetLength-len(yBytes), octetLength)
|
|
||||||
xBuf = append(xBuf, xBytes...)
|
|
||||||
yBuf = append(yBuf, yBytes...)
|
|
||||||
|
|
||||||
jwk["x"] = joseBase64UrlEncode(xBuf)
|
|
||||||
jwk["y"] = joseBase64UrlEncode(yBuf)
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
|
|
||||||
// elliptic curve keys.
|
|
||||||
func (k *ecPublicKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
|
|
||||||
func (k *ecPublicKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err)
|
|
||||||
}
|
|
||||||
k.extended["kid"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) AddExtendedField(field string, value interface{}) {
|
|
||||||
k.extended[field] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPublicKey) GetExtendedField(field string) interface{} {
|
|
||||||
v, ok := k.extended[field]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func ecPublicKeyFromMap(jwk map[string]interface{}) (*ecPublicKey, error) {
|
|
||||||
// JWK key type (kty) has already been determined to be "EC".
|
|
||||||
// Need to extract 'crv', 'x', 'y', and 'kid' and check for
|
|
||||||
// consistency.
|
|
||||||
|
|
||||||
// Get the curve identifier value.
|
|
||||||
crv, err := stringFromMap(jwk, "crv")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key curve identifier: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
curve elliptic.Curve
|
|
||||||
sigAlg *signatureAlgorithm
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case crv == "P-256":
|
|
||||||
curve = elliptic.P256()
|
|
||||||
sigAlg = es256
|
|
||||||
case crv == "P-384":
|
|
||||||
curve = elliptic.P384()
|
|
||||||
sigAlg = es384
|
|
||||||
case crv == "P-521":
|
|
||||||
curve = elliptic.P521()
|
|
||||||
sigAlg = es512
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key curve identifier not supported: %q\n", crv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the X and Y coordinates for the public key point.
|
|
||||||
xB64Url, err := stringFromMap(jwk, "x")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
x, err := parseECCoordinate(xB64Url, curve)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
yB64Url, err := stringFromMap(jwk, "y")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
y, err := parseECCoordinate(yB64Url, curve)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &ecPublicKey{
|
|
||||||
PublicKey: &ecdsa.PublicKey{Curve: curve, X: x, Y: y},
|
|
||||||
curveName: crv, signatureAlgorithm: sigAlg,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key ID is optional too, but if it exists, it should match the key.
|
|
||||||
_, ok := jwk["kid"]
|
|
||||||
if ok {
|
|
||||||
kid, err := stringFromMap(jwk, "kid")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key ID: %s", err)
|
|
||||||
}
|
|
||||||
if kid != key.KeyID() {
|
|
||||||
return nil, fmt.Errorf("JWK EC Public Key ID does not match: %s", kid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
key.extended = jwk
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* EC DSA PRIVATE KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ecPrivateKey implements a JWK Private Key using elliptic curve digital signature
|
|
||||||
// algorithms.
|
|
||||||
type ecPrivateKey struct {
|
|
||||||
ecPublicKey
|
|
||||||
*ecdsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromECPrivateKey(cryptoPrivateKey *ecdsa.PrivateKey) (*ecPrivateKey, error) {
|
|
||||||
publicKey, err := fromECPublicKey(&cryptoPrivateKey.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ecPrivateKey{*publicKey, cryptoPrivateKey}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey returns the Public Key data associated with this Private Key.
|
|
||||||
func (k *ecPrivateKey) PublicKey() PublicKey {
|
|
||||||
return &k.ecPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPrivateKey) String() string {
|
|
||||||
return fmt.Sprintf("EC Private Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the data read from the io.Reader using a signature algorithm supported
|
|
||||||
// by the elliptic curve private key. If the specified hashing algorithm is
|
|
||||||
// supported by this key, that hash function is used to generate the signature
|
|
||||||
// otherwise the the default hashing algorithm for this key is used. Returns
|
|
||||||
// the signature and the name of the JWK signature algorithm used, e.g.,
|
|
||||||
// "ES256", "ES384", "ES512".
|
|
||||||
func (k *ecPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
|
|
||||||
// Generate a signature of the data using the internal alg.
|
|
||||||
// The given hashId is only a suggestion, and since EC keys only support
|
|
||||||
// on signature/hash algorithm given the curve name, we disregard it for
|
|
||||||
// the elliptic curve JWK signature implementation.
|
|
||||||
hasher := k.signatureAlgorithm.HashID().New()
|
|
||||||
_, err = io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
r, s, err := ecdsa.Sign(rand.Reader, k.PrivateKey, hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error producing signature: %s", err)
|
|
||||||
}
|
|
||||||
rBytes, sBytes := r.Bytes(), s.Bytes()
|
|
||||||
octetLength := (k.ecPublicKey.Params().BitSize + 7) >> 3
|
|
||||||
// MUST include leading zeros in the output
|
|
||||||
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
|
|
||||||
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
|
|
||||||
|
|
||||||
rBuf = append(rBuf, rBytes...)
|
|
||||||
sBuf = append(sBuf, sBytes...)
|
|
||||||
|
|
||||||
signature = append(rBuf, sBuf...)
|
|
||||||
alg = k.signatureAlgorithm.HeaderParam()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPrivateKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *ecPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
|
|
||||||
return k.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecPrivateKey) toMap() map[string]interface{} {
|
|
||||||
jwk := k.ecPublicKey.toMap()
|
|
||||||
|
|
||||||
dBytes := k.D.Bytes()
|
|
||||||
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
|
||||||
// octets (where n is the order of the curve). This is because the private
|
|
||||||
// key d must be in the interval [1, n-1] so the bitlength of d should be
|
|
||||||
// no larger than the bitlength of n-1. The easiest way to find the octet
|
|
||||||
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
|
|
||||||
// bit sequence right by 3, which is essentially dividing by 8 and adding
|
|
||||||
// 1 if there is any remainder. Thus, the private key value d should be
|
|
||||||
// output to (bitlength(n-1)+7)>>3 octets.
|
|
||||||
n := k.ecPublicKey.Params().N
|
|
||||||
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
|
|
||||||
// Create a buffer with the necessary zero-padding.
|
|
||||||
dBuf := make([]byte, octetLength-len(dBytes), octetLength)
|
|
||||||
dBuf = append(dBuf, dBytes...)
|
|
||||||
|
|
||||||
jwk["d"] = joseBase64UrlEncode(dBuf)
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
|
|
||||||
// elliptic curve keys.
|
|
||||||
func (k *ecPrivateKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
|
|
||||||
func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes, err := x509.MarshalECPrivateKey(k.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err)
|
|
||||||
}
|
|
||||||
k.extended["keyID"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("EC PRIVATE KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ecPrivateKeyFromMap(jwk map[string]interface{}) (*ecPrivateKey, error) {
|
|
||||||
dB64Url, err := stringFromMap(jwk, "d")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Private Key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWK key type (kty) has already been determined to be "EC".
|
|
||||||
// Need to extract the public key information, then extract the private
|
|
||||||
// key value 'd'.
|
|
||||||
publicKey, err := ecPublicKeyFromMap(jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := parseECPrivateParam(dB64Url, publicKey.Curve)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK EC Private Key d-param: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &ecPrivateKey{
|
|
||||||
ecPublicKey: *publicKey,
|
|
||||||
PrivateKey: &ecdsa.PrivateKey{
|
|
||||||
PublicKey: *publicKey.PublicKey,
|
|
||||||
D: d,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Key Generation Functions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func generateECPrivateKey(curve elliptic.Curve) (k *ecPrivateKey, err error) {
|
|
||||||
k = new(ecPrivateKey)
|
|
||||||
k.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k.ecPublicKey.PublicKey = &k.PrivateKey.PublicKey
|
|
||||||
k.extended = make(map[string]interface{})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateECP256PrivateKey generates a key pair using elliptic curve P-256.
|
|
||||||
func GenerateECP256PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateECPrivateKey(elliptic.P256())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating EC P-256 key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.curveName = "P-256"
|
|
||||||
k.signatureAlgorithm = es256
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateECP384PrivateKey generates a key pair using elliptic curve P-384.
|
|
||||||
func GenerateECP384PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateECPrivateKey(elliptic.P384())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating EC P-384 key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.curveName = "P-384"
|
|
||||||
k.signatureAlgorithm = es384
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateECP521PrivateKey generates aß key pair using elliptic curve P-521.
|
|
||||||
func GenerateECP521PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateECPrivateKey(elliptic.P521())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating EC P-521 key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
k.curveName = "P-521"
|
|
||||||
k.signatureAlgorithm = es512
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
50
vendor/github.com/docker/libtrust/filter.go
generated
vendored
50
vendor/github.com/docker/libtrust/filter.go
generated
vendored
|
@ -1,50 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterByHosts filters the list of PublicKeys to only those which contain a
|
|
||||||
// 'hosts' pattern which matches the given host. If *includeEmpty* is true,
|
|
||||||
// then keys which do not specify any hosts are also returned.
|
|
||||||
func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKey, error) {
|
|
||||||
filtered := make([]PublicKey, 0, len(keys))
|
|
||||||
|
|
||||||
for _, pubKey := range keys {
|
|
||||||
var hosts []string
|
|
||||||
switch v := pubKey.GetExtendedField("hosts").(type) {
|
|
||||||
case []string:
|
|
||||||
hosts = v
|
|
||||||
case []interface{}:
|
|
||||||
for _, value := range v {
|
|
||||||
h, ok := value.(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hosts = append(hosts, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hosts) == 0 {
|
|
||||||
if includeEmpty {
|
|
||||||
filtered = append(filtered, pubKey)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if any hosts match pattern
|
|
||||||
for _, hostPattern := range hosts {
|
|
||||||
match, err := filepath.Match(hostPattern, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if match {
|
|
||||||
filtered = append(filtered, pubKey)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filtered, nil
|
|
||||||
}
|
|
56
vendor/github.com/docker/libtrust/hash.go
generated
vendored
56
vendor/github.com/docker/libtrust/hash.go
generated
vendored
|
@ -1,56 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
_ "crypto/sha256" // Registrer SHA224 and SHA256
|
|
||||||
_ "crypto/sha512" // Registrer SHA384 and SHA512
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type signatureAlgorithm struct {
|
|
||||||
algHeaderParam string
|
|
||||||
hashID crypto.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *signatureAlgorithm) HeaderParam() string {
|
|
||||||
return h.algHeaderParam
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *signatureAlgorithm) HashID() crypto.Hash {
|
|
||||||
return h.hashID
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
rs256 = &signatureAlgorithm{"RS256", crypto.SHA256}
|
|
||||||
rs384 = &signatureAlgorithm{"RS384", crypto.SHA384}
|
|
||||||
rs512 = &signatureAlgorithm{"RS512", crypto.SHA512}
|
|
||||||
es256 = &signatureAlgorithm{"ES256", crypto.SHA256}
|
|
||||||
es384 = &signatureAlgorithm{"ES384", crypto.SHA384}
|
|
||||||
es512 = &signatureAlgorithm{"ES512", crypto.SHA512}
|
|
||||||
)
|
|
||||||
|
|
||||||
func rsaSignatureAlgorithmByName(alg string) (*signatureAlgorithm, error) {
|
|
||||||
switch {
|
|
||||||
case alg == "RS256":
|
|
||||||
return rs256, nil
|
|
||||||
case alg == "RS384":
|
|
||||||
return rs384, nil
|
|
||||||
case alg == "RS512":
|
|
||||||
return rs512, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("RSA Digital Signature Algorithm %q not supported", alg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsaPKCS1v15SignatureAlgorithmForHashID(hashID crypto.Hash) *signatureAlgorithm {
|
|
||||||
switch {
|
|
||||||
case hashID == crypto.SHA512:
|
|
||||||
return rs512
|
|
||||||
case hashID == crypto.SHA384:
|
|
||||||
return rs384
|
|
||||||
case hashID == crypto.SHA256:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
return rs256
|
|
||||||
}
|
|
||||||
}
|
|
657
vendor/github.com/docker/libtrust/jsonsign.go
generated
vendored
657
vendor/github.com/docker/libtrust/jsonsign.go
generated
vendored
|
@ -1,657 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrInvalidSignContent is used when the content to be signed is invalid.
|
|
||||||
ErrInvalidSignContent = errors.New("invalid sign content")
|
|
||||||
|
|
||||||
// ErrInvalidJSONContent is used when invalid json is encountered.
|
|
||||||
ErrInvalidJSONContent = errors.New("invalid json content")
|
|
||||||
|
|
||||||
// ErrMissingSignatureKey is used when the specified signature key
|
|
||||||
// does not exist in the JSON content.
|
|
||||||
ErrMissingSignatureKey = errors.New("missing signature key")
|
|
||||||
)
|
|
||||||
|
|
||||||
type jsHeader struct {
|
|
||||||
JWK PublicKey `json:"jwk,omitempty"`
|
|
||||||
Algorithm string `json:"alg"`
|
|
||||||
Chain []string `json:"x5c,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsSignature struct {
|
|
||||||
Header jsHeader `json:"header"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
Protected string `json:"protected,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsSignaturesSorted []jsSignature
|
|
||||||
|
|
||||||
func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] }
|
|
||||||
func (jsbkid jsSignaturesSorted) Len() int { return len(jsbkid) }
|
|
||||||
|
|
||||||
func (jsbkid jsSignaturesSorted) Less(i, j int) bool {
|
|
||||||
ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID()
|
|
||||||
si, sj := jsbkid[i].Signature, jsbkid[j].Signature
|
|
||||||
|
|
||||||
if ki == kj {
|
|
||||||
return si < sj
|
|
||||||
}
|
|
||||||
|
|
||||||
return ki < kj
|
|
||||||
}
|
|
||||||
|
|
||||||
type signKey struct {
|
|
||||||
PrivateKey
|
|
||||||
Chain []*x509.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSONSignature represents a signature of a json object.
|
|
||||||
type JSONSignature struct {
|
|
||||||
payload string
|
|
||||||
signatures []jsSignature
|
|
||||||
indent string
|
|
||||||
formatLength int
|
|
||||||
formatTail []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newJSONSignature() *JSONSignature {
|
|
||||||
return &JSONSignature{
|
|
||||||
signatures: make([]jsSignature, 0, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payload returns the encoded payload of the signature. This
|
|
||||||
// payload should not be signed directly
|
|
||||||
func (js *JSONSignature) Payload() ([]byte, error) {
|
|
||||||
return joseBase64UrlDecode(js.payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (js *JSONSignature) protectedHeader() (string, error) {
|
|
||||||
protected := map[string]interface{}{
|
|
||||||
"formatLength": js.formatLength,
|
|
||||||
"formatTail": joseBase64UrlEncode(js.formatTail),
|
|
||||||
"time": time.Now().UTC().Format(time.RFC3339),
|
|
||||||
}
|
|
||||||
protectedBytes, err := json.Marshal(protected)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return joseBase64UrlEncode(protectedBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) {
|
|
||||||
buf := make([]byte, len(js.payload)+len(protectedHeader)+1)
|
|
||||||
copy(buf, protectedHeader)
|
|
||||||
buf[len(protectedHeader)] = '.'
|
|
||||||
copy(buf[len(protectedHeader)+1:], js.payload)
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign adds a signature using the given private key.
|
|
||||||
func (js *JSONSignature) Sign(key PrivateKey) error {
|
|
||||||
protected, err := js.protectedHeader()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signBytes, err := js.signBytes(protected)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = append(js.signatures, jsSignature{
|
|
||||||
Header: jsHeader{
|
|
||||||
JWK: key.PublicKey(),
|
|
||||||
Algorithm: algorithm,
|
|
||||||
},
|
|
||||||
Signature: joseBase64UrlEncode(sigBytes),
|
|
||||||
Protected: protected,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignWithChain adds a signature using the given private key
|
|
||||||
// and setting the x509 chain. The public key of the first element
|
|
||||||
// in the chain must be the public key corresponding with the sign key.
|
|
||||||
func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error {
|
|
||||||
// Ensure key.Chain[0] is public key for key
|
|
||||||
//key.Chain.PublicKey
|
|
||||||
//key.PublicKey().CryptoPublicKey()
|
|
||||||
|
|
||||||
// Verify chain
|
|
||||||
protected, err := js.protectedHeader()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
signBytes, err := js.signBytes(protected)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header := jsHeader{
|
|
||||||
Chain: make([]string, len(chain)),
|
|
||||||
Algorithm: algorithm,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, cert := range chain {
|
|
||||||
header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = append(js.signatures, jsSignature{
|
|
||||||
Header: header,
|
|
||||||
Signature: joseBase64UrlEncode(sigBytes),
|
|
||||||
Protected: protected,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifies all the signatures and returns the list of
|
|
||||||
// public keys used to sign. Any x509 chains are not checked.
|
|
||||||
func (js *JSONSignature) Verify() ([]PublicKey, error) {
|
|
||||||
keys := make([]PublicKey, len(js.signatures))
|
|
||||||
for i, signature := range js.signatures {
|
|
||||||
signBytes, err := js.signBytes(signature.Protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var publicKey PublicKey
|
|
||||||
if len(signature.Header.Chain) > 0 {
|
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if signature.Header.JWK != nil {
|
|
||||||
publicKey = signature.Header.JWK
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("missing public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
sigBytes, err := joseBase64UrlDecode(signature.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keys[i] = publicKey
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyChains verifies all the signatures and the chains associated
|
|
||||||
// with each signature and returns the list of verified chains.
|
|
||||||
// Signatures without an x509 chain are not checked.
|
|
||||||
func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
|
|
||||||
chains := make([][]*x509.Certificate, 0, len(js.signatures))
|
|
||||||
for _, signature := range js.signatures {
|
|
||||||
signBytes, err := js.signBytes(signature.Protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var publicKey PublicKey
|
|
||||||
if len(signature.Header.Chain) > 0 {
|
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intermediates := x509.NewCertPool()
|
|
||||||
if len(signature.Header.Chain) > 1 {
|
|
||||||
intermediateChain := signature.Header.Chain[1:]
|
|
||||||
for i := range intermediateChain {
|
|
||||||
certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intermediate, err := x509.ParseCertificate(certBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
intermediates.AddCert(intermediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyOptions := x509.VerifyOptions{
|
|
||||||
Intermediates: intermediates,
|
|
||||||
Roots: ca,
|
|
||||||
}
|
|
||||||
|
|
||||||
verifiedChains, err := cert.Verify(verifyOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
chains = append(chains, verifiedChains...)
|
|
||||||
|
|
||||||
sigBytes, err := joseBase64UrlDecode(signature.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return chains, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWS returns JSON serialized JWS according to
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2
|
|
||||||
func (js *JSONSignature) JWS() ([]byte, error) {
|
|
||||||
if len(js.signatures) == 0 {
|
|
||||||
return nil, errors.New("missing signature")
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(jsSignaturesSorted(js.signatures))
|
|
||||||
|
|
||||||
jsonMap := map[string]interface{}{
|
|
||||||
"payload": js.payload,
|
|
||||||
"signatures": js.signatures,
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.MarshalIndent(jsonMap, "", " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func notSpace(r rune) bool {
|
|
||||||
return !unicode.IsSpace(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectJSONIndent(jsonContent []byte) (indent string) {
|
|
||||||
if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' {
|
|
||||||
quoteIndex := bytes.IndexRune(jsonContent[1:], '"')
|
|
||||||
if quoteIndex > 0 {
|
|
||||||
indent = string(jsonContent[2 : quoteIndex+1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsParsedHeader struct {
|
|
||||||
JWK json.RawMessage `json:"jwk"`
|
|
||||||
Algorithm string `json:"alg"`
|
|
||||||
Chain []string `json:"x5c"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type jsParsedSignature struct {
|
|
||||||
Header jsParsedHeader `json:"header"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
Protected string `json:"protected"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseJWS parses a JWS serialized JSON object into a Json Signature.
|
|
||||||
func ParseJWS(content []byte) (*JSONSignature, error) {
|
|
||||||
type jsParsed struct {
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
Signatures []jsParsedSignature `json:"signatures"`
|
|
||||||
}
|
|
||||||
parsed := &jsParsed{}
|
|
||||||
err := json.Unmarshal(content, parsed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(parsed.Signatures) == 0 {
|
|
||||||
return nil, errors.New("missing signatures")
|
|
||||||
}
|
|
||||||
payload, err := joseBase64UrlDecode(parsed.Payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
js, err := NewJSONSignature(payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
js.signatures = make([]jsSignature, len(parsed.Signatures))
|
|
||||||
for i, signature := range parsed.Signatures {
|
|
||||||
header := jsHeader{
|
|
||||||
Algorithm: signature.Header.Algorithm,
|
|
||||||
}
|
|
||||||
if signature.Header.Chain != nil {
|
|
||||||
header.Chain = signature.Header.Chain
|
|
||||||
}
|
|
||||||
if signature.Header.JWK != nil {
|
|
||||||
publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
header.JWK = publicKey
|
|
||||||
}
|
|
||||||
js.signatures[i] = jsSignature{
|
|
||||||
Header: header,
|
|
||||||
Signature: signature.Signature,
|
|
||||||
Protected: signature.Protected,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSONSignature returns a new unsigned JWS from a json byte array.
|
|
||||||
// JSONSignature will need to be signed before serializing or storing.
|
|
||||||
// Optionally, one or more signatures can be provided as byte buffers,
|
|
||||||
// containing serialized JWS signatures, to assemble a fully signed JWS
|
|
||||||
// package. It is the callers responsibility to ensure uniqueness of the
|
|
||||||
// provided signatures.
|
|
||||||
func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) {
|
|
||||||
var dataMap map[string]interface{}
|
|
||||||
err := json.Unmarshal(content, &dataMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
js := newJSONSignature()
|
|
||||||
js.indent = detectJSONIndent(content)
|
|
||||||
|
|
||||||
js.payload = joseBase64UrlEncode(content)
|
|
||||||
|
|
||||||
// Find trailing } and whitespace, put in protected header
|
|
||||||
closeIndex := bytes.LastIndexFunc(content, notSpace)
|
|
||||||
if content[closeIndex] != '}' {
|
|
||||||
return nil, ErrInvalidJSONContent
|
|
||||||
}
|
|
||||||
lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace)
|
|
||||||
if content[lastRuneIndex] == ',' {
|
|
||||||
return nil, ErrInvalidJSONContent
|
|
||||||
}
|
|
||||||
js.formatLength = lastRuneIndex + 1
|
|
||||||
js.formatTail = content[js.formatLength:]
|
|
||||||
|
|
||||||
if len(signatures) > 0 {
|
|
||||||
for _, signature := range signatures {
|
|
||||||
var parsedJSig jsParsedSignature
|
|
||||||
|
|
||||||
if err := json.Unmarshal(signature, &parsedJSig); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(stevvooe): A lot of the code below is repeated in
|
|
||||||
// ParseJWS. It will require more refactoring to fix that.
|
|
||||||
jsig := jsSignature{
|
|
||||||
Header: jsHeader{
|
|
||||||
Algorithm: parsedJSig.Header.Algorithm,
|
|
||||||
},
|
|
||||||
Signature: parsedJSig.Signature,
|
|
||||||
Protected: parsedJSig.Protected,
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedJSig.Header.Chain != nil {
|
|
||||||
jsig.Header.Chain = parsedJSig.Header.Chain
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedJSig.Header.JWK != nil {
|
|
||||||
publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
jsig.Header.JWK = publicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = append(js.signatures, jsig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or
|
|
||||||
// struct. JWS will need to be signed before serializing or storing.
|
|
||||||
func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) {
|
|
||||||
switch content.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
case struct{}:
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid data type")
|
|
||||||
}
|
|
||||||
|
|
||||||
js := newJSONSignature()
|
|
||||||
js.indent = " "
|
|
||||||
|
|
||||||
payload, err := json.MarshalIndent(content, "", js.indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
js.payload = joseBase64UrlEncode(payload)
|
|
||||||
|
|
||||||
// Remove '\n}' from formatted section, put in protected header
|
|
||||||
js.formatLength = len(payload) - 2
|
|
||||||
js.formatTail = payload[js.formatLength:]
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readIntFromMap(key string, m map[string]interface{}) (int, bool) {
|
|
||||||
value, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
switch v := value.(type) {
|
|
||||||
case int:
|
|
||||||
return v, true
|
|
||||||
case float64:
|
|
||||||
return int(v), true
|
|
||||||
default:
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) {
|
|
||||||
value, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
v, ok = value.(string)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePrettySignature parses a formatted signature into a
|
|
||||||
// JSON signature. If the signatures are missing the format information
|
|
||||||
// an error is thrown. The formatted signature must be created by
|
|
||||||
// the same method as format signature.
|
|
||||||
func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) {
|
|
||||||
var contentMap map[string]json.RawMessage
|
|
||||||
err := json.Unmarshal(content, &contentMap)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling content: %s", err)
|
|
||||||
}
|
|
||||||
sigMessage, ok := contentMap[signatureKey]
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrMissingSignatureKey
|
|
||||||
}
|
|
||||||
|
|
||||||
var signatureBlocks []jsParsedSignature
|
|
||||||
err = json.Unmarshal([]byte(sigMessage), &signatureBlocks)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling signatures: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
js := newJSONSignature()
|
|
||||||
js.signatures = make([]jsSignature, len(signatureBlocks))
|
|
||||||
|
|
||||||
for i, signatureBlock := range signatureBlocks {
|
|
||||||
protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("base64 decode error: %s", err)
|
|
||||||
}
|
|
||||||
var protectedHeader map[string]interface{}
|
|
||||||
err = json.Unmarshal(protectedBytes, &protectedHeader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling protected header: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatLength, ok := readIntFromMap("formatLength", protectedHeader)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("missing formatted length")
|
|
||||||
}
|
|
||||||
encodedTail, ok := readStringFromMap("formatTail", protectedHeader)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("missing formatted tail")
|
|
||||||
}
|
|
||||||
formatTail, err := joseBase64UrlDecode(encodedTail)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("base64 decode error on tail: %s", err)
|
|
||||||
}
|
|
||||||
if js.formatLength == 0 {
|
|
||||||
js.formatLength = formatLength
|
|
||||||
} else if js.formatLength != formatLength {
|
|
||||||
return nil, errors.New("conflicting format length")
|
|
||||||
}
|
|
||||||
if len(js.formatTail) == 0 {
|
|
||||||
js.formatTail = formatTail
|
|
||||||
} else if bytes.Compare(js.formatTail, formatTail) != 0 {
|
|
||||||
return nil, errors.New("conflicting format tail")
|
|
||||||
}
|
|
||||||
|
|
||||||
header := jsHeader{
|
|
||||||
Algorithm: signatureBlock.Header.Algorithm,
|
|
||||||
Chain: signatureBlock.Header.Chain,
|
|
||||||
}
|
|
||||||
if signatureBlock.Header.JWK != nil {
|
|
||||||
publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error unmarshalling public key: %s", err)
|
|
||||||
}
|
|
||||||
header.JWK = publicKey
|
|
||||||
}
|
|
||||||
js.signatures[i] = jsSignature{
|
|
||||||
Header: header,
|
|
||||||
Signature: signatureBlock.Signature,
|
|
||||||
Protected: signatureBlock.Protected,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if js.formatLength > len(content) {
|
|
||||||
return nil, errors.New("invalid format length")
|
|
||||||
}
|
|
||||||
formatted := make([]byte, js.formatLength+len(js.formatTail))
|
|
||||||
copy(formatted, content[:js.formatLength])
|
|
||||||
copy(formatted[js.formatLength:], js.formatTail)
|
|
||||||
js.indent = detectJSONIndent(formatted)
|
|
||||||
js.payload = joseBase64UrlEncode(formatted)
|
|
||||||
|
|
||||||
return js, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrettySignature formats a json signature into an easy to read
|
|
||||||
// single json serialized object.
|
|
||||||
func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {
|
|
||||||
if len(js.signatures) == 0 {
|
|
||||||
return nil, errors.New("no signatures")
|
|
||||||
}
|
|
||||||
payload, err := joseBase64UrlDecode(js.payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
payload = payload[:js.formatLength]
|
|
||||||
|
|
||||||
sort.Sort(jsSignaturesSorted(js.signatures))
|
|
||||||
|
|
||||||
var marshalled []byte
|
|
||||||
var marshallErr error
|
|
||||||
if js.indent != "" {
|
|
||||||
marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent)
|
|
||||||
} else {
|
|
||||||
marshalled, marshallErr = json.Marshal(js.signatures)
|
|
||||||
}
|
|
||||||
if marshallErr != nil {
|
|
||||||
return nil, marshallErr
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34))
|
|
||||||
buf.Write(payload)
|
|
||||||
buf.WriteByte(',')
|
|
||||||
if js.indent != "" {
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
buf.WriteString(js.indent)
|
|
||||||
buf.WriteByte('"')
|
|
||||||
buf.WriteString(signatureKey)
|
|
||||||
buf.WriteString("\": ")
|
|
||||||
buf.Write(marshalled)
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
} else {
|
|
||||||
buf.WriteByte('"')
|
|
||||||
buf.WriteString(signatureKey)
|
|
||||||
buf.WriteString("\":")
|
|
||||||
buf.Write(marshalled)
|
|
||||||
}
|
|
||||||
buf.WriteByte('}')
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signatures provides the signatures on this JWS as opaque blobs, sorted by
|
|
||||||
// keyID. These blobs can be stored and reassembled with payloads. Internally,
|
|
||||||
// they are simply marshaled json web signatures but implementations should
|
|
||||||
// not rely on this.
|
|
||||||
func (js *JSONSignature) Signatures() ([][]byte, error) {
|
|
||||||
sort.Sort(jsSignaturesSorted(js.signatures))
|
|
||||||
|
|
||||||
var sb [][]byte
|
|
||||||
for _, jsig := range js.signatures {
|
|
||||||
p, err := json.Marshal(jsig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sb = append(sb, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge combines the signatures from one or more other signatures into the
|
|
||||||
// method receiver. If the payloads differ for any argument, an error will be
|
|
||||||
// returned and the receiver will not be modified.
|
|
||||||
func (js *JSONSignature) Merge(others ...*JSONSignature) error {
|
|
||||||
merged := js.signatures
|
|
||||||
for _, other := range others {
|
|
||||||
if js.payload != other.payload {
|
|
||||||
return fmt.Errorf("payloads differ from merge target")
|
|
||||||
}
|
|
||||||
merged = append(merged, other.signatures...)
|
|
||||||
}
|
|
||||||
|
|
||||||
js.signatures = merged
|
|
||||||
return nil
|
|
||||||
}
|
|
253
vendor/github.com/docker/libtrust/key.go
generated
vendored
253
vendor/github.com/docker/libtrust/key.go
generated
vendored
|
@ -1,253 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicKey is a generic interface for a Public Key.
|
|
||||||
type PublicKey interface {
|
|
||||||
// KeyType returns the key type for this key. For elliptic curve keys,
|
|
||||||
// this value should be "EC". For RSA keys, this value should be "RSA".
|
|
||||||
KeyType() string
|
|
||||||
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
||||||
// The format generated by this library is a base32 encoding of a 240 bit
|
|
||||||
// hash of the public key data divided into 12 groups like so:
|
|
||||||
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
|
|
||||||
KeyID() string
|
|
||||||
// Verify verifyies the signature of the data in the io.Reader using this
|
|
||||||
// Public Key. The alg parameter should identify the digital signature
|
|
||||||
// algorithm which was used to produce the signature and should be
|
|
||||||
// supported by this public key. Returns a nil error if the signature
|
|
||||||
// is valid.
|
|
||||||
Verify(data io.Reader, alg string, signature []byte) error
|
|
||||||
// CryptoPublicKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
CryptoPublicKey() crypto.PublicKey
|
|
||||||
// These public keys can be serialized to the standard JSON encoding for
|
|
||||||
// JSON Web Keys. See section 6 of the IETF draft RFC for JOSE JSON Web
|
|
||||||
// Algorithms.
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
// These keys can also be serialized to the standard PEM encoding.
|
|
||||||
PEMBlock() (*pem.Block, error)
|
|
||||||
// The string representation of a key is its key type and ID.
|
|
||||||
String() string
|
|
||||||
AddExtendedField(string, interface{})
|
|
||||||
GetExtendedField(string) interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateKey is a generic interface for a Private Key.
|
|
||||||
type PrivateKey interface {
|
|
||||||
// A PrivateKey contains all fields and methods of a PublicKey of the
|
|
||||||
// same type. The MarshalJSON method also outputs the private key as a
|
|
||||||
// JSON Web Key, and the PEMBlock method outputs the private key as a
|
|
||||||
// PEM block.
|
|
||||||
PublicKey
|
|
||||||
// PublicKey returns the PublicKey associated with this PrivateKey.
|
|
||||||
PublicKey() PublicKey
|
|
||||||
// Sign signs the data read from the io.Reader using a signature algorithm
|
|
||||||
// supported by the private key. If the specified hashing algorithm is
|
|
||||||
// supported by this key, that hash function is used to generate the
|
|
||||||
// signature otherwise the the default hashing algorithm for this key is
|
|
||||||
// used. Returns the signature and identifier of the algorithm used.
|
|
||||||
Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error)
|
|
||||||
// CryptoPrivateKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The
|
|
||||||
// type is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
CryptoPrivateKey() crypto.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromCryptoPublicKey returns a libtrust PublicKey representation of the given
|
|
||||||
// *ecdsa.PublicKey or *rsa.PublicKey. Returns a non-nil error when the given
|
|
||||||
// key is of an unsupported type.
|
|
||||||
func FromCryptoPublicKey(cryptoPublicKey crypto.PublicKey) (PublicKey, error) {
|
|
||||||
switch cryptoPublicKey := cryptoPublicKey.(type) {
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
return fromECPublicKey(cryptoPublicKey)
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
return fromRSAPublicKey(cryptoPublicKey), nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("public key type %T is not supported", cryptoPublicKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromCryptoPrivateKey returns a libtrust PrivateKey representation of the given
|
|
||||||
// *ecdsa.PrivateKey or *rsa.PrivateKey. Returns a non-nil error when the given
|
|
||||||
// key is of an unsupported type.
|
|
||||||
func FromCryptoPrivateKey(cryptoPrivateKey crypto.PrivateKey) (PrivateKey, error) {
|
|
||||||
switch cryptoPrivateKey := cryptoPrivateKey.(type) {
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
return fromECPrivateKey(cryptoPrivateKey)
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
return fromRSAPrivateKey(cryptoPrivateKey), nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("private key type %T is not supported", cryptoPrivateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyPEM parses the PEM encoded data and returns a libtrust
|
|
||||||
// PublicKey or an error if there is a problem with the encoding.
|
|
||||||
func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) {
|
|
||||||
pemBlock, _ := pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return nil, errors.New("unable to find PEM encoded data")
|
|
||||||
} else if pemBlock.Type != "PUBLIC KEY" {
|
|
||||||
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKeyFromPEMBlock(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of
|
|
||||||
// PEM blocks appended one after the other and returns a slice of PublicKey
|
|
||||||
// objects that it finds.
|
|
||||||
func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) {
|
|
||||||
pubKeys := []PublicKey{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
var pemBlock *pem.Block
|
|
||||||
pemBlock, data = pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
break
|
|
||||||
} else if pemBlock.Type != "PUBLIC KEY" {
|
|
||||||
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := pubKeyFromPEMBlock(pemBlock)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKeys = append(pubKeys, pubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKeys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust
|
|
||||||
// PrivateKey or an error if there is a problem with the encoding.
|
|
||||||
func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) {
|
|
||||||
pemBlock, _ := pem.Decode(data)
|
|
||||||
if pemBlock == nil {
|
|
||||||
return nil, errors.New("unable to find PEM encoded data")
|
|
||||||
}
|
|
||||||
|
|
||||||
var key PrivateKey
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case pemBlock.Type == "RSA PRIVATE KEY":
|
|
||||||
rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err)
|
|
||||||
}
|
|
||||||
key = fromRSAPrivateKey(rsaPrivateKey)
|
|
||||||
case pemBlock.Type == "EC PRIVATE KEY":
|
|
||||||
ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err)
|
|
||||||
}
|
|
||||||
key, err = fromECPrivateKey(ecPrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
addPEMHeadersToKey(pemBlock, key.PublicKey())
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic
|
|
||||||
// Public Key to be used with libtrust.
|
|
||||||
func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"decoding JWK Public Key JSON data: %s\n", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Key Type value.
|
|
||||||
kty, err := stringFromMap(jwk, "kty")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK Public Key type: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case kty == "EC":
|
|
||||||
// Call out to unmarshal EC public key.
|
|
||||||
return ecPublicKeyFromMap(jwk)
|
|
||||||
case kty == "RSA":
|
|
||||||
// Call out to unmarshal RSA public key.
|
|
||||||
return rsaPublicKeyFromMap(jwk)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"JWK Public Key type not supported: %q\n", kty,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set
|
|
||||||
// and returns a slice of Public Key objects.
|
|
||||||
func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) {
|
|
||||||
rawKeys, err := loadJSONKeySetRaw(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKeys := make([]PublicKey, 0, len(rawKeys))
|
|
||||||
|
|
||||||
for _, rawKey := range rawKeys {
|
|
||||||
pubKey, err := UnmarshalPublicKeyJWK(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pubKeys = append(pubKeys, pubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKeys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic
|
|
||||||
// Private Key to be used with libtrust.
|
|
||||||
func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"decoding JWK Private Key JSON data: %s\n", err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Key Type value.
|
|
||||||
kty, err := stringFromMap(jwk, "kty")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK Private Key type: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case kty == "EC":
|
|
||||||
// Call out to unmarshal EC private key.
|
|
||||||
return ecPrivateKeyFromMap(jwk)
|
|
||||||
case kty == "RSA":
|
|
||||||
// Call out to unmarshal RSA private key.
|
|
||||||
return rsaPrivateKeyFromMap(jwk)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"JWK Private Key type not supported: %q\n", kty,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
255
vendor/github.com/docker/libtrust/key_files.go
generated
vendored
255
vendor/github.com/docker/libtrust/key_files.go
generated
vendored
|
@ -1,255 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrKeyFileDoesNotExist indicates that the private key file does not exist.
|
|
||||||
ErrKeyFileDoesNotExist = errors.New("key file does not exist")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readKeyFileBytes(filename string) ([]byte, error) {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = ErrKeyFileDoesNotExist
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unable to read key file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Loading and Saving of Public and Private Keys in either PEM or JWK format.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// LoadKeyFile opens the given filename and attempts to read a Private Key
|
|
||||||
// encoded in either PEM or JWK format (if .json or .jwk file extension).
|
|
||||||
func LoadKeyFile(filename string) (PrivateKey, error) {
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var key PrivateKey
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
key, err = UnmarshalPrivateKeyJWK(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode private key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key, err = UnmarshalPrivateKeyPEM(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode private key PEM: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPublicKeyFile opens the given filename and attempts to read a Public Key
|
|
||||||
// encoded in either PEM or JWK format (if .json or .jwk file extension).
|
|
||||||
func LoadPublicKeyFile(filename string) (PublicKey, error) {
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var key PublicKey
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
key, err = UnmarshalPublicKeyJWK(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode public key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
key, err = UnmarshalPublicKeyPEM(contents)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode public key PEM: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveKey saves the given key to a file using the provided filename.
|
|
||||||
// This process will overwrite any existing file at the provided location.
|
|
||||||
func SaveKey(filename string, key PrivateKey) error {
|
|
||||||
var encodedKey []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
// Encode in JSON Web Key format.
|
|
||||||
encodedKey, err = json.MarshalIndent(key, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode private key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Encode in PEM format.
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode private key PEM: %s", err)
|
|
||||||
}
|
|
||||||
encodedKey = pem.EncodeToMemory(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write private key file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SavePublicKey saves the given public key to the file.
|
|
||||||
func SavePublicKey(filename string, key PublicKey) error {
|
|
||||||
var encodedKey []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
// Encode in JSON Web Key format.
|
|
||||||
encodedKey, err = json.MarshalIndent(key, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode public key JWK: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Encode in PEM format.
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode public key PEM: %s", err)
|
|
||||||
}
|
|
||||||
encodedKey = pem.EncodeToMemory(pemBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write public key file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public Key Set files
|
|
||||||
|
|
||||||
type jwkSet struct {
|
|
||||||
Keys []json.RawMessage `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadKeySetFile loads a key set
|
|
||||||
func LoadKeySetFile(filename string) ([]PublicKey, error) {
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
return loadJSONKeySetFile(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be a PEM format file
|
|
||||||
return loadPEMKeySetFile(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) {
|
|
||||||
if len(data) == 0 {
|
|
||||||
// This is okay, just return an empty slice.
|
|
||||||
return []json.RawMessage{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
keySet := jwkSet{}
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &keySet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return keySet.Keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadJSONKeySetFile(filename string) ([]PublicKey, error) {
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil && err != ErrKeyFileDoesNotExist {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return UnmarshalPublicKeyJWKSet(contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadPEMKeySetFile(filename string) ([]PublicKey, error) {
|
|
||||||
data, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil && err != ErrKeyFileDoesNotExist {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return UnmarshalPublicKeyPEMBundle(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddKeySetFile adds a key to a key set
|
|
||||||
func AddKeySetFile(filename string, key PublicKey) error {
|
|
||||||
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
|
|
||||||
return addKeySetJSONFile(filename, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be a PEM format file
|
|
||||||
return addKeySetPEMFile(filename, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKeySetJSONFile(filename string, key PublicKey) error {
|
|
||||||
encodedKey, err := json.Marshal(key)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode trusted client key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
contents, err := readKeyFileBytes(filename)
|
|
||||||
if err != nil && err != ErrKeyFileDoesNotExist {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawEntries, err := loadJSONKeySetRaw(contents)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawEntries = append(rawEntries, json.RawMessage(encodedKey))
|
|
||||||
entriesWrapper := jwkSet{Keys: rawEntries}
|
|
||||||
|
|
||||||
encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encode trusted client keys: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addKeySetPEMFile(filename string, key PublicKey) error {
|
|
||||||
// Encode to PEM, open file for appending, write PEM.
|
|
||||||
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
pemBlock, err := key.PEMBlock()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to encoded trusted key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = file.Write(pem.EncodeToMemory(pemBlock))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to write trusted keys file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
175
vendor/github.com/docker/libtrust/key_manager.go
generated
vendored
175
vendor/github.com/docker/libtrust/key_manager.go
generated
vendored
|
@ -1,175 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClientKeyManager manages client keys on the filesystem
|
|
||||||
type ClientKeyManager struct {
|
|
||||||
key PrivateKey
|
|
||||||
clientFile string
|
|
||||||
clientDir string
|
|
||||||
|
|
||||||
clientLock sync.RWMutex
|
|
||||||
clients []PublicKey
|
|
||||||
|
|
||||||
configLock sync.Mutex
|
|
||||||
configs []*tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientKeyManager loads a new manager from a set of key files
|
|
||||||
// and managed by the given private key.
|
|
||||||
func NewClientKeyManager(trustKey PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) {
|
|
||||||
m := &ClientKeyManager{
|
|
||||||
key: trustKey,
|
|
||||||
clientFile: clientFile,
|
|
||||||
clientDir: clientDir,
|
|
||||||
}
|
|
||||||
if err := m.loadKeys(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// TODO Start watching file and directory
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientKeyManager) loadKeys() (err error) {
|
|
||||||
// Load authorized keys file
|
|
||||||
var clients []PublicKey
|
|
||||||
if c.clientFile != "" {
|
|
||||||
clients, err = LoadKeySetFile(c.clientFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load authorized keys: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add clients from authorized keys directory
|
|
||||||
files, err := ioutil.ReadDir(c.clientDir)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("unable to open authorized keys directory: %s", err)
|
|
||||||
}
|
|
||||||
for _, f := range files {
|
|
||||||
if !f.IsDir() {
|
|
||||||
publicKey, err := LoadPublicKeyFile(path.Join(c.clientDir, f.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to load authorized key file: %s", err)
|
|
||||||
}
|
|
||||||
clients = append(clients, publicKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.clientLock.Lock()
|
|
||||||
c.clients = clients
|
|
||||||
c.clientLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterTLSConfig registers a tls configuration to manager
|
|
||||||
// such that any changes to the keys may be reflected in
|
|
||||||
// the tls client CA pool
|
|
||||||
func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error {
|
|
||||||
c.clientLock.RLock()
|
|
||||||
certPool, err := GenerateCACertPool(c.key, c.clients)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("CA pool generation error: %s", err)
|
|
||||||
}
|
|
||||||
c.clientLock.RUnlock()
|
|
||||||
|
|
||||||
tlsConfig.ClientCAs = certPool
|
|
||||||
|
|
||||||
c.configLock.Lock()
|
|
||||||
c.configs = append(c.configs, tlsConfig)
|
|
||||||
c.configLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for
|
|
||||||
// libtrust identity authentication for the domain specified
|
|
||||||
func NewIdentityAuthTLSConfig(trustKey PrivateKey, clients *ClientKeyManager, addr string, domain string) (*tls.Config, error) {
|
|
||||||
tlsConfig := newTLSConfig()
|
|
||||||
|
|
||||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
if err := clients.RegisterTLSConfig(tlsConfig); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate cert
|
|
||||||
ips, domains, err := parseAddr(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// add domain that it expects clients to use
|
|
||||||
domains = append(domains, domain)
|
|
||||||
x509Cert, err := GenerateSelfSignedServerCert(trustKey, domains, ips)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("certificate generation error: %s", err)
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{{
|
|
||||||
Certificate: [][]byte{x509Cert.Raw},
|
|
||||||
PrivateKey: trustKey.CryptoPrivateKey(),
|
|
||||||
Leaf: x509Cert,
|
|
||||||
}}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCertAuthTLSConfig creates a tls.Config for the server to use for
|
|
||||||
// certificate authentication
|
|
||||||
func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
|
|
||||||
tlsConfig := newTLSConfig()
|
|
||||||
|
|
||||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err)
|
|
||||||
}
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
||||||
|
|
||||||
// Verify client certificates against a CA?
|
|
||||||
if caPath != "" {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
file, err := ioutil.ReadFile(caPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
|
|
||||||
}
|
|
||||||
certPool.AppendCertsFromPEM(file)
|
|
||||||
|
|
||||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
tlsConfig.ClientCAs = certPool
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTLSConfig() *tls.Config {
|
|
||||||
return &tls.Config{
|
|
||||||
NextProtos: []string{"http/1.1"},
|
|
||||||
// Avoid fallback on insecure SSL protocols
|
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseAddr parses an address into an array of IPs and domains
|
|
||||||
func parseAddr(addr string) ([]net.IP, []string, error) {
|
|
||||||
host, _, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
var domains []string
|
|
||||||
var ips []net.IP
|
|
||||||
ip := net.ParseIP(host)
|
|
||||||
if ip != nil {
|
|
||||||
ips = []net.IP{ip}
|
|
||||||
} else {
|
|
||||||
domains = []string{host}
|
|
||||||
}
|
|
||||||
return ips, domains, nil
|
|
||||||
}
|
|
427
vendor/github.com/docker/libtrust/rsa_key.go
generated
vendored
427
vendor/github.com/docker/libtrust/rsa_key.go
generated
vendored
|
@ -1,427 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RSA DSA PUBLIC KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// rsaPublicKey implements a JWK Public Key using RSA digital signature algorithms.
|
|
||||||
type rsaPublicKey struct {
|
|
||||||
*rsa.PublicKey
|
|
||||||
extended map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromRSAPublicKey(cryptoPublicKey *rsa.PublicKey) *rsaPublicKey {
|
|
||||||
return &rsaPublicKey{cryptoPublicKey, map[string]interface{}{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyType returns the JWK key type for RSA keys, i.e., "RSA".
|
|
||||||
func (k *rsaPublicKey) KeyType() string {
|
|
||||||
return "RSA"
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyID returns a distinct identifier which is unique to this Public Key.
|
|
||||||
func (k *rsaPublicKey) KeyID() string {
|
|
||||||
return keyIDFromCryptoKey(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) String() string {
|
|
||||||
return fmt.Sprintf("RSA Public Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifyies the signature of the data in the io.Reader using this Public Key.
|
|
||||||
// The alg parameter should be the name of the JWA digital signature algorithm
|
|
||||||
// which was used to produce the signature and should be supported by this
|
|
||||||
// public key. Returns a nil error if the signature is valid.
|
|
||||||
func (k *rsaPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
|
|
||||||
// Verify the signature of the given date, return non-nil error if valid.
|
|
||||||
sigAlg, err := rsaSignatureAlgorithmByName(alg)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to verify Signature: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasher := sigAlg.HashID().New()
|
|
||||||
_, err = io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
err = rsa.VerifyPKCS1v15(k.PublicKey, sigAlg.HashID(), hash, signature)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid %s signature: %s", sigAlg.HeaderParam(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPublicKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
||||||
return k.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) toMap() map[string]interface{} {
|
|
||||||
jwk := make(map[string]interface{})
|
|
||||||
for k, v := range k.extended {
|
|
||||||
jwk[k] = v
|
|
||||||
}
|
|
||||||
jwk["kty"] = k.KeyType()
|
|
||||||
jwk["kid"] = k.KeyID()
|
|
||||||
jwk["n"] = joseBase64UrlEncode(k.N.Bytes())
|
|
||||||
jwk["e"] = joseBase64UrlEncode(serializeRSAPublicExponentParam(k.E))
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
|
|
||||||
// RSA keys.
|
|
||||||
func (k *rsaPublicKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
|
|
||||||
func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err)
|
|
||||||
}
|
|
||||||
k.extended["kid"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) AddExtendedField(field string, value interface{}) {
|
|
||||||
k.extended[field] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPublicKey) GetExtendedField(field string) interface{} {
|
|
||||||
v, ok := k.extended[field]
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsaPublicKeyFromMap(jwk map[string]interface{}) (*rsaPublicKey, error) {
|
|
||||||
// JWK key type (kty) has already been determined to be "RSA".
|
|
||||||
// Need to extract 'n', 'e', and 'kid' and check for
|
|
||||||
// consistency.
|
|
||||||
|
|
||||||
// Get the modulus parameter N.
|
|
||||||
nB64Url, err := stringFromMap(jwk, "n")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := parseRSAModulusParam(nB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the public exponent E.
|
|
||||||
eB64Url, err := stringFromMap(jwk, "e")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := parseRSAPublicExponentParam(eB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &rsaPublicKey{
|
|
||||||
PublicKey: &rsa.PublicKey{N: n, E: e},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key ID is optional, but if it exists, it should match the key.
|
|
||||||
_, ok := jwk["kid"]
|
|
||||||
if ok {
|
|
||||||
kid, err := stringFromMap(jwk, "kid")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key ID: %s", err)
|
|
||||||
}
|
|
||||||
if kid != key.KeyID() {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key ID does not match: %s", kid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := jwk["d"]; ok {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Public Key cannot contain private exponent")
|
|
||||||
}
|
|
||||||
|
|
||||||
key.extended = jwk
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* RSA DSA PRIVATE KEY
|
|
||||||
*/
|
|
||||||
|
|
||||||
// rsaPrivateKey implements a JWK Private Key using RSA digital signature algorithms.
|
|
||||||
type rsaPrivateKey struct {
|
|
||||||
rsaPublicKey
|
|
||||||
*rsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromRSAPrivateKey(cryptoPrivateKey *rsa.PrivateKey) *rsaPrivateKey {
|
|
||||||
return &rsaPrivateKey{
|
|
||||||
*fromRSAPublicKey(&cryptoPrivateKey.PublicKey),
|
|
||||||
cryptoPrivateKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey returns the Public Key data associated with this Private Key.
|
|
||||||
func (k *rsaPrivateKey) PublicKey() PublicKey {
|
|
||||||
return &k.rsaPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPrivateKey) String() string {
|
|
||||||
return fmt.Sprintf("RSA Private Key <%s>", k.KeyID())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the data read from the io.Reader using a signature algorithm supported
|
|
||||||
// by the RSA private key. If the specified hashing algorithm is supported by
|
|
||||||
// this key, that hash function is used to generate the signature otherwise the
|
|
||||||
// the default hashing algorithm for this key is used. Returns the signature
|
|
||||||
// and the name of the JWK signature algorithm used, e.g., "RS256", "RS384",
|
|
||||||
// "RS512".
|
|
||||||
func (k *rsaPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
|
|
||||||
// Generate a signature of the data using the internal alg.
|
|
||||||
sigAlg := rsaPKCS1v15SignatureAlgorithmForHashID(hashID)
|
|
||||||
hasher := sigAlg.HashID().New()
|
|
||||||
|
|
||||||
_, err = io.Copy(hasher, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
|
|
||||||
}
|
|
||||||
hash := hasher.Sum(nil)
|
|
||||||
|
|
||||||
signature, err = rsa.SignPKCS1v15(rand.Reader, k.PrivateKey, sigAlg.HashID(), hash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("error producing signature: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
alg = sigAlg.HeaderParam()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptoPrivateKey returns the internal object which can be used as a
|
|
||||||
// crypto.PublicKey for use with other standard library operations. The type
|
|
||||||
// is either *rsa.PublicKey or *ecdsa.PublicKey
|
|
||||||
func (k *rsaPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
|
|
||||||
return k.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *rsaPrivateKey) toMap() map[string]interface{} {
|
|
||||||
k.Precompute() // Make sure the precomputed values are stored.
|
|
||||||
jwk := k.rsaPublicKey.toMap()
|
|
||||||
|
|
||||||
jwk["d"] = joseBase64UrlEncode(k.D.Bytes())
|
|
||||||
jwk["p"] = joseBase64UrlEncode(k.Primes[0].Bytes())
|
|
||||||
jwk["q"] = joseBase64UrlEncode(k.Primes[1].Bytes())
|
|
||||||
jwk["dp"] = joseBase64UrlEncode(k.Precomputed.Dp.Bytes())
|
|
||||||
jwk["dq"] = joseBase64UrlEncode(k.Precomputed.Dq.Bytes())
|
|
||||||
jwk["qi"] = joseBase64UrlEncode(k.Precomputed.Qinv.Bytes())
|
|
||||||
|
|
||||||
otherPrimes := k.Primes[2:]
|
|
||||||
|
|
||||||
if len(otherPrimes) > 0 {
|
|
||||||
otherPrimesInfo := make([]interface{}, len(otherPrimes))
|
|
||||||
for i, r := range otherPrimes {
|
|
||||||
otherPrimeInfo := make(map[string]string, 3)
|
|
||||||
otherPrimeInfo["r"] = joseBase64UrlEncode(r.Bytes())
|
|
||||||
crtVal := k.Precomputed.CRTValues[i]
|
|
||||||
otherPrimeInfo["d"] = joseBase64UrlEncode(crtVal.Exp.Bytes())
|
|
||||||
otherPrimeInfo["t"] = joseBase64UrlEncode(crtVal.Coeff.Bytes())
|
|
||||||
otherPrimesInfo[i] = otherPrimeInfo
|
|
||||||
}
|
|
||||||
jwk["oth"] = otherPrimesInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwk
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
|
|
||||||
// RSA keys.
|
|
||||||
func (k *rsaPrivateKey) MarshalJSON() (data []byte, err error) {
|
|
||||||
return json.Marshal(k.toMap())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
|
|
||||||
func (k *rsaPrivateKey) PEMBlock() (*pem.Block, error) {
|
|
||||||
derBytes := x509.MarshalPKCS1PrivateKey(k.PrivateKey)
|
|
||||||
k.extended["keyID"] = k.KeyID() // For display purposes.
|
|
||||||
return createPemBlock("RSA PRIVATE KEY", derBytes, k.extended)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rsaPrivateKeyFromMap(jwk map[string]interface{}) (*rsaPrivateKey, error) {
|
|
||||||
// The JWA spec for RSA Private Keys (draft rfc section 5.3.2) states that
|
|
||||||
// only the private key exponent 'd' is REQUIRED, the others are just for
|
|
||||||
// signature/decryption optimizations and SHOULD be included when the JWK
|
|
||||||
// is produced. We MAY choose to accept a JWK which only includes 'd', but
|
|
||||||
// we're going to go ahead and not choose to accept it without the extra
|
|
||||||
// fields. Only the 'oth' field will be optional (for multi-prime keys).
|
|
||||||
privateExponent, err := parseRSAPrivateKeyParamFromMap(jwk, "d")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key exponent: %s", err)
|
|
||||||
}
|
|
||||||
firstPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "p")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
|
|
||||||
}
|
|
||||||
secondPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "q")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
|
|
||||||
}
|
|
||||||
firstFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dp")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
|
|
||||||
}
|
|
||||||
secondFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dq")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
|
|
||||||
}
|
|
||||||
crtCoeff, err := parseRSAPrivateKeyParamFromMap(jwk, "qi")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var oth interface{}
|
|
||||||
if _, ok := jwk["oth"]; ok {
|
|
||||||
oth = jwk["oth"]
|
|
||||||
delete(jwk, "oth")
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWK key type (kty) has already been determined to be "RSA".
|
|
||||||
// Need to extract the public key information, then extract the private
|
|
||||||
// key values.
|
|
||||||
publicKey, err := rsaPublicKeyFromMap(jwk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey := &rsa.PrivateKey{
|
|
||||||
PublicKey: *publicKey.PublicKey,
|
|
||||||
D: privateExponent,
|
|
||||||
Primes: []*big.Int{firstPrimeFactor, secondPrimeFactor},
|
|
||||||
Precomputed: rsa.PrecomputedValues{
|
|
||||||
Dp: firstFactorCRT,
|
|
||||||
Dq: secondFactorCRT,
|
|
||||||
Qinv: crtCoeff,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if oth != nil {
|
|
||||||
// Should be an array of more JSON objects.
|
|
||||||
otherPrimesInfo, ok := oth.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("JWK RSA Private Key: Invalid other primes info: must be an array")
|
|
||||||
}
|
|
||||||
numOtherPrimeFactors := len(otherPrimesInfo)
|
|
||||||
if numOtherPrimeFactors == 0 {
|
|
||||||
return nil, errors.New("JWK RSA Privake Key: Invalid other primes info: must be absent or non-empty")
|
|
||||||
}
|
|
||||||
otherPrimeFactors := make([]*big.Int, numOtherPrimeFactors)
|
|
||||||
productOfPrimes := new(big.Int).Mul(firstPrimeFactor, secondPrimeFactor)
|
|
||||||
crtValues := make([]rsa.CRTValue, numOtherPrimeFactors)
|
|
||||||
|
|
||||||
for i, val := range otherPrimesInfo {
|
|
||||||
otherPrimeinfo, ok := val.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("JWK RSA Private Key: Invalid other prime info: must be a JSON object")
|
|
||||||
}
|
|
||||||
|
|
||||||
otherPrimeFactor, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "r")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
|
|
||||||
}
|
|
||||||
otherFactorCRT, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "d")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
|
|
||||||
}
|
|
||||||
otherCrtCoeff, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "t")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
crtValue := crtValues[i]
|
|
||||||
crtValue.Exp = otherFactorCRT
|
|
||||||
crtValue.Coeff = otherCrtCoeff
|
|
||||||
crtValue.R = productOfPrimes
|
|
||||||
otherPrimeFactors[i] = otherPrimeFactor
|
|
||||||
productOfPrimes = new(big.Int).Mul(productOfPrimes, otherPrimeFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
privateKey.Primes = append(privateKey.Primes, otherPrimeFactors...)
|
|
||||||
privateKey.Precomputed.CRTValues = crtValues
|
|
||||||
}
|
|
||||||
|
|
||||||
key := &rsaPrivateKey{
|
|
||||||
rsaPublicKey: *publicKey,
|
|
||||||
PrivateKey: privateKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Key Generation Functions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func generateRSAPrivateKey(bits int) (k *rsaPrivateKey, err error) {
|
|
||||||
k = new(rsaPrivateKey)
|
|
||||||
k.PrivateKey, err = rsa.GenerateKey(rand.Reader, bits)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k.rsaPublicKey.PublicKey = &k.PrivateKey.PublicKey
|
|
||||||
k.extended = make(map[string]interface{})
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSA2048PrivateKey generates a key pair using 2048-bit RSA.
|
|
||||||
func GenerateRSA2048PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateRSAPrivateKey(2048)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating RSA 2048-bit key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSA3072PrivateKey generates a key pair using 3072-bit RSA.
|
|
||||||
func GenerateRSA3072PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateRSAPrivateKey(3072)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating RSA 3072-bit key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRSA4096PrivateKey generates a key pair using 4096-bit RSA.
|
|
||||||
func GenerateRSA4096PrivateKey() (PrivateKey, error) {
|
|
||||||
k, err := generateRSAPrivateKey(4096)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating RSA 4096-bit key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
361
vendor/github.com/docker/libtrust/util.go
generated
vendored
361
vendor/github.com/docker/libtrust/util.go
generated
vendored
|
@ -1,361 +0,0 @@
|
||||||
package libtrust
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base32"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadOrCreateTrustKey will load a PrivateKey from the specified path
|
|
||||||
func LoadOrCreateTrustKey(trustKeyPath string) (PrivateKey, error) {
|
|
||||||
if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
trustKey, err := LoadKeyFile(trustKeyPath)
|
|
||||||
if err == ErrKeyFileDoesNotExist {
|
|
||||||
trustKey, err = GenerateECP256PrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := SaveKey(trustKeyPath, trustKey); err != nil {
|
|
||||||
return nil, fmt.Errorf("error saving key file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, file := filepath.Split(trustKeyPath)
|
|
||||||
if err := SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil {
|
|
||||||
return nil, fmt.Errorf("error saving public key file: %s", err)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, fmt.Errorf("error loading key file: %s", err)
|
|
||||||
}
|
|
||||||
return trustKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityAuthTLSClientConfig returns a tls.Config configured to use identity
|
|
||||||
// based authentication from the specified dockerUrl, the rootConfigPath and
|
|
||||||
// the server name to which it is connecting.
|
|
||||||
// If trustUnknownHosts is true it will automatically add the host to the
|
|
||||||
// known-hosts.json in rootConfigPath.
|
|
||||||
func NewIdentityAuthTLSClientConfig(dockerUrl string, trustUnknownHosts bool, rootConfigPath string, serverName string) (*tls.Config, error) {
|
|
||||||
tlsConfig := newTLSConfig()
|
|
||||||
|
|
||||||
trustKeyPath := filepath.Join(rootConfigPath, "key.json")
|
|
||||||
knownHostsPath := filepath.Join(rootConfigPath, "known-hosts.json")
|
|
||||||
|
|
||||||
u, err := url.Parse(dockerUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to parse machine url")
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme == "unix" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := u.Host
|
|
||||||
proto := "tcp"
|
|
||||||
|
|
||||||
trustKey, err := LoadOrCreateTrustKey(trustKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to load trust key: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
knownHosts, err := LoadKeySetFile(knownHostsPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not load trusted hosts file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
allowedHosts, err := FilterByHosts(knownHosts, addr, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error filtering hosts: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certPool, err := GenerateCACertPool(trustKey, allowedHosts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Could not create CA pool: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.ServerName = serverName
|
|
||||||
tlsConfig.RootCAs = certPool
|
|
||||||
|
|
||||||
x509Cert, err := GenerateSelfSignedClientCert(trustKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("certificate generation error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.Certificates = []tls.Certificate{{
|
|
||||||
Certificate: [][]byte{x509Cert.Raw},
|
|
||||||
PrivateKey: trustKey.CryptoPrivateKey(),
|
|
||||||
Leaf: x509Cert,
|
|
||||||
}}
|
|
||||||
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
|
|
||||||
testConn, err := tls.Dial(proto, addr, tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("tls Handshake error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
CurrentTime: time.Now(),
|
|
||||||
DNSName: tlsConfig.ServerName,
|
|
||||||
Intermediates: x509.NewCertPool(),
|
|
||||||
}
|
|
||||||
|
|
||||||
certs := testConn.ConnectionState().PeerCertificates
|
|
||||||
for i, cert := range certs {
|
|
||||||
if i == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
opts.Intermediates.AddCert(cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := certs[0].Verify(opts); err != nil {
|
|
||||||
if _, ok := err.(x509.UnknownAuthorityError); ok {
|
|
||||||
if trustUnknownHosts {
|
|
||||||
pubKey, err := FromCryptoPublicKey(certs[0].PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error extracting public key from cert: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey.AddExtendedField("hosts", []string{addr})
|
|
||||||
|
|
||||||
if err := AddKeySetFile(knownHostsPath, pubKey); err != nil {
|
|
||||||
return nil, fmt.Errorf("error adding machine to known hosts: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("unable to connect. unknown host: %s", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testConn.Close()
|
|
||||||
tlsConfig.InsecureSkipVerify = false
|
|
||||||
|
|
||||||
return tlsConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// joseBase64UrlEncode encodes the given data using the standard base64 url
|
|
||||||
// encoding format but with all trailing '=' characters ommitted in accordance
|
|
||||||
// with the jose specification.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
|
||||||
func joseBase64UrlEncode(b []byte) string {
|
|
||||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
|
||||||
}
|
|
||||||
|
|
||||||
// joseBase64UrlDecode decodes the given string using the standard base64 url
|
|
||||||
// decoder but first adds the appropriate number of trailing '=' characters in
|
|
||||||
// accordance with the jose specification.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
|
|
||||||
func joseBase64UrlDecode(s string) ([]byte, error) {
|
|
||||||
switch len(s) % 4 {
|
|
||||||
case 0:
|
|
||||||
case 2:
|
|
||||||
s += "=="
|
|
||||||
case 3:
|
|
||||||
s += "="
|
|
||||||
default:
|
|
||||||
return nil, errors.New("illegal base64url string")
|
|
||||||
}
|
|
||||||
return base64.URLEncoding.DecodeString(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyIDEncode(b []byte) string {
|
|
||||||
s := strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=")
|
|
||||||
var buf bytes.Buffer
|
|
||||||
var i int
|
|
||||||
for i = 0; i < len(s)/4-1; i++ {
|
|
||||||
start := i * 4
|
|
||||||
end := start + 4
|
|
||||||
buf.WriteString(s[start:end] + ":")
|
|
||||||
}
|
|
||||||
buf.WriteString(s[i*4:])
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyIDFromCryptoKey(pubKey PublicKey) string {
|
|
||||||
// Generate and return a 'libtrust' fingerprint of the public key.
|
|
||||||
// For an RSA key this should be:
|
|
||||||
// SHA256(DER encoded ASN1)
|
|
||||||
// Then truncated to 240 bits and encoded into 12 base32 groups like so:
|
|
||||||
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
|
|
||||||
derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey())
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
hasher := crypto.SHA256.New()
|
|
||||||
hasher.Write(derBytes)
|
|
||||||
return keyIDEncode(hasher.Sum(nil)[:30])
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringFromMap(m map[string]interface{}, key string) (string, error) {
|
|
||||||
val, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%q value not specified", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
str, ok := val.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%q value must be a string", key)
|
|
||||||
}
|
|
||||||
delete(m, key)
|
|
||||||
|
|
||||||
return str, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseECCoordinate(cB64Url string, curve elliptic.Curve) (*big.Int, error) {
|
|
||||||
curveByteLen := (curve.Params().BitSize + 7) >> 3
|
|
||||||
|
|
||||||
cBytes, err := joseBase64UrlDecode(cB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
cByteLength := len(cBytes)
|
|
||||||
if cByteLength != curveByteLen {
|
|
||||||
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", cByteLength, curveByteLen)
|
|
||||||
}
|
|
||||||
return new(big.Int).SetBytes(cBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseECPrivateParam(dB64Url string, curve elliptic.Curve) (*big.Int, error) {
|
|
||||||
dBytes, err := joseBase64UrlDecode(dB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
|
||||||
// octets (where n is the order of the curve). This is because the private
|
|
||||||
// key d must be in the interval [1, n-1] so the bitlength of d should be
|
|
||||||
// no larger than the bitlength of n-1. The easiest way to find the octet
|
|
||||||
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
|
|
||||||
// bit sequence right by 3, which is essentially dividing by 8 and adding
|
|
||||||
// 1 if there is any remainder. Thus, the private key value d should be
|
|
||||||
// output to (bitlength(n-1)+7)>>3 octets.
|
|
||||||
n := curve.Params().N
|
|
||||||
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
|
|
||||||
dByteLength := len(dBytes)
|
|
||||||
|
|
||||||
if dByteLength != octetLength {
|
|
||||||
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", dByteLength, octetLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(big.Int).SetBytes(dBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAModulusParam(nB64Url string) (*big.Int, error) {
|
|
||||||
nBytes, err := joseBase64UrlDecode(nB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(big.Int).SetBytes(nBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeRSAPublicExponentParam(e int) []byte {
|
|
||||||
// We MUST use the minimum number of octets to represent E.
|
|
||||||
// E is supposed to be 65537 for performance and security reasons
|
|
||||||
// and is what golang's rsa package generates, but it might be
|
|
||||||
// different if imported from some other generator.
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(buf, uint32(e))
|
|
||||||
var i int
|
|
||||||
for i = 0; i < 8; i++ {
|
|
||||||
if buf[i] != 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAPublicExponentParam(eB64Url string) (int, error) {
|
|
||||||
eBytes, err := joseBase64UrlDecode(eB64Url)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("invalid base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
// Only the minimum number of bytes were used to represent E, but
|
|
||||||
// binary.BigEndian.Uint32 expects at least 4 bytes, so we need
|
|
||||||
// to add zero padding if necassary.
|
|
||||||
byteLen := len(eBytes)
|
|
||||||
buf := make([]byte, 4-byteLen, 4)
|
|
||||||
eBytes = append(buf, eBytes...)
|
|
||||||
|
|
||||||
return int(binary.BigEndian.Uint32(eBytes)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseRSAPrivateKeyParamFromMap(m map[string]interface{}, key string) (*big.Int, error) {
|
|
||||||
b64Url, err := stringFromMap(m, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
paramBytes, err := joseBase64UrlDecode(b64Url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invaled base64 URL encoding: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(big.Int).SetBytes(paramBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPemBlock(name string, derBytes []byte, headers map[string]interface{}) (*pem.Block, error) {
|
|
||||||
pemBlock := &pem.Block{Type: name, Bytes: derBytes, Headers: map[string]string{}}
|
|
||||||
for k, v := range headers {
|
|
||||||
switch val := v.(type) {
|
|
||||||
case string:
|
|
||||||
pemBlock.Headers[k] = val
|
|
||||||
case []string:
|
|
||||||
if k == "hosts" {
|
|
||||||
pemBlock.Headers[k] = strings.Join(val, ",")
|
|
||||||
} else {
|
|
||||||
// Return error, non-encodable type
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Return error, non-encodable type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pemBlock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) {
|
|
||||||
cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := FromCryptoPublicKey(cryptoPublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addPEMHeadersToKey(pemBlock, pubKey)
|
|
||||||
|
|
||||||
return pubKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) {
|
|
||||||
for key, value := range pemBlock.Headers {
|
|
||||||
var safeVal interface{}
|
|
||||||
if key == "hosts" {
|
|
||||||
safeVal = strings.Split(value, ",")
|
|
||||||
} else {
|
|
||||||
safeVal = value
|
|
||||||
}
|
|
||||||
pubKey.AddExtendedField(key, safeVal)
|
|
||||||
}
|
|
||||||
}
|
|
2
vendor/github.com/go-jose/go-jose/v3/.gitignore
generated
vendored
Normal file
2
vendor/github.com/go-jose/go-jose/v3/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
jose-util/jose-util
|
||||||
|
jose-util.t.err
|
53
vendor/github.com/go-jose/go-jose/v3/.golangci.yml
generated
vendored
Normal file
53
vendor/github.com/go-jose/go-jose/v3/.golangci.yml
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# https://github.com/golangci/golangci-lint
|
||||||
|
|
||||||
|
run:
|
||||||
|
skip-files:
|
||||||
|
- doc_test.go
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- gochecknoglobals
|
||||||
|
- goconst
|
||||||
|
- lll
|
||||||
|
- maligned
|
||||||
|
- nakedret
|
||||||
|
- scopelint
|
||||||
|
- unparam
|
||||||
|
- funlen # added in 1.18 (requires go-jose changes before it can be enabled)
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 35
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- text: "don't use ALL_CAPS in Go names"
|
||||||
|
linters:
|
||||||
|
- golint
|
||||||
|
- text: "hardcoded credentials"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- text: "weak cryptographic primitive"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
- path: json/
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- golint
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- stylecheck
|
||||||
|
- unused
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- scopelint
|
||||||
|
- path: jwk.go
|
||||||
|
linters:
|
||||||
|
- gocyclo
|
33
vendor/github.com/go-jose/go-jose/v3/.travis.yml
generated
vendored
Normal file
33
vendor/github.com/go-jose/go-jose/v3/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
go:
|
||||||
|
- "1.13.x"
|
||||||
|
- "1.14.x"
|
||||||
|
- tip
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- export PATH=$HOME/.local/bin:$PATH
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge
|
||||||
|
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
|
||||||
|
- pip install cram --user
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=profile.cov .
|
||||||
|
- go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner
|
||||||
|
- go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher
|
||||||
|
- go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt
|
||||||
|
- go test -v ./json # no coverage for forked encoding/json package
|
||||||
|
- golangci-lint run
|
||||||
|
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
|
||||||
|
- cd ..
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- gocovmerge *.cov */*.cov > merged.coverprofile
|
||||||
|
- goveralls -coverprofile merged.coverprofile -service=travis-ci
|
10
vendor/github.com/go-jose/go-jose/v3/BUG-BOUNTY.md
generated
vendored
Normal file
10
vendor/github.com/go-jose/go-jose/v3/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>.
|
||||||
|
|
15
vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md
generated
vendored
Normal file
15
vendor/github.com/go-jose/go-jose/v3/CONTRIBUTING.md
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# 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. We use [cla-assistant.io][1] and you
|
||||||
|
will be prompted to sign once a pull request is opened.
|
||||||
|
|
||||||
|
[1]: https://cla-assistant.io/
|
13
vendor/github.com/docker/libtrust/LICENSE → vendor/github.com/go-jose/go-jose/v3/LICENSE
generated
vendored
13
vendor/github.com/docker/libtrust/LICENSE → vendor/github.com/go-jose/go-jose/v3/LICENSE
generated
vendored
|
@ -176,7 +176,18 @@
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
Copyright 2014 Docker, Inc.
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
122
vendor/github.com/go-jose/go-jose/v3/README.md
generated
vendored
Normal file
122
vendor/github.com/go-jose/go-jose/v3/README.md
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# Go JOSE
|
||||||
|
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-jose_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2)
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-jwt_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt)
|
||||||
|
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
|
||||||
|
[![build](https://travis-ci.org/go-jose/go-jose.svg?branch=master)](https://travis-ci.org/go-jose/go-jose)
|
||||||
|
[![coverage](https://coveralls.io/repos/github/go-jose/go-jose/badge.svg?branch=master)](https://coveralls.io/r/go-jose/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) specifications.
|
||||||
|
Tables of supported algorithms are shown below. The library supports both
|
||||||
|
the compact and JWS/JWE JSON Serialization formats, and has optional support for
|
||||||
|
multiple recipients. It also comes with a small command-line utility
|
||||||
|
([`jose-util`](https://github.com/go-jose/go-jose/tree/master/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
|
||||||
|
|
||||||
|
[Version 2](https://gopkg.in/go-jose/go-jose.v2)
|
||||||
|
([branch](https://github.com/go-jose/go-jose/tree/v2),
|
||||||
|
[doc](https://godoc.org/gopkg.in/go-jose/go-jose.v2)) is the current stable version:
|
||||||
|
|
||||||
|
import "gopkg.in/go-jose/go-jose.v2"
|
||||||
|
|
||||||
|
[Version 3](https://github.com/go-jose/go-jose)
|
||||||
|
([branch](https://github.com/go-jose/go-jose/tree/master),
|
||||||
|
[doc](https://godoc.org/github.com/go-jose/go-jose)) is the under development/unstable version (not released yet):
|
||||||
|
|
||||||
|
import "github.com/go-jose/go-jose/v3"
|
||||||
|
|
||||||
|
All new feature development takes place on the `master` branch, which we are
|
||||||
|
preparing to release as version 3 soon. Version 2 will continue to receive
|
||||||
|
critical bug and security fixes. Note that starting with version 3 we are
|
||||||
|
using Go modules for versioning instead of `gopkg.in` as before. Version 3 also will require Go version 1.13 or higher.
|
||||||
|
|
||||||
|
Version 1 (on the `v1` branch) is frozen and not supported anymore.
|
||||||
|
|
||||||
|
### 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/pkg/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/pkg/crypto/ed25519#PrivateKey)
|
||||||
|
AES, HMAC | []byte
|
||||||
|
|
||||||
|
<sup>1. Only available in version 2 or later of the package</sup>
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-jose_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2)
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-jwt_package-blue.svg?style=flat)](https://godoc.org/gopkg.in/go-jose/go-jose.v2/jwt)
|
||||||
|
|
||||||
|
Examples can be found in the Godoc
|
||||||
|
reference for this package. The
|
||||||
|
[`jose-util`](https://github.com/go-jose/go-jose/tree/master/jose-util)
|
||||||
|
subdirectory also contains a small command-line utility which might be useful
|
||||||
|
as an example as well.
|
592
vendor/github.com/go-jose/go-jose/v3/asymmetric.go
generated
vendored
Normal file
592
vendor/github.com/go-jose/go-jose/v3/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/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
josecipher "github.com/go-jose/go-jose/v3/cipher"
|
||||||
|
"github.com/go-jose/go-jose/v3/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("go-jose/go-jose: invalid epk header")
|
||||||
|
}
|
||||||
|
if epk == nil {
|
||||||
|
return nil, errors.New("go-jose/go-jose: missing epk header")
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKey, ok := epk.Key.(*ecdsa.PublicKey)
|
||||||
|
if publicKey == nil || !ok {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid epk header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid public key in epk header")
|
||||||
|
}
|
||||||
|
|
||||||
|
apuData, err := headers.getAPU()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid apu header")
|
||||||
|
}
|
||||||
|
apvData, err := headers.getAPV()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/go-jose: ecdsa signature failed to verify")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
196
vendor/github.com/go-jose/go-jose/v3/cipher/cbc_hmac.go
generated
vendored
Normal file
196
vendor/github.com/go-jose/go-jose/v3/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("go-jose/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("go-jose/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{}, ciphertext[:offset]...)
|
||||||
|
|
||||||
|
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
|
||||||
|
return nil, errors.New("go-jose/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 that 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("go-jose/go-jose: invalid padding")
|
||||||
|
}
|
||||||
|
|
||||||
|
last := buffer[len(buffer)-1]
|
||||||
|
count := int(last)
|
||||||
|
|
||||||
|
if count == 0 || count > blockSize || count > len(buffer) {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid padding")
|
||||||
|
}
|
||||||
|
|
||||||
|
padding := bytes.Repeat([]byte{last}, count)
|
||||||
|
if !bytes.HasSuffix(buffer, padding) {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid padding")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[:len(buffer)-count], nil
|
||||||
|
}
|
75
vendor/github.com/go-jose/go-jose/v3/cipher/concat_kdf.go
generated
vendored
Normal file
75
vendor/github.com/go-jose/go-jose/v3/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/github.com/go-jose/go-jose/v3/cipher/ecdh_es.go
generated
vendored
Normal file
86
vendor/github.com/go-jose/go-jose/v3/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/github.com/go-jose/go-jose/v3/cipher/key_wrap.go
generated
vendored
Normal file
109
vendor/github.com/go-jose/go-jose/v3/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("go-jose/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] ^= 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("go-jose/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] ^= 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("go-jose/go-jose: failed to unwrap key")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]byte, n*8)
|
||||||
|
for i := range r {
|
||||||
|
copy(out[i*8:], r[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
544
vendor/github.com/go-jose/go-jose/v3/crypter.go
generated
vendored
Normal file
544
vendor/github.com/go-jose/go-jose/v3/crypter.go
generated
vendored
Normal file
|
@ -0,0 +1,544 @@
|
||||||
|
/*-
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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 len(rcpts) == 0 {
|
||||||
|
return nil, fmt.Errorf("go-jose/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
|
||||||
|
encrypter.extraHeaders = opts.ExtraHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
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("go-jose/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("go-jose/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("go-jose/go-jose: too many recipients in payload; expecting only one")
|
||||||
|
}
|
||||||
|
|
||||||
|
critical, err := headers.getCritical()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(critical) > 0 {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := tryJWKS(decryptionKey, obj.Header)
|
||||||
|
decrypter, err := newDecrypter(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher := getContentCipher(headers.getEncryption())
|
||||||
|
if cipher == nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/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("go-jose/go-jose: invalid crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(critical) > 0 {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported crit header")
|
||||||
|
}
|
||||||
|
|
||||||
|
key := tryJWKS(decryptionKey, obj.Header)
|
||||||
|
decrypter, err := newDecrypter(key)
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryption := globalHeaders.getEncryption()
|
||||||
|
cipher := getContentCipher(encryption)
|
||||||
|
if cipher == nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("go-jose/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 {
|
||||||
|
return -1, Header{}, nil, ErrCryptoFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "zip" header parameter may only be present in the protected header.
|
||||||
|
if comp := obj.protected.getCompression(); comp != "" {
|
||||||
|
plaintext, _ = decompress(comp, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitized, err := headers.sanitized()
|
||||||
|
if err != nil {
|
||||||
|
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return index, sanitized, plaintext, err
|
||||||
|
}
|
27
vendor/github.com/go-jose/go-jose/v3/doc.go
generated
vendored
Normal file
27
vendor/github.com/go-jose/go-jose/v3/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 JWS/JWE JSON Serialization formats, and has optional support for multiple
|
||||||
|
recipients.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package jose
|
191
vendor/github.com/go-jose/go-jose/v3/encoding.go
generated
vendored
Normal file
191
vendor/github.com/go-jose/go-jose/v3/encoding.go
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/*-
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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/go-jose/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("go-jose/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 := base64URLDecode(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())
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64URLDecode is implemented as defined in https://www.rfc-editor.org/rfc/rfc7515.html#appendix-C
|
||||||
|
func base64URLDecode(value string) ([]byte, error) {
|
||||||
|
value = strings.TrimRight(value, "=")
|
||||||
|
return base64.RawURLEncoding.DecodeString(value)
|
||||||
|
}
|
27
vendor/github.com/go-jose/go-jose/v3/json/LICENSE
generated
vendored
Normal file
27
vendor/github.com/go-jose/go-jose/v3/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/github.com/go-jose/go-jose/v3/json/README.md
generated
vendored
Normal file
13
vendor/github.com/go-jose/go-jose/v3/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.
|
1217
vendor/github.com/go-jose/go-jose/v3/json/decode.go
generated
vendored
Normal file
1217
vendor/github.com/go-jose/go-jose/v3/json/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1197
vendor/github.com/go-jose/go-jose/v3/json/encode.go
generated
vendored
Normal file
1197
vendor/github.com/go-jose/go-jose/v3/json/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
141
vendor/github.com/go-jose/go-jose/v3/json/indent.go
generated
vendored
Normal file
141
vendor/github.com/go-jose/go-jose/v3/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/github.com/go-jose/go-jose/v3/json/scanner.go
generated
vendored
Normal file
623
vendor/github.com/go-jose/go-jose/v3/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
|
||||||
|
}
|
485
vendor/github.com/go-jose/go-jose/v3/json/stream.go
generated
vendored
Normal file
485
vendor/github.com/go-jose/go-jose/v3/json/stream.go
generated
vendored
Normal file
|
@ -0,0 +1,485 @@
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use `SetNumberType` instead
|
||||||
|
// 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.numberType = UnmarshalJSONNumber }
|
||||||
|
|
||||||
|
// SetNumberType causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number, float64 or int64 depending on `t` enum value.
|
||||||
|
func (dec *Decoder) SetNumberType(t NumberUnmarshalType) { dec.d.numberType = t }
|
||||||
|
|
||||||
|
// 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/github.com/go-jose/go-jose/v3/json/tags.go
generated
vendored
Normal file
44
vendor/github.com/go-jose/go-jose/v3/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
|
||||||
|
}
|
295
vendor/github.com/go-jose/go-jose/v3/jwe.go
generated
vendored
Normal file
295
vendor/github.com/go-jose/go-jose/v3/jwe.go
generated
vendored
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
/*-
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case obj.original != nil && obj.original.Protected != nil:
|
||||||
|
protected = obj.original.Protected.base64()
|
||||||
|
case obj.protected != nil:
|
||||||
|
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
||||||
|
default:
|
||||||
|
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 JWE JSON 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("go-jose/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("go-jose/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 := base64URLDecode(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("go-jose/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("go-jose/go-jose: compact JWE format must have five parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawProtected, err := base64URLDecode(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedKey, err := base64URLDecode(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv, err := base64URLDecode(parts[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertext, err := base64URLDecode(parts[3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tag, err := base64URLDecode(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))
|
||||||
|
}
|
798
vendor/github.com/go-jose/go-jose/v3/jwk.go
generated
vendored
Normal file
798
vendor/github.com/go-jose/go-jose/v3/jwk.go
generated
vendored
Normal file
|
@ -0,0 +1,798 @@
|
||||||
|
/*-
|
||||||
|
* 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"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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"`
|
||||||
|
X5u string `json:"x5u,omitempty"`
|
||||||
|
X5tSHA1 string `json:"x5t,omitempty"`
|
||||||
|
X5tSHA256 string `json:"x5t#S256,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONWebKey represents a public or private key in JWK format.
|
||||||
|
type JSONWebKey struct {
|
||||||
|
// Cryptographic key, can be a symmetric or asymmetric key.
|
||||||
|
Key interface{}
|
||||||
|
// Key identifier, parsed from `kid` header.
|
||||||
|
KeyID string
|
||||||
|
// Key algorithm, parsed from `alg` header.
|
||||||
|
Algorithm string
|
||||||
|
// Key use, parsed from `use` header.
|
||||||
|
Use string
|
||||||
|
|
||||||
|
// X.509 certificate chain, parsed from `x5c` header.
|
||||||
|
Certificates []*x509.Certificate
|
||||||
|
// X.509 certificate URL, parsed from `x5u` header.
|
||||||
|
CertificatesURL *url.URL
|
||||||
|
// X.509 certificate thumbprint (SHA-1), parsed from `x5t` header.
|
||||||
|
CertificateThumbprintSHA1 []byte
|
||||||
|
// X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header.
|
||||||
|
CertificateThumbprintSHA256 []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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("go-jose/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))
|
||||||
|
}
|
||||||
|
|
||||||
|
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
|
||||||
|
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
||||||
|
if x5tSHA1Len > 0 {
|
||||||
|
if x5tSHA1Len != sha1.Size {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
|
||||||
|
}
|
||||||
|
raw.X5tSHA1 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA1)
|
||||||
|
}
|
||||||
|
if x5tSHA256Len > 0 {
|
||||||
|
if x5tSHA256Len != sha256.Size {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
|
||||||
|
}
|
||||||
|
raw.X5tSHA256 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA256)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If cert chain is attached (as opposed to being behind a URL), check the
|
||||||
|
// keys thumbprints to make sure they match what is expected. This is to
|
||||||
|
// ensure we don't accidentally produce a JWK with semantically inconsistent
|
||||||
|
// data in the headers.
|
||||||
|
if len(k.Certificates) > 0 {
|
||||||
|
expectedSHA1 := sha1.Sum(k.Certificates[0].Raw)
|
||||||
|
expectedSHA256 := sha256.Sum256(k.Certificates[0].Raw)
|
||||||
|
|
||||||
|
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(k.CertificateThumbprintSHA1, expectedSHA1[:]) {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
|
||||||
|
}
|
||||||
|
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(k.CertificateThumbprintSHA256, expectedSHA256[:]) {
|
||||||
|
return nil, errors.New("go-jose/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.CertificatesURL != nil {
|
||||||
|
raw.X5u = k.CertificatesURL.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, err := parseCertificateChain(raw.X5c)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("go-jose/go-jose: failed to unmarshal x5c field: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var key interface{}
|
||||||
|
var certPub interface{}
|
||||||
|
var keyPub interface{}
|
||||||
|
|
||||||
|
if len(certs) > 0 {
|
||||||
|
// We need to check that leaf public key matches the key embedded in this
|
||||||
|
// JWK, as required by the standard (see RFC 7517, Section 4.7). Otherwise
|
||||||
|
// the JWK parsed could be semantically invalid. Technically, should also
|
||||||
|
// check key usage fields and other extensions on the cert here, but the
|
||||||
|
// standard doesn't exactly explain how they're supposed to map from the
|
||||||
|
// JWK representation to the X.509 extensions.
|
||||||
|
certPub = certs[0].PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
switch raw.Kty {
|
||||||
|
case "EC":
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.ecPrivateKey()
|
||||||
|
if err == nil {
|
||||||
|
keyPub = key.(*ecdsa.PrivateKey).Public()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key, err = raw.ecPublicKey()
|
||||||
|
keyPub = key
|
||||||
|
}
|
||||||
|
case "RSA":
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.rsaPrivateKey()
|
||||||
|
if err == nil {
|
||||||
|
keyPub = key.(*rsa.PrivateKey).Public()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key, err = raw.rsaPublicKey()
|
||||||
|
keyPub = key
|
||||||
|
}
|
||||||
|
case "oct":
|
||||||
|
if certPub != nil {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
|
||||||
|
}
|
||||||
|
key, err = raw.symmetricKey()
|
||||||
|
case "OKP":
|
||||||
|
if raw.Crv == "Ed25519" && raw.X != nil {
|
||||||
|
if raw.D != nil {
|
||||||
|
key, err = raw.edPrivateKey()
|
||||||
|
if err == nil {
|
||||||
|
keyPub = key.(ed25519.PrivateKey).Public()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key, err = raw.edPublicKey()
|
||||||
|
keyPub = key
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if certPub != nil && keyPub != nil {
|
||||||
|
if !reflect.DeepEqual(certPub, keyPub) {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
|
||||||
|
|
||||||
|
if raw.X5u != "" {
|
||||||
|
k.CertificatesURL, err = url.Parse(raw.X5u)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("go-jose/go-jose: invalid JWK, x5u header is invalid URL: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// x5t parameters are base64url-encoded SHA thumbprints
|
||||||
|
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
|
||||||
|
x5tSHA1bytes, err := base64URLDecode(raw.X5tSHA1)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, x5t header has invalid encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
|
||||||
|
// for this reason, after base64 decoding, if the size is sha1.Size it's likely that the value is a byte encoded
|
||||||
|
// checksum so we skip this. Otherwise if the checksum was hex encoded we expect a 40 byte sized array so we'll
|
||||||
|
// try to hex decode it. When Marshalling this value we'll always use a base64 encoded version of byte format checksum.
|
||||||
|
if len(x5tSHA1bytes) == 2*sha1.Size {
|
||||||
|
hx, err := hex.DecodeString(string(x5tSHA1bytes))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
|
||||||
|
|
||||||
|
}
|
||||||
|
x5tSHA1bytes = hx
|
||||||
|
}
|
||||||
|
|
||||||
|
k.CertificateThumbprintSHA1 = x5tSHA1bytes
|
||||||
|
|
||||||
|
x5tSHA256bytes, err := base64URLDecode(raw.X5tSHA256)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(x5tSHA256bytes) == 2*sha256.Size {
|
||||||
|
hx256, err := hex.DecodeString(string(x5tSHA256bytes))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
|
||||||
|
}
|
||||||
|
x5tSHA256bytes = hx256
|
||||||
|
}
|
||||||
|
|
||||||
|
k.CertificateThumbprintSHA256 = x5tSHA256bytes
|
||||||
|
|
||||||
|
x5tSHA1Len := len(k.CertificateThumbprintSHA1)
|
||||||
|
x5tSHA256Len := len(k.CertificateThumbprintSHA256)
|
||||||
|
if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, x5t header is of incorrect size")
|
||||||
|
}
|
||||||
|
if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If certificate chain *and* thumbprints are set, verify correctness.
|
||||||
|
if len(k.Certificates) > 0 {
|
||||||
|
leaf := k.Certificates[0]
|
||||||
|
sha1sum := sha1.Sum(leaf.Raw)
|
||||||
|
sha256sum := sha256.Sum256(leaf.Raw)
|
||||||
|
|
||||||
|
if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
|
||||||
|
return errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("go-jose/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("go-jose/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("go-jose/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 public 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("go-jose/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("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.X == nil || key.Y == nil {
|
||||||
|
return nil, errors.New("go-jose/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("go-jose/go-jose: invalid EC public key, wrong length for x")
|
||||||
|
}
|
||||||
|
|
||||||
|
if curveSize(curve) != len(key.Y.data) {
|
||||||
|
return nil, fmt.Errorf("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if key.X == nil || key.Y == nil || key.D == nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/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("go-jose/go-jose: invalid EC private key, wrong length for x")
|
||||||
|
}
|
||||||
|
|
||||||
|
if curveSize(curve) != len(key.Y.data) {
|
||||||
|
return nil, fmt.Errorf("go-jose/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("go-jose/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("go-jose/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("go-jose/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++
|
||||||
|
}
|
||||||
|
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("go-jose/go-jose: invalid OCT (symmetric) key, missing k value")
|
||||||
|
}
|
||||||
|
return key.K.bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryJWKS(key interface{}, headers ...Header) interface{} {
|
||||||
|
var jwks JSONWebKeySet
|
||||||
|
|
||||||
|
switch jwksType := key.(type) {
|
||||||
|
case *JSONWebKeySet:
|
||||||
|
jwks = *jwksType
|
||||||
|
case JSONWebKeySet:
|
||||||
|
jwks = jwksType
|
||||||
|
default:
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
var kid string
|
||||||
|
for _, header := range headers {
|
||||||
|
if header.KeyID != "" {
|
||||||
|
kid = header.KeyID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if kid == "" {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := jwks.Key(kid)
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys[0].Key
|
||||||
|
}
|
366
vendor/github.com/go-jose/go-jose/v3/jws.go
generated
vendored
Normal file
366
vendor/github.com/go-jose/go-jose/v3/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"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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 JWS JSON 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("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/go-jose: compact JWS format must have three parts")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parts[1] != "" && payload != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawProtected, err := base64URLDecode(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload == nil {
|
||||||
|
payload, err = base64URLDecode(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := base64URLDecode(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))
|
||||||
|
}
|
334
vendor/github.com/go-jose/go-jose/v3/jwt/builder.go
generated
vendored
Normal file
334
vendor/github.com/go-jose/go-jose/v3/jwt/builder.go
generated
vendored
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 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 jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/json"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
|
||||||
|
// errors are accumulated until the final call to CompactSerialize/FullSerialize.
|
||||||
|
type Builder interface {
|
||||||
|
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
|
||||||
|
// into single JSON object. If you are passing private claims, make sure to set
|
||||||
|
// struct field tags to specify the name for the JSON key to be used when
|
||||||
|
// serializing.
|
||||||
|
Claims(i interface{}) Builder
|
||||||
|
// Token builds a JSONWebToken from provided data.
|
||||||
|
Token() (*JSONWebToken, error)
|
||||||
|
// FullSerialize serializes a token using the JWS/JWE JSON Serialization format.
|
||||||
|
FullSerialize() (string, error)
|
||||||
|
// CompactSerialize serializes a token using the compact serialization format.
|
||||||
|
CompactSerialize() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NestedBuilder is a utility for making Signed-Then-Encrypted JSON Web Tokens.
|
||||||
|
// Calls can be chained, and errors are accumulated until final call to
|
||||||
|
// CompactSerialize/FullSerialize.
|
||||||
|
type NestedBuilder interface {
|
||||||
|
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
|
||||||
|
// into single JSON object. If you are passing private claims, make sure to set
|
||||||
|
// struct field tags to specify the name for the JSON key to be used when
|
||||||
|
// serializing.
|
||||||
|
Claims(i interface{}) NestedBuilder
|
||||||
|
// Token builds a NestedJSONWebToken from provided data.
|
||||||
|
Token() (*NestedJSONWebToken, error)
|
||||||
|
// FullSerialize serializes a token using the JSON Serialization format.
|
||||||
|
FullSerialize() (string, error)
|
||||||
|
// CompactSerialize serializes a token using the compact serialization format.
|
||||||
|
CompactSerialize() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type builder struct {
|
||||||
|
payload map[string]interface{}
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type signedBuilder struct {
|
||||||
|
builder
|
||||||
|
sig jose.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
type encryptedBuilder struct {
|
||||||
|
builder
|
||||||
|
enc jose.Encrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
type nestedBuilder struct {
|
||||||
|
builder
|
||||||
|
sig jose.Signer
|
||||||
|
enc jose.Encrypter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signed creates builder for signed tokens.
|
||||||
|
func Signed(sig jose.Signer) Builder {
|
||||||
|
return &signedBuilder{
|
||||||
|
sig: sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypted creates builder for encrypted tokens.
|
||||||
|
func Encrypted(enc jose.Encrypter) Builder {
|
||||||
|
return &encryptedBuilder{
|
||||||
|
enc: enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignedAndEncrypted creates builder for signed-then-encrypted tokens.
|
||||||
|
// ErrInvalidContentType will be returned if encrypter doesn't have JWT content type.
|
||||||
|
func SignedAndEncrypted(sig jose.Signer, enc jose.Encrypter) NestedBuilder {
|
||||||
|
if contentType, _ := enc.Options().ExtraHeaders[jose.HeaderContentType].(jose.ContentType); contentType != "JWT" {
|
||||||
|
return &nestedBuilder{
|
||||||
|
builder: builder{
|
||||||
|
err: ErrInvalidContentType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &nestedBuilder{
|
||||||
|
sig: sig,
|
||||||
|
enc: enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b builder) claims(i interface{}) builder {
|
||||||
|
if b.err != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
m, ok := i.(map[string]interface{})
|
||||||
|
switch {
|
||||||
|
case ok:
|
||||||
|
return b.merge(m)
|
||||||
|
case reflect.Indirect(reflect.ValueOf(i)).Kind() == reflect.Struct:
|
||||||
|
m, err := normalize(i)
|
||||||
|
if err != nil {
|
||||||
|
return builder{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.merge(m)
|
||||||
|
default:
|
||||||
|
return builder{
|
||||||
|
err: ErrInvalidClaims,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize(i interface{}) (map[string]interface{}, error) {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
|
||||||
|
raw, err := json.Marshal(i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := json.NewDecoder(bytes.NewReader(raw))
|
||||||
|
d.SetNumberType(json.UnmarshalJSONNumber)
|
||||||
|
|
||||||
|
if err := d.Decode(&m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *builder) merge(m map[string]interface{}) builder {
|
||||||
|
p := make(map[string]interface{})
|
||||||
|
for k, v := range b.payload {
|
||||||
|
p[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range m {
|
||||||
|
p[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder{
|
||||||
|
payload: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *builder) token(p func(interface{}) ([]byte, error), h []jose.Header) (*JSONWebToken, error) {
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: p,
|
||||||
|
Headers: h,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) Claims(i interface{}) Builder {
|
||||||
|
return &signedBuilder{
|
||||||
|
builder: b.builder.claims(i),
|
||||||
|
sig: b.sig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) Token() (*JSONWebToken, error) {
|
||||||
|
sig, err := b.sign()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := make([]jose.Header, len(sig.Signatures))
|
||||||
|
for i, v := range sig.Signatures {
|
||||||
|
h[i] = v.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.builder.token(sig.Verify, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) CompactSerialize() (string, error) {
|
||||||
|
sig, err := b.sign()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig.CompactSerialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) FullSerialize() (string, error) {
|
||||||
|
sig, err := b.sign()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig.FullSerialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(b.payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.sig.Sign(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) Claims(i interface{}) Builder {
|
||||||
|
return &encryptedBuilder{
|
||||||
|
builder: b.builder.claims(i),
|
||||||
|
enc: b.enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) CompactSerialize() (string, error) {
|
||||||
|
enc, err := b.encrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.CompactSerialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) FullSerialize() (string, error) {
|
||||||
|
enc, err := b.encrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.FullSerialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) Token() (*JSONWebToken, error) {
|
||||||
|
enc, err := b.encrypt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.builder.token(enc.Decrypt, []jose.Header{enc.Header})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *encryptedBuilder) encrypt() (*jose.JSONWebEncryption, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(b.payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.enc.Encrypt(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) Claims(i interface{}) NestedBuilder {
|
||||||
|
return &nestedBuilder{
|
||||||
|
builder: b.builder.claims(i),
|
||||||
|
sig: b.sig,
|
||||||
|
enc: b.enc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
|
||||||
|
enc, err := b.signAndEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NestedJSONWebToken{
|
||||||
|
enc: enc,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) CompactSerialize() (string, error) {
|
||||||
|
enc, err := b.signAndEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.CompactSerialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) FullSerialize() (string, error) {
|
||||||
|
enc, err := b.signAndEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.FullSerialize(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nestedBuilder) signAndEncrypt() (*jose.JSONWebEncryption, error) {
|
||||||
|
if b.err != nil {
|
||||||
|
return nil, b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := json.Marshal(b.payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := b.sig.Sign(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p2, err := sig.CompactSerialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.enc.Encrypt([]byte(p2))
|
||||||
|
}
|
130
vendor/github.com/go-jose/go-jose/v3/jwt/claims.go
generated
vendored
Normal file
130
vendor/github.com/go-jose/go-jose/v3/jwt/claims.go
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 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 jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claims represents public claim values (as specified in RFC 7519).
|
||||||
|
type Claims struct {
|
||||||
|
Issuer string `json:"iss,omitempty"`
|
||||||
|
Subject string `json:"sub,omitempty"`
|
||||||
|
Audience Audience `json:"aud,omitempty"`
|
||||||
|
Expiry *NumericDate `json:"exp,omitempty"`
|
||||||
|
NotBefore *NumericDate `json:"nbf,omitempty"`
|
||||||
|
IssuedAt *NumericDate `json:"iat,omitempty"`
|
||||||
|
ID string `json:"jti,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumericDate represents date and time as the number of seconds since the
|
||||||
|
// epoch, ignoring leap seconds. Non-integer values can be represented
|
||||||
|
// in the serialized format, but we round to the nearest second.
|
||||||
|
// See RFC7519 Section 2: https://tools.ietf.org/html/rfc7519#section-2
|
||||||
|
type NumericDate int64
|
||||||
|
|
||||||
|
// NewNumericDate constructs NumericDate from time.Time value.
|
||||||
|
func NewNumericDate(t time.Time) *NumericDate {
|
||||||
|
if t.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// While RFC 7519 technically states that NumericDate values may be
|
||||||
|
// non-integer values, we don't bother serializing timestamps in
|
||||||
|
// claims with sub-second accurancy and just round to the nearest
|
||||||
|
// second instead. Not convined sub-second accuracy is useful here.
|
||||||
|
out := NumericDate(t.Unix())
|
||||||
|
return &out
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON serializes the given NumericDate into its JSON representation.
|
||||||
|
func (n NumericDate) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(int64(n), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON reads a date from its JSON representation.
|
||||||
|
func (n *NumericDate) UnmarshalJSON(b []byte) error {
|
||||||
|
s := string(b)
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return ErrUnmarshalNumericDate
|
||||||
|
}
|
||||||
|
|
||||||
|
*n = NumericDate(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns time.Time representation of NumericDate.
|
||||||
|
func (n *NumericDate) Time() time.Time {
|
||||||
|
if n == nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return time.Unix(int64(*n), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audience represents the recipients that the token is intended for.
|
||||||
|
type Audience []string
|
||||||
|
|
||||||
|
// UnmarshalJSON reads an audience from its JSON representation.
|
||||||
|
func (s *Audience) UnmarshalJSON(b []byte) error {
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal(b, &v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := v.(type) {
|
||||||
|
case string:
|
||||||
|
*s = []string{v}
|
||||||
|
case []interface{}:
|
||||||
|
a := make([]string, len(v))
|
||||||
|
for i, e := range v {
|
||||||
|
s, ok := e.(string)
|
||||||
|
if !ok {
|
||||||
|
return ErrUnmarshalAudience
|
||||||
|
}
|
||||||
|
a[i] = s
|
||||||
|
}
|
||||||
|
*s = a
|
||||||
|
default:
|
||||||
|
return ErrUnmarshalAudience
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts audience to json representation.
|
||||||
|
func (s Audience) MarshalJSON() ([]byte, error) {
|
||||||
|
if len(s) == 1 {
|
||||||
|
return json.Marshal(s[0])
|
||||||
|
}
|
||||||
|
return json.Marshal([]string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Contains checks whether a given string is included in the Audience
|
||||||
|
func (s Audience) Contains(v string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == v {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
22
vendor/github.com/go-jose/go-jose/v3/jwt/doc.go
generated
vendored
Normal file
22
vendor/github.com/go-jose/go-jose/v3/jwt/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2017 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 jwt provides an implementation of the JSON Web Token standard.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package jwt
|
53
vendor/github.com/go-jose/go-jose/v3/jwt/errors.go
generated
vendored
Normal file
53
vendor/github.com/go-jose/go-jose/v3/jwt/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 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 jwt
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// ErrUnmarshalAudience indicates that aud claim could not be unmarshalled.
|
||||||
|
var ErrUnmarshalAudience = errors.New("go-jose/go-jose/jwt: expected string or array value to unmarshal to Audience")
|
||||||
|
|
||||||
|
// ErrUnmarshalNumericDate indicates that JWT NumericDate could not be unmarshalled.
|
||||||
|
var ErrUnmarshalNumericDate = errors.New("go-jose/go-jose/jwt: expected number value to unmarshal NumericDate")
|
||||||
|
|
||||||
|
// ErrInvalidClaims indicates that given claims have invalid type.
|
||||||
|
var ErrInvalidClaims = errors.New("go-jose/go-jose/jwt: expected claims to be value convertible into JSON object")
|
||||||
|
|
||||||
|
// ErrInvalidIssuer indicates invalid iss claim.
|
||||||
|
var ErrInvalidIssuer = errors.New("go-jose/go-jose/jwt: validation failed, invalid issuer claim (iss)")
|
||||||
|
|
||||||
|
// ErrInvalidSubject indicates invalid sub claim.
|
||||||
|
var ErrInvalidSubject = errors.New("go-jose/go-jose/jwt: validation failed, invalid subject claim (sub)")
|
||||||
|
|
||||||
|
// ErrInvalidAudience indicated invalid aud claim.
|
||||||
|
var ErrInvalidAudience = errors.New("go-jose/go-jose/jwt: validation failed, invalid audience claim (aud)")
|
||||||
|
|
||||||
|
// ErrInvalidID indicates invalid jti claim.
|
||||||
|
var ErrInvalidID = errors.New("go-jose/go-jose/jwt: validation failed, invalid ID claim (jti)")
|
||||||
|
|
||||||
|
// ErrNotValidYet indicates that token is used before time indicated in nbf claim.
|
||||||
|
var ErrNotValidYet = errors.New("go-jose/go-jose/jwt: validation failed, token not valid yet (nbf)")
|
||||||
|
|
||||||
|
// ErrExpired indicates that token is used after expiry time indicated in exp claim.
|
||||||
|
var ErrExpired = errors.New("go-jose/go-jose/jwt: validation failed, token is expired (exp)")
|
||||||
|
|
||||||
|
// ErrIssuedInTheFuture indicates that the iat field is in the future.
|
||||||
|
var ErrIssuedInTheFuture = errors.New("go-jose/go-jose/jwt: validation field, token issued in the future (iat)")
|
||||||
|
|
||||||
|
// ErrInvalidContentType indicates that token requires JWT cty header.
|
||||||
|
var ErrInvalidContentType = errors.New("go-jose/go-jose/jwt: expected content type to be JWT (cty header)")
|
133
vendor/github.com/go-jose/go-jose/v3/jwt/jwt.go
generated
vendored
Normal file
133
vendor/github.com/go-jose/go-jose/v3/jwt/jwt.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 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 jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jose "github.com/go-jose/go-jose/v3"
|
||||||
|
"github.com/go-jose/go-jose/v3/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
|
||||||
|
type JSONWebToken struct {
|
||||||
|
payload func(k interface{}) ([]byte, error)
|
||||||
|
unverifiedPayload func() []byte
|
||||||
|
Headers []jose.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
type NestedJSONWebToken struct {
|
||||||
|
enc *jose.JSONWebEncryption
|
||||||
|
Headers []jose.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims deserializes a JSONWebToken into dest using the provided key.
|
||||||
|
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
|
||||||
|
b, err := t.payload(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range dest {
|
||||||
|
if err := json.Unmarshal(b, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsafeClaimsWithoutVerification deserializes the claims of a
|
||||||
|
// JSONWebToken into the dests. For signed JWTs, the claims are not
|
||||||
|
// verified. This function won't work for encrypted JWTs.
|
||||||
|
func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
|
||||||
|
if t.unverifiedPayload == nil {
|
||||||
|
return fmt.Errorf("go-jose/go-jose: Cannot get unverified claims")
|
||||||
|
}
|
||||||
|
claims := t.unverifiedPayload()
|
||||||
|
for _, d := range dest {
|
||||||
|
if err := json.Unmarshal(claims, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
|
||||||
|
b, err := t.enc.Decrypt(decryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := ParseSigned(string(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSigned parses token from JWS form.
|
||||||
|
func ParseSigned(s string) (*JSONWebToken, error) {
|
||||||
|
sig, err := jose.ParseSigned(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
headers := make([]jose.Header, len(sig.Signatures))
|
||||||
|
for i, signature := range sig.Signatures {
|
||||||
|
headers[i] = signature.Header
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: sig.Verify,
|
||||||
|
unverifiedPayload: sig.UnsafePayloadWithoutVerification,
|
||||||
|
Headers: headers,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseEncrypted parses token from JWE form.
|
||||||
|
func ParseEncrypted(s string) (*JSONWebToken, error) {
|
||||||
|
enc, err := jose.ParseEncrypted(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &JSONWebToken{
|
||||||
|
payload: enc.Decrypt,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
|
||||||
|
func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
|
||||||
|
enc, err := jose.ParseEncrypted(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
|
||||||
|
if strings.ToUpper(contentType) != "JWT" {
|
||||||
|
return nil, ErrInvalidContentType
|
||||||
|
}
|
||||||
|
|
||||||
|
return &NestedJSONWebToken{
|
||||||
|
enc: enc,
|
||||||
|
Headers: []jose.Header{enc.Header},
|
||||||
|
}, nil
|
||||||
|
}
|
120
vendor/github.com/go-jose/go-jose/v3/jwt/validation.go
generated
vendored
Normal file
120
vendor/github.com/go-jose/go-jose/v3/jwt/validation.go
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*-
|
||||||
|
* Copyright 2016 Zbigniew Mandziejewicz
|
||||||
|
* Copyright 2016 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 jwt
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultLeeway defines the default leeway for matching NotBefore/Expiry claims.
|
||||||
|
DefaultLeeway = 1.0 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// Expected defines values used for protected claims validation.
|
||||||
|
// If field has zero value then validation is skipped, with the exception of
|
||||||
|
// Time, where the zero value means "now." To skip validating them, set the
|
||||||
|
// corresponding field in the Claims struct to nil.
|
||||||
|
type Expected struct {
|
||||||
|
// Issuer matches the "iss" claim exactly.
|
||||||
|
Issuer string
|
||||||
|
// Subject matches the "sub" claim exactly.
|
||||||
|
Subject string
|
||||||
|
// Audience matches the values in "aud" claim, regardless of their order.
|
||||||
|
Audience Audience
|
||||||
|
// ID matches the "jti" claim exactly.
|
||||||
|
ID string
|
||||||
|
// Time matches the "exp", "nbf" and "iat" claims with leeway.
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTime copies expectations with new time.
|
||||||
|
func (e Expected) WithTime(t time.Time) Expected {
|
||||||
|
e.Time = t
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks claims in a token against expected values.
|
||||||
|
// A default leeway value of one minute is used to compare time values.
|
||||||
|
//
|
||||||
|
// The default leeway will cause the token to be deemed valid until one
|
||||||
|
// minute after the expiration time. If you're a server application that
|
||||||
|
// wants to give an extra minute to client tokens, use this
|
||||||
|
// function. If you're a client application wondering if the server
|
||||||
|
// will accept your token, use ValidateWithLeeway with a leeway <=0,
|
||||||
|
// otherwise this function might make you think a token is valid when
|
||||||
|
// it is not.
|
||||||
|
func (c Claims) Validate(e Expected) error {
|
||||||
|
return c.ValidateWithLeeway(e, DefaultLeeway)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWithLeeway checks claims in a token against expected values. A
|
||||||
|
// custom leeway may be specified for comparing time values. You may pass a
|
||||||
|
// zero value to check time values with no leeway, but you should note that
|
||||||
|
// numeric date values are rounded to the nearest second and sub-second
|
||||||
|
// precision is not supported.
|
||||||
|
//
|
||||||
|
// The leeway gives some extra time to the token from the server's
|
||||||
|
// point of view. That is, if the token is expired, ValidateWithLeeway
|
||||||
|
// will still accept the token for 'leeway' amount of time. This fails
|
||||||
|
// if you're using this function to check if a server will accept your
|
||||||
|
// token, because it will think the token is valid even after it
|
||||||
|
// expires. So if you're a client validating if the token is valid to
|
||||||
|
// be submitted to a server, use leeway <=0, if you're a server
|
||||||
|
// validation a token, use leeway >=0.
|
||||||
|
func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
|
||||||
|
if e.Issuer != "" && e.Issuer != c.Issuer {
|
||||||
|
return ErrInvalidIssuer
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Subject != "" && e.Subject != c.Subject {
|
||||||
|
return ErrInvalidSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.ID != "" && e.ID != c.ID {
|
||||||
|
return ErrInvalidID
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(e.Audience) != 0 {
|
||||||
|
for _, v := range e.Audience {
|
||||||
|
if !c.Audience.Contains(v) {
|
||||||
|
return ErrInvalidAudience
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate using the e.Time, or time.Now if not provided
|
||||||
|
validationTime := e.Time
|
||||||
|
if validationTime.IsZero() {
|
||||||
|
validationTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.NotBefore != nil && validationTime.Add(leeway).Before(c.NotBefore.Time()) {
|
||||||
|
return ErrNotValidYet
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Expiry != nil && validationTime.Add(-leeway).After(c.Expiry.Time()) {
|
||||||
|
return ErrExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssuedAt is optional but cannot be in the future. This is not required by the RFC, but
|
||||||
|
// something is misconfigured if this happens and we should not trust it.
|
||||||
|
if c.IssuedAt != nil && validationTime.Add(leeway).Before(c.IssuedAt.Time()) {
|
||||||
|
return ErrIssuedInTheFuture
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
144
vendor/github.com/go-jose/go-jose/v3/opaque.go
generated
vendored
Normal file
144
vendor/github.com/go-jose/go-jose/v3/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 performed by implementers 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/github.com/go-jose/go-jose/v3/shared.go
generated
vendored
Normal file
520
vendor/github.com/go-jose/go-jose/v3/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"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/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("go-jose/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 = "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 unmarshalled
|
||||||
|
// 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("go-jose/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" from the raw JSON.
|
||||||
|
func (parsed rawHeader) getIV() (*byteBuffer, error) {
|
||||||
|
return parsed.getByteBuffer(headerIV)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTag extracts parsed "tag" from 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 (parsed rawHeader) isSet(k HeaderKey) bool {
|
||||||
|
dvr := parsed[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 (parsed rawHeader) merge(src *rawHeader) {
|
||||||
|
if src == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range *src {
|
||||||
|
if parsed.isSet(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed[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("go-jose/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
|
||||||
|
}
|
450
vendor/github.com/go-jose/go-jose/v3/signing.go
generated
vendored
Normal file
450
vendor/github.com/go-jose/go-jose/v3/signing.go
generated
vendored
Normal file
|
@ -0,0 +1,450 @@
|
||||||
|
/*-
|
||||||
|
* 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/ed25519"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-jose/go-jose/v3/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("go-jose/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/go-jose/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("go-jose/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("go-jose/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("go-jose/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 {
|
||||||
|
key := tryJWKS(verificationKey, obj.headers()...)
|
||||||
|
verifier, err := newVerifier(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(obj.Signatures) > 1 {
|
||||||
|
return errors.New("go-jose/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) {
|
||||||
|
key := tryJWKS(verificationKey, obj.headers()...)
|
||||||
|
verifier, err := newVerifier(key)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj JSONWebSignature) headers() []Header {
|
||||||
|
headers := make([]Header, len(obj.Signatures))
|
||||||
|
for i, sig := range obj.Signatures {
|
||||||
|
headers[i] = sig.Header
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
495
vendor/github.com/go-jose/go-jose/v3/symmetric.go
generated
vendored
Normal file
495
vendor/github.com/go-jose/go-jose/v3/symmetric.go
generated
vendored
Normal file
|
@ -0,0 +1,495 @@
|
||||||
|
/*-
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
josecipher "github.com/go-jose/go-jose/v3/cipher"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandReader is a cryptographically secure random number generator (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{}
|
||||||
|
|
||||||
|
if err = header.set(headerIV, newBuffer(parts.iv)); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = header.set(headerTag, newBuffer(parts.tag)); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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{}
|
||||||
|
|
||||||
|
if err = header.set(headerP2C, ctx.p2c); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = header.set(headerP2S, newBuffer(ctx.p2s)); err != nil {
|
||||||
|
return recipientInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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("go-jose/go-jose: invalid IV: %v", err)
|
||||||
|
}
|
||||||
|
tag, err := headers.getTag()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/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("go-jose/go-jose: invalid P2S: %v", err)
|
||||||
|
}
|
||||||
|
if p2s == nil || len(p2s.data) == 0 {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: must be present")
|
||||||
|
}
|
||||||
|
|
||||||
|
p2c, err := headers.getP2C()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("go-jose/go-jose: invalid P2C: %v", err)
|
||||||
|
}
|
||||||
|
if p2c <= 0 {
|
||||||
|
return nil, fmt.Errorf("go-jose/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("go-jose/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("go-jose/go-jose: failed to compute hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mac) != len(expected) {
|
||||||
|
return errors.New("go-jose/go-jose: invalid hmac")
|
||||||
|
}
|
||||||
|
|
||||||
|
match := subtle.ConstantTimeCompare(mac, expected)
|
||||||
|
if match != 1 {
|
||||||
|
return errors.New("go-jose/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
|
||||||
|
}
|
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]
|
||||||
|
}
|
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
|
@ -172,12 +172,15 @@ github.com/docker/go-events
|
||||||
# github.com/docker/go-metrics v0.0.1
|
# github.com/docker/go-metrics v0.0.1
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/docker/go-metrics
|
github.com/docker/go-metrics
|
||||||
# github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1
|
|
||||||
## explicit
|
|
||||||
github.com/docker/libtrust
|
|
||||||
# github.com/felixge/httpsnoop v1.0.1
|
# github.com/felixge/httpsnoop v1.0.1
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/felixge/httpsnoop
|
github.com/felixge/httpsnoop
|
||||||
|
# github.com/go-jose/go-jose/v3 v3.0.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
github.com/go-jose/go-jose/v3
|
||||||
|
github.com/go-jose/go-jose/v3/cipher
|
||||||
|
github.com/go-jose/go-jose/v3/json
|
||||||
|
github.com/go-jose/go-jose/v3/jwt
|
||||||
# github.com/go-logr/logr v1.2.4
|
# github.com/go-logr/logr v1.2.4
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/go-logr/logr
|
github.com/go-logr/logr
|
||||||
|
@ -363,6 +366,7 @@ 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/pbkdf2
|
||||||
golang.org/x/crypto/pkcs12
|
golang.org/x/crypto/pkcs12
|
||||||
golang.org/x/crypto/pkcs12/internal/rc2
|
golang.org/x/crypto/pkcs12/internal/rc2
|
||||||
# golang.org/x/net v0.17.0
|
# golang.org/x/net v0.17.0
|
||||||
|
|
Loading…
Reference in a new issue