Initial QSfera import
This commit is contained in:
+259
@@ -0,0 +1,259 @@
|
||||
package jws
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
"github.com/lestrrat-go/option/v2"
|
||||
)
|
||||
|
||||
type identInsecureNoSignature struct{}
|
||||
|
||||
// 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) SignVerifyParseOption {
|
||||
var pretty bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identPretty{}:
|
||||
if err := option.Value(&pretty); err != nil {
|
||||
panic(`jws.WithJSON() option must be of type bool`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format := fmtJSON
|
||||
if pretty {
|
||||
format = fmtJSONPretty
|
||||
}
|
||||
return &signVerifyParseOption{option.New(identSerialization{}, format)}
|
||||
}
|
||||
|
||||
type withKey struct {
|
||||
alg jwa.KeyAlgorithm
|
||||
key any
|
||||
protected Headers
|
||||
public Headers
|
||||
}
|
||||
|
||||
// Protected exists as an escape hatch 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.KeyEncryptionAlgorithm`,
|
||||
// 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 (WithInsecureNoSignature).
|
||||
//
|
||||
// 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 following 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 further 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 any, 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 {
|
||||
switch option.Ident() {
|
||||
case identProtectedHeaders{}:
|
||||
if err := option.Value(&protected); err != nil {
|
||||
panic(`jws.WithKey() option must be of type Headers`)
|
||||
}
|
||||
case identPublicHeaders{}:
|
||||
if err := option.Value(&public); err != nil {
|
||||
panic(`jws.WithKey() option must be of type 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.WithKey(key, jws.WithRequireKid(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.WithKeySet(key, 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 {
|
||||
switch option.Ident() {
|
||||
case identRequireKid{}:
|
||||
if err := option.Value(&requireKid); err != nil {
|
||||
panic(`jws.WithKeySet() option must be of type bool`)
|
||||
}
|
||||
case identUseDefault{}:
|
||||
if err := option.Value(&useDefault); err != nil {
|
||||
panic(`jws.WithKeySet() option must be of type bool`)
|
||||
}
|
||||
case identMultipleKeysPerKeyID{}:
|
||||
if err := option.Value(&multipleKeysPerKeyID); err != nil {
|
||||
panic(`jws.WithKeySet() option must be of type bool`)
|
||||
}
|
||||
case identInferAlgorithmFromKey{}:
|
||||
if err := option.Value(&inferAlgorithm); err != nil {
|
||||
panic(`jws.WithKeySet() option must be of type bool`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WithKeyProvider(&keySetProvider{
|
||||
set: set,
|
||||
requireKid: requireKid,
|
||||
useDefault: useDefault,
|
||||
multipleKeysPerKeyID: multipleKeysPerKeyID,
|
||||
inferAlgorithm: inferAlgorithm,
|
||||
})
|
||||
}
|
||||
|
||||
// WithVerifyAuto enables automatic verification of the signature using the JWKS specified in
|
||||
// the `jku` header. Note that by default this option will _reject_ any jku
|
||||
// provided by the JWS message. Read on for details.
|
||||
//
|
||||
// The JWKS is retrieved by the `jwk.Fetcher` specified in the first argument.
|
||||
// If the fetcher object is nil, the default fetcher, which is the `jwk.Fetch()`
|
||||
// function (wrapped in the `jwk.FetchFunc` type) is used.
|
||||
//
|
||||
// The remaining arguments are passed to the `(jwk.Fetcher).Fetch` method
|
||||
// when the JWKS is retrieved.
|
||||
//
|
||||
// jws.WithVerifyAuto(nil) // uses jwk.Fetch
|
||||
// jws.WithVerifyAuto(jwk.NewCachedFetcher(...)) // uses cached fetcher
|
||||
// jws.WithVerifyAuto(myFetcher) // use your custom fetcher
|
||||
//
|
||||
// By default a whitelist that disallows all URLs is added to the options
|
||||
// passed to the fetcher. You must explicitly specify a whitelist that allows
|
||||
// the URLs you trust. This default behavior is provided because by design
|
||||
// of the JWS specification it is the/ caller's responsibility to verify if
|
||||
// the URL specified in the `jku` header can be trusted -- thus by default
|
||||
// we trust nothing.
|
||||
//
|
||||
// Users are free to specify an open whitelist if they so choose, but this must
|
||||
// be explicitly done:
|
||||
//
|
||||
// jws.WithVerifyAuto(nil, jwk.WithFetchWhitelist(jwk.InsecureWhitelist()))
|
||||
//
|
||||
// You can also use `jwk.CachedFetcher` to use cached JWKS objects, but do note
|
||||
// that this object is not really designed to accommodate a large set of
|
||||
// arbitrary URLs. Use `jwk.CachedFetcher` as the first argument if you only
|
||||
// have a small set of URLs that you trust. For anything more complex, you should
|
||||
// implement your own `jwk.Fetcher` object.
|
||||
func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption {
|
||||
// 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
|
||||
}
|
||||
|
||||
// Protected exists as an escape hatch 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 suboption set for this option
|
||||
func WithInsecureNoSignature(options ...WithKeySuboption) SignOption {
|
||||
var protected Headers
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identProtectedHeaders{}:
|
||||
if err := option.Value(&protected); err != nil {
|
||||
panic(`jws.WithInsecureNoSignature() option must be of type Headers`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &signOption{
|
||||
option.New(identInsecureNoSignature{},
|
||||
&withInsecureNoSignature{
|
||||
protected: protected,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user