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_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));
        }
Beispiel #3
0
        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_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();
            }
        }