예제 #1
0
        /// <inheritdoc />
        public override async Task <EncryptionKeyWrapResult> WrapKeyAsync(
            byte[] key,
            EncryptionKeyWrapMetadata metadata,
            CancellationToken cancellationToken)
        {
            if (metadata.Type != AzureKeyVaultKeyWrapMetadata.TypeConstant)
            {
                throw new ArgumentException("Invalid metadata", nameof(metadata));
            }

            if (!KeyVaultKeyUriProperties.TryParse(new Uri(metadata.Value), out KeyVaultKeyUriProperties keyVaultUriProperties))
            {
                throw new ArgumentException("KeyVault Key Uri {0} is invalid.", metadata.Value);
            }

            if (!await this.keyVaultAccessClient.ValidatePurgeProtectionAndSoftDeleteSettingsAsync(keyVaultUriProperties, cancellationToken))
            {
                throw new ArgumentException(string.Format("Key Vault {0} provided must have soft delete and purge protection enabled.", keyVaultUriProperties.KeyUri));
            }

            byte[] result = await this.keyVaultAccessClient.WrapKeyAsync(key, keyVaultUriProperties, cancellationToken);

            EncryptionKeyWrapMetadata responseMetadata = new EncryptionKeyWrapMetadata(
                type: metadata.Type,
                value: metadata.Value,
                algorithm: KeyVaultConstants.RsaOaep256);

            return(new EncryptionKeyWrapResult(result, responseMetadata));
        }
예제 #2
0
        /// <inheritdoc />
        public override async Task <EncryptionKeyUnwrapResult> UnwrapKeyAsync(
            byte[] wrappedKey,
            EncryptionKeyWrapMetadata metadata,
            CancellationToken cancellationToken)
        {
            if (metadata.Type != AzureKeyVaultKeyWrapMetadata.TypeConstant)
            {
                throw new ArgumentException("Invalid metadata", nameof(metadata));
            }

            if (metadata.Algorithm != KeyVaultConstants.RsaOaep256)
            {
                throw new ArgumentException(
                          string.Format("Unknown encryption key wrap algorithm {0}", metadata.Algorithm),
                          nameof(metadata));
            }

            if (!KeyVaultKeyUriProperties.TryParse(new Uri(metadata.Value), out KeyVaultKeyUriProperties keyVaultUriProperties))
            {
                throw new ArgumentException("KeyVault Key Uri {0} is invalid.", metadata.Value);
            }

            byte[] result = await this.keyVaultAccessClient.UnwrapKeyAsync(wrappedKey, keyVaultUriProperties, cancellationToken);

            return(new EncryptionKeyUnwrapResult(result, this.rawDekCacheTimeToLive));
        }
예제 #3
0
        internal async Task <(byte[], EncryptionKeyWrapMetadata, InMemoryRawDek)> WrapAsync(
            string id,
            byte[] key,
            string encryptionAlgorithm,
            EncryptionKeyWrapMetadata metadata,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            EncryptionKeyWrapResult keyWrapResponse = null;

            using (diagnosticsContext.CreateScope("WrapDataEncryptionKey"))
            {
                keyWrapResponse = string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized) && this.DekProvider.EncryptionKeyWrapProvider != null
                    ? await this.DekProvider.EncryptionKeyWrapProvider.WrapKeyAsync(key, metadata, cancellationToken)
                    : string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized) && this.DekProvider.MdeKeyWrapProvider != null
                        ? await this.DekProvider.MdeKeyWrapProvider.WrapKeyAsync(key, metadata, cancellationToken)
                        : throw new ArgumentException(string.Format(
                                                          "Unsupported encryption algorithm {0}." +
                                                          " Please initialize the Encryptor or CosmosDataEncryptionKeyProvider with an implementation of the EncryptionKeyStoreProvider / EncryptionKeyWrapProvider.",
                                                          encryptionAlgorithm));
            }

            // Verify
            DataEncryptionKeyProperties tempDekProperties = new DataEncryptionKeyProperties(
                id,
                encryptionAlgorithm,
                keyWrapResponse.WrappedDataEncryptionKey,
                keyWrapResponse.EncryptionKeyWrapMetadata,
                DateTime.UtcNow);

            byte[]         rawKey            = null;
            InMemoryRawDek roundTripResponse = null;

            if (string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized))
            {
                roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken);

                rawKey = roundTripResponse.DataEncryptionKey.RawKey;
            }
            else if (string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized))
            {
                EncryptionKeyUnwrapResult unwrapResult = await this.UnWrapDekMdeEncAlgoAsync(
                    tempDekProperties,
                    diagnosticsContext,
                    cancellationToken);

                rawKey = unwrapResult.DataEncryptionKey;
            }

            if (!rawKey.SequenceEqual(key))
            {
                throw new InvalidOperationException("The key wrapping provider configured was unable to unwrap the wrapped key correctly.");
            }

            return(keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse);
        }
예제 #4
0
 public override Task <ItemResponse <DataEncryptionKeyProperties> > CreateDataEncryptionKeyAsync(
     string id,
     string encryptionAlgorithm,
     EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
     ItemRequestOptions requestOptions   = null,
     CancellationToken cancellationToken = default)
 {
     return(TaskHelper.RunInlineIfNeededAsync(() =>
                                              this.dataEncryptionKeyContainerCore.CreateDataEncryptionKeyAsync(id, encryptionAlgorithm, encryptionKeyWrapMetadata, requestOptions, cancellationToken)));
 }
 /// <summary>
 /// Initializes a new instance of <see cref="DataEncryptionKeyProperties"/>.
 /// </summary>
 /// <param name="id">Unique identifier for the data encryption key.</param>
 /// <param name="encryptionAlgorithm">Encryption algorithm that will be used along with this data encryption key to encrypt/decrypt data.</param>
 /// <param name="wrappedDataEncryptionKey">Wrapped (encrypted) form of the data encryption key.</param>
 /// <param name="encryptionKeyWrapMetadata">Metadata used by the configured key wrapping provider in order to unwrap the key.</param>
 /// <param name="createdTime">Time at which this data encryption key was created.</param>
 public DataEncryptionKeyProperties(
     string id,
     string encryptionAlgorithm,
     byte[] wrappedDataEncryptionKey,
     EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
     DateTime createdTime)
 {
     this.Id = !string.IsNullOrEmpty(id) ? id : throw new ArgumentNullException(nameof(id));
     this.EncryptionAlgorithm       = encryptionAlgorithm;
     this.WrappedDataEncryptionKey  = wrappedDataEncryptionKey ?? throw new ArgumentNullException(nameof(wrappedDataEncryptionKey));
     this.EncryptionKeyWrapMetadata = encryptionKeyWrapMetadata ?? throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
     this.CreatedTime = createdTime;
 }
예제 #6
0
        public override Task <EncryptionKeyWrapResult> WrapKeyAsync(
            byte[] key,
            EncryptionKeyWrapMetadata metadata,
            CancellationToken cancellationToken)
        {
            if (metadata == null)
            {
                throw new ArgumentNullException(nameof(metadata));
            }

            KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate(
                metadata.Name,
                metadata.Value,
                this.EncryptionKeyStoreProvider);

            byte[] result = keyEncryptionKey.EncryptEncryptionKey(key);
            return(Task.FromResult(new EncryptionKeyWrapResult(result, metadata)));
        }
예제 #7
0
        /// <inheritdoc/>
        public override Task <ItemResponse <DataEncryptionKeyProperties> > RewrapDataEncryptionKeyAsync(
            string id,
            EncryptionKeyWrapMetadata newWrapMetadata,
            ItemRequestOptions requestOptions   = null,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentNullException(nameof(id));
            }

            if (newWrapMetadata == null)
            {
                throw new ArgumentNullException(nameof(newWrapMetadata));
            }

            return(TaskHelper.RunInlineIfNeededAsync(() =>
                                                     this.dataEncryptionKeyContainerCore.RewrapDataEncryptionKeyAsync(id, newWrapMetadata, requestOptions, cancellationToken)));
        }
예제 #8
0
        private (byte[], EncryptionKeyWrapMetadata) GenerateAndWrapPdekForMdeEncAlgo(EncryptionKeyWrapMetadata encryptionKeyWrapMetadata)
        {
            if (this.DekProvider.MdeKeyWrapProvider == null)
            {
                throw new InvalidOperationException($"For use of '{CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized}' algorithm, " +
                                                    "Encryptor or CosmosDataEncryptionKeyProvider needs to be initialized with EncryptionKeyStoreProvider.");
            }

            KeyEncryptionKey keyEncryptionKey = KeyEncryptionKey.GetOrCreate(
                encryptionKeyWrapMetadata.Name,
                encryptionKeyWrapMetadata.Value,
                this.DekProvider.MdeKeyWrapProvider.EncryptionKeyStoreProvider);

            ProtectedDataEncryptionKey protectedDataEncryptionKey = new ProtectedDataEncryptionKey(
                encryptionKeyWrapMetadata.Name,
                keyEncryptionKey);

            byte[] wrappedDek = protectedDataEncryptionKey.EncryptedValue;
            EncryptionKeyWrapMetadata updatedMetadata = encryptionKeyWrapMetadata;

            return(wrappedDek, updatedMetadata);
        }
예제 #9
0
        private async Task <(byte[], EncryptionKeyWrapMetadata, InMemoryRawDek)> GenerateAndWrapRawDekForLegacyEncAlgoAsync(
            string id,
            string encryptionAlgorithm,
            EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
            CosmosDiagnosticsContext diagnosticsContext,
            CancellationToken cancellationToken)
        {
            if (this.DekProvider.EncryptionKeyWrapProvider == null)
            {
                throw new InvalidOperationException($"For use of '{CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized}' algorithm, " +
                                                    "Encryptor or CosmosDataEncryptionKeyProvider needs to be initialized with EncryptionKeyWrapProvider.");
            }

            byte[] rawDek = DataEncryptionKey.Generate(encryptionAlgorithm);

            return(await this.WrapAsync(
                       id,
                       rawDek,
                       encryptionAlgorithm,
                       encryptionKeyWrapMetadata,
                       diagnosticsContext,
                       cancellationToken));
        }
예제 #10
0
 /// <summary>
 /// Unwraps (i.e. decrypts) the provided wrapped data encryption key.
 /// </summary>
 /// <param name="wrappedKey">Wrapped form of data encryption key that needs to be unwrapped.</param>
 /// <param name="metadata">Metadata for the wrap provider that should be used to wrap / unwrap the key.</param>
 /// <param name="cancellationToken">Cancellation token allowing for cancellation of this operation.</param>
 /// <returns>Awaitable unwrapped (i.e. unencrypted) version of data encryption key passed in and how long the raw data encryption key can be cached on the client.</returns>
 public abstract Task <EncryptionKeyUnwrapResult> UnwrapKeyAsync(byte[] wrappedKey, EncryptionKeyWrapMetadata metadata, CancellationToken cancellationToken);
예제 #11
0
        public override async Task <ItemResponse <DataEncryptionKeyProperties> > CreateDataEncryptionKeyAsync(
            string id,
            string encryptionAlgorithm,
            EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
            ItemRequestOptions requestOptions   = null,
            CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentNullException(nameof(id));
            }

            if (!CosmosEncryptionAlgorithm.VerifyIfSupportedAlgorithm(encryptionAlgorithm))
            {
                throw new ArgumentException(string.Format("Unsupported Encryption Algorithm {0}", encryptionAlgorithm), nameof(encryptionAlgorithm));
            }

            if (encryptionKeyWrapMetadata == null)
            {
                throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
            }

            CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);

            byte[] wrappedDek = null;
            EncryptionKeyWrapMetadata updatedMetadata = null;
            InMemoryRawDek            inMemoryRawDek  = null;

            if (string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized))
            {
                (wrappedDek, updatedMetadata, inMemoryRawDek) = await this.GenerateAndWrapRawDekForLegacyEncAlgoAsync(
                    id,
                    encryptionAlgorithm,
                    encryptionKeyWrapMetadata,
                    diagnosticsContext,
                    cancellationToken);
            }
            else if (string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized))
            {
                (wrappedDek, updatedMetadata) = this.GenerateAndWrapPdekForMdeEncAlgo(encryptionKeyWrapMetadata);
            }

            DataEncryptionKeyProperties dekProperties = new DataEncryptionKeyProperties(
                id,
                encryptionAlgorithm,
                wrappedDek,
                updatedMetadata,
                DateTime.UtcNow);

            ItemResponse <DataEncryptionKeyProperties> dekResponse = await this.DekProvider.Container.CreateItemAsync(
                dekProperties,
                new PartitionKey(dekProperties.Id),
                cancellationToken : cancellationToken);

            this.DekProvider.DekCache.SetDekProperties(id, dekResponse.Resource);

            if (string.Equals(encryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized))
            {
                this.DekProvider.DekCache.SetRawDek(id, inMemoryRawDek);
            }

            return(dekResponse);
        }
예제 #12
0
        /// <inheritdoc/>
        public override async Task <ItemResponse <DataEncryptionKeyProperties> > RewrapDataEncryptionKeyAsync(
            string id,
            EncryptionKeyWrapMetadata newWrapMetadata,
            ItemRequestOptions requestOptions   = null,
            CancellationToken cancellationToken = default)
        {
            if (newWrapMetadata == null)
            {
                throw new ArgumentNullException(nameof(newWrapMetadata));
            }

            CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(requestOptions);

            DataEncryptionKeyProperties dekProperties = await this.FetchDataEncryptionKeyPropertiesAsync(
                id,
                diagnosticsContext,
                cancellationToken);

            byte[] rawkey = null;

            if (string.Equals(dekProperties.EncryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized))
            {
                InMemoryRawDek inMemoryRawDek = await this.FetchUnwrappedAsync(
                    dekProperties,
                    diagnosticsContext,
                    cancellationToken);

                rawkey = inMemoryRawDek.DataEncryptionKey.RawKey;
            }
            else if (string.Equals(dekProperties.EncryptionAlgorithm, CosmosEncryptionAlgorithm.MdeAeadAes256CbcHmac256Randomized))
            {
                EncryptionKeyUnwrapResult encryptionKeyUnwrapResult = await this.DekProvider.MdeKeyWrapProvider.UnwrapKeyAsync(
                    dekProperties.WrappedDataEncryptionKey,
                    dekProperties.EncryptionKeyWrapMetadata,
                    cancellationToken);

                rawkey = encryptionKeyUnwrapResult.DataEncryptionKey;
            }

            (byte[] wrappedDek, EncryptionKeyWrapMetadata updatedMetadata, InMemoryRawDek updatedRawDek) = await this.WrapAsync(
                id,
                rawkey,
                dekProperties.EncryptionAlgorithm,
                newWrapMetadata,
                diagnosticsContext,
                cancellationToken);

            if (requestOptions == null)
            {
                requestOptions = new ItemRequestOptions();
            }

            requestOptions.IfMatchEtag = dekProperties.ETag;

            DataEncryptionKeyProperties newDekProperties = new DataEncryptionKeyProperties(dekProperties)
            {
                WrappedDataEncryptionKey  = wrappedDek,
                EncryptionKeyWrapMetadata = updatedMetadata,
            };

            ItemResponse <DataEncryptionKeyProperties> response;

            try
            {
                response = await this.DekProvider.Container.ReplaceItemAsync(
                    newDekProperties,
                    newDekProperties.Id,
                    new PartitionKey(newDekProperties.Id),
                    requestOptions,
                    cancellationToken);

                Debug.Assert(response.Resource != null);
            }
            catch (CosmosException ex)
            {
                if (!ex.StatusCode.Equals(HttpStatusCode.PreconditionFailed))
                {
                    throw;
                }

                // Handle if exception is due to etag mismatch. The scenario is as follows - say there are 2 clients A and B that both have the DEK properties cached.
                // From A, rewrap worked and the DEK is updated. Now from B, rewrap was attempted later based on the cached properties which will fail due to etag mismatch.
                // To address this, we do an explicit read, which reads the key from storage and updates the cached properties; and then attempt rewrap again.
                await this.ReadDataEncryptionKeyAsync(
                    newDekProperties.Id,
                    requestOptions,
                    cancellationToken);

                return(await this.RewrapDataEncryptionKeyAsync(
                           id,
                           newWrapMetadata,
                           requestOptions,
                           cancellationToken));
            }

            this.DekProvider.DekCache.SetDekProperties(id, response.Resource);

            if (string.Equals(dekProperties.EncryptionAlgorithm, CosmosEncryptionAlgorithm.AEAes256CbcHmacSha256Randomized))
            {
                this.DekProvider.DekCache.SetRawDek(id, updatedRawDek);
            }

            return(response);
        }
예제 #13
0
 /// <summary>
 /// Generates a data encryption key, wraps it using the key wrap metadata provided
 /// with the key wrapping provider in the <see cref="CosmosDataEncryptionKeyProvider"/> for encryption,
 /// and saves the wrapped data encryption key as an asynchronous operation in the Azure Cosmos service.
 /// </summary>
 /// <param name="id">Unique identifier for the data encryption key.</param>
 /// <param name="encryptionAlgorithm">Encryption algorithm that will be used along with this data encryption key to encrypt/decrypt data.</param>
 /// <param name="encryptionKeyWrapMetadata">Metadata used by the configured key wrapping provider in order to wrap the key.</param>
 /// <param name="requestOptions">(Optional) The options for the request.</param>
 /// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
 /// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing the read resource record.</returns>
 /// <exception cref="ArgumentNullException">If <paramref name="id"/> is not set.</exception>
 /// <exception cref="CosmosException">
 /// This exception can encapsulate many different types of errors.
 /// To determine the specific error always look at the StatusCode property.
 /// Some common codes you may get when creating a data encryption key are:
 /// <list type="table">
 ///     <listheader>
 ///         <term>StatusCode</term><description>Reason for exception</description>
 ///     </listheader>
 ///     <item>
 ///         <term>400</term><description>BadRequest - This means something was wrong with the request supplied. It is likely that an id was not supplied for the new encryption key.</description>
 ///     </item>
 ///     <item>
 ///         <term>409</term><description>Conflict - This means an <see cref="DataEncryptionKeyProperties"/> with an id matching the id you supplied already existed.</description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <example>
 ///
 /// <code language="c#">
 /// <![CDATA[
 /// AzureKeyVaultKeyWrapMetadata wrapMetadata = new AzureKeyVaultKeyWrapMetadata("/path/to/my/akv/secret/v1");
 /// await this.cosmosDatabase.CreateDataEncryptionKeyAsync("myKey", wrapMetadata);
 /// ]]>
 /// </code>
 /// </example>
 public abstract Task <ItemResponse <DataEncryptionKeyProperties> > CreateDataEncryptionKeyAsync(
     string id,
     string encryptionAlgorithm,
     EncryptionKeyWrapMetadata encryptionKeyWrapMetadata,
     ItemRequestOptions requestOptions   = null,
     CancellationToken cancellationToken = default(CancellationToken));
예제 #14
0
 /// <summary>
 /// Initializes a new instance of the result of wrapping a data encryption key.
 /// </summary>
 /// <param name="wrappedDataEncryptionKey">
 /// Wrapped form of data encryption key.
 /// The byte array passed in must not be modified after this call by the <see cref="EncryptionKeyWrapProvider"/>.
 /// </param>
 /// <param name="encryptionKeyWrapMetadata">Metadata that can be used by the wrap provider to unwrap the data encryption key.</param>
 public EncryptionKeyWrapResult(byte[] wrappedDataEncryptionKey, EncryptionKeyWrapMetadata encryptionKeyWrapMetadata)
 {
     this.WrappedDataEncryptionKey  = wrappedDataEncryptionKey ?? throw new ArgumentNullException(nameof(wrappedDataEncryptionKey));
     this.EncryptionKeyWrapMetadata = encryptionKeyWrapMetadata ?? throw new ArgumentNullException(nameof(encryptionKeyWrapMetadata));
 }
예제 #15
0
 /// <summary>
 /// Wraps the raw data encryption key (after unwrapping using the old metadata if needed) using the provided
 /// metadata with the help of the key wrapping provider in the EncryptionSerializer configured on the client via
 /// <see cref="CosmosClientBuilder.WithCustomSerializer"/>, and saves the re-wrapped data encryption key as an asynchronous
 /// operation in the Azure Cosmos service.
 /// </summary>
 /// <param name="id">Unique identifier of the data encryption key.</param>
 /// <param name="newWrapMetadata">The metadata using which the data encryption key needs to now be wrapped.</param>
 /// <param name="requestOptions">(Optional) The options for the request.</param>
 /// <param name="cancellationToken">(Optional) Token representing request cancellation.</param>
 /// <returns>An awaitable response which wraps a <see cref="DataEncryptionKeyProperties"/> containing details of the data encryption key that was re-wrapped.</returns>
 /// <exception cref="CosmosException">
 /// This exception can encapsulate many different types of errors.
 /// To determine the specific error always look at the StatusCode property.
 /// Some common codes you may get when re-wrapping a data encryption key are:
 /// <list type="table">
 ///     <listheader>
 ///         <term>StatusCode</term>
 ///         <description>Reason for exception</description>
 ///     </listheader>
 ///     <item>
 ///         <term>404</term>
 ///         <description>
 ///         NotFound - This means the resource or parent resource you tried to replace did not exist.
 ///         </description>
 ///     </item>
 ///     <item>
 ///         <term>429</term>
 ///         <description>
 ///         TooManyRequests - This means you have exceeded the number of request units per second.
 ///         Consult the CosmosException.RetryAfter value to see how long you should wait before retrying this operation.
 ///         </description>
 ///     </item>
 /// </list>
 /// </exception>
 /// <example>
 /// <code language="c#">
 /// <![CDATA[
 /// AzureKeyVaultKeyWrapMetadata v2Metadata = new AzureKeyVaultKeyWrapMetadata("/path/to/my/master/key/v2");
 /// await key.RewrapAsync(v2Metadata);
 /// ]]>
 /// </code>
 /// </example>
 public abstract Task <ItemResponse <DataEncryptionKeyProperties> > RewrapDataEncryptionKeyAsync(
     string id,
     EncryptionKeyWrapMetadata newWrapMetadata,
     ItemRequestOptions requestOptions   = null,
     CancellationToken cancellationToken = default(CancellationToken));