github.com/lestrrat-go/jwx/v2@v2.0.21/jws/options.go (about)

     1  package jws
     2  
     3  import (
     4  	"github.com/lestrrat-go/jwx/v2/jwa"
     5  	"github.com/lestrrat-go/jwx/v2/jwk"
     6  	"github.com/lestrrat-go/option"
     7  )
     8  
     9  type identHeaders struct{}
    10  type identInsecureNoSignature struct{}
    11  
    12  // WithHeaders allows you to specify extra header values to include in the
    13  // final JWS message
    14  func WithHeaders(h Headers) SignOption {
    15  	return &signOption{option.New(identHeaders{}, h)}
    16  }
    17  
    18  // WithJSON specifies that the result of `jws.Sign()` is serialized in
    19  // JSON format.
    20  //
    21  // If you pass multiple keys to `jws.Sign()`, it will fail unless
    22  // you also pass this option.
    23  func WithJSON(options ...WithJSONSuboption) SignVerifyParseOption {
    24  	var pretty bool
    25  	for _, option := range options {
    26  		//nolint:forcetypeassert
    27  		switch option.Ident() {
    28  		case identPretty{}:
    29  			pretty = option.Value().(bool)
    30  		}
    31  	}
    32  
    33  	format := fmtJSON
    34  	if pretty {
    35  		format = fmtJSONPretty
    36  	}
    37  	return &signVerifyParseOption{option.New(identSerialization{}, format)}
    38  }
    39  
    40  type withKey struct {
    41  	alg       jwa.KeyAlgorithm
    42  	key       interface{}
    43  	protected Headers
    44  	public    Headers
    45  }
    46  
    47  // This exist as escape hatches to modify the header values after the fact
    48  func (w *withKey) Protected(v Headers) Headers {
    49  	if w.protected == nil && v != nil {
    50  		w.protected = v
    51  	}
    52  	return w.protected
    53  }
    54  
    55  // WithKey is used to pass a static algorithm/key pair to either `jws.Sign()` or `jws.Verify()`.
    56  //
    57  // The `alg` parameter is the identifier for the signature algorithm that should be used.
    58  // It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.SignatureAlgorithm`
    59  // types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly
    60  // passed to the option. If you specify other algorithm types such as `jwa.ContentEncryptionAlgorithm`,
    61  // then you will get an error when `jws.Sign()` or `jws.Verify()` is executed.
    62  //
    63  // The `alg` parameter cannot be "none" (jwa.NoSignature) for security reasons.
    64  // You will have to use a separate, more explicit option to allow the use of "none"
    65  // algorithm.
    66  //
    67  // The algorithm specified in the `alg` parameter MUST be able to support
    68  // the type of key you provided, otherwise an error is returned.
    69  //
    70  // Any of the followin is accepted for the `key` parameter:
    71  // * A "raw" key (e.g. rsa.PrivateKey, ecdsa.PrivateKey, etc)
    72  // * A crypto.Signer
    73  // * A jwk.Key
    74  //
    75  // Note that due to technical reasons, this library is NOT able to differentiate
    76  // between a valid/invalid key for given algorithm if the key implements crypto.Signer
    77  // and the key is from an external library. For example, while we can tell that it is
    78  // invalid to use `jwk.WithKey(jwa.RSA256, ecdsaPrivateKey)` because the key is
    79  // presumably from `crypto/ecdsa` or this library, if you use a KMS wrapper
    80  // that implements crypto.Signer that is outside of the go standard library or this
    81  // library, we will not be able to properly catch the misuse of such keys --
    82  // the output will happily generate an ECDSA signature even in the presence of
    83  // `jwa.RSA256`
    84  //
    85  // A `crypto.Signer` is used when the private part of a key is
    86  // kept in an inaccessible location, such as hardware.
    87  // `crypto.Signer` is currently supported for RSA, ECDSA, and EdDSA
    88  // family of algorithms. You may consider using `github.com/jwx-go/crypto-signer`
    89  // if you would like to use keys stored in GCP/AWS KMS services.
    90  //
    91  // If the key is a jwk.Key and the key contains a key ID (`kid` field),
    92  // then it is added to the protected header generated by the signature.
    93  //
    94  // `jws.WithKey()` can furher accept suboptions to change signing behavior
    95  // when used with `jws.Sign()`. `jws.WithProtected()` and `jws.WithPublic()`
    96  // can be passed to specify JWS headers that should be used whe signing.
    97  //
    98  // If the protected headers contain "b64" field, then the boolean value for the field
    99  // is respected when serializing. That is, if you specify a header with
   100  // `{"b64": false}`, then the payload is not base64 encoded.
   101  //
   102  // These suboptions are ignored when the `jws.WithKey()` option is used with `jws.Verify()`.
   103  func WithKey(alg jwa.KeyAlgorithm, key interface{}, options ...WithKeySuboption) SignVerifyOption {
   104  	// Implementation note: this option is shared between Sign() and
   105  	// Verify(). As such we don't create a KeyProvider here because
   106  	// if used in Sign() we would be doing something else.
   107  	var protected, public Headers
   108  	for _, option := range options {
   109  		//nolint:forcetypeassert
   110  		switch option.Ident() {
   111  		case identProtectedHeaders{}:
   112  			protected = option.Value().(Headers)
   113  		case identPublicHeaders{}:
   114  			public = option.Value().(Headers)
   115  		}
   116  	}
   117  
   118  	return &signVerifyOption{
   119  		option.New(identKey{}, &withKey{
   120  			alg:       alg,
   121  			key:       key,
   122  			protected: protected,
   123  			public:    public,
   124  		}),
   125  	}
   126  }
   127  
   128  // WithKeySet specifies a JWKS (jwk.Set) to use for verification.
   129  //
   130  // Because a JWKS can contain multiple keys and this library cannot tell
   131  // which one of the keys should be used for verification, we by default
   132  // require that both `alg` and `kid` fields in the JWS _and_ the
   133  // key match before a key is considered to be used.
   134  //
   135  // There are ways to override this behavior, but they must be explicitly
   136  // specified by the caller.
   137  //
   138  // To work with keys/JWS messages not having a `kid` field, you may specify
   139  // the suboption `WithKeySetRequired` via `jws.WithKey(key, jws.WithRequireKid(false))`.
   140  // This will allow the library to proceed without having to match the `kid` field.
   141  //
   142  // However, it will still check if the `alg` fields in the JWS message and the key(s)
   143  // match. If you must work with JWS messages that do not have an `alg` field,
   144  // you will need to use `jws.WithKeySet(key, jws.WithInferAlgorithm(true))`.
   145  //
   146  // See the documentation for `WithInferAlgorithm()` for more details.
   147  func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) VerifyOption {
   148  	requireKid := true
   149  	var useDefault, inferAlgorithm, multipleKeysPerKeyID bool
   150  	for _, option := range options {
   151  		//nolint:forcetypeassert
   152  		switch option.Ident() {
   153  		case identRequireKid{}:
   154  			requireKid = option.Value().(bool)
   155  		case identUseDefault{}:
   156  			useDefault = option.Value().(bool)
   157  		case identMultipleKeysPerKeyID{}:
   158  			multipleKeysPerKeyID = option.Value().(bool)
   159  		case identInferAlgorithmFromKey{}:
   160  			inferAlgorithm = option.Value().(bool)
   161  		}
   162  	}
   163  
   164  	return WithKeyProvider(&keySetProvider{
   165  		set:                  set,
   166  		requireKid:           requireKid,
   167  		useDefault:           useDefault,
   168  		multipleKeysPerKeyID: multipleKeysPerKeyID,
   169  		inferAlgorithm:       inferAlgorithm,
   170  	})
   171  }
   172  
   173  func WithVerifyAuto(f jwk.Fetcher, options ...jwk.FetchOption) VerifyOption {
   174  	if f == nil {
   175  		f = jwk.FetchFunc(jwk.Fetch)
   176  	}
   177  
   178  	// the option MUST start with a "disallow no whitelist" to force
   179  	// users provide a whitelist
   180  	options = append(append([]jwk.FetchOption(nil), jwk.WithFetchWhitelist(allowNoneWhitelist)), options...)
   181  
   182  	return WithKeyProvider(jkuProvider{
   183  		fetcher: f,
   184  		options: options,
   185  	})
   186  }
   187  
   188  type withInsecureNoSignature struct {
   189  	protected Headers
   190  }
   191  
   192  // This exist as escape hatches to modify the header values after the fact
   193  func (w *withInsecureNoSignature) Protected(v Headers) Headers {
   194  	if w.protected == nil && v != nil {
   195  		w.protected = v
   196  	}
   197  	return w.protected
   198  }
   199  
   200  // WithInsecureNoSignature creates an option that allows the user to use the
   201  // "none" signature algorithm.
   202  //
   203  // Please note that this is insecure, and should never be used in production
   204  // (this is exactly why specifying "none"/jwa.NoSignature to `jws.WithKey()`
   205  // results in an error when `jws.Sign()` is called -- we do not allow using
   206  // "none" by accident)
   207  //
   208  // TODO: create specific sub-option set for this option
   209  func WithInsecureNoSignature(options ...WithKeySuboption) SignOption {
   210  	var protected Headers
   211  	for _, option := range options {
   212  		//nolint:forcetypeassert
   213  		switch option.Ident() {
   214  		case identProtectedHeaders{}:
   215  			protected = option.Value().(Headers)
   216  		}
   217  	}
   218  
   219  	return &signOption{
   220  		option.New(identInsecureNoSignature{},
   221  			&withInsecureNoSignature{
   222  				protected: protected,
   223  			},
   224  		),
   225  	}
   226  }