Migrate from SermoDigital go Square JOSE (#6445)

This commit is contained in:
Jeff Mitchell 2019-03-20 14:54:03 -04:00 committed by GitHub
parent 7d945f2ddd
commit 6797e21f54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 84 additions and 2791 deletions

View file

@ -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()
return &result
var claims squarejwt.Claims
if err = parsedJWT.UnsafeClaimsWithoutVerification(&claims); err != nil {
return nil
}
return &claims.ID
}

View file

@ -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`)

View file

@ -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{}
claims := squarejwt.Claims{
// Map the JWT ID to the token ID for ease of use
claims.SetJWTID(te.ID)
ID: te.ID,
// Set the issue time to the creation time
claims.SetIssuedAt(creationTime)
IssuedAt: squarejwt.NewNumericDate(creationTime),
// Set the expiration to the TTL
claims.SetExpiration(creationTime.Add(resp.WrapInfo.TTL))
if resp.Auth != nil {
claims.Set("accessor", resp.Auth.Accessor)
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 {
// 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)
}
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.
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 = token
req.ClientToken = claims.ID
} else {
req.Data["token"] = token
}
req.Data["token"] = claims.ID
}
token = claims.ID
}
if token == "" {

View file

@ -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.

View file

@ -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

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
go build ./...
go test ./...
golint ./...
go vet ./...

View file

@ -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)
}

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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")
)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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
}

View file

@ -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)
)

View file

@ -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
}

View file

@ -1,3 +0,0 @@
// Package jose implements some helper functions and types for the children
// packages, jws, jwt, and jwe.
package jose

View file

@ -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)
)

View file

@ -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)
)

View file

@ -1,2 +0,0 @@
// Package jws implements JWSs per RFC 7515
package jws

View file

@ -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")
)

View file

@ -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)

View file

@ -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{'.'})
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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)
)

View file

@ -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)
)

View file

@ -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
}

View file

@ -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)
)

View file

@ -1,2 +0,0 @@
// Package jwt implements JWTs per RFC 7519
package jwt

View file

@ -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
}

View file

@ -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")
)

View file

@ -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)
}
}

View file

@ -1,6 +0,0 @@
package jose
import "time"
// Now returns the current time in UTC.
func Now() time.Time { return time.Now().UTC() }

24
vendor/vendor.json vendored
View file

@ -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",