vendor: revendor

This commit is contained in:
Eric Chiang
2016-11-17 15:21:26 -08:00
parent 522749b5d8
commit a876ab37af
192 changed files with 12003 additions and 18629 deletions

View File

@@ -10,9 +10,10 @@ matrix:
go:
- 1.5
- 1.6
- 1.7
- tip
go_import_path: gopkg.in/square/go-jose.v1
go_import_path: gopkg.in/square/go-jose.v2
before_script:
- export PATH=$HOME/.local/bin:$PATH
@@ -32,7 +33,6 @@ before_install:
script:
- go test . -v -covermode=count -coverprofile=profile.cov
- go test . -tags std_json -v -covermode=count -coverprofile=profile-std-json.cov
- go test ./cipher -v -covermode=count -coverprofile=cipher/profile.cov
- go test ./jwt -v -covermode=count -coverprofile=jwt/profile.cov
- go test ./json -v # no coverage for forked encoding/json package

View File

@@ -1,16 +1,14 @@
# Go JOSE
**Please note that this branch is still under active development.**
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
[![license](http://img.shields.io/badge/license-apache_2.0-red.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
[![build](https://img.shields.io/travis/square/go-jose.svg?style=flat)](https://travis-ci.org/square/go-jose)
[![coverage](https://img.shields.io/coveralls/square/go-jose.svg?style=flat)](https://coveralls.io/r/square/go-jose)
[![report card](https://goreportcard.com/badge/github.com/square/go-jose)](https://goreportcard.com/report/github.com/square/go-jose)
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
[![license](http://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
[![build](https://travis-ci.org/square/go-jose.svg?branch=master)](https://travis-ci.org/square/go-jose)
[![coverage](https://coveralls.io/repos/github/square/go-jose/badge.svg?branch=master)](https://coveralls.io/r/square/go-jose)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. For the moment, it mainly focuses on encryption
and signing based on the JSON Web Encryption and JSON Web Signature standards.
and Encryption set of standards. This includes support for JSON Web Encryption,
JSON Web Signature, and JSON Web Token standards.
**Disclaimer**: This library contains encryption software that is subject to
the U.S. Export Administration Regulations. You may not export, re-export,
@@ -23,44 +21,50 @@ US maintained blocked list.
## Overview
The implementation follows the
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516)
standard (RFC 7516) and
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515)
standard (RFC 7515). Tables of supported algorithms are shown below.
The library supports both the compact and full serialization formats, and has
optional support for multiple recipients. It also comes with a small
command-line utility
([`jose-util`](https://github.com/square/go-jose/tree/master/jose-util))
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516) (RFC 7516),
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
[JSON Web Token](http://dx.doi.org/10.17487/RFC7519) (RFC 7519).
Tables of supported algorithms are shown below. The library supports both
the compact and full serialization formats, and has optional support for
multiple recipients. It also comes with a small command-line utility
([`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util))
for dealing with JOSE messages in a shell.
**Note**: We use a forked version of the `encoding/json` package from the Go
standard library which uses case-sensitive matching for member names (instead
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
This is to avoid differences in interpretation of messages between go-jose and
libraries in other languages. If you do not like this behavior, you can use the
`std_json` build tag to disable it (though we do not recommend doing so).
libraries in other languages.
### Versions
We use [gopkg.in](https://gopkg.in) for versioning.
[Version 1](https://gopkg.in/square/go-jose.v1) is the current stable version:
[Version 1](https://gopkg.in/square/go-jose.v1) is the old stable version:
import "gopkg.in/square/go-jose.v1"
[Version 2](https://gopkg.in/square/go-jose.v2) is for new development:
import "gopkg.in/square/go-jose.v2"
The interface for [go-jose.v1](https://gopkg.in/square/go-jose.v1) will remain
backwards compatible. We're currently sketching out ideas for a new version, to
clean up the interface a bit. If you have ideas or feature requests [please let
us know](https://github.com/square/go-jose/issues/64)!
backwards compatible. No new feature development will take place on the `v1` branch,
however bug fixes and security fixes will be backported.
The interface for [go-jose.v2](https://gopkg.in/square/go-jose.v2) is mostly
stable, but we suggest pinning to a particular revision for now as we still reserve
the right to make changes. New feature development happens on this branch.
New in [go-jose.v2](https://gopkg.in/square/go-jose.v2) is a
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) sub-package
contributed by [@shaxbee](https://github.com/shaxbee).
### Supported algorithms
See below for a table of supported algorithms. Algorithm identifiers match
the names in the
[JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
standard where possible. The
[Godoc reference](https://godoc.org/github.com/square/go-jose#pkg-constants)
has a list of constants.
the names in the [JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
standard where possible. The Godoc reference has a list of constants.
Key encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
@@ -94,122 +98,22 @@ has a list of constants.
See below for a table of supported key types. These are understood by the
library, and can be passed to corresponding functions such as `NewEncrypter` or
`NewSigner`. Note that if you are creating a new encrypter or signer with a
JSONWebKey, the key id of the JSONWebKey (if present) will be added to any
resulting messages.
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
allows attaching a key id.
Algorithm(s) | Corresponding types
:------------------------- | -------------------------------
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey), *[jose.JSONWebKey](https://godoc.org/gopkg.in/square/go-jose.v2#JSONWebKey)
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey), *[jose.JSONWebKey](https://godoc.org/gopkg.in/square/go-jose.v2#JSONWebKey)
AES, HMAC | []byte, *[jose.JSONWebKey](https://godoc.org/gopkg.in/square/go-jose.v2#JSONWebKey)
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
AES, HMAC | []byte
## Examples
Encryption/decryption example using RSA:
[![godoc](http://img.shields.io/badge/godoc-version_1-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v1)
[![godoc](http://img.shields.io/badge/godoc-version_2-blue.svg?style=flat)](https://godoc.org/gopkg.in/square/go-jose.v2)
```Go
// Generate a public/private key pair to use for this example. The library
// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
// that can be used to load keys from PEM/DER-encoded data.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Instantiate an encrypter using RSA-OAEP with AES128-GCM. An error would
// indicate that the selected algorithm(s) are not currently supported.
publicKey := &privateKey.PublicKey
encrypter, err := NewEncrypter(A128GCM, Recipient{Algorithm: RSA_OAEP, Key: publicKey}, nil)
if err != nil {
panic(err)
}
// Encrypt a sample plaintext. Calling the encrypter returns an encrypted
// JWE object, which can then be serialized for output afterwards. An error
// would indicate a problem in an underlying cryptographic primitive.
var plaintext = []byte("Lorem ipsum dolor sit amet")
object, err := encrypter.Encrypt(plaintext)
if err != nil {
panic(err)
}
// Serialize the encrypted object using the full serialization format.
// Alternatively you can also use the compact format here by calling
// object.CompactSerialize() instead.
serialized := object.FullSerialize()
// Parse the serialized, encrypted JWE object. An error would indicate that
// the given input did not represent a valid message.
object, err = ParseEncrypted(serialized)
if err != nil {
panic(err)
}
// Now we can decrypt and get back our original plaintext. An error here
// would indicate the the message failed to decrypt, e.g. because the auth
// tag was broken or the message was tampered with.
decrypted, err := object.Decrypt(privateKey)
if err != nil {
panic(err)
}
fmt.Printf(string(decrypted))
// output: Lorem ipsum dolor sit amet
```
Signing/verification example using RSA:
```Go
// Generate a public/private key pair to use for this example. The library
// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
// that can be used to load keys from PEM/DER-encoded data.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Instantiate a signer using RSASSA-PSS (SHA512) with the given private key.
signer, err := NewSigner(SigningKey{Algorithm: PS512, Key: privateKey}, nil)
if err != nil {
panic(err)
}
// Sign a sample payload. Calling the signer returns a protected JWS object,
// which can then be serialized for output afterwards. An error would
// indicate a problem in an underlying cryptographic primitive.
var payload = []byte("Lorem ipsum dolor sit amet")
object, err := signer.Sign(payload)
if err != nil {
panic(err)
}
// Serialize the encrypted object using the full serialization format.
// Alternatively you can also use the compact format here by calling
// object.CompactSerialize() instead.
serialized := object.FullSerialize()
// Parse the serialized, protected JWS object. An error would indicate that
// the given input did not represent a valid message.
object, err = ParseSigned(serialized)
if err != nil {
panic(err)
}
// Now we can verify the signature on the payload. An error here would
// indicate the the message failed to verify, e.g. because the signature was
// broken or the message was tampered with.
output, err := object.Verify(&privateKey.PublicKey)
if err != nil {
panic(err)
}
fmt.Printf(string(output))
// output: Lorem ipsum dolor sit amet
```
More examples can be found in the [Godoc
reference](https://godoc.org/github.com/square/go-jose) for this package. The
[`jose-util`](https://github.com/square/go-jose/tree/master/jose-util)
subdirectory also contains a small command-line utility which might
be useful as an example.
Examples can be found in the Godoc
reference for this package. The
[`jose-util`](https://github.com/square/go-jose/tree/v2/jose-util)
subdirectory also contains a small command-line utility which might be useful
as an example.

View File

@@ -67,6 +67,10 @@ func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKe
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
}
if publicKey == nil {
return recipientKeyInfo{}, errors.New("invalid public key")
}
return recipientKeyInfo{
keyAlg: keyAlg,
keyEncrypter: &rsaEncrypterVerifier{
@@ -84,6 +88,10 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
if privateKey == nil {
return recipientSigInfo{}, errors.New("invalid private key")
}
return recipientSigInfo{
sigAlg: sigAlg,
publicKey: &JSONWebKey{
@@ -104,6 +112,10 @@ func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipien
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
}
if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
return recipientKeyInfo{}, errors.New("invalid public key")
}
return recipientKeyInfo{
keyAlg: keyAlg,
keyEncrypter: &ecEncrypterVerifier{
@@ -121,6 +133,10 @@ func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (re
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
if privateKey == nil {
return recipientSigInfo{}, errors.New("invalid private key")
}
return recipientSigInfo{
sigAlg: sigAlg,
publicKey: &JSONWebKey{
@@ -370,6 +386,10 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
return nil, errors.New("square/go-jose: invalid epk header")
}
if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) {
return nil, errors.New("square/go-jose: invalid public key in epk header")
}
apuData := headers.Apu.bytes()
apvData := headers.Apv.bytes()
@@ -474,6 +494,8 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
case ES512:
keySize = 66
hash = crypto.SHA512
default:
return ErrUnsupportedAlgorithm
}
if len(signature) != 2*keySize {

View File

@@ -18,6 +18,8 @@ package jose
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"errors"
@@ -429,3 +431,38 @@ func TestInvalidEllipticCurve(t *testing.T) {
t.Error("should not generate ES384 signature with P-521 key")
}
}
func estInvalidECPublicKey(t *testing.T) {
// Invalid key
invalid := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P256(),
X: fromBase64Int("MTEx"),
Y: fromBase64Int("MTEx"),
},
D: fromBase64Int("0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"),
}
headers := rawHeader{
Alg: string(ECDH_ES),
Epk: &JSONWebKey{
Key: &invalid.PublicKey,
},
}
dec := ecDecrypterSigner{
privateKey: ecTestKey256,
}
_, err := dec.decryptKey(headers, nil, randomKeyGenerator{size: 16})
if err == nil {
t.Fatal("decrypter accepted JWS with invalid ECDH public key")
}
}
func TestInvalidAlgorithmEC(t *testing.T) {
err := ecEncrypterVerifier{publicKey: &ecTestKey256.PublicKey}.verifyPayload([]byte{}, []byte{}, "XYZ")
if err != ErrUnsupportedAlgorithm {
t.Fatal("should not accept invalid/unsupported algorithm")
}
}

View File

@@ -82,7 +82,7 @@ func (ctx *cbcAEAD) Overhead() int {
// Seal encrypts and authenticates the plaintext.
func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
// Output buffer -- must take care not to mangle plaintext input.
ciphertext := make([]byte, len(plaintext)+ctx.Overhead())[:len(plaintext)]
ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)]
copy(ciphertext, plaintext)
ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
@@ -91,7 +91,7 @@ func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
cbc.CryptBlocks(ciphertext, ciphertext)
authtag := ctx.computeAuthTag(data, nonce, ciphertext)
ret, out := resize(dst, len(dst)+len(ciphertext)+len(authtag))
ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag)))
copy(out, ciphertext)
copy(out[len(ciphertext):], authtag)
@@ -128,7 +128,7 @@ func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
return nil, err
}
ret, out := resize(dst, len(dst)+len(plaintext))
ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext)))
copy(out, plaintext)
return ret, nil
@@ -136,12 +136,12 @@ func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
// Compute an authentication tag
func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
buffer := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8)
n := 0
n += copy(buffer, aad)
n += copy(buffer[n:], nonce)
n += copy(buffer[n:], ciphertext)
binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad)*8))
binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8)
// According to documentation, Write() on hash.Hash never fails.
hmac := hmac.New(ctx.hash, ctx.integrityKey)
@@ -153,8 +153,8 @@ func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
// resize ensures the the given slice has a capacity of at least n bytes.
// If the capacity of the slice is less than n, a new slice is allocated
// and the existing data will be copied.
func resize(in []byte, n int) (head, tail []byte) {
if cap(in) >= n {
func resize(in []byte, n uint64) (head, tail []byte) {
if uint64(cap(in)) >= n {
head = in[:n]
} else {
head = make([]byte, n)
@@ -168,7 +168,7 @@ func resize(in []byte, n int) (head, tail []byte) {
// Apply padding
func padBuffer(buffer []byte, blockSize int) []byte {
missing := blockSize - (len(buffer) % blockSize)
ret, out := resize(buffer, len(buffer)+missing)
ret, out := resize(buffer, uint64(len(buffer))+uint64(missing))
padding := bytes.Repeat([]byte{byte(missing)}, missing)
copy(out, padding)
return ret

View File

@@ -283,7 +283,7 @@ func TestTruncatedCiphertext(t *testing.T) {
ct := aead.Seal(nil, nonce, data, nil)
// Truncated ciphertext, but with correct auth tag
truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], len(ct)-2)
truncated, tail := resize(ct[:len(ct)-ctx.authtagBytes-2], uint64(len(ct))-2)
copy(tail, ctx.computeAuthTag(nil, nonce, truncated[:len(truncated)-ctx.authtagBytes]))
// Open should fail
@@ -313,8 +313,8 @@ func TestInvalidPaddingOpen(t *testing.T) {
ctx := aead.(*cbcAEAD)
// Mutated ciphertext, but with correct auth tag
size := len(buffer)
ciphertext, tail := resize(buffer, size+(len(key)/2))
size := uint64(len(buffer))
ciphertext, tail := resize(buffer, size+(uint64(len(key))/2))
copy(tail, ctx.computeAuthTag(nil, nonce, ciphertext[:size]))
// Open should fail (b/c of invalid padding, even though tag matches)

View File

@@ -32,7 +32,7 @@ type concatKDF struct {
// NewConcatKDF builds a KDF reader based on the given inputs.
func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
buffer := make([]byte, len(algID)+len(ptyUInfo)+len(ptyVInfo)+len(supPubInfo)+len(supPrivInfo))
buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo)))
n := 0
n += copy(buffer, algID)
n += copy(buffer[n:], ptyUInfo)

View File

@@ -23,7 +23,14 @@ import (
)
// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
// It is an error to call this function with a private/public key that are not on the same
// curve. Callers must ensure that the keys are valid before calling this function. Output
// size may be at most 1<<16 bytes (64 KiB).
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
if size > 1<<16 {
panic("ECDH-ES output size too large, must be less than 1<<16")
}
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
algID := lengthPrefixed([]byte(alg))
ptyUInfo := lengthPrefixed(apuData)
@@ -33,6 +40,10 @@ func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, p
supPubInfo := make([]byte, 4)
binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8)
if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) {
panic("public key not on same curve as private key")
}
z, _ := priv.PublicKey.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
reader := NewConcatKDF(crypto.SHA256, z.Bytes(), algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{})

View File

@@ -48,7 +48,7 @@ var bobKey = &ecdsa.PrivateKey{
func fromBase64Int(data string) *big.Int {
val, err := base64.URLEncoding.DecodeString(data)
if err != nil {
panic("Invalid test data")
panic("Invalid test data: " + err.Error())
}
return new(big.Int).SetBytes(val)
}
@@ -67,6 +67,23 @@ func TestVectorECDHES(t *testing.T) {
}
}
func TestInvalidECPublicKey(t *testing.T) {
defer func() { recover() }()
// Invalid key
invalid := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P256(),
X: fromBase64Int("MTEx"),
Y: fromBase64Int("MTEx"),
},
D: fromBase64Int("0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo="),
}
DeriveECDHES("A128GCM", []byte{}, []byte{}, bobKey, &invalid.PublicKey, 16)
t.Fatal("should panic if public key was invalid")
}
func BenchmarkECDHES_128(b *testing.B) {
apuData := []byte("APU")
apvData := []byte("APV")

View File

@@ -19,6 +19,7 @@ package jose
import (
"crypto/ecdsa"
"crypto/rsa"
"errors"
"fmt"
"reflect"
)
@@ -308,10 +309,16 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWe
return obj, nil
}
// Decrypt and validate the object and return the plaintext.
// Decrypt and validate the object and return the plaintext. Note that this
// function does not support multi-recipient, if you desire multi-recipient
// decryption use DecryptMulti instead.
func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
headers := obj.mergedHeaders(nil)
if len(obj.recipients) > 1 {
return nil, errors.New("square/go-jose: too many recipients in payload; expecting only one")
}
if len(headers.Crit) > 0 {
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
}
@@ -339,17 +346,13 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
authData := obj.computeAuthData()
var plaintext []byte
for _, recipient := range obj.recipients {
recipientHeaders := obj.mergedHeaders(&recipient)
recipient := obj.recipients[0]
recipientHeaders := obj.mergedHeaders(&recipient)
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err == nil {
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err == nil {
break
}
}
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err == nil {
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
}
if plaintext == nil {
@@ -363,3 +366,67 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
return plaintext, err
}
// DecryptMulti decrypts and validates the object and returns the plaintexts,
// with support for multiple recipients. It returns the index of the recipient
// for which the decryption was successful, the merged headers for that recipient,
// and the plaintext.
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
globalHeaders := obj.mergedHeaders(nil)
if len(globalHeaders.Crit) > 0 {
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported crit header")
}
decrypter, err := newDecrypter(decryptionKey)
if err != nil {
return -1, Header{}, nil, err
}
cipher := getContentCipher(globalHeaders.Enc)
if cipher == nil {
return -1, Header{}, nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(globalHeaders.Enc))
}
generator := randomKeyGenerator{
size: cipher.keySize(),
}
parts := &aeadParts{
iv: obj.iv,
ciphertext: obj.ciphertext,
tag: obj.tag,
}
authData := obj.computeAuthData()
index := -1
var plaintext []byte
var headers rawHeader
for i, recipient := range obj.recipients {
recipientHeaders := obj.mergedHeaders(&recipient)
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err == nil {
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err == nil {
index = i
headers = recipientHeaders
break
}
}
}
if plaintext == nil || err != nil {
return -1, Header{}, nil, ErrCryptoFailure
}
// The "zip" header parameter may only be present in the protected header.
if obj.protected.Zip != "" {
plaintext, err = decompress(obj.protected.Zip, plaintext)
}
return index, headers.sanitized(), plaintext, err
}

View File

@@ -279,38 +279,40 @@ func TestMultiRecipientJWE(t *testing.T) {
input := []byte("Lorem ipsum dolor sit amet")
obj, err := enc.Encrypt(input)
if err != nil {
t.Error("error in encrypt: ", err)
return
t.Fatal("error in encrypt: ", err)
}
msg := obj.FullSerialize()
parsed, err := ParseEncrypted(msg)
if err != nil {
t.Error("error in parse: ", err)
return
t.Fatal("error in parse: ", err)
}
output, err := parsed.Decrypt(rsaTestKey)
i, _, output, err := parsed.DecryptMulti(rsaTestKey)
if err != nil {
t.Error("error on decrypt with RSA: ", err)
return
t.Fatal("error on decrypt with RSA: ", err)
}
if i != 0 {
t.Fatal("recipient index should be 0 for RSA key")
}
if bytes.Compare(input, output) != 0 {
t.Error("Decrypted output does not match input: ", output, input)
return
t.Fatal("Decrypted output does not match input: ", output, input)
}
output, err = parsed.Decrypt(sharedKey)
i, _, output, err = parsed.DecryptMulti(sharedKey)
if err != nil {
t.Error("error on decrypt with AES: ", err)
return
t.Fatal("error on decrypt with AES: ", err)
}
if i != 1 {
t.Fatal("recipient index should be 1 for shared key")
}
if bytes.Compare(input, output) != 0 {
t.Error("Decrypted output does not match input", output, input)
return
t.Fatal("Decrypted output does not match input", output, input)
}
}

View File

@@ -21,6 +21,7 @@ import (
"compress/flate"
"encoding/base64"
"encoding/binary"
"encoding/json"
"io"
"math/big"
"regexp"
@@ -31,7 +32,7 @@ var stripWhitespaceRegex = regexp.MustCompile("\\s")
// Helper function to serialize known-good objects.
// Precondition: value is not a nil pointer.
func mustSerializeJSON(value interface{}) []byte {
out, err := MarshalJSON(value)
out, err := json.Marshal(value)
if err != nil {
panic(err)
}
@@ -132,12 +133,12 @@ func newBufferFromInt(num uint64) *byteBuffer {
}
func (b *byteBuffer) MarshalJSON() ([]byte, error) {
return MarshalJSON(b.base64())
return json.Marshal(b.base64())
}
func (b *byteBuffer) UnmarshalJSON(data []byte) error {
var encoded string
err := UnmarshalJSON(data, &encoded)
err := json.Unmarshal(data, &encoded)
if err != nil {
return err
}

View File

@@ -86,3 +86,9 @@ Sign and verify a test message (EC).
> jose-util sign --alg ES384 --key ec.key |
> jose-util verify --key ec.pub
Lorem ipsum dolor sit amet
Expand a compact message to full format.
$ echo "eyJhbGciOiJFUzM4NCJ9.TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQK.QPU35XY913Im7ZEaN2yHykfbtPqjHZvYp-lV8OcTAJZs67bJFSdTSkQhQWE9ch6tvYrj_7py6HKaWVFLll_s_Rm6bmwq3JszsHrIvFFm1NydruYHhvAnx7rjYiqwOu0W" |
> jose-util expand --format JWS
{"payload":"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQK","protected":"eyJhbGciOiJFUzM4NCJ9","signature":"QPU35XY913Im7ZEaN2yHykfbtPqjHZvYp-lV8OcTAJZs67bJFSdTSkQhQWE9ch6tvYrj_7py6HKaWVFLll_s_Rm6bmwq3JszsHrIvFFm1NydruYHhvAnx7rjYiqwOu0W"}

View File

@@ -28,7 +28,7 @@ import (
var (
app = kingpin.New("jose-util", "A command-line utility for dealing with JOSE objects.")
keyFile = app.Flag("key", "Path to key file (PEM or DER-encoded)").Required().ExistingFile()
keyFile = app.Flag("key", "Path to key file (PEM or DER-encoded)").ExistingFile()
inFile = app.Flag("in", "Path to input file (stdin if missing)").ExistingFile()
outFile = app.Flag("out", "Path to output file (stdout if missing)").ExistingFile()
@@ -54,8 +54,12 @@ func main() {
command := kingpin.MustParse(app.Parse(os.Args[1:]))
keyBytes, err := ioutil.ReadFile(*keyFile)
exitOnError(err, "unable to read key file")
var keyBytes []byte
var err error
if command != "expand" {
keyBytes, err = ioutil.ReadFile(*keyFile)
exitOnError(err, "unable to read key file")
}
switch command {
case "encrypt":
@@ -144,6 +148,7 @@ func main() {
exitOnError(err, "unable to expand message")
writeOutput(*outFile, []byte(serialized))
writeOutput(*outFile, []byte("\n"))
}
}

View File

@@ -1258,7 +1258,7 @@ func TestSliceOfCustomByte(t *testing.T) {
t.Fatal(err)
}
if !reflect.DeepEqual(a, b) {
t.Fatal("expected %v == %v", a, b)
t.Fatalf("expected %v == %v", a, b)
}
}

View File

@@ -1,31 +0,0 @@
// +build !std_json
/*-
* Copyright 2014 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jose
import (
"gopkg.in/square/go-jose.v2/json"
)
func MarshalJSON(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
func UnmarshalJSON(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}

View File

@@ -1,105 +0,0 @@
// +build !std_json
/*-
* Copyright 2014 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jose
import (
"testing"
)
type CaseSensitive struct {
A int `json:"Test"`
B int `json:"test"`
C int `json:"TEST"`
}
type UnicodeTest struct {
Sig string `json:"sig"`
}
func TestUnicodeComparison(t *testing.T) {
// Some tests from RFC 7515, Section 10.13
raw := []byte(`{"\u0073ig":"foo"}`)
var ut UnicodeTest
err := UnmarshalJSON(raw, &ut)
if err != nil {
t.Error(err)
}
if ut.Sig != "foo" {
t.Error("strings 'sig' and '\\u0073ig' should be equal")
}
raw = []byte(`{"si\u0047":"bar"}`)
var ut2 UnicodeTest
err = UnmarshalJSON(raw, &ut2)
if err != nil {
t.Error(err)
}
if ut2.Sig != "" {
t.Error("strings 'sig' and 'si\\u0047' should not be equal")
}
}
func TestCaseSensitiveJSON(t *testing.T) {
raw := []byte(`{"test":42}`)
var cs CaseSensitive
err := UnmarshalJSON(raw, &cs)
if err != nil {
t.Error(err)
}
if cs.A != 0 || cs.B != 42 || cs.C != 0 {
t.Errorf("parsing JSON should be case-sensitive (got %v)", cs)
}
}
func TestRejectDuplicateKeysObject(t *testing.T) {
raw := []byte(`{"test":42,"test":43}`)
var cs CaseSensitive
err := UnmarshalJSON(raw, &cs)
if err == nil {
t.Error("should reject JSON with duplicate keys, but didn't")
}
}
func TestRejectDuplicateKeysInterface(t *testing.T) {
raw := []byte(`{"test":42,"test":43}`)
var m interface{}
err := UnmarshalJSON(raw, &m)
if err == nil {
t.Error("should reject JSON with duplicate keys, but didn't")
}
}
func TestParseCaseSensitiveJWE(t *testing.T) {
invalidJWE := `{"protected":"eyJlbmMiOiJYWVoiLCJBTEciOiJYWVoifQo","encrypted_key":"QUJD","iv":"QUJD","ciphertext":"QUJD","tag":"QUJD"}`
_, err := ParseEncrypted(invalidJWE)
if err == nil {
t.Error("Able to parse message with case-invalid headers", invalidJWE)
}
}
func TestParseCaseSensitiveJWS(t *testing.T) {
invalidJWS := `{"PAYLOAD":"CUJD","signatures":[{"protected":"e30","signature":"CUJD"}]}`
_, err := ParseSigned(invalidJWS)
if err == nil {
t.Error("Able to parse message with case-invalid headers", invalidJWS)
}
}

View File

@@ -1,31 +0,0 @@
// +build std_json
/*-
* Copyright 2014 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jose
import (
"encoding/json"
)
func MarshalJSON(v interface{}) ([]byte, error) {
return json.Marshal(v)
}
func UnmarshalJSON(data []byte, v interface{}) error {
return json.Unmarshal(data, v)
}

View File

@@ -1,124 +0,0 @@
// +build std_json
/*-
* Copyright 2014 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jose
import (
"testing"
)
type CaseInsensitive struct {
A int `json:"TEST"`
}
type UnicodeTest struct {
Sig string `json:"sig"`
}
func TestUnicodeComparison(t *testing.T) {
// Some tests from RFC 7515, Section 10.13
raw := []byte(`{"\u0073ig":"foo"}`)
var ut UnicodeTest
err := UnmarshalJSON(raw, &ut)
if err != nil {
t.Error(err)
}
if ut.Sig != "foo" {
t.Error("strings 'sig' and '\\u0073ig' should be equal")
}
}
func TestCaseInsensitiveJSON(t *testing.T) {
raw := []byte(`{"test":42}`)
var ci CaseInsensitive
err := UnmarshalJSON(raw, &ci)
if err != nil {
t.Error(err)
}
if ci.A != 42 {
t.Errorf("parsing JSON should be case-insensitive (got %v)", ci)
}
}
func TestParseCaseInsensitiveJWE(t *testing.T) {
invalidJWE := `{"protected":"eyJlbmMiOiJYWVoiLCJBTEciOiJYWVoifQo","encrypted_key":"QUJD","iv":"QUJD","ciphertext":"QUJD","tag":"QUJD"}`
_, err := ParseEncrypted(invalidJWE)
if err != nil {
t.Error("Unable to parse message with case-invalid headers", invalidJWE)
}
}
func TestParseCaseInsensitiveJWS(t *testing.T) {
invalidJWS := `{"PAYLOAD":"CUJD","signatures":[{"protected":"e30","signature":"CUJD"}]}`
_, err := ParseSigned(invalidJWS)
if err != nil {
t.Error("Unable to parse message with case-invalid headers", invalidJWS)
}
}
var JWKSetDuplicates = stripWhitespace(`{
"keys": [{
"kty": "RSA",
"kid": "exclude-me",
"use": "sig",
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT
-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV
wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-
oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde
3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC
LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g
HdrNP5zw",
"e": "AQAB"
}],
"keys": [{
"kty": "RSA",
"kid": "include-me",
"use": "sig",
"n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT
-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV
wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-
oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde
3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC
LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g
HdrNP5zw",
"e": "AQAB"
}],
"custom": "exclude-me",
"custom": "include-me"
}`)
func TestDuplicateJWKSetMembersIgnored(t *testing.T) {
type CustomSet struct {
JSONWebKeySet
CustomMember string `json:"custom"`
}
data := []byte(JWKSetDuplicates)
var set CustomSet
UnmarshalJSON(data, &set)
if len(set.Keys) != 1 {
t.Error("expected only one key in set")
}
if set.Keys[0].KeyID != "include-me" {
t.Errorf("expected key with kid: \"include-me\", got: %s", set.Keys[0].KeyID)
}
if set.CustomMember != "include-me" {
t.Errorf("expected custom member value: \"include-me\", got: %s", set.CustomMember)
}
}

View File

@@ -18,6 +18,7 @@ package jose
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
@@ -112,7 +113,7 @@ func ParseEncrypted(input string) (*JSONWebEncryption, error) {
// parseEncryptedFull parses a message in compact format.
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
var parsed rawJSONWebEncryption
err := UnmarshalJSON([]byte(input), &parsed)
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
@@ -134,7 +135,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
}
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
err := UnmarshalJSON(parsed.Protected.bytes(), &obj.protected)
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
}

View File

@@ -21,10 +21,15 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"math/big"
"reflect"
"strings"
"gopkg.in/square/go-jose.v2/json"
)
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
@@ -49,14 +54,17 @@ type rawJSONWebKey struct {
Dp *byteBuffer `json:"dp,omitempty"`
Dq *byteBuffer `json:"dq,omitempty"`
Qi *byteBuffer `json:"qi,omitempty"`
// Certificates
X5c []string `json:"x5c,omitempty"`
}
// JSONWebKey represents a public or private key in JWK format.
type JSONWebKey struct {
Key interface{}
KeyID string
Algorithm string
Use string
Key interface{}
Certificates []*x509.Certificate
KeyID string
Algorithm string
Use string
}
// MarshalJSON serializes the given key to its JSON representation.
@@ -87,13 +95,17 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
raw.Alg = k.Algorithm
raw.Use = k.Use
return MarshalJSON(raw)
for _, cert := range k.Certificates {
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
}
return json.Marshal(raw)
}
// UnmarshalJSON reads a key from its JSON representation.
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
var raw rawJSONWebKey
err = UnmarshalJSON(data, &raw)
err = json.Unmarshal(data, &raw)
if err != nil {
return err
}
@@ -121,6 +133,19 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
if err == nil {
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
}
k.Certificates = make([]*x509.Certificate, len(raw.X5c))
for i, cert := range raw.X5c {
raw, err := base64.StdEncoding.DecodeString(cert)
if err != nil {
return err
}
k.Certificates[i], err = x509.ParseCertificate(raw)
if err != nil {
return err
}
}
return
}
@@ -192,7 +217,17 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
return h.Sum(nil), nil
}
// Valid checks that the key contains the expected parameters
// IsPublic returns true if the JWK represents a public key (not symmetric, not private).
func (k *JSONWebKey) IsPublic() bool {
switch k.Key.(type) {
case *ecdsa.PublicKey, *rsa.PublicKey:
return true
default:
return false
}
}
// Valid checks that the key contains the expected parameters.
func (k *JSONWebKey) Valid() bool {
if k.Key == nil {
return false
@@ -253,13 +288,20 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
}
if key.X == nil || key.Y == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC key, missing x/y values")
return nil, errors.New("square/go-jose: invalid EC key, missing x/y values")
}
x := key.X.bigInt()
y := key.Y.bigInt()
if !curve.IsOnCurve(x, y) {
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
}
return &ecdsa.PublicKey{
Curve: curve,
X: key.X.bigInt(),
Y: key.Y.bigInt(),
X: x,
Y: y,
}, nil
}
@@ -368,11 +410,18 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
}
x := key.X.bigInt()
y := key.Y.bigInt()
if !curve.IsOnCurve(x, y) {
return nil, errors.New("square/go-jose: invalid EC key, X/Y are not on declared curve")
}
return &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: curve,
X: key.X.bigInt(),
Y: key.Y.bigInt(),
X: x,
Y: y,
},
D: key.D.bigInt(),
}, nil

View File

@@ -22,12 +22,57 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"math/big"
"reflect"
"testing"
"gopkg.in/square/go-jose.v2/json"
)
// Test chain of two X.509 certificates
var testCertificates, _ = x509.ParseCertificates(fromBase64Bytes(`
MIIDfDCCAmSgAwIBAgIJANWAkzF7PA8/MA0GCSqGSIb3DQEBCwUAMFUxCzAJ
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4GA1UEChMHY2VydGlnbzEQMA4G
A1UECxMHZXhhbXBsZTEVMBMGA1UEAxMMZXhhbXBsZS1sZWFmMB4XDTE2MDYx
MDIyMTQxMVoXDTIzMDQxNTIyMTQxMVowVTELMAkGA1UEBhMCVVMxCzAJBgNV
BAgTAkNBMRAwDgYDVQQKEwdjZXJ0aWdvMRAwDgYDVQQLEwdleGFtcGxlMRUw
EwYDVQQDEwxleGFtcGxlLWxlYWYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQC7stSvfQyGuHw3v34fisqIdDXberrFoFk9ht/WdXgYzX2uLNKd
sR/J5sbWSl8K/5djpzj31eIzqU69w8v7SChM5x9bouDsABHz3kZucx5cSafE
gJojysBkcrq3VY+aJanzbL+qErYX+lhRpPcZK6JMWIwar8Y3B2la4yWwieec
w2/WfEVvG0M/DOYKnR8QHFsfl3US1dnBM84czKPyt9r40gDk2XiH/lGts5a9
4rAGvbr8IMCtq0mA5aH3Fx3mDSi3+4MZwygCAHrF5O5iSV9rEI+m2+7j2S+j
HDUnvV+nqcpb9m6ENECnYX8FD2KcqlOjTmw8smDy09N2Np6i464lAgMBAAGj
TzBNMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAsBgNVHREEJTAj
hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABgglsb2NhbGhvc3QwDQYJKoZIhvcN
AQELBQADggEBAGM4aa/qrURUweZBIwZYv8O9b2+r4l0HjGAh982/B9sMlM05
kojyDCUGvj86z18Lm8mKr4/y+i0nJ+vDIksEvfDuzw5ALAXGcBzPJKtICUf7
LstA/n9NNpshWz0kld9ylnB5mbUzSFDncVyeXkEf5sGQXdIIZT9ChRBoiloS
aa7dvBVCcsX1LGP2LWqKtD+7nUnw5qCwtyAVT8pthEUxFTpywoiJS5ZdzeEx
8MNGvUeLFj2kleqPF78EioEQlSOxViCuctEtnQuPcDLHNFr10byTZY9roObi
qdsJLMVvb2XliJjAqaPa9AkYwGE6xHw2ispwg64Rse0+AtKups19WIUwggNT
MIICO6ADAgECAgkAqD4tCWKt9/AwDQYJKoZIhvcNAQELBQAwVTELMAkGA1UE
BhMCVVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQKEwdjZXJ0aWdvMRAwDgYDVQQL
EwdleGFtcGxlMRUwEwYDVQQDEwxleGFtcGxlLXJvb3QwHhcNMTYwNjEwMjIx
NDExWhcNMjMwNDE1MjIxNDExWjBVMQswCQYDVQQGEwJVUzELMAkGA1UECBMC
Q0ExEDAOBgNVBAoTB2NlcnRpZ28xEDAOBgNVBAsTB2V4YW1wbGUxFTATBgNV
BAMTDGV4YW1wbGUtcm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMo4ShKI2MxDz/NQVxBbz0tbD5R5NcobA0NKkaPKLyMEpnWVY9ucyauM
joNn1F568cfOoF0pm3700U8UTPt2MMxEHIi4mFG/OF8UF+Voh1J42Tb42lRo
W5RRR3ogh4+7QB1G94nxkYddHAJ4QMhUJlLigFg8c6Ff/MxYODy9I7ilLFOM
Zzsjx8fFpRKRXNQFt471P/V4WTSba7GzdTOJRyTZf/xipF36n8RoEQPvyde8
pEAsCC4oDOrEiCTdxw8rRJVAU0Wr55XX+qjxyi55C6oykIC/BWR+lUqGd7IL
Y2Uyt/OVxllt8b+KuVKNCfn4TFlfgizLWkJRs6JV9KuwJ20CAwEAAaMmMCQw
DgYDVR0PAQH/BAQDAgIEMBIGA1UdEwEB/wQIMAYBAf8CAQAwDQYJKoZIhvcN
AQELBQADggEBAIsQlTrm9NT6gts0cs4JHp8AutuMrvGyLpIUOlJcEybvgxaz
LebIMGZek5w3yEJiCyCK9RdNDP3Kdc/+nM6PhvzfPOVo58+0tMCYyEpZVXhD
zmasNDP4fMbiUpczvx5OwPw/KuhwD+1ITuZUQnQlqXgTYoj9n39+qlgUsHos
WXHmfzd6Fcz96ADSXg54IL2cEoJ41Q3ewhA7zmWWPLMAl21aex2haiAmzqqN
xXyfZTnGNnE3lkV1yVguOrqDZyMRdcxDFvxvtmEeMtYV2Mc/zlS9ccrcOkrc
mZSDxthLu3UMl98NA2NrCGWwzJwpk36vQ0PRSbibsCMarFspP8zbIoU=`))
func TestCurveSize(t *testing.T) {
size256 := curveSize(elliptic.P256())
size384 := curveSize(elliptic.P384())
@@ -155,6 +200,38 @@ func TestRoundtripEcPrivate(t *testing.T) {
}
}
func TestRoundtripX5C(t *testing.T) {
jwk := JSONWebKey{
Key: rsaTestKey,
KeyID: "bar",
Algorithm: "foo",
Certificates: testCertificates,
}
jsonbar, err := jwk.MarshalJSON()
if err != nil {
t.Error("problem marshaling", err)
}
var jwk2 JSONWebKey
err = jwk2.UnmarshalJSON(jsonbar)
if err != nil {
t.Error("problem unmarshalling", err)
}
if !reflect.DeepEqual(testCertificates, jwk2.Certificates) {
t.Error("Certificates not equal", jwk.Certificates, jwk2.Certificates)
}
jsonbar2, err := jwk2.MarshalJSON()
if err != nil {
t.Error("problem marshaling", err)
}
if !bytes.Equal(jsonbar, jsonbar2) {
t.Error("roundtrip should not lose information")
}
}
func TestMarshalUnmarshal(t *testing.T) {
kid := "DEADBEEF"
@@ -210,7 +287,7 @@ func TestMarshalNonPointer(t *testing.T) {
"n": "vd7rZIoTLEe-z1_8G1FcXSw9CQFEJgV4g9V277sER7yx5Qjz_Pkf2YVth6wwwFJEmzc0hoKY-MMYFNwBE4hQHw"
}`)
var parsedKey JSONWebKey
err := UnmarshalJSON(keyJSON, &parsedKey)
err := json.Unmarshal(keyJSON, &parsedKey)
if err != nil {
t.Errorf("Error unmarshalling key: %v", err)
return
@@ -218,7 +295,7 @@ func TestMarshalNonPointer(t *testing.T) {
ek := EmbedsKey{
Key: parsedKey,
}
out, err := MarshalJSON(ek)
out, err := json.Marshal(ek)
if err != nil {
t.Errorf("Error marshalling JSON: %v", err)
return
@@ -382,6 +459,38 @@ var cookbookJWKs = []string{
"qi":"lSQi-w9CpyUReMErP1RsBLk7wNtOvs5EQpPqmuMvqW57NBUczScEoPwmUqq
abu9V0-Py4dQ57_bapoKRu1R90bvuFnU63SHWEFglZQvJDMeAvmj4sm-Fp0o
Yu_neotgQ0hzbI5gry7ajdYy9-2lNx_76aBZoOUu9HCJ-UsfSOI8"}`),
// X.509 Certificate Chain
stripWhitespace(`{"kty":"RSA",
"use":"sig",
"kid":"1b94c",
"n":"vrjOfz9Ccdgx5nQudyhdoR17V-IubWMeOZCwX_jj0hgAsz2J_pqYW08
PLbK_PdiVGKPrqzmDIsLI7sA25VEnHU1uCLNwBuUiCO11_-7dYbsr4iJmG0Q
u2j8DsVyT1azpJC_NG84Ty5KKthuCaPod7iI7w0LK9orSMhBEwwZDCxTWq4a
YWAchc8t-emd9qOvWtVMDC2BXksRngh6X5bUYLy6AyHKvj-nUy1wgzjYQDwH
MTplCoLtU-o-8SNnZ1tmRoGE9uJkBLdh5gFENabWnU5m1ZqZPdwS-qo-meMv
VfJb6jJVWRpl2SUtCnYG2C32qvbWbjZ_jBPD5eunqsIo1vQ",
"e":"AQAB",
"x5c":
["MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJB
gNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYD
VQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1
wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBg
NVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDV
QQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1w
YmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnH
YMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66
s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6
SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpn
fajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPq
PvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVk
aZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BA
QUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL
+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1
zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL
2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo
4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTq
gawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA=="]}`),
}
// SHA-256 thumbprints of the above keys, hex-encoded
@@ -390,6 +499,7 @@ var cookbookJWKThumbprints = []string{
"747ae2dd2003664aeeb21e4753fe7402846170a16bc8df8f23a8cf06d3cbe793",
"f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932",
"0fc478f8579325fcee0d4cbc6d9d1ce21730a6e97e435d6008fb379b0ebe47d4",
"0ddb05bfedbec2070fa037324ba397396561d3425d6d69245570c261dc49dee3",
}
func TestWebKeyVectorsValid(t *testing.T) {
@@ -429,16 +539,16 @@ func TestMarshalUnmarshalJWKSet(t *testing.T) {
set.Keys = append(set.Keys, jwk1)
set.Keys = append(set.Keys, jwk2)
jsonbar, err := MarshalJSON(&set)
jsonbar, err := json.Marshal(&set)
if err != nil {
t.Error("problem marshalling set", err)
}
var set2 JSONWebKeySet
err = UnmarshalJSON(jsonbar, &set2)
err = json.Unmarshal(jsonbar, &set2)
if err != nil {
t.Error("problem unmarshalling set", err)
}
jsonbar2, err := MarshalJSON(&set2)
jsonbar2, err := json.Marshal(&set2)
if err != nil {
t.Error("problem marshalling set", err)
}
@@ -467,7 +577,7 @@ func TestJWKSymmetricKey(t *testing.T) {
sample2 := `{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow","kid":"HMAC key used in JWS spec Appendix A.1 example"}`
var jwk1 JSONWebKey
UnmarshalJSON([]byte(sample1), &jwk1)
json.Unmarshal([]byte(sample1), &jwk1)
if jwk1.Algorithm != "A128KW" {
t.Errorf("expected Algorithm to be A128KW, but was '%s'", jwk1.Algorithm)
@@ -478,7 +588,7 @@ func TestJWKSymmetricKey(t *testing.T) {
}
var jwk2 JSONWebKey
UnmarshalJSON([]byte(sample2), &jwk2)
json.Unmarshal([]byte(sample2), &jwk2)
if jwk2.KeyID != "HMAC key used in JWS spec Appendix A.1 example" {
t.Errorf("expected KeyID to be 'HMAC key used in JWS spec Appendix A.1 example', but was '%s'", jwk2.KeyID)

View File

@@ -18,8 +18,11 @@ package jose
import (
"encoding/base64"
"errors"
"fmt"
"strings"
"gopkg.in/square/go-jose.v2/json"
)
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
@@ -40,7 +43,10 @@ type rawSignatureInfo struct {
// JSONWebSignature represents a signed JWS object after parsing.
type JSONWebSignature struct {
payload []byte
payload []byte
// Signatures attached to this object (may be more than one for multi-sig).
// Be careful about accessing these directly, prefer to use Verify() or
// VerifyMulti() to ensure that the data you're getting is verified.
Signatures []Signature
}
@@ -57,7 +63,7 @@ type Signature struct {
original *rawSignatureInfo
}
// ParseSigned parses an encrypted message in compact or full serialization format.
// ParseSigned parses a signed message in compact or full serialization format.
func ParseSigned(input string) (*JSONWebSignature, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
@@ -95,7 +101,7 @@ func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte {
// parseSignedFull parses a message in full format.
func parseSignedFull(input string) (*JSONWebSignature, error) {
var parsed rawJSONWebSignature
err := UnmarshalJSON([]byte(input), &parsed)
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
@@ -119,12 +125,13 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
signature := Signature{}
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
signature.protected = &rawHeader{}
err := UnmarshalJSON(parsed.Protected.bytes(), signature.protected)
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
if err != nil {
return nil, err
}
}
// Check that there is not a nonce in the unprotected header
if parsed.Header != nil && parsed.Header.Nonce != "" {
return nil, ErrUnprotectedNonce
}
@@ -147,13 +154,20 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
}
signature.Header = signature.mergedHeaders().sanitized()
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
jwk := signature.Header.JSONWebKey
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
}
obj.Signatures = append(obj.Signatures, signature)
}
for i, sig := range parsed.Signatures {
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
obj.Signatures[i].protected = &rawHeader{}
err := UnmarshalJSON(sig.Protected.bytes(), obj.Signatures[i].protected)
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
if err != nil {
return nil, err
}
@@ -164,14 +178,20 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
return nil, ErrUnprotectedNonce
}
obj.Signatures[i].Header = obj.Signatures[i].mergedHeaders().sanitized()
obj.Signatures[i].Signature = sig.Signature.bytes()
// As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded.
jwk := obj.Signatures[i].Header.JSONWebKey
if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) {
return nil, errors.New("square/go-jose: invalid embedded jwk, must be public key")
}
// Copy value of sig
original := sig
obj.Signatures[i].header = sig.Header
obj.Signatures[i].original = &original
obj.Signatures[i].Header = obj.Signatures[i].mergedHeaders().sanitized()
}
return obj, nil

View File

@@ -22,6 +22,16 @@ import (
"testing"
)
func TestEmbeddedHMAC(t *testing.T) {
// protected: {"alg":"HS256", "jwk":{"kty":"oct", "k":"MTEx"}}, aka HMAC key.
msg := `{"payload":"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQ","protected":"eyJhbGciOiJIUzI1NiIsICJqd2siOnsia3R5Ijoib2N0IiwgImsiOiJNVEV4In19","signature":"lvo41ZZsuHwQvSh0uJtEXRR3vmuBJ7in6qMoD7p9jyo"}`
_, err := ParseSigned(msg)
if err == nil {
t.Error("should not allow parsing JWS with embedded JWK with HMAC key")
}
}
func TestCompactParseJWS(t *testing.T) {
// Should parse
msg := "eyJhbGciOiJYWVoifQ.cGF5bG9hZA.c2lnbmF0dXJl"

View File

@@ -17,111 +17,211 @@
package jwt
import "gopkg.in/square/go-jose.v2"
import (
"reflect"
"gopkg.in/square/go-jose.v2/json"
"gopkg.in/square/go-jose.v2"
)
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
// errors are accumulated until the final call to CompactSerialize/FullSerialize.
type Builder struct {
transform func([]byte) (serializer, payload, error)
payload payload
serializer serializer
err error
}
type payload func(interface{}) ([]byte, error)
type serializer interface {
FullSerialize() string
type Builder interface {
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
// into single JSON object.
Claims(i interface{}) Builder
// Token builds a JSONWebToken from provided data.
Token() (*JSONWebToken, error)
// FullSerialize serializes a token using the full serialization format.
FullSerialize() (string, error)
// CompactSerialize serializes a token using the compact serialization format.
CompactSerialize() (string, error)
}
type builder struct {
payload map[string]interface{}
err error
}
type signedBuilder struct {
builder
sig jose.Signer
}
type encryptedBuilder struct {
builder
enc jose.Encrypter
}
// Signed creates builder for signed tokens.
func Signed(sig jose.Signer) *Builder {
return &Builder{
transform: func(b []byte) (serializer, payload, error) {
s, err := sig.Sign(b)
if err != nil {
return nil, nil, err
}
return s, s.Verify, nil
},
func Signed(sig jose.Signer) Builder {
return &signedBuilder{
sig: sig,
}
}
// Encrypted creates builder for encrypted tokens.
func Encrypted(enc jose.Encrypter) *Builder {
return &Builder{
transform: func(b []byte) (serializer, payload, error) {
e, err := enc.Encrypt(b)
if err != nil {
return nil, nil, err
}
return e, e.Decrypt, nil
},
func Encrypted(enc jose.Encrypter) Builder {
return &encryptedBuilder{
enc: enc,
}
}
// Claims encodes claims into the builder.
func (b *Builder) Claims(c interface{}) *Builder {
if b.transform == nil {
panic("Signer/Encrypter not set")
func (b builder) claims(i interface{}) builder {
if b.err != nil {
return b
}
if b.payload != nil {
panic("Claims already set")
}
raw, err := marshalClaims(c)
if err != nil {
return &Builder{
err: err,
m, ok := i.(map[string]interface{})
switch {
case ok:
return b.merge(m)
case reflect.Indirect(reflect.ValueOf(i)).Kind() == reflect.Struct:
m, err := normalize(i)
if err != nil {
return builder{
err: err,
}
}
return b.merge(m)
default:
return builder{
err: ErrInvalidClaims,
}
}
}
ser, pl, err := b.transform(raw)
return &Builder{
transform: b.transform,
serializer: ser,
payload: pl,
err: err,
func normalize(i interface{}) (map[string]interface{}, error) {
m := make(map[string]interface{})
raw, err := json.Marshal(i)
if err != nil {
return nil, err
}
if err := json.Unmarshal(raw, &m); err != nil {
return nil, err
}
return m, nil
}
func (b *builder) merge(m map[string]interface{}) builder {
p := make(map[string]interface{})
for k, v := range b.payload {
p[k] = v
}
for k, v := range m {
p[k] = v
}
return builder{
payload: p,
}
}
// Token builds a JSONWebToken from provided data.
func (b *Builder) Token() (*JSONWebToken, error) {
func (b *builder) token(p func(interface{}) ([]byte, error), h []jose.Header) (*JSONWebToken, error) {
return &JSONWebToken{
payload: p,
Headers: h,
}, nil
}
func (b *signedBuilder) Claims(i interface{}) Builder {
return &signedBuilder{
builder: b.builder.claims(i),
sig: b.sig,
}
}
func (b *signedBuilder) Token() (*JSONWebToken, error) {
sig, err := b.sign()
if err != nil {
return nil, err
}
h := make([]jose.Header, len(sig.Signatures))
for i, v := range sig.Signatures {
h[i] = v.Header
}
return b.builder.token(sig.Verify, h)
}
func (b *signedBuilder) CompactSerialize() (string, error) {
sig, err := b.sign()
if err != nil {
return "", err
}
return sig.CompactSerialize()
}
func (b *signedBuilder) FullSerialize() (string, error) {
sig, err := b.sign()
if err != nil {
return "", err
}
return sig.FullSerialize(), nil
}
func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) {
if b.err != nil {
return nil, b.err
}
if b.payload == nil {
return nil, ErrInvalidClaims
p, err := json.Marshal(b.payload)
if err != nil {
return nil, err
}
return &JSONWebToken{b.payload}, nil
return b.sig.Sign(p)
}
// FullSerialize serializes a token using the full serialization format.
func (b *Builder) FullSerialize() (string, error) {
func (b *encryptedBuilder) Claims(i interface{}) Builder {
return &encryptedBuilder{
builder: b.builder.claims(i),
enc: b.enc,
}
}
func (b *encryptedBuilder) CompactSerialize() (string, error) {
enc, err := b.encrypt()
if err != nil {
return "", err
}
return enc.CompactSerialize()
}
func (b *encryptedBuilder) FullSerialize() (string, error) {
enc, err := b.encrypt()
if err != nil {
return "", err
}
return enc.FullSerialize(), nil
}
func (b *encryptedBuilder) Token() (*JSONWebToken, error) {
enc, err := b.encrypt()
if err != nil {
return nil, err
}
return b.builder.token(enc.Decrypt, []jose.Header{enc.Header})
}
func (b *encryptedBuilder) encrypt() (*jose.JSONWebEncryption, error) {
if b.err != nil {
return "", b.err
return nil, b.err
}
if b.serializer == nil {
return "", ErrInvalidClaims
p, err := json.Marshal(b.payload)
if err != nil {
return nil, err
}
return b.serializer.FullSerialize(), nil
}
// CompactSerialize serializes a token using the compact serialization format.
func (b *Builder) CompactSerialize() (string, error) {
if b.err != nil {
return "", b.err
}
if b.serializer == nil {
return "", ErrInvalidClaims
}
return b.serializer.CompactSerialize()
return b.enc.Encrypt(p)
}

415
vendor/gopkg.in/square/go-jose.v2/jwt/builder_test.go generated vendored Normal file
View File

@@ -0,0 +1,415 @@
/*-
* Copyright 2016 Zbigniew Mandziejewicz
* Copyright 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"io"
"reflect"
"sort"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
)
type testClaims struct {
Subject string `json:"sub"`
}
type invalidMarshalClaims struct {
}
var errInvalidMarshalClaims = errors.New("Failed marshaling invalid claims.")
func (c invalidMarshalClaims) MarshalJSON() ([]byte, error) {
return nil, errInvalidMarshalClaims
}
var sampleClaims = Claims{
Subject: "42",
IssuedAt: NewNumericDate(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)),
Issuer: "issuer",
Audience: Audience{"a1", "a2"},
}
func TestBuilderCustomClaimsNonPointer(t *testing.T) {
jwt, err := Signed(rsaSigner).Claims(testClaims{"foo"}).CompactSerialize()
require.NoError(t, err, "Error creating JWT.")
parsed, err := ParseSigned(jwt)
require.NoError(t, err, "Error parsing JWT.")
out := &testClaims{}
if assert.NoError(t, parsed.Claims(&testPrivRSAKey1.PublicKey, out), "Error unmarshaling claims.") {
assert.Equal(t, "foo", out.Subject)
}
}
func TestBuilderCustomClaimsPointer(t *testing.T) {
jwt, err := Signed(rsaSigner).Claims(&testClaims{"foo"}).CompactSerialize()
require.NoError(t, err, "Error creating JWT.")
parsed, err := ParseSigned(jwt)
require.NoError(t, err, "Error parsing JWT.")
out := &testClaims{}
if assert.NoError(t, parsed.Claims(&testPrivRSAKey1.PublicKey, out), "Error unmarshaling claims.") {
assert.Equal(t, "foo", out.Subject)
}
}
func TestBuilderMergeClaims(t *testing.T) {
jwt, err := Signed(rsaSigner).
Claims(&Claims{
Subject: "42",
}).
Claims(map[string]interface{}{
"Scopes": []string{"read:users"},
}).
CompactSerialize()
require.NoError(t, err, "Error creating JWT.")
parsed, err := ParseSigned(jwt)
require.NoError(t, err, "Error parsing JWT.")
out := make(map[string]interface{})
if assert.NoError(t, parsed.Claims(&testPrivRSAKey1.PublicKey, &out), "Error unmarshaling claims.") {
assert.Equal(t, map[string]interface{}{
"sub": "42",
"Scopes": []interface{}{"read:users"},
}, out)
}
_, err = Signed(rsaSigner).Claims("invalid-claims").Claims(&testClaims{"foo"}).CompactSerialize()
assert.Equal(t, err, ErrInvalidClaims)
_, err = Signed(rsaSigner).Claims(&invalidMarshalClaims{}).CompactSerialize()
assert.EqualError(t, err, "json: error calling MarshalJSON for type *jwt.invalidMarshalClaims: Failed marshaling invalid claims.")
}
func TestSignedFullSerializeAndToken(t *testing.T) {
b := Signed(rsaSigner).Claims(&testClaims{"foo"})
jwt, err := b.FullSerialize()
require.NoError(t, err, "Error creating JWT.")
parsed, err := ParseSigned(jwt)
require.NoError(t, err, "Error parsing JWT.")
out := &testClaims{}
if assert.NoError(t, parsed.Claims(&testPrivRSAKey1.PublicKey, &out), "Error unmarshaling claims.") {
assert.Equal(t, &testClaims{
Subject: "foo",
}, out)
}
jwt2, err := b.Token()
require.NoError(t, err, "Error creating JWT.")
out2 := &testClaims{}
if assert.NoError(t, jwt2.Claims(&testPrivRSAKey1.PublicKey, &out2), "Error unmarshaling claims.") {
assert.Equal(t, &testClaims{
Subject: "foo",
}, out2)
}
b2 := Signed(rsaSigner).Claims(&invalidMarshalClaims{})
_, err = b2.FullSerialize()
require.EqualError(t, err, "json: error calling MarshalJSON for type *jwt.invalidMarshalClaims: Failed marshaling invalid claims.")
_, err = b2.Token()
require.EqualError(t, err, "json: error calling MarshalJSON for type *jwt.invalidMarshalClaims: Failed marshaling invalid claims.")
}
func TestEncryptedFullSerializeAndToken(t *testing.T) {
recipient := jose.Recipient{
Algorithm: jose.RSA1_5,
Key: testPrivRSAKey1.Public(),
}
encrypter, err := jose.NewEncrypter(jose.A128CBC_HS256, recipient, nil)
require.NoError(t, err, "Error creating encrypter.")
b := Encrypted(encrypter).Claims(&testClaims{"foo"})
jwt, err := b.FullSerialize()
require.NoError(t, err, "Error creating JWT.")
parsed, err := ParseEncrypted(jwt)
require.NoError(t, err, "Error parsing JWT.")
out := &testClaims{}
if assert.NoError(t, parsed.Claims(testPrivRSAKey1, &out)) {
assert.Equal(t, &testClaims{
Subject: "foo",
}, out)
}
jwt2, err := b.Token()
require.NoError(t, err, "Error creating JWT.")
out2 := &testClaims{}
if assert.NoError(t, jwt2.Claims(testPrivRSAKey1, &out2)) {
assert.Equal(t, &testClaims{
Subject: "foo",
}, out2)
}
b2 := Encrypted(encrypter).Claims(&invalidMarshalClaims{})
_, err = b2.FullSerialize()
require.EqualError(t, err, "json: error calling MarshalJSON for type *jwt.invalidMarshalClaims: Failed marshaling invalid claims.")
_, err = b2.Token()
require.EqualError(t, err, "json: error calling MarshalJSON for type *jwt.invalidMarshalClaims: Failed marshaling invalid claims.")
}
func TestBuilderHeadersSigner(t *testing.T) {
tests := []struct {
Keys []*rsa.PrivateKey
Claims interface{}
}{
{
Keys: []*rsa.PrivateKey{testPrivRSAKey1},
Claims: &Claims{Issuer: "foo"},
},
{
Keys: []*rsa.PrivateKey{testPrivRSAKey1, testPrivRSAKey2},
Claims: &Claims{Issuer: "foo"},
},
}
for i, tc := range tests {
wantKeyIDs := make([]string, len(tc.Keys))
signingKeys := make([]jose.SigningKey, len(tc.Keys))
for j, key := range tc.Keys {
keyIDBytes := make([]byte, 20)
if _, err := io.ReadFull(rand.Reader, keyIDBytes); err != nil {
t.Fatalf("failed to read random bytes: %v", err)
}
keyID := hex.EncodeToString(keyIDBytes)
wantKeyIDs[j] = keyID
signingKeys[j] = jose.SigningKey{
Algorithm: jose.RS256,
Key: &jose.JSONWebKey{
KeyID: keyID,
Algorithm: "RSA",
Key: key,
},
}
}
signer, err := jose.NewMultiSigner(signingKeys, nil)
if err != nil {
t.Errorf("case %d: NewMultiSigner(): %v", i, err)
continue
}
var token string
if len(tc.Keys) == 1 {
token, err = Signed(signer).Claims(tc.Claims).CompactSerialize()
} else {
token, err = Signed(signer).Claims(tc.Claims).FullSerialize()
}
if err != nil {
t.Errorf("case %d: failed to create token: %v", i, err)
continue
}
jws, err := jose.ParseSigned(token)
if err != nil {
t.Errorf("case %d: parse signed: %v", i, err)
continue
}
gotKeyIDs := make([]string, len(jws.Signatures))
for i, sig := range jws.Signatures {
gotKeyIDs[i] = sig.Header.KeyID
}
sort.Strings(wantKeyIDs)
sort.Strings(gotKeyIDs)
if !reflect.DeepEqual(wantKeyIDs, gotKeyIDs) {
t.Errorf("case %d: wanted=%q got=%q", i, wantKeyIDs, gotKeyIDs)
}
}
}
func TestBuilderHeadersEncrypter(t *testing.T) {
key := testPrivRSAKey1
claims := &Claims{Issuer: "foo"}
keyIDBytes := make([]byte, 20)
if _, err := io.ReadFull(rand.Reader, keyIDBytes); err != nil {
t.Fatalf("failed to read random bytes: %v", err)
}
keyID := hex.EncodeToString(keyIDBytes)
wantKeyID := keyID
recipient := jose.Recipient{
Algorithm: jose.RSA1_5,
Key: key.Public(),
KeyID: keyID,
}
encrypter, err := jose.NewEncrypter(jose.A128CBC_HS256, recipient, nil)
if err != nil {
t.Errorf("NewEncrypter(): %v", err)
return
}
token, err := Encrypted(encrypter).Claims(claims).CompactSerialize()
if err != nil {
t.Errorf("failed to create token: %v", err)
return
}
jwe, err := jose.ParseEncrypted(token)
if err != nil {
t.Errorf("parse signed: %v", err)
return
}
if gotKeyID := jwe.Header.KeyID; gotKeyID != wantKeyID {
t.Errorf("wanted=%q got=%q", wantKeyID, gotKeyID)
}
}
func BenchmarkMapClaims(b *testing.B) {
m := map[string]interface{}{
"sub": "42",
"iat": 1451606400,
"iss": "issuer",
"aud": []string{"a1", "a2"},
}
for i := 0; i < b.N; i++ {
Signed(rsaSigner).Claims(m)
}
}
func BenchmarkStructClaims(b *testing.B) {
for i := 0; i < b.N; i++ {
Signed(rsaSigner).Claims(sampleClaims)
}
}
func BenchmarkSignedCompactSerializeRSA(b *testing.B) {
tb := Signed(rsaSigner).Claims(sampleClaims)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tb.CompactSerialize()
}
}
func BenchmarkSignedCompactSerializeSHA(b *testing.B) {
tb := Signed(hmacSigner).Claims(sampleClaims)
b.ResetTimer()
for i := 0; i < b.N; i++ {
tb.CompactSerialize()
}
}
func mustUnmarshalRSA(data string) *rsa.PrivateKey {
block, _ := pem.Decode([]byte(data))
if block == nil {
panic("failed to decode PEM data")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
panic("failed to parse RSA key: " + err.Error())
}
if key, ok := key.(*rsa.PrivateKey); ok {
return key
}
panic("key is not of type *rsa.PrivateKey")
}
func mustMakeSigner(alg jose.SignatureAlgorithm, k interface{}) jose.Signer {
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: alg, Key: k}, nil)
if err != nil {
panic("failed to create signer:" + err.Error())
}
return sig
}
var (
sharedKey = []byte("secret")
sharedEncryptionKey = []byte("itsa16bytesecret")
testPrivRSAKey1 = mustUnmarshalRSA(`-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDIHBvDHAr7jh8h
xaqBCl11fjI9YZtdC5b3HtXTXZW3c2dIOImNUjffT8POP6p5OpzivmC1om7iOyuZ
3nJjC9LT3zqqs3f2i5d4mImxEuqG6uWdryFfkp0uIv5VkjVO+iQWd6pDAPGP7r1Z
foXCleyCtmyNH4JSkJneNPOk/4BxO8vcvRnCMT/Gv81IT6H+OQ6OovWOuJr8RX9t
1wuCjC9ezZxeI9ONffhiO5FMrVh5H9LJTl3dPOVa4aEcOvgd45hBmvxAyXqf8daE
6Kl2O7vQ4uwgnSTVXYIIjCjbepuersApIMGx/XPSgiU1K3Xtah/TBvep+S3VlwPc
q/QH25S9AgMBAAECggEAe+y8XKYfPw4SxY1uPB+5JSwT3ON3nbWxtjSIYy9Pqp5z
Vcx9kuFZ7JevQSk4X38m7VzM8282kC/ono+d8yy9Uayq3k/qeOqV0X9Vti1qxEbw
ECkG1/MqGApfy4qSLOjINInDDV+mOWa2KJgsKgdCwuhKbVMYGB2ozG2qfYIlfvlY
vLcBEpGWmswJHNmkcjTtGFIyJgPbsI6ndkkOeQbqQKAaadXtG1xUzH+vIvqaUl/l
AkNf+p4qhPkHsoAWXf1qu9cYa2T8T+mEo79AwlgVC6awXQWNRTiyClDJC7cu6NBy
ZHXCLFMbalzWF9qeI2OPaFX2x3IBWrbyDxcJ4TSdQQKBgQD/Fp/uQonMBh1h4Vi4
HlxZdqSOArTitXValdLFGVJ23MngTGV/St4WH6eRp4ICfPyldsfcv6MZpNwNm1Rn
lB5Gtpqpby1dsrOSfvVbY7U3vpLnd8+hJ/lT5zCYt5Eor46N6iWRkYWzNe4PixiF
z1puGUvFCbZdeeACVrPLmW3JKQKBgQDI0y9WTf8ezKPbtap4UEE6yBf49ftohVGz
p4iD6Ng1uqePwKahwoVXKOc179CjGGtW/UUBORAoKRmxdHajHq6LJgsBxpaARz21
COPy99BUyp9ER5P8vYn63lC7Cpd/K7uyMjaz1DAzYBZIeVZHIw8O9wuGNJKjRFy9
SZyD3V0ddQKBgFMdohrWH2QVEfnUnT3Q1rJn0BJdm2bLTWOosbZ7G72TD0xAWEnz
sQ1wXv88n0YER6X6YADziEdQykq8s/HT91F/KkHO8e83zP8M0xFmGaQCOoelKEgQ
aFMIX3NDTM7+9OoUwwz9Z50PE3SJFAJ1n7eEEoYvNfabQXxBl+/dHEKRAoGAPEvU
EaiXacrtg8EWrssB2sFLGU/ZrTciIbuybFCT4gXp22pvXXAHEvVP/kzDqsRhLhwb
BNP6OuSkNziNikpjA5pngZ/7fgZly54gusmW/m5bxWdsUl0iOXVYbeAvPlqGH2me
LP4Pfs1hw17S/cbT9Z1NE31jbavP4HFikeD73SUCgYEArQfuudml6ei7XZ1Emjq8
jZiD+fX6e6BD/ISatVnuyZmGj9wPFsEhY2BpLiAMQHMDIvH9nlKzsFvjkTPB86qG
jCh3D67Os8eSBk5uRC6iW3Fc4DXvB5EFS0W9/15Sl+V5vXAcrNMpYS82OTSMG2Gt
b9Ym/nxaqyTu0PxajXkKm5Q=
-----END PRIVATE KEY-----`)
testPrivRSAKey2 = mustUnmarshalRSA(`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxJ09jkXZ5Okyq
FrEKrs+GTzZRvoLziyzDTIZLJC6BVryau4gaFjuBG+pnm4z53oDP0XVnjFsx1mBw
R6RHeXlXbxLXsMfJpMzU9I2SRen9DokpD187CAnjLOoN9QRl1h8CA+sqR5Jw9mdl
mdaBKC99M9QYAPK3vGNfPC4soo8LDSBiemmt5raL4WSfoYh/6qg5rHUTymY28uxV
ew3I9Yp+3ltIw+WlRDtW5l+MM5CSUofjj2zcgcG3LEuPtvyZ+CSObxxcZZugm9zc
JdiazNyUxtX8yAj3Xg8Hde0jt0QDXv7A+U0KMVi9lX6PJEaNj4tOhOmQhJVMzAyr
1W/bifZVAgMBAAECggEAduKnn21GMZLTUi4KP94SvNK55F/Sp7hVoPbhBNpSL1BT
IBAMBV24LyvZwhAcqq8MiOrLPGNv6+EvNQqPD7xQl0GeRouHeCYVpDA+NdSfc8jm
eVysjwQVBpTkudsdSW5JvuN8VRJVD2P8/a0gy+p4/C/k/Prd6DoQAiBz6FZrYoEd
iYgIegHOMXWd4vzO3ENOWSIUI6ci7Aro+Y0Z75kfiVokAGhUcFgrZ58E82fBYh8I
cxO20oMnucGrLicQzj536jx4wX3Cdd4jr9UVEJ9ZII1ldlp03nZlFLXqJH1547Aq
ZM+3vVcBGoJ8T9ZQ4VDAL++0K2DLC9JkTARAYCEi/QKBgQDebIc1+2zblhQtVQ/e
IbEErZcB7v+TkUoRoBfR0lj7bKBFJgRe37fgu1xf95/s63okdnOw/OuQqtGmgx/J
TL3yULBdNcwTCRm41t+cqoGymjK0VRbqk6CWBId0E3r5TaCVWedk2JI2XwTvIJ1A
eDiqfJeDHUD44yaonwbysj9ZDwKBgQDL5VQfTppVaJk2PXNwhAkRQklZ8RFmt/7p
yA3dddQNdwMk4Fl8F7QuO1gBxDiHdnwIrlEOz6fTsM3LwIS+Q12P1vYFIhpo7HDB
wvjfMwCPxBIS4jI28RgcAf0VbZ/+CHAm6bb9iDwsjXhh1J5oOm5VKnju6/rPH/QY
+md40pnSWwKBgBnKPbdNquafNUG4XjmkcHEZa6wGuU20CAGZLYnfuP+WLdM2wET7
7cc6ElDyVnHTL/twXKPF/85rcBm9lH7zzgZ9wqVcKoh+gqQDDjSNNLKv3Hc6cojK
i1E5vzb/Vz/290q5/PGdhv6U7+6GOpWSGwfxoGPMjY8OT5o3rkeP0XaTAoGBALLR
GQmr4eZtqZDMK+XNpjYgsDvVE7HGRCW7cY17vNFiQruglloiX778BJ7n+7uxye3D
EwuuSj15ncLHwKMsaW2w1GqEEi1azzjfSWxWSnPLPR6aifdtUfueMtsMHXio5dL6
vaV0SXG5UI5b7eDy/bhrW0wOYRQtreIKGZz49jZpAoGBAIvxYngkLwmq6g6MmnAc
YK4oT6YAm2wfSy2mzpEQP5r1igp1rN7T46o7FMUPDLS9wK3ESAaIYe01qT6Yftcc
5qF+yiOGDTr9XQiHwe4BcyrNEMfUjDhDU5ao2gH8+t1VGr1KspLsUNbedrJwZsY4
UCZVKEEDHzKfLO/iBgKjJQF7
-----END PRIVATE KEY-----`)
rsaSigner = mustMakeSigner(jose.RS256, testPrivRSAKey1)
hmacSigner = mustMakeSigner(jose.HS256, sharedKey)
)

View File

@@ -18,66 +18,98 @@
package jwt
import (
"encoding/json"
"strconv"
"time"
"gopkg.in/square/go-jose.v2"
)
// Claims represents public claim values (as specified in RFC 7519).
type Claims struct {
Issuer string `json:"-"`
Subject string `json:"-"`
Audience []string `json:"-"`
Expiry time.Time `json:"-"`
NotBefore time.Time `json:"-"`
IssuedAt time.Time `json:"-"`
ID string `json:"-"`
Issuer string `json:"iss,omitempty"`
Subject string `json:"sub,omitempty"`
Audience Audience `json:"aud,omitempty"`
Expiry NumericDate `json:"exp,omitempty"`
NotBefore NumericDate `json:"nbf,omitempty"`
IssuedAt NumericDate `json:"iat,omitempty"`
ID string `json:"jti,omitempty"`
}
type rawClaims struct {
Iss string `json:"iss,omitempty"`
Sub string `json:"sub,omitempty"`
Aud audience `json:"aud,omitempty"`
Exp NumericDate `json:"exp,omitempty"`
Nbf NumericDate `json:"nbf,omitempty"`
Iat NumericDate `json:"iat,omitempty"`
Jti string `json:"jti,omitempty"`
}
// NumericDate represents date and time as the number of seconds since the
// epoch, including leap seconds. Non-integer values can be represented
// in the serialized format, but we round to the nearest second.
type NumericDate int64
func (c *Claims) marshalJSON() ([]byte, error) {
t := rawClaims{
Iss: c.Issuer,
Sub: c.Subject,
Aud: audience(c.Audience),
Exp: TimeToNumericDate(c.Expiry),
Nbf: TimeToNumericDate(c.NotBefore),
Iat: TimeToNumericDate(c.IssuedAt),
Jti: c.ID,
// NewNumericDate constructs NumericDate from time.Time value.
func NewNumericDate(t time.Time) NumericDate {
if t.IsZero() {
return NumericDate(0)
}
b, err := jose.MarshalJSON(t)
// While RFC 7519 technically states that NumericDate values may be
// non-integer values, we don't bother serializing timestamps in
// claims with sub-second accurancy and just round to the nearest
// second instead. Not convined sub-second accuracy is useful here.
return NumericDate(t.Unix())
}
// MarshalJSON serializes the given NumericDate into its JSON representation.
func (n NumericDate) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(int64(n), 10)), nil
}
// UnmarshalJSON reads a date from its JSON representation.
func (n *NumericDate) UnmarshalJSON(b []byte) error {
s := string(b)
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
return ErrUnmarshalNumericDate
}
return b, err
*n = NumericDate(f)
return nil
}
func (c *Claims) unmarshalJSON(b []byte) error {
t := rawClaims{}
// Time returns time.Time representation of NumericDate.
func (n NumericDate) Time() time.Time {
return time.Unix(int64(n), 0)
}
if err := jose.UnmarshalJSON(b, &t); err != nil {
// Audience represents the recipents that the token is intended for.
type Audience []string
// UnmarshalJSON reads an audience from its JSON representation.
func (s *Audience) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
c.Issuer = t.Iss
c.Subject = t.Sub
c.Audience = []string(t.Aud)
c.Expiry = t.Exp.Time()
c.NotBefore = t.Nbf.Time()
c.IssuedAt = t.Iat.Time()
c.ID = t.Jti
switch v := v.(type) {
case string:
*s = []string{v}
case []interface{}:
a := make([]string, len(v))
for i, e := range v {
s, ok := e.(string)
if !ok {
return ErrUnmarshalAudience
}
a[i] = s
}
*s = a
default:
return ErrUnmarshalAudience
}
return nil
}
func (s Audience) Contains(v string) bool {
for _, a := range s {
if a == v {
return true
}
}
return false
}

View File

@@ -21,6 +21,8 @@ import (
"testing"
"time"
"gopkg.in/square/go-jose.v2/json"
"github.com/stretchr/testify/assert"
)
@@ -28,14 +30,15 @@ func TestEncodeClaims(t *testing.T) {
now := time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)
c := Claims{
Issuer: "issuer",
Subject: "subject",
Audience: []string{"a1", "a2"},
IssuedAt: now,
Expiry: now.Add(1 * time.Hour),
Issuer: "issuer",
Subject: "subject",
Audience: Audience{"a1", "a2"},
NotBefore: NewNumericDate(time.Time{}),
IssuedAt: NewNumericDate(now),
Expiry: NewNumericDate(now.Add(1 * time.Hour)),
}
b, err := c.marshalJSON()
b, err := json.Marshal(c)
assert.NoError(t, err)
expected := `{"iss":"issuer","sub":"subject","aud":["a1","a2"],"exp":1451610000,"iat":1451606400}`
@@ -47,12 +50,31 @@ func TestDecodeClaims(t *testing.T) {
now := time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)
c := Claims{}
err := c.unmarshalJSON(s)
assert.NoError(t, err)
if err := json.Unmarshal(s, &c); assert.NoError(t, err) {
assert.Equal(t, "issuer", c.Issuer)
assert.Equal(t, "subject", c.Subject)
assert.Equal(t, Audience{"a1", "a2"}, c.Audience)
assert.True(t, now.Equal(c.IssuedAt.Time()))
assert.True(t, now.Add(1*time.Hour).Equal(c.Expiry.Time()))
}
assert.Equal(t, "issuer", c.Issuer)
assert.Equal(t, "subject", c.Subject)
assert.Equal(t, []string{"a1", "a2"}, c.Audience)
assert.True(t, now.Equal(c.IssuedAt))
assert.True(t, now.Add(1*time.Hour).Equal(c.Expiry))
s2 := []byte(`{"aud": "a1"}`)
c2 := Claims{}
if err := json.Unmarshal(s2, &c2); assert.NoError(t, err) {
assert.Equal(t, Audience{"a1"}, c2.Audience)
}
invalid := []struct {
Raw string
Err error
}{
{`{"aud": 5}`, ErrUnmarshalAudience},
{`{"aud": ["foo", 5, "bar"]}`, ErrUnmarshalAudience},
{`{"exp": "invalid"}`, ErrUnmarshalNumericDate},
}
for _, v := range invalid {
c := Claims{}
assert.Equal(t, v.Err, json.Unmarshal([]byte(v.Raw), &c))
}
}

200
vendor/gopkg.in/square/go-jose.v2/jwt/example_test.go generated vendored Normal file
View File

@@ -0,0 +1,200 @@
/*-
* Copyright 2016 Zbigniew Mandziejewicz
* Copyright 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt_test
import (
"fmt"
"strings"
"time"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
var sharedKey = []byte("secret")
var sharedEncryptionKey = []byte("itsa16bytesecret")
var signer, _ = jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: sharedKey}, &jose.SignerOptions{})
func ExampleParseSigned() {
raw := `eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJzdWIiOiJzdWJqZWN0In0.gpHyA1B1H6X4a4Edm9wo7D3X2v3aLSDBDG2_5BzXYe0`
tok, err := jwt.ParseSigned(raw)
if err != nil {
panic(err)
}
out := jwt.Claims{}
if err := tok.Claims(sharedKey, &out); err != nil {
panic(err)
}
fmt.Printf("iss: %s, sub: %s\n", out.Issuer, out.Subject)
// Output: iss: issuer, sub: subject
}
func ExampleParseEncrypted() {
key := []byte("itsa16bytesecret")
raw := `eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..jg45D9nmr6-8awml.z-zglLlEw9MVkYHi-Znd9bSwc-oRGbqKzf9WjXqZxno.kqji2DiZHZmh-1bLF6ARPw`
tok, err := jwt.ParseEncrypted(raw)
if err != nil {
panic(err)
}
out := jwt.Claims{}
if err := tok.Claims(key, &out); err != nil {
panic(err)
}
fmt.Printf("iss: %s, sub: %s\n", out.Issuer, out.Subject)
//Output: iss: issuer, sub: subject
}
func ExampleClaims_Validate() {
cl := jwt.Claims{
Subject: "subject",
Issuer: "issuer",
NotBefore: jwt.NewNumericDate(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)),
Expiry: jwt.NewNumericDate(time.Date(2016, 1, 1, 0, 15, 0, 0, time.UTC)),
Audience: jwt.Audience{"leela", "fry"},
}
err := cl.Validate(jwt.Expected{
Issuer: "issuer",
Time: time.Date(2016, 1, 1, 0, 10, 0, 0, time.UTC),
})
if err != nil {
panic(err)
}
fmt.Printf("valid!")
// Output: valid!
}
func ExampleClaims_Validate_withParse() {
raw := `eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJzdWIiOiJzdWJqZWN0In0.gpHyA1B1H6X4a4Edm9wo7D3X2v3aLSDBDG2_5BzXYe0`
tok, err := jwt.ParseSigned(raw)
if err != nil {
panic(err)
}
cl := jwt.Claims{}
if err := tok.Claims(sharedKey, &cl); err != nil {
panic(err)
}
err = cl.Validate(jwt.Expected{
Issuer: "issuer",
Subject: "subject",
})
if err != nil {
panic(err)
}
fmt.Printf("valid!")
// Output: valid!
}
func ExampleSigned() {
key := []byte("secret")
sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, &jose.SignerOptions{})
if err != nil {
panic(err)
}
cl := jwt.Claims{
Subject: "subject",
Issuer: "issuer",
NotBefore: jwt.NewNumericDate(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)),
Audience: jwt.Audience{"leela", "fry"},
}
raw, err := jwt.Signed(sig).Claims(cl).CompactSerialize()
if err != nil {
panic(err)
}
fmt.Println(raw)
// Output: eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsibGVlbGEiLCJmcnkiXSwiaXNzIjoiaXNzdWVyIiwibmJmIjoxLjQ1MTYwNjRlKzA5LCJzdWIiOiJzdWJqZWN0In0.uazfxZNgnlLdNDK7JkuYj3LlT4jSyEDG8EWISBPUuME
}
func ExampleEncrypted() {
enc, err := jose.NewEncrypter(jose.A128GCM, jose.Recipient{Algorithm: jose.DIRECT, Key: sharedEncryptionKey}, nil)
if err != nil {
panic(err)
}
cl := jwt.Claims{
Subject: "subject",
Issuer: "issuer",
}
raw, err := jwt.Encrypted(enc).Claims(cl).CompactSerialize()
if err != nil {
panic(err)
}
fmt.Println(raw)
}
func ExampleSigned_multipleClaims() {
c := &jwt.Claims{
Subject: "subject",
Issuer: "issuer",
}
c2 := struct {
Scopes []string
}{
[]string{"foo", "bar"},
}
raw, err := jwt.Signed(signer).Claims(c).Claims(c2).CompactSerialize()
if err != nil {
panic(err)
}
fmt.Println(raw)
// Output: eyJhbGciOiJIUzI1NiJ9.eyJTY29wZXMiOlsiZm9vIiwiYmFyIl0sImlzcyI6Imlzc3VlciIsInN1YiI6InN1YmplY3QifQ.esKOIsmwkudr_gnfnB4SngxIr-7pspd5XzG3PImfQ6Y
}
func ExampleJSONWebToken_Claims_map() {
raw := `eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJzdWIiOiJzdWJqZWN0In0.gpHyA1B1H6X4a4Edm9wo7D3X2v3aLSDBDG2_5BzXYe0`
tok, err := jwt.ParseSigned(raw)
if err != nil {
panic(err)
}
out := make(map[string]interface{})
if err := tok.Claims(sharedKey, &out); err != nil {
panic(err)
}
fmt.Printf("iss: %s, sub: %s\n", out["iss"], out["sub"])
// Output: iss: issuer, sub: subject
}
func ExampleJSONWebToken_Claims_multiple() {
raw := `eyJhbGciOiJIUzI1NiJ9.eyJTY29wZXMiOlsiZm9vIiwiYmFyIl0sImlzcyI6Imlzc3VlciIsInN1YiI6InN1YmplY3QifQ.esKOIsmwkudr_gnfnB4SngxIr-7pspd5XzG3PImfQ6Y`
tok, err := jwt.ParseSigned(raw)
if err != nil {
panic(err)
}
out := jwt.Claims{}
out2 := struct {
Scopes []string
}{}
if err := tok.Claims(sharedKey, &out, &out2); err != nil {
panic(err)
}
fmt.Printf("iss: %s, sub: %s, scopes: %s\n", out.Issuer, out.Subject, strings.Join(out2.Scopes, ","))
// Output: iss: issuer, sub: subject, scopes: foo,bar
}

View File

@@ -1,179 +0,0 @@
/*-
* Copyright 2016 Zbigniew Mandziejewicz
* Copyright 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
import (
"math"
"reflect"
"strconv"
"time"
"gopkg.in/square/go-jose.v2"
)
// NumericDate represents date and time as the number of seconds since the
// epoch, including leap seconds. Non-integer values can be represented
// in the serialized format, but we round to the nearest second.
type NumericDate int64
// TimeToNumericDate converts time.Time value into NumericDate.
func TimeToNumericDate(t time.Time) NumericDate {
if t.IsZero() {
return NumericDate(0)
}
// While RFC 7519 technically states that NumericDate values may be
// non-integer values, we don't bother serializing timestamps in
// claims with sub-second accurancy and just round to the nearest
// second instead. Not convined sub-second accuracy is useful here.
return NumericDate(t.Unix())
}
// MarshalJSON serializes the given NumericDate into its JSON representation.
func (n NumericDate) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(int64(n), 10)), nil
}
// UnmarshalJSON reads a date from its JSON representation.
func (n *NumericDate) UnmarshalJSON(b []byte) error {
s := string(b)
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return ErrUnmarshalNumericDate
}
*n = NumericDate(f)
return nil
}
// Time returns time.Time representation of NumericDate.
func (n NumericDate) Time() time.Time {
i, f := math.Modf(float64(n))
return time.Unix(int64(i), int64(f*float64(time.Second)))
}
type audience []string
func (s *audience) UnmarshalJSON(b []byte) error {
var v interface{}
if err := jose.UnmarshalJSON(b, &v); err != nil {
return err
}
switch v := v.(type) {
case string:
*s = append(*s, v)
case []interface{}:
a := make([]string, len(v))
for i, e := range v {
s, ok := e.(string)
if !ok {
return ErrUnmarshalAudience
}
a[i] = s
}
*s = a
default:
return ErrUnmarshalAudience
}
return nil
}
var claimsType = reflect.TypeOf((*Claims)(nil)).Elem()
func publicClaims(cl interface{}) (*Claims, error) {
v := reflect.ValueOf(cl)
if v.IsNil() || v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
return nil, ErrInvalidClaims
}
v = v.Elem()
f := v.FieldByName("Claims")
if !f.IsValid() || f.Type() != claimsType {
return nil, nil
}
c := f.Addr().Interface().(*Claims)
return c, nil
}
func marshalClaims(cl interface{}) ([]byte, error) {
switch cl := cl.(type) {
case *Claims:
return cl.marshalJSON()
case map[string]interface{}:
return jose.MarshalJSON(cl)
}
public, err := publicClaims(cl)
if err != nil {
return nil, err
}
// i doesn't contain nested jwt.Claims
if public == nil {
return jose.MarshalJSON(cl)
}
// marshal jwt.Claims
b1, err := public.marshalJSON()
if err != nil {
return nil, err
}
// marshal private claims
b2, err := jose.MarshalJSON(cl)
if err != nil {
return nil, err
}
// merge claims
r := make([]byte, len(b1)+len(b2)-1)
copy(r, b1)
r[len(b1)-1] = ','
copy(r[len(b1):], b2[1:])
return r, nil
}
func unmarshalClaims(b []byte, cl interface{}) error {
switch cl := cl.(type) {
case *Claims:
return cl.unmarshalJSON(b)
case map[string]interface{}:
return jose.UnmarshalJSON(b, cl)
}
if err := jose.UnmarshalJSON(b, cl); err != nil {
return err
}
public, err := publicClaims(cl)
if err != nil {
return err
}
// unmarshal jwt.Claims
if public != nil {
if err := public.unmarshalJSON(b); err != nil {
return err
}
}
return nil
}

View File

@@ -17,20 +17,31 @@
package jwt
import "gopkg.in/square/go-jose.v2"
import (
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/json"
)
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
type JSONWebToken struct {
payload func(k interface{}) ([]byte, error)
Headers []jose.Header
}
// Claims deserializes a JSONWebToken into dest using the provided key.
func (t *JSONWebToken) Claims(dest interface{}, key interface{}) error {
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
b, err := t.payload(key)
if err != nil {
return err
}
return unmarshalClaims(b, dest)
for _, d := range dest {
if err := json.Unmarshal(b, d); err != nil {
return err
}
}
return nil
}
// ParseSigned parses token from JWS form.
@@ -39,8 +50,12 @@ func ParseSigned(s string) (*JSONWebToken, error) {
if err != nil {
return nil, err
}
headers := make([]jose.Header, len(sig.Signatures))
for i, signature := range sig.Signatures {
headers[i] = signature.Header
}
return &JSONWebToken{sig.Verify}, nil
return &JSONWebToken{sig.Verify, headers}, nil
}
// ParseEncrypted parses token from JWE form.
@@ -50,5 +65,5 @@ func ParseEncrypted(s string) (*JSONWebToken, error) {
return nil, err
}
return &JSONWebToken{enc.Decrypt}, nil
return &JSONWebToken{enc.Decrypt, []jose.Header{enc.Header}}, nil
}

View File

@@ -21,57 +21,96 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/square/go-jose.v2"
)
var encryptionKey = []byte("secret")
var rawToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwic2NvcGVzIjpbInMxIiwiczIiXX0.Y6_PfQHrzRJ_Vlxij5VI07-pgDIuJNN3Z_g5sSaGQ0c`
var (
hmacSignedToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwic2NvcGVzIjpbInMxIiwiczIiXX0.Y6_PfQHrzRJ_Vlxij5VI07-pgDIuJNN3Z_g5sSaGQ0c`
rsaSignedToken = `eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJpc3N1ZXIiLCJzY29wZXMiOlsiczEiLCJzMiJdLCJzdWIiOiJzdWJqZWN0In0.UDDtyK9gC9kyHltcP7E_XODsnqcJWZIiXeGmSAH7SE9YKy3N0KSfFIN85dCNjTfs6zvy4rkrCHzLB7uKAtzMearh3q7jL4nxbhUMhlUcs_9QDVoN4q_j58XmRqBqRnBk-RmDu9TgcV8RbErP4awpIhwWb5UU-hR__4_iNbHdKqwSUPDKYGlf5eicuiYrPxH8mxivk4LRD-vyRdBZZKBt0XIDnEU4TdcNCzAXojkftqcFWYsczwS8R4JHd1qYsMyiaWl4trdHZkO4QkeLe34z4ZAaPMt3wE-gcU-VoqYTGxz-K3Le2VaZ0r3j_z6bOInsv0yngC_cD1dCXMyQJWnWjQ`
invalidPayloadSignedToken = `eyJhbGciOiJIUzI1NiJ9.aW52YWxpZC1wYXlsb2Fk.ScBKKm18jcaMLGYDNRUqB5gVMRZl4DM6dh3ShcxeNgY`
invalidPartsSignedToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaXNzIjoiaXNzdWVyIiwic2NvcGVzIjpbInMxIiwiczIiXX0`
hmacEncryptedToken = `eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..NZrU98U4QNO0y-u6.HSq5CvlmkUT1BPqLGZ4.1-zuiZ4RbHrTTUoA8Dvfhg`
rsaEncryptedToken = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.IvkVHHiI8JwwavvTR80xGjYvkzubMrZ-TDDx8k8SNJMEylfFfNUc7F2rC3WAABF_xmJ3SW2A6on-S6EAG97k0RsjqHHNqZuaFpDvjeuLqZFfYKzI45aCtkGG4C2ij2GbeySqJ784CcvFJPUWJ-6VPN2Ho2nhefUSqig0jE2IvOKy1ywTj_VBVBxF_dyXFnXwxPKGUQr3apxrWeRJfDh2Cf8YPBlLiRznjfBfwgePB1jP7WCZNwItj10L7hsT_YWEx01XJcbxHaXFLwKyVzwWaDhreFyaWMRbGqEfqVuOT34zfmhLDhQlgLLwkXrvYqX90NsQ9Ftg0LLIfRMbsfdgug.BFy2Tj1RZN8yq2Lk-kMiZQ.9Z0eOyPiv5cEzmXh64RlAQ36Uvz0WpZgqRcc2_69zHTmUOv0Vnl1I6ks8sTraUEvukAilolNBjBj47s0b4b-Og.VM8-eJg5ZsqnTqs0LtGX_Q`
invalidPayloadEncryptedToken = `eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..T4jCS4Yyw1GCH0aW.y4gFaMITdBs_QZM8RKrL.6MPyk1cMVaOJFoNGlEuaRQ`
invalidPartsEncryptedToken = `eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..NZrU98U4QNO0y-u6.HSq5CvlmkUT1BPqLGZ4`
)
type customClaims struct {
Claims
Scopes []string `json:"scopes,omitempty"`
}
func TestDecodeToken(t *testing.T) {
tok, err := ParseSigned(rawToken)
assert.NoError(t, err)
c := &Claims{}
if assert.NoError(t, tok.Claims(c, encryptionKey)) {
assert.Equal(t, c.Subject, "subject")
assert.Equal(t, c.Issuer, "issuer")
tok, err := ParseSigned(hmacSignedToken)
if assert.NoError(t, err, "Error parsing signed token.") {
c := &Claims{}
c2 := &customClaims{}
if assert.NoError(t, tok.Claims(sharedKey, c, c2)) {
assert.Equal(t, "subject", c.Subject)
assert.Equal(t, "issuer", c.Issuer)
assert.Equal(t, []string{"s1", "s2"}, c2.Scopes)
}
}
assert.EqualError(t, tok.Claims([]byte("invalid-secret")), "square/go-jose: error in cryptographic primitive")
tok2, err := ParseSigned(rsaSignedToken)
if assert.NoError(t, err, "Error parsing encrypted token.") {
c := make(map[string]interface{})
if assert.NoError(t, tok2.Claims(&testPrivRSAKey1.PublicKey, &c)) {
assert.Equal(t, map[string]interface{}{
"sub": "subject",
"iss": "issuer",
"scopes": []interface{}{"s1", "s2"},
}, c)
}
}
assert.EqualError(t, tok.Claims(&testPrivRSAKey2.PublicKey), "square/go-jose: error in cryptographic primitive")
tok3, err := ParseSigned(invalidPayloadSignedToken)
if assert.NoError(t, err, "Error parsing signed token.") {
assert.Error(t, tok3.Claims(sharedKey, &Claims{}), "Expected unmarshaling claims to fail.")
}
c2 := &customClaims{}
if assert.NoError(t, tok.Claims(c2, encryptionKey)) {
assert.Equal(t, c2.Subject, "subject")
assert.Equal(t, c2.Issuer, "issuer")
assert.Equal(t, c2.Scopes, []string{"s1", "s2"})
_, err = ParseSigned(invalidPartsSignedToken)
assert.EqualError(t, err, "square/go-jose: compact JWS format must have three parts")
tok4, err := ParseEncrypted(hmacEncryptedToken)
if assert.NoError(t, err, "Error parsing encrypted token.") {
c := Claims{}
if assert.NoError(t, tok4.Claims(sharedEncryptionKey, &c)) {
assert.Equal(t, "foo", c.Subject)
}
}
assert.EqualError(t, tok4.Claims([]byte("invalid-secret-key")), "square/go-jose: error in cryptographic primitive")
tok5, err := ParseEncrypted(rsaEncryptedToken)
if assert.NoError(t, err, "Error parsing encrypted token.") {
c := make(map[string]interface{})
if assert.NoError(t, tok5.Claims(testPrivRSAKey1, &c)) {
assert.Equal(t, map[string]interface{}{
"sub": "subject",
"iss": "issuer",
"scopes": []interface{}{"s1", "s2"},
}, c)
}
}
assert.EqualError(t, tok5.Claims(testPrivRSAKey2), "square/go-jose: error in cryptographic primitive")
tok6, err := ParseEncrypted(invalidPayloadEncryptedToken)
if assert.NoError(t, err, "Error parsing encrypted token.") {
assert.Error(t, tok6.Claims(sharedEncryptionKey, &Claims{}))
}
_, err = ParseEncrypted(invalidPartsEncryptedToken)
assert.EqualError(t, err, "square/go-jose: compact JWE format must have five parts")
}
func BenchmarkDecodeSignedToken(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseSigned(hmacSignedToken)
}
}
func TestEncodeToken(t *testing.T) {
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: encryptionKey}, &jose.SignerOptions{})
require.NoError(t, err)
c := &customClaims{
Claims: Claims{
Subject: "subject",
Issuer: "issuer",
},
Scopes: []string{"s1", "s2"},
}
raw, err := Signed(signer).Claims(c).CompactSerialize()
require.NoError(t, err)
tok, err := ParseSigned(raw)
require.NoError(t, err)
c2 := &customClaims{}
if assert.NoError(t, tok.Claims(c2, encryptionKey)) {
assert.Equal(t, c2.Subject, "subject")
assert.Equal(t, c2.Issuer, "issuer")
assert.Equal(t, c2.Scopes, []string{"s1", "s2"})
func BenchmarkDecodeEncryptedHMACToken(b *testing.B) {
for i := 0; i < b.N; i++ {
ParseEncrypted(hmacEncryptedToken)
}
}

View File

@@ -24,13 +24,19 @@ const (
DefaultLeeway = 1.0 * time.Minute
)
// Expected defines values used for claims validation.
// Expected defines values used for protected claims validation.
// If field has zero value then validation is skipped.
type Expected struct {
Issuer string
Subject string
Audience []string
ID string
Time time.Time
// Issuer matches the "iss" claim exactly.
Issuer string
// Subject matches the "sub" claim exactly.
Subject string
// Audience matches the values in "aud" claim, regardless of their order.
Audience Audience
// ID matches the "jti" claim exactly.
ID string
// Time matches the "exp" and "ebf" claims with leeway.
Time time.Time
}
// WithTime copies expectations with new time.
@@ -68,18 +74,18 @@ func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
return ErrInvalidAudience
}
for i, a := range e.Audience {
if a != c.Audience[i] {
for _, v := range e.Audience {
if !c.Audience.Contains(v) {
return ErrInvalidAudience
}
}
}
if !e.Time.IsZero() && e.Time.Add(leeway).Before(c.NotBefore) {
if !e.Time.IsZero() && e.Time.Add(leeway).Before(c.NotBefore.Time()) {
return ErrNotValidYet
}
if !e.Time.IsZero() && e.Time.Add(-leeway).After(c.Expiry) {
if !e.Time.IsZero() && e.Time.Add(-leeway).After(c.Expiry.Time()) {
return ErrExpired
}

View File

@@ -32,28 +32,32 @@ func TestFieldsMatch(t *testing.T) {
ID: "42",
}
assert.NoError(t, c.Validate(Expected{Issuer: "issuer"}))
err := c.Validate(Expected{Issuer: "invalid-issuer"})
if assert.Error(t, err) {
assert.Equal(t, err, ErrInvalidIssuer)
valid := []Expected{
{Issuer: "issuer"},
{Subject: "subject"},
{Audience: Audience{"a1", "a2"}},
{Audience: Audience{"a2", "a1"}},
{ID: "42"},
}
assert.NoError(t, c.Validate(Expected{Subject: "subject"}))
err = c.Validate(Expected{Subject: "invalid-subject"})
if assert.Error(t, err) {
assert.Equal(t, err, ErrInvalidSubject)
for _, v := range valid {
assert.NoError(t, c.Validate(v))
}
assert.NoError(t, c.Validate(Expected{Audience: []string{"a1", "a2"}}))
err = c.Validate(Expected{Audience: []string{"invalid-audience"}})
if assert.Error(t, err) {
assert.Equal(t, err, ErrInvalidAudience)
invalid := []struct {
Expected Expected
Error error
}{
{Expected{Issuer: "invalid-issuer"}, ErrInvalidIssuer},
{Expected{Subject: "invalid-subject"}, ErrInvalidSubject},
{Expected{Audience: Audience{"a1"}}, ErrInvalidAudience},
{Expected{Audience: Audience{"a1", "invalid-audience"}}, ErrInvalidAudience},
{Expected{Audience: Audience{"invalid-audience"}}, ErrInvalidAudience},
{Expected{ID: "invalid-id"}, ErrInvalidID},
}
assert.NoError(t, c.Validate(Expected{ID: "42"}))
err = c.Validate(Expected{ID: "invalid-id"})
if assert.Error(t, err) {
assert.Equal(t, err, ErrInvalidID)
for _, v := range invalid {
assert.Equal(t, v.Error, c.Validate(v.Expected))
}
}
@@ -62,9 +66,9 @@ func TestExpiryAndNotBefore(t *testing.T) {
twelveHoursAgo := now.Add(-12 * time.Hour)
c := Claims{
IssuedAt: twelveHoursAgo,
NotBefore: twelveHoursAgo,
Expiry: now,
IssuedAt: NewNumericDate(twelveHoursAgo),
NotBefore: NewNumericDate(twelveHoursAgo),
Expiry: NewNumericDate(now),
}
// expired - default leeway (1 minute)

View File

@@ -20,6 +20,7 @@ import (
"crypto/ecdsa"
"crypto/rsa"
"encoding/base64"
"errors"
"fmt"
)
@@ -185,13 +186,50 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
}
// Verify validates the signature on the object and returns the payload.
// This function does not support multi-signature, if you desire multi-sig
// verification use VerifyMulti instead.
//
// Be careful when verifying signatures based on embedded JWKs inside the
// payload header. You cannot assume that the key received in a payload is
// trusted.
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
verifier, err := newVerifier(verificationKey)
if err != nil {
return nil, err
}
for _, signature := range obj.Signatures {
if len(obj.Signatures) > 1 {
return nil, errors.New("square/go-jose: too many signatures in payload; expecting only one")
}
signature := obj.Signatures[0]
headers := signature.mergedHeaders()
if len(headers.Crit) > 0 {
// Unsupported crit header
return nil, ErrCryptoFailure
}
input := obj.computeAuthData(&signature)
alg := SignatureAlgorithm(headers.Alg)
err = verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return obj.payload, nil
}
return nil, ErrCryptoFailure
}
// VerifyMulti validates (one of the multiple) signatures on the object and
// returns the index of the signature that was verified, along with the signature
// object and the payload. We return the signature and index to guarantee that
// callers are getting the verified value.
func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) {
verifier, err := newVerifier(verificationKey)
if err != nil {
return -1, Signature{}, nil, err
}
for i, signature := range obj.Signatures {
headers := signature.mergedHeaders()
if len(headers.Crit) > 0 {
// Unsupported crit header
@@ -202,9 +240,9 @@ func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error)
alg := SignatureAlgorithm(headers.Alg)
err := verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return obj.payload, nil
return i, signature, obj.payload, nil
}
}
return nil, ErrCryptoFailure
return -1, Signature{}, nil, ErrCryptoFailure
}

View File

@@ -24,6 +24,8 @@ import (
"fmt"
"io"
"testing"
"gopkg.in/square/go-jose.v2/json"
)
type staticNonceSource string
@@ -223,43 +225,45 @@ func TestMultiRecipientJWS(t *testing.T) {
input := []byte("Lorem ipsum dolor sit amet")
obj, err := signer.Sign(input)
if err != nil {
t.Error("error on sign: ", err)
return
t.Fatal("error on sign: ", err)
}
_, err = obj.CompactSerialize()
if err == nil {
t.Error("message with multiple recipient was compact serialized")
t.Fatal("message with multiple recipient was compact serialized")
}
msg := obj.FullSerialize()
obj, err = ParseSigned(msg)
if err != nil {
t.Error("error on parse: ", err)
return
t.Fatal("error on parse: ", err)
}
output, err := obj.Verify(&rsaTestKey.PublicKey)
i, _, output, err := obj.VerifyMulti(&rsaTestKey.PublicKey)
if err != nil {
t.Error("error on verify: ", err)
return
t.Fatal("error on verify: ", err)
}
if i != 0 {
t.Fatal("signature index should be 0 for RSA key")
}
if bytes.Compare(output, input) != 0 {
t.Error("input/output do not match", output, input)
return
t.Fatal("input/output do not match", output, input)
}
output, err = obj.Verify(sharedKey)
i, _, output, err = obj.VerifyMulti(sharedKey)
if err != nil {
t.Error("error on verify: ", err)
return
t.Fatal("error on verify: ", err)
}
if i != 1 {
t.Fatal("signature index should be 1 for EC key")
}
if bytes.Compare(output, input) != 0 {
t.Error("input/output do not match", output, input)
return
t.Fatal("input/output do not match", output, input)
}
}
@@ -344,12 +348,12 @@ func TestSignerKid(t *testing.T) {
}
var jsonmsi map[string]interface{}
err = UnmarshalJSON(jsonbar, &jsonmsi)
err = json.Unmarshal(jsonbar, &jsonmsi)
if err != nil {
t.Error("problem unmarshalling base JWK", err)
}
jsonmsi["kid"] = kid
jsonbar2, err := MarshalJSON(jsonmsi)
jsonbar2, err := json.Marshal(jsonmsi)
if err != nil {
t.Error("problem marshalling kided JWK", err)
}

View File

@@ -44,7 +44,7 @@ func fromBase64Int(encoded string) *big.Int {
re := regexp.MustCompile(`\s+`)
val, err := base64.RawURLEncoding.DecodeString(re.ReplaceAllString(encoded, ""))
if err != nil {
panic("Invalid test data")
panic("Invalid test data: " + err.Error())
}
return new(big.Int).SetBytes(val)
}