github.com/lestrrat-go/jwx/v2@v2.0.21/docs/02-jws.md (about) 1 # Working with JWS 2 3 In this document we describe how to work with JWS using [`github.com/lestrrat-go/jwx/v2/jws`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws) 4 5 * [Parsing](#parsing) 6 * [Parse a JWS message stored in memory](#parse-a-jws-message-stored-in-memory) 7 * [Parse a JWS message stored in a file](#parse-a-jws-message-stored-in-a-file) 8 * [Parse a JWS message and access JWS headers](#parse-a-jws-message-and-access-jws-headers) 9 * [Signing](#signing) 10 * [Generating a JWS message in compact serialization format](#generating-a-jws-message-in-compact-serialization-format) 11 * [Generating a JWS message in JSON serialization format](#generating-a-jws-message-in-json-serialization-format) 12 * [Generating a JWS message with detached payload](#generating-a-jws-message-with-detached-payload) 13 * [Using cloud KMS services](#using-cloud-kms-services) 14 * [Including arbitrary headers](#including-arbitrary-headers) 15 * [Verifying](#verifying) 16 * [Verification using a single key](#verification-using-a-single-key) 17 * [Verification using a JWKS](#verification-using-a-jwks) 18 * [Verification using a detached payload](#verification-using-a-detached-payload) 19 * [Verification using `jku`](#verification-using-jku) 20 * [Using a custom signing/verification algorithm](#using-a-customg-signingverification-algorithm) 21 * [Enabling ES256K](#enabling-es256k) 22 23 # Parsing 24 25 Parsing a JWS message means taking either a JWS message serialized in JSON or Compact form 26 and loading it into a `jws.Message` object. No verification is performed, and therefore 27 you cannot "trust" the contents in the same way that a verified message could be trusted. 28 29 Also, be aware that a `jws.Message` is not meant to be used for either signing or 30 verification. It is only provided such that it can be inspected -- there is no way 31 to sign or verify using a parsed `jws.Message`. To do this, you would need to use 32 `jws.Sign()` or `jws.Message()`. 33 34 ## Parse a JWS message stored in memory 35 36 You can parse a JWS message in memory stored as `[]byte` into a [`jws.Message`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#Message) object. In this mode, there is no verification performed. 37 38 <!-- INCLUDE(examples/jws_parse_example_test.go) --> 39 ```go 40 package examples_test 41 42 import ( 43 "encoding/json" 44 "fmt" 45 "os" 46 47 "github.com/lestrrat-go/jwx/v2/jws" 48 ) 49 50 func ExampleJWS_Parse() { 51 const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo` 52 53 msg, err := jws.Parse([]byte(src)) 54 if err != nil { 55 fmt.Printf("failed to parse JWS message: %s\n", err) 56 return 57 } 58 59 json.NewEncoder(os.Stdout).Encode(msg) 60 // OUTPUT: 61 // {"payload":"TG9yZW0gaXBzdW0","protected":"eyJhbGciOiJIUzI1NiJ9","signature":"idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo"} 62 } 63 ``` 64 source: [examples/jws_parse_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_parse_example_test.go) 65 <!-- END INCLUDE --> 66 67 ## Parse a JWS message stored in a file 68 69 To parse a JWS stored in a file, use [`jws.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#ReadFile). [`jws.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#ReadFile) accepts the same options as [`jws.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#Parse). 70 71 <!-- INCLUDE(examples/jws_readfile_example_test.go) --> 72 ```go 73 package examples_test 74 75 import ( 76 "encoding/json" 77 "fmt" 78 "os" 79 80 "github.com/lestrrat-go/jwx/v2/jws" 81 ) 82 83 func ExampleJWS_ReadFile() { 84 const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo` 85 f, err := os.CreateTemp(``, `jws_readfile-*.jws`) 86 if err != nil { 87 fmt.Printf("failed to create temporary file: %s\n", err) 88 return 89 } 90 defer os.Remove(f.Name()) 91 92 fmt.Fprintf(f, src) 93 f.Close() 94 95 msg, err := jws.ReadFile(f.Name()) 96 if err != nil { 97 fmt.Printf("failed to parse JWS message: %s\n", err) 98 return 99 } 100 101 json.NewEncoder(os.Stdout).Encode(msg) 102 103 // OUTPUT: 104 // {"payload":"TG9yZW0gaXBzdW0","protected":"eyJhbGciOiJIUzI1NiJ9","signature":"idbECxA8ZhQbU0ddZmzdRZxQmHjwvw77lT2bwqGgNMo"} 105 } 106 ``` 107 source: [examples/jws_readfile_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_readfile_example_test.go) 108 <!-- END INCLUDE --> 109 110 ## Parse a JWS message and access JWS headers 111 112 Note: If you are considering using JWS header fields to decide on which key to use for verification, consider [using a `jwt.KeyProvider`](./01-jwt.md#parse-and-verify-a-jwt-using-arbitrary-keys). 113 114 While a lot of documentation in the wild treat as if a JWT message encoded in base64 is... a JWT message, in truth it is a JWT message enveloped in a JWS message. Therefore in order to access the JWS headers of a JWT message you will need to work witha `jws.Message` object, which you can obtain from parsing the JWS payload. You will need to understand [the structure of a generic JWS message](https://www.rfc-editor.org/rfc/rfc7515#section-7.2.1). 115 116 Below sample code extracts the `kid` field of a single-signature JWS message: 117 118 <!-- INCLUDE(examples/jws_use_jws_header_test.go) --> 119 ```go 120 package examples_test 121 122 import ( 123 "fmt" 124 125 "github.com/lestrrat-go/jwx/v2/jwa" 126 "github.com/lestrrat-go/jwx/v2/jwk" 127 "github.com/lestrrat-go/jwx/v2/jws" 128 "github.com/lestrrat-go/jwx/v2/jwt" 129 ) 130 131 func ExampleJWS_UseJWSHeader() { 132 key, err := jwk.FromRaw([]byte(`abracadabra`)) 133 if err != nil { 134 fmt.Printf(`failed to create new symmetric key: %s`, err) 135 return 136 } 137 key.Set(jws.KeyIDKey, `secret-key`) 138 139 tok, err := jwt.NewBuilder(). 140 Issuer(`github.com/lestrrat-go/jwx`). 141 Build() 142 if err != nil { 143 fmt.Printf(`failed to build token: %s`, err) 144 return 145 } 146 147 signed, err := jwt.Sign(tok, jwt.WithKey(jwa.HS256, key)) 148 if err != nil { 149 fmt.Printf(`failed to sign token: %s`, err) 150 return 151 } 152 153 msg, err := jws.Parse(signed) 154 if err != nil { 155 fmt.Printf(`failed to parse serialized JWT: %s`, err) 156 return 157 } 158 159 // While JWT enveloped with JWS in compact format only has 1 signature, 160 // a generic JWS message may have multiple signatures. Therefore we 161 // need to access the first element 162 fmt.Printf("%q\n", msg.Signatures()[0].ProtectedHeaders().KeyID()) 163 // OUTPUT: 164 // "secret-key" 165 } 166 ``` 167 source: [examples/jws_use_jws_header_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_use_jws_header_test.go) 168 <!-- END INCLUDE --> 169 170 # Signing 171 172 ## Generating a JWS message in compact serialization format 173 174 To sign an arbitrary payload as a JWS message in compact serialization format, use `jwt.Sign()`. 175 176 Note that this would be [slightly different if you are signing JWTs](01-jwt.md#serialize-using-jws), as you would be 177 using functions from the `jwt` package instead of `jws`. 178 179 <!-- INCLUDE(examples/jws_sign_example_test.go) --> 180 ```go 181 package examples_test 182 183 import ( 184 "fmt" 185 186 "github.com/lestrrat-go/jwx/v2/jwa" 187 "github.com/lestrrat-go/jwx/v2/jwk" 188 "github.com/lestrrat-go/jwx/v2/jws" 189 ) 190 191 func ExampleJWS_Sign() { 192 key, err := jwk.FromRaw([]byte(`abracadabra`)) 193 if err != nil { 194 fmt.Printf("failed to create key: %s\n", err) 195 return 196 } 197 198 buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.HS256, key)) 199 if err != nil { 200 fmt.Printf("failed to sign payload: %s\n", err) 201 return 202 } 203 fmt.Printf("%s\n", buf) 204 // OUTPUT: 205 // eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0 206 } 207 ``` 208 source: [examples/jws_sign_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_sign_example_test.go) 209 <!-- END INCLUDE --> 210 211 ## Generating a JWS message in JSON serialization format 212 213 Generally the only time you need to use a JSON serialization format is when you have to generate multiple signatures for a given payload using multiple signing algorithms and keys. 214 215 When this need arises, use the [`jws.Sign()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#Sign) function with the `jws.WithJSON()` option and multiple `jws.WithKey()` options: 216 217 <!-- INCLUDE(examples/jws_sign_json_example_test.go) --> 218 ```go 219 package examples_test 220 221 import ( 222 "fmt" 223 224 "github.com/lestrrat-go/jwx/v2/jwa" 225 "github.com/lestrrat-go/jwx/v2/jwk" 226 "github.com/lestrrat-go/jwx/v2/jws" 227 ) 228 229 func ExampleJWS_SignJSON() { 230 var keys []jwk.Key 231 232 for i := 0; i < 3; i++ { 233 key, err := jwk.FromRaw([]byte(fmt.Sprintf(`abracadabra-%d`, i))) 234 if err != nil { 235 fmt.Printf("failed to create key: %s\n", err) 236 return 237 } 238 keys = append(keys, key) 239 } 240 241 options := []jws.SignOption{jws.WithJSON()} 242 for _, key := range keys { 243 options = append(options, jws.WithKey(jwa.HS256, key)) 244 } 245 246 buf, err := jws.Sign([]byte("Lorem ipsum"), options...) 247 if err != nil { 248 fmt.Printf("failed to sign payload: %s\n", err) 249 return 250 } 251 fmt.Printf("%s\n", buf) 252 // OUTPUT: 253 // {"payload":"TG9yZW0gaXBzdW0","signatures":[{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"bCQtU2y4PEnG78dUN-tXea8YEwhBAzLX7ZEYlRVtX_g"},{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"0ovW79M_bbaRDBrBLaNKN7rgJeXaSRAnu5rhAuRXBR4"},{"protected":"eyJhbGciOiJIUzI1NiJ9","signature":"ZkUzwlK5E6LFKsYEIyUvskOKLMDxE0MvvkvNrwINNWE"}]} 254 } 255 ``` 256 source: [examples/jws_sign_json_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_sign_json_example_test.go) 257 <!-- END INCLUDE --> 258 259 ## Generating a JWS message with detached payload 260 261 JWS messages can be constructed with a detached payload. Use the `jws.WithDetachedPayload()` option to 262 create a JWS message with the message detached from the result. 263 264 <!-- INCLUDE(examples/jws_sign_detached_payload_example_test.go) --> 265 ```go 266 package examples_test 267 268 import ( 269 "fmt" 270 271 "github.com/lestrrat-go/jwx/v2/jwa" 272 "github.com/lestrrat-go/jwx/v2/jwk" 273 "github.com/lestrrat-go/jwx/v2/jws" 274 ) 275 276 func ExampleJWS_SignDetachedPayload() { 277 payload := `$.02` 278 279 key, err := jwk.FromRaw([]byte(`abracadabra`)) 280 if err != nil { 281 fmt.Printf("failed to create symmetric key: %s\n", err) 282 return 283 } 284 285 serialized, err := jws.Sign(nil, jws.WithKey(jwa.HS256, key), jws.WithDetachedPayload([]byte(payload))) 286 if err != nil { 287 fmt.Printf("failed to sign payload: %s\n", err) 288 return 289 } 290 291 fmt.Printf("%s\n", serialized) 292 // OUTPUT: 293 // eyJhbGciOiJIUzI1NiJ9..H14oXKwyvAsl0IbBLjw9tLxNIoYisuIyb_oDV4-30Vk 294 } 295 ``` 296 source: [examples/jws_sign_detached_payload_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_sign_detached_payload_example_test.go) 297 <!-- END INCLUDE --> 298 299 ## Including arbitrary headers 300 301 By default, only some header fields are included in the result from `jws.Sign()`. 302 If you want to include more header fields in the resulting JWS, you will have to provide them via the `jws.WithProtectedHeaders()` option. 303 304 While `jws.WithPublicHeaders()` exists to keep API symmetric and complete, for most 305 cases you only want to use `jws.WithProtectedHeaders()` 306 307 <!-- INCLUDE(examples/jws_sign_with_headers_example_test.go) --> 308 ```go 309 package examples_test 310 311 import ( 312 "fmt" 313 314 "github.com/lestrrat-go/jwx/v2/jwa" 315 "github.com/lestrrat-go/jwx/v2/jwk" 316 "github.com/lestrrat-go/jwx/v2/jws" 317 ) 318 319 func ExampleJWS_SignWithHeaders() { 320 key, err := jwk.FromRaw([]byte(`abracadabra`)) 321 if err != nil { 322 fmt.Printf("failed to create key: %s\n", err) 323 return 324 } 325 326 hdrs := jws.NewHeaders() 327 hdrs.Set(`x-example`, true) 328 buf, err := jws.Sign([]byte("Lorem ipsum"), jws.WithKey(jwa.HS256, key, jws.WithProtectedHeaders(hdrs))) 329 if err != nil { 330 fmt.Printf("failed to sign payload: %s\n", err) 331 return 332 } 333 fmt.Printf("%s\n", buf) 334 // OUTPUT: 335 // eyJhbGciOiJIUzI1NiIsIngtZXhhbXBsZSI6dHJ1ZX0.TG9yZW0gaXBzdW0.9nIX0hN7u1b97UcjmrVvd5y1ubkQp_1gz1V3Mkkcm14 336 } 337 ``` 338 source: [examples/jws_sign_with_headers_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_sign_with_headers_example_test.go) 339 <!-- END INCLUDE --> 340 341 ## Using cloud KMS services 342 343 If you want to use cloud KMSes such as AWS KMS to sign and verify payloads, look for an object that implements 344 `crypto.Signer`. There are some [implementations written for this module](https://github.com/jwx-go/crypto-signer). 345 346 Event if you cannot find an implementation that you are looking for in the above repository, any other implementation that implements `crypto.Signer` should work. 347 348 # Verifying 349 350 ## Verification using a single key 351 352 To verify a JWS message using a single key, use `jws.Verify()` with the `jws.WithKey()` option. 353 It will automatically do the right thing whether it's serialized in compact form or JSON form. 354 355 The `alg` must be explicitly specified. See "[Why don't you automatically infer the algorithm for `jws.Verify`?](99-faq.md#why-dont-you-automatically-infer-the-algorithm-for-jwsverify-)" 356 357 <!-- INCLUDE(examples/jws_verify_with_key_example_test.go) --> 358 ```go 359 package examples_test 360 361 import ( 362 "fmt" 363 364 "github.com/lestrrat-go/jwx/v2/jwa" 365 "github.com/lestrrat-go/jwx/v2/jwk" 366 "github.com/lestrrat-go/jwx/v2/jws" 367 ) 368 369 func ExampleJWS_VerifyWithKey() { 370 const src = `eyJhbGciOiJIUzI1NiJ9.TG9yZW0gaXBzdW0.EjVtju0uXjSz6QevNgAqN1ESd9aNCP7-tJLifkQ0_C0` 371 372 key, err := jwk.FromRaw([]byte(`abracadabra`)) 373 if err != nil { 374 fmt.Printf("failed to create key: %s\n", err) 375 return 376 } 377 378 buf, err := jws.Verify([]byte(src), jws.WithKey(jwa.HS256, key)) 379 if err != nil { 380 fmt.Printf("failed to verify payload: %s\n", err) 381 return 382 } 383 fmt.Printf("%s\n", buf) 384 // OUTPUT: 385 // Lorem ipsum 386 } 387 ``` 388 source: [examples/jws_verify_with_key_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_verify_with_key_example_test.go) 389 <!-- END INCLUDE --> 390 391 ## Verification using a JWKS 392 393 To verify a payload using JWKS, by default you will need your payload and JWKS to have matching `kid` and `alg` fields. 394 395 The `alg` field's requirement is the same for using a single key. See "[Why don't you automatically infer the algorithm for `jws.Verify`?](99-faq.md#why-dont-you-automatically-infer-the-algorithm-for-jwsverify-)" 396 397 The `kid` field by default must match between the JWS signature and the key in JWKS. This can be explictly disabled by specifying `jws.WithRequireKid(false)` suboption when using the `jws.WithKeySet()` option (i.e.: `jws.WithKeySet(keyset, jws.WithRequireKid(false))`) 398 399 For more discussion on why/how `alg`/`kid` values work, please read the [relevant section in the JWT documentation](01-jwt.md#parse-and-verify-a-jwt-with-a-key-set-matching-kid) 400 401 <!-- INCLUDE(examples/jws_verify_with_keyset_example_test.go) --> 402 ```go 403 package examples_test 404 405 import ( 406 "crypto/rand" 407 "crypto/rsa" 408 "fmt" 409 410 "github.com/lestrrat-go/jwx/v2/jwa" 411 "github.com/lestrrat-go/jwx/v2/jwk" 412 "github.com/lestrrat-go/jwx/v2/jws" 413 ) 414 415 func ExampleJWS_VerifyWithJWKSet() { 416 // Setup payload first... 417 privkey, err := rsa.GenerateKey(rand.Reader, 2048) 418 if err != nil { 419 fmt.Printf("failed to create private key: %s\n", err) 420 return 421 } 422 const payload = "Lorem ipsum" 423 signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.RS256, privkey)) 424 if err != nil { 425 fmt.Printf("failed to sign payload: %s\n", err) 426 return 427 } 428 429 // Create a JWK Set 430 set := jwk.NewSet() 431 // Add some bogus keys 432 k1, _ := jwk.FromRaw([]byte("abracadabra")) 433 set.AddKey(k1) 434 k2, _ := jwk.FromRaw([]byte("opensesame")) 435 set.AddKey(k2) 436 // AddKey the real thing 437 pubkey, _ := jwk.PublicRawKeyOf(privkey) 438 k3, _ := jwk.FromRaw(pubkey) 439 k3.Set(jwk.AlgorithmKey, jwa.RS256) 440 set.AddKey(k3) 441 442 // Up to this point, you probably will replace with a simple jwk.Fetch() 443 444 // Now verify using the set. 445 if _, err := jws.Verify(signed, jws.WithKeySet(set, jws.WithRequireKid(false))); err != nil { 446 fmt.Printf("Failed to verify using jwk.Set: %s", err) 447 } 448 449 // OUTPUT: 450 } 451 ``` 452 source: [examples/jws_verify_with_keyset_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_verify_with_keyset_example_test.go) 453 <!-- END INCLUDE --> 454 455 ## Verification using a detached payload 456 457 To verify a JWS message with detached payload, use the `jws.WithDetachedPayload()` option: 458 459 <!-- INCLUDE(examples/jws_verify_detached_payload_example_test.go) --> 460 ```go 461 package examples_test 462 463 import ( 464 "fmt" 465 466 "github.com/lestrrat-go/jwx/v2/jwa" 467 "github.com/lestrrat-go/jwx/v2/jwk" 468 "github.com/lestrrat-go/jwx/v2/jws" 469 ) 470 471 func ExampleJWS_VerifyDetachedPayload() { 472 serialized := `eyJhbGciOiJIUzI1NiJ9..H14oXKwyvAsl0IbBLjw9tLxNIoYisuIyb_oDV4-30Vk` 473 payload := `$.02` 474 475 key, err := jwk.FromRaw([]byte(`abracadabra`)) 476 if err != nil { 477 fmt.Printf("failed to create symmetric key: %s\n", err) 478 return 479 } 480 481 verified, err := jws.Verify([]byte(serialized), jws.WithKey(jwa.HS256, key), jws.WithDetachedPayload([]byte(payload))) 482 if err != nil { 483 fmt.Printf("failed to verify payload: %s\n", err) 484 return 485 } 486 487 fmt.Printf("%s\n", verified) 488 // OUTPUT: 489 // $.02 490 } 491 ``` 492 source: [examples/jws_verify_detached_payload_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_verify_detached_payload_example_test.go) 493 <!-- END INCLUDE --> 494 495 ## Verification using `jku` 496 497 Regular calls to `jws.Verify()` does not respect the JWK Set referenced in the `jku` field. In order to 498 verify the payload using the `jku` field, you must use the `jws.VerifyAuto()` function. 499 500 ```go 501 wl := ... // Create an appropriate whitelist 502 payload, _ := jws.VerifyAuto(buf, jws.WithFetchWhitelist(wl)) 503 ``` 504 505 This will tell `jws` to verify the given buffer using the JWK Set presented at the URL specified in 506 the `jku` field. If the buffer is a JSON message, then this is done for each of the signature in 507 the `signatures` array. 508 509 The URL in the `jku` field must have the `https` scheme, and the key ID in the JWK Set must 510 match the key ID present in the JWS message. 511 512 Because this operation will result in your program accessing remote resources, the default behavior 513 is to NOT allow any URLs. You must specify a whitelist 514 515 ```go 516 wl := jwk.NewMapWhitelist(). 517 Add(`https://white-listed-address`) 518 519 payload, _ := jws.VerifyAuto(buf, jws.WithFetchWhitelist(wl)) 520 ``` 521 522 If you want to allow any URLs to be accessible, use the `jwk.InsecureWhitelist`. 523 524 ```go 525 wl := jwk.InsecureWhitelist{} 526 payload, _ := jws.VerifyAuto(buf, jws.WithFetchWhitelist(wl)) 527 ``` 528 529 If you must configure the HTTP Client in a special way, use the `jws.WithHTTPClient()` option: 530 531 ```go 532 client := &http.Client{ ... } 533 payload, _ := jws.VerifyAuto(buf, jws.WithHTTPClient(client)) 534 ``` 535 536 # Using a custom signing/verification algorithm 537 538 Sometimes we do not offer a particular algorithm out of the box, but you have an implementation for it. 539 540 In such scenarios, you can use the [`jws.RegisterSigner()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#RegisterSigner) and [`jws.RegisterVerifier()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2/jws#RegisterVerifier) functions to 541 generate your own verifier instance. 542 543 <!-- INCLUDE(examples/jws_custom_signer_verifier_example_test.go) --> 544 ```go 545 package examples_test 546 547 import ( 548 "crypto/rand" 549 "fmt" 550 551 "github.com/cloudflare/circl/sign/ed25519" 552 "github.com/lestrrat-go/jwx/v2/jwa" 553 "github.com/lestrrat-go/jwx/v2/jws" 554 ) 555 556 type CirclEdDSASignerVerifier struct{} 557 558 func NewCirclEdDSASigner() (jws.Signer, error) { 559 return &CirclEdDSASignerVerifier{}, nil 560 } 561 562 func NewCirclEdDSAVerifier() (jws.Verifier, error) { 563 return &CirclEdDSASignerVerifier{}, nil 564 } 565 566 func (s CirclEdDSASignerVerifier) Algorithm() jwa.SignatureAlgorithm { 567 return jwa.EdDSA 568 } 569 570 func (s CirclEdDSASignerVerifier) Sign(payload []byte, keyif interface{}) ([]byte, error) { 571 switch key := keyif.(type) { 572 case ed25519.PrivateKey: 573 return ed25519.Sign(key, payload), nil 574 default: 575 return nil, fmt.Errorf(`invalid key type %T`, keyif) 576 } 577 } 578 579 func (s CirclEdDSASignerVerifier) Verify(payload []byte, signature []byte, keyif interface{}) error { 580 switch key := keyif.(type) { 581 case ed25519.PublicKey: 582 if ed25519.Verify(key, payload, signature) { 583 return nil 584 } 585 return fmt.Errorf(`failed to verify EdDSA signature`) 586 default: 587 return fmt.Errorf(`invalid key type %T`, keyif) 588 } 589 } 590 591 func ExampleJWS_CustomSignerVerifier() { 592 // This example shows how to register external jws.Signer / jws.Verifier for 593 // a given algorithm. 594 jws.RegisterSigner(jwa.EdDSA, jws.SignerFactoryFn(NewCirclEdDSASigner)) 595 jws.RegisterVerifier(jwa.EdDSA, jws.VerifierFactoryFn(NewCirclEdDSAVerifier)) 596 597 pubkey, privkey, err := ed25519.GenerateKey(rand.Reader) 598 if err != nil { 599 fmt.Printf(`failed to generate keys: %s`, err) 600 return 601 } 602 603 const payload = "Lorem Ipsum" 604 signed, err := jws.Sign([]byte(payload), jws.WithKey(jwa.EdDSA, privkey)) 605 if err != nil { 606 fmt.Printf(`failed to generate signed message: %s`, err) 607 return 608 } 609 610 verified, err := jws.Verify(signed, jws.WithKey(jwa.EdDSA, pubkey)) 611 if err != nil { 612 fmt.Printf(`failed to verify signed message: %s`, err) 613 return 614 } 615 616 if string(verified) != payload { 617 fmt.Printf(`got invalid payload: %s`, verified) 618 return 619 } 620 621 // OUTPUT: 622 } 623 ``` 624 source: [examples/jws_custom_signer_verifier_example_test.go](https://github.com/lestrrat-go/jwx/blob/v2/examples/jws_custom_signer_verifier_example_test.go) 625 <!-- END INCLUDE --> 626 627 # Enabling ES256K 628 629 See [Enabling Optional Signature Methods](./20-global-settings.md#enabling-optional-signature-methods)