Raw
  1package dexstore
  2
  3import (
  4	"bytes"
  5	"context"
  6	"crypto/rand"
  7	"crypto/rsa"
  8	"encoding/hex"
  9	"encoding/json"
 10	"errors"
 11	"fmt"
 12	"io"
 13	"time"
 14
 15	dexstorage "github.com/dexidp/dex/storage"
 16	"github.com/go-jose/go-jose/v4"
 17)
 18
 19var (
 20	ErrNoKeys      = errors.New("dexstore: no signing keys")
 21	errInvalidKeys = errors.New("dexstore: invalid keys")
 22)
 23
 24type exportedKeys struct {
 25	SigningKeyPub    *jose.JSONWebKey          `json:"signing_key_pub"`
 26	VerificationKeys []exportedVerificationKey `json:"verification_keys"`
 27}
 28
 29type exportedVerificationKey struct {
 30	PublicKey *jose.JSONWebKey `json:"public_key"`
 31	Expiry    *int64           `json:"expiry"`
 32}
 33
 34func ExportPublicKeys(ctx context.Context, dex dexstorage.Storage) (json.RawMessage, error) {
 35	k, err := dex.GetKeys(ctx)
 36	if errors.Is(err, dexstorage.ErrNotFound) {
 37		return nil, ErrNoKeys
 38	}
 39	if err != nil {
 40		return nil, fmt.Errorf("dexstore: read keys: %w", err)
 41	}
 42	if k.SigningKeyPub == nil {
 43		return nil, ErrNoKeys
 44	}
 45	out := exportedKeys{
 46		SigningKeyPub:    k.SigningKeyPub,
 47		VerificationKeys: make([]exportedVerificationKey, 0, len(k.VerificationKeys)),
 48	}
 49	for _, v := range k.VerificationKeys {
 50		expiry := v.Expiry.UnixMicro()
 51		out.VerificationKeys = append(out.VerificationKeys, exportedVerificationKey{PublicKey: v.PublicKey, Expiry: &expiry})
 52	}
 53	if !publicOnly(out) {
 54		return nil, errors.New("dexstore: refusing to export private key material")
 55	}
 56	var buf bytes.Buffer
 57	enc := json.NewEncoder(&buf)
 58	enc.SetEscapeHTML(false)
 59	if err := enc.Encode(out); err != nil {
 60		return nil, fmt.Errorf("dexstore: encode keys: %w", err)
 61	}
 62	return bytes.TrimSuffix(buf.Bytes(), []byte("\n")), nil
 63}
 64
 65func publicOnly(k exportedKeys) bool {
 66	if k.SigningKeyPub == nil || !k.SigningKeyPub.IsPublic() {
 67		return false
 68	}
 69	for _, v := range k.VerificationKeys {
 70		if v.PublicKey == nil || !v.PublicKey.IsPublic() {
 71			return false
 72		}
 73	}
 74	return true
 75}
 76
 77func ImportPublicKeys(ctx context.Context, dex dexstorage.Storage, doc json.RawMessage, now time.Time, validFor time.Duration, nextRotation time.Time) error {
 78	in, err := decodeKeys(doc)
 79	if err != nil {
 80		return err
 81	}
 82	priv, pub, err := mintKey()
 83	if err != nil {
 84		return fmt.Errorf("dexstore: mint signing key: %w", err)
 85	}
 86	keys := restoredKeys(in, priv, pub, now, validFor, nextRotation)
 87	return dex.UpdateKeys(ctx, func(dexstorage.Keys) (dexstorage.Keys, error) {
 88		return keys, nil
 89	})
 90}
 91
 92func restoredKeys(in dexstorage.Keys, priv, pub *jose.JSONWebKey, now time.Time, validFor time.Duration, nextRotation time.Time) dexstorage.Keys {
 93	out := dexstorage.Keys{
 94		SigningKey:       priv,
 95		SigningKeyPub:    pub,
 96		VerificationKeys: make([]dexstorage.VerificationKey, 0, len(in.VerificationKeys)+1),
 97		NextRotation:     nextRotation.UTC(),
 98	}
 99	for _, v := range in.VerificationKeys {
100		if now.After(v.Expiry) {
101			continue
102		}
103		out.VerificationKeys = append(out.VerificationKeys, v)
104	}
105	out.VerificationKeys = append(out.VerificationKeys, dexstorage.VerificationKey{PublicKey: in.SigningKeyPub, Expiry: now.Add(validFor).UTC()})
106	return out
107}
108
109func mintKey() (priv, pub *jose.JSONWebKey, err error) {
110	key, err := rsa.GenerateKey(rand.Reader, 2048)
111	if err != nil {
112		return nil, nil, err
113	}
114	b := make([]byte, 20)
115	if _, err := io.ReadFull(rand.Reader, b); err != nil {
116		return nil, nil, err
117	}
118	kid := hex.EncodeToString(b)
119	priv = &jose.JSONWebKey{Key: key, KeyID: kid, Algorithm: string(jose.RS256), Use: "sig"}
120	pub = &jose.JSONWebKey{Key: key.Public(), KeyID: kid, Algorithm: string(jose.RS256), Use: "sig"}
121	return priv, pub, nil
122}
123
124func invalidKeys(reason string) error {
125	return fmt.Errorf("%w: %s", errInvalidKeys, reason)
126}
127
128func publicKey(k *jose.JSONWebKey, what string) error {
129	if k == nil {
130		return invalidKeys(what + " is missing")
131	}
132	if !k.IsPublic() || !k.Valid() {
133		return invalidKeys(what + " is not a public key")
134	}
135	if k.KeyID == "" {
136		return invalidKeys(what + " has no kid")
137	}
138	return nil
139}
140
141func decodeKeys(doc json.RawMessage) (dexstorage.Keys, error) {
142	var in exportedKeys
143	dec := json.NewDecoder(bytes.NewReader(doc))
144	dec.DisallowUnknownFields()
145	if err := dec.Decode(&in); err != nil {
146		return dexstorage.Keys{}, fmt.Errorf("%w: %v", errInvalidKeys, err)
147	}
148	if dec.More() {
149		return dexstorage.Keys{}, invalidKeys("trailing data")
150	}
151	if err := publicKey(in.SigningKeyPub, "signing_key_pub"); err != nil {
152		return dexstorage.Keys{}, err
153	}
154	out := dexstorage.Keys{SigningKeyPub: in.SigningKeyPub}
155	for i, v := range in.VerificationKeys {
156		if err := publicKey(v.PublicKey, fmt.Sprintf("verification_keys[%d]", i)); err != nil {
157			return dexstorage.Keys{}, err
158		}
159		if v.Expiry == nil {
160			return dexstorage.Keys{}, invalidKeys(fmt.Sprintf("verification_keys[%d] has no expiry", i))
161		}
162		out.VerificationKeys = append(out.VerificationKeys, dexstorage.VerificationKey{PublicKey: v.PublicKey, Expiry: time.UnixMicro(*v.Expiry).UTC()})
163	}
164	return out, nil
165}