forked from supabase/auth
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: encrypt sensitive columns (supabase#1593)
Adds support for encrypting sensitive columns like the MFA secret and password hash. The goal with this encryption mechanism is to add yet another layer of security on top of the database permissions provided by Postgres. In the event that the database leaks or is accessed by malicious users or the database permissions are incorrectly defined, the encryption key would also be required to inspect this sensitive data. Encryption is done using AES-GCM-256. Strings that are encrypted are converted into a JSON string with this shape: ```json { "key_id": "key identifier used for encryption", "alg": "aes-gcm-hkdf", "nonce": "GCM 12 byte nonce", "data": "Base64 standard encoding of the ciphertext" } ``` As AES-GCM must not be used more than 2^32 times with a single symmetric key, and this is not that much -- imagine serving 100m users -- then this means that all users can only add 42 passwords or MFA verification factors before running into this hard limit. To fix this, a symmetric key is derived using [HKDF](https://datatracker.ietf.org/doc/html/rfc5869) with SHA256 such that the symmetric key is used together with the object ID (for passwords - the user ID, for TOTP secrets - the factor ID). This way there's a separate AES-GCM key per object, and additionally gives the security property that a malicious actor with write permissions to the database cannot swap passwords / TOTP secrets from Malice's account to Target's account. They would need to also change the UUIDs of these objects, which is likely to be hard. To turn on encryption the following configs need to be added: `GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPT=true` -- that turns on encryption for new objects. `GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPTION_KEY_ID=key-id` -- ID of the encryption key, allowing to rotate keys easily. `GOTRUE_SECURITY_DB_ENCRYPTION_ENCRYPTION_KEY=key` -- Base64 URL encoding of a 256 bit AES key Once encryption has been turned on, in order to have the rows be readable **for ever** this config must be provided with all past and future keys: `GOTRUE_SECURITY_DB_ENCRYPTION_DECRYPTION_KEYS=key-id:key` -- A map of key IDs and Base64 URL key encodings of the keys. To retire keys, you should just move the old key to the decryption keys map, and advertise the new encryption key ID. On each successful sign in with password, or any MFA verification attempt, the latest key will be used to re-encrypt the column. This also applies for the non-encrypted-to-encrypted case.
- Loading branch information
Showing
16 changed files
with
413 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -310,7 +310,10 @@ func (ts *UserTestSuite) TestUserUpdatePassword() { | |
u, err = models.FindUserByEmailAndAudience(ts.API.db, "[email protected]", ts.Config.JWT.Aud) | ||
require.NoError(ts.T(), err) | ||
|
||
require.Equal(ts.T(), c.expected.isAuthenticated, u.Authenticate(context.Background(), c.newPassword)) | ||
isAuthenticated, _, err := u.Authenticate(context.Background(), c.newPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID) | ||
require.NoError(ts.T(), err) | ||
|
||
require.Equal(ts.T(), c.expected.isAuthenticated, isAuthenticated) | ||
}) | ||
} | ||
} | ||
|
@@ -369,7 +372,10 @@ func (ts *UserTestSuite) TestUserUpdatePasswordNoReauthenticationRequired() { | |
u, err = models.FindUserByEmailAndAudience(ts.API.db, "[email protected]", ts.Config.JWT.Aud) | ||
require.NoError(ts.T(), err) | ||
|
||
require.Equal(ts.T(), c.expected.isAuthenticated, u.Authenticate(context.Background(), c.newPassword)) | ||
isAuthenticated, _, err := u.Authenticate(context.Background(), c.newPassword, ts.API.config.Security.DBEncryption.DecryptionKeys, ts.API.config.Security.DBEncryption.Encrypt, ts.API.config.Security.DBEncryption.EncryptionKeyID) | ||
require.NoError(ts.T(), err) | ||
|
||
require.Equal(ts.T(), c.expected.isAuthenticated, isAuthenticated) | ||
}) | ||
} | ||
} | ||
|
@@ -424,7 +430,10 @@ func (ts *UserTestSuite) TestUserUpdatePasswordReauthentication() { | |
u, err = models.FindUserByEmailAndAudience(ts.API.db, "[email protected]", ts.Config.JWT.Aud) | ||
require.NoError(ts.T(), err) | ||
|
||
require.True(ts.T(), u.Authenticate(context.Background(), "newpass")) | ||
isAuthenticated, _, err := u.Authenticate(context.Background(), "newpass", ts.Config.Security.DBEncryption.DecryptionKeys, ts.Config.Security.DBEncryption.Encrypt, ts.Config.Security.DBEncryption.EncryptionKeyID) | ||
require.NoError(ts.T(), err) | ||
|
||
require.True(ts.T(), isAuthenticated) | ||
require.Empty(ts.T(), u.ReauthenticationToken) | ||
require.Nil(ts.T(), u.ReauthenticationSentAt) | ||
} | ||
|
Oops, something went wrong.