/
keys.go
179 lines (139 loc) · 4.38 KB
/
keys.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package oidc
import (
"context"
"crypto"
"crypto/rsa"
"errors"
"fmt"
"strings"
"github.com/ory/fosite/token/jwt"
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
// initial key to the manager.
func NewKeyManagerWithConfiguration(config *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
manager = NewKeyManager()
if _, err = manager.AddActiveJWK(config.IssuerCertificateChain, config.IssuerPrivateKey); err != nil {
return nil, err
}
return manager, nil
}
// NewKeyManager creates a new empty KeyManager.
func NewKeyManager() (manager *KeyManager) {
return &KeyManager{
jwks: &jose.JSONWebKeySet{},
}
}
// Strategy returns the fosite jwt.JWTStrategy.
func (m *KeyManager) Strategy() (strategy jwt.JWTStrategy) {
if m.jwk == nil {
return nil
}
return m.jwk.Strategy()
}
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
func (m *KeyManager) GetKeySet() (jwks *jose.JSONWebKeySet) {
return m.jwks
}
// GetActiveJWK obtains the currently active jose.JSONWebKey.
func (m *KeyManager) GetActiveJWK() (jwk *jose.JSONWebKey, err error) {
if m.jwks == nil || m.jwk == nil {
return nil, errors.New("could not obtain the active JWK from an improperly configured key manager")
}
jwks := m.jwks.Key(m.jwk.id)
if len(jwks) == 1 {
return &jwks[0], nil
}
if len(jwks) == 0 {
return nil, errors.New("could not find a key with the active key id")
}
return nil, errors.New("multiple keys with the same key id")
}
// GetActiveKeyID returns the key id of the currently active key.
func (m *KeyManager) GetActiveKeyID() (keyID string) {
if m.jwk == nil {
return ""
}
return m.jwk.id
}
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
func (m *KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) {
if m.jwk == nil {
return nil, errors.New("failed to retrieve active private key")
}
return m.jwk.key, nil
}
// AddActiveJWK is used to add a cert and key pair.
func (m *KeyManager) AddActiveJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (jwk *JWK, err error) {
// TODO: Add a mutex when implementing key rotation to be utilized here and in methods which retrieve the JWK or JWKS.
if m.jwk, err = NewJWK(chain, key); err != nil {
return nil, err
}
m.jwks.Keys = append(m.jwks.Keys, *m.jwk.JSONWebKey())
return m.jwk, nil
}
// JWTStrategy is a decorator struct for the fosite jwt.JWTStrategy.
type JWTStrategy struct {
jwt.JWTStrategy
id string
}
// KeyID returns the key id.
func (s *JWTStrategy) KeyID() (id string) {
return s.id
}
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
return s.id, nil
}
// NewJWK creates a new JWK.
func NewJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (j *JWK, err error) {
if key == nil {
return nil, fmt.Errorf("JWK is not properly initialized: missing key")
}
j = &JWK{
key: key,
chain: chain,
}
jwk := &jose.JSONWebKey{
Algorithm: SigningAlgorithmRSAWithSHA256,
Use: "sig",
Key: &key.PublicKey,
}
var thumbprint []byte
if thumbprint, err = jwk.Thumbprint(crypto.SHA1); err != nil {
return nil, fmt.Errorf("failed to calculate SHA1 thumbprint for certificate: %w", err)
}
j.id = strings.ToLower(fmt.Sprintf("%x", thumbprint))
if len(j.id) >= 7 {
j.id = j.id[:6]
}
if len(j.id) >= 7 {
j.id = j.id[:6]
}
return j, nil
}
// JWK is a utility wrapper for JSON Web Key's.
type JWK struct {
id string
key *rsa.PrivateKey
chain schema.X509CertificateChain
}
// Strategy returns the relevant jwt.JWTStrategy for this JWT.
func (j *JWK) Strategy() (strategy jwt.JWTStrategy) {
return &JWTStrategy{id: j.id, JWTStrategy: &jwt.RS256JWTStrategy{PrivateKey: j.key}}
}
// JSONWebKey returns the relevant *jose.JSONWebKey for this JWT.
func (j *JWK) JSONWebKey() (jwk *jose.JSONWebKey) {
jwk = &jose.JSONWebKey{
Key: &j.key.PublicKey,
KeyID: j.id,
Algorithm: "RS256",
Use: "sig",
Certificates: j.chain.Certificates(),
}
if len(jwk.Certificates) != 0 {
jwk.CertificateThumbprintSHA1, jwk.CertificateThumbprintSHA256 = j.chain.Thumbprint(crypto.SHA1), j.chain.Thumbprint(crypto.SHA256)
}
return jwk
}