From ed214e3871cb0f4f51ad974b43cb3d9172eaf766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 22 Jul 2022 17:26:33 -0700 Subject: [PATCH 1/3] Support local IV generation --- .../keys/crypto/_providers/local_provider.py | 12 ++++++++++++ .../azure-keyvault-keys/tests/test_local_crypto.py | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py index df8c03638bc1..b5dd308d328f 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py @@ -3,6 +3,7 @@ # Licensed under the MIT License. # ------------------------------------ import abc +import os from typing import TYPE_CHECKING from azure.core.exceptions import AzureError @@ -63,6 +64,17 @@ def _raise_if_unsupported(self, operation, algorithm): def encrypt(self, algorithm, plaintext, iv=None): # type: (EncryptionAlgorithm, bytes, Optional[bytes]) -> EncryptResult self._raise_if_unsupported(KeyOperation.encrypt, algorithm) + + # If an IV isn't provided with AES-CBCPAD encryption, try to create one + if iv is None and algorithm.value.endswith("CBCPAD"): + try: + iv = os.urandom(16) + except NotImplementedError as ex: + raise RuntimeError( + "An IV could not be generated on this OS. Please provide your own cryptographically random, " + "non-repeating IV for local cryptography." + ) from ex + ciphertext = self._internal_key.encrypt(plaintext, algorithm=algorithm.value, iv=iv) return EncryptResult( key_id=self._key.kid, algorithm=algorithm, ciphertext=ciphertext, iv=iv # type: ignore[attr-defined] diff --git a/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py b/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py index edcb4e739779..52ec32992543 100644 --- a/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py +++ b/sdk/keyvault/azure-keyvault-keys/tests/test_local_crypto.py @@ -64,6 +64,15 @@ def test_symmetric_encrypt_decrypt(algorithm, key_size): encrypt_result = provider.encrypt(algorithm, plaintext, iv=iv) assert encrypt_result.key_id == key.id + assert encrypt_result.iv == iv + + decrypt_result = provider.decrypt(encrypt_result.algorithm, encrypt_result.ciphertext, iv=encrypt_result.iv) + assert decrypt_result.plaintext == plaintext + + # Try with no IV to verify that we generate one for local crypto + encrypt_result = provider.encrypt(algorithm, plaintext, iv=None) + assert encrypt_result.key_id == key.id + assert len(encrypt_result.iv) == 16 decrypt_result = provider.decrypt(encrypt_result.algorithm, encrypt_result.ciphertext, iv=encrypt_result.iv) assert decrypt_result.plaintext == plaintext From fd62fc69e75f522f95f6082b5fbedf2b74e2f03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 22 Jul 2022 17:26:43 -0700 Subject: [PATCH 2/3] Update documentation --- .../azure/keyvault/keys/crypto/_client.py | 5 +++-- .../azure/keyvault/keys/crypto/aio/_client.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py index 8daff45a9a23..c4877af2f777 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py @@ -223,8 +223,9 @@ def encrypt(self, algorithm, plaintext, **kwargs): :type algorithm: :class:`~azure.keyvault.keys.crypto.EncryptionAlgorithm` :param bytes plaintext: Bytes to encrypt :keyword bytes iv: Initialization vector. Required for only AES-CBC(PAD) encryption. If you pass your own IV, - make sure you use a cryptographically random, non-repeating IV. If omitted for remote cryptography, Key - Vault will generate an IV. + make sure you use a cryptographically random, non-repeating IV. If omitted, an attempt will be made to + generate an IV via `os.urandom `_ for local + cryptography; for remote cryptography, Key Vault will generate an IV. :keyword bytes additional_authenticated_data: Optional data that is authenticated but not encrypted. For use with AES-GCM encryption. :rtype: :class:`~azure.keyvault.keys.crypto.EncryptResult` diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py index 513f1b746ca0..8b733d1e7aed 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py @@ -166,8 +166,9 @@ async def encrypt(self, algorithm: "EncryptionAlgorithm", plaintext: bytes, **kw :type algorithm: :class:`~azure.keyvault.keys.crypto.EncryptionAlgorithm` :param bytes plaintext: Bytes to encrypt :keyword bytes iv: Initialization vector. Required for only AES-CBC(PAD) encryption. If you pass your own IV, - make sure you use a cryptographically random, non-repeating IV. If omitted for remote cryptography, Key - Vault will generate an IV. + make sure you use a cryptographically random, non-repeating IV. If omitted, an attempt will be made to + generate an IV via `os.urandom `_ for local + cryptography; for remote cryptography, Key Vault will generate an IV. :keyword bytes additional_authenticated_data: Optional data that is authenticated but not encrypted. For use with AES-GCM encryption. :rtype: :class:`~azure.keyvault.keys.crypto.EncryptResult` From 06aa5c7d947dd56ec1c68570218cef38e255e0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?McCoy=20Pati=C3=B1o?= Date: Fri, 12 Aug 2022 11:11:33 -0700 Subject: [PATCH 3/3] Update exception type --- .../azure/keyvault/keys/crypto/_client.py | 5 ++--- .../azure/keyvault/keys/crypto/_providers/local_provider.py | 2 +- .../azure/keyvault/keys/crypto/aio/_client.py | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py index c4877af2f777..6653fbde00fe 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_client.py @@ -229,9 +229,8 @@ def encrypt(self, algorithm, plaintext, **kwargs): :keyword bytes additional_authenticated_data: Optional data that is authenticated but not encrypted. For use with AES-GCM encryption. :rtype: :class:`~azure.keyvault.keys.crypto.EncryptResult` - :raises: - ValueError if parameters that are incompatible with the specified algorithm are provided, - RuntimeError if an IV cannot be generated + :raises ValueError: if parameters that are incompatible with the specified algorithm are provided, or if + generating an IV fails on the current platform. .. literalinclude:: ../tests/test_examples_crypto.py :start-after: [START encrypt] diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py index b5dd308d328f..f9073924e026 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/_providers/local_provider.py @@ -70,7 +70,7 @@ def encrypt(self, algorithm, plaintext, iv=None): try: iv = os.urandom(16) except NotImplementedError as ex: - raise RuntimeError( + raise ValueError( "An IV could not be generated on this OS. Please provide your own cryptographically random, " "non-repeating IV for local cryptography." ) from ex diff --git a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py index 8b733d1e7aed..b8455c6c7c8f 100644 --- a/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py +++ b/sdk/keyvault/azure-keyvault-keys/azure/keyvault/keys/crypto/aio/_client.py @@ -172,9 +172,8 @@ async def encrypt(self, algorithm: "EncryptionAlgorithm", plaintext: bytes, **kw :keyword bytes additional_authenticated_data: Optional data that is authenticated but not encrypted. For use with AES-GCM encryption. :rtype: :class:`~azure.keyvault.keys.crypto.EncryptResult` - :raises: - ValueError if parameters that are incompatible with the specified algorithm are provided, - RuntimeError if an IV cannot be generated + :raises ValueError: if parameters that are incompatible with the specified algorithm are provided, or if + generating an IV fails on the current platform. .. literalinclude:: ../tests/test_examples_crypto_async.py :start-after: [START encrypt]