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}