blob: fa9db20c92807b283aada1c936f823902d24d724 [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001// Copyright 2018 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package auth
16
17import (
18 "crypto"
19 "crypto/ecdsa"
20 "crypto/ed25519"
21 "crypto/rsa"
22 "fmt"
23 "os"
24 "time"
25
26 "github.com/golang-jwt/jwt/v5"
27)
28
29const (
30 optSignMethod = "sign-method"
31 optPublicKey = "pub-key"
32 optPrivateKey = "priv-key"
33 optTTL = "ttl"
34)
35
36var knownOptions = map[string]bool{
37 optSignMethod: true,
38 optPublicKey: true,
39 optPrivateKey: true,
40 optTTL: true,
41}
42
43// DefaultTTL will be used when a 'ttl' is not specified
44var DefaultTTL = 5 * time.Minute
45
46type jwtOptions struct {
47 SignMethod jwt.SigningMethod
48 PublicKey []byte
49 PrivateKey []byte
50 TTL time.Duration
51}
52
53// ParseWithDefaults will load options from the specified map or set defaults where appropriate
54func (opts *jwtOptions) ParseWithDefaults(optMap map[string]string) error {
55 if opts.TTL == 0 && optMap[optTTL] == "" {
56 opts.TTL = DefaultTTL
57 }
58
59 return opts.Parse(optMap)
60}
61
62// Parse will load options from the specified map
63func (opts *jwtOptions) Parse(optMap map[string]string) error {
64 var err error
65 if ttl := optMap[optTTL]; ttl != "" {
66 opts.TTL, err = time.ParseDuration(ttl)
67 if err != nil {
68 return err
69 }
70 }
71
72 if file := optMap[optPublicKey]; file != "" {
73 opts.PublicKey, err = os.ReadFile(file)
74 if err != nil {
75 return err
76 }
77 }
78
79 if file := optMap[optPrivateKey]; file != "" {
80 opts.PrivateKey, err = os.ReadFile(file)
81 if err != nil {
82 return err
83 }
84 }
85
86 // signing method is a required field
87 method := optMap[optSignMethod]
88 opts.SignMethod = jwt.GetSigningMethod(method)
89 if opts.SignMethod == nil {
90 return ErrInvalidAuthMethod
91 }
92
93 return nil
94}
95
96// Key will parse and return the appropriately typed key for the selected signature method
97func (opts *jwtOptions) Key() (any, error) {
98 switch opts.SignMethod.(type) {
99 case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS:
100 return opts.rsaKey()
101 case *jwt.SigningMethodECDSA:
102 return opts.ecKey()
103 case *jwt.SigningMethodEd25519:
104 return opts.edKey()
105 case *jwt.SigningMethodHMAC:
106 return opts.hmacKey()
107 default:
108 return nil, fmt.Errorf("unsupported signing method: %T", opts.SignMethod)
109 }
110}
111
112func (opts *jwtOptions) hmacKey() (any, error) {
113 if len(opts.PrivateKey) == 0 {
114 return nil, ErrMissingKey
115 }
116 return opts.PrivateKey, nil
117}
118
119func (opts *jwtOptions) rsaKey() (any, error) {
120 var (
121 priv *rsa.PrivateKey
122 pub *rsa.PublicKey
123 err error
124 )
125
126 if len(opts.PrivateKey) > 0 {
127 priv, err = jwt.ParseRSAPrivateKeyFromPEM(opts.PrivateKey)
128 if err != nil {
129 return nil, err
130 }
131 }
132
133 if len(opts.PublicKey) > 0 {
134 pub, err = jwt.ParseRSAPublicKeyFromPEM(opts.PublicKey)
135 if err != nil {
136 return nil, err
137 }
138 }
139
140 if priv == nil {
141 if pub == nil {
142 // Neither key given
143 return nil, ErrMissingKey
144 }
145 // Public key only, can verify tokens
146 return pub, nil
147 }
148
149 // both keys provided, make sure they match
150 if pub != nil && !pub.Equal(priv.Public()) {
151 return nil, ErrKeyMismatch
152 }
153
154 return priv, nil
155}
156
157func (opts *jwtOptions) ecKey() (any, error) {
158 var (
159 priv *ecdsa.PrivateKey
160 pub *ecdsa.PublicKey
161 err error
162 )
163
164 if len(opts.PrivateKey) > 0 {
165 priv, err = jwt.ParseECPrivateKeyFromPEM(opts.PrivateKey)
166 if err != nil {
167 return nil, err
168 }
169 }
170
171 if len(opts.PublicKey) > 0 {
172 pub, err = jwt.ParseECPublicKeyFromPEM(opts.PublicKey)
173 if err != nil {
174 return nil, err
175 }
176 }
177
178 if priv == nil {
179 if pub == nil {
180 // Neither key given
181 return nil, ErrMissingKey
182 }
183 // Public key only, can verify tokens
184 return pub, nil
185 }
186
187 // both keys provided, make sure they match
188 if pub != nil && !pub.Equal(priv.Public()) {
189 return nil, ErrKeyMismatch
190 }
191
192 return priv, nil
193}
194
195func (opts *jwtOptions) edKey() (any, error) {
196 var (
197 priv ed25519.PrivateKey
198 pub ed25519.PublicKey
199 err error
200 )
201
202 if len(opts.PrivateKey) > 0 {
203 var privKey crypto.PrivateKey
204 privKey, err = jwt.ParseEdPrivateKeyFromPEM(opts.PrivateKey)
205 if err != nil {
206 return nil, err
207 }
208 priv = privKey.(ed25519.PrivateKey)
209 }
210
211 if len(opts.PublicKey) > 0 {
212 var pubKey crypto.PublicKey
213 pubKey, err = jwt.ParseEdPublicKeyFromPEM(opts.PublicKey)
214 if err != nil {
215 return nil, err
216 }
217 pub = pubKey.(ed25519.PublicKey)
218 }
219
220 if priv == nil {
221 if pub == nil {
222 // Neither key given
223 return nil, ErrMissingKey
224 }
225 // Public key only, can verify tokens
226 return pub, nil
227 }
228
229 // both keys provided, make sure they match
230 if pub != nil && !pub.Equal(priv.Public()) {
231 return nil, ErrKeyMismatch
232 }
233
234 return priv, nil
235}