160 lines
5.2 KiB
Go
160 lines
5.2 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{}
|
|
|
|
// 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 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
|
|
//
|
|
// 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 whe 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.
|
|
//
|
|
// By default both `alg` and `kid` fields in the JWS _and_ the
|
|
// key must match for a key in the JWKS to be considered to be used.
|
|
//
|
|
// The behavior can be tweaked by using the `jws.WithKeySetSuboption`
|
|
// suboption types.
|
|
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,
|
|
})
|
|
}
|