mirror of https://github.com/ory/hydra
495 lines
14 KiB
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) {}
|