diff --git a/cmd/crypto/error.go b/cmd/crypto/error.go index 5e35148c8..d3a3a2c6a 100644 --- a/cmd/crypto/error.go +++ b/cmd/crypto/error.go @@ -62,6 +62,8 @@ var ( errInvalidInternalIV = Error{"The internal encryption IV is malformed"} errInvalidInternalSealAlgorithm = Error{"The internal seal algorithm is invalid and not supported"} + + errMissingUpdatedKey = Error{"The key update returned no error but also no sealed key"} ) var ( diff --git a/cmd/crypto/kms.go b/cmd/crypto/kms.go index 3eb9bf8ef..53a27252b 100644 --- a/cmd/crypto/kms.go +++ b/cmd/crypto/kms.go @@ -86,6 +86,19 @@ type KMS interface { // referenced by the keyID. The provided context must // match the context used to generate the sealed key. UnsealKey(keyID string, sealedKey []byte, context Context) (key [32]byte, err error) + + // UpdateKey re-wraps the sealedKey if the master key, referenced by + // `keyID`, has changed in the meantime. This usually happens when the + // KMS operator performs a key-rotation operation of the master key. + // UpdateKey fails if the provided sealedKey cannot be decrypted using + // the master key referenced by keyID. + // + // UpdateKey makes no guarantees whatsoever about whether the returned + // rotatedKey is actually different from the sealedKey. If nothing has + // changed at the KMS or if the KMS does not support updating generated + // keys this method may behave like a NOP and just return the sealedKey + // itself. + UpdateKey(keyID string, sealedKey []byte, context Context) (rotatedKey []byte, err error) } type masterKeyKMS struct { @@ -126,6 +139,13 @@ func (kms *masterKeyKMS) UnsealKey(keyID string, sealedKey []byte, ctx Context) return key, nil } +func (kms *masterKeyKMS) UpdateKey(keyID string, sealedKey []byte, ctx Context) ([]byte, error) { + if _, err := kms.UnsealKey(keyID, sealedKey, ctx); err != nil { + return nil, err + } + return sealedKey, nil // The master key cannot update data keys -> Do nothing. +} + func (kms *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte) { if context == nil { context = Context{} diff --git a/cmd/crypto/kms_test.go b/cmd/crypto/kms_test.go index 8884ee9d2..8e985d04a 100644 --- a/cmd/crypto/kms_test.go +++ b/cmd/crypto/kms_test.go @@ -51,11 +51,20 @@ func TestMasterKeyKMS(t *testing.T) { t.Errorf("Test %d: KMS failed to unseal the generated key: %v", i, err) } if err == nil && test.ShouldFail { - t.Errorf("Test %d: KMS unsealed the generated successfully but should have failed", i) + t.Errorf("Test %d: KMS unsealed the generated key successfully but should have failed", i) } if !test.ShouldFail && !bytes.Equal(key[:], unsealedKey[:]) { t.Errorf("Test %d: The generated and unsealed key differ", i) } + + rotatedKey, err := kms.UpdateKey(test.UnsealKeyID, sealedKey, test.UnsealContext) + if err == nil && test.ShouldFail { + t.Errorf("Test %d: KMS updated the generated key successfully but should have failed", i) + } + if !test.ShouldFail && !bytes.Equal(rotatedKey, sealedKey[:]) { + t.Errorf("Test %d: The updated and sealed key differ", i) + } + } } diff --git a/cmd/crypto/vault.go b/cmd/crypto/vault.go index fd651294a..1ccd39747 100644 --- a/cmd/crypto/vault.go +++ b/cmd/crypto/vault.go @@ -250,3 +250,30 @@ func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (k copy(key[:], []byte(plainKey)) return key, nil } + +// UpdateKey re-wraps the sealedKey if the master key referenced by the keyID +// has been changed by the KMS operator - i.e. the master key has been rotated. +// If the master key hasn't changed since the sealedKey has been created / updated +// it may return the same sealedKey as rotatedKey. +// +// The context must be same context as the one provided while +// generating the plaintext key / sealedKey. +func (v *vaultService) UpdateKey(keyID string, sealedKey []byte, ctx Context) (rotatedKey []byte, err error) { + var contextStream bytes.Buffer + ctx.WriteTo(&contextStream) + + payload := map[string]interface{}{ + "ciphertext": string(sealedKey), + "context": base64.StdEncoding.EncodeToString(contextStream.Bytes()), + } + s, err := v.client.Logical().Write(fmt.Sprintf("/transit/rewrap/%s", keyID), payload) + if err != nil { + return nil, err + } + ciphertext, ok := s.Data["ciphertext"] + if !ok { + return nil, errMissingUpdatedKey + } + rotatedKey = ciphertext.([]byte) + return rotatedKey, nil +} diff --git a/docs/kms/README.md b/docs/kms/README.md index 6bf4235b4..a19bc916f 100644 --- a/docs/kms/README.md +++ b/docs/kms/README.md @@ -141,8 +141,8 @@ path "transit/datakey/plaintext/my-minio-key" { path "transit/decrypt/my-minio-key" { capabilities = [ "read", "update"] } -path "transit/encrypt/my-minio-key" { - capabilities = [ "read", "update"] +path "transit/rewrap/my-minio-key" { + capabilities = ["update"] } EOF