Files
QSfera/Server/vendor/github.com/lestrrat-go/jwx/v3/jws/jws.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

666 lines
22 KiB
Go

//go:generate ../tools/cmd/genjws.sh
// Package jws implements the digital signature on JSON based data
// structures as described in https://tools.ietf.org/html/rfc7515
//
// If you do not care about the details, the only things that you
// would need to use are the following functions:
//
// jws.Sign(payload, jws.WithKey(algorithm, key))
// jws.Verify(serialized, jws.WithKey(algorithm, key))
//
// To sign, simply use `jws.Sign`. `payload` is a []byte buffer that
// contains whatever data you want to sign. `alg` is one of the
// jwa.SignatureAlgorithm constants from package jwa. For RSA and
// ECDSA family of algorithms, you will need to prepare a private key.
// For HMAC family, you just need a []byte value. The `jws.Sign`
// function will return the encoded JWS message on success.
//
// To verify, use `jws.Verify`. It will parse the `encodedjws` buffer
// and verify the result using `algorithm` and `key`. Upon successful
// verification, the original payload is returned, so you can work on it.
//
// As a sidenote, consider using github.com/lestrrat-go/htmsig if you
// looking for HTTP Message Signatures (RFC9421) -- it uses the same
// underlying signing/verification mechanisms as this module.
package jws
import (
"bufio"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"errors"
"fmt"
"io"
"reflect"
"sync"
"unicode"
"unicode/utf8"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/internal/jwxio"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwa"
"github.com/lestrrat-go/jwx/v3/jwk"
"github.com/lestrrat-go/jwx/v3/jws/jwsbb"
)
var registry = json.NewRegistry()
var signers = make(map[jwa.SignatureAlgorithm]Signer)
var muSigner = &sync.Mutex{}
func removeSigner(alg jwa.SignatureAlgorithm) {
muSigner.Lock()
defer muSigner.Unlock()
delete(signers, alg)
}
type defaultSigner struct {
alg jwa.SignatureAlgorithm
}
func (s defaultSigner) Algorithm() jwa.SignatureAlgorithm {
return s.alg
}
func (s defaultSigner) Sign(key any, payload []byte) ([]byte, error) {
return jwsbb.Sign(key, s.alg.String(), payload, nil)
}
type signerAdapter struct {
signer Signer
}
func (s signerAdapter) Algorithm() jwa.SignatureAlgorithm {
return s.signer.Algorithm()
}
func (s signerAdapter) Sign(key any, payload []byte) ([]byte, error) {
return s.signer.Sign(payload, key)
}
const (
fmtInvalid = 1 << iota
fmtCompact
fmtJSON
fmtJSONPretty
fmtMax
)
// silence linters
var _ = fmtInvalid
var _ = fmtMax
func validateKeyBeforeUse(key any) error {
jwkKey, ok := key.(jwk.Key)
if !ok {
converted, err := jwk.Import(key)
if err != nil {
return fmt.Errorf(`could not convert key of type %T to jwk.Key for validation: %w`, key, err)
}
jwkKey = converted
}
return jwkKey.Validate()
}
// Sign generates a JWS message for the given payload and returns
// it in serialized form, which can be in either compact or
// JSON format. Default is compact.
//
// You must pass at least one key to `jws.Sign()` by using `jws.WithKey()`
// option.
//
// jws.Sign(payload, jws.WithKey(alg, key))
// jws.Sign(payload, jws.WithJSON(), jws.WithKey(alg1, key1), jws.WithKey(alg2, key2))
//
// Note that in the second example the `jws.WithJSON()` option is
// specified as well. This is because the compact serialization
// format does not support multiple signatures, and users must
// specifically ask for the JSON serialization format.
//
// Read the documentation for `jws.WithKey()` to learn more about the
// possible values that can be used for `alg` and `key`.
//
// You may create JWS messages with the "none" (jwa.NoSignature) algorithm
// if you use the `jws.WithInsecureNoSignature()` option. This option
// can be combined with one or more signature keys, as well as the
// `jws.WithJSON()` option to generate multiple signatures (though
// the usefulness of such constructs is highly debatable)
//
// Note that this library does not allow you to successfully call `jws.Verify()` on
// signatures with the "none" algorithm. To parse these, use `jws.Parse()` instead.
//
// If you want to use a detached payload, use `jws.WithDetachedPayload()` as
// one of the options. When you use this option, you must always set the
// first parameter (`payload`) to `nil`, or the function will return an error
//
// You may also want to look at how to pass protected headers to the
// signing process, as you will likely be required to set the `b64` field
// when using detached payload.
//
// Look for options that return `jws.SignOption` or `jws.SignVerifyOption`
// for a complete list of options that can be passed to this function.
//
// You can use `errors.Is` with `jws.SignError()` to check if an error is from this function.
func Sign(payload []byte, options ...SignOption) ([]byte, error) {
sc := signContextPool.Get()
defer signContextPool.Put(sc)
sc.payload = payload
if err := sc.ProcessOptions(options); err != nil {
return nil, signerr(`failed to process options: %w`, err)
}
lsigner := len(sc.sigbuilders)
if lsigner == 0 {
return nil, signerr(`no signers available. Specify an algorithm and a key using jws.WithKey()`)
}
// Design note: while we could have easily set format = fmtJSON when
// lsigner > 1, I believe the decision to change serialization formats
// must be explicitly stated by the caller. Otherwise, I'm pretty sure
// there would be people filing issues saying "I get JSON when I expected
// compact serialization".
//
// Therefore, instead of making implicit format conversions, we force the
// user to spell it out as `jws.Sign(..., jws.WithJSON(), jws.WithKey(...), jws.WithKey(...))`
if sc.format == fmtCompact && lsigner != 1 {
return nil, signerr(`cannot have multiple signers (keys) specified for compact serialization. Use only one jws.WithKey()`)
}
// Create a Message object with all the bits and bobs, and we'll
// serialize it in the end
var result Message
if err := sc.PopulateMessage(&result); err != nil {
return nil, signerr(`failed to populate message: %w`, err)
}
switch sc.format {
case fmtJSON:
return json.Marshal(result)
case fmtJSONPretty:
return json.MarshalIndent(result, "", " ")
case fmtCompact:
// Take the only signature object, and convert it into a Compact
// serialization format
var compactOpts []CompactOption
if sc.detached {
compactOpts = append(compactOpts, WithDetached(true))
}
for _, option := range options {
if copt, ok := option.(CompactOption); ok {
compactOpts = append(compactOpts, copt)
}
}
return Compact(&result, compactOpts...)
default:
return nil, signerr(`invalid serialization format`)
}
}
var allowNoneWhitelist = jwk.WhitelistFunc(func(string) bool {
return false
})
// Verify checks if the given JWS message is verifiable using `alg` and `key`.
// `key` may be a "raw" key (e.g. rsa.PublicKey) or a jwk.Key
//
// If the verification is successful, `err` is nil, and the content of the
// payload that was signed is returned. If you need more fine-grained
// control of the verification process, manually generate a
// `Verifier` in `verify` subpackage, and call `Verify` method on it.
// If you need to access signatures and JOSE headers in a JWS message,
// use `Parse` function to get `Message` object.
//
// Because the use of "none" (jwa.NoSignature) algorithm is strongly discouraged,
// this function DOES NOT consider it a success when `{"alg":"none"}` is
// encountered in the message (it would also be counterintuitive when the code says
// it _verified_ something when in fact it did no such thing). If you want to
// accept messages with "none" signature algorithm, use `jws.Parse` to get the
// raw JWS message.
//
// The error returned by this function is of type can be checked against
// `jws.VerifyError()` and `jws.VerificationError()`. The latter is returned
// when the verification process itself fails (e.g. invalid signature, wrong key),
// while the former is returned when any other part of the `jws.Verify()`
// function fails.
func Verify(buf []byte, options ...VerifyOption) ([]byte, error) {
vc := verifyContextPool.Get()
defer verifyContextPool.Put(vc)
if err := vc.ProcessOptions(options); err != nil {
return nil, verifyerr(`failed to process options: %w`, err)
}
return vc.VerifyMessage(buf)
}
// get the value of b64 header field.
// If the field does not exist, returns true (default)
// Otherwise return the value specified by the header field.
func getB64Value(hdr Headers) bool {
var b64 bool
if err := hdr.Get("b64", &b64); err != nil {
return true // default
}
return b64
}
// Parse parses contents from the given source and creates a jws.Message
// struct. By default the input can be in either compact or full JSON serialization.
//
// You may pass `jws.WithJSON()` and/or `jws.WithCompact()` to specify
// explicitly which format to use. If neither or both is specified, the function
// will attempt to autodetect the format. If one or the other is specified,
// only the specified format will be attempted.
//
// On error, returns a jws.ParseError.
func Parse(src []byte, options ...ParseOption) (*Message, error) {
var formats int
for _, option := range options {
switch option.Ident() {
case identSerialization{}:
var v int
if err := option.Value(&v); err != nil {
return nil, parseerr(`failed to retrieve serialization option value: %w`, err)
}
switch v {
case fmtJSON:
formats |= fmtJSON
case fmtCompact:
formats |= fmtCompact
}
}
}
// if format is 0 or both JSON/Compact, auto detect
if v := formats & (fmtJSON | fmtCompact); v == 0 || v == fmtJSON|fmtCompact {
CHECKLOOP:
for i := range src {
r := rune(src[i])
if r >= utf8.RuneSelf {
r, _ = utf8.DecodeRune(src)
}
if !unicode.IsSpace(r) {
if r == tokens.OpenCurlyBracket {
formats = fmtJSON
} else {
formats = fmtCompact
}
break CHECKLOOP
}
}
}
if formats&fmtCompact == fmtCompact {
msg, err := parseCompact(src)
if err != nil {
return nil, parseerr(`failed to parse compact format: %w`, err)
}
return msg, nil
} else if formats&fmtJSON == fmtJSON {
msg, err := parseJSON(src)
if err != nil {
return nil, parseerr(`failed to parse JSON format: %w`, err)
}
return msg, nil
}
return nil, parseerr(`invalid byte sequence`)
}
// ParseString parses contents from the given source and creates a jws.Message
// struct. The input can be in either compact or full JSON serialization.
//
// On error, returns a jws.ParseError.
func ParseString(src string) (*Message, error) {
msg, err := Parse([]byte(src))
if err != nil {
return nil, sparseerr(`failed to parse string: %w`, err)
}
return msg, nil
}
// ParseReader parses contents from the given source and creates a jws.Message
// struct. The input can be in either compact or full JSON serialization.
//
// On error, returns a jws.ParseError.
func ParseReader(src io.Reader) (*Message, error) {
data, err := jwxio.ReadAllFromFiniteSource(src)
if err == nil {
return Parse(data)
}
if !errors.Is(err, jwxio.NonFiniteSourceError()) {
return nil, rparseerr(`failed to read from finite source: %w`, err)
}
rdr := bufio.NewReader(src)
var first rune
for {
r, _, err := rdr.ReadRune()
if err != nil {
return nil, rparseerr(`failed to read rune: %w`, err)
}
if !unicode.IsSpace(r) {
first = r
if err := rdr.UnreadRune(); err != nil {
return nil, rparseerr(`failed to unread rune: %w`, err)
}
break
}
}
var parser func(io.Reader) (*Message, error)
if first == tokens.OpenCurlyBracket {
parser = parseJSONReader
} else {
parser = parseCompactReader
}
m, err := parser(rdr)
if err != nil {
return nil, rparseerr(`failed to parse reader: %w`, err)
}
return m, nil
}
func parseJSONReader(src io.Reader) (result *Message, err error) {
var m Message
if err := json.NewDecoder(src).Decode(&m); err != nil {
return nil, fmt.Errorf(`failed to unmarshal jws message: %w`, err)
}
return &m, nil
}
func parseJSON(data []byte) (result *Message, err error) {
var m Message
if err := json.Unmarshal(data, &m); err != nil {
return nil, fmt.Errorf(`failed to unmarshal jws message: %w`, err)
}
return &m, nil
}
// SplitCompact splits a JWS in compact format and returns its three parts
// separately: protected headers, payload and signature.
// On error, returns a jws.ParseError.
//
// This function will be deprecated in v4. It is a low-level API, and
// thus will be available in the `jwsbb` package.
func SplitCompact(src []byte) ([]byte, []byte, []byte, error) {
hdr, payload, signature, err := jwsbb.SplitCompact(src)
if err != nil {
return nil, nil, nil, parseerr(`%w`, err)
}
return hdr, payload, signature, nil
}
// SplitCompactString splits a JWT and returns its three parts
// separately: protected headers, payload and signature.
// On error, returns a jws.ParseError.
//
// This function will be deprecated in v4. It is a low-level API, and
// thus will be available in the `jwsbb` package.
func SplitCompactString(src string) ([]byte, []byte, []byte, error) {
hdr, payload, signature, err := jwsbb.SplitCompactString(src)
if err != nil {
return nil, nil, nil, parseerr(`%w`, err)
}
return hdr, payload, signature, nil
}
// SplitCompactReader splits a JWT and returns its three parts
// separately: protected headers, payload and signature.
// On error, returns a jws.ParseError.
//
// This function will be deprecated in v4. It is a low-level API, and
// thus will be available in the `jwsbb` package.
func SplitCompactReader(rdr io.Reader) ([]byte, []byte, []byte, error) {
hdr, payload, signature, err := jwsbb.SplitCompactReader(rdr)
if err != nil {
return nil, nil, nil, parseerr(`%w`, err)
}
return hdr, payload, signature, nil
}
// parseCompactReader parses a JWS value serialized via compact serialization.
func parseCompactReader(rdr io.Reader) (m *Message, err error) {
protected, payload, signature, err := SplitCompactReader(rdr)
if err != nil {
return nil, fmt.Errorf(`invalid compact serialization format: %w`, err)
}
return parse(protected, payload, signature)
}
func parseCompact(data []byte) (m *Message, err error) {
protected, payload, signature, err := SplitCompact(data)
if err != nil {
return nil, fmt.Errorf(`invalid compact serialization format: %w`, err)
}
return parse(protected, payload, signature)
}
func parse(protected, payload, signature []byte) (*Message, error) {
decodedHeader, err := base64.Decode(protected)
if err != nil {
return nil, fmt.Errorf(`failed to decode protected headers: %w`, err)
}
hdr := NewHeaders()
if err := json.Unmarshal(decodedHeader, hdr); err != nil {
return nil, fmt.Errorf(`failed to parse JOSE headers: %w`, err)
}
var decodedPayload []byte
b64 := getB64Value(hdr)
if !b64 {
decodedPayload = payload
} else {
v, err := base64.Decode(payload)
if err != nil {
return nil, fmt.Errorf(`failed to decode payload: %w`, err)
}
decodedPayload = v
}
decodedSignature, err := base64.Decode(signature)
if err != nil {
return nil, fmt.Errorf(`failed to decode signature: %w`, err)
}
var msg Message
msg.payload = decodedPayload
msg.signatures = append(msg.signatures, &Signature{
protected: hdr,
signature: decodedSignature,
})
msg.b64 = b64
return &msg, nil
}
type CustomDecoder = json.CustomDecoder
type CustomDecodeFunc = json.CustomDecodeFunc
// RegisterCustomField allows users to specify that a private field
// be decoded as an instance of the specified type. This option has
// a global effect.
//
// For example, suppose you have a custom field `x-birthday`, which
// you want to represent as a string formatted in RFC3339 in JSON,
// but want it back as `time.Time`.
//
// In such case you would register a custom field as follows
//
// jws.RegisterCustomField(`x-birthday`, time.Time{})
//
// Then you can use a `time.Time` variable to extract the value
// of `x-birthday` field, instead of having to use `any`
// and later convert it to `time.Time`
//
// var bday time.Time
// _ = hdr.Get(`x-birthday`, &bday)
//
// If you need a more fine-tuned control over the decoding process,
// you can register a `CustomDecoder`. For example, below shows
// how to register a decoder that can parse RFC1123 format string:
//
// jws.RegisterCustomField(`x-birthday`, jws.CustomDecodeFunc(func(data []byte) (any, error) {
// return time.Parse(time.RFC1123, string(data))
// }))
//
// Please note that use of custom fields can be problematic if you
// are using a library that does not implement MarshalJSON/UnmarshalJSON
// and you try to roundtrip from an object to JSON, and then back to an object.
// For example, in the above example, you can _parse_ time values formatted
// in the format specified in RFC822, but when you convert an object into
// JSON, it will be formatted in RFC3339, because that's what `time.Time`
// likes to do. To avoid this, it's always better to use a custom type
// that wraps your desired type (in this case `time.Time`) and implement
// MarshalJSON and UnmashalJSON.
func RegisterCustomField(name string, object any) {
registry.Register(name, object)
}
// Helpers for signature verification
var rawKeyToKeyType = make(map[reflect.Type]jwa.KeyType)
var keyTypeToAlgorithms = make(map[jwa.KeyType][]jwa.SignatureAlgorithm)
func init() {
rawKeyToKeyType[reflect.TypeFor[[]byte]()] = jwa.OctetSeq()
rawKeyToKeyType[reflect.TypeFor[ed25519.PublicKey]()] = jwa.OKP()
rawKeyToKeyType[reflect.TypeFor[rsa.PublicKey]()] = jwa.RSA()
rawKeyToKeyType[reflect.TypeFor[*rsa.PublicKey]()] = jwa.RSA()
rawKeyToKeyType[reflect.TypeFor[ecdsa.PublicKey]()] = jwa.EC()
rawKeyToKeyType[reflect.TypeFor[*ecdsa.PublicKey]()] = jwa.EC()
addAlgorithmForKeyType(jwa.OKP(), jwa.EdDSA())
for _, alg := range []jwa.SignatureAlgorithm{jwa.HS256(), jwa.HS384(), jwa.HS512()} {
addAlgorithmForKeyType(jwa.OctetSeq(), alg)
}
for _, alg := range []jwa.SignatureAlgorithm{jwa.RS256(), jwa.RS384(), jwa.RS512(), jwa.PS256(), jwa.PS384(), jwa.PS512()} {
addAlgorithmForKeyType(jwa.RSA(), alg)
}
for _, alg := range []jwa.SignatureAlgorithm{jwa.ES256(), jwa.ES384(), jwa.ES512()} {
addAlgorithmForKeyType(jwa.EC(), alg)
}
}
func addAlgorithmForKeyType(kty jwa.KeyType, alg jwa.SignatureAlgorithm) {
keyTypeToAlgorithms[kty] = append(keyTypeToAlgorithms[kty], alg)
}
// AlgorithmsForKey returns the possible signature algorithms that can
// be used for a given key. It only takes in consideration keys/algorithms
// for verification purposes, as this is the only usage where one may need
// dynamically figure out which method to use.
func AlgorithmsForKey(key any) ([]jwa.SignatureAlgorithm, error) {
var kty jwa.KeyType
switch key := key.(type) {
case jwk.Key:
kty = key.KeyType()
case rsa.PublicKey, *rsa.PublicKey, rsa.PrivateKey, *rsa.PrivateKey:
kty = jwa.RSA()
case ecdsa.PublicKey, *ecdsa.PublicKey, ecdsa.PrivateKey, *ecdsa.PrivateKey:
kty = jwa.EC()
case ed25519.PublicKey, ed25519.PrivateKey, *ecdh.PublicKey, ecdh.PublicKey, *ecdh.PrivateKey, ecdh.PrivateKey:
kty = jwa.OKP()
case []byte:
kty = jwa.OctetSeq()
default:
return nil, fmt.Errorf(`unknown key type %T`, key)
}
algs, ok := keyTypeToAlgorithms[kty]
if !ok {
return nil, fmt.Errorf(`unregistered key type %q`, kty)
}
return algs, nil
}
// Settings allows you to set global settings for this JWS operations.
//
// Currently, the only setting available is `jws.WithLegacySigners()`,
// which for various reason is now a no-op.
func Settings(options ...GlobalOption) {
for _, option := range options {
switch option.Ident() {
case identLegacySigners{}:
}
}
}
// VerifyCompactFast is a fast path verification function for JWS messages
// in compact serialization format.
//
// This function is considered experimental, and may change or be removed
// in the future.
//
// VerifyCompactFast performs signature verification on a JWS compact
// serialization without fully parsing the message into a jws.Message object.
// This makes it more efficient for cases where you only need to verify
// the signature and extract the payload, without needing access to headers
// or other JWS metadata.
//
// Returns the original payload that was signed if verification succeeds.
//
// Unlike jws.Verify(), this function requires you to specify the
// algorithm explicitly rather than extracting it from the JWS headers.
// This can be useful for performance-critical applications where the
// algorithm is known in advance.
//
// Since this function avoids doing many checks that jws.Verify would perform,
// you must ensure to perform the necessary checks including ensuring that algorithm is safe to use for your payload yourself.
func VerifyCompactFast(key any, compact []byte, alg jwa.SignatureAlgorithm) ([]byte, error) {
algstr := alg.String()
// Split the serialized JWT into its components
hdr, payload, encodedSig, err := jwsbb.SplitCompact(compact)
if err != nil {
return nil, fmt.Errorf("jwt.verifyFast: failed to split compact: %w", err)
}
signature, err := base64.Decode(encodedSig)
if err != nil {
return nil, fmt.Errorf("jwt.verifyFast: failed to decode signature: %w", err)
}
// Instead of appending, copy the data from hdr/payload
lvb := len(hdr) + 1 + len(payload)
verifyBuf := pool.ByteSlice().GetCapacity(lvb)
verifyBuf = verifyBuf[:lvb]
copy(verifyBuf, hdr)
verifyBuf[len(hdr)] = tokens.Period
copy(verifyBuf[len(hdr)+1:], payload)
defer pool.ByteSlice().Put(verifyBuf)
// Verify the signature
if verifier2, err := VerifierFor(alg); err == nil {
if err := verifier2.Verify(key, verifyBuf, signature); err != nil {
return nil, verifyError{verificationError{fmt.Errorf("jwt.VerifyCompact: signature verification failed for %s: %w", algstr, err)}}
}
} else {
legacyVerifier, err := NewVerifier(alg)
if err != nil {
return nil, verifyerr("jwt.VerifyCompact: failed to create verifier for %s: %w", algstr, err)
}
if err := legacyVerifier.Verify(verifyBuf, signature, key); err != nil {
return nil, verifyError{verificationError{fmt.Errorf("jwt.VerifyCompact: signature verification failed for %s: %w", algstr, err)}}
}
}
decoded, err := base64.Decode(payload)
if err != nil {
return nil, verifyerr("jwt.VerifyCompact: failed to decode payload: %w", err)
}
return decoded, nil
}