public async Task KeyVault_CapacityLimitOfCachingKeyResolver() { var mockedResolver = new Mock<IKeyResolver>(); var ct = default(CancellationToken); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .Returns(Task.FromResult<IKey>(new RsaKey(KeyId))); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId2, ct)) .Returns(Task.FromResult<IKey>(new RsaKey(KeyId2))); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId3, ct)) .Returns(Task.FromResult<IKey>(new RsaKey(KeyId3))); using (var resolver = new CachingKeyResolver(2, mockedResolver.Object)) { // Three items are added to the cache and because the size of the cache is 2 // after the third key is added the least recently used (LRU) key (first key) is evicted await resolver.ResolveKeyAsync(KeyId, ct); await resolver.ResolveKeyAsync(KeyId2, ct); await resolver.ResolveKeyAsync(KeyId3, ct); // First Key is evicted; so it is going to be added to the cache again when its key is resolved // and second key is evicted to open space for first key as it is the LRU key await resolver.ResolveKeyAsync(KeyId2, ct); await resolver.ResolveKeyAsync(KeyId3, ct); await resolver.ResolveKeyAsync(KeyId, ct); await resolver.ResolveKeyAsync(KeyId3, ct); } mockedResolver.Verify(r => r.ResolveKeyAsync(KeyId2, ct), Times.Once()); mockedResolver.Verify(r => r.ResolveKeyAsync(KeyId3, ct), Times.Once()); mockedResolver.Verify(r => r.ResolveKeyAsync(KeyId, ct), Times.Exactly(2)); }
static void Main(string[] args) { Console.WriteLine("Blob encryption with Key Vault integration demonstrating key rotation from Key 1 to Key 2"); Console.WriteLine(); // Create two secrets and obtain their IDs. This is normally a one-time setup step. // Although it is possible to use keys (rather than secrets) stored in Key Vault, this prevents caching. // Therefore it is recommended to use secrets along with a caching resolver (see below). string keyID1 = EncryptionShared.KeyVaultUtility.SetUpKeyVaultSecret("KeyRotationSampleSecret1"); string keyID2 = EncryptionShared.KeyVaultUtility.SetUpKeyVaultSecret("KeyRotationSampleSecret2"); // Retrieve storage account information from connection string // How to create a storage connection string - https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/ CloudStorageAccount storageAccount = EncryptionShared.Utility.CreateStorageAccountFromConnectionString(); CloudBlobClient client = storageAccount.CreateCloudBlobClient(); CloudBlobContainer container = client.GetContainerReference(DemoContainer + Guid.NewGuid().ToString("N")); // Construct a resolver capable of looking up keys and secrets stored in Key Vault. KeyVaultKeyResolver cloudResolver = new KeyVaultKeyResolver(EncryptionShared.KeyVaultUtility.GetAccessToken); // Set up a caching resolver so the secrets can be cached on the client. This is the recommended usage // pattern since the throttling targets for Storage and Key Vault services are orders of magnitude // different. CachingKeyResolver cachingResolver = new CachingKeyResolver(2, cloudResolver); // Create key instances corresponding to the key IDs. This will cache the secrets. IKey cloudKey1 = cachingResolver.ResolveKeyAsync(keyID1, CancellationToken.None).GetAwaiter().GetResult(); IKey cloudKey2 = cachingResolver.ResolveKeyAsync(keyID2, CancellationToken.None).GetAwaiter().GetResult(); // We begin with cloudKey1, and a resolver capable of resolving and caching Key Vault secrets. BlobEncryptionPolicy encryptionPolicy = new BlobEncryptionPolicy(cloudKey1, cachingResolver); client.DefaultRequestOptions.EncryptionPolicy = encryptionPolicy; client.DefaultRequestOptions.RequireEncryption = true; try { container.Create(); int size = 5 * 1024 * 1024; byte[] buffer1 = new byte[size]; byte[] buffer2 = new byte[size]; Random rand = new Random(); rand.NextBytes(buffer1); rand.NextBytes(buffer2); // Upload the first blob using the secret stored in Azure Key Vault. CloudBlockBlob blob = container.GetBlockBlobReference("blockblob1"); Console.WriteLine("Uploading Blob 1 using Key 1."); // Upload the encrypted contents to the first blob. using (MemoryStream stream = new MemoryStream(buffer1)) { blob.UploadFromStream(stream, size); } Console.WriteLine("Downloading and decrypting Blob 1."); // Download and decrypt the encrypted contents from the first blob. using (MemoryStream outputStream = new MemoryStream()) { blob.DownloadToStream(outputStream); } // At this point we will rotate our keys so new encrypted content will use the // second key. Note that the same resolver is used, as this resolver is capable // of decrypting blobs encrypted using either key. Console.WriteLine("Rotating the active encryption key to Key 2."); client.DefaultRequestOptions.EncryptionPolicy = new BlobEncryptionPolicy(cloudKey2, cachingResolver); // Upload the second blob using the key stored in Azure Key Vault. CloudBlockBlob blob2 = container.GetBlockBlobReference("blockblob2"); Console.WriteLine("Uploading Blob 2 using Key 2."); // Upload the encrypted contents to the second blob. using (MemoryStream stream = new MemoryStream(buffer2)) { blob2.UploadFromStream(stream, size); } Console.WriteLine("Downloading and decrypting Blob 2."); // Download and decrypt the encrypted contents from the second blob. using (MemoryStream outputStream = new MemoryStream()) { blob2.DownloadToStream(outputStream); } // Here we download and re-upload the first blob. This has the effect of updating // the blob to use the new key. using (MemoryStream memoryStream = new MemoryStream()) { Console.WriteLine("Downloading and decrypting Blob 1."); blob.DownloadToStream(memoryStream); memoryStream.Seek(0, SeekOrigin.Begin); Console.WriteLine("Re-uploading Blob 1 using Key 2."); blob.UploadFromStream(memoryStream); } // For the purposes of demonstration, we now override the encryption policy to only recognize key 2. BlobEncryptionPolicy key2OnlyPolicy = new BlobEncryptionPolicy(cloudKey2, null); BlobRequestOptions key2OnlyOptions = new BlobRequestOptions() { EncryptionPolicy = key2OnlyPolicy }; Console.WriteLine("Downloading and decrypting Blob 1."); // The first blob can still be decrypted because it is using the second key. using (MemoryStream outputStream = new MemoryStream()) { blob.DownloadToStream(outputStream, options: key2OnlyOptions); } Console.WriteLine("Press enter key to exit"); Console.ReadLine(); } finally { container.DeleteIfExists(); } }
public async Task KeyVault_CachingKeyResolverThrows() { var mockedResolver = new Mock<IKeyResolver>(); var ct = default(CancellationToken); using (var resolver = new CachingKeyResolver(10, mockedResolver.Object)) { // Throws mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .Throws(new EmptyException()); try { await resolver.ResolveKeyAsync(KeyId, ct); } catch (EmptyException) { } // Throws async mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .ThrowsAsync(new FalseException("test")); try { await resolver.ResolveKeyAsync(KeyId, ct); } catch (FalseException) { } // Succeeds and caches mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .Returns(Task.FromResult<IKey>(new RsaKey(KeyId))); await resolver.ResolveKeyAsync(KeyId, ct); await resolver.ResolveKeyAsync(KeyId, ct); resolver.Dispose(); } mockedResolver.Verify(r => r.ResolveKeyAsync(KeyId, ct), Times.Exactly(3)); }
public async Task KeyVault_CachingKeyResolverDisposeCachedKey() { var mockedResolver = new Mock<IKeyResolver>(); var mockedKey = new Mock<IKey>(); var ct = default(CancellationToken); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .Returns(Task.FromResult(mockedKey.Object)); using (var resolver = new CachingKeyResolver(2, mockedResolver.Object)) { var cachedKey = await resolver.ResolveKeyAsync(KeyId, ct); // key doesn't get disposed because dispose of key is disabled cachedKey.Dispose(); mockedKey.Verify(k => k.Dispose(), Times.Never); await resolver.ResolveKeyAsync(KeyId, ct); } // Dispose on key is only called when cache is disposing mockedKey.Verify(k => k.Dispose(), Times.Exactly(1)); mockedResolver.Verify(r => r.ResolveKeyAsync(KeyId, ct), Times.Exactly(1)); }
public async Task KeyVault_CachingKeyResolverKeyIsDisposed() { var mockedResolver = new Mock<IKeyResolver>(); var mockedKey = new Mock<IKey>(); var ct = default(CancellationToken); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .Returns(Task.FromResult(mockedKey.Object)); using (var resolver = new CachingKeyResolver(2, mockedResolver.Object)) { await resolver.ResolveKeyAsync(KeyId, ct); } mockedKey.Verify(k => k.Dispose(), Times.Once); }
public void KeyVault_CachingKeyResolverCapacityThreadSafety() { const int parallelThreads = 30; var mockedResolver = new Mock<IKeyResolver>(); var ct = default(CancellationToken); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId, ct)) .Returns(() => Task.FromResult<IKey>(new RsaKey(KeyId))); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId2, ct)) .Returns(() => Task.FromResult<IKey>(new RsaKey(KeyId2))); mockedResolver.Setup(r => r.ResolveKeyAsync(KeyId3, ct)) .Returns(() => Task.FromResult<IKey>(new RsaKey(KeyId3))); using (var resolver = new CachingKeyResolver(2, mockedResolver.Object)) { var tasks = new List<Task>(); for (var i = 0; i < parallelThreads; i += 3) { tasks.Add(Task.Run(() => resolver.ResolveKeyAsync(KeyId, ct), ct)); tasks.Add(Task.Run(() => resolver.ResolveKeyAsync(KeyId, ct), ct)); tasks.Add(Task.Run(() => resolver.ResolveKeyAsync(KeyId, ct), ct)); } Task.WaitAll(tasks.ToArray()); } }
static void Main(string[] args) { Console.WriteLine("Blob encryption with Key Vault integration"); Console.WriteLine(); // Get the key ID from App.config if it exists. string keyID = CloudConfigurationManager.GetSetting("KeyID"); // If no key ID was specified, we will create a new secret in Key Vault. // To create a new secret, this client needs full permission to Key Vault secrets. // Once the secret is created, its ID can be added to App.config. Once this is done, // this client only needs read access to secrets. if (string.IsNullOrEmpty(keyID)) { Console.WriteLine("No secret specified in App.config."); Console.WriteLine("Please enter the name of a new secret to create in Key Vault."); Console.WriteLine("WARNING: This will delete any existing secret with the same name."); Console.Write("Name of the new secret to create [{0}]: ", DefaultSecretName); string newSecretName = Console.ReadLine().Trim(); if (string.IsNullOrEmpty(newSecretName)) { newSecretName = DefaultSecretName; } // Although it is possible to use keys (rather than secrets) stored in Key Vault, this prevents caching. // Therefore it is recommended to use secrets along with a caching resolver (see below). keyID = EncryptionShared.KeyVaultUtility.SetUpKeyVaultSecret(newSecretName); Console.WriteLine(); Console.WriteLine("Created a secret with ID: {0}", keyID); Console.WriteLine("Copy the secret ID to App.config to reuse."); Console.WriteLine(); } // Retrieve storage account information from connection string // How to create a storage connection string - https://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/ CloudStorageAccount storageAccount = EncryptionShared.Utility.CreateStorageAccountFromConnectionString(); CloudBlobClient client = storageAccount.CreateCloudBlobClient(); CloudBlobContainer container = client.GetContainerReference(DemoContainer + Guid.NewGuid().ToString("N")); // Construct a resolver capable of looking up keys and secrets stored in Key Vault. KeyVaultKeyResolver cloudResolver = new KeyVaultKeyResolver(EncryptionShared.KeyVaultUtility.GetAccessToken); // To demonstrate how multiple different types of key can be used, we also create a local key and resolver. // This key is temporary and won't be persisted. RsaKey rsaKey = new RsaKey("rsakey"); LocalResolver resolver = new LocalResolver(); resolver.Add(rsaKey); // If there are multiple key sources like Azure Key Vault and local KMS, set up an aggregate resolver as follows. // This helps users to define a plug-in model for all the different key providers they support. AggregateKeyResolver aggregateResolver = new AggregateKeyResolver() .Add(resolver) .Add(cloudResolver); // Set up a caching resolver so the secrets can be cached on the client. This is the recommended usage // pattern since the throttling targets for Storage and Key Vault services are orders of magnitude // different. CachingKeyResolver cachingResolver = new CachingKeyResolver(2, aggregateResolver); // Create a key instance corresponding to the key ID. This will cache the secret. IKey cloudKey = cachingResolver.ResolveKeyAsync(keyID, CancellationToken.None).GetAwaiter().GetResult(); try { container.Create(); int size = 5 * 1024 * 1024; byte[] buffer = new byte[size]; Random rand = new Random(); rand.NextBytes(buffer); // The first blob will use the key stored in Azure Key Vault. CloudBlockBlob blob = container.GetBlockBlobReference("blockblob1"); // Create the encryption policy using the secret stored in Azure Key Vault to be used for upload. BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(cloudKey, null); // Set the encryption policy on the request options. BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; Console.WriteLine("Uploading the 1st encrypted blob."); // Upload the encrypted contents to the blob. using (MemoryStream stream = new MemoryStream(buffer)) { blob.UploadFromStream(stream, size, null, uploadOptions, null); } // Download the encrypted blob. BlobEncryptionPolicy downloadPolicy = new BlobEncryptionPolicy(null, cachingResolver); // Set the decryption policy on the request options. BlobRequestOptions downloadOptions = new BlobRequestOptions() { EncryptionPolicy = downloadPolicy }; Console.WriteLine("Downloading the 1st encrypted blob."); // Download and decrypt the encrypted contents from the blob. using (MemoryStream outputStream = new MemoryStream()) { blob.DownloadToStream(outputStream, null, downloadOptions, null); } // Upload second blob using the local key. blob = container.GetBlockBlobReference("blockblob2"); // Create the encryption policy using the local key. uploadPolicy = new BlobEncryptionPolicy(rsaKey, null); // Set the encryption policy on the request options. uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy }; Console.WriteLine("Uploading the 2nd encrypted blob."); // Upload the encrypted contents to the blob. using (MemoryStream stream = new MemoryStream(buffer)) { blob.UploadFromStream(stream, size, null, uploadOptions, null); } // Download the encrypted blob. The same policy and options created before can be used because the aggregate resolver contains both // resolvers and will pick the right one based on the key ID stored in blob metadata on the service. Console.WriteLine("Downloading the 2nd encrypted blob."); // Download and decrypt the encrypted contents from the blob. using (MemoryStream outputStream = new MemoryStream()) { blob.DownloadToStream(outputStream, null, downloadOptions, null); } Console.WriteLine("Press enter key to exit"); Console.ReadLine(); } finally { container.DeleteIfExists(); } }