-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added app level encryption feature. #599
Added app level encryption feature. #599
Conversation
Codecov Report
@@ Coverage Diff @@
## main #599 +/- ##
==========================================
+ Coverage 25.70% 26.97% +1.27%
==========================================
Files 46 48 +2
Lines 5699 5860 +161
==========================================
+ Hits 1465 1581 +116
- Misses 3951 3995 +44
- Partials 283 284 +1
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be great to have tests that exercise different confidence levels (no encryption, local encryption, hsm encryption)
for hsm we would need a reliable cloud test env, so maybe that's out of scope for now but would be good to track in a future issue.
similarly, we should track issues for managing key rotation.
if you didn't (I may have missed) it would be useful to highlight that we've specifically chosen an authenticated encryption scheme which gives us the added benefit of tamper evidence, and may also prove useful for decrypting/re-encrypting data during key rotations since we'll be able to see which key encrypted the data
another thing worth covering in the doc is the data that we need to keep in plaintext, and the considerations around that, for keys. these keys are often just UUIDs but sometimes contain other information to make prefix queries viable. that means they could contain a DID, which by someones definition may be PII. <-- not sure here but we should verify
} | ||
|
||
type EncryptionConfig struct { | ||
DisableEncryption bool `toml:"disable_encryption"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have some circuit-breaker logic that doesn't allow this to be a set, or at least throws a warning if env == prod?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some in a061870
@@ -72,6 +72,10 @@ type ServicesConfig struct { | |||
StorageOptions []storage.Option `toml:"storage_option"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
general comment: it would be great to have a light design doc around this kind of thing before going for an implementation. there are a number of hairy details that would be good to discuss and get feedback on. not only that it would serve as a record for how the feature was thought through which will be useful in the future (as we have already seen with existing SIPs)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't deem it worth making a doc at the time I started this. Seemed pretty straightforward. I'll do one in a separate PR. Tracked in #602
doc/STORAGE.md
Outdated
# encryption | ||
disable_encryption = true | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is great to have
pkg/encryption/encryption.go
Outdated
"context" | ||
"strings" | ||
|
||
util2 "github.com/TBD54566975/ssi-sdk/util" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
util2 "github.com/TBD54566975/ssi-sdk/util" | |
sdkutil "github.com/TBD54566975/ssi-sdk/util" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
type KeyResolver func(ctx context.Context) ([]byte, error) | ||
|
||
type XChaCha20Poly1305Encrypter struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this only used locally since the doc above mentions only AES GCM is supported by HSMs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only used when the encryption keys are stored locally (either KEK or DEK).
@@ -50,20 +49,17 @@ func (s Service) Config() config.KeyStoreServiceConfig { | |||
} | |||
|
|||
func NewKeyStoreService(config config.KeyStoreServiceConfig, s storage.ServiceStorage) (*Service, error) { | |||
if err := EnsureServiceKeyExists(config, s); err != nil { | |||
return nil, sdkutil.LoggingErrorMsg(err, "initializing keystore") | |||
encrypter, decrypter, err := NewServiceEncryption(s, config.EncryptionConfig, ServiceKeyEncryptionKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer, that instead of a noopEncrypter
and noopDecrypter
we check config here, and if the flag is off we pass in nil values below, this avoids the overhead of a no-op call for each read/write
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, I'm not quite sure there is any overhead.
A conditional statement evaluating whether an encrypter is nil
is the same overhead as making a call that does a noop on a non-nil object. In fact, the latter might end up being optimized away by the compiler. Not having if statements typically helps with branch prediction.
I changed this internally so nil
values can be returned, but I'm not convinced it's easier to read.
kt: crypto.RSA, | ||
}, | ||
} | ||
for _, test := range tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why delete this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The {En,De}cryptKey
methods weren't being used, so I removed that test. After evaluating, the better thing to do is to make these tests acts on XChaCha20Poly1305Encrypter
. I've moved this to the relevant file.
pkg/service/keystore/storage.go
Outdated
return nil | ||
} | ||
|
||
watchKeys := []storage.WatchKey{{ | ||
Namespace: namespace, | ||
Key: skKey, | ||
Key: encryptionKeyKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe encryptionKeyID
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Settled for encryptionMaterialKey
.
pkg/service/service.go
Outdated
if err != nil { | ||
return nil, errors.Wrap(err, "creating app level encrypter") | ||
} | ||
storageProvider := storage.NewEncryptedWrapper(unencryptedStorageProvider, storageEncrypter, storageDecrypter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is a no-op the encrypted wrapper is unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
keyStoreServiceFactory := keystore.NewKeyStoreServiceFactory(config.KeyStoreConfig, storageProvider) | ||
keyStoreServiceFactory := keystore.NewKeyStoreServiceFactory(config.KeyStoreConfig, storageProvider, keyEncrypter, keyDecrypter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed internally to handle possible nil key{En,De}crypter
values.
no encryption is covered in unit tests. Local encryption is covered in integration tests. HSM will be tracked in #600
I'm going to leave that for the same issue as above (i.e. #601 )
We could encrypt keys as long as we preserve the key prefix structure. That said, it doesn't seem like it's worth the effort and additional complexity. I added some more info to the docs, and created another issue to track #603 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like these never went through :O
} | ||
|
||
type EncryptionConfig struct { | ||
DisableEncryption bool `toml:"disable_encryption"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some in a061870
|
||
type KeyResolver func(ctx context.Context) ([]byte, error) | ||
|
||
type XChaCha20Poly1305Encrypter struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only used when the encryption keys are stored locally (either KEK or DEK).
@@ -50,20 +49,17 @@ func (s Service) Config() config.KeyStoreServiceConfig { | |||
} | |||
|
|||
func NewKeyStoreService(config config.KeyStoreServiceConfig, s storage.ServiceStorage) (*Service, error) { | |||
if err := EnsureServiceKeyExists(config, s); err != nil { | |||
return nil, sdkutil.LoggingErrorMsg(err, "initializing keystore") | |||
encrypter, decrypter, err := NewServiceEncryption(s, config.EncryptionConfig, ServiceKeyEncryptionKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, I'm not quite sure there is any overhead.
A conditional statement evaluating whether an encrypter is nil
is the same overhead as making a call that does a noop on a non-nil object. In fact, the latter might end up being optimized away by the compiler. Not having if statements typically helps with branch prediction.
I changed this internally so nil
values can be returned, but I'm not convinced it's easier to read.
kt: crypto.RSA, | ||
}, | ||
} | ||
for _, test := range tests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The {En,De}cryptKey
methods weren't being used, so I removed that test. After evaluating, the better thing to do is to make these tests acts on XChaCha20Poly1305Encrypter
. I've moved this to the relevant file.
pkg/service/keystore/storage.go
Outdated
return nil | ||
} | ||
|
||
watchKeys := []storage.WatchKey{{ | ||
Namespace: namespace, | ||
Key: skKey, | ||
Key: encryptionKeyKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Settled for encryptionMaterialKey
.
pkg/service/service.go
Outdated
if err != nil { | ||
return nil, errors.Wrap(err, "creating app level encrypter") | ||
} | ||
storageProvider := storage.NewEncryptedWrapper(unencryptedStorageProvider, storageEncrypter, storageDecrypter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
keyStoreServiceFactory := keystore.NewKeyStoreServiceFactory(config.KeyStoreConfig, storageProvider) | ||
keyStoreServiceFactory := keystore.NewKeyStoreServiceFactory(config.KeyStoreConfig, storageProvider, keyEncrypter, keyDecrypter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed internally to handle possible nil key{En,De}crypter
values.
pkg/encryption/encryption.go
Outdated
"context" | ||
"strings" | ||
|
||
util2 "github.com/TBD54566975/ssi-sdk/util" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -72,6 +72,10 @@ type ServicesConfig struct { | |||
StorageOptions []storage.Option `toml:"storage_option"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't deem it worth making a doc at the time I started this. Seemed pretty straightforward. I'll do one in a separate PR. Tracked in #602
Overview
Allow for operators of ssi-service to choose whether to do app level encryption.
Description
At a high-level, this PR is adding a Data Encryption Key. The DEK can be stored either in the configured storage, or in a KMS. The DEK is used for every read/write that happens in the application.
Specifically, this PR does the following:
EncryptedWrapper
, which takes in any implementation of a storage, and adds an encryption layer.How Has This Been Tested?
All unit tests and integration tests pass when encryption is enabled (see the dev.toml file).