Update square/go-jose to v2.3.1 (#4299)
Also excises the existing bad padding metrics code, adds a special error for when we encounter badly padded keys, and adds a test for the new special error. Fixes #4070 and fixes #3964.
This commit is contained in:
parent
1c8cfb3cba
commit
14d34e9075
2
go.mod
2
go.mod
|
@ -45,6 +45,6 @@ require (
|
|||
google.golang.org/genproto v0.0.0-20190415143225-d1146b9035b9 // indirect
|
||||
google.golang.org/grpc v1.20.0
|
||||
gopkg.in/go-gorp/gorp.v2 v2.0.0-20180410155428-6032c66e0f5f
|
||||
gopkg.in/square/go-jose.v2 v2.1.4
|
||||
gopkg.in/square/go-jose.v2 v2.3.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -169,6 +169,8 @@ gopkg.in/go-gorp/gorp.v2 v2.0.0-20180410155428-6032c66e0f5f h1:zundYZrPliLrlQV98
|
|||
gopkg.in/go-gorp/gorp.v2 v2.0.0-20180410155428-6032c66e0f5f/go.mod h1:eJwu1bWCXesk9aw26U78PFtctx3Y8haXGmL7x3VJlrw=
|
||||
gopkg.in/square/go-jose.v2 v2.1.4 h1:2F80z1AzUrkIpMSw+HIa5WJBzPTFEzQiTTPLES3hbfI=
|
||||
gopkg.in/square/go-jose.v2 v2.1.4/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||
2898 / PKCS #5 v2.0.
|
||||
|
||||
A key derivation function is useful when encrypting data based on a password
|
||||
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||
a secure encryption key based on the password.
|
||||
|
||||
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||
choose, you can pass the `New` functions from the different SHA packages to
|
||||
pbkdf2.Key.
|
||||
*/
|
||||
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Key derives a key from the password, salt and iteration count, returning a
|
||||
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||
// the supplied hash function.
|
||||
//
|
||||
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||
// doing:
|
||||
//
|
||||
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||
//
|
||||
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||
// RFC.
|
||||
//
|
||||
// Using a higher iteration count will increase the cost of an exhaustive
|
||||
// search but will also make derivation proportionally slower.
|
||||
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := prf.Size()
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := 1; block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[len(dk)-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := 2; n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dk[:keyLen]
|
||||
}
|
|
@ -8,13 +8,11 @@ matrix:
|
|||
- go: tip
|
||||
|
||||
go:
|
||||
- '1.5.x'
|
||||
- '1.6.x'
|
||||
- '1.7.x'
|
||||
- '1.8.x'
|
||||
- '1.9.x'
|
||||
- '1.10.x'
|
||||
- tip
|
||||
- '1.11.x'
|
||||
|
||||
go_import_path: gopkg.in/square/go-jose.v2
|
||||
|
||||
|
|
|
@ -6,5 +6,5 @@ can make. We therefore encourage reporting security issues with the code
|
|||
contained in this repository.
|
||||
|
||||
If you believe you have discovered a security vulnerability, please follow the
|
||||
guidelines at <https://hackerone.com/square-open-source>.
|
||||
guidelines at <https://bugcrowd.com/squareopensource>.
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
[](https://godoc.org/gopkg.in/square/go-jose.v1)
|
||||
[](https://godoc.org/gopkg.in/square/go-jose.v2)
|
||||
[](https://raw.githubusercontent.com/square/go-jose/master/LICENSE)
|
||||
[](https://travis-ci.org/square/go-jose)
|
||||
[](https://coveralls.io/r/square/go-jose)
|
||||
[](https://travis-ci.org/square/go-jose)
|
||||
[](https://coveralls.io/r/square/go-jose)
|
||||
|
||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
||||
and Encryption set of standards. This includes support for JSON Web Encryption,
|
||||
|
@ -40,24 +40,17 @@ libraries in other languages.
|
|||
|
||||
We use [gopkg.in](https://gopkg.in) for versioning.
|
||||
|
||||
[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:
|
||||
[Version 2](https://gopkg.in/square/go-jose.v2)
|
||||
([branch](https://github.com/square/go-jose/tree/v2),
|
||||
[doc](https://godoc.org/gopkg.in/square/go-jose.v2)) is the current version:
|
||||
|
||||
import "gopkg.in/square/go-jose.v2"
|
||||
|
||||
The interface for [go-jose.v1](https://gopkg.in/square/go-jose.v1) will remain
|
||||
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
|
||||
The old `v1` branch ([go-jose.v1](https://gopkg.in/square/go-jose.v1)) will
|
||||
still receive backported bug fixes and security fixes, but otherwise
|
||||
development is frozen. All new feature development takes place on the `v2`
|
||||
branch. Version 2 also contains additional sub-packages such as the
|
||||
[jwt](https://godoc.org/gopkg.in/square/go-jose.v2/jwt) implementation
|
||||
contributed by [@shaxbee](https://github.com/shaxbee).
|
||||
|
||||
### Supported algorithms
|
||||
|
@ -84,7 +77,9 @@ standard where possible. The Godoc reference has a list of constants.
|
|||
RSASSA-PSS | PS256, PS384, PS512
|
||||
HMAC | HS256, HS384, HS512
|
||||
ECDSA | ES256, ES384, ES512
|
||||
Ed25519 | EdDSA
|
||||
Ed25519 | EdDSA<sup>2</sup>
|
||||
|
||||
<sup>2. Only available in version 2 of the package</sup>
|
||||
|
||||
Content encryption | Algorithm identifier(s)
|
||||
:------------------------- | :------------------------------
|
||||
|
@ -106,8 +101,11 @@ allows attaching a key id.
|
|||
:------------------------- | -------------------------------
|
||||
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
|
||||
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
|
||||
EdDSA<sup>1</sup> | [ed25519.PublicKey](https://godoc.org/golang.org/x/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://godoc.org/golang.org/x/crypto/ed25519#PrivateKey)
|
||||
AES, HMAC | []byte
|
||||
|
||||
<sup>1. Only available in version 2 of the package</sup>
|
||||
|
||||
## Examples
|
||||
|
||||
[](https://godoc.org/gopkg.in/square/go-jose.v1)
|
||||
|
|
|
@ -104,9 +104,9 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi
|
|||
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: &JSONWebKey{
|
||||
Key: &privateKey.PublicKey,
|
||||
},
|
||||
publicKey: staticPublicKey(&JSONWebKey{
|
||||
Key: privateKey.Public(),
|
||||
}),
|
||||
signer: &rsaDecrypterSigner{
|
||||
privateKey: privateKey,
|
||||
},
|
||||
|
@ -123,9 +123,9 @@ func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey)
|
|||
}
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: &JSONWebKey{
|
||||
publicKey: staticPublicKey(&JSONWebKey{
|
||||
Key: privateKey.Public(),
|
||||
},
|
||||
}),
|
||||
signer: &edDecrypterSigner{
|
||||
privateKey: privateKey,
|
||||
},
|
||||
|
@ -168,9 +168,9 @@ func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (re
|
|||
|
||||
return recipientSigInfo{
|
||||
sigAlg: sigAlg,
|
||||
publicKey: &JSONWebKey{
|
||||
Key: &privateKey.PublicKey,
|
||||
},
|
||||
publicKey: staticPublicKey(&JSONWebKey{
|
||||
Key: privateKey.Public(),
|
||||
}),
|
||||
signer: &ecDecrypterSigner{
|
||||
privateKey: privateKey,
|
||||
},
|
||||
|
@ -195,11 +195,11 @@ func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipi
|
|||
func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
|
||||
switch alg {
|
||||
case RSA1_5:
|
||||
return rsa.EncryptPKCS1v15(randReader, ctx.publicKey, cek)
|
||||
return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek)
|
||||
case RSA_OAEP:
|
||||
return rsa.EncryptOAEP(sha1.New(), randReader, ctx.publicKey, cek, []byte{})
|
||||
return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{})
|
||||
case RSA_OAEP_256:
|
||||
return rsa.EncryptOAEP(sha256.New(), randReader, ctx.publicKey, cek, []byte{})
|
||||
return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{})
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedAlgorithm
|
||||
|
@ -285,9 +285,9 @@ func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm
|
|||
|
||||
switch alg {
|
||||
case RS256, RS384, RS512:
|
||||
out, err = rsa.SignPKCS1v15(randReader, ctx.privateKey, hash, hashed)
|
||||
out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed)
|
||||
case PS256, PS384, PS512:
|
||||
out, err = rsa.SignPSS(randReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
|
||||
out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
})
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ func (ctx ecKeyGenerator) keySize() int {
|
|||
|
||||
// Get a content encryption key for ECDH-ES
|
||||
func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, randReader)
|
||||
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader)
|
||||
if err != nil {
|
||||
return nil, rawHeader{}, err
|
||||
}
|
||||
|
@ -466,12 +466,13 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI
|
|||
|
||||
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||
}
|
||||
|
||||
func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
if alg != EdDSA {
|
||||
return Signature{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
sig, err := ctx.privateKey.Sign(randReader, payload, crypto.Hash(0))
|
||||
sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
@ -521,7 +522,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
|
|||
_, _ = hasher.Write(payload)
|
||||
hashed := hasher.Sum(nil)
|
||||
|
||||
r, s, err := ecdsa.Sign(randReader, ctx.privateKey, hashed)
|
||||
r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed)
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
@ -531,7 +532,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
|
|||
keyBytes++
|
||||
}
|
||||
|
||||
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
||||
// We serialize the outputs (r and s) into big-endian byte arrays and pad
|
||||
// them with zeros on the left to make sure the sizes work out. Both arrays
|
||||
// must be keyBytes long, and the output must be 2*keyBytes long.
|
||||
rBytes := r.Bytes()
|
||||
|
|
|
@ -104,10 +104,18 @@ func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions {
|
|||
}
|
||||
|
||||
// Recipient represents an algorithm/key to encrypt messages to.
|
||||
//
|
||||
// PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used
|
||||
// on the password-based encryption algorithms PBES2-HS256+A128KW,
|
||||
// PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe
|
||||
// default of 100000 will be used for the count and a 128-bit random salt will
|
||||
// be generated.
|
||||
type Recipient struct {
|
||||
Algorithm KeyAlgorithm
|
||||
Key interface{}
|
||||
KeyID string
|
||||
Algorithm KeyAlgorithm
|
||||
Key interface{}
|
||||
KeyID string
|
||||
PBES2Count int
|
||||
PBES2Salt []byte
|
||||
}
|
||||
|
||||
// NewEncrypter creates an appropriate encrypter based on the key type
|
||||
|
@ -143,6 +151,9 @@ func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions)
|
|||
if reflect.TypeOf(rawKey) != reflect.TypeOf([]byte{}) {
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
if encrypter.cipher.keySize() != len(rawKey.([]byte)) {
|
||||
return nil, ErrInvalidKeySize
|
||||
}
|
||||
encrypter.keyGenerator = staticKeyGenerator{
|
||||
key: rawKey.([]byte),
|
||||
}
|
||||
|
@ -228,6 +239,14 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
|
|||
recipientInfo.keyID = recipient.KeyID
|
||||
}
|
||||
|
||||
switch recipient.Algorithm {
|
||||
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||
if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok {
|
||||
sr.p2c = recipient.PBES2Count
|
||||
sr.p2s = recipient.PBES2Salt
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
ctx.recipients = append(ctx.recipients, recipientInfo)
|
||||
}
|
||||
|
@ -242,6 +261,8 @@ func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKey
|
|||
return newECDHRecipient(alg, encryptionKey)
|
||||
case []byte:
|
||||
return newSymmetricRecipient(alg, encryptionKey)
|
||||
case string:
|
||||
return newSymmetricRecipient(alg, []byte(encryptionKey))
|
||||
case *JSONWebKey:
|
||||
recipient, err := makeJWERecipient(alg, encryptionKey.Key)
|
||||
recipient.keyID = encryptionKey.KeyID
|
||||
|
@ -266,6 +287,10 @@ func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
|
|||
return &symmetricKeyCipher{
|
||||
key: decryptionKey,
|
||||
}, nil
|
||||
case string:
|
||||
return &symmetricKeyCipher{
|
||||
key: []byte(decryptionKey),
|
||||
}, nil
|
||||
case JSONWebKey:
|
||||
return newDecrypter(decryptionKey.Key)
|
||||
case *JSONWebKey:
|
||||
|
|
|
@ -21,10 +21,11 @@ import (
|
|||
"compress/flate"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math/big"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
var stripWhitespaceRegex = regexp.MustCompile("\\s")
|
||||
|
|
|
@ -18,9 +18,10 @@ package jose
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/square/go-jose.v2/json"
|
||||
)
|
||||
|
||||
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
|
||||
|
@ -85,10 +86,12 @@ func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
|
|||
func (obj JSONWebEncryption) computeAuthData() []byte {
|
||||
var protected string
|
||||
|
||||
if obj.original != nil {
|
||||
if obj.original != nil && obj.original.Protected != nil {
|
||||
protected = obj.original.Protected.base64()
|
||||
} else {
|
||||
} else if obj.protected != nil {
|
||||
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
|
||||
} else {
|
||||
protected = ""
|
||||
}
|
||||
|
||||
output := []byte(protected)
|
||||
|
|
|
@ -148,17 +148,10 @@ 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)
|
||||
k.Certificates, err = parseCertificateChain(raw.X5c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.Certificates[i], err = x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to unmarshal x5c field: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,6 +189,10 @@ func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if len(x.Bytes()) > coordLength || len(y.Bytes()) > coordLength {
|
||||
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ecThumbprintTemplate, crv,
|
||||
newFixedSizeBuffer(x.Bytes(), coordLength).base64(),
|
||||
newFixedSizeBuffer(y.Bytes(), coordLength).base64()), nil
|
||||
|
@ -209,6 +206,9 @@ func rsaThumbprintInput(n *big.Int, e int) (string, error) {
|
|||
|
||||
func edThumbprintInput(ed ed25519.PublicKey) (string, error) {
|
||||
crv := "Ed25519"
|
||||
if len(ed) > 32 {
|
||||
return "", errors.New("square/go-jose: invalid elliptic key (too large)")
|
||||
}
|
||||
return fmt.Sprintf(edThumbprintTemplate, crv,
|
||||
newFixedSizeBuffer(ed, 32).base64()), nil
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
|
|||
case *rsa.PrivateKey:
|
||||
input, err = rsaThumbprintInput(key.N, key.E)
|
||||
case ed25519.PrivateKey:
|
||||
input, err = edThumbprintInput(ed25519.PublicKey(key[0:32]))
|
||||
input, err = edThumbprintInput(ed25519.PublicKey(key[32:]))
|
||||
default:
|
||||
return nil, fmt.Errorf("square/go-jose: unknown key type '%s'", reflect.TypeOf(key))
|
||||
}
|
||||
|
@ -353,6 +353,17 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
|
|||
return nil, errors.New("square/go-jose: invalid EC key, missing x/y values")
|
||||
}
|
||||
|
||||
// The length of this octet string MUST be the full size of a coordinate for
|
||||
// the curve specified in the "crv" parameter.
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||
if curveSize(curve) != len(key.X.data) {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for x")
|
||||
}
|
||||
|
||||
if curveSize(curve) != len(key.Y.data) {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for y")
|
||||
}
|
||||
|
||||
x := key.X.bigInt()
|
||||
y := key.Y.bigInt()
|
||||
|
||||
|
@ -410,8 +421,8 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
|
|||
}
|
||||
|
||||
privateKey := make([]byte, ed25519.PrivateKeySize)
|
||||
copy(privateKey[0:32], key.X.bytes())
|
||||
copy(privateKey[32:], key.D.bytes())
|
||||
copy(privateKey[0:32], key.D.bytes())
|
||||
copy(privateKey[32:], key.X.bytes())
|
||||
rv := ed25519.PrivateKey(privateKey)
|
||||
return rv, nil
|
||||
}
|
||||
|
@ -472,9 +483,9 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
|
|||
}
|
||||
|
||||
func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) {
|
||||
raw := fromEdPublicKey(ed25519.PublicKey(ed[0:32]))
|
||||
raw := fromEdPublicKey(ed25519.PublicKey(ed[32:]))
|
||||
|
||||
raw.D = newBuffer(ed[32:])
|
||||
raw.D = newBuffer(ed[0:32])
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
|
@ -489,6 +500,16 @@ func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJSONWebKey, error) {
|
|||
raw.P = newBuffer(rsa.Primes[0].Bytes())
|
||||
raw.Q = newBuffer(rsa.Primes[1].Bytes())
|
||||
|
||||
if rsa.Precomputed.Dp != nil {
|
||||
raw.Dp = newBuffer(rsa.Precomputed.Dp.Bytes())
|
||||
}
|
||||
if rsa.Precomputed.Dq != nil {
|
||||
raw.Dq = newBuffer(rsa.Precomputed.Dq.Bytes())
|
||||
}
|
||||
if rsa.Precomputed.Qinv != nil {
|
||||
raw.Qi = newBuffer(rsa.Precomputed.Qinv.Bytes())
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
|
@ -509,6 +530,22 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
|
|||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
|
||||
}
|
||||
|
||||
// The length of this octet string MUST be the full size of a coordinate for
|
||||
// the curve specified in the "crv" parameter.
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
|
||||
if curveSize(curve) != len(key.X.data) {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for x")
|
||||
}
|
||||
|
||||
if curveSize(curve) != len(key.Y.data) {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for y")
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
||||
if dSize(curve) != len(key.D.data) {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for d")
|
||||
}
|
||||
|
||||
x := key.X.bigInt()
|
||||
y := key.Y.bigInt()
|
||||
|
||||
|
@ -536,11 +573,26 @@ func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJSONWebKey, error) {
|
|||
return nil, fmt.Errorf("square/go-jose: invalid EC private key")
|
||||
}
|
||||
|
||||
raw.D = newBuffer(ec.D.Bytes())
|
||||
raw.D = newFixedSizeBuffer(ec.D.Bytes(), dSize(ec.PublicKey.Curve))
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// dSize returns the size in octets for the "d" member of an elliptic curve
|
||||
// private key.
|
||||
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
|
||||
// octets (where n is the order of the curve).
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.2.1
|
||||
func dSize(curve elliptic.Curve) int {
|
||||
order := curve.Params().P
|
||||
bitLen := order.BitLen()
|
||||
size := bitLen / 8
|
||||
if bitLen%8 != 0 {
|
||||
size = size + 1
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func fromSymmetricKey(key []byte) (*rawJSONWebKey, error) {
|
||||
return &rawJSONWebKey{
|
||||
Kty: "oct",
|
||||
|
|
|
@ -93,7 +93,7 @@ func (sig Signature) mergedHeaders() rawHeader {
|
|||
}
|
||||
|
||||
// Compute data to be signed
|
||||
func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte {
|
||||
func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) []byte {
|
||||
var serializedProtected string
|
||||
|
||||
if signature.original != nil && signature.original.Protected != nil {
|
||||
|
@ -106,7 +106,7 @@ func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte {
|
|||
|
||||
return []byte(fmt.Sprintf("%s.%s",
|
||||
serializedProtected,
|
||||
base64.RawURLEncoding.EncodeToString(obj.payload)))
|
||||
base64.RawURLEncoding.EncodeToString(payload)))
|
||||
}
|
||||
|
||||
// parseSignedFull parses a message in full format.
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*-
|
||||
* Copyright 2018 Square Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jose
|
||||
|
||||
// OpaqueSigner is an interface that supports signing payloads with opaque
|
||||
// private key(s). Private key operations preformed by implementors may, for
|
||||
// example, occur in a hardware module. An OpaqueSigner may rotate signing keys
|
||||
// transparently to the user of this interface.
|
||||
type OpaqueSigner interface {
|
||||
// Public returns the public key of the current signing key.
|
||||
Public() *JSONWebKey
|
||||
// Algs returns a list of supported signing algorithms.
|
||||
Algs() []SignatureAlgorithm
|
||||
// SignPayload signs a payload with the current signing key using the given
|
||||
// algorithm.
|
||||
SignPayload(payload []byte, alg SignatureAlgorithm) ([]byte, error)
|
||||
}
|
||||
|
||||
type opaqueSigner struct {
|
||||
signer OpaqueSigner
|
||||
}
|
||||
|
||||
func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigInfo, error) {
|
||||
var algSupported bool
|
||||
for _, salg := range signer.Algs() {
|
||||
if alg == salg {
|
||||
algSupported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !algSupported {
|
||||
return recipientSigInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
||||
return recipientSigInfo{
|
||||
sigAlg: alg,
|
||||
publicKey: signer.Public,
|
||||
signer: &opaqueSigner{
|
||||
signer: signer,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *opaqueSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
|
||||
out, err := o.signer.SignPayload(payload, alg)
|
||||
if err != nil {
|
||||
return Signature{}, err
|
||||
}
|
||||
|
||||
return Signature{
|
||||
Signature: out,
|
||||
protected: &rawHeader{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OpaqueVerifier is an interface that supports verifying payloads with opaque
|
||||
// public key(s). An OpaqueSigner may rotate signing keys transparently to the
|
||||
// user of this interface.
|
||||
type OpaqueVerifier interface {
|
||||
VerifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
|
||||
}
|
||||
|
||||
type opaqueVerifier struct {
|
||||
verifier OpaqueVerifier
|
||||
}
|
||||
|
||||
func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
|
||||
return o.verifier.VerifyPayload(payload, signature, alg)
|
||||
}
|
|
@ -18,6 +18,8 @@ package jose
|
|||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -56,6 +58,11 @@ var (
|
|||
// an RSA private key with more than two primes.
|
||||
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
|
||||
|
||||
// ErrInvalidKeySize indicates that the given key is not the correct size
|
||||
// for the selected algorithm. This can occur, for example, when trying to
|
||||
// encrypt with AES-256 but passing only a 128-bit key as input.
|
||||
ErrInvalidKeySize = errors.New("square/go-jose: invalid key size for algorithm")
|
||||
|
||||
// ErrNotSupported serialization of object is not supported. This occurs when
|
||||
// trying to compact-serialize an object which can't be represented in
|
||||
// compact form.
|
||||
|
@ -141,10 +148,15 @@ const (
|
|||
headerEPK = "epk" // *JSONWebKey
|
||||
headerIV = "iv" // *byteBuffer
|
||||
headerTag = "tag" // *byteBuffer
|
||||
headerX5c = "x5c" // []*x509.Certificate
|
||||
|
||||
headerJWK = "jwk" // *JSONWebKey
|
||||
headerKeyID = "kid" // string
|
||||
headerNonce = "nonce" // string
|
||||
|
||||
headerP2C = "p2c" // *byteBuffer (int)
|
||||
headerP2S = "p2s" // *byteBuffer ([]byte)
|
||||
|
||||
)
|
||||
|
||||
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
|
||||
|
@ -162,11 +174,34 @@ type Header struct {
|
|||
Algorithm string
|
||||
Nonce string
|
||||
|
||||
// Any headers not recognised above get unmarshaled from JSON in a generic
|
||||
// manner and placed in this map.
|
||||
// Unverified certificate chain parsed from x5c header.
|
||||
certificates []*x509.Certificate
|
||||
|
||||
// Any headers not recognised above get unmarshaled
|
||||
// from JSON in a generic manner and placed in this map.
|
||||
ExtraHeaders map[HeaderKey]interface{}
|
||||
}
|
||||
|
||||
// Certificates verifies & returns the certificate chain present
|
||||
// in the x5c header field of a message, if one was present. Returns
|
||||
// an error if there was no x5c header present or the chain could
|
||||
// not be validated with the given verify options.
|
||||
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
|
||||
if len(h.certificates) == 0 {
|
||||
return nil, errors.New("square/go-jose: no x5c header present in message")
|
||||
}
|
||||
|
||||
leaf := h.certificates[0]
|
||||
if opts.Intermediates == nil {
|
||||
opts.Intermediates = x509.NewCertPool()
|
||||
for _, intermediate := range h.certificates[1:] {
|
||||
opts.Intermediates.AddCert(intermediate)
|
||||
}
|
||||
}
|
||||
|
||||
return leaf.Verify(opts)
|
||||
}
|
||||
|
||||
func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
|
@ -180,7 +215,7 @@ func (parsed rawHeader) set(k HeaderKey, v interface{}) error {
|
|||
// getString gets a string from the raw JSON, defaulting to "".
|
||||
func (parsed rawHeader) getString(k HeaderKey) string {
|
||||
v, ok := parsed[k]
|
||||
if !ok {
|
||||
if !ok || v == nil {
|
||||
return ""
|
||||
}
|
||||
var s string
|
||||
|
@ -294,6 +329,26 @@ func (parsed rawHeader) getCritical() ([]string, error) {
|
|||
return q, nil
|
||||
}
|
||||
|
||||
// getS2C extracts parsed "p2c" from the raw JSON.
|
||||
func (parsed rawHeader) getP2C() (int, error) {
|
||||
v := parsed[headerP2C]
|
||||
if v == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
var p2c int
|
||||
err := json.Unmarshal(*v, &p2c)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p2c, nil
|
||||
}
|
||||
|
||||
// getS2S extracts parsed "p2s" from the raw JSON.
|
||||
func (parsed rawHeader) getP2S() (*byteBuffer, error) {
|
||||
return parsed.getByteBuffer(headerP2S)
|
||||
}
|
||||
|
||||
// sanitized produces a cleaned-up header object from the raw JSON.
|
||||
func (parsed rawHeader) sanitized() (h Header, err error) {
|
||||
for k, v := range parsed {
|
||||
|
@ -333,6 +388,18 @@ func (parsed rawHeader) sanitized() (h Header, err error) {
|
|||
return
|
||||
}
|
||||
h.Nonce = s
|
||||
case headerX5c:
|
||||
c := []string{}
|
||||
err = json.Unmarshal(*v, &c)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
h.certificates, err = parseCertificateChain(c)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v))
|
||||
return
|
||||
}
|
||||
default:
|
||||
if h.ExtraHeaders == nil {
|
||||
h.ExtraHeaders = map[HeaderKey]interface{}{}
|
||||
|
@ -349,6 +416,21 @@ func (parsed rawHeader) sanitized() (h Header, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
|
||||
out := make([]*x509.Certificate, len(chain))
|
||||
for i, cert := range chain {
|
||||
raw, err := base64.StdEncoding.DecodeString(cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[i], err = x509.ParseCertificate(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (dst rawHeader) isSet(k HeaderKey) bool {
|
||||
dvr := dst[k]
|
||||
if dvr == nil {
|
||||
|
|
|
@ -94,10 +94,16 @@ type genericSigner struct {
|
|||
|
||||
type recipientSigInfo struct {
|
||||
sigAlg SignatureAlgorithm
|
||||
publicKey *JSONWebKey
|
||||
publicKey func() *JSONWebKey
|
||||
signer payloadSigner
|
||||
}
|
||||
|
||||
func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey {
|
||||
return func() *JSONWebKey {
|
||||
return jwk
|
||||
}
|
||||
}
|
||||
|
||||
// NewSigner creates an appropriate signer based on the key type
|
||||
func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) {
|
||||
return NewMultiSigner([]SigningKey{sig}, opts)
|
||||
|
@ -146,9 +152,11 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
|
|||
return newVerifier(verificationKey.Key)
|
||||
case *JSONWebKey:
|
||||
return newVerifier(verificationKey.Key)
|
||||
default:
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
if ov, ok := verificationKey.(OpaqueVerifier); ok {
|
||||
return &opaqueVerifier{verifier: ov}, nil
|
||||
}
|
||||
return nil, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
|
||||
|
@ -175,9 +183,11 @@ func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipient
|
|||
return newJWKSigner(alg, signingKey)
|
||||
case *JSONWebKey:
|
||||
return newJWKSigner(alg, *signingKey)
|
||||
default:
|
||||
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||
}
|
||||
if signer, ok := signingKey.(OpaqueSigner); ok {
|
||||
return newOpaqueSigner(alg, signer)
|
||||
}
|
||||
return recipientSigInfo{}, ErrUnsupportedKeyType
|
||||
}
|
||||
|
||||
func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) {
|
||||
|
@ -185,16 +195,16 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn
|
|||
if err != nil {
|
||||
return recipientSigInfo{}, err
|
||||
}
|
||||
if recipient.publicKey != nil {
|
||||
if recipient.publicKey != nil && recipient.publicKey() != nil {
|
||||
// recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo
|
||||
// was created for the inner key (such as a RSA or ECDSA public key). It contains
|
||||
// the pub key for embedding, but doesn't have extra params like key id.
|
||||
publicKey := signingKey
|
||||
publicKey.Key = recipient.publicKey.Key
|
||||
recipient.publicKey = &publicKey
|
||||
publicKey.Key = recipient.publicKey().Key
|
||||
recipient.publicKey = staticPublicKey(&publicKey)
|
||||
|
||||
// This should be impossible, but let's check anyway.
|
||||
if !recipient.publicKey.IsPublic() {
|
||||
if !recipient.publicKey().IsPublic() {
|
||||
return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public")
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +221,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||
headerAlgorithm: string(recipient.sigAlg),
|
||||
}
|
||||
|
||||
if recipient.publicKey != nil {
|
||||
if recipient.publicKey != nil && recipient.publicKey() != nil {
|
||||
// We want to embed the JWK or set the kid header, but not both. Having a protected
|
||||
// header that contains an embedded JWK while also simultaneously containing the kid
|
||||
// header is confusing, and at least in ACME the two are considered to be mutually
|
||||
|
@ -221,9 +231,9 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
|
|||
//
|
||||
// See https://github.com/square/go-jose/issues/157 for more context.
|
||||
if ctx.embedJWK {
|
||||
protected[headerJWK] = recipient.publicKey
|
||||
protected[headerJWK] = recipient.publicKey()
|
||||
} else {
|
||||
protected[headerKeyID] = recipient.publicKey.KeyID
|
||||
protected[headerKeyID] = recipient.publicKey().KeyID
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,34 +290,53 @@ func (ctx *genericSigner) Options() SignerOptions {
|
|||
// 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)
|
||||
err := obj.DetachedVerify(obj.payload, verificationKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.payload, nil
|
||||
}
|
||||
|
||||
// UnsafePayloadWithoutVerification returns the payload without
|
||||
// verifying it. The content returned from this function cannot be
|
||||
// trusted.
|
||||
func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
|
||||
return obj.payload
|
||||
}
|
||||
|
||||
// DetachedVerify validates a detached signature on the given payload. In
|
||||
// most cases, you will probably want to use Verify instead. DetachedVerify
|
||||
// is only useful if you have a payload and signature that are separated from
|
||||
// each other.
|
||||
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
|
||||
verifier, err := newVerifier(verificationKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(obj.Signatures) > 1 {
|
||||
return nil, errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
||||
return errors.New("square/go-jose: too many signatures in payload; expecting only one")
|
||||
}
|
||||
|
||||
signature := obj.Signatures[0]
|
||||
headers := signature.mergedHeaders()
|
||||
critical, err := headers.getCritical()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if len(critical) > 0 {
|
||||
// Unsupported crit header
|
||||
return nil, ErrCryptoFailure
|
||||
return ErrCryptoFailure
|
||||
}
|
||||
|
||||
input := obj.computeAuthData(&signature)
|
||||
input := obj.computeAuthData(payload, &signature)
|
||||
alg := headers.getSignatureAlgorithm()
|
||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||
if err == nil {
|
||||
return obj.payload, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil, ErrCryptoFailure
|
||||
return ErrCryptoFailure
|
||||
}
|
||||
|
||||
// VerifyMulti validates (one of the multiple) signatures on the object and
|
||||
|
@ -315,10 +344,27 @@ func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error)
|
|||
// 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)
|
||||
idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey)
|
||||
if err != nil {
|
||||
return -1, Signature{}, nil, err
|
||||
}
|
||||
return idx, sig, obj.payload, nil
|
||||
}
|
||||
|
||||
// DetachedVerifyMulti validates a detached signature on the given payload with
|
||||
// a signature/object that has potentially multiple signers. This returns the index
|
||||
// of the signature that was verified, along with the signature object. We return
|
||||
// the signature and index to guarantee that callers are getting the verified value.
|
||||
//
|
||||
// In most cases, you will probably want to use Verify or VerifyMulti instead.
|
||||
// DetachedVerifyMulti is only useful if you have a payload and signature that are
|
||||
// separated from each other, and the signature can have multiple signers at the
|
||||
// same time.
|
||||
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
|
||||
verifier, err := newVerifier(verificationKey)
|
||||
if err != nil {
|
||||
return -1, Signature{}, err
|
||||
}
|
||||
|
||||
for i, signature := range obj.Signatures {
|
||||
headers := signature.mergedHeaders()
|
||||
|
@ -331,13 +377,13 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa
|
|||
continue
|
||||
}
|
||||
|
||||
input := obj.computeAuthData(&signature)
|
||||
input := obj.computeAuthData(payload, &signature)
|
||||
alg := headers.getSignatureAlgorithm()
|
||||
err = verifier.verifyPayload(input, signature.Signature, alg)
|
||||
if err == nil {
|
||||
return i, signature, obj.payload, nil
|
||||
return i, signature, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, Signature{}, nil, ErrCryptoFailure
|
||||
return -1, Signature{}, ErrCryptoFailure
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package jose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
|
@ -29,15 +30,30 @@ import (
|
|||
"hash"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"gopkg.in/square/go-jose.v2/cipher"
|
||||
)
|
||||
|
||||
// Random reader (stubbed out in tests)
|
||||
var randReader = rand.Reader
|
||||
var RandReader = rand.Reader
|
||||
|
||||
const (
|
||||
// RFC7518 recommends a minimum of 1,000 iterations:
|
||||
// https://tools.ietf.org/html/rfc7518#section-4.8.1.2
|
||||
// NIST recommends a minimum of 10,000:
|
||||
// https://pages.nist.gov/800-63-3/sp800-63b.html
|
||||
// 1Password uses 100,000:
|
||||
// https://support.1password.com/pbkdf2/
|
||||
defaultP2C = 100000
|
||||
// Default salt size: 128 bits
|
||||
defaultP2SSize = 16
|
||||
)
|
||||
|
||||
// Dummy key cipher for shared symmetric key mode
|
||||
type symmetricKeyCipher struct {
|
||||
key []byte // Pre-shared content-encryption key
|
||||
p2c int // PBES2 Count
|
||||
p2s []byte // PBES2 Salt Input
|
||||
}
|
||||
|
||||
// Signer/verifier for MAC modes
|
||||
|
@ -87,7 +103,7 @@ func newAESGCM(keySize int) contentCipher {
|
|||
func newAESCBC(keySize int) contentCipher {
|
||||
return &aeadContentCipher{
|
||||
keyBytes: keySize * 2,
|
||||
authtagBytes: 16,
|
||||
authtagBytes: keySize,
|
||||
getAead: func(key []byte) (cipher.AEAD, error) {
|
||||
return josecipher.NewCBCHMAC(key, aes.NewCipher)
|
||||
},
|
||||
|
@ -114,10 +130,37 @@ func getContentCipher(alg ContentEncryption) contentCipher {
|
|||
}
|
||||
}
|
||||
|
||||
// getPbkdf2Params returns the key length and hash function used in
|
||||
// pbkdf2.Key.
|
||||
func getPbkdf2Params(alg KeyAlgorithm) (int, func() hash.Hash) {
|
||||
switch alg {
|
||||
case PBES2_HS256_A128KW:
|
||||
return 16, sha256.New
|
||||
case PBES2_HS384_A192KW:
|
||||
return 24, sha512.New384
|
||||
case PBES2_HS512_A256KW:
|
||||
return 32, sha512.New
|
||||
default:
|
||||
panic("invalid algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
// getRandomSalt generates a new salt of the given size.
|
||||
func getRandomSalt(size int) ([]byte, error) {
|
||||
salt := make([]byte, size)
|
||||
_, err := io.ReadFull(RandReader, salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
// newSymmetricRecipient creates a JWE encrypter based on AES-GCM key wrap.
|
||||
func newSymmetricRecipient(keyAlg KeyAlgorithm, key []byte) (recipientKeyInfo, error) {
|
||||
switch keyAlg {
|
||||
case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW:
|
||||
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||
default:
|
||||
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
|
||||
}
|
||||
|
@ -150,7 +193,7 @@ func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo
|
|||
// Generate a random key for the given content cipher
|
||||
func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) {
|
||||
key := make([]byte, ctx.size)
|
||||
_, err := io.ReadFull(randReader, key)
|
||||
_, err := io.ReadFull(RandReader, key)
|
||||
if err != nil {
|
||||
return nil, rawHeader{}, err
|
||||
}
|
||||
|
@ -190,7 +233,7 @@ func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) {
|
|||
|
||||
// Initialize a new nonce
|
||||
iv := make([]byte, aead.NonceSize())
|
||||
_, err = io.ReadFull(randReader, iv)
|
||||
_, err = io.ReadFull(RandReader, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -212,6 +255,10 @@ func (ctx aeadContentCipher) decrypt(key, aad []byte, parts *aeadParts) ([]byte,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(parts.iv) != aead.NonceSize() || len(parts.tag) < ctx.authtagBytes {
|
||||
return nil, ErrCryptoFailure
|
||||
}
|
||||
|
||||
return aead.Open(nil, parts.iv, append(parts.ciphertext, parts.tag...), aad)
|
||||
}
|
||||
|
||||
|
@ -253,6 +300,45 @@ func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipie
|
|||
encryptedKey: jek,
|
||||
header: &rawHeader{},
|
||||
}, nil
|
||||
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||
if len(ctx.p2s) == 0 {
|
||||
salt, err := getRandomSalt(defaultP2SSize)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
ctx.p2s = salt
|
||||
}
|
||||
|
||||
if ctx.p2c <= 0 {
|
||||
ctx.p2c = defaultP2C
|
||||
}
|
||||
|
||||
// salt is UTF8(Alg) || 0x00 || Salt Input
|
||||
salt := bytes.Join([][]byte{[]byte(alg), ctx.p2s}, []byte{0x00})
|
||||
|
||||
// derive key
|
||||
keyLen, h := getPbkdf2Params(alg)
|
||||
key := pbkdf2.Key(ctx.key, salt, ctx.p2c, keyLen, h)
|
||||
|
||||
// use AES cipher with derived key
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
jek, err := josecipher.KeyWrap(block, cek)
|
||||
if err != nil {
|
||||
return recipientInfo{}, err
|
||||
}
|
||||
|
||||
header := &rawHeader{}
|
||||
header.set(headerP2C, ctx.p2c)
|
||||
header.set(headerP2S, newBuffer(ctx.p2s))
|
||||
|
||||
return recipientInfo{
|
||||
encryptedKey: jek,
|
||||
header: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return recipientInfo{}, ErrUnsupportedAlgorithm
|
||||
|
@ -295,6 +381,42 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
|
|||
return nil, err
|
||||
}
|
||||
|
||||
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cek, nil
|
||||
case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW:
|
||||
p2s, err := headers.getP2S()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid P2S: %v", err)
|
||||
}
|
||||
if p2s == nil || len(p2s.data) == 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid P2S: must be present")
|
||||
}
|
||||
|
||||
p2c, err := headers.getP2C()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid P2C: %v", err)
|
||||
}
|
||||
if p2c <= 0 {
|
||||
return nil, fmt.Errorf("square/go-jose: invalid P2C: must be a positive integer")
|
||||
}
|
||||
|
||||
// salt is UTF8(Alg) || 0x00 || Salt Input
|
||||
alg := headers.getAlgorithm()
|
||||
salt := bytes.Join([][]byte{[]byte(alg), p2s.bytes()}, []byte{0x00})
|
||||
|
||||
// derive key
|
||||
keyLen, h := getPbkdf2Params(alg)
|
||||
key := pbkdf2.Key(ctx.key, salt, p2c, keyLen, h)
|
||||
|
||||
// use AES cipher with derived key
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -105,6 +105,7 @@ golang.org/x/crypto/ed25519
|
|||
golang.org/x/crypto/pkcs12
|
||||
golang.org/x/crypto/cryptobyte
|
||||
golang.org/x/crypto/cryptobyte/asn1
|
||||
golang.org/x/crypto/pbkdf2
|
||||
golang.org/x/crypto/ed25519/internal/edwards25519
|
||||
golang.org/x/crypto/pkcs12/internal/rc2
|
||||
# golang.org/x/net v0.0.0-20190415214537-1da14a5a36f2
|
||||
|
@ -165,7 +166,7 @@ google.golang.org/grpc/binarylog/grpc_binarylog_v1
|
|||
google.golang.org/grpc/internal/syscall
|
||||
# gopkg.in/go-gorp/gorp.v2 v2.0.0-20180410155428-6032c66e0f5f
|
||||
gopkg.in/go-gorp/gorp.v2
|
||||
# gopkg.in/square/go-jose.v2 v2.1.4
|
||||
# gopkg.in/square/go-jose.v2 v2.3.1
|
||||
gopkg.in/square/go-jose.v2
|
||||
gopkg.in/square/go-jose.v2/cipher
|
||||
gopkg.in/square/go-jose.v2/json
|
||||
|
|
|
@ -4,9 +4,7 @@ import (
|
|||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -318,6 +316,14 @@ func (wfe *WebFrontEndImpl) parseJWS(body []byte) (*jose.JSONWebSignature, *prob
|
|||
parsedJWS, err := jose.ParseSigned(bodyStr)
|
||||
if err != nil {
|
||||
wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSParseError"}).Inc()
|
||||
// TODO(#4300): This error references private keys, while the issue is
|
||||
// actually with public keys. A fix has been merged upstream but we
|
||||
// are waiting until a tagged release containing the change is made.
|
||||
// Once there is a release containing the fix we'll need to update
|
||||
// the dep and fix this check.
|
||||
if strings.HasPrefix(err.Error(), "failed to unmarshal JWK: square/go-jose: invalid EC private key, wrong length") {
|
||||
return nil, probs.Malformed("Parse error reading JWS: EC public key has incorrect padding")
|
||||
}
|
||||
return nil, probs.Malformed("Parse error reading JWS")
|
||||
}
|
||||
if len(parsedJWS.Signatures) > 1 {
|
||||
|
@ -356,16 +362,6 @@ func (wfe *WebFrontEndImpl) parseJWSRequest(request *http.Request) (*jose.JSONWe
|
|||
return nil, prob
|
||||
}
|
||||
|
||||
// if the protected header contains a JWK and it's a EC key verify that it has
|
||||
// proper field lengths in a goroutine as the caller doesn't care about the
|
||||
// result of this check
|
||||
if jws.Signatures[0].Header.JSONWebKey != nil {
|
||||
switch jws.Signatures[0].Header.JSONWebKey.Key.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
go wfe.verifyECFieldLengths(bodyBytes, request)
|
||||
}
|
||||
}
|
||||
|
||||
return jws, nil
|
||||
}
|
||||
|
||||
|
@ -767,63 +763,3 @@ func (wfe *WebFrontEndImpl) validKeyRollover(
|
|||
NewKey: *jwk,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func bitSizeToByteSize(bitSize int) int {
|
||||
byteSize := (bitSize / 8)
|
||||
if bitSize%8 > 0 {
|
||||
return byteSize + 1
|
||||
}
|
||||
return byteSize
|
||||
}
|
||||
|
||||
var curvesToSize = map[string]int{
|
||||
"P-256": bitSizeToByteSize(elliptic.P256().Params().BitSize),
|
||||
"P-384": bitSizeToByteSize(elliptic.P384().Params().BitSize),
|
||||
"P-521": bitSizeToByteSize(elliptic.P521().Params().BitSize),
|
||||
}
|
||||
|
||||
func (wfe *WebFrontEndImpl) verifyECFieldLengths(body []byte, request *http.Request) {
|
||||
var payload struct {
|
||||
Protected string
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
return
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(payload.Protected)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var keyData struct {
|
||||
JWK struct {
|
||||
X string
|
||||
Y string
|
||||
Crv string
|
||||
}
|
||||
}
|
||||
if err := json.Unmarshal(b, &keyData); err != nil {
|
||||
// this really shouldn't happen as we've already done this once before
|
||||
// successfully, in order to avoid a headache just return and pretend
|
||||
// it didn't happen
|
||||
return
|
||||
}
|
||||
x, err := base64.RawURLEncoding.DecodeString(keyData.JWK.X)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
y, err := base64.RawURLEncoding.DecodeString(keyData.JWK.Y)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
xLen, yLen := len(x), len(y)
|
||||
curveSize, present := curvesToSize[keyData.JWK.Crv]
|
||||
if !present {
|
||||
return
|
||||
}
|
||||
if xLen != curveSize || yLen != curveSize {
|
||||
wfe.stats.improperECFieldLengths.Inc()
|
||||
// if extractRequesterIP fails just continue on as we still want to know
|
||||
// that the key was badly padded, and a empty net.IP will render just fine
|
||||
ip, _ := extractRequesterIP(request)
|
||||
wfe.log.Infof("Incorrectly padded account EC JWK key from UA=%q IP=%s", request.UserAgent(), ip)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1566,36 +1566,13 @@ func TestMatchJWSURLs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestVerifyECFieldLengths(t *testing.T) {
|
||||
func TestBadPaddingError(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
|
||||
var testCases = []struct {
|
||||
Name string
|
||||
Body string
|
||||
ExpectedStat int
|
||||
}{
|
||||
{
|
||||
Name: "Correct lengths",
|
||||
Body: `{"protected":"eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlM4Rk9tclozeXdqNHl5RnF0MGV0QUQ5MFUtRW5rTmFPQlNMZlFtZjdwTmciLCJ5Ijoidk12cER5cUZEUkhqR2ZaMXNpRE9tNUxTNnhOZFI1eFRweW9RR0xET1gyUSJ9fQ"}`,
|
||||
ExpectedStat: 0,
|
||||
},
|
||||
{
|
||||
Name: "Incorrect X length",
|
||||
Body: `{"protected":"eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Im5QVElBQmNEQVNZNkZOR1NOZkhDQjUxdFk3cUNodGd6ZVZhek90THJ3USIsInkiOiJ2RUVzNFYwZWdKa055TTJRNHBwMDAxenUxNFZjcFEwX0VpOHhPT1B4S1pzIn19"}`,
|
||||
ExpectedStat: 1,
|
||||
},
|
||||
{
|
||||
Name: "Incorrect Y length",
|
||||
Body: `{"protected":"eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6InZFRXM0VjBlZ0prTnlNMlE0cHAwMDF6dTE0VmNwUTBfRWk4eE9PUHhLWnMiLCJ5IjoiblBUSUFCY0RBU1k2Rk5HU05mSENCNTF0WTdxQ2h0Z3plVmF6T3RMcndRIn19"}`,
|
||||
ExpectedStat: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
wfe.stats.improperECFieldLengths.Set(0)
|
||||
wfe.verifyECFieldLengths([]byte(tc.Body), &http.Request{})
|
||||
test.AssertEquals(t, test.CountCounter(wfe.stats.improperECFieldLengths), tc.ExpectedStat)
|
||||
})
|
||||
}
|
||||
_, prob := wfe.parseJWS([]byte(`{"protected":"eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Im5QVElBQmNEQVNZNkZOR1NOZkhDQjUxdFk3cUNodGd6ZVZhek90THJ3USIsInkiOiJ2RUVzNFYwZWdKa055TTJRNHBwMDAxenUxNFZjcFEwX0VpOHhPT1B4S1pzIn19","payload":""}`))
|
||||
test.Assert(t, prob != nil, "parseJWS didn't return a problem")
|
||||
test.AssertEquals(t, prob.Detail, "Parse error reading JWS: EC public key has incorrect padding")
|
||||
_, prob = wfe.parseJWS([]byte(`{"protected":"eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6InZFRXM0VjBlZ0prTnlNMlE0cHAwMDF6dTE0VmNwUTBfRWk4eE9PUHhLWnMiLCJ5IjoiblBUSUFCY0RBU1k2Rk5HU05mSENCNTF0WTdxQ2h0Z3plVmF6T3RMcndRIn19","payload":""}`))
|
||||
test.Assert(t, prob != nil, "parseJWS didn't return a problem")
|
||||
test.AssertEquals(t, prob.Detail, "Parse error reading JWS: EC public key has incorrect padding")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue