@@ -2,6 +2,7 @@ package keyloader
22
33import (
44 "fmt"
5+ "sort"
56 "strconv"
67 "sync"
78 "sync/atomic"
@@ -202,6 +203,7 @@ func reorderTimestamp(s *configstore.Item) int64 {
202203 i , err := s .Unmarshaled ()
203204 if err == nil {
204205 ret := i .(* KeyConfig ).Timestamp
206+ // small hack to tiebreak in favor of sealed keys (mostly in case of zero-value timestamp)
205207 if i .(* KeyConfig ).Sealed {
206208 ret ++
207209 }
@@ -218,7 +220,62 @@ func configFactory() interface{} {
218220** CONSTRUCTORS
219221 */
220222
223+ // NewKey returns a symmecrypt.Key object configured from a number of KeyConfig objects.
224+ // If several KeyConfigs are supplied, the returned Key will be composite.
225+ // A composite key encrypts with the latest Key (based on timestamp) and decrypts with any of they keys.
226+ //
227+ // If the key configuration specifies it is sealed, the key returned will be wrapped by an unseal mechanism.
228+ // When the symmecrypt/seal global singleton gets unsealed, the key will become usable instantly. It will return errors in the meantime.
229+ //
230+ // The key cipher name is expected to match a KeyFactory that got registered through RegisterCipher().
231+ // Either use a built-in cipher, or make sure to register a proper factory for this cipher.
232+ // This KeyFactory will be called, either directly or when the symmecrypt/seal global singleton gets unsealed, if applicable.
233+ func NewKey (cfgs ... * KeyConfig ) (symmecrypt.Key , error ) {
234+
235+ if len (cfgs ) == 0 {
236+ return nil , errors .New ("missing key config" )
237+ }
238+
239+ // sort by timestamp: latest (bigger timestamp) first
240+ sort .Slice (cfgs , func (i , j int ) bool { return cfgs [i ].Timestamp > cfgs [j ].Timestamp })
241+
242+ firstNonSealed := ! cfgs [0 ].Sealed
243+ comp := symmecrypt.CompositeKey {}
244+
245+ for _ , cfg := range cfgs {
246+
247+ var ref symmecrypt.Key
248+ factory , err := symmecrypt .GetKeyFactory (cfg .Cipher )
249+ if err != nil {
250+ return nil , err
251+ }
252+ if cfg .Sealed {
253+ // if the first position (used for encryption in composite keys) was not sealed, but other keys used for fallback decryption are sealed
254+ // it may be an attack to trigger a reencrypt with a key known by the attacker
255+ if firstNonSealed {
256+ return nil , errors .New ("DANGER! Detected downgrade to non-sealed encryption key. Non-sealed key has higher priority, this looks malicious. Aborting!" )
257+ }
258+ ref = newSealedKey (cfg , factory )
259+ } else {
260+ ref , err = factory .NewKey (cfg .Key )
261+ if err != nil {
262+ return nil , err
263+ }
264+ }
265+
266+ comp = append (comp , ref )
267+ }
268+
269+ // if only a single key config was provided, decapsulate the composite key
270+ if len (comp ) == 1 {
271+ return comp [0 ], nil
272+ }
273+
274+ return comp , nil
275+ }
276+
221277// LoadKey instantiates a new encryption key for a given identifier from the default store in configstore.
278+ // It retrieves all the necessary data from configstore then calls NewKey().
222279//
223280// If several keys are found for the identifier, they are sorted by timestamp, and a composite key is returned.
224281// The most recent key will be used for encryption, and decryption will be done by any of them.
@@ -235,6 +292,7 @@ func LoadKey(identifier string) (symmecrypt.Key, error) {
235292}
236293
237294// LoadKeyFromStore instantiates a new encryption key for a given identifier from a specific store instance.
295+ // It retrieves all the necessary data from configstore then calls NewKey().
238296//
239297// If several keys are found for the identifier, they are sorted by timestamp, and a composite key is returned.
240298// The most recent key will be used for encryption, and decryption will be done by any of them.
@@ -261,52 +319,34 @@ func LoadKeyFromStore(identifier string, store *configstore.Store) (symmecrypt.K
261319 return nil , fmt .Errorf ("ambiguous config: several encryption keys conflicting for '%s'" , identifier )
262320 }
263321
264- comp := symmecrypt.CompositeKey {}
265-
266- hadNonSealed := false
322+ var cfgs []* KeyConfig
267323
268324 for _ , item := range items .Items {
269-
270325 i , err := item .Unmarshaled ()
271326 if err != nil {
272327 return nil , err
273328 }
274- var ref symmecrypt.Key
275329 cfg := i .(* KeyConfig )
276- factory , err := symmecrypt .GetKeyFactory (cfg .Cipher )
277- if err != nil {
278- return nil , err
279- }
280- if cfg .Sealed {
281- if hadNonSealed {
282- panic (fmt .Sprintf ("encryption key '%s': DANGER! Detected downgrade to non-sealed encryption key. Non-sealed key has higher priority, this looks malicious. Aborting!" , identifier ))
283- }
284- ref = newSealedKey (cfg , factory )
285- } else {
286- hadNonSealed = true
287- ref , err = factory .NewKey (cfg .Key )
288- if err != nil {
289- return nil , err
290- }
291- }
292-
293- comp = append (comp , ref )
330+ cfgs = append (cfgs , cfg )
294331 }
295332
296- if len (comp ) == 1 {
297- return comp [0 ], nil
333+ key , err := NewKey (cfgs ... )
334+ if err != nil {
335+ return nil , fmt .Errorf ("encryption key '%s': %s" , identifier , err )
298336 }
299337
300- return comp , nil
338+ return key , nil
301339}
302340
303341// LoadSingleKey instantiates a new encryption key using LoadKey from the default store in configstore without specifying its identifier.
342+ // It retrieves all the necessary data from configstore then calls NewKey().
304343// It will error if several different identifiers are found.
305344func LoadSingleKey () (symmecrypt.Key , error ) {
306345 return LoadSingleKeyFromStore (configstore .DefaultStore )
307346}
308347
309348// LoadSingleKey instantiates a new encryption key using LoadKey from a specific store instance without specifying its identifier.
349+ // It retrieves all the necessary data from configstore then calls NewKey().
310350// It will error if several different identifiers are found.
311351func LoadSingleKeyFromStore (store * configstore.Store ) (symmecrypt.Key , error ) {
312352 ident , err := singleKeyIdentifier (store )
0 commit comments