def test_calls_service_for_operations_unsupported_locally(): """When an operation can't be performed locally, the client should request Key Vault perform it""" mock_client = mock.Mock() key = mock.Mock(spec=KeyVaultKey, id="https://localhost/fake/key/version") client = CryptographyClient(key, mock.Mock()) client._client = mock_client supports_nothing = mock.Mock(supports=mock.Mock(return_value=False)) with mock.patch( CryptographyClient.__module__ + ".get_local_cryptography_provider", lambda *_: supports_nothing): client.decrypt(EncryptionAlgorithm.rsa_oaep, b"...") assert mock_client.decrypt.call_count == 1 assert supports_nothing.decrypt.call_count == 0 client.encrypt(EncryptionAlgorithm.rsa_oaep, b"...") assert mock_client.encrypt.call_count == 1 assert supports_nothing.encrypt.call_count == 0 client.sign(SignatureAlgorithm.rs256, b"...") assert mock_client.sign.call_count == 1 assert supports_nothing.sign.call_count == 0 client.verify(SignatureAlgorithm.rs256, b"...", b"...") assert mock_client.verify.call_count == 1 assert supports_nothing.verify.call_count == 0 client.unwrap_key(KeyWrapAlgorithm.rsa_oaep, b"...") assert mock_client.unwrap_key.call_count == 1 assert supports_nothing.unwrap_key.call_count == 0 client.wrap_key(KeyWrapAlgorithm.rsa_oaep, b"...") assert mock_client.wrap_key.call_count == 1 assert supports_nothing.wrap_key.call_count == 0
class SignTest(PerfStressTest): def __init__(self, arguments): super().__init__(arguments) from dotenv import load_dotenv load_dotenv() # Auth configuration self.credential = DefaultAzureCredential() self.async_credential = AsyncDefaultAzureCredential() # Create clients vault_url = self.get_from_env("AZURE_KEYVAULT_URL") self.client = KeyClient(vault_url, self.credential, **self._client_kwargs) self.async_client = AsyncKeyClient(vault_url, self.async_credential, **self._client_kwargs) self.key_name = "livekvtestsignperfkey" async def global_setup(self): """The global setup is run only once.""" await super().global_setup() rsa_key = await self.async_client.create_rsa_key(self.key_name) self.crypto_client = CryptographyClient(rsa_key.id, self.credential, permissions=NO_GET, **self._client_kwargs) self.async_crypto_client = AsyncCryptographyClient( rsa_key.id, self.async_credential, permissions=NO_GET, **self._client_kwargs ) self.test_algorithm = SignatureAlgorithm.rs256 plaintext = os.urandom(2048) hasher = hashlib.sha256() hasher.update(plaintext) self.digest = hasher.digest() async def global_cleanup(self): """The global cleanup is run only once.""" await self.async_client.delete_key(self.key_name) await self.async_client.purge_deleted_key(self.key_name) await super().global_cleanup() async def close(self): """This is run after cleanup.""" await self.async_client.close() await self.async_crypto_client.close() await self.async_credential.close() await super().close() def run_sync(self): """The synchronous perf test.""" self.crypto_client.sign(self.test_algorithm, self.digest) async def run_async(self): """The asynchronous perf test.""" await self.async_crypto_client.sign(self.test_algorithm, self.digest)
def test_sign_verify(self, key_client, credential, **kwargs): key_name = self.get_resource_name("crypto-test-wrapping-key") key = key_client.create_rsa_key(key_name) client = CryptographyClient(key, credential) # [START sign] import hashlib from azure.keyvault.keys.crypto import SignatureAlgorithm digest = hashlib.sha256(b"plaintext").digest() # sign returns a tuple with the signature and the metadata required to verify it result = client.sign(SignatureAlgorithm.rs256, digest) # the result contains the signature and identifies the key and algorithm used print(result.key_id) print(result.algorithm) signature = result.signature # [END sign] # [START verify] from azure.keyvault.keys.crypto import SignatureAlgorithm verified = client.verify(SignatureAlgorithm.rs256, digest, signature) assert verified.is_valid
def test_sign_verify(self, key_client, **kwargs): credential = self.get_credential(CryptographyClient) key_name = self.get_resource_name("crypto-test-wrapping-key") key = key_client.create_rsa_key(key_name) client = CryptographyClient(key, credential, api_version=key_client.api_version) # [START sign] import hashlib from azure.keyvault.keys.crypto import SignatureAlgorithm digest = hashlib.sha256(b"plaintext").digest() # sign returns the signature and the metadata required to verify it result = client.sign(SignatureAlgorithm.rs256, digest) print(result.key_id) print(result.algorithm) signature = result.signature # [END sign] # [START verify] from azure.keyvault.keys.crypto import SignatureAlgorithm result = client.verify(SignatureAlgorithm.rs256, digest, signature) assert result.is_valid
class KeyVaultRSAKey(rsa.RSAPublicKey, rsa.RSAPrivateKey): """Azure KeyVault provider for public and private account key""" def __init__(self, credentials, vault_url: str, key_name: str): self.vault_url = vault_url self.key_name = key_name self.key_client = KeyClient(vault_url=vault_url, credential=credentials) try: self.kv_key = self.key_client.get_key(key_name) logger.info('Using existing user key from KeyVault') except ResourceNotFoundError: logger.info('Creating new user key in KeyVault') self.kv_key = self.key_client.create_rsa_key(key_name, size=self.key_size) self.crypto_client = CryptographyClient(self.kv_key, credential=credentials) @property def key_size(self): return 2048 def encrypt(self, plaintext, padding): result = self.crypto_client.encrypt(EncryptionAlgorithm.rsa_oaep, plaintext) return result def public_numbers(self): e = int.from_bytes(self.kv_key.key.e, byteorder='big') n = int.from_bytes(self.kv_key.key.n, byteorder='big') return rsa.RSAPublicNumbers(e, n) def public_bytes(self): pass def verifier(self, signature, padding, algorithm): pass def verify(self, signature, data, padding, algorithm): pass def public_key(self): return self def signer(self, padding, algorithm): pass def decrypt(self, ciphertext, padding): pass def sign(self, data, padding, algorithm): value = hashlib.sha256(data).digest() res = self.crypto_client.sign(SignatureAlgorithm.rs256, digest=value) return res.signature
def test_prefers_local_provider(): """The client should complete operations locally whenever possible""" mock_client = mock.Mock() key = mock.Mock( spec=KeyVaultKey, id="https://localhost/fake/key/version", properties=mock.Mock(not_before=datetime(2000, 1, 1, tzinfo=_UTC), expires_on=datetime(3000, 1, 1, tzinfo=_UTC)), ) client = CryptographyClient(key, mock.Mock()) client._client = mock_client supports_everything = mock.Mock(supports=mock.Mock(return_value=True)) with mock.patch( CryptographyClient.__module__ + ".get_local_cryptography_provider", lambda *_: supports_everything): client.decrypt(EncryptionAlgorithm.rsa_oaep, b"...") assert mock_client.decrypt.call_count == 0 assert supports_everything.decrypt.call_count == 1 client.encrypt(EncryptionAlgorithm.rsa_oaep, b"...") assert mock_client.encrypt.call_count == 0 assert supports_everything.encrypt.call_count == 1 client.sign(SignatureAlgorithm.rs256, b"...") assert mock_client.sign.call_count == 0 assert supports_everything.sign.call_count == 1 client.verify(SignatureAlgorithm.rs256, b"...", b"...") assert mock_client.verify.call_count == 0 assert supports_everything.verify.call_count == 1 client.unwrap_key(KeyWrapAlgorithm.rsa_oaep, b"...") assert mock_client.unwrap_key.call_count == 0 assert supports_everything.unwrap_key.call_count == 1 client.wrap_key(KeyWrapAlgorithm.rsa_oaep, b"...") assert mock_client.wrap_key.call_count == 0 assert supports_everything.wrap_key.call_count == 1
def main(): try: # Enable logging enable_logging() # Setup some sample data sample_data = { "value1": "some value 1", "value2": "some value 2", "value3": "some value 3", "value4": "some value 4" } # Convert the dict to string, encode to bytes, and hash the data sample_data_hash = hashlib.sha512( json.dumps(sample_data).encode('UTF-8')).digest() # Obtain a credential from the system-assigned managed identity msi_credential = DefaultAzureCredential() # Get the key from Key Vault and setup a cryptography client key_client = KeyClient(KEY_VAULT_URL, msi_credential) key = key_client.get_key(CERTIFICATE_NAME) crypto_client = CryptographyClient(key, credential=msi_credential) # Use Key Vault to calculate a signature using RSASSA-PKCS1-v1_5 using SHA-512 data_signature = (crypto_client.sign(SignatureAlgorithm.rs512, sample_data_hash)).signature # Retrieve the certificate from Key Vault cert_client = CertificateClient(KEY_VAULT_URL, msi_credential) result = (cert_client.get_certificate(CERTIFICATE_NAME)).cer # Load the DER certificate returned into an x509 object and get the public key cert = load_der_x509_certificate(result, backend=default_backend()) public_key = cert.public_key() # Verify the signature try: public_key.verify(signature=data_signature, data=(json.dumps(sample_data)).encode('UTF-8'), padding=padding.PKCS1v15(), algorithm=hashes.SHA512()) logging.info('Payload verified successfully') print('Payload verified successfully!') except InvalidSignature: print('Payload and/or signature files failed verification') except Exception: logging.error('Execution error: ', exc_info=True)
def get_github_auth_token(): # credentials for AKV come from environment settings credential = azid.DefaultAzureCredential() akvURL = '{0}keys/{1}/{2}'.format(os.environ['key_vault_uri'], os.environ['key_name'], os.environ['key_version']) # built JWT token header = { 'alg': 'RS256', 'kid': akvURL, 'typ': 'JWT', } payload = { 'iss': os.environ['github_appID'], 'iat': int(datetime.now(timezone.utc).timestamp()), 'exp': int((datetime.now(timezone.utc) + timedelta(minutes=1)).timestamp()), } p = [] p.append(base64.urlsafe_b64encode(json.dumps(header).encode('utf-8'))) p.append(base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8'))) digest = hashlib.sha256(b".".join(p)).digest() cryptoClient = CryptographyClient(akvURL, credential) result = cryptoClient.sign(azkeys.crypto.SignatureAlgorithm.rs256, digest) p.append(base64.urlsafe_b64encode(result.signature).replace(b"=", b"")) token = b".".join(p).decode() headers = { "Authorization": "Bearer " + token, "Accept": "application/vnd.github.machine-man-preview+json", } r = requests.get('https://api.github.com/integration/installations', headers=headers) logging.debug(r.json()) access_token_url = r.json()[0]['access_tokens_url'] r = requests.post(access_token_url, headers=headers) logging.debug(r.json()) return r.json()['token']
def test_sign_and_verify(self, key_client, credential, **kwargs): key_name = self.get_resource_name("keysign") md = hashlib.sha256() md.update(self.plaintext) digest = md.digest() imported_key = self._import_test_key(key_client, key_name) crypto_client = CryptographyClient(imported_key.id, credential) result = crypto_client.sign(SignatureAlgorithm.rs256, digest) self.assertEqual(result.key_id, imported_key.id) verified = crypto_client.verify(result.algorithm, digest, result.signature) self.assertEqual(result.key_id, imported_key.id) self.assertEqual(result.algorithm, SignatureAlgorithm.rs256) self.assertTrue(verified.is_valid)
def test_rsa_verify_local(self, key_client, credential, **kwargs): """Sign with Key Vault, verify locally""" for size in (2048, 3072, 4096): key = key_client.create_rsa_key("rsa-verify-{}".format(size), size=size) crypto_client = CryptographyClient(key, credential) for signature_algorithm, hash_function in ( (SignatureAlgorithm.ps256, hashlib.sha256), (SignatureAlgorithm.ps384, hashlib.sha384), (SignatureAlgorithm.ps512, hashlib.sha512), (SignatureAlgorithm.rs256, hashlib.sha256), (SignatureAlgorithm.rs384, hashlib.sha384), (SignatureAlgorithm.rs512, hashlib.sha512), ): digest = hash_function(self.plaintext).digest() result = crypto_client.sign(signature_algorithm, digest) self.assertEqual(result.key_id, key.id) result = crypto_client.verify(result.algorithm, digest, result.signature) self.assertTrue(result.is_valid)
def test_ec_verify_local(self, key_client, credential, **kwargs): """Sign with Key Vault, verify locally""" matrix = { KeyCurveName.p_256: (SignatureAlgorithm.es256, hashlib.sha256), KeyCurveName.p_256_k: (SignatureAlgorithm.es256_k, hashlib.sha256), KeyCurveName.p_384: (SignatureAlgorithm.es384, hashlib.sha384), KeyCurveName.p_521: (SignatureAlgorithm.es512, hashlib.sha512), } for curve, (signature_algorithm, hash_function) in matrix.items(): key = key_client.create_ec_key("ec-verify-{}".format(curve.value), curve=curve) crypto_client = CryptographyClient(key, credential) digest = hash_function(self.plaintext).digest() result = crypto_client.sign(signature_algorithm, digest) self.assertEqual(result.key_id, key.id) result = crypto_client.verify(result.algorithm, digest, result.signature) self.assertTrue(result.is_valid)
def obtain_access_token(key_vault_url, msi_credential, certificate_name, tenant_id, client_id, resource): # Get certificate from Key Vault, load the DER certificate it returns, and calculate the thumbprint cert_client = CertificateClient(key_vault_url, msi_credential) result = (cert_client.get_certificate(certificate_name)).cer cert = load_der_x509_certificate(result, backend=default_backend()) thumbprint = base64.urlsafe_b64encode(cert.fingerprint( hashes.SHA1())).decode('UTF-8') # Create the headers for the JWT headers = {"alg": "RS256", "typ": "JWT", "x5t": thumbprint} encoded_header = (base64.urlsafe_b64encode( bytes(json.dumps(headers), 'UTF-8'))).decode('UTF-8') # Generate a nonce nonce = uuid4().hex # Create the JWT payload claims = { "aud": f"https://login.microsoftonline.com/{tenant_id}/oauth2/token", "iss": client_id, "sub": client_id, "jti": nonce, "nbf": int(time.time()), "exp": int(time.time() + (7 * 86400)) } encoded_claims = (base64.urlsafe_b64encode( bytes(json.dumps(claims), 'UTF-8'))).decode('UTF-8').rstrip('=') # Issue the request to Key Vault to sign the data key_client = KeyClient(key_vault_url, msi_credential) key = key_client.get_key(certificate_name) crypto_client = CryptographyClient(key, credential=msi_credential) data_hash = hashlib.sha256( bytes((encoded_header + '.' + encoded_claims), 'UTF-8')).digest() # Use Key Vault to calculate a signature using RSASSA-PKCS1-v1_5 using SHA-256 jws_signature = (crypto_client.sign(SignatureAlgorithm.rs256, data_hash)).signature encoded_jws_signature = ( base64.urlsafe_b64encode(jws_signature)).decode('UTF-8').rstrip('=') assertion = encoded_header + '.' + encoded_claims + '.' + encoded_jws_signature payload = { "grant_type": "client_credentials", "client_id": client_id, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "resource": resource } # Post the request for the access token result = requests.post( url=f"https://login.microsoftonline.com/{tenant_id}/oauth2/token", data=payload) # Validate that access token was returned if result.status_code == 200: logging.info('Access token successfully obtained') return ((json.loads(result.text))['access_token']) else: error = json.loads(result.text) logging.error('Unable to obtain access token') logging.error(f"Error was: {error['error']}") logging.error(f"Error description was: {error['error_description']}") logging.error(f"Error correlation_id was: {error['correlation_id']}") raise Exception('Failed to obtain access token')