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

Fixed Security Issues

- Thumbprint: reject invalid RSA key [GHSA-r39w-98wm-9pp6]
- json: backport stdlib fix for embedded unexported field [GHSA-27pc-rm86-68pq]
- Detect if headers are set by presence in the map [GHSA-p9j4-2j95-j4rv]
- limit RSA size in certificate verify [GHSA-v6h2-qj3f-2h6v]
- Limit recipients and signatures in JWE/JWS [GHSA-c67m-wr4h-2ccg]

Fixed

- jwk: fix bug in x5c parsing
- reject base64-encoded "null" in protected headers

Deprecated

- deprecate ParseEncrypted / ParseSigned

full diff: https://github.com/go-jose/go-jose/compare/v4.1.6...v4.1.7

[GHSA-r39w-98wm-9pp6]: https://github.com/go-jose/go-jose/security/advisories/GHSA-r39w-98wm-9pp6
[GHSA-27pc-rm86-68pq]: https://github.com/go-jose/go-jose/security/advisories/GHSA-27pc-rm86-68pq
[GHSA-p9j4-2j95-j4rv]: https://github.com/go-jose/go-jose/security/advisories/GHSA-p9j4-2j95-j4rv
[GHSA-v6h2-qj3f-2h6v]: https://github.com/go-jose/go-jose/security/advisories/GHSA-v6h2-qj3f-2h6v
[GHSA-c67m-wr4h-2ccg]: https://github.com/go-jose/go-jose/security/advisories/GHSA-c67m-wr4h-2ccg

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 81e4a109a3)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2026-11-03 14:25:22 +01:00
parent 86e51b44de
commit bb485bc900
14 changed files with 259 additions and 139 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ require (
github.com/docker/go-connections v0.8.2
github.com/docker/go-units v0.5.0
github.com/fvbommel/sortorder v1.2.1
github.com/go-jose/go-jose/v4 v4.1.6
github.com/go-jose/go-jose/v4 v4.1.7
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.1 h1:ASc85RbthwLEqzuiCk7WQQiopfyGWQiEYZVJ3Qa4A+U=
github.com/fvbommel/sortorder v1.2.1/go.mod h1:LbhO04ijZIeUuvz9B9BkI/qYrpZZEn1gWhxv4QjUKVs=
github.com/go-jose/go-jose/v4 v4.1.6 h1:sNRkftxFJxwsILiQukqp5z83QBSmntVwmgRY7AqBDO4=
github.com/go-jose/go-jose/v4 v4.1.6/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-jose/go-jose/v4 v4.1.7 h1:Nac/98uT5LPkfbiFuYa8ktf62a2VxwQ4xbq1GK8pV5E=
github.com/go-jose/go-jose/v4 v4.1.7/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 -3
View File
@@ -408,7 +408,10 @@ func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWe
(*obj.protected)[k] = makeRawMessage(b)
}
authData := obj.computeAuthData()
authData, err := obj.computeAuthData()
if err != nil {
return nil, err
}
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
if err != nil {
return nil, err
@@ -492,7 +495,10 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
tag: obj.tag,
}
authData := obj.computeAuthData()
authData, err := obj.computeAuthData()
if err != nil {
return nil, err
}
var plaintext []byte
recipient := obj.recipients[0]
@@ -531,6 +537,12 @@ func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error)
//
// Automatically decompresses plaintext, but returns an error if the decompressed
// data would be >250kB or >3x the size of the compressed data, whichever is larger.
//
// Calling DecryptMulti() with PBES2 enabled can result in excessive CPU work.
// The PBES2 key unwrapping algorithms are intentionally expensive, and have to
// be run for each recipient. To be safe: don't allow PBES2 algorithms in calls
// to ParseEncrypted / ParseEncryptedJSON. If your application requires PBES2,
// prefer Decrypt(), which will only process single-recipient messages.
func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) {
globalHeaders := obj.mergedHeaders(nil)
@@ -564,7 +576,10 @@ func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Heade
tag: obj.tag,
}
authData := obj.computeAuthData()
authData, err := obj.computeAuthData()
if err != nil {
return -1, Header{}, nil, err
}
index := -1
var plaintext []byte
+6 -32
View File
@@ -30,30 +30,6 @@ import (
"github.com/go-jose/go-jose/v4/json"
)
// Helper function to serialize known-good objects.
// Precondition: value is not a nil pointer.
func mustSerializeJSON(value interface{}) []byte {
out, err := json.Marshal(value)
if err != nil {
panic(err)
}
// We never want to serialize the top-level value "null," since it's not a
// valid JOSE message. But if a caller passes in a nil pointer to this method,
// MarshalJSON will happily serialize it as the top-level value "null". If
// that value is then embedded in another operation, for instance by being
// base64-encoded and fed as input to a signing algorithm
// (https://github.com/go-jose/go-jose/issues/22), the result will be
// incorrect. Because this method is intended for known-good objects, and a nil
// pointer is not a known-good object, we are free to panic in this case.
// Note: It's not possible to directly check whether the data pointed at by an
// interface is a nil pointer, so we do this hacky workaround.
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
if string(out) == "null" {
panic("Tried to serialize a nil pointer.")
}
return out
}
// Strip all newlines and whitespace
func stripWhitespace(data string) string {
buf := strings.Builder{}
@@ -122,6 +98,8 @@ func inflate(input []byte) ([]byte, error) {
}
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
//
// A nil byteBuffer represents an empty slice of bytes.
type byteBuffer struct {
data []byte
}
@@ -169,13 +147,13 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
return err
}
*b = *newBuffer(decoded)
b.data = decoded
return nil
}
func (b *byteBuffer) base64() string {
return base64.RawURLEncoding.EncodeToString(b.data)
return base64.RawURLEncoding.EncodeToString(b.bytes())
}
func (b *byteBuffer) bytes() []byte {
@@ -186,12 +164,8 @@ func (b *byteBuffer) bytes() []byte {
return b.data
}
func (b byteBuffer) bigInt() *big.Int {
return new(big.Int).SetBytes(b.data)
}
func (b byteBuffer) toInt() int {
return int(b.bigInt().Int64())
func (b *byteBuffer) bigInt() *big.Int {
return new(big.Int).SetBytes(b.bytes())
}
func base64EncodeLen(sl []byte) int {
+13
View File
@@ -671,6 +671,19 @@ func (d *decodeState) object(v reflect.Value) {
for _, i := range f.index {
if subv.Kind() == reflect.Ptr {
if subv.IsNil() {
// If a struct embeds a pointer to an unexported type,
// it is not possible to set a newly allocated value
// since the field is unexported.
//
// See https://golang.org/issue/21357
if !subv.CanSet() {
d.saveError(fmt.Errorf("json: cannot set embedded pointer to unexported struct: %v", subv.Type().Elem()))
// Invalidate subv to ensure d.value(subv) skips over
// the JSON value without assigning it to subv.
subv = reflect.Value{}
destring = false
break
}
subv.Set(reflect.New(subv.Type().Elem()))
}
subv = subv.Elem()
+13 -1
View File
@@ -1025,7 +1025,19 @@ func typeFields(t reflect.Type) []field {
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
if sf.Anonymous {
t := sf.Type
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
if !sf.IsExported() && t.Kind() != reflect.Struct {
// Ignore embedded fields of unexported non-struct types.
continue
}
// Do not ignore embedded fields of unexported struct types
// since they may have exported fields.
} else if !sf.IsExported() {
// Ignore unexported non-embedded fields.
continue
}
tag := sf.Tag.Get("json")
+52 -18
View File
@@ -25,6 +25,12 @@ import (
"github.com/go-jose/go-jose/v4/json"
)
// MaxRecipients is the maximum number of recipients of a JWE at parse time.
// Higher values mean more potential work done across untrusted input.
//
// If modifying MaxRecipients, do it before any parsing occurs.
var MaxRecipients = 32
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
type rawJSONWebEncryption struct {
Protected *byteBuffer `json:"protected,omitempty"`
@@ -84,14 +90,18 @@ func (obj JSONWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
}
// Get the additional authenticated data from a JWE object.
func (obj JSONWebEncryption) computeAuthData() []byte {
func (obj JSONWebEncryption) computeAuthData() ([]byte, error) {
var protected string
switch {
case obj.original != nil && obj.original.Protected != nil:
protected = obj.original.Protected.base64()
case obj.protected != nil:
protected = base64.RawURLEncoding.EncodeToString(mustSerializeJSON((obj.protected)))
serializedProtected, err := json.Marshal(obj.protected)
if err != nil {
return nil, err
}
protected = base64.RawURLEncoding.EncodeToString(serializedProtected)
default:
protected = ""
}
@@ -102,7 +112,7 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
output = append(output, []byte(base64.RawURLEncoding.EncodeToString(obj.aad))...)
}
return output
return output, nil
}
func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
@@ -128,11 +138,10 @@ func containsContentEncryption(haystack []ContentEncryption, needle ContentEncry
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
//
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
// parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
// header parameters respectively. They must be non-empty. Non-matching algorithms cause a parse error.
//
// Deprecated: use ParseEncryptedJSON or ParseEncryptedCompact depending on which serialization you expect.
func ParseEncrypted(input string,
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
@@ -145,9 +154,13 @@ func ParseEncrypted(input string,
return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
}
// ParseEncryptedJSON parses a message in JWE JSON Serialization.
// ParseEncryptedJSON parses a message in JWE JSON Serialization. Messages with
// more than [MaxRecipients] recipients are rejected.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
//
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
// header parameters respectively. They must be non-empty. Any non-allowed algorithms will cause parsing to fail.
func ParseEncryptedJSON(
input string,
keyEncryptionAlgorithms []KeyAlgorithm,
@@ -177,6 +190,9 @@ func (parsed *rawJSONWebEncryption) sanitized(
if len(contentEncryption) == 0 {
return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
}
if len(parsed.Recipients) > MaxRecipients {
return nil, fmt.Errorf("go-jose/go-jose: too many recipients in JWE; maximum is %d", MaxRecipients)
}
obj := &JSONWebEncryption{
original: parsed,
@@ -195,12 +211,11 @@ func (parsed *rawJSONWebEncryption) sanitized(
}
}
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
}
protected, err := parseProtectedHeader(parsed.Protected)
if err != nil {
return nil, err
}
obj.protected = protected
if len(parsed.Recipients) == 0 {
obj.recipients = []recipientInfo{
@@ -258,7 +273,6 @@ func (parsed *rawJSONWebEncryption) sanitized(
// Note: this must be called _after_ we parse the protected header,
// otherwise fields from the protected header will not get picked up.
mergedHeaders := obj.mergedHeaders(nil)
var err error
obj.Header, err = mergedHeaders.sanitized()
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: cannot sanitize merged headers: %w", err)
@@ -287,6 +301,9 @@ func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncr
// ParseEncryptedCompact parses a message in JWE Compact Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
//
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
// header parameters respectively. They must be non-empty. Any non-allowed algorithms will cause parsing to fail.
func ParseEncryptedCompact(
input string,
keyAlgorithms []KeyAlgorithm,
@@ -354,7 +371,10 @@ func (obj JSONWebEncryption) CompactSerialize() (string, error) {
return "", ErrNotSupported
}
serializedProtected := mustSerializeJSON(obj.protected)
serializedProtected, err := json.Marshal(obj.protected)
if err != nil {
return "", err
}
return base64JoinWithDots(
serializedProtected,
@@ -366,7 +386,13 @@ func (obj JSONWebEncryption) CompactSerialize() (string, error) {
}
// FullSerialize serializes an object using the full JSON serialization format.
//
// It returns "" if obj cannot be serialized, for example if it has no recipients.
func (obj JSONWebEncryption) FullSerialize() string {
if len(obj.recipients) == 0 {
return ""
}
raw := rawJSONWebEncryption{
Unprotected: obj.unprotected,
Iv: newBuffer(obj.iv),
@@ -392,8 +418,16 @@ func (obj JSONWebEncryption) FullSerialize() string {
}
if obj.protected != nil {
raw.Protected = newBuffer(mustSerializeJSON(obj.protected))
serializedProtected, err := json.Marshal(obj.protected)
if err != nil {
return ""
}
raw.Protected = newBuffer(serializedProtected)
}
return string(mustSerializeJSON(raw))
out, err := json.Marshal(raw)
if err != nil {
return ""
}
return string(out)
}
+40 -25
View File
@@ -30,6 +30,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"math"
"math/big"
"net/url"
"reflect"
@@ -249,7 +250,7 @@ func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) {
keyPub = key
}
case "oct":
if certPub != nil {
if len(certs) > 0 {
return errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
}
key, err = raw.symmetricKey()
@@ -288,10 +289,8 @@ func (k *JSONWebKey) unmarshalJSON(data []byte, allowPrivate bool) (err error) {
return ErrUnsupportedKeyType
}
if certPub != nil && keyPub != nil {
if !reflect.DeepEqual(certPub, keyPub) {
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
}
if len(certs) > 0 && (certPub == nil || !reflect.DeepEqual(certPub, keyPub)) {
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
}
*k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
@@ -408,6 +407,9 @@ func ecThumbprintInput(curve elliptic.Curve, x, y *big.Int) (string, error) {
}
func rsaThumbprintInput(n *big.Int, e int) (string, error) {
if n == nil || e <= 0 {
return "", errors.New("go-jose/go-jose: invalid RSA key")
}
return fmt.Sprintf(rsaThumbprintTemplate,
newBufferFromInt(uint64(e)).base64(),
newBuffer(n.Bytes()).base64()), nil
@@ -522,25 +524,40 @@ func (k *JSONWebKey) Valid() bool {
}
// MaxRSAModulusBits is the largest RSA modulus size, in bits, that will be
// accepted when parsing a JWK. Applications that need larger keys can raise
// this limit.
// accepted when parsing a JWK or verifying an x5c certificate chain with
// [Header.Certificates]. Applications that need larger keys can raise this
// limit.
//
// If modifying MaxRSAModulusBits, do it before any parsing occurs.
var MaxRSAModulusBits = 4096
func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
if key.N == nil || key.E == nil {
return rsaPublicKey(key.N, key.E)
}
func rsaPublicKey(N, E *byteBuffer) (*rsa.PublicKey, error) {
if N == nil || E == nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values")
}
n := key.N.bigInt()
n := N.bigInt()
if n.BitLen() > MaxRSAModulusBits {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large")
}
e := new(big.Int).SetBytes(E.bytes())
if !e.IsInt64() {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, bad e value")
}
eInt64 := e.Int64()
if eInt64 < 2 || eInt64 > math.MaxInt {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, bad e value")
}
return &rsa.PublicKey{
N: n,
E: key.E.toInt(),
E: int(eInt64),
}, nil
}
@@ -584,11 +601,11 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
// 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) {
if curveSize(curve) != len(key.X.bytes()) {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for x")
}
if curveSize(curve) != len(key.Y.data) {
if curveSize(curve) != len(key.Y.bytes()) {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for y")
}
@@ -701,23 +718,21 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
}
n := key.N.bigInt()
if n.BitLen() > MaxRSAModulusBits {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, modulus too large")
pubKey, err := rsaPublicKey(key.N, key.E)
if err != nil {
return nil, err
}
n := pubKey.N
p, q := key.P.bigInt(), key.Q.bigInt()
if p.BitLen()+q.BitLen() > n.BitLen()+1 || new(big.Int).Mul(p, q).Cmp(n) != 0 {
return nil, errors.New("go-jose/go-jose: invalid RSA private key, p * q != n")
}
rv := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: n,
E: key.E.toInt(),
},
D: key.D.bigInt(),
Primes: []*big.Int{p, q},
PublicKey: *pubKey,
D: key.D.bigInt(),
Primes: []*big.Int{p, q},
}
if key.Dp != nil {
@@ -730,7 +745,7 @@ func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
rv.Precomputed.Qinv = key.Qi.bigInt()
}
err := rv.Validate()
err = rv.Validate()
return rv, err
}
@@ -804,16 +819,16 @@ func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
// 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) {
if curveSize(curve) != len(key.X.bytes()) {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for x")
}
if curveSize(curve) != len(key.Y.data) {
if curveSize(curve) != len(key.Y.bytes()) {
return nil, fmt.Errorf("go-jose/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) {
if dSize(curve) != len(key.D.bytes()) {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for d")
}
+64 -20
View File
@@ -27,6 +27,12 @@ import (
"github.com/go-jose/go-jose/v4/json"
)
// MaxSignatures is the maximum number of signatures in a JWS at parse time.
// Higher values mean more potential work done across untrusted input.
//
// If modifying MaxSignatures, do it before any parsing occurs.
var MaxSignatures = 32
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
type rawJSONWebSignature struct {
Payload *byteBuffer `json:"payload,omitempty"`
@@ -86,6 +92,11 @@ type Signature struct {
// Verify() after parsing to validate the signature and obtain the payload.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
//
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
// It must be non-empty. Non-matching algorithms cause a parse error.
//
// Deprecated: call ParseSignedJSON or ParseSignedCompact depending on which serialization you expect.
func ParseSigned(
signature string,
signatureAlgorithms []SignatureAlgorithm,
@@ -108,6 +119,9 @@ func ParseSigned(
// Verify() after parsing to validate the signature and obtain the payload.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
//
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
// It must be non-empty. Non-matching algorithms cause a parse error.
func ParseSignedCompact(
signature string,
signatureAlgorithms []SignatureAlgorithm,
@@ -125,6 +139,9 @@ func ParseSignedCompact(
// parsing. Call Verify() after parsing to validate the signature and obtain the payload.
//
// https://datatracker.ietf.org/doc/html/rfc7515#appendix-F
//
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
// It must be non-empty. Any non-allowed algorithms will cause parsing to fail.
func ParseDetached(
signature string,
payload []byte,
@@ -157,7 +174,11 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
authData.WriteString(signature.original.Protected.base64())
} else if signature.protected != nil {
protectedHeader = signature.protected
authData.WriteString(base64.RawURLEncoding.EncodeToString(mustSerializeJSON(protectedHeader)))
serializedProtected, err := json.Marshal(protectedHeader)
if err != nil {
return nil, err
}
authData.WriteString(base64.RawURLEncoding.EncodeToString(serializedProtected))
}
needsBase64 := true
@@ -180,9 +201,13 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
return authData.Bytes(), nil
}
// ParseSignedJSON parses a message in JWS JSON Serialization.
// ParseSignedJSON parses a message in JWS JSON Serialization. Messages with
// more than [MaxSignatures] signatures are rejected.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
//
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
// It must be non-empty. Non-matching algorithms cause a parse error.
func ParseSignedJSON(
input string,
signatureAlgorithms []SignatureAlgorithm,
@@ -236,6 +261,9 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
if parsed.Payload == nil {
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
}
if len(parsed.Signatures) > MaxSignatures {
return nil, fmt.Errorf("go-jose/go-jose: too many signatures in JWS; maximum is %d", MaxSignatures)
}
obj := &JSONWebSignature{
payload: parsed.Payload.bytes(),
@@ -245,14 +273,13 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
if len(parsed.Signatures) == 0 {
// No signatures array, must be flattened serialization
signature := Signature{}
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
signature.protected = &rawHeader{}
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
if err != nil {
return nil, err
}
prot, err := parseProtectedHeader(parsed.Protected)
if err != nil {
return nil, err
}
signature.protected = prot
// Check that there is not a nonce in the unprotected header
if parsed.Header != nil && parsed.Header.getNonce() != "" {
return nil, ErrUnprotectedNonce
@@ -281,7 +308,6 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
}
var err error
signature.Header, err = mergedHeaders.sanitized()
if err != nil {
return nil, err
@@ -311,14 +337,13 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
}
for i, sig := range parsed.Signatures {
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
obj.Signatures[i].protected = &rawHeader{}
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
if err != nil {
return nil, err
}
prot, err := parseProtectedHeader(sig.Protected)
if err != nil {
return nil, err
}
obj.Signatures[i].protected = prot
// Check that there is not a nonce in the unprotected header
if sig.Header != nil && sig.Header.getNonce() != "" {
return nil, ErrUnprotectedNonce
@@ -332,7 +357,6 @@ func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgo
return nil, newErrUnexpectedSignatureAlgorithm(alg, signatureAlgorithms)
}
var err error
obj.Signatures[i].Header, err = mergedHeaders.sanitized()
if err != nil {
return nil, err
@@ -427,7 +451,10 @@ func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {
return "", ErrNotSupported
}
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
serializedProtected, err := json.Marshal(obj.Signatures[0].protected)
if err != nil {
return "", err
}
var payload []byte
if !detached {
@@ -452,14 +479,23 @@ func (obj JSONWebSignature) DetachedCompactSerialize() (string, error) {
}
// FullSerialize serializes an object using the full JSON serialization format.
//
// It returns "" if obj cannot be serialized.
func (obj JSONWebSignature) FullSerialize() string {
raw := rawJSONWebSignature{
Payload: newBuffer(obj.payload),
}
if len(obj.Signatures) == 0 {
return ""
}
if len(obj.Signatures) == 1 {
if obj.Signatures[0].protected != nil {
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
serializedProtected, err := json.Marshal(obj.Signatures[0].protected)
if err != nil {
return ""
}
raw.Protected = newBuffer(serializedProtected)
}
raw.Header = obj.Signatures[0].header
@@ -473,10 +509,18 @@ func (obj JSONWebSignature) FullSerialize() string {
}
if signature.protected != nil {
raw.Signatures[i].Protected = newBuffer(mustSerializeJSON(signature.protected))
serializedProtected, err := json.Marshal(signature.protected)
if err != nil {
return ""
}
raw.Signatures[i].Protected = newBuffer(serializedProtected)
}
}
}
return string(mustSerializeJSON(raw))
out, err := json.Marshal(raw)
if err != nil {
return ""
}
return string(out)
}
+12 -17
View File
@@ -85,7 +85,12 @@ func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken,
return sig, nil
}
// ParseSigned parses token from JWS form.
// ParseSigned parses a JWT in the JWS Compact Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-3.1
//
// The signatureAlgorithms parameter contains the allowable algorithms in the "alg" header parameter.
// It must be non-empty. Non-matching algorithms cause a parse error.
func ParseSigned(s string, signatureAlgorithms []jose.SignatureAlgorithm) (*JSONWebToken, error) {
sig, err := jose.ParseSignedCompact(s, signatureAlgorithms)
if err != nil {
@@ -142,13 +147,10 @@ func parseEncryptedCompact(
return enc, nil
}
// ParseEncrypted parses token from JWE form.
// ParseEncrypted parses a JWT in the JWE Compact Serialization.
//
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
// parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
// The keyAlgorithms and contentEncryption parameters contain the allowable algorithms in the "alg" and "enc"
// header parameters respectively. They must be non-empty. Non-matching algorithms cause a parse error.
func ParseEncrypted(s string,
keyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
@@ -164,17 +166,10 @@ func ParseEncrypted(s string,
}, nil
}
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
// ParseSignedAndEncrypted parses a signed-then-encrypted token in the JWE Compact Serialization.
//
// The encryptionKeyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters, respectively, of the outer JWE. They must be nonempty, and each "alg" or "enc"
// header in parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
//
// The signatureAlgorithms parameter is used to validate the "alg" header parameter of the
// inner JWS. It must be nonempty, and the "alg" header in the inner JWS must contain a value
// that is present in the parameter.
// The encryptionKeyAlgorithms, contentEncryption, and signatureAlgorithms parameters contain the allowable
// algorithms. They must be non-empty. Non-matching algorithms cause a parse error.
func ParseSignedAndEncrypted(s string,
encryptionKeyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
+32 -17
View File
@@ -18,6 +18,7 @@ package jose
import (
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
@@ -210,13 +211,20 @@ type Header struct {
// 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
// an error if there was no x5c header present, any certificate in the
// chain has an RSA key larger than [MaxRSAModulusBits], 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, ErrMissingX5cHeader
}
for _, cert := range h.certificates {
if pub, ok := cert.PublicKey.(*rsa.PublicKey); ok && pub.N.BitLen() > MaxRSAModulusBits {
return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key in x5c certificate, modulus too large")
}
}
leaf := h.certificates[0]
if opts.Intermediates == nil {
opts.Intermediates = x509.NewCertPool()
@@ -490,22 +498,8 @@ func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
}
func (parsed rawHeader) isSet(k HeaderKey) bool {
dvr := parsed[k]
if dvr == nil {
return false
}
var dv interface{}
err := json.Unmarshal(*dvr, &dv)
if err != nil {
return true
}
if dvStr, ok := dv.(string); ok {
return dvStr != ""
}
return true
_, ok := parsed[k]
return ok
}
// Merge headers from src into dst, giving precedence to headers from l.
@@ -555,3 +549,24 @@ func makeRawMessage(b []byte) *json.RawMessage {
rm := json.RawMessage(b)
return &rm
}
// parseProtectedHeader parses a `byteBuffer` into a `*rawHeader`.
//
// The byteBuffer represents a base64-decoded "protected" header, which
// is specified to contain a JSON object.
//
// If the input is nil or empty, returns `nil, nil`.
//
// `rawHeader` is a map type and so can be nil. When this function
// returns a non-nil `*rawHeader`, the map is guaranteed to be non-nil.
func parseProtectedHeader(in *byteBuffer) (*rawHeader, error) {
if in == nil || len(in.bytes()) == 0 {
return nil, nil
}
var header rawHeader
err := json.Unmarshal(in.bytes(), &header)
if err != nil || header == nil {
return nil, errors.New("go-jose/go-jose: invalid protected header")
}
return &header, nil
}
+4 -1
View File
@@ -321,7 +321,10 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) {
protected[k] = v
}
serializedProtected := mustSerializeJSON(protected)
serializedProtected, err := json.Marshal(protected)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: Error marshaling JSON: %s", err)
}
needsBase64 := true
if b64, ok := protected[headerB64]; ok {
+1 -1
View File
@@ -421,7 +421,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: %v", err)
}
if p2s == nil || len(p2s.data) == 0 {
if p2s == nil || len(p2s.bytes()) == 0 {
return nil, fmt.Errorf("go-jose/go-jose: invalid P2S: must be present")
}
+1 -1
View File
@@ -93,7 +93,7 @@ github.com/felixge/httpsnoop
# github.com/fvbommel/sortorder v1.2.1
## explicit; go 1.21
github.com/fvbommel/sortorder
# github.com/go-jose/go-jose/v4 v4.1.6
# github.com/go-jose/go-jose/v4 v4.1.7
## explicit; go 1.24.0
github.com/go-jose/go-jose/v4
github.com/go-jose/go-jose/v4/cipher