Skip to content

Commit e081f30

Browse files
MB-61292: Change proof calculation for encr at rest keys
Current algorithm for calculating proof for a key uses this very key for encryption. This may cause issues when key is can't be used for encryption (e.g. there is no connection to AWS). Inability to calculate proof causes inability to save that key on disk. This leads to several usability problems and confusions, such as: 1. constant save retries causes pollution of logs with errors even when the key is not used. 2. integrity tokens can't be rotated until all keys can be saved successfully. 3. when the key is modified and can not be saved, the previous version of this key will be used for encryption of other keys, which may lead to confusion (e.g. AWS credentials file is changed, but the system keeps using prev creds file). This change's goal is to only use those parts of the key that are available locally for proof calculation. Change-Id: Ie1cdab90a81545a3a650910e58c658e16b17e00c Reviewed-on: https://review.couchbase.org/c/ns_server/+/232626 Reviewed-by: Navdeep S Boparai <[email protected]> Tested-by: Timofey Barmin <[email protected]> Well-Formed: Build Bot <[email protected]>
1 parent 8c04281 commit e081f30

File tree

4 files changed

+100
-73
lines changed

4 files changed

+100
-73
lines changed

apps/ns_babysitter/src/cb_gosecrets_runner.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,7 @@ mac_test() ->
15431543
verify_mac(Pid, <<0, 1, 2, 3>>, Data)),
15441544
?assertMatch({error, "invalid utf-8 in uuid: " ++ _},
15451545
verify_mac(Pid, RandomUUIDMac, Data)),
1546-
?assertEqual({error, "invalid mac"},
1546+
?assertMatch({error, "invalid mac (token uuid: " ++ _},
15471547
verify_mac(Pid, WrongMac, Data)),
15481548

15491549
%% Rotate tokens and make sure that old mac is still valid

apps/ns_server/src/cb_cluster_secrets.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,7 +2634,7 @@ maybe_reencrypt_data(#{type := encrypted, data := Bin,
26342634
encrypted_by => {NewSecretId, NewKekId}}}
26352635
else
26362636
{error, Error} ->
2637-
{error, encryption_service:maybe_map_not_found_key_error(
2637+
{error, encryption_service:maybe_wrap_encryption_error(
26382638
Error, failed_to_encrypt_or_decrypt_key)}
26392639
end;
26402640
%% Encrypted, but we want it to be unencrypted (encrypted by node SM actually)
@@ -2657,7 +2657,7 @@ maybe_reencrypt_data(#{type := sensitive, data := Bin,
26572657
encrypted_by => {NewSecretId, NewKekId}}}
26582658
else
26592659
{error, Error} ->
2660-
{error, encryption_service:maybe_map_not_found_key_error(
2660+
{error, encryption_service:maybe_wrap_encryption_error(
26612661
Error, failed_to_encrypt_or_decrypt_key)}
26622662
end;
26632663
%% Not encrypted, and that's right

apps/ns_server/src/encryption_service.erl

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
encrypt_key/3,
3333
decrypt_key/3,
3434
test_existing_key/1,
35-
maybe_map_not_found_key_error/2,
35+
maybe_wrap_encryption_error/2,
3636
change_password/1,
3737
get_keys_ref/0,
3838
rotate_data_key/0,
@@ -278,20 +278,17 @@ test_existing_key(KekId) when is_binary(KekId) ->
278278
end
279279
else
280280
{error, Error} ->
281-
{error, maybe_map_not_found_key_error(Error,
282-
invalid_key_settings)}
281+
{error, maybe_wrap_encryption_error(Error, invalid_key_settings)}
283282
end.
284283

285-
maybe_map_not_found_key_error({T, "no files found matching:" ++ _ = Error},
286-
Wrapper)
287-
when T == encrypt_key_error;
288-
T == decrypt_key_error ->
284+
maybe_wrap_encryption_error({T, Error}, Wrapper) when T == encrypt_key_error;
285+
T == decrypt_key_error ->
289286
%% We know that if this function is called the secret actually
290287
%% exists, so if we get this error, it means that we have problems
291288
%% saving the key on disk (likely because something is wrong with secret
292289
%% settings). Changing the error to avoid confusion.
293290
{Wrapper, Error};
294-
maybe_map_not_found_key_error(Error, _Wrapper) ->
291+
maybe_wrap_encryption_error(Error, _Wrapper) ->
295292
Error.
296293

297294
maybe_rotate_integrity_tokens(undefined) ->

deps/gocode/gosecrets/stored_keys.go

Lines changed: 92 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type storedKeyIface interface {
6161
kind() string
6262
needRewrite(*storedKeyConfig, *StoredKeysState, *storedKeysCtx) (bool, int, error)
6363
ad() []byte
64+
asBytes() ([]byte, error)
6465
encryptMe(*StoredKeysState, *storedKeysCtx) error
6566
decryptMe(bool, *StoredKeysState, *storedKeysCtx) error
6667
encryptData([]byte, []byte) ([]byte, error)
@@ -91,6 +92,7 @@ const (
9192
encryptedFileHeaderSize = 80
9293
encryptedFileKeyNameLength = byte(36)
9394
macLen = 101 // Vsn: 1B, UUID: 36B, MAC: 64B
95+
intTokenSize = 64
9496
)
9597

9698
// Struct for marshalling/unmarshalling of a raw aes-gcm stored key
@@ -286,11 +288,14 @@ func (state *StoredKeysState) maybeRegenerateProof(path, name string, ctx *store
286288
logDbg("Failed to read key %s from file %s: %s", name, filePathPrefix, err.Error())
287289
return err
288290
}
289-
parts := strings.Split(proof, ":")
290-
if len(parts) != 2 {
291-
return fmt.Errorf("file %s has invalid proof format", filePathPrefix)
291+
proofBytes, err := base64.StdEncoding.DecodeString(proof)
292+
if err != nil {
293+
return fmt.Errorf("file %s has invalid proof format: %s", filePathPrefix, err.Error())
294+
}
295+
uuid, _, err := parseMac(proofBytes)
296+
if err != nil {
297+
return fmt.Errorf("file %s has invalid proof format: %s", filePathPrefix, err.Error())
292298
}
293-
uuid := parts[0]
294299
if uuid == state.intTokens[0].uuid {
295300
// Proof is still valid, no need to regenerate proof
296301
return nil
@@ -312,7 +317,7 @@ func (state *StoredKeysState) maybeRegenerateProof(path, name string, ctx *store
312317
func (state *StoredKeysState) generateIntToken(ctx *storedKeysCtx) error {
313318
newToken := intToken{
314319
uuid: uuid.New().String(),
315-
token: createRandomKey(),
320+
token: generateRandomBytes(intTokenSize),
316321
}
317322
prevTokens := state.intTokens
318323
state.intTokens = append([]intToken{newToken}, state.intTokens...)
@@ -415,47 +420,52 @@ func (state *StoredKeysState) mac(data []byte) ([]byte, error) {
415420
return nil, fmt.Errorf("no keys")
416421
}
417422
token := state.intTokens[0]
418-
h := hmac.New(sha512.New, token.token)
419-
h.Write(data)
420-
mac := h.Sum(nil)
423+
hmacBytes := hmacSHA512(token.token, data)
421424
// Format: Version: 1 byte, TokenUUID, MAC
422425
res := append([]byte{0}, token.uuid...)
423-
res = append(res, mac...)
426+
res = append(res, hmacBytes...)
424427
if len(res) != macLen {
425428
return nil, fmt.Errorf("unexpected mac length: %d", len(res))
426429
}
427430
return res, nil
428431
}
429432

430433
func (state *StoredKeysState) verifyMac(mac, data []byte) error {
434+
uuid, hmacBytes, err := parseMac(mac)
435+
if err != nil {
436+
return err
437+
}
438+
for _, token := range state.intTokens {
439+
if token.uuid == uuid {
440+
expectedHmacBytes := hmacSHA512(token.token, data)
441+
if hmac.Equal(hmacBytes, expectedHmacBytes) {
442+
return nil
443+
}
444+
return fmt.Errorf("invalid mac (token uuid: %s)", uuid)
445+
}
446+
}
447+
return fmt.Errorf("unknown token: %s", uuid)
448+
}
449+
450+
// Format: Version = 0: 1 byte, TokenUUID (36 bytes), MAC (64 bytes)
451+
func parseMac(mac []byte) (string, []byte, error) {
431452
if len(mac) == 0 {
432-
return fmt.Errorf("mac is empty")
453+
return "", nil, fmt.Errorf("mac is empty")
433454
}
434455
if mac[0] != 0 {
435-
return fmt.Errorf("unknown mac version: %v", mac[0])
456+
return "", nil, fmt.Errorf("unknown mac version: %v", mac[0])
436457
}
437458

438459
if len(mac) != macLen {
439-
return fmt.Errorf("unexpected mac length: %d", len(mac))
460+
return "", nil, fmt.Errorf("unexpected mac length: %d", len(mac))
440461
}
441462
uuidBytes := mac[1:37]
442463
if !utf8.Valid(uuidBytes) {
443-
return fmt.Errorf("invalid utf-8 in uuid: %q", uuidBytes)
464+
return "", nil, fmt.Errorf("invalid utf-8 in uuid: %q", uuidBytes)
444465
}
445466
uuid := string(uuidBytes)
446-
mac = mac[37:]
447-
for _, token := range state.intTokens {
448-
if token.uuid == uuid {
449-
h := hmac.New(sha512.New, token.token)
450-
h.Write(data)
451-
expectedMac := h.Sum(nil)
452-
if hmac.Equal(mac, expectedMac) {
453-
return nil
454-
}
455-
return fmt.Errorf("invalid mac")
456-
}
457-
}
458-
return fmt.Errorf("unknown token: %s", uuid)
467+
hmacBytes := mac[37:]
468+
return uuid, hmacBytes, nil
459469
}
460470

461471
func (state *StoredKeysState) revalidateKeyCache(ctx *storedKeysCtx) {
@@ -850,9 +860,9 @@ func (state *StoredKeysState) decryptKey(keyIface storedKeyIface, validateProof
850860
return err
851861
}
852862
if validateProof {
853-
err = validateKeyProof(keyIface, proof, state.intTokens)
863+
err = state.validateKeyProof(keyIface, proof)
854864
if err != nil {
855-
return err
865+
return fmt.Errorf("key integrity check failed for key %s: %s", keyIface.name(), err.Error())
856866
}
857867
}
858868
if keyIface.canBeCached() {
@@ -872,7 +882,7 @@ func (state *StoredKeysState) writeKeyToFile(keyIface storedKeyIface, curVsn int
872882
if err != nil {
873883
return err
874884
}
875-
proof, err := generateKeyProof(keyIface, state.intTokens)
885+
proof, err := state.generateKeyProof(keyIface)
876886
if err != nil {
877887
return err
878888
}
@@ -908,48 +918,40 @@ func (state *StoredKeysState) writeKeyToFile(keyIface storedKeyIface, curVsn int
908918
return nil
909919
}
910920

911-
func generateKeyProof(keyIface storedKeyIface, intTokens []intToken) (string, error) {
912-
if len(intTokens) == 0 {
913-
return "", fmt.Errorf("failed to generate proof: empty integrity tokens")
921+
func (state *StoredKeysState) generateKeyProof(keyIface storedKeyIface) (string, error) {
922+
keyBytes, err := keyIface.asBytes()
923+
if err != nil {
924+
return "", fmt.Errorf("failed to convert key to bytes: %s", err.Error())
914925
}
915-
tokenHash := tokenHash(intTokens[0], keyIface)
916-
encryptedTokenHash, err := keyIface.encryptData(tokenHash[:], []byte(keyIface.name()))
926+
proofBytes, err := state.mac(keyBytes)
917927
if err != nil {
918-
return "", fmt.Errorf("failed to use key to generate proof: %s", err.Error())
928+
return "", err
919929
}
920-
proof := fmt.Sprintf("%s:%s", intTokens[0].uuid, base64.StdEncoding.EncodeToString(encryptedTokenHash))
921-
return proof, nil
930+
proofStr := base64.StdEncoding.EncodeToString(proofBytes)
931+
return proofStr, nil
922932
}
923933

924-
func validateKeyProof(keyIface storedKeyIface, proof string, intTokens []intToken) error {
925-
parts := strings.Split(proof, ":")
926-
if len(parts) != 2 {
927-
return fmt.Errorf("key integrity check failed: invalid proof format")
934+
func (state *StoredKeysState) validateKeyProof(keyIface storedKeyIface, proof string) error {
935+
proofBytes, err := base64.StdEncoding.DecodeString(proof)
936+
if err != nil {
937+
return fmt.Errorf("failed to decode proof: %s", err.Error())
928938
}
929-
uuid := parts[0]
930-
encryptedTokenHashBase64 := parts[1]
931-
for _, token := range intTokens {
932-
if token.uuid == uuid {
933-
encryptedTokenHash, err := base64.StdEncoding.DecodeString(encryptedTokenHashBase64)
934-
if err != nil {
935-
return fmt.Errorf("key integrity check failed: failed to decode encrypted token hash: %s", err.Error())
936-
}
937-
decryptedTokenHash, err := keyIface.decryptData(encryptedTokenHash, []byte(keyIface.name()))
938-
if err != nil {
939-
return fmt.Errorf("key integrity check failed: failed to decrypt proof: %s", err.Error())
940-
}
941-
tokenHash := tokenHash(token, keyIface)
942-
if bytes.Equal(tokenHash[:], decryptedTokenHash) {
943-
return nil
944-
}
945-
return fmt.Errorf("key integrity check failed: invalid integrity token (token uuid: %s, key name: %s)", uuid, keyIface.name())
946-
}
939+
keyBytes, err := keyIface.asBytes()
940+
if err != nil {
941+
return fmt.Errorf("failed to convert key to bytes: %s", err.Error())
947942
}
948-
return fmt.Errorf("key integrity check failed: unknown token: %s (key name: %s)", uuid, keyIface.name())
943+
err = state.verifyMac(proofBytes, keyBytes)
944+
if err != nil {
945+
return err
946+
}
947+
return nil
949948
}
950949

951-
func tokenHash(token intToken, keyIface storedKeyIface) [64]byte {
952-
return sha512.Sum512(append(token.token, []byte(keyIface.name())...))
950+
func hmacSHA512(key []byte, data []byte) []byte {
951+
h := hmac.New(sha512.New, key)
952+
h.Write(data)
953+
mac := h.Sum(nil)
954+
return mac
953955
}
954956

955957
func (state *StoredKeysState) encryptWithKey(keyKind, keyName string, data, AD []byte, ctx *storedKeysCtx) ([]byte, error) {
@@ -1313,6 +1315,13 @@ func (k *rawAesGcmStoredKey) ad() []byte {
13131315
return []byte(string(rawAESGCMKey) + k.Name + k.Kind + k.CreationTime + k.EncryptionKeyName + strconv.FormatBool(k.CanBeCached))
13141316
}
13151317

1318+
func (k *rawAesGcmStoredKey) asBytes() ([]byte, error) {
1319+
if k.DecryptedKey == nil {
1320+
return nil, fmt.Errorf("key %s is encrypted", k.Name)
1321+
}
1322+
return append(k.ad(), k.DecryptedKey...), nil
1323+
}
1324+
13161325
func (k *rawAesGcmStoredKey) encryptMe(state *StoredKeysState, ctx *storedKeysCtx) error {
13171326
if k.EncryptedKey != nil {
13181327
// Seems like it is already encrypted
@@ -1449,6 +1458,20 @@ func (k *awsStoredKey) ad() []byte {
14491458
return []byte("")
14501459
}
14511460

1461+
func (k *awsStoredKey) asBytes() ([]byte, error) {
1462+
return []byte(
1463+
string(awskmKey) +
1464+
k.Name +
1465+
k.Kind +
1466+
k.KeyArn +
1467+
k.Region +
1468+
k.ConfigFile +
1469+
k.CredsFile +
1470+
k.Profile +
1471+
strconv.FormatBool(k.UseIMDS) +
1472+
k.CreationTime), nil
1473+
}
1474+
14521475
func (k *awsStoredKey) encryptMe(state *StoredKeysState, ctx *storedKeysCtx) error {
14531476
// Nothing to encrypt here, configuration should contain no secrets
14541477
return nil
@@ -1625,6 +1648,13 @@ func (k *kmipStoredKey) ad() []byte {
16251648
k.CreationTime)
16261649
}
16271650

1651+
func (k *kmipStoredKey) asBytes() ([]byte, error) {
1652+
if k.decryptedPassphrase == nil {
1653+
return nil, fmt.Errorf("key %s is encrypted", k.Name)
1654+
}
1655+
return append(k.ad(), k.decryptedPassphrase...), nil
1656+
}
1657+
16281658
func (k *kmipStoredKey) encryptMe(state *StoredKeysState, ctx *storedKeysCtx) error {
16291659
if k.EncryptedPassphrase != nil {
16301660
// Seems like it is already encrypted

0 commit comments

Comments
 (0)