666 lines
22 KiB
Go
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
|
|
}
|