vendor: github.com/go-jose/go-jose/v4 v4.1.5

Fixed security issues

- cipher/cbc_hmac: don't panic on empty ciphertext
- cipher/cbc_hmac: don't panic on invalid key
- json: limit stack depth
- jwt: reject out-of-range NumericDate values
- Check alg against pubkey curve during verify.
- Reject malformed Ed25519 JWKs
- jws: choose verification key per-signature

Changed

- Verify OpaqueSigner's Public() return is public
- jws: skip signature on ErrJWKSKidNotFound
- Handle JWE JSON without protected header
- jws: don't strip internal whitespace before parsing JSON
- jws: fewer calls to OpaqueSigner.Public()
- Return a specific error when parsing empty string
- Reject typed nil at Verify time

full diff: https://github.com/go-jose/go-jose/compare/v4.1.4...v4.1.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2026-09-03 23:40:55 +02:00
parent cec24c7b9a
commit c50ce17c19
15 changed files with 388 additions and 144 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ require (
github.com/docker/go-connections v0.8.1
github.com/docker/go-units v0.5.0
github.com/fvbommel/sortorder v1.2.0
github.com/go-jose/go-jose/v4 v4.1.4
github.com/go-jose/go-jose/v4 v4.1.5
github.com/go-viper/mapstructure/v2 v2.5.0
github.com/gogo/protobuf v1.3.2
github.com/google/go-cmp v0.7.0
+2 -2
View File
@@ -51,8 +51,8 @@ github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeO
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/fvbommel/sortorder v1.2.0 h1:TRIiRiGX+djh3Yf4FVxmWmAcYfIr5dH0NbzJWOSAWZk=
github.com/fvbommel/sortorder v1.2.0/go.mod h1:LbhO04ijZIeUuvz9B9BkI/qYrpZZEn1gWhxv4QjUKVs=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+18 -8
View File
@@ -21,6 +21,7 @@ import (
"crypto/aes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
@@ -195,11 +196,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
@@ -288,9 +289,9 @@ func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm
// TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the
// random parameter is legacy and ignored, and it can be nil.
// https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1
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.PSSSaltLengthEqualsHash,
})
}
@@ -391,7 +392,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
}
@@ -483,7 +484,7 @@ func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm)
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
}
@@ -533,7 +534,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
}
@@ -571,12 +572,21 @@ func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, a
case ES256:
keySize = 32
hash = crypto.SHA256
if ctx.publicKey.Curve != elliptic.P256() {
return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key")
}
case ES384:
keySize = 48
hash = crypto.SHA384
if ctx.publicKey.Curve != elliptic.P384() {
return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key")
}
case ES512:
keySize = 66
hash = crypto.SHA512
if ctx.publicKey.Curve != elliptic.P521() {
return fmt.Errorf("go-jose/go-jose: signature uses different algorithm than public key")
}
default:
return ErrUnsupportedAlgorithm
}
+5 -1
View File
@@ -51,6 +51,8 @@ func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (
hash = sha512.New384
case 32:
hash = sha512.New
default:
return nil, errors.New("go-jose/go-jose: invalid key size for CBC-HMAC")
}
return &cbcAEAD{
@@ -176,7 +178,9 @@ func padBuffer(buffer []byte, blockSize int) []byte {
// Remove padding
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
if len(buffer)%blockSize != 0 {
// A padded buffer can't be empty because an empty input is padded with
// `blockSize` bytes, resulting in a non-empty ciphertext.
if len(buffer) == 0 || len(buffer)%blockSize != 0 {
return nil, errors.New("go-jose/go-jose: invalid padding")
}
+58 -32
View File
@@ -258,6 +258,10 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
}
recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key)
if err != nil {
return err
}
if recipient.KeyID != "" {
recipientInfo.keyID = recipient.KeyID
}
@@ -270,10 +274,8 @@ func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) {
}
}
if err == nil {
ctx.recipients = append(ctx.recipients, recipientInfo)
}
return err
ctx.recipients = append(ctx.recipients, recipientInfo)
return nil
}
func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) {
@@ -490,21 +492,19 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
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 plaintext == nil {
if err != nil {
return nil, ErrCryptoFailure
}
// The "zip" header parameter may only be present in the protected header.
if comp := obj.protected.getCompression(); comp != "" {
plaintext, err = decompress(comp, plaintext)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
}
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err != nil {
return nil, ErrCryptoFailure
}
plaintext, err = obj.decompress(plaintext)
if err != nil {
return nil, err
}
return plaintext, nil
@@ -559,31 +559,38 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
var plaintext []byte
var headers rawHeader
if len(obj.recipients) == 0 {
return -1, Header{}, nil, errors.New("go-jose/go-jose: no recipients")
}
// Loop sets `err` in the function scope; don't shadow it.
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
}
var cek []byte
cek, err = decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err != nil {
continue
}
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err != nil {
continue
}
index = i
headers = recipientHeaders
break
}
if plaintext == nil {
if err != nil {
return -1, Header{}, nil, ErrCryptoFailure
}
// The "zip" header parameter may only be present in the protected header.
if comp := obj.protected.getCompression(); comp != "" {
plaintext, err = decompress(comp, plaintext)
if err != nil {
return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
}
plaintext, err = obj.decompress(plaintext)
if err != nil {
return -1, Header{}, nil, err
}
sanitized, err := headers.sanitized()
@@ -593,3 +600,22 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
return index, sanitized, plaintext, err
}
// decompress decompresses plaintext using the protected "zip" header, if present.
// It returns plaintext unchanged when there is no protected header or "zip" value.
func (obj JSONWebEncryption) decompress(plaintext []byte) ([]byte, error) {
if obj.protected == nil {
return plaintext, nil
}
comp := obj.protected.getCompression()
if comp == "" {
return plaintext, nil
}
plaintext, err := decompress(comp, plaintext)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err)
}
return plaintext, nil
}
+13 -6
View File
@@ -140,6 +140,10 @@ const (
parseArrayValue // parsing array value
)
// This limits the max nesting depth to prevent stack overflow.
// This is permitted by https://tools.ietf.org/html/rfc7159#section-9
const maxNestingDepth = 10000
// reset prepares the scanner for use.
// It must be called before calling s.step.
func (s *scanner) reset() {
@@ -170,8 +174,13 @@ func (s *scanner) eof() int {
}
// pushParseState pushes a new parse state p onto the parse stack.
func (s *scanner) pushParseState(p int) {
s.parseState = append(s.parseState, p)
// an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned.
func (s *scanner) pushParseState(c byte, newParseState int, successState int) int {
s.parseState = append(s.parseState, newParseState)
if len(s.parseState) <= maxNestingDepth {
return successState
}
return s.error(c, "exceeded max depth")
}
// popParseState pops a parse state (already obtained) off the stack
@@ -211,12 +220,10 @@ func stateBeginValue(s *scanner, c byte) int {
switch c {
case '{':
s.step = stateBeginStringOrEmpty
s.pushParseState(parseObjectKey)
return scanBeginObject
return s.pushParseState(c, parseObjectKey, scanBeginObject)
case '[':
s.step = stateBeginValueOrEmpty
s.pushParseState(parseArrayValue)
return scanBeginArray
return s.pushParseState(c, parseArrayValue, scanBeginArray)
case '"':
s.step = stateInString
return scanBeginLiteral
+8
View File
@@ -153,6 +153,10 @@ func ParseEncryptedJSON(
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
if len(input) == 0 {
return nil, errEmptyInput
}
var parsed rawJSONWebEncryption
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
@@ -291,6 +295,10 @@ func ParseEncryptedCompact(
var parts [5]string
var ok bool
if len(input) == 0 {
return nil, errEmptyInput
}
for i := range 4 {
parts[i], input, ok = strings.Cut(input, ".")
if !ok {
+174 -39
View File
@@ -107,7 +107,7 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
switch key := k.Key.(type) {
case ed25519.PublicKey:
raw = fromEdPublicKey(key)
raw, err = fromEdPublicKey(key)
case *ecdsa.PublicKey:
raw, err = fromEcPublicKey(key)
case *rsa.PublicKey:
@@ -207,21 +207,29 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
case "EC":
if raw.D != nil {
key, err = raw.ecPrivateKey()
if err == nil {
keyPub = key.(*ecdsa.PrivateKey).Public()
if err != nil {
return err
}
keyPub = key.(*ecdsa.PrivateKey).Public()
} else {
key, err = raw.ecPublicKey()
if err != nil {
return err
}
keyPub = key
}
case "RSA":
if raw.D != nil {
key, err = raw.rsaPrivateKey()
if err == nil {
keyPub = key.(*rsa.PrivateKey).Public()
if err != nil {
return err
}
keyPub = key.(*rsa.PrivateKey).Public()
} else {
key, err = raw.rsaPublicKey()
if err != nil {
return err
}
keyPub = key
}
case "oct":
@@ -229,25 +237,28 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
}
key, err = raw.symmetricKey()
if err != nil {
return err
}
case "OKP":
if raw.Crv == "Ed25519" {
if raw.D != nil {
key, err = raw.edPrivateKey()
if err == nil {
keyPub = key.(ed25519.PrivateKey).Public()
if err != nil {
return err
}
keyPub = key.(ed25519.PrivateKey).Public()
} else {
key, err = raw.edPublicKey()
if err != nil {
return err
}
keyPub = key
}
}
case "":
// kty MUST be present
err = fmt.Errorf("go-jose/go-jose: missing json web key type")
}
if err != nil {
return
return fmt.Errorf("go-jose/go-jose: missing json web key type")
}
if key == nil {
@@ -388,11 +399,12 @@ 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("go-jose/go-jose: invalid elliptic key (too large)")
err := validateEd25519PublicKey(ed)
if err != nil {
return "", err
}
return fmt.Sprintf(edThumbprintTemplate, crv,
newFixedSizeBuffer(ed, 32).base64()), nil
newFixedSizeBuffer(ed, ed25519.PublicKeySize).base64()), nil
}
// Thumbprint computes the JWK Thumbprint of a key using the
@@ -504,12 +516,16 @@ func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
}, nil
}
func fromEdPublicKey(pub ed25519.PublicKey) *rawJSONWebKey {
func fromEdPublicKey(pub ed25519.PublicKey) (*rawJSONWebKey, error) {
err := validateEd25519PublicKey(pub)
if err != nil {
return nil, err
}
return &rawJSONWebKey{
Kty: "OKP",
Crv: "Ed25519",
X: newBuffer(pub),
}
}, nil
}
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJSONWebKey {
@@ -604,21 +620,36 @@ func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
}
privateKey := make([]byte, ed25519.PrivateKeySize)
copy(privateKey[0:32], key.D.bytes())
copy(privateKey[32:], key.X.bytes())
rv := ed25519.PrivateKey(privateKey)
return rv, nil
publicKey := key.X.bytes()
err := validateEd25519PublicKey(publicKey)
if err != nil {
return nil, err
}
seed := key.D.bytes()
if len(seed) != ed25519.SeedSize {
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, wrong length for d")
}
privateKey := ed25519.NewKeyFromSeed(seed)
derivedPublicKey := privateKey.Public().(ed25519.PublicKey)
if !bytes.Equal(derivedPublicKey, publicKey) {
return nil, errors.New("go-jose/go-jose: invalid Ed25519 private key, x does not match d")
}
return privateKey, nil
}
func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
if key.X == nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid Ed key, missing x value")
}
publicKey := make([]byte, ed25519.PublicKeySize)
copy(publicKey[0:32], key.X.bytes())
rv := ed25519.PublicKey(publicKey)
return rv, nil
publicKey := key.X.bytes()
err := validateEd25519PublicKey(publicKey)
if err != nil {
return nil, err
}
return ed25519.PublicKey(bytes.Clone(publicKey)), nil
}
func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
@@ -669,9 +700,17 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
}
func fromEdPrivateKey(ed ed25519.PrivateKey) (*rawJSONWebKey, error) {
raw := fromEdPublicKey(ed25519.PublicKey(ed[32:]))
if len(ed) != ed25519.PrivateKeySize {
return nil, errors.New("go-jose/go-jose: invalid Ed25519 private key length")
}
raw.D = newBuffer(ed[0:32])
publicKey := ed.Public().(ed25519.PublicKey)
raw, err := fromEdPublicKey(publicKey)
if err != nil {
return nil, err
}
raw.D = newBuffer(ed.Seed())
return raw, nil
}
@@ -810,7 +849,7 @@ var (
ErrJWKSKidNotFound = errors.New("go-jose/go-jose: JWK with matching kid not found in JWK Set")
)
func tryJWKS(key interface{}, headers ...Header) (interface{}, error) {
func tryJWKS(key interface{}, header Header) (interface{}, error) {
var jwks JSONWebKeySet
switch jwksType := key.(type) {
@@ -823,16 +862,8 @@ func tryJWKS(key interface{}, headers ...Header) (interface{}, error) {
return key, nil
}
// Determine the KID to search for from the headers.
var kid string
for _, header := range headers {
if header.KeyID != "" {
kid = header.KeyID
break
}
}
// If no KID is specified in the headers, reject.
// If no KID is specified in the header, reject.
kid := header.KeyID
if kid == "" {
return nil, ErrJWKSKidNotFound
}
@@ -846,3 +877,107 @@ func tryJWKS(key interface{}, headers ...Header) (interface{}, error) {
return keys[0].Key, nil
}
// weakEd25519PublicKeys contains the low-order Ed25519 encodings accepted by
// Go's verifier that must not be accepted as JOSE verification keys.
var weakEd25519PublicKeys = map[[ed25519.PublicKeySize]byte]struct{}{
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}: {},
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
}: {},
{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}: {},
{
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
}: {},
{
0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05,
}: {},
{
0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85,
}: {},
{
0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a,
}: {},
{
0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa,
}: {},
{
0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
}: {},
{
0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}: {},
{
0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
}: {},
{
0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}: {},
{
0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
}: {},
{
0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
}: {},
}
func validateEd25519PublicKey(publicKey ed25519.PublicKey) error {
if len(publicKey) != ed25519.PublicKeySize {
return fmt.Errorf("go-jose/go-jose: invalid Ed25519 public key, wrong length for x")
}
var encoded [ed25519.PublicKeySize]byte
copy(encoded[:], publicKey)
_, ok := weakEd25519PublicKeys[encoded]
if ok {
return errors.New("go-jose/go-jose: invalid Ed25519 public key, low-order point")
}
return nil
}
+12 -3
View File
@@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"strings"
"unicode"
"github.com/go-jose/go-jose/v4/json"
)
@@ -89,12 +90,12 @@ func ParseSigned(
signature string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
signature = stripWhitespace(signature)
if strings.HasPrefix(signature, "{") {
trimmed := strings.TrimLeftFunc(signature, unicode.IsSpace)
if strings.HasPrefix(trimmed, "{") {
return ParseSignedJSON(signature, signatureAlgorithms)
}
return parseSignedCompact(signature, nil, signatureAlgorithms)
return parseSignedCompact(stripWhitespace(signature), nil, signatureAlgorithms)
}
// ParseSignedCompact parses a message in JWS Compact Serialization. Validation fails if the JWS is
@@ -186,6 +187,10 @@ func ParseSignedJSON(
input string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
if len(input) == 0 {
return nil, errEmptyInput
}
var parsed rawJSONWebSignature
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
@@ -369,6 +374,10 @@ func parseSignedCompact(
payload []byte,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
if len(input) == 0 {
return nil, errEmptyInput
}
protected, s, ok := strings.Cut(input, tokenDelim)
if !ok { // no period found
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
+15
View File
@@ -24,6 +24,11 @@ import (
"github.com/go-jose/go-jose/v4/json"
)
// maxNumericDate bounds an accepted NumericDate (in seconds). It is far past
// any real timestamp but safely below where an int64 conversion or time.Unix
// would overflow and wrap Time() to a bogus instant.
const maxNumericDate = 1 << 62
// Claims represents public claim values (as specified in RFC 7519).
type Claims struct {
Issuer string `json:"iss,omitempty"`
@@ -69,6 +74,16 @@ func (n *NumericDate) UnmarshalJSON(b []byte) error {
return ErrUnmarshalNumericDate
}
// Reject values large enough to overflow either the int64 conversion below
// or time.Unix in Time(). time.Unix adds a ~62e9-second offset internally,
// so a value near the int64 limit wraps and compares as a time in the
// past; for "nbf" that lets a not-yet-valid token pass the not-before
// check instead of being rejected. maxNumericDate (2^62 seconds) is far
// beyond any real timestamp yet safely below both overflow points.
if f >= maxNumericDate || f <= -maxNumericDate {
return ErrUnmarshalNumericDate
}
*n = NumericDate(f)
return nil
}
+5
View File
@@ -46,6 +46,11 @@ func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigI
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
pk := signer.Public()
if pk != nil && !pk.IsPublic() {
return recipientSigInfo{}, ErrNotPublic
}
return recipientSigInfo{
sigAlg: alg,
publicKey: signer.Public,
+6
View File
@@ -80,6 +80,12 @@ var (
// ErrUnsupportedCriticalHeader is returned when a header is marked critical but not supported by go-jose.
ErrUnsupportedCriticalHeader = errors.New("go-jose/go-jose: unsupported critical header")
// errEmptyInput is returned when go-jose was asked to parse an empty string.
errEmptyInput = errors.New("go-jose/go-jose: empty input")
// ErrNotPublic indicates a private key was passed where a public key was expected
ErrNotPublic = errors.New("go-jose/go-jose: public key was unexpectedly not public")
)
// Key management algorithms
+65 -46
View File
@@ -130,6 +130,8 @@ type payloadVerifier interface {
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
}
var errInvalidVerificationKey = errors.New("go-jose/go-jose: invalid verification key")
type genericSigner struct {
recipients []recipientSigInfo
nonceSource NonceSource
@@ -138,11 +140,21 @@ type genericSigner struct {
}
type recipientSigInfo struct {
sigAlg SignatureAlgorithm
sigAlg SignatureAlgorithm
// publicKey returns a synthetic JSONWebKey for the signer.
// For opaque signers, it calls OpaqueSigner.Public().
publicKey func() *JSONWebKey
signer payloadSigner
}
// getPublicKey gets the public key, with a nil check on the func.
func (r recipientSigInfo) getPublicKey() *JSONWebKey {
if r.publicKey == nil {
return nil
}
return r.publicKey()
}
func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey {
return func() *JSONWebKey {
return jwk
@@ -178,14 +190,23 @@ func NewMultiSigner(sigs []SigningKey, opts *SignerOptions) (Signer, error) {
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
switch verificationKey := verificationKey.(type) {
case ed25519.PublicKey:
if len(verificationKey) == 0 {
return nil, errInvalidVerificationKey
}
return &edEncrypterVerifier{
publicKey: verificationKey,
}, nil
case *rsa.PublicKey:
if verificationKey == nil {
return nil, errInvalidVerificationKey
}
return &rsaEncrypterVerifier{
publicKey: verificationKey,
}, nil
case *ecdsa.PublicKey:
if verificationKey == nil {
return nil, errInvalidVerificationKey
}
return &ecEncrypterVerifier{
publicKey: verificationKey,
}, nil
@@ -196,6 +217,9 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
case JSONWebKey:
return newVerifier(verificationKey.Key)
case *JSONWebKey:
if verificationKey == nil {
return nil, errInvalidVerificationKey
}
return newVerifier(verificationKey.Key)
case OpaqueVerifier:
return &opaqueVerifier{verifier: verificationKey}, nil
@@ -240,18 +264,18 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn
if err != nil {
return recipientSigInfo{}, err
}
if recipient.publicKey != nil && recipient.publicKey() != nil {
if recipientPubKey := recipient.getPublicKey(); recipientPubKey != nil {
// This should be impossible, but let's check anyway.
if !recipientPubKey.IsPublic() {
return recipientSigInfo{}, ErrNotPublic
}
// 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
publicKey.Key = recipientPubKey.Key
recipient.publicKey = staticPublicKey(&publicKey)
// This should be impossible, but let's check anyway.
if !recipient.publicKey().IsPublic() {
return recipientSigInfo{}, errors.New("go-jose/go-jose: public key was unexpectedly not public")
}
}
return recipient, nil
}
@@ -266,7 +290,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
headerAlgorithm: string(recipient.sigAlg),
}
if recipient.publicKey != nil && recipient.publicKey() != nil {
if recipientPubKey := recipient.getPublicKey(); recipientPubKey != 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
@@ -274,11 +298,11 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
// result of the JOSE spec. We've decided that this library will only include one or
// the other to avoid this confusion.
//
// See https://github.com/go-jose/go-jose/issues/157 for more context.
// See https://github.com/square/go-jose/issues/157 for more context.
if ctx.embedJWK {
protected[headerJWK] = recipient.publicKey()
protected[headerJWK] = recipientPubKey
} else {
keyID := recipient.publicKey().KeyID
keyID := recipientPubKey.KeyID
if keyID != "" {
protected[headerKeyID] = keyID
}
@@ -390,7 +414,13 @@ func (obj JSONWebSignature) UnsafePayloadWithoutVerification() []byte {
// The verificationKey argument must have one of the types allowed for the
// verificationKey argument of JSONWebSignature.Verify().
func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error {
key, err := tryJWKS(verificationKey, obj.headers()...)
if len(obj.Signatures) > 1 {
return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one")
}
signature := obj.Signatures[0]
key, err := tryJWKS(verificationKey, signature.Header)
if err != nil {
return err
}
@@ -399,12 +429,6 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter
return err
}
if len(obj.Signatures) > 1 {
return errors.New("go-jose/go-jose: too many signatures in payload; expecting only one")
}
signature := obj.Signatures[0]
if signature.header != nil {
// Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11,
// 4.1.11. "crit" (Critical) Header Parameter
@@ -432,11 +456,11 @@ func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey inter
headers := signature.mergedHeaders()
alg := headers.getSignatureAlgorithm()
err = verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return nil
if err != nil {
return ErrCryptoFailure
}
return ErrCryptoFailure
return nil
}
// VerifyMulti validates (one of the multiple) signatures on the object and
@@ -467,16 +491,6 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa
// The verificationKey argument must have one of the types allowed for the
// verificationKey argument of JSONWebSignature.Verify().
func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) {
key, err := tryJWKS(verificationKey, obj.headers()...)
if err != nil {
return -1, Signature{}, err
}
verifier, err := newVerifier(key)
if err != nil {
return -1, Signature{}, err
}
outer:
for i, signature := range obj.Signatures {
if signature.header != nil {
// Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11,
@@ -484,20 +498,31 @@ outer:
// "When used, this Header Parameter MUST be integrity
// protected; therefore, it MUST occur only within the JWS
// Protected Header."
err = signature.header.checkNoCritical()
err := signature.header.checkNoCritical()
if err != nil {
continue outer
continue
}
}
if signature.protected != nil {
// Check for only supported critical headers
err = signature.protected.checkSupportedCritical(supportedCritical)
err := signature.protected.checkSupportedCritical(supportedCritical)
if err != nil {
continue outer
continue
}
}
// If the verification key is a JWK Set, pick a key based on this signature's
// "kid" header. If no match, skip this signature.
key, err := tryJWKS(verificationKey, signature.Header)
if err != nil {
continue
}
verifier, err := newVerifier(key)
if err != nil {
continue
}
input, err := obj.computeAuthData(payload, &signature)
if err != nil {
continue
@@ -506,18 +531,12 @@ outer:
headers := signature.mergedHeaders()
alg := headers.getSignatureAlgorithm()
err = verifier.verifyPayload(input, signature.Signature, alg)
if err == nil {
return i, signature, nil
if err != nil {
continue
}
return i, signature, nil
}
return -1, Signature{}, ErrCryptoFailure
}
func (obj JSONWebSignature) headers() []Header {
headers := make([]Header, len(obj.Signatures))
for i, sig := range obj.Signatures {
headers[i] = sig.Header
}
return headers
}
+5 -5
View File
@@ -34,8 +34,8 @@ import (
josecipher "github.com/go-jose/go-jose/v4/cipher"
)
// RandReader is a cryptographically secure random number generator (stubbed out in tests).
var RandReader = rand.Reader
// randReader is a cryptographically secure random number generator (stubbed out in tests).
var randReader = rand.Reader
const (
// RFC7518 recommends a minimum of 1,000 iterations:
@@ -153,7 +153,7 @@ func getPbkdf2Params(alg KeyAlgorithm) (int, func() hash.Hash) {
// getRandomSalt generates a new salt of the given size.
func getRandomSalt(size int) ([]byte, error) {
salt := make([]byte, size)
_, err := io.ReadFull(RandReader, salt)
_, err := io.ReadFull(randReader, salt)
if err != nil {
return nil, err
}
@@ -198,7 +198,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
}
@@ -238,7 +238,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
}
+1 -1
View File
@@ -94,7 +94,7 @@ github.com/felixge/httpsnoop
# github.com/fvbommel/sortorder v1.2.0
## explicit; go 1.21
github.com/fvbommel/sortorder
# github.com/go-jose/go-jose/v4 v4.1.4
# github.com/go-jose/go-jose/v4 v4.1.5
## explicit; go 1.24.0
github.com/go-jose/go-jose/v4
github.com/go-jose/go-jose/v4/cipher