auth/vendor/github.com/lestrrat-go/jwx/v2/jws/options.go

227 lines
7.8 KiB
Go

package jws
import (
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/option"
)
type identHeaders struct{}
type identInsecureNoSignature struct{}
// WithHeaders allows you to specify extra header values to include in the
// final JWS message
func WithHeaders(h Headers) SignOption {
return &signOption{option.New(identHeaders{}, h)}
}
// WithJSON specifies that the result of `jws.Sign()` is serialized in
// JSON format.
//
// If you pass multiple keys to `jws.Sign()`, it will fail unless
// you also pass this option.
func WithJSON(options ...WithJSONSuboption) SignOption {
var pretty bool
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identPretty{}:
pretty = option.Value().(bool)
}
}
format := fmtJSON
if pretty {
format = fmtJSONPretty
}
return &signOption{option.New(identSerialization{}, format)}
}
type withKey struct {
alg jwa.KeyAlgorithm
key interface{}
protected Headers
public Headers
}
// This exist as escape hatches to modify the header values after the fact
func (w *withKey) Protected(v Headers) Headers {
if w.protected == nil && v != nil {
w.protected = v
}
return w.protected
}
// WithKey is used to pass a static algorithm/key pair to either `jws.Sign()` or `jws.Verify()`.
//
// The `alg` parameter is the identifier for the signature algorithm that should be used.
// It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.SignatureAlgorithm`
// types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly
// passed to the option. If you specify other algorithm types such as `jwa.ContentEncryptionAlgorithm`,
// then you will get an error when `jws.Sign()` or `jws.Verify()` is executed.
//
// The `alg` parameter cannot be "none" (jwa.NoSignature) for security reasons.
// You will have to use a separate, more explicit option to allow the use of "none"
// algorithm.
//
// The algorithm specified in the `alg` parameter MUST be able to support
// the type of key you provided, otherwise an error is returned.
//
// Any of the followin is accepted for the `key` parameter:
// * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc)
// * A crypto.Signer
// * A jwk.Key
//
// Note that due to technical reasons, this library is NOT able to differentiate
// between a valid/invalid key for given algorithm if the key implements crypto.Signer
// and the key is from an external library. For example, while we can tell that it is
// invalid to use `jwk.WithKey(jwa.RSA256, ecdsaPrivateKey)` because the key is
// presumably from `crypto/ecdsa` or this library, if you use a KMS wrapper
// that implements crypto.Signer that is outside of the go standard library or this
// library, we will not be able to properly catch the misuse of such keys --
// the output will happily generate an ECDSA signature even in the presence of
// `jwa.RSA256`
//
// A `crypto.Signer` is used when the private part of a key is
// kept in an inaccessible location, such as hardware.
// `crypto.Signer` is currently supported for RSA, ECDSA, and EdDSA
// family of algorithms. You may consider using `github.com/jwx-go/crypto-signer`
// if you would like to use keys stored in GCP/AWS KMS services.
//
// If the key is a jwk.Key and the key contains a key ID (`kid` field),
// then it is added to the protected header generated by the signature.
//
// `jws.WithKey()` can furher accept suboptions to change signing behavior
// when used with `jws.Sign()`. `jws.WithProtected()` and `jws.WithPublic()`
// can be passed to specify JWS headers that should be used whe signing.
//
// If the protected headers contain "b64" field, then the boolean value for the field
// is respected when serializing. That is, if you specify a header with
// `{"b64": false}`, then the payload is not base64 encoded.
//
// These suboptions are ignored when the `jws.WithKey()` option is used with `jws.Verify()`.
func WithKey(alg jwa.KeyAlgorithm, key interface{}, options ...WithKeySuboption) SignVerifyOption {
// Implementation note: this option is shared between Sign() and
// Verify(). As such we don't create a KeyProvider here because
// if used in Sign() we would be doing something else.
var protected, public Headers
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identProtectedHeaders{}:
protected = option.Value().(Headers)
case identPublicHeaders{}:
public = option.Value().(Headers)
}
}
return &signVerifyOption{
option.New(identKey{}, &withKey{
alg: alg,
key: key,
protected: protected,
public: public,
}),
}
}
// WithKeySet specifies a JWKS (jwk.Set) to use for verification.
//
// Because a JWKS can contain multiple keys and this library cannot tell
// which one of the keys should be used for verification, we by default
// require that both `alg` and `kid` fields in the JWS _and_ the
// key match before a key is considered to be used.
//
// There are ways to override this behavior, but they must be explicitly
// specified by the caller.
//
// To work with keys/JWS messages not having a `kid` field, you may specify
// the suboption `WithKeySetRequired` via `jws.WithKeySetSuboption(jws.WithKeySetRequireKid(false))`.
// This will allow the library to proceed without having to match the `kid` field.
//
// However, it will still check if the `alg` fields in the JWS message and the key(s)
// match. If you must work with JWS messages that do not have an `alg` field,
// you will need to use `jws.WithKeySetSuboption(jws.WithInferAlgorithm(true))`.
//
// See the documentation for `WithInferAlgorithm()` for more details.
func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) VerifyOption {
requireKid := true
var useDefault, inferAlgorithm, multipleKeysPerKeyID bool
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identRequireKid{}:
requireKid = option.Value().(bool)
case identUseDefault{}:
useDefault = option.Value().(bool)
case identMultipleKeysPerKeyID{}:
multipleKeysPerKeyID = option.Value().(bool)
case identInferAlgorithmFromKey{}:
inferAlgorithm = option.Value().(bool)
}
}
return WithKeyProvider(&keySetProvider{
set: set,
requireKid: requireKid,
useDefault: useDefault,
multipleKeysPerKeyID: multipleKeysPerKeyID,
inferAlgorithm: inferAlgorithm,
})
}
func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption {
if f == nil {
f = jwk.FetchFunc(jwk.Fetch)
}
// the option MUST start with a "disallow no whitelist" to force
// users provide a whitelist
options = append(append([]jwk.FetchOption(nil), jwk.WithFetchWhitelist(allowNoneWhitelist)), options...)
return WithKeyProvider(jkuProvider{
fetcher: f,
options: options,
})
}
type withInsecureNoSignature struct {
protected Headers
}
// This exist as escape hatches to modify the header values after the fact
func (w *withInsecureNoSignature) Protected(v Headers) Headers {
if w.protected == nil && v != nil {
w.protected = v
}
return w.protected
}
// WithInsecureNoSignature creates an option that allows the user to use the
// "none" signature algorithm.
//
// Please note that this is insecure, and should never be used in production
// (this is exactly why specifying "none"/jwa.NoSignature to `jws.WithKey()`
// results in an error when `jws.Sign()` is called -- we do not allow using
// "none" by accident)
//
// TODO: create specific sub-option set for this option
func WithInsecureNoSignature(options ...WithKeySuboption) SignOption {
var protected Headers
for _, option := range options {
//nolint:forcetypeassert
switch option.Ident() {
case identProtectedHeaders{}:
protected = option.Value().(Headers)
}
}
return &signOption{
option.New(identInsecureNoSignature{},
&withInsecureNoSignature{
protected: protected,
},
),
}
}