...

Text file src/github.com/lestrrat-go/jwx/docs/04-jwk.md

Documentation: github.com/lestrrat-go/jwx/docs

     1# Working with JWK
     2
     3In this document we describe how to work with JWK using `github.com/lestrrat-go/jwx/jwk`
     4
     5* [Terminology](#terminology)
     6  * [JWK / Key](#jwk--key)
     7  * [JWK Set / Set](#jwk-set--set)
     8  * [Raw Key](#raw-key)
     9* [Parsing](#parsing)
    10  * [Parse a set](#parse-a-set)
    11  * [Parse a key](#parse-a-key)
    12  * [Parse a key or set in PEM format](#parse-a-key-or-a-set-in-pem-format)
    13  * [Parse a key from a file](#parse-a-key-from-a-file)
    14  * [Parse a key from a remote resource](#parse-a-key-from-a-remote-resource)
    15* [Construction](#construction)
    16  * [Using jwk.New()](#using-jwknew)
    17  * [Construct a specific key type from scratch](#construct-a-specific-key-type-from-scratch)
    18  * [Construct a specific key type from a raw key](#construct-a-specific-key-type-from-a-raw-key)
    19* [Setting values to fields](#setting-values-to-fields)
    20* [Fetching JWK Sets](#fetching-jwk-sets)
    21  * [Fetching a JWK Set once](#fetching-a-jwk-set-once)
    22  * [Auto-refreshing remote keys](#auto-refreshing-remote-keys)
    23  * [Using Whitelists](#using-whitelists)
    24* [Converting a jwk.Key to a raw key](#converting-a-jwkkey-to-a-raw-key)
    25
    26---
    27
    28# Terminology
    29
    30## JWK / Key
    31
    32Used to describe a JWK key, possibly of typeRSA, ECDSA, OKP, or Symmetric.
    33
    34## JWK Set / Set
    35
    36A "jwk" resource on the web can either contain a single JWK or an array of multiple JWKs.
    37The latter is called a JWK Set.
    38
    39It is impossible to know what the resource contains beforehand, so functions like [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Parse)
    40and [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ReadFile) returns a [`jwk.Set`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Set) by default.
    41
    42## Raw Key
    43
    44Used to describe the underlying raw key that a JWK represents. For example, an RSA JWK can
    45represent rsa.PrivateKey/rsa.PublicKey, ECDSA JWK can represent ecdsa.PrivateKey/ecdsa.PublicKey,
    46and so forth
    47
    48# Parsing
    49
    50## Parse a set
    51
    52If you have a key set, or are unsure if the source is a set or a single key, you should use [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Parse)
    53
    54```go
    55keyset, _ := jwk.Parse(src)
    56```
    57
    58## Parse a key
    59
    60If you are sure that the source only contains a single key, you can use [`jwk.ParseKey()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ParseKey)
    61
    62```go
    63key, _ := jwk.ParseKey(src)
    64```
    65
    66## Parse a key or a set in PEM format
    67
    68Sometimes keys come in ASN.1 DER PEM format.  To parse these files, use the [`jwk.WithPEM()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#WithPEM) option.
    69
    70```go
    71keyset, _ := jwk.Parse(srcSet, jwk.WithPEM(true))
    72
    73key, _ := jwk.ParseKey(src, jwk.WithPEM(true))
    74```
    75
    76## Parse a key from a file
    77
    78To parse keys stored in a file, [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ReadFile) can be used. 
    79
    80```go
    81keyset, _ := jwk.ReadFile(filename)
    82```
    83
    84`jwk.ReadFile()` accepts the same options as [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Parse), therefore you can read a PEM-encoded file via the following incantation:
    85
    86```go
    87keyset, _ := jwk.ReadFile(filename, jwk.WithPEM(true))
    88```
    89
    90## Parse a key from a remote resource
    91
    92To parse keys stored in a remote location pointed by a HTTP(s) URL, use [`jwk.Fetch()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Fetch)
    93
    94```go
    95keyset, _ := jwk.Fetch(ctx, url)
    96```
    97
    98If you are going to be using this key repeatedly in a long running process, consider using [`jwk.AutoRefresh`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#AutoRefresh) described elsewhere in this document.
    99
   100# Construction
   101
   102## Using jwk.New()
   103
   104Users can create a new key from scratch using [`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New).
   105
   106[`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New) requires the raw key as its argument.
   107There are other ways to creating keys from a raw key, but they require knowing its type in advance.
   108Use [`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New) when you have a key type which you do not know its underlying type in advance.
   109
   110It automatically creates the appropriate underlying key based on the given argument type.
   111
   112| Argument Type | Key Type | Note |
   113|---------------|----------|------|
   114| []byte        | Symmetric Key | |
   115| ecdsa.PrivateKey | ECDSA Private Key | Argument may also be a pointer |
   116| ecdsa.PubliKey | ECDSA Public Key | Argument may also be a pointer |
   117| rsa.PrivateKey | RSA Private Key | Argument may also be a pointer |
   118| rsa.PubliKey | RSA Public Key | Argument may also be a pointer |
   119| x25519.PrivateKey | OKP Private Key | |
   120| x25519.PubliKey | OKP Public Key | |
   121
   122One common mistake we see is users using [`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New) to construct a key from a []byte variable containing the raw JSON format JWK.
   123
   124```go
   125// THIS IS WRONG!
   126buf, _ := ioutil.ReadFile(`key.json`) // os.ReadFile in go 1.16+
   127key, _ := jwk.New(buf) // ALWAYS creates a symmetric key
   128```
   129
   130[`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New) is used to create a new key from a known, *raw key* type. To process a yet-to-be-parsed
   131JWK, use [`jwk.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Parse) or [`jwk.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ReadFile)
   132
   133```go
   134// Parse a buffer containing a JSON JWK
   135buf, _ := ioutil.ReadFile(`key.json`) // os.ReadFile in go 1.16+
   136key, _ := jwk.Parse(buf)
   137```
   138
   139```go
   140// Read a file, parse it as JSON
   141key, _ := jwk.ParseFile(`key.json`)
   142```
   143
   144## Construct a specific key type from scratch
   145
   146Each of Symmetric, RSA, ECDSA, OKP key types have corresponding constructors to create an empty instance.
   147These keys are completely empty, so if you tried using them without initialization, it will not work.
   148
   149```go
   150key := jwk.NewSymmetricKey()
   151key := jwk.NewECDSAPrivateKey()
   152key := jwk.NewECDSAPublicKey()
   153key := jwk.NewRSAPrivateKey()
   154key := jwk.NewRSAPublicKey()
   155key := jwk.NewOKPPrivateKey()
   156key := jwk.NewOKPPublicKey()
   157```
   158
   159For advanced users: Once you obtain these "empty" objects, you *can* use `json.Unmarshal()` to parse the JWK.
   160
   161```
   162// OK
   163key := jwk.NewRSAPrivateKey()
   164if err := json.Unmarshal(src, key); err != nil {
   165  return errors.New(`failed to unmarshal RSA key`)
   166}
   167
   168// NOT OK
   169var key jwk.Key // we can't do this because we don't know where to store the data
   170if err := json.Unmarshal(src, &key); err !+ nil {
   171  ...
   172}
   173```
   174
   175## Construct a specific key type from a raw key
   176
   177[`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New) already does this, but if for some reason you would like to initialize an already existing [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Key), you can use the [`jwk.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#FromRaw) method.
   178
   179```go
   180privkey, err := rsa.GenerateKey(...)
   181
   182key := jwk.NewRSAPrivateKey()
   183err := key.FromRaw(privkey)
   184```
   185
   186## Setting values to fields
   187
   188Using [`jwk.New()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#New) or [`jwk.FromRaw()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#FromRaw) allows you to populate the fields that are required to do perform the computations, but there are other fields that you may want to populate in a key. These fields can all be set using the [`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Set) method.
   189
   190The [`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Set) method takes the name of the key, and a value to be associated with it. Some predefined keys have specific types (in which type checks are enforced), and others not.
   191
   192[`jwk.Set()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Set) may not alter the Key Type (`kty`) field of a key.
   193
   194the `jwk` package defines field key names for predefined keys as constants so you won't ever have to bang your head againt the wall after finding out that you have a typo.
   195
   196```go
   197key.Set(jwk.KeyIDKey, `my-awesome-key`)
   198key.Set(`my-custom-field`, `unbelievable-value`)
   199```
   200# Fetching JWK Sets
   201
   202## Fetching a JWK Set once
   203
   204To fetch a JWK Set once, use `jwk.Fetch()`.
   205
   206```go
   207set, err := jwk.Fetch(ctx, url, options...)
   208```
   209
   210## Auto-refreshing remote keys
   211
   212Sometimes you need to fetch a remote JWK, and use it mltiple times in a long-running process.
   213For example, you may act as an itermediary to some other service, and you may need to verify incoming JWT tokens against the tokens in said other service.
   214
   215Normally, you should be able to simply fetch the JWK using [`jwk.Fetch()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Fetch), but keys are usually expired and rotated due to security reasons.
   216In such cases you would need to refetch the JWK periodically, which is a pain.
   217
   218`github.com/lestrrat-go/jwx/jwk` provides the [`jwk.AutoRefresh`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#AutoRefresh) tool to do this for you.
   219
   220First, set up the [`jwk.AutoRefresh`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#AutoRefresh) object.
   221You need to pass it a `context.Context` object to control the lifecycle of the background fetching goroutine.
   222
   223```go
   224ar := jwk.NewAutoRefresh(ctx)
   225```
   226
   227Next you need to tell [`jwk.AutoRefresh`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#AutoRefresh) which URLs to keep updating. For this, we use the `Configure()` method. [`jwk.AutoRefresh`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#AutoRefresh) will use the information found in the HTTP headers (`Cache-Control` and `Expires`) or the default interval to determine when to fetch the key next time.
   228
   229```go
   230ar.Configure(`https://example.com/certs/pubkeys.json`)
   231```
   232
   233And lastly, each time you are about to use the key, load it from the [`jwk.AutoRefresh`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#AutoRefresh) object.
   234
   235```go
   236keyset, _ := ar.Fetch(ctx, `https://example.com/certs/pubkeys.json`)
   237```
   238
   239The returned `keyset` will always be "reasonably" new. It is important that you always call `ar.Fetch()` before using the `keyset` as this is where the refreshing occurs.
   240
   241By "reasonably" we mean that we cannot guarantee that the keys will be refreshed immediately after it has been rotated in the remote source. But it should be close enough, and should you need to forcefully refresh the token using the `(jwk.AutoRefresh).Refresh()` method.
   242
   243If re-fetching the keyset fails, a cached version will be returned from the previous successful fetch upon calling `(jwk.AutoRefresh).Fetch()`.
   244
   245## Using Whitelists
   246
   247If you are fetching JWK Sets from a possibly untrusted source such as the `"jku"` field of a JWS message, you may have to perform some sort of
   248whitelist checking. You can provide a `jwk.Whitelist` object to either `jwk.Fetch()` or `(*jwk.AutoRefresh).Configure()` methods to specify the
   249use of a whitelist.
   250
   251Currently the package provides `jwk.MapWhitelist` and `jwk.RegexpWhitelist` types for simpler cases.
   252
   253```go
   254wl := jwk.NewMapWhitelist().
   255  Add(url1).
   256  Add(url2).
   257  Add(url3
   258
   259wl := jwk.NewRegexpWhitelist().
   260  Add(regexp1).
   261  Add(regexp2).
   262  Add(regexp3)
   263
   264jwk.Fetch(ctx, url, jwk.WithWhitelist(wl))
   265
   266// or in a jwk.AutoRefresh object:
   267ar.Configure(url, jwk.WithWhitelist(wl))
   268```
   269
   270If you would like to implement something more complex, you can provide a function via `jwk.WhitelistFunc` or implement you own type of `jwk.Whitelist`.
   271
   272# Converting a jwk.Key to a raw key
   273
   274As discussed in [Terminology](#terminology), this package calls the "original" keys (e.g. `rsa.PublicKey`, `ecdsa.PrivateKey`, etc) as "raw" keys. To obtain a raw key from a  [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Key) object, use the [`Raw()`](https://github.com/github.com/lestrrat-go/jwx/jwk#Raw) method.
   275
   276```go
   277key, _ := jwk.ParseKey(src)
   278
   279var raw interface{}
   280if err := key.Raw(&raw); err != nil {
   281  ...
   282}
   283```
   284
   285In the above example, `raw` contains whatever the [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Key) represents.
   286If `key` represents an RSA key, it will contain either a `rsa.PublicKey` or `rsa.PrivateKey`. If it represents an ECDSA key, an `ecdsa.PublicKey`, or `ecdsa.PrivateKey`, etc.
   287
   288If the only operation that you are performing is to grab the raw key out of a JSON JWK, use [`jwk.ParseRawKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ParseRawKey)
   289
   290```go
   291var raw interface{}
   292if err := jwk.ParseRawKey(src, &raw); err != nil {
   293  ...
   294}
   295```

View as plain text