diff options
Diffstat (limited to 'app/models/session_key.go')
-rw-r--r-- | app/models/session_key.go | 207 |
1 files changed, 169 insertions, 38 deletions
diff --git a/app/models/session_key.go b/app/models/session_key.go index b1fdc90..c8b327e 100644 --- a/app/models/session_key.go +++ b/app/models/session_key.go | |||
@@ -8,6 +8,7 @@ import ( | |||
8 | "crypto/x509" | 8 | "crypto/x509" |
9 | "encoding/base64" | 9 | "encoding/base64" |
10 | "encoding/hex" | 10 | "encoding/hex" |
11 | "encoding/json" | ||
11 | "fmt" | 12 | "fmt" |
12 | "time" | 13 | "time" |
13 | 14 | ||
@@ -18,8 +19,16 @@ import ( | |||
18 | // that will be stored on the user's record in the user store. These keys are | 19 | // that will be stored on the user's record in the user store. These keys are |
19 | // used for signing authentication JWTs. | 20 | // used for signing authentication JWTs. |
20 | // | 21 | // |
21 | // This object is designed to be serialized to BSON. Other serializations can | 22 | // This object is designed to be serialized to and from BSON and JSON. Other |
22 | // be added in the future as needed. | 23 | // serializations can be added in the future as needed. |
24 | // | ||
25 | // The ExposePrivateKeysInJSON controls how JSON serialization of this struct | ||
26 | // works. When the field is set to false (the default) then serialization into | ||
27 | // JSON will never encode a private key, but may encode a public key. If this | ||
28 | // is set to true then the private key will be encoded into the JSON value and | ||
29 | // not the public key. SETTING THIS TO TRUE AND EXPOSING THE RESULTS TO THE | ||
30 | // USER IS A SECURITY ERROR so this should normally not be changed. This value | ||
31 | // of this field will never be persisted in any form. | ||
23 | // | 32 | // |
24 | // There are two flavors of this record. A record with a private key (which | 33 | // There are two flavors of this record. A record with a private key (which |
25 | // implies a public key) is a key that the service generated and is used by the | 34 | // implies a public key) is a key that the service generated and is used by the |
@@ -36,13 +45,14 @@ import ( | |||
36 | // private key and the service will validate the signature with the public key. | 45 | // private key and the service will validate the signature with the public key. |
37 | // These keys (as of Nov 2021) do not expire, though they can be revoked. | 46 | // These keys (as of Nov 2021) do not expire, though they can be revoked. |
38 | type SessionKey struct { | 47 | type SessionKey struct { |
39 | KeyId string | 48 | KeyId string |
40 | Description string | 49 | Description string |
41 | Revoked *time.Time | 50 | Revoked *time.Time |
42 | NotAfter *time.Time | 51 | NotAfter *time.Time |
43 | NotBefore *time.Time | 52 | NotBefore *time.Time |
44 | PublicKey crypto.PublicKey | 53 | PublicKey crypto.PublicKey |
45 | PrivateKey *ecdsa.PrivateKey | 54 | PrivateKey *ecdsa.PrivateKey |
55 | ExposePrivateKeysInJSON bool `"-" json:"-" bson:"-"` | ||
46 | } | 56 | } |
47 | 57 | ||
48 | func GenerateSessionKey(ttl time.Duration) (*SessionKey, error) { | 58 | func GenerateSessionKey(ttl time.Duration) (*SessionKey, error) { |
@@ -108,12 +118,156 @@ func (s *SessionKey) IsValid() bool { | |||
108 | return true | 118 | return true |
109 | } | 119 | } |
110 | 120 | ||
121 | // MarshalPrivateKey marshals the private key to a X509 encoded base64 string | ||
122 | func (s *SessionKey) MarshalPrivateKey() (string, error) { | ||
123 | priv, err := x509.MarshalECPrivateKey(s.PrivateKey) | ||
124 | if err != nil { | ||
125 | return "", err | ||
126 | } | ||
127 | return base64.StdEncoding.EncodeToString(priv), nil | ||
128 | } | ||
129 | |||
130 | // MarshalPublicKey marshals the public key to an X509 encoded base64 string | ||
131 | func (s *SessionKey) MarshalPublicKey() (string, error) { | ||
132 | pub, err := x509.MarshalPKIXPublicKey(s.PublicKey) | ||
133 | if err != nil { | ||
134 | return "", err | ||
135 | } | ||
136 | return base64.StdEncoding.EncodeToString(pub), nil | ||
137 | } | ||
138 | |||
139 | // UnmarshalPrivateKey unmarshals the private key from a base64 encoded X509 | ||
140 | // string into the public and private key fields. | ||
141 | func (s *SessionKey) UnmarshalPrivateKey(k string) error { | ||
142 | privb, err := base64.StdEncoding.DecodeString(k) | ||
143 | if err != nil { | ||
144 | return err | ||
145 | } | ||
146 | |||
147 | priv, err := x509.ParseECPrivateKey(privb) | ||
148 | if err != nil { | ||
149 | return err | ||
150 | } | ||
151 | |||
152 | s.PrivateKey = priv | ||
153 | s.PublicKey = priv.Public() | ||
154 | |||
155 | return nil | ||
156 | } | ||
157 | |||
158 | // UnmarshalPublicKey unmarshals the public key from a base64 encoded X509 | ||
159 | // string into the public key field. | ||
160 | func (s *SessionKey) UnmarshalPublicKey(k string) error { | ||
161 | pubb, err := base64.StdEncoding.DecodeString(k) | ||
162 | if err != nil { | ||
163 | return err | ||
164 | } | ||
165 | |||
166 | pubp, err := x509.ParsePKIXPublicKey(pubb) | ||
167 | if err != nil { | ||
168 | return err | ||
169 | } | ||
170 | |||
171 | pub, ok := pubp.(*ecdsa.PublicKey) | ||
172 | if !ok { | ||
173 | return fmt.Errorf("Failed to convert public key to *ecdsa.PublicKey") | ||
174 | } | ||
175 | |||
176 | s.PublicKey = pub | ||
177 | return nil | ||
178 | } | ||
179 | |||
180 | // MarshalJSON marshals a struct to JSON | ||
181 | // | ||
182 | // This method will have different behavior if the ExposePrivateKeysInJSON | ||
183 | // field is set in the struct (the default is false). If this field is set to | ||
184 | // true the private keys will be exposed in the JSON results. If it is false | ||
185 | // then private keys will not be exposed. The ExposePrivateKeysInJSON itself | ||
186 | // will never be serialized. | ||
187 | func (s *SessionKey) MarshalJSON() ([]byte, error) { | ||
188 | var err error | ||
189 | var privKey *string | ||
190 | var pub, priv string | ||
191 | |||
192 | if s.PrivateKey != nil && s.ExposePrivateKeysInJSON { | ||
193 | priv, err = s.MarshalPrivateKey() | ||
194 | if err != nil { | ||
195 | return nil, err | ||
196 | } | ||
197 | if priv != "" { | ||
198 | privKey = &priv | ||
199 | } | ||
200 | } | ||
201 | |||
202 | // If there's a private key and a public key set, and exposing the private | ||
203 | // key is allowed, then just save the private key. The private key already | ||
204 | // contains a copy of the public key. | ||
205 | if s.PublicKey != nil && (s.PrivateKey == nil || !s.ExposePrivateKeysInJSON) { | ||
206 | pub, err = s.MarshalPublicKey() | ||
207 | if err != nil { | ||
208 | return nil, err | ||
209 | } | ||
210 | } | ||
211 | |||
212 | return json.Marshal(struct { | ||
213 | KeyId string `json:"key_id"` | ||
214 | Revoked *time.Time `json:"revoked,omitempty"` | ||
215 | NotAfter *time.Time `json:"not_after"` | ||
216 | NotBefore *time.Time `json:"not_before"` | ||
217 | PublicKey string `json:"public_key"` | ||
218 | PrivateKey *string `json:"private_key,omitempty"` | ||
219 | }{ | ||
220 | s.KeyId, | ||
221 | s.Revoked, s.NotAfter, s.NotBefore, | ||
222 | pub, privKey, | ||
223 | }) | ||
224 | } | ||
225 | |||
226 | // UnmarshalJSON unmarshals a struct from JSON. | ||
227 | // | ||
228 | // This method does attempt to unmarshal private keys. | ||
229 | func (s *SessionKey) UnmarshalJSON(d []byte) error { | ||
230 | v := struct { | ||
231 | KeyId string `json:"key_id"` | ||
232 | Revoked *time.Time `json:"revoked,omitempty"` | ||
233 | NotAfter *time.Time `json:"not_after"` | ||
234 | NotBefore *time.Time `json:"not_before"` | ||
235 | PublicKey string `json:"public_key"` | ||
236 | PrivateKey string `json:"private_key"` | ||
237 | }{} | ||
238 | if err := json.Unmarshal(d, &v); err != nil { | ||
239 | return err | ||
240 | } | ||
241 | |||
242 | s.KeyId = v.KeyId | ||
243 | s.Revoked = v.Revoked | ||
244 | s.NotAfter = v.NotAfter | ||
245 | s.NotBefore = v.NotBefore | ||
246 | |||
247 | if v.PrivateKey != "" { | ||
248 | if err := s.UnmarshalPrivateKey(v.PrivateKey); err != nil { | ||
249 | return err | ||
250 | } | ||
251 | } | ||
252 | |||
253 | // If there was a private key then the public key was already set by | ||
254 | // decoding that private key. No need to do this a second time (also it's | ||
255 | // rather unlikely that both would be set). | ||
256 | if v.PublicKey != "" && s.PublicKey == nil { | ||
257 | if err := s.UnmarshalPublicKey(v.PublicKey); err != nil { | ||
258 | return err | ||
259 | } | ||
260 | } | ||
261 | |||
262 | return nil | ||
263 | } | ||
264 | |||
111 | func (s *SessionKey) MarshalBSON() ([]byte, error) { | 265 | func (s *SessionKey) MarshalBSON() ([]byte, error) { |
112 | var err error | 266 | var err error |
113 | var pub, priv []byte | 267 | var pub, priv string |
114 | 268 | ||
115 | if s.PrivateKey != nil { | 269 | if s.PrivateKey != nil { |
116 | priv, err = x509.MarshalECPrivateKey(s.PrivateKey) | 270 | priv, err = s.MarshalPrivateKey() |
117 | if err != nil { | 271 | if err != nil { |
118 | return nil, err | 272 | return nil, err |
119 | } | 273 | } |
@@ -122,7 +276,7 @@ func (s *SessionKey) MarshalBSON() ([]byte, error) { | |||
122 | // If there's a private key and a public key set then just save the private | 276 | // If there's a private key and a public key set then just save the private |
123 | // key. The private key already contains a copy of the public key. | 277 | // key. The private key already contains a copy of the public key. |
124 | if s.PublicKey != nil && s.PrivateKey == nil { | 278 | if s.PublicKey != nil && s.PrivateKey == nil { |
125 | pub, err = x509.MarshalPKIXPublicKey(s.PublicKey) | 279 | pub, err = s.MarshalPublicKey() |
126 | if err != nil { | 280 | if err != nil { |
127 | return nil, err | 281 | return nil, err |
128 | } | 282 | } |
@@ -138,8 +292,7 @@ func (s *SessionKey) MarshalBSON() ([]byte, error) { | |||
138 | }{ | 292 | }{ |
139 | s.KeyId, | 293 | s.KeyId, |
140 | s.Revoked, s.NotAfter, s.NotBefore, | 294 | s.Revoked, s.NotAfter, s.NotBefore, |
141 | base64.StdEncoding.EncodeToString(pub), | 295 | pub, priv, |
142 | base64.StdEncoding.EncodeToString(priv), | ||
143 | }) | 296 | }) |
144 | } | 297 | } |
145 | 298 | ||
@@ -162,40 +315,18 @@ func (s *SessionKey) UnmarshalBSON(d []byte) error { | |||
162 | s.NotBefore = v.NotBefore | 315 | s.NotBefore = v.NotBefore |
163 | 316 | ||
164 | if v.PrivateKey != "" { | 317 | if v.PrivateKey != "" { |
165 | privb, err := base64.StdEncoding.DecodeString(v.PrivateKey) | 318 | if err := s.UnmarshalPrivateKey(v.PrivateKey); err != nil { |
166 | if err != nil { | ||
167 | return err | 319 | return err |
168 | } | 320 | } |
169 | |||
170 | priv, err := x509.ParseECPrivateKey(privb) | ||
171 | if err != nil { | ||
172 | return err | ||
173 | } | ||
174 | |||
175 | s.PrivateKey = priv | ||
176 | s.PublicKey = priv.Public() | ||
177 | } | 321 | } |
178 | 322 | ||
179 | // If there was a private key then the public key was already set by | 323 | // If there was a private key then the public key was already set by |
180 | // decoding that private key. No need to do this a second time (also it's | 324 | // decoding that private key. No need to do this a second time (also it's |
181 | // rather unlikely that both would be set). | 325 | // rather unlikely that both would be set). |
182 | if v.PublicKey != "" && s.PublicKey == nil { | 326 | if v.PublicKey != "" && s.PublicKey == nil { |
183 | pubb, err := base64.StdEncoding.DecodeString(v.PublicKey) | 327 | if err := s.UnmarshalPublicKey(v.PublicKey); err != nil { |
184 | if err != nil { | ||
185 | return err | 328 | return err |
186 | } | 329 | } |
187 | |||
188 | pubp, err := x509.ParsePKIXPublicKey(pubb) | ||
189 | if err != nil { | ||
190 | return err | ||
191 | } | ||
192 | |||
193 | pub, ok := pubp.(*ecdsa.PublicKey) | ||
194 | if !ok { | ||
195 | return fmt.Errorf("Failed to convert public key to *ecdsa.PublicKey") | ||
196 | } | ||
197 | |||
198 | s.PublicKey = pub | ||
199 | } | 330 | } |
200 | 331 | ||
201 | return nil | 332 | return nil |