hydra/jwk/handler.go

495 lines
14 KiB
Go

// Copyright © 2022 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package jwk
import (
"encoding/json"
"net/http"
"net/url"
"sync/atomic"
"github.com/go-jose/go-jose/v3"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"github.com/ory/herodot"
"github.com/ory/hydra/v2/x"
"github.com/ory/x/httprouterx"
"github.com/ory/x/urlx"
)
const (
KeyHandlerPath = "/keys"
WellKnownKeysPath = "/.well-known/jwks.json"
)
type Handler struct {
r InternalRegistry
}
// JSON Web Key Set
//
// swagger:model jsonWebKeySet
//
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type jsonWebKeySet struct {
// List of JSON Web Keys
//
// The value of the "keys" parameter is an array of JSON Web Key (JWK)
// values. By default, the order of the JWK values within the array does
// not imply an order of preference among them, although applications
// of JWK Sets can choose to assign a meaning to the order for their
// purposes, if desired.
Keys []x.JSONWebKey `json:"keys"`
}
func NewHandler(r InternalRegistry) *Handler {
return &Handler{r: r}
}
func (h *Handler) SetPublicRoutes(r *httprouterx.RouterPublic, corsMiddleware func(http.Handler) http.Handler) {
r.Handler("OPTIONS", WellKnownKeysPath, corsMiddleware(http.HandlerFunc(h.handleOptions)))
r.Handler("GET", WellKnownKeysPath, corsMiddleware(http.HandlerFunc(h.discoverJsonWebKeys)))
}
func (h *Handler) SetAdminRoutes(r *httprouterx.RouterAdmin) {
r.GET(KeyHandlerPath+"/{set}/{key}", h.getJsonWebKey)
r.GET(KeyHandlerPath+"/{set}", h.getJsonWebKeySet)
r.POST(KeyHandlerPath+"/{set}", h.createJsonWebKeySet)
r.PUT(KeyHandlerPath+"/{set}/{key}", h.adminUpdateJsonWebKey)
r.PUT(KeyHandlerPath+"/{set}", h.setJsonWebKeySet)
r.DELETE(KeyHandlerPath+"/{set}/{key}", h.deleteJsonWebKey)
r.DELETE(KeyHandlerPath+"/{set}", h.adminDeleteJsonWebKeySet)
}
// swagger:route GET /.well-known/jwks.json wellknown discoverJsonWebKeys
//
// # Discover Well-Known JSON Web Keys
//
// This endpoint returns JSON Web Keys required to verifying OpenID Connect ID Tokens and,
// if enabled, OAuth 2.0 JWT Access Tokens. This endpoint can be used with client libraries like
// [node-jwks-rsa](https://github.com/auth0/node-jwks-rsa) among others.
//
// Adding custom keys requires first creating a keyset via the createJsonWebKeySet operation,
// and then configuring the webfinger.jwks.broadcast_keys configuration value to include the keyset name.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: jsonWebKeySet
// default: errorOAuth2
func (h *Handler) discoverJsonWebKeys(w http.ResponseWriter, r *http.Request) {
eg, ctx := errgroup.WithContext(r.Context())
wellKnownKeys := h.r.Config().WellKnownKeys(ctx)
keys := make([]*jose.JSONWebKeySet, len(wellKnownKeys))
nTotalKeys := atomic.Int64{}
for i, set := range wellKnownKeys {
eg.Go(func() error {
k, err := h.r.KeyManager().GetKeySet(ctx, set)
if errors.Is(err, x.ErrNotFound) {
h.r.Logger().Warnf("JSON Web Key Set %q does not exist yet, generating new key pair...", set)
k, err = h.r.KeyManager().GenerateAndPersistKeySet(ctx, set, "", string(jose.RS256), "sig")
if err != nil {
return err
}
} else if err != nil {
return err
}
keys[i] = ExcludePrivateKeys(k)
nTotalKeys.Add(int64(len(keys[i].Keys)))
return nil
})
}
if err := eg.Wait(); err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
jwks := jose.JSONWebKeySet{Keys: make([]jose.JSONWebKey, 0, nTotalKeys.Load())}
for _, k := range keys {
jwks.Keys = append(jwks.Keys, k.Keys...)
}
h.r.Writer().Write(w, r, &jwks)
}
// Get JSON Web Key Request
//
// swagger:parameters getJsonWebKey
type _ struct {
// JSON Web Key Set ID
//
// in: path
// required: true
Set string `json:"set"`
// JSON Web Key ID
//
// in: path
// required: true
KID string `json:"kid"`
}
// swagger:route GET /admin/keys/{set}/{kid} jwk getJsonWebKey
//
// # Get JSON Web Key
//
// This endpoint returns a singular JSON Web Key contained in a set. It is identified by the set and the specific key ID (kid).
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: jsonWebKeySet
// default: errorOAuth2
func (h *Handler) getJsonWebKey(w http.ResponseWriter, r *http.Request) {
var setName = r.PathValue("set")
var keyName = r.PathValue("key")
keys, err := h.r.KeyManager().GetKey(r.Context(), setName, keyName)
if err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
keys = ExcludeOpaquePrivateKeys(keys)
h.r.Writer().Write(w, r, keys)
}
// Get JSON Web Key Set Parameters
//
// swagger:parameters getJsonWebKeySet
type _ struct {
// JSON Web Key Set ID
//
// in: path
// required: true
Set string `json:"set"`
}
// swagger:route GET /admin/keys/{set} jwk getJsonWebKeySet
//
// # Retrieve a JSON Web Key Set
//
// This endpoint can be used to retrieve JWK Sets stored in ORY Hydra.
//
// A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. A JWK Set is a JSON data structure that represents a set of JWKs. A JSON Web Key is identified by its set and key id. ORY Hydra uses this functionality to store cryptographic keys used for TLS and JSON Web Tokens (such as OpenID Connect ID tokens), and allows storing user-defined keys as well.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: jsonWebKeySet
// default: errorOAuth2
func (h *Handler) getJsonWebKeySet(w http.ResponseWriter, r *http.Request) {
var setName = r.PathValue("set")
keys, err := h.r.KeyManager().GetKeySet(r.Context(), setName)
if err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
keys = ExcludeOpaquePrivateKeys(keys)
h.r.Writer().Write(w, r, keys)
}
// Create JSON Web Key Set Request
//
// swagger:parameters createJsonWebKeySet
type _ struct {
// The JSON Web Key Set ID
//
// in: path
// required: true
Set string `json:"set"`
// in: body
// required: true
Body createJsonWebKeySetBody
}
// Create JSON Web Key Set Request Body
//
// swagger:model createJsonWebKeySet
type createJsonWebKeySetBody struct {
// JSON Web Key Algorithm
//
// The algorithm to be used for creating the key. Supports `RS256`, `ES256`, `ES512`, `HS512`, and `HS256`.
//
// required: true
Algorithm string `json:"alg"`
// JSON Web Key Use
//
// The "use" (public key use) parameter identifies the intended use of
// the public key. The "use" parameter is employed to indicate whether
// a public key is used for encrypting data or verifying the signature
// on data. Valid values are "enc" and "sig".
// required: true
Use string `json:"use"`
// JSON Web Key ID
//
// The Key ID of the key to be created.
//
// required: true
KeyID string `json:"kid"`
}
// swagger:route POST /admin/keys/{set} jwk createJsonWebKeySet
//
// # Create JSON Web Key
//
// This endpoint is capable of generating JSON Web Key Sets for you. There a different strategies available, such as symmetric cryptographic keys (HS256, HS512) and asymetric cryptographic keys (RS256, ECDSA). If the specified JSON Web Key Set does not exist, it will be created.
//
// A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. A JWK Set is a JSON data structure that represents a set of JWKs. A JSON Web Key is identified by its set and key id. ORY Hydra uses this functionality to store cryptographic keys used for TLS and JSON Web Tokens (such as OpenID Connect ID tokens), and allows storing user-defined keys as well.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 201: jsonWebKeySet
// default: errorOAuth2
func (h *Handler) createJsonWebKeySet(w http.ResponseWriter, r *http.Request) {
var keyRequest createJsonWebKeySetBody
var set = r.PathValue("set")
if err := json.NewDecoder(r.Body).Decode(&keyRequest); err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body: %s", err)))
return
}
if keys, err := h.r.KeyManager().GenerateAndPersistKeySet(r.Context(), set, keyRequest.KeyID, keyRequest.Algorithm, keyRequest.Use); err == nil {
keys = ExcludeOpaquePrivateKeys(keys)
h.r.Writer().WriteCreated(w, r, urlx.AppendPaths(h.r.Config().IssuerURL(r.Context()), "keys", url.PathEscape(set)).String(), keys)
} else {
h.r.Writer().WriteError(w, r, err)
}
}
// Set JSON Web Key Set Request
//
// swagger:parameters setJsonWebKeySet
type _ struct {
// The JSON Web Key Set ID
//
// in: path
// required: true
Set string `json:"set"`
// in: body
Body jsonWebKeySet
}
// swagger:route PUT /admin/keys/{set} jwk setJsonWebKeySet
//
// # Update a JSON Web Key Set
//
// Use this method if you do not want to let Hydra generate the JWKs for you, but instead save your own.
//
// A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. A JWK Set is a JSON data structure that represents a set of JWKs. A JSON Web Key is identified by its set and key id. ORY Hydra uses this functionality to store cryptographic keys used for TLS and JSON Web Tokens (such as OpenID Connect ID tokens), and allows storing user-defined keys as well.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: jsonWebKeySet
// default: errorOAuth2
func (h *Handler) setJsonWebKeySet(w http.ResponseWriter, r *http.Request) {
var keySet jose.JSONWebKeySet
var set = r.PathValue("set")
if err := json.NewDecoder(r.Body).Decode(&keySet); err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body: %s", err)))
return
}
if err := h.r.KeyManager().UpdateKeySet(r.Context(), set, &keySet); err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
h.r.Writer().Write(w, r, &keySet)
}
// Set JSON Web Key Request
//
// swagger:parameters setJsonWebKey
type _ struct {
// The JSON Web Key Set ID
//
// in: path
// required: true
Set string `json:"set"`
// JSON Web Key ID
//
// in: path
// required: true
KID string `json:"kid"`
// in: body
Body x.JSONWebKey
}
// swagger:route PUT /admin/keys/{set}/{kid} jwk setJsonWebKey
//
// # Set JSON Web Key
//
// Use this method if you do not want to let Hydra generate the JWKs for you, but instead save your own.
//
// A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. A JWK Set is a JSON data structure that represents a set of JWKs. A JSON Web Key is identified by its set and key id. ORY Hydra uses this functionality to store cryptographic keys used for TLS and JSON Web Tokens (such as OpenID Connect ID tokens), and allows storing user-defined keys as well.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: jsonWebKey
// default: errorOAuth2
func (h *Handler) adminUpdateJsonWebKey(w http.ResponseWriter, r *http.Request) {
var key jose.JSONWebKey
var set = r.PathValue("set")
if err := json.NewDecoder(r.Body).Decode(&key); err != nil {
h.r.Writer().WriteError(w, r, errors.WithStack(herodot.ErrBadRequest.WithReasonf("Unable to decode the request body: %s", err)))
return
}
if err := h.r.KeyManager().UpdateKey(r.Context(), set, &key); err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
h.r.Writer().Write(w, r, key)
}
// Delete JSON Web Key Set Parameters
//
// swagger:parameters deleteJsonWebKeySet
type _ struct {
// The JSON Web Key Set
// in: path
// required: true
Set string `json:"set"`
}
// swagger:route DELETE /admin/keys/{set} jwk deleteJsonWebKeySet
//
// # Delete JSON Web Key Set
//
// Use this endpoint to delete a complete JSON Web Key Set and all the keys in that set.
//
// A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. A JWK Set is a JSON data structure that represents a set of JWKs. A JSON Web Key is identified by its set and key id. ORY Hydra uses this functionality to store cryptographic keys used for TLS and JSON Web Tokens (such as OpenID Connect ID tokens), and allows storing user-defined keys as well.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 204: emptyResponse
// default: errorOAuth2
func (h *Handler) adminDeleteJsonWebKeySet(w http.ResponseWriter, r *http.Request) {
var setName = r.PathValue("set")
if err := h.r.KeyManager().DeleteKeySet(r.Context(), setName); err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// Delete JSON Web Key Parameters
//
// swagger:parameters deleteJsonWebKey
type _ struct {
// The JSON Web Key Set
// in: path
// required: true
Set string `json:"set"`
// The JSON Web Key ID (kid)
//
// in: path
// required: true
KID string `json:"kid"`
}
// swagger:route DELETE /admin/keys/{set}/{kid} jwk deleteJsonWebKey
//
// # Delete JSON Web Key
//
// Use this endpoint to delete a single JSON Web Key.
//
// A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. A
// JWK Set is a JSON data structure that represents a set of JWKs. A JSON Web Key is identified by its set and key id. ORY Hydra uses
// this functionality to store cryptographic keys used for TLS and JSON Web Tokens (such as OpenID Connect ID tokens),
// and allows storing user-defined keys as well.
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 204: emptyResponse
// default: errorOAuth2
func (h *Handler) deleteJsonWebKey(w http.ResponseWriter, r *http.Request) {
setName, keyName := r.PathValue("set"), r.PathValue("key")
if err := h.r.KeyManager().DeleteKey(r.Context(), setName, keyName); err != nil {
h.r.Writer().WriteError(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// This function will not be called, OPTIONS request will be handled by cors
// this is just a placeholder.
func (h *Handler) handleOptions(http.ResponseWriter, *http.Request) {}