Migrate from SermoDigital go Square JOSE (#6445)
This commit is contained in:
parent
7d945f2ddd
commit
6797e21f54
|
@ -7,7 +7,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SermoDigital/jose/jws"
|
||||
squarejwt "gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/salt"
|
||||
|
@ -481,12 +482,15 @@ func parseVaultTokenFromJWT(token string) *string {
|
|||
return nil
|
||||
}
|
||||
|
||||
wt, err := jws.ParseJWT([]byte(token))
|
||||
if err != nil || wt == nil {
|
||||
parsedJWT, err := squarejwt.ParseSigned(token)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, _ := wt.Claims().JWTID()
|
||||
var claims squarejwt.Claims
|
||||
if err = parsedJWT.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &result
|
||||
return &claims.ID
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/SermoDigital/jose/jws"
|
||||
squarejwt "gopkg.in/square/go-jose.v2/jwt"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/api"
|
||||
|
@ -133,22 +134,23 @@ func VaultPluginTLSProvider(apiTLSConfig *api.TLSConfig) func() (*tls.Config, er
|
|||
return func() (*tls.Config, error) {
|
||||
unwrapToken := os.Getenv(PluginUnwrapTokenEnv)
|
||||
|
||||
// Parse the JWT and retrieve the vault address
|
||||
wt, err := jws.ParseJWT([]byte(unwrapToken))
|
||||
parsedJWT, err := squarejwt.ParseSigned(unwrapToken)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("error decoding token: {{err}}", err)
|
||||
}
|
||||
if wt == nil {
|
||||
return nil, errors.New("nil decoded token")
|
||||
return nil, errwrap.Wrapf("error parsing wrapping token: {{err}}", err)
|
||||
}
|
||||
|
||||
addrRaw := wt.Claims().Get("addr")
|
||||
if addrRaw == nil {
|
||||
return nil, errors.New("decoded token does not contain the active node's api_addr")
|
||||
var allClaims = make(map[string]interface{})
|
||||
if err = parsedJWT.UnsafeClaimsWithoutVerification(&allClaims); err != nil {
|
||||
return nil, errwrap.Wrapf("error parsing claims from wrapping token: {{err}}", err)
|
||||
}
|
||||
vaultAddr, ok := addrRaw.(string)
|
||||
|
||||
addrClaimRaw, ok := allClaims["addr"]
|
||||
if !ok {
|
||||
return nil, errors.New("decoded token's api_addr not valid")
|
||||
return nil, errors.New("could not validate addr claim")
|
||||
}
|
||||
vaultAddr, ok := addrClaimRaw.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("could not parse addr claim")
|
||||
}
|
||||
if vaultAddr == "" {
|
||||
return nil, errors.New(`no vault api_addr found`)
|
||||
|
|
|
@ -6,19 +6,19 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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/certutil"
|
||||
"github.com/hashicorp/vault/helper/consts"
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
squarejwt "gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -165,26 +165,44 @@ DONELISTHANDLING:
|
|||
switch resp.WrapInfo.Format {
|
||||
case "jwt":
|
||||
// Create the JWT
|
||||
claims := jws.Claims{}
|
||||
// Map the JWT ID to the token ID for ease of use
|
||||
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 := squarejwt.Claims{
|
||||
// Map the JWT ID to the token ID for ease of use
|
||||
ID: te.ID,
|
||||
// Set the issue time to the creation time
|
||||
IssuedAt: squarejwt.NewNumericDate(creationTime),
|
||||
// Set the expiration to the TTL
|
||||
Expiry: squarejwt.NewNumericDate(creationTime.Add(resp.WrapInfo.TTL)),
|
||||
// Set a reasonable not-before time; since unwrapping happens on this
|
||||
// node we shouldn't have to worry much about drift
|
||||
NotBefore: squarejwt.NewNumericDate(time.Now().Add(-5 * time.Second)),
|
||||
}
|
||||
claims.Set("type", "wrapping")
|
||||
claims.Set("addr", c.redirectAddr)
|
||||
jwt := jws.NewJWT(claims, crypto.SigningMethodES512)
|
||||
serWebToken, err := jwt.Serialize(c.wrappingJWTKey)
|
||||
type privateClaims struct {
|
||||
Accessor string `json:"accessor"`
|
||||
Type string `json:"type"`
|
||||
Addr string `json:"addr"`
|
||||
}
|
||||
priClaims := &privateClaims{
|
||||
Type: "wrapping",
|
||||
Addr: c.redirectAddr,
|
||||
}
|
||||
if resp.Auth != nil {
|
||||
priClaims.Accessor = resp.Auth.Accessor
|
||||
}
|
||||
sig, err := jose.NewSigner(
|
||||
jose.SigningKey{Algorithm: jose.ES512, Key: c.wrappingJWTKey},
|
||||
(&jose.SignerOptions{}).WithType("JWT"))
|
||||
if err != nil {
|
||||
c.tokenStore.revokeOrphan(ctx, te.ID)
|
||||
c.logger.Error("failed to create JWT builder", "error", err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
ser, err := squarejwt.Signed(sig).Claims(claims).Claims(priClaims).CompactSerialize()
|
||||
if err != nil {
|
||||
c.tokenStore.revokeOrphan(ctx, te.ID)
|
||||
c.logger.Error("failed to serialize JWT", "error", err)
|
||||
return nil, ErrInternalError
|
||||
}
|
||||
resp.WrapInfo.Token = string(serWebToken)
|
||||
resp.WrapInfo.Token = ser
|
||||
if c.redirectAddr == "" {
|
||||
resp.AddWarning("No redirect address set in Vault so none could be encoded in the token. You may need to supply Vault's API address when unwrapping the token.")
|
||||
}
|
||||
|
@ -317,24 +335,33 @@ func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request)
|
|||
// 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()
|
||||
// We override the given request client token so that the rest of
|
||||
// Vault sees the real value. This also ensures audit logs are
|
||||
// consistent with the actual token that was issued.
|
||||
if !thirdParty {
|
||||
req.ClientToken = token
|
||||
} else {
|
||||
req.Data["token"] = token
|
||||
}
|
||||
// Implement the jose library way
|
||||
parsedJWT, err := squarejwt.ParseSigned(token)
|
||||
if err != nil {
|
||||
return false, errwrap.Wrapf("wrapping token could not be parsed: {{err}}", err)
|
||||
}
|
||||
var claims squarejwt.Claims
|
||||
var allClaims = make(map[string]interface{})
|
||||
if err = parsedJWT.Claims(&c.wrappingJWTKey.PublicKey, &claims, &allClaims); err != nil {
|
||||
return false, errwrap.Wrapf("wrapping token signature could not be validated: {{err}}", err)
|
||||
}
|
||||
typeClaimRaw, ok := allClaims["type"]
|
||||
if !ok {
|
||||
return false, errors.New("could not validate type claim")
|
||||
}
|
||||
typeClaim, ok := typeClaimRaw.(string)
|
||||
if !ok {
|
||||
return false, errors.New("could not parse type claim")
|
||||
}
|
||||
if typeClaim != "wrapping" {
|
||||
return false, errors.New("unexpected type claim")
|
||||
}
|
||||
if !thirdParty {
|
||||
req.ClientToken = claims.ID
|
||||
} else {
|
||||
req.Data["token"] = claims.ID
|
||||
}
|
||||
token = claims.ID
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
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.
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
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
|
|
@ -1,8 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
go build ./...
|
||||
go test ./...
|
||||
golint ./...
|
||||
go vet ./...
|
|
@ -1,44 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
// Package crypto implements "SigningMethods" and "EncryptionMethods";
|
||||
// that is, ways to sign and encrypt JWS and JWEs, respectively, as well
|
||||
// as JWTs.
|
||||
package crypto
|
|
@ -1,117 +0,0 @@
|
|||
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)
|
|
@ -1,48 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
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")
|
||||
)
|
|
@ -1,81 +0,0 @@
|
|||
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)
|
|
@ -1,72 +0,0 @@
|
|||
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)
|
|
@ -1,80 +0,0 @@
|
|||
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)
|
|
@ -1,96 +0,0 @@
|
|||
// +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)
|
|
@ -1,70 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
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)
|
||||
)
|
|
@ -1,24 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// Package jose implements some helper functions and types for the children
|
||||
// packages, jws, jwt, and jwe.
|
||||
package jose
|
|
@ -1,124 +0,0 @@
|
|||
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)
|
||||
)
|
|
@ -1,190 +0,0 @@
|
|||
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)
|
||||
)
|
|
@ -1,2 +0,0 @@
|
|||
// Package jws implements JWSs per RFC 7515
|
||||
package jws
|
|
@ -1,62 +0,0 @@
|
|||
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")
|
||||
)
|
|
@ -1,490 +0,0 @@
|
|||
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)
|
|
@ -1,132 +0,0 @@
|
|||
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{'.'})
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
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)
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
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)
|
|
@ -1,52 +0,0 @@
|
|||
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)
|
||||
)
|
|
@ -1,28 +0,0 @@
|
|||
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)
|
||||
)
|
|
@ -1,63 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
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)
|
||||
)
|
|
@ -1,2 +0,0 @@
|
|||
// Package jwt implements JWTs per RFC 7519
|
||||
package jwt
|
|
@ -1,47 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
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")
|
||||
)
|
|
@ -1,144 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package jose
|
||||
|
||||
import "time"
|
||||
|
||||
// Now returns the current time in UTC.
|
||||
func Now() time.Time { return time.Now().UTC() }
|
|
@ -270,30 +270,6 @@
|
|||
"revision": "c55cd652ac372df54ed1c9f05eba4351a3f3f2bd",
|
||||
"revisionTime": "2018-09-09T23:36:13Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "t+uej2kiyqRyQYguygI8t9nJH2w=",
|
||||
"path": "github.com/SermoDigital/jose",
|
||||
"revision": "803625baeddc3526d01d321b5066029f53eafc81",
|
||||
"revisionTime": "2018-01-04T20:38:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "u92C5yEz1FLBeoXp8jujn3tUNFI=",
|
||||
"path": "github.com/SermoDigital/jose/crypto",
|
||||
"revision": "803625baeddc3526d01d321b5066029f53eafc81",
|
||||
"revisionTime": "2018-01-04T20:38:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "FJP5enLaw1JzZNu6Fen+eEKY7Lo=",
|
||||
"path": "github.com/SermoDigital/jose/jws",
|
||||
"revision": "803625baeddc3526d01d321b5066029f53eafc81",
|
||||
"revisionTime": "2018-01-04T20:38:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "3gGGWQ3PcKHE+2cVlkJmIlBfBlw=",
|
||||
"path": "github.com/SermoDigital/jose/jwt",
|
||||
"revision": "803625baeddc3526d01d321b5066029f53eafc81",
|
||||
"revisionTime": "2018-01-04T20:38:59Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "YCtIqzuVlzVA7jh1GQF6BWaom6E=",
|
||||
"path": "github.com/aliyun/alibaba-cloud-sdk-go/sdk",
|
||||
|
|
Loading…
Reference in New Issue