Skip to content

Commit 79eddaa

Browse files
authored
Add Key Vault keys cryptography tests (Azure#2145)
1 parent d712758 commit 79eddaa

File tree

5 files changed

+254
-2
lines changed

5 files changed

+254
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/keyvault/assets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"AssetsRepo": "Azure/azure-sdk-assets",
33
"AssetsRepoPrefixPath": "rust",
44
"TagPrefix": "rust/keyvault",
5-
"Tag": "rust/keyvault_9add154b5a"
5+
"Tag": "rust/keyvault_c966c130b6"
66
}

sdk/keyvault/azure_security_keyvault_keys/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ azure_core_test = { path = "../../core/azure_core_test", features = [
2828
azure_identity.path = "../../identity/azure_identity"
2929
azure_security_keyvault_test = { path = "../azure_security_keyvault_test" }
3030
rand.workspace = true
31+
sha2.workspace = true
3132
tokio.workspace = true
3233

3334
[build-dependencies]

sdk/keyvault/azure_security_keyvault_keys/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,71 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
282282
}
283283
```
284284

285+
### Encrypt and decrypt
286+
287+
You can create an asymmetric key in Azure Key Vault (Managed HSM also supports AES symmetric key encryption) and encrypt or decrypt data
288+
without the private key ever leaving the HSM.
289+
290+
```rust no_run
291+
use azure_identity::DefaultAzureCredential;
292+
use azure_security_keyvault_keys::{
293+
models::{KeyBundle, KeyCreateParameters, KeyOperationsParameters, JsonWebKeyEncryptionAlgorithm, JsonWebKeyType},
294+
ResourceExt, KeyClient,
295+
};
296+
use rand::random;
297+
298+
#[tokio::main]
299+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
300+
let credential = DefaultAzureCredential::new()?;
301+
let client = KeyClient::new(
302+
"https://your-key-vault-name.vault.azure.net/",
303+
credential.clone(),
304+
None,
305+
)?;
306+
307+
// Create a key encryption key (KEK) using RSA.
308+
let body = KeyCreateParameters {
309+
kty: Some(JsonWebKeyType::RSA),
310+
key_size: Some(2048),
311+
..Default::default()
312+
};
313+
314+
let key = client
315+
.create_key("key-name", body.try_into()?, None)
316+
.await?
317+
.into_body()
318+
.await?;
319+
let version = key.resource_id()?.version.unwrap_or_default();
320+
321+
// Generate a symmetric data encryption key (DEK). You'd encrypt your data using this DEK.
322+
let dek = random::<u32>().to_le_bytes().to_vec();
323+
324+
// Wrap the DEK. You'd store the wrapped DEK along with your encrypted data.
325+
let mut parameters = KeyOperationsParameters {
326+
algorithm: Some(JsonWebKeyEncryptionAlgorithm::RsaOAEP256),
327+
value: Some(dek.clone()),
328+
..Default::default()
329+
};
330+
let wrapped = client
331+
.wrap_key("key-name", version.as_ref(), parameters.clone().try_into()?, None)
332+
.await?
333+
.into_body()
334+
.await?;
335+
336+
// Unwrap the DEK and decrypt your data.
337+
parameters.value = wrapped.result;
338+
let unwrapped = client
339+
.unwrap_key("key-name", version.as_ref(), parameters.try_into()?, None)
340+
.await?
341+
.into_body()
342+
.await?;
343+
344+
assert!(matches!(unwrapped.result, Some(result) if result.eq(&dek)));
345+
346+
Ok(())
347+
}
348+
```
349+
285350
## Troubleshooting
286351

287352
### General

sdk/keyvault/azure_security_keyvault_keys/tests/key_client.rs

Lines changed: 186 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
use azure_core::{Result, StatusCode};
77
use azure_core_test::{recorded, TestContext, TestMode};
88
use azure_security_keyvault_keys::{
9-
models::{JsonWebKeyCurveName, JsonWebKeyType, KeyCreateParameters, KeyUpdateParameters},
9+
models::{
10+
JsonWebKeyCurveName, JsonWebKeyEncryptionAlgorithm, JsonWebKeySignatureAlgorithm,
11+
JsonWebKeyType, KeyCreateParameters, KeyOperationsParameters, KeySignParameters,
12+
KeyUpdateParameters, KeyVerifyParameters,
13+
},
1014
KeyClient, KeyClientOptions, ResourceExt as _,
1115
};
1216
use azure_security_keyvault_test::Retry;
@@ -222,3 +226,184 @@ async fn purge_key(ctx: TestContext) -> Result<()> {
222226

223227
Ok(())
224228
}
229+
230+
#[recorded::test]
231+
async fn encrypt_decrypt(ctx: TestContext) -> Result<()> {
232+
let recording = ctx.recording();
233+
recording.remove_sanitizers(REMOVE_SANITIZERS).await?;
234+
235+
let mut options = KeyClientOptions::default();
236+
recording.instrument(&mut options.client_options);
237+
238+
let client = KeyClient::new(
239+
recording.var("AZURE_KEYVAULT_URL", None).as_str(),
240+
recording.credential(),
241+
Some(options),
242+
)?;
243+
244+
// Create an RSA key.
245+
let body = KeyCreateParameters {
246+
kty: Some(JsonWebKeyType::RSA),
247+
key_size: Some(2048),
248+
..Default::default()
249+
};
250+
251+
const NAME: &str = "encrypt-decrypt";
252+
253+
let key = client
254+
.create_key(NAME, body.try_into()?, None)
255+
.await?
256+
.into_body()
257+
.await?;
258+
let version = key.resource_id()?.version.unwrap_or_default();
259+
260+
// Encrypt plaintext.
261+
let plaintext = b"plaintext".to_vec();
262+
let mut parameters = KeyOperationsParameters {
263+
algorithm: Some(JsonWebKeyEncryptionAlgorithm::RsaOAEP256),
264+
value: Some(plaintext.clone()),
265+
..Default::default()
266+
};
267+
let encrypted = client
268+
.encrypt(NAME, version.as_ref(), parameters.clone().try_into()?, None)
269+
.await?
270+
.into_body()
271+
.await?;
272+
assert!(matches!(encrypted.result.as_ref(), Some(ciphertext) if !ciphertext.is_empty()));
273+
274+
// Decrypt ciphertext.
275+
parameters.value = encrypted.result;
276+
let decrypted = client
277+
.decrypt(NAME, version.as_ref(), parameters.try_into()?, None)
278+
.await?
279+
.into_body()
280+
.await?;
281+
assert!(matches!(decrypted.result, Some(result) if result.eq(&plaintext)));
282+
283+
Ok(())
284+
}
285+
286+
#[recorded::test]
287+
async fn sign_verify(ctx: TestContext) -> Result<()> {
288+
use sha2::{Digest as _, Sha256};
289+
290+
let recording = ctx.recording();
291+
recording.remove_sanitizers(REMOVE_SANITIZERS).await?;
292+
293+
let mut options = KeyClientOptions::default();
294+
recording.instrument(&mut options.client_options);
295+
296+
let client = KeyClient::new(
297+
recording.var("AZURE_KEYVAULT_URL", None).as_str(),
298+
recording.credential(),
299+
Some(options),
300+
)?;
301+
302+
// Create an EC key.
303+
let body = KeyCreateParameters {
304+
kty: Some(JsonWebKeyType::EC),
305+
curve: Some(JsonWebKeyCurveName::P256),
306+
..Default::default()
307+
};
308+
309+
const NAME: &str = "sign-verify";
310+
const ALG: Option<JsonWebKeySignatureAlgorithm> = Some(JsonWebKeySignatureAlgorithm::ES256);
311+
312+
let key = client
313+
.create_key(NAME, body.try_into()?, None)
314+
.await?
315+
.into_body()
316+
.await?;
317+
let version = key.resource_id()?.version.unwrap_or_default();
318+
319+
// Hash and sign plaintext.
320+
let plaintext = b"plaintext".to_vec();
321+
let digest = Sha256::digest(plaintext).to_vec();
322+
323+
let parameters = KeySignParameters {
324+
algorithm: ALG,
325+
value: Some(digest.clone()),
326+
};
327+
let signed = client
328+
.sign(NAME, version.as_ref(), parameters.try_into()?, None)
329+
.await?
330+
.into_body()
331+
.await?;
332+
assert!(matches!(signed.result.as_ref(), Some(signature) if !signature.is_empty()));
333+
334+
// Verify signature.
335+
let parameters = KeyVerifyParameters {
336+
algorithm: ALG,
337+
digest: Some(digest),
338+
signature: signed.result,
339+
};
340+
let verified = client
341+
.verify(NAME, version.as_ref(), parameters.try_into()?, None)
342+
.await?
343+
.into_body()
344+
.await?;
345+
assert_eq!(verified.value, Some(true));
346+
347+
Ok(())
348+
}
349+
350+
#[recorded::test]
351+
async fn wrap_key_unwrap_key(ctx: TestContext) -> Result<()> {
352+
let recording = ctx.recording();
353+
recording.remove_sanitizers(REMOVE_SANITIZERS).await?;
354+
355+
let mut options = KeyClientOptions::default();
356+
recording.instrument(&mut options.client_options);
357+
358+
let client = KeyClient::new(
359+
recording.var("AZURE_KEYVAULT_URL", None).as_str(),
360+
recording.credential(),
361+
Some(options),
362+
)?;
363+
364+
// Create a KEK using RSA.
365+
let body = KeyCreateParameters {
366+
kty: Some(JsonWebKeyType::RSA),
367+
key_size: Some(2048),
368+
..Default::default()
369+
};
370+
371+
const NAME: &str = "wrap-key-unwrap-key";
372+
const ALG: Option<JsonWebKeyEncryptionAlgorithm> =
373+
Some(JsonWebKeyEncryptionAlgorithm::RsaOAEP256);
374+
375+
let key = client
376+
.create_key(NAME, body.try_into()?, None)
377+
.await?
378+
.into_body()
379+
.await?;
380+
let version = key.resource_id()?.version.unwrap_or_default();
381+
382+
// Generate a data encryption key.
383+
// TODO: Replace with recorded randomness similar to .NET's: https://github.com/Azure/azure-sdk-for-net/blob/fefa057116832364695ca010216f66f198182647/sdk/core/Azure.Core.TestFramework/src/TestRecording.cs#L165
384+
let dek = b"17cf8194356442099ef7482b0f22340e".to_vec();
385+
386+
// Wrap the DEK.
387+
let mut parameters = KeyOperationsParameters {
388+
algorithm: ALG,
389+
value: Some(dek.clone()),
390+
..Default::default()
391+
};
392+
let wrapped = client
393+
.wrap_key(NAME, version.as_ref(), parameters.clone().try_into()?, None)
394+
.await?
395+
.into_body()
396+
.await?;
397+
assert!(matches!(wrapped.result.as_ref(), Some(result) if !result.is_empty()));
398+
399+
// Unwrap the DEK.
400+
parameters.value = wrapped.result;
401+
let unwrapped = client
402+
.unwrap_key(NAME, version.as_ref(), parameters.try_into()?, None)
403+
.await?
404+
.into_body()
405+
.await?;
406+
assert!(matches!(unwrapped.result, Some(result) if result.eq(&dek)));
407+
408+
Ok(())
409+
}

0 commit comments

Comments
 (0)