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 }