From 3129187dc275290b538586ab595f92ff878df118 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 4 Jan 2017 16:44:03 -0500 Subject: [PATCH] JWT wrapping tokens (#2172) --- audit/format.go | 35 +- audit/format_json_test.go | 6 +- audit/format_jsonx_test.go | 6 +- audit/hashstructure.go | 2 +- audit/hashstructure_test.go | 10 +- command/unwrap.go | 7 - http/handler.go | 39 +- http/logical.go | 2 +- logical/request.go | 17 +- logical/response.go | 9 +- vault/cluster.go | 13 +- vault/core.go | 33 +- vault/logical_system.go | 52 +- vault/request_handling.go | 187 ++----- vault/request_handling_test.go | 8 +- vault/router.go | 12 +- vault/wrapping.go | 309 +++++++++++ vendor/github.com/SermoDigital/jose/LICENSE | 22 + vendor/github.com/SermoDigital/jose/README.md | 40 ++ vendor/github.com/SermoDigital/jose/_test.sh | 8 + vendor/github.com/SermoDigital/jose/base64.go | 44 ++ .../SermoDigital/jose/crypto/doc.go | 4 + .../SermoDigital/jose/crypto/ecdsa.go | 117 +++++ .../SermoDigital/jose/crypto/ecdsa_utils.go | 48 ++ .../SermoDigital/jose/crypto/errors.go | 9 + .../SermoDigital/jose/crypto/hmac.go | 81 +++ .../SermoDigital/jose/crypto/none.go | 72 +++ .../SermoDigital/jose/crypto/rsa.go | 80 +++ .../SermoDigital/jose/crypto/rsa_pss.go | 96 ++++ .../SermoDigital/jose/crypto/rsa_utils.go | 70 +++ .../SermoDigital/jose/crypto/signature.go | 36 ++ .../jose/crypto/signing_method.go | 24 + vendor/github.com/SermoDigital/jose/doc.go | 3 + vendor/github.com/SermoDigital/jose/header.go | 124 +++++ .../SermoDigital/jose/jws/claims.go | 190 +++++++ .../github.com/SermoDigital/jose/jws/doc.go | 2 + .../SermoDigital/jose/jws/errors.go | 62 +++ .../github.com/SermoDigital/jose/jws/jws.go | 490 ++++++++++++++++++ .../SermoDigital/jose/jws/jws_serialize.go | 132 +++++ .../SermoDigital/jose/jws/jws_validate.go | 203 ++++++++ .../github.com/SermoDigital/jose/jws/jwt.go | 115 ++++ .../SermoDigital/jose/jws/payload.go | 52 ++ .../SermoDigital/jose/jws/rawbase64.go | 28 + .../SermoDigital/jose/jws/signing_methods.go | 63 +++ .../SermoDigital/jose/jwt/claims.go | 274 ++++++++++ .../github.com/SermoDigital/jose/jwt/doc.go | 2 + vendor/github.com/SermoDigital/jose/jwt/eq.go | 47 ++ .../SermoDigital/jose/jwt/errors.go | 28 + .../github.com/SermoDigital/jose/jwt/jwt.go | 144 +++++ vendor/github.com/SermoDigital/jose/time.go | 6 + vendor/vendor.json | 24 + 51 files changed, 3247 insertions(+), 240 deletions(-) create mode 100644 vault/wrapping.go create mode 100644 vendor/github.com/SermoDigital/jose/LICENSE create mode 100644 vendor/github.com/SermoDigital/jose/README.md create mode 100755 vendor/github.com/SermoDigital/jose/_test.sh create mode 100644 vendor/github.com/SermoDigital/jose/base64.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/doc.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/ecdsa.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/ecdsa_utils.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/errors.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/hmac.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/none.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/rsa.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/rsa_pss.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/rsa_utils.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/signature.go create mode 100644 vendor/github.com/SermoDigital/jose/crypto/signing_method.go create mode 100644 vendor/github.com/SermoDigital/jose/doc.go create mode 100644 vendor/github.com/SermoDigital/jose/header.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/claims.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/doc.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/errors.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/jws.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/jws_serialize.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/jws_validate.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/jwt.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/payload.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/rawbase64.go create mode 100644 vendor/github.com/SermoDigital/jose/jws/signing_methods.go create mode 100644 vendor/github.com/SermoDigital/jose/jwt/claims.go create mode 100644 vendor/github.com/SermoDigital/jose/jwt/doc.go create mode 100644 vendor/github.com/SermoDigital/jose/jwt/eq.go create mode 100644 vendor/github.com/SermoDigital/jose/jwt/errors.go create mode 100644 vendor/github.com/SermoDigital/jose/jwt/jwt.go create mode 100644 vendor/github.com/SermoDigital/jose/time.go diff --git a/audit/format.go b/audit/format.go index d2171642c..c1d0126ed 100644 --- a/audit/format.go +++ b/audit/format.go @@ -87,6 +87,14 @@ func (f *AuditFormatter) FormatRequest( errString = err.Error() } + var reqWrapInfo *AuditRequestWrapInfo + if req.WrapInfo != nil { + reqWrapInfo = &AuditRequestWrapInfo{ + TTL: int(req.WrapInfo.TTL / time.Second), + Format: req.WrapInfo.Format, + } + } + reqEntry := &AuditRequestEntry{ Type: "request", Error: errString, @@ -105,7 +113,7 @@ func (f *AuditFormatter) FormatRequest( Path: req.Path, Data: req.Data, RemoteAddr: getRemoteAddr(req), - WrapTTL: int(req.WrapTTL / time.Second), + WrapInfo: reqWrapInfo, }, } @@ -238,9 +246,17 @@ func (f *AuditFormatter) FormatResponse( } } - var respWrapInfo *AuditWrapInfo + var reqWrapInfo *AuditRequestWrapInfo + if req.WrapInfo != nil { + reqWrapInfo = &AuditRequestWrapInfo{ + TTL: int(req.WrapInfo.TTL / time.Second), + Format: req.WrapInfo.Format, + } + } + + var respWrapInfo *AuditResponseWrapInfo if resp.WrapInfo != nil { - respWrapInfo = &AuditWrapInfo{ + respWrapInfo = &AuditResponseWrapInfo{ TTL: int(resp.WrapInfo.TTL / time.Second), Token: resp.WrapInfo.Token, CreationTime: resp.WrapInfo.CreationTime.Format(time.RFC3339Nano), @@ -266,7 +282,7 @@ func (f *AuditFormatter) FormatResponse( Path: req.Path, Data: req.Data, RemoteAddr: getRemoteAddr(req), - WrapTTL: int(req.WrapTTL / time.Second), + WrapInfo: reqWrapInfo, }, Response: AuditResponse{ @@ -312,7 +328,7 @@ type AuditRequest struct { Path string `json:"path"` Data map[string]interface{} `json:"data"` RemoteAddr string `json:"remote_address"` - WrapTTL int `json:"wrap_ttl"` + WrapInfo *AuditRequestWrapInfo `json:"wrap_info,omitempty"` } type AuditResponse struct { @@ -320,7 +336,7 @@ type AuditResponse struct { Secret *AuditSecret `json:"secret,omitempty"` Data map[string]interface{} `json:"data,omitempty"` Redirect string `json:"redirect,omitempty"` - WrapInfo *AuditWrapInfo `json:"wrap_info,omitempty"` + WrapInfo *AuditResponseWrapInfo `json:"wrap_info,omitempty"` } type AuditAuth struct { @@ -335,7 +351,12 @@ type AuditSecret struct { LeaseID string `json:"lease_id"` } -type AuditWrapInfo struct { +type AuditRequestWrapInfo struct { + TTL int `json:"ttl"` + Format string `json:"format"` +} + +type AuditResponseWrapInfo struct { TTL int `json:"ttl"` Token string `json:"token"` CreationTime string `json:"creation_time"` diff --git a/audit/format_json_test.go b/audit/format_json_test.go index c4cb2da54..5b2b09848 100644 --- a/audit/format_json_test.go +++ b/audit/format_json_test.go @@ -29,7 +29,9 @@ func TestFormatJSON_formatRequest(t *testing.T) { Connection: &logical.Connection{ RemoteAddr: "127.0.0.1", }, - WrapTTL: 60 * time.Second, + WrapInfo: &logical.RequestWrapInfo{ + TTL: 60 * time.Second, + }, }, errors.New("this is an error"), testFormatJSONReqBasicStr, @@ -74,5 +76,5 @@ func TestFormatJSON_formatRequest(t *testing.T) { } } -const testFormatJSONReqBasicStr = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"display_name":"","policies":["root"],"metadata":null},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1"},"error":"this is an error"} +const testFormatJSONReqBasicStr = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"display_name":"","policies":["root"],"metadata":null},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1","wrap_info":{"ttl":60,"format":""}},"error":"this is an error"} ` diff --git a/audit/format_jsonx_test.go b/audit/format_jsonx_test.go index 16515a84f..1324b534a 100644 --- a/audit/format_jsonx_test.go +++ b/audit/format_jsonx_test.go @@ -28,11 +28,13 @@ func TestFormatJSONx_formatRequest(t *testing.T) { Connection: &logical.Connection{ RemoteAddr: "127.0.0.1", }, - WrapTTL: 60 * time.Second, + WrapInfo: &logical.RequestWrapInfo{ + TTL: 60 * time.Second, + }, }, errors.New("this is an error"), "", - `rootthis is an errorupdate/foo127.0.0.160request`, + `rootthis is an errorupdate/foo127.0.0.160request`, }, } diff --git a/audit/hashstructure.go b/audit/hashstructure.go index ff51f5ea1..8d0fd7c6c 100644 --- a/audit/hashstructure.go +++ b/audit/hashstructure.go @@ -84,7 +84,7 @@ func Hash(salter *salt.Salt, raw interface{}) error { s.Data = data.(map[string]interface{}) - case *logical.WrapInfo: + case *logical.ResponseWrapInfo: if s == nil { return nil } diff --git a/audit/hashstructure_test.go b/audit/hashstructure_test.go index ab33acca2..5fefa0fa9 100644 --- a/audit/hashstructure_test.go +++ b/audit/hashstructure_test.go @@ -44,7 +44,9 @@ func TestCopy_request(t *testing.T) { Data: map[string]interface{}{ "foo": "bar", }, - WrapTTL: 60 * time.Second, + WrapInfo: &logical.RequestWrapInfo{ + TTL: 60 * time.Second, + }, } arg := expected @@ -67,7 +69,7 @@ func TestCopy_response(t *testing.T) { Data: map[string]interface{}{ "foo": "bar", }, - WrapInfo: &logical.WrapInfo{ + WrapInfo: &logical.ResponseWrapInfo{ TTL: 60, Token: "foo", CreationTime: time.Now(), @@ -138,7 +140,7 @@ func TestHash(t *testing.T) { Data: map[string]interface{}{ "foo": "bar", }, - WrapInfo: &logical.WrapInfo{ + WrapInfo: &logical.ResponseWrapInfo{ TTL: 60, Token: "bar", CreationTime: now, @@ -149,7 +151,7 @@ func TestHash(t *testing.T) { Data: map[string]interface{}{ "foo": "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", }, - WrapInfo: &logical.WrapInfo{ + WrapInfo: &logical.ResponseWrapInfo{ TTL: 60, Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", CreationTime: now, diff --git a/command/unwrap.go b/command/unwrap.go index e4bfaf497..5194028be 100644 --- a/command/unwrap.go +++ b/command/unwrap.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" - "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/meta" ) @@ -37,12 +36,6 @@ func (c *UnwrapCommand) Run(args []string) int { case 0: case 1: tokenID = args[0] - _, err = uuid.ParseUUID(tokenID) - if err != nil { - c.Ui.Error(fmt.Sprintf( - "Given token could not be parsed as a UUID: %v", err)) - return 1 - } default: c.Ui.Error("Unwrap expects zero or one argument (the ID of the wrapping token)") flags.Usage() diff --git a/http/handler.go b/http/handler.go index af77ddda9..8a83af658 100644 --- a/http/handler.go +++ b/http/handler.go @@ -19,10 +19,14 @@ const ( // AuthHeaderName is the name of the header containing the token. AuthHeaderName = "X-Vault-Token" - // WrapHeaderName is the name of the header containing a directive to wrap the - // response. + // WrapTTLHeaderName is the name of the header containing a directive to + // wrap the response WrapTTLHeaderName = "X-Vault-Wrap-TTL" + // WrapFormatHeaderName is the name of the header containing the format to + // wrap in; has no effect if the wrap TTL is not set + WrapFormatHeaderName = "X-Vault-Wrap-Format" + // NoRequestForwardingHeaderName is the name of the header telling Vault // not to use request forwarding NoRequestForwardingHeaderName = "X-Vault-No-Request-Forwarding" @@ -91,20 +95,7 @@ func wrappingVerificationFunc(core *vault.Core, req *logical.Request) error { return fmt.Errorf("invalid request") } - var token string - if req.Data != nil && req.Data["token"] != nil { - if tokenStr, ok := req.Data["token"].(string); !ok { - return fmt.Errorf("could not decode token in request body") - } else if tokenStr == "" { - return fmt.Errorf("empty token in request body") - } else { - token = tokenStr - } - } else { - token = req.ClientToken - } - - valid, err := core.ValidateWrappingToken(token) + valid, err := core.ValidateWrappingToken(req) if err != nil { return fmt.Errorf("error validating wrapping token: %v", err) } @@ -288,9 +279,8 @@ func requestAuth(core *vault.Core, r *http.Request, req *logical.Request) *logic return req } -// requestWrapTTL adds the WrapTTL value to the logical.Request if it -// exists. -func requestWrapTTL(r *http.Request, req *logical.Request) (*logical.Request, error) { +// requestWrapInfo adds the WrapInfo value to the logical.Request if wrap info exists +func requestWrapInfo(r *http.Request, req *logical.Request) (*logical.Request, error) { // First try for the header value wrapTTL := r.Header.Get(WrapTTLHeaderName) if wrapTTL == "" { @@ -305,7 +295,16 @@ func requestWrapTTL(r *http.Request, req *logical.Request) (*logical.Request, er if int64(dur) < 0 { return req, fmt.Errorf("requested wrap ttl cannot be negative") } - req.WrapTTL = dur + + req.WrapInfo = &logical.RequestWrapInfo{ + TTL: dur, + } + + wrapFormat := r.Header.Get(WrapFormatHeaderName) + switch wrapFormat { + case "jwt": + req.WrapInfo.Format = "jwt" + } return req, nil } diff --git a/http/logical.go b/http/logical.go index 9d83614ca..b3dcbc516 100644 --- a/http/logical.go +++ b/http/logical.go @@ -80,7 +80,7 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques Connection: getConnection(r), }) - req, err = requestWrapTTL(r, req) + req, err = requestWrapInfo(r, req) if err != nil { return nil, http.StatusBadRequest, errwrap.Wrapf("error parsing X-Vault-Wrap-TTL header: {{err}}", err) } diff --git a/logical/request.go b/logical/request.go index e6f7a3247..4420d73b4 100644 --- a/logical/request.go +++ b/logical/request.go @@ -6,6 +6,18 @@ import ( "time" ) +// RequestWrapInfo is a struct that stores information about desired response +// wrapping behavior +type RequestWrapInfo struct { + // Setting to non-zero specifies that the response should be wrapped. + // Specifies the desired TTL of the wrapping token. + TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"` + + // The format to use for the wrapped response; if not specified it's a bare + // token + Format string `json:"format" structs:"format" mapstructure:"format"` +} + // Request is a struct that stores the parameters and context // of a request being made to Vault. It is used to abstract // the details of the higher level request protocol from the handlers. @@ -61,9 +73,8 @@ type Request struct { // request path with the MountPoint trimmed off. MountPoint string `json:"mount_point" structs:"mount_point" mapstructure:"mount_point"` - // WrapTTL contains the requested TTL of the token used to wrap the - // response in a cubbyhole. - WrapTTL time.Duration `json:"wrap_ttl" struct:"wrap_ttl" mapstructure:"wrap_ttl"` + // WrapInfo contains requested response wrapping parameters + WrapInfo *RequestWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"` } // Get returns a data field and guards for nil Data diff --git a/logical/response.go b/logical/response.go index 059ebc7c0..e502aa607 100644 --- a/logical/response.go +++ b/logical/response.go @@ -28,7 +28,7 @@ const ( HTTPStatusCode = "http_status_code" ) -type WrapInfo struct { +type ResponseWrapInfo struct { // Setting to non-zero specifies that the response should be wrapped. // Specifies the desired TTL of the wrapping token. TTL time.Duration `json:"ttl" structs:"ttl" mapstructure:"ttl"` @@ -43,6 +43,9 @@ type WrapInfo struct { // If the contained response is the output of a token creation call, the // created token's accessor will be accessible here WrappedAccessor string `json:"wrapped_accessor" structs:"wrapped_accessor" mapstructure:"wrapped_accessor"` + + // The format to use. This doesn't get returned, it's only internal. + Format string `json:"format" structs:"format" mapstructure:"format"` } // Response is a struct that stores the response of a request. @@ -75,7 +78,7 @@ type Response struct { warnings []string `json:"warnings" structs:"warnings" mapstructure:"warnings"` // Information for wrapping the response in a cubbyhole - WrapInfo *WrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"` + WrapInfo *ResponseWrapInfo `json:"wrap_info" structs:"wrap_info" mapstructure:"wrap_info"` } func init() { @@ -120,7 +123,7 @@ func init() { if err != nil { return nil, fmt.Errorf("error copying WrapInfo: %v", err) } - ret.WrapInfo = retWrapInfo.(*WrapInfo) + ret.WrapInfo = retWrapInfo.(*ResponseWrapInfo) } return &ret, nil diff --git a/vault/cluster.go b/vault/cluster.go index 798787277..69c171fbf 100644 --- a/vault/cluster.go +++ b/vault/cluster.go @@ -29,7 +29,8 @@ const ( // Storage path where the local cluster name and identifier are stored coreLocalClusterInfoPath = "core/cluster/local/info" - corePrivateKeyTypeP521 = "p521" + corePrivateKeyTypeP521 = "p521" + corePrivateKeyTypeED25519 = "ed25519" // Internal so as not to log a trace message IntNoForwardingHeaderName = "X-Vault-Internal-No-Request-Forwarding" @@ -39,11 +40,13 @@ var ( ErrCannotForward = errors.New("cannot forward request; no connection or address not known") ) +// This can be one of a few key types so the different params may or may not be filled type clusterKeyParams struct { - Type string `json:"type"` - X *big.Int `json:"x"` - Y *big.Int `json:"y"` - D *big.Int `json:"d"` + Type string `json:"type"` + X *big.Int `json:"x,omitempty"` + Y *big.Int `json:"y,omitempty"` + D *big.Int `json:"d,omitempty"` + ED25519Key []byte `json:"ed25519_key,omitempty"` } type activeConnection struct { diff --git a/vault/core.go b/vault/core.go index 5afccaa7f..7435d7605 100644 --- a/vault/core.go +++ b/vault/core.go @@ -251,6 +251,10 @@ type Core struct { // reloadFuncsLock controlls access to the funcs reloadFuncsLock sync.RWMutex + // wrappingJWTKey is the key used for generating JWTs containing response + // wrapping information + wrappingJWTKey *ecdsa.PrivateKey + // // Cluster information // @@ -820,7 +824,7 @@ func (c *Core) Unseal(key []byte) (bool, error) { // Do post-unseal setup if HA is not enabled if c.ha == nil { // We still need to set up cluster info even if it's not part of a - // cluster right now + // cluster right now. This also populates the cached cluster object. if err := c.setupCluster(); err != nil { c.logger.Error("core: cluster setup failed", "error", err) c.barrier.Seal() @@ -1139,6 +1143,9 @@ func (c *Core) postUnseal() (retErr error) { return err } } + if err := c.ensureWrappingKey(); err != nil { + return err + } if err := c.loadMounts(); err != nil { return err } @@ -1556,27 +1563,3 @@ func (c *Core) BarrierKeyLength() (min, max int) { max += shamir.ShareOverhead return } - -func (c *Core) ValidateWrappingToken(token string) (bool, error) { - if token == "" { - return false, fmt.Errorf("token is empty") - } - - te, err := c.tokenStore.Lookup(token) - if err != nil { - return false, err - } - if te == nil { - return false, nil - } - - if len(te.Policies) != 1 { - return false, nil - } - - if te.Policies[0] != responseWrappingPolicyName { - return false, nil - } - - return true, nil -} diff --git a/vault/logical_system.go b/vault/logical_system.go index 1a2159642..f8519527f 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -41,6 +41,10 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen "raw/*", "rotate", }, + + Unauthenticated: []string{ + "wrapping/pubkey", + }, }, Paths: []*framework.Path{ @@ -542,6 +546,20 @@ func NewSystemBackend(core *Core, config *logical.BackendConfig) (logical.Backen HelpDescription: strings.TrimSpace(sysHelp["rotate"][1]), }, + /* + // Disabled for the moment as we don't support this externally + &framework.Path{ + Pattern: "wrapping/pubkey$", + + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.ReadOperation: b.handleWrappingPubkey, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["wrappubkey"][0]), + HelpDescription: strings.TrimSpace(sysHelp["wrappubkey"][1]), + }, + */ + &framework.Path{ Pattern: "wrapping/wrap$", @@ -1472,9 +1490,22 @@ func (b *SystemBackend) handleRotate( return nil, nil } +func (b *SystemBackend) handleWrappingPubkey( + req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + x, _ := b.Core.wrappingJWTKey.X.MarshalText() + y, _ := b.Core.wrappingJWTKey.Y.MarshalText() + return &logical.Response{ + Data: map[string]interface{}{ + "jwt_x": string(x), + "jwt_y": string(y), + "jwt_curve": corePrivateKeyTypeP521, + }, + }, nil +} + func (b *SystemBackend) handleWrappingWrap( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - if req.WrapTTL == 0 { + if req.WrapInfo == nil || req.WrapInfo.TTL == 0 { return logical.ErrorResponse("endpoint requires response wrapping to be used"), logical.ErrInvalidRequest } @@ -1497,6 +1528,10 @@ func (b *SystemBackend) handleWrappingUnwrap( token = req.ClientToken } + if wt := b.Core.parseVaultTokenFromJWT(token); wt != nil { + token = *wt + } + if thirdParty { // Use the token to decrement the use count to avoid a second operation on the token. _, err := b.Core.tokenStore.UseTokenByID(token) @@ -1557,6 +1592,10 @@ func (b *SystemBackend) handleWrappingLookup( return logical.ErrorResponse("missing \"token\" value in input"), logical.ErrInvalidRequest } + if wt := b.Core.parseVaultTokenFromJWT(token); wt != nil { + token = *wt + } + cubbyReq := &logical.Request{ Operation: logical.ReadOperation, Path: "cubbyhole/wrapinfo", @@ -1613,6 +1652,10 @@ func (b *SystemBackend) handleWrappingRewrap( token = req.ClientToken } + if wt := b.Core.parseVaultTokenFromJWT(token); wt != nil { + token = *wt + } + if thirdParty { // Use the token to decrement the use count to avoid a second operation on the token. _, err := b.Core.tokenStore.UseTokenByID(token) @@ -1683,7 +1726,7 @@ func (b *SystemBackend) handleWrappingRewrap( Data: map[string]interface{}{ "response": response, }, - WrapInfo: &logical.WrapInfo{ + WrapInfo: &logical.ResponseWrapInfo{ TTL: time.Duration(creationTTL), }, }, nil @@ -2087,6 +2130,11 @@ Enable a new audit backend or disable an existing backend. `Round trips the given input data into a response-wrapped token.`, }, + "wrappubkey": { + "Returns pubkeys used in some wrapping formats.", + "Returns pubkeys used in some wrapping formats.", + }, + "unwrap": { "Unwraps a response-wrapped token.", `Unwraps a response-wrapped token. Unlike simply reading from cubbyhole/response, diff --git a/vault/request_handling.go b/vault/request_handling.go index fce14e3d6..8f1325df9 100644 --- a/vault/request_handling.go +++ b/vault/request_handling.go @@ -1,7 +1,6 @@ package vault import ( - "encoding/json" "fmt" "strings" "time" @@ -188,27 +187,37 @@ func (c *Core) handleRequest(req *logical.Request) (retResp *logical.Response, r if resp != nil { // If wrapping is used, use the shortest between the request and response var wrapTTL time.Duration + var wrapFormat string // Ensure no wrap info information is set other than, possibly, the TTL if resp.WrapInfo != nil { if resp.WrapInfo.TTL > 0 { wrapTTL = resp.WrapInfo.TTL } + wrapFormat = resp.WrapInfo.Format resp.WrapInfo = nil } - if req.WrapTTL > 0 { - switch { - case wrapTTL == 0: - wrapTTL = req.WrapTTL - case req.WrapTTL < wrapTTL: - wrapTTL = req.WrapTTL + if req.WrapInfo != nil { + if req.WrapInfo.TTL > 0 { + switch { + case wrapTTL == 0: + wrapTTL = req.WrapInfo.TTL + case req.WrapInfo.TTL < wrapTTL: + wrapTTL = req.WrapInfo.TTL + } + } + // If the wrap format hasn't been set by the response, set it to + // the request format + if req.WrapInfo.Format != "" && wrapFormat == "" { + wrapFormat = req.WrapInfo.Format } } if wrapTTL > 0 { - resp.WrapInfo = &logical.WrapInfo{ - TTL: wrapTTL, + resp.WrapInfo = &logical.ResponseWrapInfo{ + TTL: wrapTTL, + Format: wrapFormat, } } } @@ -325,30 +334,37 @@ func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *log if resp != nil { // If wrapping is used, use the shortest between the request and response var wrapTTL time.Duration + var wrapFormat string // Ensure no wrap info information is set other than, possibly, the TTL if resp.WrapInfo != nil { if resp.WrapInfo.TTL > 0 { wrapTTL = resp.WrapInfo.TTL } + wrapFormat = resp.WrapInfo.Format resp.WrapInfo = nil } - if req.WrapTTL > 0 { - switch { - case wrapTTL == 0: - wrapTTL = req.WrapTTL - case req.WrapTTL < wrapTTL: - wrapTTL = req.WrapTTL + if req.WrapInfo != nil { + if req.WrapInfo.TTL > 0 { + switch { + case wrapTTL == 0: + wrapTTL = req.WrapInfo.TTL + case req.WrapInfo.TTL < wrapTTL: + wrapTTL = req.WrapInfo.TTL + } + } + if req.WrapInfo.Format != "" && wrapFormat == "" { + wrapFormat = req.WrapInfo.Format } } if wrapTTL > 0 { - resp.WrapInfo = &logical.WrapInfo{ - TTL: wrapTTL, + resp.WrapInfo = &logical.ResponseWrapInfo{ + TTL: wrapTTL, + Format: wrapFormat, } } - } // A login request should never return a secret! @@ -431,138 +447,3 @@ func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *log return resp, auth, err } - -func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response) (*logical.Response, error) { - // Before wrapping, obey special rules for listing: if no entries are - // found, 404. This prevents unwrapping only to find empty data. - if req.Operation == logical.ListOperation { - if resp == nil || len(resp.Data) == 0 { - return nil, logical.ErrUnsupportedPath - } - keysRaw, ok := resp.Data["keys"] - if !ok || keysRaw == nil { - return nil, logical.ErrUnsupportedPath - } - keys, ok := keysRaw.([]string) - if !ok { - return nil, logical.ErrUnsupportedPath - } - if len(keys) == 0 { - return nil, logical.ErrUnsupportedPath - } - } - - // If we are wrapping, the first part (performed in this functions) happens - // before auditing so that resp.WrapInfo.Token can contain the HMAC'd - // wrapping token ID in the audit logs, so that it can be determined from - // the audit logs whether the token was ever actually used. - creationTime := time.Now() - te := TokenEntry{ - Path: req.Path, - Policies: []string{"response-wrapping"}, - CreationTime: creationTime.Unix(), - TTL: resp.WrapInfo.TTL, - NumUses: 1, - ExplicitMaxTTL: resp.WrapInfo.TTL, - } - - if err := c.tokenStore.create(&te); err != nil { - c.logger.Error("core: failed to create wrapping token", "error", err) - return nil, ErrInternalError - } - - resp.WrapInfo.Token = te.ID - resp.WrapInfo.CreationTime = creationTime - - // This will only be non-nil if this response contains a token, so in that - // case put the accessor in the wrap info. - if resp.Auth != nil { - resp.WrapInfo.WrappedAccessor = resp.Auth.Accessor - } - - cubbyReq := &logical.Request{ - Operation: logical.CreateOperation, - Path: "cubbyhole/response", - ClientToken: te.ID, - } - - // During a rewrap, store the original response, don't wrap it again. - if req.Path == "sys/wrapping/rewrap" { - cubbyReq.Data = map[string]interface{}{ - "response": resp.Data["response"], - } - } else { - httpResponse := logical.LogicalResponseToHTTPResponse(resp) - - // Add the unique identifier of the original request to the response - httpResponse.RequestID = req.ID - - // Because of the way that JSON encodes (likely just in Go) we actually get - // mixed-up values for ints if we simply put this object in the response - // and encode the whole thing; so instead we marshal it first, then store - // the string response. This actually ends up making it easier on the - // client side, too, as it becomes a straight read-string-pass-to-unmarshal - // operation. - - marshaledResponse, err := json.Marshal(httpResponse) - if err != nil { - c.logger.Error("core: failed to marshal wrapped response", "error", err) - return nil, ErrInternalError - } - - cubbyReq.Data = map[string]interface{}{ - "response": string(marshaledResponse), - } - } - - cubbyResp, err := c.router.Route(cubbyReq) - if err != nil { - // Revoke since it's not yet being tracked for expiration - c.tokenStore.Revoke(te.ID) - c.logger.Error("core: failed to store wrapped response information", "error", err) - return nil, ErrInternalError - } - if cubbyResp != nil && cubbyResp.IsError() { - c.tokenStore.Revoke(te.ID) - c.logger.Error("core: failed to store wrapped response information", "error", cubbyResp.Data["error"]) - return cubbyResp, nil - } - - // Store info for lookup - cubbyReq.Path = "cubbyhole/wrapinfo" - cubbyReq.Data = map[string]interface{}{ - "creation_ttl": resp.WrapInfo.TTL, - "creation_time": creationTime, - } - cubbyResp, err = c.router.Route(cubbyReq) - if err != nil { - // Revoke since it's not yet being tracked for expiration - c.tokenStore.Revoke(te.ID) - c.logger.Error("core: failed to store wrapping information", "error", err) - return nil, ErrInternalError - } - if cubbyResp != nil && cubbyResp.IsError() { - c.tokenStore.Revoke(te.ID) - c.logger.Error("core: failed to store wrapping information", "error", cubbyResp.Data["error"]) - return cubbyResp, nil - } - - auth := &logical.Auth{ - ClientToken: te.ID, - Policies: []string{"response-wrapping"}, - LeaseOptions: logical.LeaseOptions{ - TTL: te.TTL, - Renewable: false, - }, - } - - // Register the wrapped token with the expiration manager - if err := c.expiration.RegisterAuth(te.Path, auth); err != nil { - // Revoke since it's not yet being tracked for expiration - c.tokenStore.Revoke(te.ID) - c.logger.Error("core: failed to register cubbyhole wrapping token lease", "request_path", req.Path, "error", err) - return nil, ErrInternalError - } - - return nil, nil -} diff --git a/vault/request_handling_test.go b/vault/request_handling_test.go index 2a4cbdce4..c966b04d3 100644 --- a/vault/request_handling_test.go +++ b/vault/request_handling_test.go @@ -46,7 +46,9 @@ func TestRequestHandling_Wrapping(t *testing.T) { Path: "wraptest/foo", ClientToken: root, Operation: logical.ReadOperation, - WrapTTL: time.Duration(15 * time.Second), + WrapInfo: &logical.RequestWrapInfo{ + TTL: time.Duration(15 * time.Second), + }, } resp, err = core.HandleRequest(req) if err != nil { @@ -120,7 +122,9 @@ func TestRequestHandling_LoginWrapping(t *testing.T) { req = &logical.Request{ Path: "auth/userpass/login/test", Operation: logical.UpdateOperation, - WrapTTL: time.Duration(15 * time.Second), + WrapInfo: &logical.RequestWrapInfo{ + TTL: time.Duration(15 * time.Second), + }, Data: map[string]interface{}{ "password": "foo", }, diff --git a/vault/router.go b/vault/router.go index 16bd2a434..ea0b9775b 100644 --- a/vault/router.go +++ b/vault/router.go @@ -252,8 +252,14 @@ func (r *Router) routeCommon(req *logical.Request, existenceCheck bool) (*logica // Cache the identifier of the request originalReqID := req.ID - // Cache the wrap TTL of the request - originalWrapTTL := req.WrapTTL + // Cache the wrap info of the request + var wrapInfo *logical.RequestWrapInfo + if req.WrapInfo != nil { + wrapInfo = &logical.RequestWrapInfo{ + TTL: req.WrapInfo.TTL, + Format: req.WrapInfo.Format, + } + } // Reset the request before returning defer func() { @@ -263,7 +269,7 @@ func (r *Router) routeCommon(req *logical.Request, existenceCheck bool) (*logica req.ID = originalReqID req.Storage = nil req.ClientToken = clientToken - req.WrapTTL = originalWrapTTL + req.WrapInfo = wrapInfo }() // Invoke the backend diff --git a/vault/wrapping.go b/vault/wrapping.go new file mode 100644 index 000000000..afa29bfd6 --- /dev/null +++ b/vault/wrapping.go @@ -0,0 +1,309 @@ +package vault + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/SermoDigital/jose/crypto" + "github.com/SermoDigital/jose/jws" + "github.com/SermoDigital/jose/jwt" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/helper/jsonutil" + "github.com/hashicorp/vault/logical" +) + +const ( + // The location of the key used to generate response-wrapping JWTs + coreWrappingJWTKeyPath = "core/wrapping/jwtkey" +) + +func (c *Core) ensureWrappingKey() error { + entry, err := c.barrier.Get(coreWrappingJWTKeyPath) + if err != nil { + return err + } + + var keyParams clusterKeyParams + + if entry == nil { + key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + return errwrap.Wrapf("failed to generate wrapping key: {{err}}", err) + } + keyParams.D = key.D + keyParams.X = key.X + keyParams.Y = key.Y + keyParams.Type = corePrivateKeyTypeP521 + val, err := jsonutil.EncodeJSON(keyParams) + if err != nil { + return errwrap.Wrapf("failed to encode wrapping key: {{err}}", err) + } + entry = &Entry{ + Key: coreWrappingJWTKeyPath, + Value: val, + } + if err = c.barrier.Put(entry); err != nil { + return errwrap.Wrapf("failed to store wrapping key: {{err}}", err) + } + } + + // Redundant if we just created it, but in this case serves as a check anyways + if err = jsonutil.DecodeJSON(entry.Value, &keyParams); err != nil { + return errwrap.Wrapf("failed to decode wrapping key parameters: {{err}}", err) + } + + c.wrappingJWTKey = &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: elliptic.P521(), + X: keyParams.X, + Y: keyParams.Y, + }, + D: keyParams.D, + } + + c.logger.Info("core: loaded wrapping token key") + + return nil +} + +func (c *Core) wrapInCubbyhole(req *logical.Request, resp *logical.Response) (*logical.Response, error) { + // Before wrapping, obey special rules for listing: if no entries are + // found, 404. This prevents unwrapping only to find empty data. + if req.Operation == logical.ListOperation { + if resp == nil || len(resp.Data) == 0 { + return nil, logical.ErrUnsupportedPath + } + keysRaw, ok := resp.Data["keys"] + if !ok || keysRaw == nil { + return nil, logical.ErrUnsupportedPath + } + keys, ok := keysRaw.([]string) + if !ok { + return nil, logical.ErrUnsupportedPath + } + if len(keys) == 0 { + return nil, logical.ErrUnsupportedPath + } + } + + var err error + + // If we are wrapping, the first part (performed in this functions) happens + // before auditing so that resp.WrapInfo.Token can contain the HMAC'd + // wrapping token ID in the audit logs, so that it can be determined from + // the audit logs whether the token was ever actually used. + creationTime := time.Now() + te := TokenEntry{ + Path: req.Path, + Policies: []string{"response-wrapping"}, + CreationTime: creationTime.Unix(), + TTL: resp.WrapInfo.TTL, + NumUses: 1, + ExplicitMaxTTL: resp.WrapInfo.TTL, + } + + if err := c.tokenStore.create(&te); err != nil { + c.logger.Error("core: failed to create wrapping token", "error", err) + return nil, ErrInternalError + } + + resp.WrapInfo.Token = te.ID + resp.WrapInfo.CreationTime = creationTime + + // This will only be non-nil if this response contains a token, so in that + // case put the accessor in the wrap info. + if resp.Auth != nil { + resp.WrapInfo.WrappedAccessor = resp.Auth.Accessor + } + + switch resp.WrapInfo.Format { + case "jwt": + // Create the JWT + claims := jws.Claims{} + // Map the JWT ID to the token ID for ease ofuse + claims.SetJWTID(te.ID) + // Set the issue time to the creation time + claims.SetIssuedAt(creationTime) + // Set the expiration to the TTL + claims.SetExpiration(creationTime.Add(resp.WrapInfo.TTL)) + if resp.Auth != nil { + claims.Set("accessor", resp.Auth.Accessor) + } + claims.Set("type", "wrapping") + claims.Set("addr", c.redirectAddr) + jwt := jws.NewJWT(claims, crypto.SigningMethodES512) + serWebToken, err := jwt.Serialize(c.wrappingJWTKey) + if err != nil { + c.logger.Error("core: failed to serialize JWT", "error", err) + return nil, ErrInternalError + } + resp.WrapInfo.Token = string(serWebToken) + } + + cubbyReq := &logical.Request{ + Operation: logical.CreateOperation, + Path: "cubbyhole/response", + ClientToken: te.ID, + } + + // During a rewrap, store the original response, don't wrap it again. + if req.Path == "sys/wrapping/rewrap" { + cubbyReq.Data = map[string]interface{}{ + "response": resp.Data["response"], + } + } else { + httpResponse := logical.LogicalResponseToHTTPResponse(resp) + + // Add the unique identifier of the original request to the response + httpResponse.RequestID = req.ID + + // Because of the way that JSON encodes (likely just in Go) we actually get + // mixed-up values for ints if we simply put this object in the response + // and encode the whole thing; so instead we marshal it first, then store + // the string response. This actually ends up making it easier on the + // client side, too, as it becomes a straight read-string-pass-to-unmarshal + // operation. + + marshaledResponse, err := json.Marshal(httpResponse) + if err != nil { + c.logger.Error("core: failed to marshal wrapped response", "error", err) + return nil, ErrInternalError + } + + cubbyReq.Data = map[string]interface{}{ + "response": string(marshaledResponse), + } + } + + cubbyResp, err := c.router.Route(cubbyReq) + if err != nil { + // Revoke since it's not yet being tracked for expiration + c.tokenStore.Revoke(te.ID) + c.logger.Error("core: failed to store wrapped response information", "error", err) + return nil, ErrInternalError + } + if cubbyResp != nil && cubbyResp.IsError() { + c.tokenStore.Revoke(te.ID) + c.logger.Error("core: failed to store wrapped response information", "error", cubbyResp.Data["error"]) + return cubbyResp, nil + } + + // Store info for lookup + cubbyReq.Path = "cubbyhole/wrapinfo" + cubbyReq.Data = map[string]interface{}{ + "creation_ttl": resp.WrapInfo.TTL, + "creation_time": creationTime, + } + cubbyResp, err = c.router.Route(cubbyReq) + if err != nil { + // Revoke since it's not yet being tracked for expiration + c.tokenStore.Revoke(te.ID) + c.logger.Error("core: failed to store wrapping information", "error", err) + return nil, ErrInternalError + } + if cubbyResp != nil && cubbyResp.IsError() { + c.tokenStore.Revoke(te.ID) + c.logger.Error("core: failed to store wrapping information", "error", cubbyResp.Data["error"]) + return cubbyResp, nil + } + + auth := &logical.Auth{ + ClientToken: te.ID, + Policies: []string{"response-wrapping"}, + LeaseOptions: logical.LeaseOptions{ + TTL: te.TTL, + Renewable: false, + }, + } + + // Register the wrapped token with the expiration manager + if err := c.expiration.RegisterAuth(te.Path, auth); err != nil { + // Revoke since it's not yet being tracked for expiration + c.tokenStore.Revoke(te.ID) + c.logger.Error("core: failed to register cubbyhole wrapping token lease", "request_path", req.Path, "error", err) + return nil, ErrInternalError + } + + return nil, nil +} + +func (c *Core) ValidateWrappingToken(req *logical.Request) (bool, error) { + if req == nil { + return false, fmt.Errorf("invalid request") + } + + var err error + + var token string + if req.Data != nil && req.Data["token"] != nil { + if tokenStr, ok := req.Data["token"].(string); !ok { + return false, fmt.Errorf("could not decode token in request body") + } else if tokenStr == "" { + return false, fmt.Errorf("empty token in request body") + } else { + token = tokenStr + } + } else { + token = req.ClientToken + } + + // Check for it being a JWT. If it is, and it is valid, we extract the + // internal client token from it and use that during lookup. + if strings.Count(token, ".") == 2 { + wt, err := jws.ParseJWT([]byte(token)) + // If there's an error we simply fall back to attempting to use it as a regular token + if err == nil && wt != nil { + validator := &jwt.Validator{} + validator.SetClaim("type", "wrapping") + if err = wt.Validate(&c.wrappingJWTKey.PublicKey, crypto.SigningMethodES512, []*jwt.Validator{validator}...); err != nil { + return false, errwrap.Wrapf("wrapping token signature could not be validated: {{err}}", err) + } + token, _ = wt.Claims().JWTID() + } + } + + if token == "" { + return false, fmt.Errorf("token is empty") + } + + te, err := c.tokenStore.Lookup(token) + if err != nil { + return false, err + } + if te == nil { + return false, nil + } + + if len(te.Policies) != 1 { + return false, nil + } + + if te.Policies[0] != responseWrappingPolicyName { + return false, nil + } + + return true, nil +} + +// parseVaultTokenFromJWT returns a string iff the token was a JWT and we could +// extract the original token ID from inside +func (c *Core) parseVaultTokenFromJWT(token string) *string { + var result string + if strings.Count(token, ".") != 2 { + return nil + } + + wt, err := jws.ParseJWT([]byte(token)) + if err != nil || wt == nil { + return nil + } + + result, _ = wt.Claims().JWTID() + + return &result +} diff --git a/vendor/github.com/SermoDigital/jose/LICENSE b/vendor/github.com/SermoDigital/jose/LICENSE new file mode 100644 index 000000000..d2d35b66c --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sermo Digital LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/SermoDigital/jose/README.md b/vendor/github.com/SermoDigital/jose/README.md new file mode 100644 index 000000000..621862ef9 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/README.md @@ -0,0 +1,40 @@ +JOSE +============ +[![Build Status](https://travis-ci.org/SermoDigital/jose.svg?branch=master)](https://travis-ci.org/SermoDigital/jose) +[![GoDoc](https://godoc.org/github.com/SermoDigital/jose?status.svg)](https://godoc.org/github.com/SermoDigital/jose) + +JOSE is a comprehensive set of JWT, JWS, and JWE libraries. + +## Why + +The only other JWS/JWE/JWT implementations are specific to JWT, and none +were particularly pleasant to work with. + +These libraries should provide an easy, straightforward way to securely +create, parse, and validate JWS, JWE, and JWTs. + +## Notes: +JWE is currently unimplemented. + +## Version 0.9: + +## Documentation + +The docs can be found at [godoc.org] [docs], as usual. + +A gopkg.in mirror can be found at https://gopkg.in/jose.v1, thanks to +@zia-newversion. (For context, see issue #30.) + +### [JWS RFC][jws] +### [JWE RFC][jwe] +### [JWT RFC][jwt] + +## License + +[MIT] [license]. + +[docs]: https://godoc.org/github.com/SermoDigital/jose +[license]: https://github.com/SermoDigital/jose/blob/master/LICENSE.md +[jws]: https://tools.ietf.org/html/rfc7515 +[jwe]: https://tools.ietf.org/html/rfc7516 +[jwt]: https://tools.ietf.org/html/rfc7519 diff --git a/vendor/github.com/SermoDigital/jose/_test.sh b/vendor/github.com/SermoDigital/jose/_test.sh new file mode 100755 index 000000000..a36a4709b --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/_test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +go build ./... +go test ./... +golint ./... +go vet ./... \ No newline at end of file diff --git a/vendor/github.com/SermoDigital/jose/base64.go b/vendor/github.com/SermoDigital/jose/base64.go new file mode 100644 index 000000000..f7275fb2e --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/base64.go @@ -0,0 +1,44 @@ +package jose + +import "encoding/base64" + +// Encoder is satisfied if the type can marshal itself into a valid +// structure for a JWS. +type Encoder interface { + // Base64 implies T -> JSON -> RawURLEncodingBase64 + Base64() ([]byte, error) +} + +// Base64Decode decodes a base64-encoded byte slice. +func Base64Decode(b []byte) ([]byte, error) { + buf := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) + n, err := base64.RawURLEncoding.Decode(buf, b) + return buf[:n], err +} + +// Base64Encode encodes a byte slice. +func Base64Encode(b []byte) []byte { + buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(b))) + base64.RawURLEncoding.Encode(buf, b) + return buf +} + +// EncodeEscape base64-encodes a byte slice but escapes it for JSON. +// It'll return the format: `"base64"` +func EncodeEscape(b []byte) []byte { + buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(b))+2) + buf[0] = '"' + base64.RawURLEncoding.Encode(buf[1:], b) + buf[len(buf)-1] = '"' + return buf +} + +// DecodeEscaped decodes a base64-encoded byte slice straight from a JSON +// structure. It assumes it's in the format: `"base64"`, but can handle +// cases where it's not. +func DecodeEscaped(b []byte) ([]byte, error) { + if len(b) > 1 && b[0] == '"' && b[len(b)-1] == '"' { + b = b[1 : len(b)-1] + } + return Base64Decode(b) +} diff --git a/vendor/github.com/SermoDigital/jose/crypto/doc.go b/vendor/github.com/SermoDigital/jose/crypto/doc.go new file mode 100644 index 000000000..16cf476ba --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/doc.go @@ -0,0 +1,4 @@ +// Package crypto implements "SigningMethods" and "EncryptionMethods"; +// that is, ways to sign and encrypt JWS and JWEs, respectively, as well +// as JWTs. +package crypto diff --git a/vendor/github.com/SermoDigital/jose/crypto/ecdsa.go b/vendor/github.com/SermoDigital/jose/crypto/ecdsa.go new file mode 100644 index 000000000..3ef12ba22 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/ecdsa.go @@ -0,0 +1,117 @@ +package crypto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "encoding/asn1" + "encoding/json" + "errors" + "math/big" +) + +// ErrECDSAVerification is missing from crypto/ecdsa compared to crypto/rsa +var ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") + +// SigningMethodECDSA implements the ECDSA family of signing methods signing +// methods +type SigningMethodECDSA struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// ECPoint is a marshalling structure for the EC points R and S. +type ECPoint struct { + R *big.Int + S *big.Int +} + +// Specific instances of EC SigningMethods. +var ( + // SigningMethodES256 implements ES256. + SigningMethodES256 = &SigningMethodECDSA{ + Name: "ES256", + Hash: crypto.SHA256, + } + + // SigningMethodES384 implements ES384. + SigningMethodES384 = &SigningMethodECDSA{ + Name: "ES384", + Hash: crypto.SHA384, + } + + // SigningMethodES512 implements ES512. + SigningMethodES512 = &SigningMethodECDSA{ + Name: "ES512", + Hash: crypto.SHA512, + } +) + +// Alg returns the name of the SigningMethodECDSA instance. +func (m *SigningMethodECDSA) Alg() string { return m.Name } + +// Verify implements the Verify method from SigningMethod. +// For this verify method, key must be an *ecdsa.PublicKey. +func (m *SigningMethodECDSA) Verify(raw []byte, signature Signature, key interface{}) error { + + ecdsaKey, ok := key.(*ecdsa.PublicKey) + if !ok { + return ErrInvalidKey + } + + // Unmarshal asn1 ECPoint + var ecpoint ECPoint + if _, err := asn1.Unmarshal(signature, &ecpoint); err != nil { + return err + } + + // Verify the signature + if !ecdsa.Verify(ecdsaKey, m.sum(raw), ecpoint.R, ecpoint.S) { + return ErrECDSAVerification + } + return nil +} + +// Sign implements the Sign method from SigningMethod. +// For this signing method, key must be an *ecdsa.PrivateKey. +func (m *SigningMethodECDSA) Sign(data []byte, key interface{}) (Signature, error) { + + ecdsaKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, ErrInvalidKey + } + + r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, m.sum(data)) + if err != nil { + return nil, err + } + + signature, err := asn1.Marshal(ECPoint{R: r, S: s}) + if err != nil { + return nil, err + } + return Signature(signature), nil +} + +func (m *SigningMethodECDSA) sum(b []byte) []byte { + h := m.Hash.New() + h.Write(b) + return h.Sum(nil) +} + +// Hasher implements the Hasher method from SigningMethod. +func (m *SigningMethodECDSA) Hasher() crypto.Hash { + return m.Hash +} + +// MarshalJSON is in case somebody decides to place SigningMethodECDSA +// inside the Header, presumably because they (wrongly) decided it was a good +// idea to use the SigningMethod itself instead of the SigningMethod's Alg +// method. In order to keep things sane, marshalling this will simply +// return the JSON-compatible representation of m.Alg(). +func (m *SigningMethodECDSA) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodECDSA)(nil) diff --git a/vendor/github.com/SermoDigital/jose/crypto/ecdsa_utils.go b/vendor/github.com/SermoDigital/jose/crypto/ecdsa_utils.go new file mode 100644 index 000000000..4bd75d2e5 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/ecdsa_utils.go @@ -0,0 +1,48 @@ +package crypto + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +// ECDSA parsing errors. +var ( + ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") + ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") +) + +// ParseECPrivateKeyFromPEM will parse a PEM encoded EC Private +// Key Structure. +func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { + block, _ := pem.Decode(key) + if block == nil { + return nil, ErrKeyMustBePEMEncoded + } + return x509.ParseECPrivateKey(block.Bytes) +} + +// ParseECPublicKeyFromPEM will parse a PEM encoded PKCS1 or PKCS8 public key +func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { + + block, _ := pem.Decode(key) + if block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + parsedKey = cert.PublicKey + } + + pkey, ok := parsedKey.(*ecdsa.PublicKey) + if !ok { + return nil, ErrNotECPublicKey + } + return pkey, nil +} diff --git a/vendor/github.com/SermoDigital/jose/crypto/errors.go b/vendor/github.com/SermoDigital/jose/crypto/errors.go new file mode 100644 index 000000000..34fbd25ff --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/errors.go @@ -0,0 +1,9 @@ +package crypto + +import "errors" + +var ( + // ErrInvalidKey means the key argument passed to SigningMethod.Verify + // was not the correct type. + ErrInvalidKey = errors.New("key is invalid") +) diff --git a/vendor/github.com/SermoDigital/jose/crypto/hmac.go b/vendor/github.com/SermoDigital/jose/crypto/hmac.go new file mode 100644 index 000000000..1cb7f6e09 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/hmac.go @@ -0,0 +1,81 @@ +package crypto + +import ( + "crypto" + "crypto/hmac" + "encoding/json" + "errors" +) + +// SigningMethodHMAC implements the HMAC-SHA family of SigningMethods. +type SigningMethodHMAC struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// Specific instances of HMAC-SHA SigningMethods. +var ( + // SigningMethodHS256 implements HS256. + SigningMethodHS256 = &SigningMethodHMAC{ + Name: "HS256", + Hash: crypto.SHA256, + } + + // SigningMethodHS384 implements HS384. + SigningMethodHS384 = &SigningMethodHMAC{ + Name: "HS384", + Hash: crypto.SHA384, + } + + // SigningMethodHS512 implements HS512. + SigningMethodHS512 = &SigningMethodHMAC{ + Name: "HS512", + Hash: crypto.SHA512, + } + + // ErrSignatureInvalid is returned when the provided signature is found + // to be invalid. + ErrSignatureInvalid = errors.New("signature is invalid") +) + +// Alg implements the SigningMethod interface. +func (m *SigningMethodHMAC) Alg() string { return m.Name } + +// Verify implements the Verify method from SigningMethod. +// For this signing method, must be a []byte. +func (m *SigningMethodHMAC) Verify(raw []byte, signature Signature, key interface{}) error { + keyBytes, ok := key.([]byte) + if !ok { + return ErrInvalidKey + } + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write(raw) + if hmac.Equal(signature, hasher.Sum(nil)) { + return nil + } + return ErrSignatureInvalid +} + +// Sign implements the Sign method from SigningMethod for this signing method. +// Key must be a []byte. +func (m *SigningMethodHMAC) Sign(data []byte, key interface{}) (Signature, error) { + keyBytes, ok := key.([]byte) + if !ok { + return nil, ErrInvalidKey + } + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write(data) + return Signature(hasher.Sum(nil)), nil +} + +// Hasher implements the SigningMethod interface. +func (m *SigningMethodHMAC) Hasher() crypto.Hash { return m.Hash } + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodHMAC) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodHMAC)(nil) diff --git a/vendor/github.com/SermoDigital/jose/crypto/none.go b/vendor/github.com/SermoDigital/jose/crypto/none.go new file mode 100644 index 000000000..db3d139e9 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/none.go @@ -0,0 +1,72 @@ +package crypto + +import ( + "crypto" + "encoding/json" + "hash" + "io" +) + +func init() { + crypto.RegisterHash(crypto.Hash(0), h) +} + +// h is passed to crypto.RegisterHash. +func h() hash.Hash { + return &f{Writer: nil} +} + +type f struct{ io.Writer } + +// Sum helps implement the hash.Hash interface. +func (_ *f) Sum(b []byte) []byte { return nil } + +// Reset helps implement the hash.Hash interface. +func (_ *f) Reset() {} + +// Size helps implement the hash.Hash interface. +func (_ *f) Size() int { return -1 } + +// BlockSize helps implement the hash.Hash interface. +func (_ *f) BlockSize() int { return -1 } + +// Unsecured is the default "none" algorithm. +var Unsecured = &SigningMethodNone{ + Name: "none", + Hash: crypto.Hash(0), +} + +// SigningMethodNone is the default "none" algorithm. +type SigningMethodNone struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// Verify helps implement the SigningMethod interface. +func (_ *SigningMethodNone) Verify(_ []byte, _ Signature, _ interface{}) error { + return nil +} + +// Sign helps implement the SigningMethod interface. +func (_ *SigningMethodNone) Sign(_ []byte, _ interface{}) (Signature, error) { + return nil, nil +} + +// Alg helps implement the SigningMethod interface. +func (m *SigningMethodNone) Alg() string { + return m.Name +} + +// Hasher helps implement the SigningMethod interface. +func (m *SigningMethodNone) Hasher() crypto.Hash { + return m.Hash +} + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodNone) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodNone)(nil) diff --git a/vendor/github.com/SermoDigital/jose/crypto/rsa.go b/vendor/github.com/SermoDigital/jose/crypto/rsa.go new file mode 100644 index 000000000..80596df33 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/rsa.go @@ -0,0 +1,80 @@ +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/json" +) + +// SigningMethodRSA implements the RSA family of SigningMethods. +type SigningMethodRSA struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// Specific instances of RSA SigningMethods. +var ( + // SigningMethodRS256 implements RS256. + SigningMethodRS256 = &SigningMethodRSA{ + Name: "RS256", + Hash: crypto.SHA256, + } + + // SigningMethodRS384 implements RS384. + SigningMethodRS384 = &SigningMethodRSA{ + Name: "RS384", + Hash: crypto.SHA384, + } + + // SigningMethodRS512 implements RS512. + SigningMethodRS512 = &SigningMethodRSA{ + Name: "RS512", + Hash: crypto.SHA512, + } +) + +// Alg implements the SigningMethod interface. +func (m *SigningMethodRSA) Alg() string { return m.Name } + +// Verify implements the Verify method from SigningMethod. +// For this signing method, must be an *rsa.PublicKey. +func (m *SigningMethodRSA) Verify(raw []byte, sig Signature, key interface{}) error { + rsaKey, ok := key.(*rsa.PublicKey) + if !ok { + return ErrInvalidKey + } + return rsa.VerifyPKCS1v15(rsaKey, m.Hash, m.sum(raw), sig) +} + +// Sign implements the Sign method from SigningMethod. +// For this signing method, must be an *rsa.PrivateKey structure. +func (m *SigningMethodRSA) Sign(data []byte, key interface{}) (Signature, error) { + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, ErrInvalidKey + } + sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, m.sum(data)) + if err != nil { + return nil, err + } + return Signature(sigBytes), nil +} + +func (m *SigningMethodRSA) sum(b []byte) []byte { + h := m.Hash.New() + h.Write(b) + return h.Sum(nil) +} + +// Hasher implements the SigningMethod interface. +func (m *SigningMethodRSA) Hasher() crypto.Hash { return m.Hash } + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodRSA) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodRSA)(nil) diff --git a/vendor/github.com/SermoDigital/jose/crypto/rsa_pss.go b/vendor/github.com/SermoDigital/jose/crypto/rsa_pss.go new file mode 100644 index 000000000..3847ae2d2 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/rsa_pss.go @@ -0,0 +1,96 @@ +// +build go1.4 + +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/json" +) + +// SigningMethodRSAPSS implements the RSAPSS family of SigningMethods. +type SigningMethodRSAPSS struct { + *SigningMethodRSA + Options *rsa.PSSOptions +} + +// Specific instances for RS/PS SigningMethods. +var ( + // SigningMethodPS256 implements PS256. + SigningMethodPS256 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS256", + Hash: crypto.SHA256, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA256, + }, + } + + // SigningMethodPS384 implements PS384. + SigningMethodPS384 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS384", + Hash: crypto.SHA384, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA384, + }, + } + + // SigningMethodPS512 implements PS512. + SigningMethodPS512 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS512", + Hash: crypto.SHA512, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA512, + }, + } +) + +// Verify implements the Verify method from SigningMethod. +// For this verify method, key must be an *rsa.PublicKey. +func (m *SigningMethodRSAPSS) Verify(raw []byte, signature Signature, key interface{}) error { + rsaKey, ok := key.(*rsa.PublicKey) + if !ok { + return ErrInvalidKey + } + return rsa.VerifyPSS(rsaKey, m.Hash, m.sum(raw), signature, m.Options) +} + +// Sign implements the Sign method from SigningMethod. +// For this signing method, key must be an *rsa.PrivateKey. +func (m *SigningMethodRSAPSS) Sign(raw []byte, key interface{}) (Signature, error) { + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, ErrInvalidKey + } + sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, m.sum(raw), m.Options) + if err != nil { + return nil, err + } + return Signature(sigBytes), nil +} + +func (m *SigningMethodRSAPSS) sum(b []byte) []byte { + h := m.Hash.New() + h.Write(b) + return h.Sum(nil) +} + +// Hasher implements the Hasher method from SigningMethod. +func (m *SigningMethodRSAPSS) Hasher() crypto.Hash { return m.Hash } + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodRSAPSS) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodRSAPSS)(nil) diff --git a/vendor/github.com/SermoDigital/jose/crypto/rsa_utils.go b/vendor/github.com/SermoDigital/jose/crypto/rsa_utils.go new file mode 100644 index 000000000..43aeff375 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/rsa_utils.go @@ -0,0 +1,70 @@ +package crypto + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +// Errors specific to rsa_utils. +var ( + ErrKeyMustBePEMEncoded = errors.New("invalid key: Key must be PEM encoded PKCS1 or PKCS8 private key") + ErrNotRSAPrivateKey = errors.New("key is not a valid RSA private key") + ErrNotRSAPublicKey = errors.New("key is not a valid RSA public key") +) + +// ParseRSAPrivateKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 private key. +func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + var parsedKey interface{} + if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { + return nil, err + } + } + + var pkey *rsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, ErrNotRSAPrivateKey + } + + return pkey, nil +} + +// ParseRSAPublicKeyFromPEM parses PEM encoded PKCS1 or PKCS8 public key. +func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *rsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { + return nil, ErrNotRSAPublicKey + } + + return pkey, nil +} diff --git a/vendor/github.com/SermoDigital/jose/crypto/signature.go b/vendor/github.com/SermoDigital/jose/crypto/signature.go new file mode 100644 index 000000000..37571f9de --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/signature.go @@ -0,0 +1,36 @@ +package crypto + +import ( + "encoding/json" + + "github.com/SermoDigital/jose" +) + +// Signature is a JWS signature. +type Signature []byte + +// MarshalJSON implements json.Marshaler for a signature. +func (s Signature) MarshalJSON() ([]byte, error) { + return jose.EncodeEscape(s), nil +} + +// Base64 helps implements jose.Encoder for Signature. +func (s Signature) Base64() ([]byte, error) { + return jose.Base64Encode(s), nil +} + +// UnmarshalJSON implements json.Unmarshaler for signature. +func (s *Signature) UnmarshalJSON(b []byte) error { + dec, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + *s = Signature(dec) + return nil +} + +var ( + _ json.Marshaler = (Signature)(nil) + _ json.Unmarshaler = (*Signature)(nil) + _ jose.Encoder = (Signature)(nil) +) diff --git a/vendor/github.com/SermoDigital/jose/crypto/signing_method.go b/vendor/github.com/SermoDigital/jose/crypto/signing_method.go new file mode 100644 index 000000000..c8b8874b2 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/crypto/signing_method.go @@ -0,0 +1,24 @@ +package crypto + +import "crypto" + +// SigningMethod is an interface that provides a way to sign JWS tokens. +type SigningMethod interface { + // Alg describes the signing algorithm, and is used to uniquely + // describe the specific crypto.SigningMethod. + Alg() string + + // Verify accepts the raw content, the signature, and the key used + // to sign the raw content, and returns any errors found while validating + // the signature and content. + Verify(raw []byte, sig Signature, key interface{}) error + + // Sign returns a Signature for the raw bytes, as well as any errors + // that occurred during the signing. + Sign(raw []byte, key interface{}) (Signature, error) + + // Used to cause quick panics when a crypto.SigningMethod whose form of hashing + // isn't linked in the binary when you register a crypto.SigningMethod. + // To spoof this, see "crypto.SigningMethodNone". + Hasher() crypto.Hash +} diff --git a/vendor/github.com/SermoDigital/jose/doc.go b/vendor/github.com/SermoDigital/jose/doc.go new file mode 100644 index 000000000..7abb7bf14 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/doc.go @@ -0,0 +1,3 @@ +// Package jose implements some helper functions and types for the children +// packages, jws, jwt, and jwe. +package jose diff --git a/vendor/github.com/SermoDigital/jose/header.go b/vendor/github.com/SermoDigital/jose/header.go new file mode 100644 index 000000000..4499a7696 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/header.go @@ -0,0 +1,124 @@ +package jose + +import "encoding/json" + +// Header implements a JOSE Header with the addition of some helper +// methods, similar to net/url.Values. +type Header map[string]interface{} + +// Get retrieves the value corresponding with key from the Header. +func (h Header) Get(key string) interface{} { + if h == nil { + return nil + } + return h[key] +} + +// Set sets Claims[key] = val. It'll overwrite without warning. +func (h Header) Set(key string, val interface{}) { + h[key] = val +} + +// Del removes the value that corresponds with key from the Header. +func (h Header) Del(key string) { + delete(h, key) +} + +// Has returns true if a value for the given key exists inside the Header. +func (h Header) Has(key string) bool { + _, ok := h[key] + return ok +} + +// MarshalJSON implements json.Marshaler for Header. +func (h Header) MarshalJSON() ([]byte, error) { + if len(h) == 0 { + return nil, nil + } + b, err := json.Marshal(map[string]interface{}(h)) + if err != nil { + return nil, err + } + return EncodeEscape(b), nil +} + +// Base64 implements the Encoder interface. +func (h Header) Base64() ([]byte, error) { + return h.MarshalJSON() +} + +// UnmarshalJSON implements json.Unmarshaler for Header. +func (h *Header) UnmarshalJSON(b []byte) error { + if b == nil { + return nil + } + b, err := DecodeEscaped(b) + if err != nil { + return err + } + return json.Unmarshal(b, (*map[string]interface{})(h)) +} + +// Protected Headers are base64-encoded after they're marshaled into +// JSON. +type Protected Header + +// Get retrieves the value corresponding with key from the Protected Header. +func (p Protected) Get(key string) interface{} { + if p == nil { + return nil + } + return p[key] +} + +// Set sets Protected[key] = val. It'll overwrite without warning. +func (p Protected) Set(key string, val interface{}) { + p[key] = val +} + +// Del removes the value that corresponds with key from the Protected Header. +func (p Protected) Del(key string) { + delete(p, key) +} + +// Has returns true if a value for the given key exists inside the Protected +// Header. +func (p Protected) Has(key string) bool { + _, ok := p[key] + return ok +} + +// MarshalJSON implements json.Marshaler for Protected. +func (p Protected) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(map[string]interface{}(p)) + if err != nil { + return nil, err + } + return EncodeEscape(b), nil +} + +// Base64 implements the Encoder interface. +func (p Protected) Base64() ([]byte, error) { + b, err := json.Marshal(map[string]interface{}(p)) + if err != nil { + return nil, err + } + return Base64Encode(b), nil +} + +// UnmarshalJSON implements json.Unmarshaler for Protected. +func (p *Protected) UnmarshalJSON(b []byte) error { + var h Header + if err := h.UnmarshalJSON(b); err != nil { + return err + } + *p = Protected(h) + return nil +} + +var ( + _ json.Marshaler = (Protected)(nil) + _ json.Unmarshaler = (*Protected)(nil) + _ json.Marshaler = (Header)(nil) + _ json.Unmarshaler = (*Header)(nil) +) diff --git a/vendor/github.com/SermoDigital/jose/jws/claims.go b/vendor/github.com/SermoDigital/jose/jws/claims.go new file mode 100644 index 000000000..4cc616cfa --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/claims.go @@ -0,0 +1,190 @@ +package jws + +import ( + "encoding/json" + "time" + + "github.com/SermoDigital/jose" + "github.com/SermoDigital/jose/jwt" +) + +// Claims represents a set of JOSE Claims. +type Claims jwt.Claims + +// Get retrieves the value corresponding with key from the Claims. +func (c Claims) Get(key string) interface{} { + return jwt.Claims(c).Get(key) +} + +// Set sets Claims[key] = val. It'll overwrite without warning. +func (c Claims) Set(key string, val interface{}) { + jwt.Claims(c).Set(key, val) +} + +// Del removes the value that corresponds with key from the Claims. +func (c Claims) Del(key string) { + jwt.Claims(c).Del(key) +} + +// Has returns true if a value for the given key exists inside the Claims. +func (c Claims) Has(key string) bool { + return jwt.Claims(c).Has(key) +} + +// MarshalJSON implements json.Marshaler for Claims. +func (c Claims) MarshalJSON() ([]byte, error) { + return jwt.Claims(c).MarshalJSON() +} + +// Base64 implements the Encoder interface. +func (c Claims) Base64() ([]byte, error) { + return jwt.Claims(c).Base64() +} + +// UnmarshalJSON implements json.Unmarshaler for Claims. +func (c *Claims) UnmarshalJSON(b []byte) error { + if b == nil { + return nil + } + + b, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + + // Since json.Unmarshal calls UnmarshalJSON, + // calling json.Unmarshal on *p would be infinitely recursive + // A temp variable is needed because &map[string]interface{}(*p) is + // invalid Go. + + tmp := map[string]interface{}(*c) + if err = json.Unmarshal(b, &tmp); err != nil { + return err + } + *c = Claims(tmp) + return nil +} + +// Issuer retrieves claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) Issuer() (string, bool) { + return jwt.Claims(c).Issuer() +} + +// Subject retrieves claim "sub" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) Subject() (string, bool) { + return jwt.Claims(c).Subject() +} + +// Audience retrieves claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) Audience() ([]string, bool) { + return jwt.Claims(c).Audience() +} + +// Expiration retrieves claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) Expiration() (time.Time, bool) { + return jwt.Claims(c).Expiration() +} + +// NotBefore retrieves claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) NotBefore() (time.Time, bool) { + return jwt.Claims(c).NotBefore() +} + +// IssuedAt retrieves claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) IssuedAt() (time.Time, bool) { + return jwt.Claims(c).IssuedAt() +} + +// JWTID retrieves claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) JWTID() (string, bool) { + return jwt.Claims(c).JWTID() +} + +// RemoveIssuer deletes claim "iss" from c. +func (c Claims) RemoveIssuer() { + jwt.Claims(c).RemoveIssuer() +} + +// RemoveSubject deletes claim "sub" from c. +func (c Claims) RemoveSubject() { + jwt.Claims(c).RemoveIssuer() +} + +// RemoveAudience deletes claim "aud" from c. +func (c Claims) RemoveAudience() { + jwt.Claims(c).Audience() +} + +// RemoveExpiration deletes claim "exp" from c. +func (c Claims) RemoveExpiration() { + jwt.Claims(c).RemoveExpiration() +} + +// RemoveNotBefore deletes claim "nbf" from c. +func (c Claims) RemoveNotBefore() { + jwt.Claims(c).NotBefore() +} + +// RemoveIssuedAt deletes claim "iat" from c. +func (c Claims) RemoveIssuedAt() { + jwt.Claims(c).IssuedAt() +} + +// RemoveJWTID deletes claim "jti" from c. +func (c Claims) RemoveJWTID() { + jwt.Claims(c).RemoveJWTID() +} + +// SetIssuer sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) SetIssuer(issuer string) { + jwt.Claims(c).SetIssuer(issuer) +} + +// SetSubject sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) SetSubject(subject string) { + jwt.Claims(c).SetSubject(subject) +} + +// SetAudience sets claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) SetAudience(audience ...string) { + jwt.Claims(c).SetAudience(audience...) +} + +// SetExpiration sets claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) SetExpiration(expiration time.Time) { + jwt.Claims(c).SetExpiration(expiration) +} + +// SetNotBefore sets claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) SetNotBefore(notBefore time.Time) { + jwt.Claims(c).SetNotBefore(notBefore) +} + +// SetIssuedAt sets claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) SetIssuedAt(issuedAt time.Time) { + jwt.Claims(c).SetIssuedAt(issuedAt) +} + +// SetJWTID sets claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) SetJWTID(uniqueID string) { + jwt.Claims(c).SetJWTID(uniqueID) +} + +var ( + _ json.Marshaler = (Claims)(nil) + _ json.Unmarshaler = (*Claims)(nil) +) diff --git a/vendor/github.com/SermoDigital/jose/jws/doc.go b/vendor/github.com/SermoDigital/jose/jws/doc.go new file mode 100644 index 000000000..165836d57 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/doc.go @@ -0,0 +1,2 @@ +// Package jws implements JWSs per RFC 7515 +package jws diff --git a/vendor/github.com/SermoDigital/jose/jws/errors.go b/vendor/github.com/SermoDigital/jose/jws/errors.go new file mode 100644 index 000000000..0512a0e40 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/errors.go @@ -0,0 +1,62 @@ +package jws + +import "errors" + +var ( + + // ErrNotEnoughMethods is returned if New was called _or_ the Flat/Compact + // methods were called with 0 SigningMethods. + ErrNotEnoughMethods = errors.New("not enough methods provided") + + // ErrCouldNotUnmarshal is returned when Parse's json.Unmarshaler + // parameter returns an error. + ErrCouldNotUnmarshal = errors.New("custom unmarshal failed") + + // ErrNotCompact signals that the provided potential JWS is not + // in its compact representation. + ErrNotCompact = errors.New("not a compact JWS") + + // ErrDuplicateHeaderParameter signals that there are duplicate parameters + // in the provided Headers. + ErrDuplicateHeaderParameter = errors.New("duplicate parameters in the JOSE Header") + + // ErrTwoEmptyHeaders is returned if both Headers are empty. + ErrTwoEmptyHeaders = errors.New("both headers cannot be empty") + + // ErrNotEnoughKeys is returned when not enough keys are provided for + // the given SigningMethods. + ErrNotEnoughKeys = errors.New("not enough keys (for given methods)") + + // ErrDidNotValidate means the given JWT did not properly validate + ErrDidNotValidate = errors.New("did not validate") + + // ErrNoAlgorithm means no algorithm ("alg") was found in the Protected + // Header. + ErrNoAlgorithm = errors.New("no algorithm found") + + // ErrAlgorithmDoesntExist means the algorithm asked for cannot be + // found inside the signingMethod cache. + ErrAlgorithmDoesntExist = errors.New("algorithm doesn't exist") + + // ErrMismatchedAlgorithms means the algorithm inside the JWT was + // different than the algorithm the caller wanted to use. + ErrMismatchedAlgorithms = errors.New("mismatched algorithms") + + // ErrCannotValidate means the JWS cannot be validated for various + // reasons. For example, if there aren't any signatures/payloads/headers + // to actually validate. + ErrCannotValidate = errors.New("cannot validate") + + // ErrIsNotJWT means the given JWS is not a JWT. + ErrIsNotJWT = errors.New("JWS is not a JWT") + + // ErrHoldsJWE means the given JWS holds a JWE inside its payload. + ErrHoldsJWE = errors.New("JWS holds JWE") + + // ErrNotEnoughValidSignatures means the JWS did not meet the required + // number of signatures. + ErrNotEnoughValidSignatures = errors.New("not enough valid signatures in the JWS") + + // ErrNoTokenInRequest means there's no token present inside the *http.Request. + ErrNoTokenInRequest = errors.New("no token present in request") +) diff --git a/vendor/github.com/SermoDigital/jose/jws/jws.go b/vendor/github.com/SermoDigital/jose/jws/jws.go new file mode 100644 index 000000000..49e7b976d --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/jws.go @@ -0,0 +1,490 @@ +package jws + +import ( + "bytes" + "encoding/json" + "net/http" + "strings" + + "github.com/SermoDigital/jose" + "github.com/SermoDigital/jose/crypto" +) + +// JWS implements a JWS per RFC 7515. +type JWS interface { + // Payload Returns the payload. + Payload() interface{} + + // SetPayload sets the payload with the given value. + SetPayload(p interface{}) + + // Protected returns the JWS' Protected Header. + Protected() jose.Protected + + // ProtectedAt returns the JWS' Protected Header. + // i represents the index of the Protected Header. + ProtectedAt(i int) jose.Protected + + // Header returns the JWS' unprotected Header. + Header() jose.Header + + // HeaderAt returns the JWS' unprotected Header. + // i represents the index of the unprotected Header. + HeaderAt(i int) jose.Header + + // Verify validates the current JWS' signature as-is. Refer to + // ValidateMulti for more information. + Verify(key interface{}, method crypto.SigningMethod) error + + // ValidateMulti validates the current JWS' signature as-is. Since it's + // meant to be called after parsing a stream of bytes into a JWS, it + // shouldn't do any internal parsing like the Sign, Flat, Compact, or + // General methods do. + VerifyMulti(keys []interface{}, methods []crypto.SigningMethod, o *SigningOpts) error + + // VerifyCallback validates the current JWS' signature as-is. It + // accepts a callback function that can be used to access header + // parameters to lookup needed information. For example, looking + // up the "kid" parameter. + // The return slice must be a slice of keys used in the verification + // of the JWS. + VerifyCallback(fn VerifyCallback, methods []crypto.SigningMethod, o *SigningOpts) error + + // General serializes the JWS into its "general" form per + // https://tools.ietf.org/html/rfc7515#section-7.2.1 + General(keys ...interface{}) ([]byte, error) + + // Flat serializes the JWS to its "flattened" form per + // https://tools.ietf.org/html/rfc7515#section-7.2.2 + Flat(key interface{}) ([]byte, error) + + // Compact serializes the JWS into its "compact" form per + // https://tools.ietf.org/html/rfc7515#section-7.1 + Compact(key interface{}) ([]byte, error) + + // IsJWT returns true if the JWS is a JWT. + IsJWT() bool +} + +// jws represents a specific jws. +type jws struct { + payload *payload + plcache rawBase64 + clean bool + + sb []sigHead + + isJWT bool +} + +// Payload returns the jws' payload. +func (j *jws) Payload() interface{} { + return j.payload.v +} + +// SetPayload sets the jws' raw, unexported payload. +func (j *jws) SetPayload(val interface{}) { + j.payload.v = val +} + +// Protected returns the JWS' Protected Header. +func (j *jws) Protected() jose.Protected { + return j.sb[0].protected +} + +// Protected returns the JWS' Protected Header. +// i represents the index of the Protected Header. +// Left empty, it defaults to 0. +func (j *jws) ProtectedAt(i int) jose.Protected { + return j.sb[i].protected +} + +// Header returns the JWS' unprotected Header. +func (j *jws) Header() jose.Header { + return j.sb[0].unprotected +} + +// HeaderAt returns the JWS' unprotected Header. +// |i| is the index of the unprotected Header. +func (j *jws) HeaderAt(i int) jose.Header { + return j.sb[i].unprotected +} + +// sigHead represents the 'signatures' member of the jws' "general" +// serialization form per +// https://tools.ietf.org/html/rfc7515#section-7.2.1 +// +// It's embedded inside the "flat" structure in order to properly +// create the "flat" jws. +type sigHead struct { + Protected rawBase64 `json:"protected,omitempty"` + Unprotected rawBase64 `json:"header,omitempty"` + Signature crypto.Signature `json:"signature"` + + protected jose.Protected + unprotected jose.Header + clean bool + + method crypto.SigningMethod +} + +func (s *sigHead) unmarshal() error { + if err := s.protected.UnmarshalJSON(s.Protected); err != nil { + return err + } + return s.unprotected.UnmarshalJSON(s.Unprotected) +} + +// New creates a JWS with the provided crypto.SigningMethods. +func New(content interface{}, methods ...crypto.SigningMethod) JWS { + sb := make([]sigHead, len(methods)) + for i := range methods { + sb[i] = sigHead{ + protected: jose.Protected{ + "alg": methods[i].Alg(), + }, + unprotected: jose.Header{}, + method: methods[i], + } + } + return &jws{ + payload: &payload{v: content}, + sb: sb, + } +} + +func (s *sigHead) assignMethod(p jose.Protected) error { + alg, ok := p.Get("alg").(string) + if !ok { + return ErrNoAlgorithm + } + + sm := GetSigningMethod(alg) + if sm == nil { + return ErrNoAlgorithm + } + s.method = sm + return nil +} + +type generic struct { + Payload rawBase64 `json:"payload"` + sigHead + Signatures []sigHead `json:"signatures,omitempty"` +} + +// Parse parses any of the three serialized jws forms into a physical +// jws per https://tools.ietf.org/html/rfc7515#section-5.2 +// +// It accepts a json.Unmarshaler in order to properly parse +// the payload. In order to keep the caller from having to do extra +// parsing of the payload, a json.Unmarshaler can be passed +// which will be then to unmarshal the payload however the caller +// wishes. Do note that if json.Unmarshal returns an error the +// original payload will be used as if no json.Unmarshaler was +// passed. +// +// Internally, Parse applies some heuristics and then calls either +// ParseGeneral, ParseFlat, or ParseCompact. +// It should only be called if, for whatever reason, you do not +// know which form the serialized JWT is in. +// +// It cannot parse a JWT. +func Parse(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + // Try and unmarshal into a generic struct that'll + // hopefully hold either of the two JSON serialization + // formats. + var g generic + + // Not valid JSON. Let's try compact. + if err := json.Unmarshal(encoded, &g); err != nil { + return ParseCompact(encoded, u...) + } + + if g.Signatures == nil { + return g.parseFlat(u...) + } + return g.parseGeneral(u...) +} + +// ParseGeneral parses a jws serialized into its "general" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.1 +// into a physical jws per +// https://tools.ietf.org/html/rfc7515#section-5.2 +// +// For information on the json.Unmarshaler parameter, see Parse. +func ParseGeneral(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + var g generic + if err := json.Unmarshal(encoded, &g); err != nil { + return nil, err + } + return g.parseGeneral(u...) +} + +func (g *generic) parseGeneral(u ...json.Unmarshaler) (JWS, error) { + + var p payload + if len(u) > 0 { + p.u = u[0] + } + + if err := p.UnmarshalJSON(g.Payload); err != nil { + return nil, err + } + + for i := range g.Signatures { + if err := g.Signatures[i].unmarshal(); err != nil { + return nil, err + } + if err := checkHeaders(jose.Header(g.Signatures[i].protected), g.Signatures[i].unprotected); err != nil { + return nil, err + } + + if err := g.Signatures[i].assignMethod(g.Signatures[i].protected); err != nil { + return nil, err + } + } + + g.clean = len(g.Signatures) != 0 + + return &jws{ + payload: &p, + plcache: g.Payload, + clean: true, + sb: g.Signatures, + }, nil +} + +// ParseFlat parses a jws serialized into its "flat" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.2 +// into a physical jws per +// https://tools.ietf.org/html/rfc7515#section-5.2 +// +// For information on the json.Unmarshaler parameter, see Parse. +func ParseFlat(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + var g generic + if err := json.Unmarshal(encoded, &g); err != nil { + return nil, err + } + return g.parseFlat(u...) +} + +func (g *generic) parseFlat(u ...json.Unmarshaler) (JWS, error) { + + var p payload + if len(u) > 0 { + p.u = u[0] + } + + if err := p.UnmarshalJSON(g.Payload); err != nil { + return nil, err + } + + if err := g.sigHead.unmarshal(); err != nil { + return nil, err + } + g.sigHead.clean = true + + if err := checkHeaders(jose.Header(g.sigHead.protected), g.sigHead.unprotected); err != nil { + return nil, err + } + + if err := g.sigHead.assignMethod(g.sigHead.protected); err != nil { + return nil, err + } + + return &jws{ + payload: &p, + plcache: g.Payload, + clean: true, + sb: []sigHead{g.sigHead}, + }, nil +} + +// ParseCompact parses a jws serialized into its "compact" form per +// https://tools.ietf.org/html/rfc7515#section-7.1 +// into a physical jws per +// https://tools.ietf.org/html/rfc7515#section-5.2 +// +// For information on the json.Unmarshaler parameter, see Parse. +func ParseCompact(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + return parseCompact(encoded, false, u...) +} + +func parseCompact(encoded []byte, jwt bool, u ...json.Unmarshaler) (*jws, error) { + + // This section loosely follows + // https://tools.ietf.org/html/rfc7519#section-7.2 + // because it's used to parse _both_ jws and JWTs. + + parts := bytes.Split(encoded, []byte{'.'}) + if len(parts) != 3 { + return nil, ErrNotCompact + } + + var p jose.Protected + if err := p.UnmarshalJSON(parts[0]); err != nil { + return nil, err + } + + s := sigHead{ + Protected: parts[0], + protected: p, + Signature: parts[2], + clean: true, + } + + if err := s.assignMethod(p); err != nil { + return nil, err + } + + var pl payload + if len(u) > 0 { + pl.u = u[0] + } + + j := jws{ + payload: &pl, + plcache: parts[1], + sb: []sigHead{s}, + isJWT: jwt, + } + + if err := j.payload.UnmarshalJSON(parts[1]); err != nil { + return nil, err + } + + j.clean = true + + if err := j.sb[0].Signature.UnmarshalJSON(parts[2]); err != nil { + return nil, err + } + + // https://tools.ietf.org/html/rfc7519#section-7.2.8 + cty, ok := p.Get("cty").(string) + if ok && cty == "JWT" { + return &j, ErrHoldsJWE + } + return &j, nil +} + +var ( + // JWSFormKey is the form "key" which should be used inside + // ParseFromRequest if the request is a multipart.Form. + JWSFormKey = "access_token" + + // MaxMemory is maximum amount of memory which should be used + // inside ParseFromRequest while parsing the multipart.Form + // if the request is a multipart.Form. + MaxMemory int64 = 10e6 +) + +// Format specifies which "format" the JWS is in -- Flat, General, +// or compact. Additionally, constants for JWT/Unknown are added. +type Format uint8 + +const ( + // Unknown format. + Unknown Format = iota + + // Flat format. + Flat + + // General format. + General + + // Compact format. + Compact +) + +var parseJumpTable = [...]func([]byte, ...json.Unmarshaler) (JWS, error){ + Unknown: Parse, + Flat: ParseFlat, + General: ParseGeneral, + Compact: ParseCompact, + 1<<8 - 1: Parse, // Max uint8. +} + +func init() { + for i := range parseJumpTable { + if parseJumpTable[i] == nil { + parseJumpTable[i] = Parse + } + } +} + +func fromHeader(req *http.Request) ([]byte, bool) { + if ah := req.Header.Get("Authorization"); len(ah) > 7 && strings.EqualFold(ah[0:7], "BEARER ") { + return []byte(ah[7:]), true + } + return nil, false +} + +func fromForm(req *http.Request) ([]byte, bool) { + if err := req.ParseMultipartForm(MaxMemory); err != nil { + return nil, false + } + if tokStr := req.Form.Get(JWSFormKey); tokStr != "" { + return []byte(tokStr), true + } + return nil, false +} + +// ParseFromHeader tries to find the JWS in an http.Request header. +func ParseFromHeader(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) { + if b, ok := fromHeader(req); ok { + return parseJumpTable[format](b, u...) + } + return nil, ErrNoTokenInRequest +} + +// ParseFromForm tries to find the JWS in an http.Request form request. +func ParseFromForm(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) { + if b, ok := fromForm(req); ok { + return parseJumpTable[format](b, u...) + } + return nil, ErrNoTokenInRequest +} + +// ParseFromRequest tries to find the JWS in an http.Request. +// This method will call ParseMultipartForm if there's no token in the header. +func ParseFromRequest(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) { + token, err := ParseFromHeader(req, format, u...) + if err == nil { + return token, nil + } + + token, err = ParseFromForm(req, format, u...) + if err == nil { + return token, nil + } + + return nil, err +} + +// IgnoreDupes should be set to true if the internal duplicate header key check +// should ignore duplicate Header keys instead of reporting an error when +// duplicate Header keys are found. +// +// Note: +// Duplicate Header keys are defined in +// https://tools.ietf.org/html/rfc7515#section-5.2 +// meaning keys that both the protected and unprotected +// Headers possess. +var IgnoreDupes bool + +// checkHeaders returns an error per the constraints described in +// IgnoreDupes' comment. +func checkHeaders(a, b jose.Header) error { + if len(a)+len(b) == 0 { + return ErrTwoEmptyHeaders + } + for key := range a { + if b.Has(key) && !IgnoreDupes { + return ErrDuplicateHeaderParameter + } + } + return nil +} + +var _ JWS = (*jws)(nil) diff --git a/vendor/github.com/SermoDigital/jose/jws/jws_serialize.go b/vendor/github.com/SermoDigital/jose/jws/jws_serialize.go new file mode 100644 index 000000000..923fdc224 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/jws_serialize.go @@ -0,0 +1,132 @@ +package jws + +import ( + "bytes" + "encoding/json" +) + +// Flat serializes the JWS to its "flattened" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.2 +func (j *jws) Flat(key interface{}) ([]byte, error) { + if len(j.sb) < 1 { + return nil, ErrNotEnoughMethods + } + if err := j.sign(key); err != nil { + return nil, err + } + return json.Marshal(struct { + Payload rawBase64 `json:"payload"` + sigHead + }{ + Payload: j.plcache, + sigHead: j.sb[0], + }) +} + +// General serializes the JWS into its "general" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.1 +// +// If only one key is passed it's used for all the provided +// crypto.SigningMethods. Otherwise, len(keys) must equal the number +// of crypto.SigningMethods added. +func (j *jws) General(keys ...interface{}) ([]byte, error) { + if err := j.sign(keys...); err != nil { + return nil, err + } + return json.Marshal(struct { + Payload rawBase64 `json:"payload"` + Signatures []sigHead `json:"signatures"` + }{ + Payload: j.plcache, + Signatures: j.sb, + }) +} + +// Compact serializes the JWS into its "compact" form per +// https://tools.ietf.org/html/rfc7515#section-7.1 +func (j *jws) Compact(key interface{}) ([]byte, error) { + if len(j.sb) < 1 { + return nil, ErrNotEnoughMethods + } + + if err := j.sign(key); err != nil { + return nil, err + } + + sig, err := j.sb[0].Signature.Base64() + if err != nil { + return nil, err + } + return format( + j.sb[0].Protected, + j.plcache, + sig, + ), nil +} + +// sign signs each index of j's sb member. +func (j *jws) sign(keys ...interface{}) error { + if err := j.cache(); err != nil { + return err + } + + if len(keys) < 1 || + len(keys) > 1 && len(keys) != len(j.sb) { + return ErrNotEnoughKeys + } + + if len(keys) == 1 { + k := keys[0] + keys = make([]interface{}, len(j.sb)) + for i := range keys { + keys[i] = k + } + } + + for i := range j.sb { + if err := j.sb[i].cache(); err != nil { + return err + } + + raw := format(j.sb[i].Protected, j.plcache) + sig, err := j.sb[i].method.Sign(raw, keys[i]) + if err != nil { + return err + } + j.sb[i].Signature = sig + } + + return nil +} + +// cache marshals the payload, but only if it's changed since the last cache. +func (j *jws) cache() (err error) { + if !j.clean { + j.plcache, err = j.payload.Base64() + j.clean = err == nil + } + return err +} + +// cache marshals the protected and unprotected headers, but only if +// they've changed since their last cache. +func (s *sigHead) cache() (err error) { + if !s.clean { + s.Protected, err = s.protected.Base64() + if err != nil { + return err + } + s.Unprotected, err = s.unprotected.Base64() + if err != nil { + return err + } + } + s.clean = true + return nil +} + +// format formats a slice of bytes in the order given, joining +// them with a period. +func format(a ...[]byte) []byte { + return bytes.Join(a, []byte{'.'}) +} diff --git a/vendor/github.com/SermoDigital/jose/jws/jws_validate.go b/vendor/github.com/SermoDigital/jose/jws/jws_validate.go new file mode 100644 index 000000000..e5e3abd18 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/jws_validate.go @@ -0,0 +1,203 @@ +package jws + +import ( + "fmt" + + "github.com/SermoDigital/jose/crypto" +) + +// VerifyCallback is a callback function that can be used to access header +// parameters to lookup needed information. For example, looking +// up the "kid" parameter. +// The return slice must be a slice of keys used in the verification +// of the JWS. +type VerifyCallback func(JWS) ([]interface{}, error) + +// VerifyCallback validates the current JWS' signature as-is. It +// accepts a callback function that can be used to access header +// parameters to lookup needed information. For example, looking +// up the "kid" parameter. +// The return slice must be a slice of keys used in the verification +// of the JWS. +func (j *jws) VerifyCallback(fn VerifyCallback, methods []crypto.SigningMethod, o *SigningOpts) error { + keys, err := fn(j) + if err != nil { + return err + } + return j.VerifyMulti(keys, methods, o) +} + +// IsMultiError returns true if the given error is type *MultiError. +func IsMultiError(err error) bool { + _, ok := err.(*MultiError) + return ok +} + +// MultiError is a slice of errors. +type MultiError []error + +// Errors implements the error interface. +func (m *MultiError) Error() string { + var s string + var n int + for _, err := range *m { + if err != nil { + if n == 0 { + s = err.Error() + } + n++ + } + } + switch n { + case 0: + return "" + case 1: + return s + case 2: + return s + " and 1 other error" + } + return fmt.Sprintf("%s (and %d other errors)", s, n-1) +} + +// Any means any of the JWS signatures need to verify. +// Refer to verifyMulti for more information. +const Any int = 0 + +// VerifyMulti verifies the current JWS as-is. Since it's meant to be +// called after parsing a stream of bytes into a JWS, it doesn't do any +// internal parsing like the Sign, Flat, Compact, or General methods do. +func (j *jws) VerifyMulti(keys []interface{}, methods []crypto.SigningMethod, o *SigningOpts) error { + + // Catch a simple mistake. Parameter o is irrelevant in this scenario. + if len(keys) == 1 && + len(methods) == 1 && + len(j.sb) == 1 { + return j.Verify(keys[0], methods[0]) + } + + if len(j.sb) != len(methods) { + return ErrNotEnoughMethods + } + + if len(keys) < 1 || + len(keys) > 1 && len(keys) != len(j.sb) { + return ErrNotEnoughKeys + } + + // TODO do this better. + if len(keys) == 1 { + k := keys[0] + keys = make([]interface{}, len(methods)) + for i := range keys { + keys[i] = k + } + } + + var o2 SigningOpts + if o == nil { + o = new(SigningOpts) + } + + var m MultiError + for i := range j.sb { + err := j.sb[i].verify(j.plcache, keys[i], methods[i]) + if err != nil { + m = append(m, err) + } else { + o2.Inc() + if o.Needs(i) { + o.ptr++ + o2.Append(i) + } + } + } + + err := o.Validate(&o2) + if err != nil { + m = append(m, err) + } + if len(m) == 0 { + return nil + } + return &m +} + +// SigningOpts is a struct which holds options for validating +// JWS signatures. +// Number represents the cumulative which signatures need to verify +// in order for the JWS to be considered valid. +// Leave 'Number' empty or set it to the constant 'Any' if any number of +// valid signatures (greater than one) should verify the JWS. +// +// Use the indices of the signatures that need to verify in order +// for the JWS to be considered valid if specific signatures need +// to verify in order for the JWS to be considered valid. +// +// Note: +// The JWS spec requires *at least* one +// signature to verify in order for the JWS to be considered valid. +type SigningOpts struct { + // Minimum of signatures which need to verify. + Number int + + // Indices of specific signatures which need to verify. + Indices []int + ptr int + + _ struct{} +} + +// Append appends x to s' Indices member. +func (s *SigningOpts) Append(x int) { + s.Indices = append(s.Indices, x) +} + +// Needs returns true if x resides inside s' Indices member +// for the given index. It's used to match two SigningOpts Indices members. +func (s *SigningOpts) Needs(x int) bool { + return s.ptr < len(s.Indices) && s.Indices[s.ptr] == x +} + +// Inc increments s' Number member by one. +func (s *SigningOpts) Inc() { s.Number++ } + +// Validate returns any errors found while validating the +// provided SigningOpts. The receiver validates |have|. +// It'll return an error if the passed SigningOpts' Number member is less +// than s' or if the passed SigningOpts' Indices slice isn't equal to s'. +func (s *SigningOpts) Validate(have *SigningOpts) error { + if have.Number < s.Number || + (s.Indices != nil && + !eq(s.Indices, have.Indices)) { + return ErrNotEnoughValidSignatures + } + return nil +} + +func eq(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// Verify verifies the current JWS as-is. Refer to verifyMulti +// for more information. +func (j *jws) Verify(key interface{}, method crypto.SigningMethod) error { + if len(j.sb) < 1 { + return ErrCannotValidate + } + return j.sb[0].verify(j.plcache, key, method) +} + +func (s *sigHead) verify(pl []byte, key interface{}, method crypto.SigningMethod) error { + if s.method.Alg() != method.Alg() || s.method.Hasher() != method.Hasher() { + return ErrMismatchedAlgorithms + } + return method.Verify(format(s.Protected, pl), s.Signature, key) +} diff --git a/vendor/github.com/SermoDigital/jose/jws/jwt.go b/vendor/github.com/SermoDigital/jose/jws/jwt.go new file mode 100644 index 000000000..53da1fcf7 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/jwt.go @@ -0,0 +1,115 @@ +package jws + +import ( + "net/http" + "time" + + "github.com/SermoDigital/jose" + "github.com/SermoDigital/jose/crypto" + "github.com/SermoDigital/jose/jwt" +) + +// NewJWT creates a new JWT with the given claims. +func NewJWT(claims Claims, method crypto.SigningMethod) jwt.JWT { + j, ok := New(claims, method).(*jws) + if !ok { + panic("jws.NewJWT: runtime panic: New(...).(*jws) != true") + } + j.sb[0].protected.Set("typ", "JWT") + j.isJWT = true + return j +} + +// Serialize helps implements jwt.JWT. +func (j *jws) Serialize(key interface{}) ([]byte, error) { + if j.isJWT { + return j.Compact(key) + } + return nil, ErrIsNotJWT +} + +// Claims helps implements jwt.JWT. +func (j *jws) Claims() jwt.Claims { + if j.isJWT { + if c, ok := j.payload.v.(Claims); ok { + return jwt.Claims(c) + } + } + return nil +} + +// ParseJWTFromRequest tries to find the JWT in an http.Request. +// This method will call ParseMultipartForm if there's no token in the header. +func ParseJWTFromRequest(req *http.Request) (jwt.JWT, error) { + if b, ok := fromHeader(req); ok { + return ParseJWT(b) + } + if b, ok := fromForm(req); ok { + return ParseJWT(b) + } + return nil, ErrNoTokenInRequest +} + +// ParseJWT parses a serialized jwt.JWT into a physical jwt.JWT. +// If its payload isn't a set of claims (or able to be coerced into +// a set of claims) it'll return an error stating the +// JWT isn't a JWT. +func ParseJWT(encoded []byte) (jwt.JWT, error) { + t, err := parseCompact(encoded, true) + if err != nil { + return nil, err + } + c, ok := t.Payload().(map[string]interface{}) + if !ok { + return nil, ErrIsNotJWT + } + t.SetPayload(Claims(c)) + return t, nil +} + +// IsJWT returns true if the JWS is a JWT. +func (j *jws) IsJWT() bool { + return j.isJWT +} + +func (j *jws) Validate(key interface{}, m crypto.SigningMethod, v ...*jwt.Validator) error { + if j.isJWT { + if err := j.Verify(key, m); err != nil { + return err + } + var v1 jwt.Validator + if len(v) > 0 { + v1 = *v[0] + } + c, ok := j.payload.v.(Claims) + if ok { + if err := v1.Validate(j); err != nil { + return err + } + return jwt.Claims(c).Validate(jose.Now(), v1.EXP, v1.NBF) + } + } + return ErrIsNotJWT +} + +// Conv converts a func(Claims) error to type jwt.ValidateFunc. +func Conv(fn func(Claims) error) jwt.ValidateFunc { + if fn == nil { + return nil + } + return func(c jwt.Claims) error { + return fn(Claims(c)) + } +} + +// NewValidator returns a jwt.Validator. +func NewValidator(c Claims, exp, nbf time.Duration, fn func(Claims) error) *jwt.Validator { + return &jwt.Validator{ + Expected: jwt.Claims(c), + EXP: exp, + NBF: nbf, + Fn: Conv(fn), + } +} + +var _ jwt.JWT = (*jws)(nil) diff --git a/vendor/github.com/SermoDigital/jose/jws/payload.go b/vendor/github.com/SermoDigital/jose/jws/payload.go new file mode 100644 index 000000000..58bfd066f --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/payload.go @@ -0,0 +1,52 @@ +package jws + +import ( + "encoding/json" + + "github.com/SermoDigital/jose" +) + +// payload represents the payload of a JWS. +type payload struct { + v interface{} + u json.Unmarshaler + _ struct{} +} + +// MarshalJSON implements json.Marshaler for payload. +func (p *payload) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(p.v) + if err != nil { + return nil, err + } + return jose.EncodeEscape(b), nil +} + +// Base64 implements jose.Encoder. +func (p *payload) Base64() ([]byte, error) { + b, err := json.Marshal(p.v) + if err != nil { + return nil, err + } + return jose.Base64Encode(b), nil +} + +// MarshalJSON implements json.Unmarshaler for payload. +func (p *payload) UnmarshalJSON(b []byte) error { + b2, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + if p.u != nil { + err := p.u.UnmarshalJSON(b2) + p.v = p.u + return err + } + return json.Unmarshal(b2, &p.v) +} + +var ( + _ json.Marshaler = (*payload)(nil) + _ json.Unmarshaler = (*payload)(nil) + _ jose.Encoder = (*payload)(nil) +) diff --git a/vendor/github.com/SermoDigital/jose/jws/rawbase64.go b/vendor/github.com/SermoDigital/jose/jws/rawbase64.go new file mode 100644 index 000000000..f2c406048 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/rawbase64.go @@ -0,0 +1,28 @@ +package jws + +import "encoding/json" + +type rawBase64 []byte + +// MarshalJSON implements json.Marshaler for rawBase64. +func (r rawBase64) MarshalJSON() ([]byte, error) { + buf := make([]byte, len(r)+2) + buf[0] = '"' + copy(buf[1:], r) + buf[len(buf)-1] = '"' + return buf, nil +} + +// MarshalJSON implements json.Unmarshaler for rawBase64. +func (r *rawBase64) UnmarshalJSON(b []byte) error { + if len(b) > 1 && b[0] == '"' && b[len(b)-1] == '"' { + b = b[1 : len(b)-1] + } + *r = rawBase64(b) + return nil +} + +var ( + _ json.Marshaler = (rawBase64)(nil) + _ json.Unmarshaler = (*rawBase64)(nil) +) diff --git a/vendor/github.com/SermoDigital/jose/jws/signing_methods.go b/vendor/github.com/SermoDigital/jose/jws/signing_methods.go new file mode 100644 index 000000000..525806f4a --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jws/signing_methods.go @@ -0,0 +1,63 @@ +package jws + +import ( + "sync" + + "github.com/SermoDigital/jose/crypto" +) + +var ( + mu sync.RWMutex + + signingMethods = map[string]crypto.SigningMethod{ + crypto.SigningMethodES256.Alg(): crypto.SigningMethodES256, + crypto.SigningMethodES384.Alg(): crypto.SigningMethodES384, + crypto.SigningMethodES512.Alg(): crypto.SigningMethodES512, + + crypto.SigningMethodPS256.Alg(): crypto.SigningMethodPS256, + crypto.SigningMethodPS384.Alg(): crypto.SigningMethodPS384, + crypto.SigningMethodPS512.Alg(): crypto.SigningMethodPS512, + + crypto.SigningMethodRS256.Alg(): crypto.SigningMethodRS256, + crypto.SigningMethodRS384.Alg(): crypto.SigningMethodRS384, + crypto.SigningMethodRS512.Alg(): crypto.SigningMethodRS512, + + crypto.SigningMethodHS256.Alg(): crypto.SigningMethodHS256, + crypto.SigningMethodHS384.Alg(): crypto.SigningMethodHS384, + crypto.SigningMethodHS512.Alg(): crypto.SigningMethodHS512, + + crypto.Unsecured.Alg(): crypto.Unsecured, + } +) + +// RegisterSigningMethod registers the crypto.SigningMethod in the global map. +// This is typically done inside the caller's init function. +func RegisterSigningMethod(sm crypto.SigningMethod) { + alg := sm.Alg() + if GetSigningMethod(alg) != nil { + panic("jose/jws: cannot duplicate signing methods") + } + + if !sm.Hasher().Available() { + panic("jose/jws: specific hash is unavailable") + } + + mu.Lock() + signingMethods[alg] = sm + mu.Unlock() +} + +// RemoveSigningMethod removes the crypto.SigningMethod from the global map. +func RemoveSigningMethod(sm crypto.SigningMethod) { + mu.Lock() + delete(signingMethods, sm.Alg()) + mu.Unlock() +} + +// GetSigningMethod retrieves a crypto.SigningMethod from the global map. +func GetSigningMethod(alg string) (method crypto.SigningMethod) { + mu.RLock() + method = signingMethods[alg] + mu.RUnlock() + return method +} diff --git a/vendor/github.com/SermoDigital/jose/jwt/claims.go b/vendor/github.com/SermoDigital/jose/jwt/claims.go new file mode 100644 index 000000000..d3d93bfb5 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jwt/claims.go @@ -0,0 +1,274 @@ +package jwt + +import ( + "encoding/json" + "time" + + "github.com/SermoDigital/jose" +) + +// Claims implements a set of JOSE Claims with the addition of some helper +// methods, similar to net/url.Values. +type Claims map[string]interface{} + +// Validate validates the Claims per the claims found in +// https://tools.ietf.org/html/rfc7519#section-4.1 +func (c Claims) Validate(now time.Time, expLeeway, nbfLeeway time.Duration) error { + if exp, ok := c.Expiration(); ok { + if now.After(exp.Add(expLeeway)) { + return ErrTokenIsExpired + } + } + + if nbf, ok := c.NotBefore(); ok { + if !now.After(nbf.Add(-nbfLeeway)) { + return ErrTokenNotYetValid + } + } + return nil +} + +// Get retrieves the value corresponding with key from the Claims. +func (c Claims) Get(key string) interface{} { + if c == nil { + return nil + } + return c[key] +} + +// Set sets Claims[key] = val. It'll overwrite without warning. +func (c Claims) Set(key string, val interface{}) { + c[key] = val +} + +// Del removes the value that corresponds with key from the Claims. +func (c Claims) Del(key string) { + delete(c, key) +} + +// Has returns true if a value for the given key exists inside the Claims. +func (c Claims) Has(key string) bool { + _, ok := c[key] + return ok +} + +// MarshalJSON implements json.Marshaler for Claims. +func (c Claims) MarshalJSON() ([]byte, error) { + if c == nil || len(c) == 0 { + return nil, nil + } + return json.Marshal(map[string]interface{}(c)) +} + +// Base64 implements the jose.Encoder interface. +func (c Claims) Base64() ([]byte, error) { + b, err := c.MarshalJSON() + if err != nil { + return nil, err + } + return jose.Base64Encode(b), nil +} + +// UnmarshalJSON implements json.Unmarshaler for Claims. +func (c *Claims) UnmarshalJSON(b []byte) error { + if b == nil { + return nil + } + + b, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + + // Since json.Unmarshal calls UnmarshalJSON, + // calling json.Unmarshal on *p would be infinitely recursive + // A temp variable is needed because &map[string]interface{}(*p) is + // invalid Go. (Address of unaddressable object and all that...) + + tmp := map[string]interface{}(*c) + if err = json.Unmarshal(b, &tmp); err != nil { + return err + } + *c = Claims(tmp) + return nil +} + +// Issuer retrieves claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) Issuer() (string, bool) { + v, ok := c.Get("iss").(string) + return v, ok +} + +// Subject retrieves claim "sub" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) Subject() (string, bool) { + v, ok := c.Get("sub").(string) + return v, ok +} + +// Audience retrieves claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) Audience() ([]string, bool) { + // Audience claim must be stringy. That is, it may be one string + // or multiple strings but it should not be anything else. E.g. an int. + switch t := c.Get("aud").(type) { + case string: + return []string{t}, true + case []string: + return t, true + case []interface{}: + return stringify(t...) + case interface{}: + return stringify(t) + } + return nil, false +} + +func stringify(a ...interface{}) ([]string, bool) { + if len(a) == 0 { + return nil, false + } + + s := make([]string, len(a)) + for i := range a { + str, ok := a[i].(string) + if !ok { + return nil, false + } + s[i] = str + } + return s, true +} + +// Expiration retrieves claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) Expiration() (time.Time, bool) { + return c.GetTime("exp") +} + +// NotBefore retrieves claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) NotBefore() (time.Time, bool) { + return c.GetTime("nbf") +} + +// IssuedAt retrieves claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) IssuedAt() (time.Time, bool) { + return c.GetTime("iat") +} + +// JWTID retrieves claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) JWTID() (string, bool) { + v, ok := c.Get("jti").(string) + return v, ok +} + +// RemoveIssuer deletes claim "iss" from c. +func (c Claims) RemoveIssuer() { c.Del("iss") } + +// RemoveSubject deletes claim "sub" from c. +func (c Claims) RemoveSubject() { c.Del("sub") } + +// RemoveAudience deletes claim "aud" from c. +func (c Claims) RemoveAudience() { c.Del("aud") } + +// RemoveExpiration deletes claim "exp" from c. +func (c Claims) RemoveExpiration() { c.Del("exp") } + +// RemoveNotBefore deletes claim "nbf" from c. +func (c Claims) RemoveNotBefore() { c.Del("nbf") } + +// RemoveIssuedAt deletes claim "iat" from c. +func (c Claims) RemoveIssuedAt() { c.Del("iat") } + +// RemoveJWTID deletes claim "jti" from c. +func (c Claims) RemoveJWTID() { c.Del("jti") } + +// SetIssuer sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) SetIssuer(issuer string) { + c.Set("iss", issuer) +} + +// SetSubject sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) SetSubject(subject string) { + c.Set("sub", subject) +} + +// SetAudience sets claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) SetAudience(audience ...string) { + if len(audience) == 1 { + c.Set("aud", audience[0]) + } else { + c.Set("aud", audience) + } +} + +// SetExpiration sets claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) SetExpiration(expiration time.Time) { + c.SetTime("exp", expiration) +} + +// SetNotBefore sets claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) SetNotBefore(notBefore time.Time) { + c.SetTime("nbf", notBefore) +} + +// SetIssuedAt sets claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) SetIssuedAt(issuedAt time.Time) { + c.SetTime("iat", issuedAt) +} + +// SetJWTID sets claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) SetJWTID(uniqueID string) { + c.Set("jti", uniqueID) +} + +// GetTime returns a Unix timestamp for the given key. +// +// It converts an int, int32, int64, uint, uint32, uint64 or float64 into a Unix +// timestamp (epoch seconds). float32 does not have sufficient precision to +// store a Unix timestamp. +// +// Numeric values parsed from JSON will always be stored as float64 since +// Claims is a map[string]interface{}. However, the values may be stored directly +// in the claims as a different type. +func (c Claims) GetTime(key string) (time.Time, bool) { + switch t := c.Get(key).(type) { + case int: + return time.Unix(int64(t), 0), true + case int32: + return time.Unix(int64(t), 0), true + case int64: + return time.Unix(int64(t), 0), true + case uint: + return time.Unix(int64(t), 0), true + case uint32: + return time.Unix(int64(t), 0), true + case uint64: + return time.Unix(int64(t), 0), true + case float64: + return time.Unix(int64(t), 0), true + default: + return time.Time{}, false + } +} + +// SetTime stores a UNIX time for the given key. +func (c Claims) SetTime(key string, t time.Time) { + c.Set(key, t.Unix()) +} + +var ( + _ json.Marshaler = (Claims)(nil) + _ json.Unmarshaler = (*Claims)(nil) +) diff --git a/vendor/github.com/SermoDigital/jose/jwt/doc.go b/vendor/github.com/SermoDigital/jose/jwt/doc.go new file mode 100644 index 000000000..6004d0fa9 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jwt/doc.go @@ -0,0 +1,2 @@ +// Package jwt implements JWTs per RFC 7519 +package jwt diff --git a/vendor/github.com/SermoDigital/jose/jwt/eq.go b/vendor/github.com/SermoDigital/jose/jwt/eq.go new file mode 100644 index 000000000..3113269fb --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jwt/eq.go @@ -0,0 +1,47 @@ +package jwt + +func verifyPrincipals(pcpls, auds []string) bool { + // "Each principal intended to process the JWT MUST + // identify itself with a value in the audience claim." + // - https://tools.ietf.org/html/rfc7519#section-4.1.3 + + found := -1 + for i, p := range pcpls { + for _, v := range auds { + if p == v { + found++ + break + } + } + if found != i { + return false + } + } + return true +} + +// ValidAudience returns true iff: +// - a and b are strings and a == b +// - a is string, b is []string and a is in b +// - a is []string, b is []string and all of a is in b +// - a is []string, b is string and len(a) == 1 and a[0] == b +func ValidAudience(a, b interface{}) bool { + s1, ok := a.(string) + if ok { + if s2, ok := b.(string); ok { + return s1 == s2 + } + a2, ok := b.([]string) + return ok && verifyPrincipals([]string{s1}, a2) + } + + a1, ok := a.([]string) + if !ok { + return false + } + if a2, ok := b.([]string); ok { + return verifyPrincipals(a1, a2) + } + s2, ok := b.(string) + return ok && len(a1) == 1 && a1[0] == s2 +} diff --git a/vendor/github.com/SermoDigital/jose/jwt/errors.go b/vendor/github.com/SermoDigital/jose/jwt/errors.go new file mode 100644 index 000000000..96b240d54 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jwt/errors.go @@ -0,0 +1,28 @@ +package jwt + +import "errors" + +var ( + // ErrTokenIsExpired is return when time.Now().Unix() is after + // the token's "exp" claim. + ErrTokenIsExpired = errors.New("token is expired") + + // ErrTokenNotYetValid is return when time.Now().Unix() is before + // the token's "nbf" claim. + ErrTokenNotYetValid = errors.New("token is not yet valid") + + // ErrInvalidISSClaim means the "iss" claim is invalid. + ErrInvalidISSClaim = errors.New("claim \"iss\" is invalid") + + // ErrInvalidSUBClaim means the "sub" claim is invalid. + ErrInvalidSUBClaim = errors.New("claim \"sub\" is invalid") + + // ErrInvalidIATClaim means the "iat" claim is invalid. + ErrInvalidIATClaim = errors.New("claim \"iat\" is invalid") + + // ErrInvalidJTIClaim means the "jti" claim is invalid. + ErrInvalidJTIClaim = errors.New("claim \"jti\" is invalid") + + // ErrInvalidAUDClaim means the "aud" claim is invalid. + ErrInvalidAUDClaim = errors.New("claim \"aud\" is invalid") +) diff --git a/vendor/github.com/SermoDigital/jose/jwt/jwt.go b/vendor/github.com/SermoDigital/jose/jwt/jwt.go new file mode 100644 index 000000000..feb17126f --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/jwt/jwt.go @@ -0,0 +1,144 @@ +package jwt + +import ( + "time" + + "github.com/SermoDigital/jose/crypto" +) + +// JWT represents a JWT per RFC 7519. +// It's described as an interface instead of a physical structure +// because both JWS and JWEs can be JWTs. So, in order to use either, +// import one of those two packages and use their "NewJWT" (and other) +// functions. +type JWT interface { + // Claims returns the set of Claims. + Claims() Claims + + // Validate returns an error describing any issues found while + // validating the JWT. For info on the fn parameter, see the + // comment on ValidateFunc. + Validate(key interface{}, method crypto.SigningMethod, v ...*Validator) error + + // Serialize serializes the JWT into its on-the-wire + // representation. + Serialize(key interface{}) ([]byte, error) +} + +// ValidateFunc is a function that provides access to the JWT +// and allows for custom validation. Keep in mind that the Verify +// methods in the JWS/JWE sibling packages call ValidateFunc *after* +// validating the JWS/JWE, but *before* any validation per the JWT +// RFC. Therefore, the ValidateFunc can be used to short-circuit +// verification, but cannot be used to circumvent the RFC. +// Custom JWT implementations are free to abuse this, but it is +// not recommended. +type ValidateFunc func(Claims) error + +// Validator represents some of the validation options. +type Validator struct { + Expected Claims // If non-nil, these are required to match. + EXP time.Duration // EXPLeeway + NBF time.Duration // NBFLeeway + Fn ValidateFunc // See ValidateFunc for more information. + + _ struct{} // Require explicitly-named struct fields. +} + +// Validate validates the JWT based on the expected claims in v. +// Note: it only validates the registered claims per +// https://tools.ietf.org/html/rfc7519#section-4.1 +// +// Custom claims should be validated using v's Fn member. +func (v *Validator) Validate(j JWT) error { + if iss, ok := v.Expected.Issuer(); ok && + j.Claims().Get("iss") != iss { + return ErrInvalidISSClaim + } + if sub, ok := v.Expected.Subject(); ok && + j.Claims().Get("sub") != sub { + return ErrInvalidSUBClaim + } + if iat, ok := v.Expected.IssuedAt(); ok { + if t, ok := j.Claims().GetTime("iat"); !t.Equal(iat) || !ok { + return ErrInvalidIATClaim + } + } + if jti, ok := v.Expected.JWTID(); ok && + j.Claims().Get("jti") != jti { + return ErrInvalidJTIClaim + } + + if aud, ok := v.Expected.Audience(); ok { + aud2, ok := j.Claims().Audience() + if !ok || !ValidAudience(aud, aud2) { + return ErrInvalidAUDClaim + } + } + + if v.Fn != nil { + return v.Fn(j.Claims()) + } + return nil +} + +// SetClaim sets the claim with the given val. +func (v *Validator) SetClaim(claim string, val interface{}) { + v.expect() + v.Expected.Set(claim, val) +} + +// SetIssuer sets the "iss" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (v *Validator) SetIssuer(iss string) { + v.expect() + v.Expected.Set("iss", iss) +} + +// SetSubject sets the "sub" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (v *Validator) SetSubject(sub string) { + v.expect() + v.Expected.Set("sub", sub) +} + +// SetAudience sets the "aud" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (v *Validator) SetAudience(aud string) { + v.expect() + v.Expected.Set("aud", aud) +} + +// SetExpiration sets the "exp" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (v *Validator) SetExpiration(exp time.Time) { + v.expect() + v.Expected.Set("exp", exp) +} + +// SetNotBefore sets the "nbf" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (v *Validator) SetNotBefore(nbf time.Time) { + v.expect() + v.Expected.Set("nbf", nbf) +} + +// SetIssuedAt sets the "iat" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (v *Validator) SetIssuedAt(iat time.Time) { + v.expect() + v.Expected.Set("iat", iat) +} + +// SetJWTID sets the "jti" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (v *Validator) SetJWTID(jti string) { + v.expect() + v.Expected.Set("jti", jti) +} + +func (v *Validator) expect() { + if v.Expected == nil { + v.Expected = make(Claims) + } +} diff --git a/vendor/github.com/SermoDigital/jose/time.go b/vendor/github.com/SermoDigital/jose/time.go new file mode 100644 index 000000000..f366a7a67 --- /dev/null +++ b/vendor/github.com/SermoDigital/jose/time.go @@ -0,0 +1,6 @@ +package jose + +import "time" + +// Now returns the current time in UTC. +func Now() time.Time { return time.Now().UTC() } diff --git a/vendor/vendor.json b/vendor/vendor.json index 4bb0379fa..7ef41333f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -60,6 +60,30 @@ "revision": "2a3aa15961d5fee6047b8151b67ac2f08ba2c48c", "revisionTime": "2016-11-16T21:39:02Z" }, + { + "checksumSHA1": "t+uej2kiyqRyQYguygI8t9nJH2w=", + "path": "github.com/SermoDigital/jose", + "revision": "2bd9b81ac51d6d6134fcd4fd846bd2e7347a15f9", + "revisionTime": "2016-12-05T22:51:55Z" + }, + { + "checksumSHA1": "u92C5yEz1FLBeoXp8jujn3tUNFI=", + "path": "github.com/SermoDigital/jose/crypto", + "revision": "2bd9b81ac51d6d6134fcd4fd846bd2e7347a15f9", + "revisionTime": "2016-12-05T22:51:55Z" + }, + { + "checksumSHA1": "FJP5enLaw1JzZNu6Fen+eEKY7Lo=", + "path": "github.com/SermoDigital/jose/jws", + "revision": "2bd9b81ac51d6d6134fcd4fd846bd2e7347a15f9", + "revisionTime": "2016-12-05T22:51:55Z" + }, + { + "checksumSHA1": "3gGGWQ3PcKHE+2cVlkJmIlBfBlw=", + "path": "github.com/SermoDigital/jose/jwt", + "revision": "2bd9b81ac51d6d6134fcd4fd846bd2e7347a15f9", + "revisionTime": "2016-12-05T22:51:55Z" + }, { "checksumSHA1": "LLVyR2dAgkihu0+HdZF+JK0gMMs=", "path": "github.com/agl/ed25519",