private XElement ReadElementFromRegKey(RegistryKey regKey, string valueName)
        {
            if (_logger.IsVerboseLevelEnabled())
            {
                _logger.LogVerboseF($"Reading data from registry key '{regKey}', value '{valueName}'.");
            }

            string data = regKey.GetValue(valueName) as string;

            return((!String.IsNullOrEmpty(data)) ? XElement.Parse(data) : null);
        }
Exemple #2
0
        private XElement ReadElementFromFile(string fullPath)
        {
            if (_logger.IsVerboseLevelEnabled())
            {
                _logger.LogVerboseF($"Reading data from file '{fullPath}'.");
            }

            using (var fileStream = File.OpenRead(fullPath))
            {
                return(XElement.Load(fileStream));
            }
        }
        private CacheableKeyRing CreateCacheableKeyRingCoreStep2(DateTimeOffset now, CancellationToken cacheExpirationToken, IKey defaultKey, IEnumerable <IKey> allKeys)
        {
            Debug.Assert(defaultKey != null);

            // Invariant: our caller ensures that CreateEncryptorInstance succeeded at least once
            Debug.Assert(defaultKey.CreateEncryptorInstance() != null);

            if (_logger.IsVerboseLevelEnabled())
            {
                _logger.LogVerboseF($"Using key {defaultKey.KeyId:B} as the default key.");
            }

            DateTimeOffset nextAutoRefreshTime = now + GetRefreshPeriodWithJitter(_keyManagementOptions.KeyRingRefreshPeriod);

            // The cached keyring should expire at the earliest of (default key expiration, next auto-refresh time).
            // Since the refresh period and safety window are not user-settable, we can guarantee that there's at
            // least one auto-refresh between the start of the safety window and the key's expiration date.
            // This gives us an opportunity to update the key ring before expiration, and it prevents multiple
            // servers in a cluster from trying to update the key ring simultaneously. Special case: if the default
            // key's expiration date is in the past, then we know we're using a fallback key and should disregard
            // its expiration date in favor of the next auto-refresh time.
            return(new CacheableKeyRing(
                       expirationToken: cacheExpirationToken,
                       expirationTime: (defaultKey.ExpirationDate <= now) ? nextAutoRefreshTime : Min(defaultKey.ExpirationDate, nextAutoRefreshTime),
                       defaultKey: defaultKey,
                       allKeys: allKeys));
        }
        private Func <KeyedHashAlgorithm> GetKeyedHashAlgorithmFactory(ILogger logger)
        {
            // basic argument checking
            if (ValidationAlgorithmType == null)
            {
                throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(ValidationAlgorithmType));
            }

            if (logger.IsVerboseLevelEnabled())
            {
                logger.LogVerboseF($"Using managed keyed hash algorithm '{ValidationAlgorithmType.FullName}'.");
            }

            if (ValidationAlgorithmType == typeof(HMACSHA256))
            {
                return(() => new HMACSHA256());
            }
            else if (ValidationAlgorithmType == typeof(HMACSHA512))
            {
                return(() => new HMACSHA512());
            }
            else
            {
                return(AlgorithmActivator.CreateFactory <KeyedHashAlgorithm>(ValidationAlgorithmType));
            }
        }
        private Func <SymmetricAlgorithm> GetSymmetricBlockCipherAlgorithmFactory(ILogger logger)
        {
            // basic argument checking
            if (EncryptionAlgorithmType == null)
            {
                throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithmType));
            }
            typeof(SymmetricAlgorithm).AssertIsAssignableFrom(EncryptionAlgorithmType);
            if (EncryptionAlgorithmKeySize < 0)
            {
                throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
            }

            if (logger.IsVerboseLevelEnabled())
            {
                logger.LogVerboseF($"Using managed symmetric algorithm '{EncryptionAlgorithmType.FullName}'.");
            }

            if (EncryptionAlgorithmType == typeof(Aes))
            {
                Func <Aes> factory = null;
#if !DOTNET5_4
                if (OSVersionUtil.IsWindows())
                {
                    // If we're on desktop CLR and running on Windows, use the FIPS-compliant implementation.
                    factory = () => new AesCryptoServiceProvider();
                }
#endif
                return(factory ?? Aes.Create);
            }
            else
            {
                return(AlgorithmActivator.CreateFactory <SymmetricAlgorithm>(EncryptionAlgorithmType));
            }
        }
Exemple #6
0
        /// <summary>
        /// Encrypts the specified <see cref="XElement"/>.
        /// </summary>
        /// <param name="plaintextElement">The plaintext to encrypt.</param>
        /// <returns>
        /// An <see cref="EncryptedXmlInfo"/> that contains the encrypted value of
        /// <paramref name="plaintextElement"/> along with information about how to
        /// decrypt it.
        /// </returns>
        public EncryptedXmlInfo Encrypt(XElement plaintextElement)
        {
            if (plaintextElement == null)
            {
                throw new ArgumentNullException(nameof(plaintextElement));
            }

            if (_logger.IsVerboseLevelEnabled())
            {
                if (_protectToLocalMachine)
                {
                    _logger.LogVerbose("Encrypting to Windows DPAPI for local machine account.");
                }
                else
                {
                    _logger.LogVerboseF($"Encrypting to Windows DPAPI for current user account ({WindowsIdentity.GetCurrent().Name}).");
                }
            }

            // Convert the XML element to a binary secret so that it can be run through DPAPI
            byte[] dpapiEncryptedData;
            try
            {
                using (Secret plaintextElementAsSecret = plaintextElement.ToSecret())
                {
                    dpapiEncryptedData = DpapiSecretSerializerHelper.ProtectWithDpapi(plaintextElementAsSecret, protectToLocalMachine: _protectToLocalMachine);
                }
            }
            catch (Exception ex)
            {
                if (_logger.IsErrorLevelEnabled())
                {
                    _logger.LogError(ex, "An error occurred while encrypting to Windows DPAPI.");
                }
                throw;
            }

            // <encryptedKey>
            //   <!-- This key is encrypted with {provider}. -->
            //   <value>{base64}</value>
            // </encryptedKey>

            var element = new XElement("encryptedKey",
                                       new XComment(" This key is encrypted with Windows DPAPI. "),
                                       new XElement("value",
                                                    Convert.ToBase64String(dpapiEncryptedData)));

            return(new EncryptedXmlInfo(element, typeof(DpapiXmlDecryptor)));
        }
Exemple #7
0
        /// <summary>
        /// Decrypts the specified XML element.
        /// </summary>
        /// <param name="encryptedElement">An encrypted XML element.</param>
        /// <returns>The decrypted form of <paramref name="encryptedElement"/>.</returns>
        /// <remarks>
        public XElement Decrypt(XElement encryptedElement)
        {
            if (encryptedElement == null)
            {
                throw new ArgumentNullException(nameof(encryptedElement));
            }

            try
            {
                // <encryptedKey>
                //   <!-- This key is encrypted with {provider}. -->
                //   <!-- rule string -->
                //   <value>{base64}</value>
                // </encryptedKey>

                byte[] protectedSecret = Convert.FromBase64String((string)encryptedElement.Element("value"));
                if (_logger.IsVerboseLevelEnabled())
                {
                    string protectionDescriptorRule;
                    try
                    {
                        protectionDescriptorRule = DpapiSecretSerializerHelper.GetRuleFromDpapiNGProtectedPayload(protectedSecret);
                    }
                    catch
                    {
                        // swallow all errors - it's just a log
                        protectionDescriptorRule = null;
                    }
                    _logger.LogVerboseF($"Decrypting secret element using Windows DPAPI-NG with protection descriptor rule '{protectionDescriptorRule}'.");
                }

                using (Secret secret = DpapiSecretSerializerHelper.UnprotectWithDpapiNG(protectedSecret))
                {
                    return(secret.ToXElement());
                }
            }
            catch (Exception ex)
            {
                // It's OK for us to log the error, as we control the exception, and it doesn't contain
                // sensitive information.
                if (_logger.IsErrorLevelEnabled())
                {
                    _logger.LogError(ex, "An exception occurred while trying to decrypt the element.");
                }
                throw;
            }
        }
        private BCryptAlgorithmHandle GetHmacAlgorithmHandle(ILogger logger)
        {
            // basic argument checking
            if (String.IsNullOrEmpty(HashAlgorithm))
            {
                throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(HashAlgorithm));
            }

            if (logger.IsVerboseLevelEnabled())
            {
                logger.LogVerboseF($"Opening CNG algorithm '{HashAlgorithm}' from provider '{HashAlgorithmProvider}' with HMAC.");
            }

            BCryptAlgorithmHandle algorithmHandle = null;

            // Special-case cached providers
            if (HashAlgorithmProvider == null)
            {
                if (HashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM)
                {
                    algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1;
                }
                else if (HashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM)
                {
                    algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256;
                }
                else if (HashAlgorithm == Constants.BCRYPT_SHA512_ALGORITHM)
                {
                    algorithmHandle = CachedAlgorithmHandles.HMAC_SHA512;
                }
            }

            // Look up the provider dynamically if we couldn't fetch a cached instance
            if (algorithmHandle == null)
            {
                algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(HashAlgorithm, HashAlgorithmProvider, hmac: true);
            }

            // Make sure we're using a hash algorithm. We require a minimum 128-bit digest.
            uint digestSize = algorithmHandle.GetHashDigestLength();

            AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked (digestSize * 8));

            // all good!
            return(algorithmHandle);
        }
Exemple #9
0
        /// <summary>
        /// Encrypts the specified <see cref="XElement"/>.
        /// </summary>
        /// <param name="plaintextElement">The plaintext to encrypt.</param>
        /// <returns>
        /// An <see cref="EncryptedXmlInfo"/> that contains the encrypted value of
        /// <paramref name="plaintextElement"/> along with information about how to
        /// decrypt it.
        /// </returns>
        public EncryptedXmlInfo Encrypt(XElement plaintextElement)
        {
            if (plaintextElement == null)
            {
                throw new ArgumentNullException(nameof(plaintextElement));
            }

            string protectionDescriptorRuleString = _protectionDescriptorHandle.GetProtectionDescriptorRuleString();

            if (_logger.IsVerboseLevelEnabled())
            {
                _logger.LogVerboseF($"Encrypting to Windows DPAPI-NG using protection descriptor rule '{protectionDescriptorRuleString}'.");
            }

            // Convert the XML element to a binary secret so that it can be run through DPAPI
            byte[] cngDpapiEncryptedData;
            try
            {
                using (Secret plaintextElementAsSecret = plaintextElement.ToSecret())
                {
                    cngDpapiEncryptedData = DpapiSecretSerializerHelper.ProtectWithDpapiNG(plaintextElementAsSecret, _protectionDescriptorHandle);
                }
            }
            catch (Exception ex)
            {
                if (_logger.IsErrorLevelEnabled())
                {
                    _logger.LogError(ex, "An error occurred while encrypting to Windows DPAPI-NG.");
                }
                throw;
            }

            // <encryptedKey>
            //   <!-- This key is encrypted with {provider}. -->
            //   <!-- rule string -->
            //   <value>{base64}</value>
            // </encryptedKey>

            var element = new XElement("encryptedKey",
                                       new XComment(" This key is encrypted with Windows DPAPI-NG. "),
                                       new XComment(" Rule: " + protectionDescriptorRuleString + " "),
                                       new XElement("value",
                                                    Convert.ToBase64String(cngDpapiEncryptedData)));

            return(new EncryptedXmlInfo(element, typeof(DpapiNGXmlDecryptor)));
        }
Exemple #10
0
        private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(ILogger logger)
        {
            // basic argument checking
            if (String.IsNullOrEmpty(EncryptionAlgorithm))
            {
                throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm));
            }
            if (EncryptionAlgorithmKeySize < 0)
            {
                throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
            }

            BCryptAlgorithmHandle algorithmHandle = null;

            if (logger.IsVerboseLevelEnabled())
            {
                logger.LogVerboseF($"Opening CNG algorithm '{EncryptionAlgorithm}' from provider '{EncryptionAlgorithmProvider}' with chaining mode GCM.");
            }

            // Special-case cached providers
            if (EncryptionAlgorithmProvider == null)
            {
                if (EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM)
                {
                    algorithmHandle = CachedAlgorithmHandles.AES_GCM;
                }
            }

            // Look up the provider dynamically if we couldn't fetch a cached instance
            if (algorithmHandle == null)
            {
                algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(EncryptionAlgorithm, EncryptionAlgorithmProvider);
                algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_GCM);
            }

            // make sure we're using a block cipher with an appropriate key size & block size
            CryptoUtil.Assert(algorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size.");
            AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked ((uint)EncryptionAlgorithmKeySize));

            // make sure the provided key length is valid
            algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize);

            // all good!
            return(algorithmHandle);
        }
Exemple #11
0
        EncryptedData IInternalCertificateXmlEncryptor.PerformEncryption(EncryptedXml encryptedXml, XmlElement elementToEncrypt)
        {
            var cert = _certFactory()
                       ?? CryptoUtil.Fail <X509Certificate2>("Cert factory returned null.");

            if (_logger.IsVerboseLevelEnabled())
            {
                _logger.LogVerboseF($"Encrypting to X.509 certificate with thumbprint '{cert.Thumbprint}'.");
            }

            try
            {
                return(encryptedXml.Encrypt(elementToEncrypt, cert));
            }
            catch (Exception ex)
            {
                if (_logger.IsErrorLevelEnabled())
                {
                    _logger.LogErrorF(ex, $"An error occurred while encrypting to X.509 certificate with thumbprint '{cert.Thumbprint}'.");
                }
                throw;
            }
        }
        private BCryptAlgorithmHandle GetSymmetricBlockCipherAlgorithmHandle(ILogger logger)
        {
            // basic argument checking
            if (String.IsNullOrEmpty(EncryptionAlgorithm))
            {
                throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(EncryptionAlgorithm));
            }
            if (EncryptionAlgorithmKeySize < 0)
            {
                throw Error.Common_PropertyMustBeNonNegative(nameof(EncryptionAlgorithmKeySize));
            }

            BCryptAlgorithmHandle algorithmHandle = null;

            if (logger.IsVerboseLevelEnabled())
            {
                logger.LogVerboseF($"Opening CNG algorithm '{EncryptionAlgorithm}' from provider '{EncryptionAlgorithmProvider}' with chaining mode GCM.");
            }

            // Special-case cached providers
            if (EncryptionAlgorithmProvider == null)
            {
                if (EncryptionAlgorithm == Constants.BCRYPT_AES_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.AES_GCM; }
            }

            // Look up the provider dynamically if we couldn't fetch a cached instance
            if (algorithmHandle == null)
            {
                algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(EncryptionAlgorithm, EncryptionAlgorithmProvider);
                algorithmHandle.SetChainingMode(Constants.BCRYPT_CHAIN_MODE_GCM);
            }

            // make sure we're using a block cipher with an appropriate key size & block size
            CryptoUtil.Assert(algorithmHandle.GetCipherBlockLength() == 128 / 8, "GCM requires a block cipher algorithm with a 128-bit block size.");
            AlgorithmAssert.IsAllowableSymmetricAlgorithmKeySize(checked((uint)EncryptionAlgorithmKeySize));

            // make sure the provided key length is valid
            algorithmHandle.GetSupportedKeyLengths().EnsureValidKeyLength((uint)EncryptionAlgorithmKeySize);

            // all good!
            return algorithmHandle;
        }
        public IReadOnlyCollection <IKey> GetAllKeys()
        {
            var allElements = KeyRepository.GetAllElements();

            // We aggregate all the information we read into three buckets
            Dictionary <Guid, KeyBase> keyIdToKeyMap = new Dictionary <Guid, KeyBase>();
            HashSet <Guid>             revokedKeyIds = null;
            DateTimeOffset?            mostRecentMassRevocationDate = null;

            foreach (var element in allElements)
            {
                if (element.Name == KeyElementName)
                {
                    // ProcessKeyElement can return null in the case of failure, and if this happens we'll move on.
                    // Still need to throw if we see duplicate keys with the same id.
                    KeyBase key = ProcessKeyElement(element);
                    if (key != null)
                    {
                        if (keyIdToKeyMap.ContainsKey(key.KeyId))
                        {
                            throw Error.XmlKeyManager_DuplicateKey(key.KeyId);
                        }
                        keyIdToKeyMap[key.KeyId] = key;
                    }
                }
                else if (element.Name == RevocationElementName)
                {
                    object revocationInfo = ProcessRevocationElement(element);
                    if (revocationInfo is Guid)
                    {
                        // a single key was revoked
                        if (revokedKeyIds == null)
                        {
                            revokedKeyIds = new HashSet <Guid>();
                        }
                        revokedKeyIds.Add((Guid)revocationInfo);
                    }
                    else
                    {
                        // all keys as of a certain date were revoked
                        DateTimeOffset thisMassRevocationDate = (DateTimeOffset)revocationInfo;
                        if (!mostRecentMassRevocationDate.HasValue || mostRecentMassRevocationDate < thisMassRevocationDate)
                        {
                            mostRecentMassRevocationDate = thisMassRevocationDate;
                        }
                    }
                }
                else
                {
                    // Skip unknown elements.
                    if (_logger.IsWarningLevelEnabled())
                    {
                        _logger.LogWarningF($"Unknown element with name '{element.Name}' found in keyring, skipping.");
                    }
                }
            }

            // Apply individual revocations
            if (revokedKeyIds != null)
            {
                foreach (Guid revokedKeyId in revokedKeyIds)
                {
                    KeyBase key;
                    keyIdToKeyMap.TryGetValue(revokedKeyId, out key);
                    if (key != null)
                    {
                        key.SetRevoked();
                        if (_logger.IsVerboseLevelEnabled())
                        {
                            _logger.LogVerboseF($"Marked key {revokedKeyId:B} as revoked in the keyring.");
                        }
                    }
                    else
                    {
                        if (_logger.IsWarningLevelEnabled())
                        {
                            _logger.LogWarningF($"Tried to process revocation of key {revokedKeyId:B}, but no such key was found in keyring. Skipping.");
                        }
                    }
                }
            }

            // Apply mass revocations
            if (mostRecentMassRevocationDate.HasValue)
            {
                foreach (var key in keyIdToKeyMap.Values)
                {
                    // The contract of IKeyManager.RevokeAllKeys is that keys created *strictly before* the
                    // revocation date are revoked. The system clock isn't very granular, and if this were
                    // a less-than-or-equal check we could end up with the weird case where a revocation
                    // immediately followed by a key creation results in a newly-created revoked key (since
                    // the clock hasn't yet stepped).
                    if (key.CreationDate < mostRecentMassRevocationDate)
                    {
                        key.SetRevoked();
                        if (_logger.IsVerboseLevelEnabled())
                        {
                            _logger.LogVerboseF($"Marked key {key.KeyId:B} as revoked in the keyring.");
                        }
                    }
                }
            }

            // And we're finished!
            return(keyIdToKeyMap.Values.ToList().AsReadOnly());
        }
        private IKey FindDefaultKey(DateTimeOffset now, IEnumerable <IKey> allKeys, out IKey fallbackKey, out bool callerShouldGenerateNewKey)
        {
            // find the preferred default key (allowing for server-to-server clock skew)
            var preferredDefaultKey = (from key in allKeys
                                       where key.ActivationDate <= now + _maxServerToServerClockSkew
                                       orderby key.ActivationDate descending, key.KeyId ascending
                                       select key).FirstOrDefault();

            if (preferredDefaultKey != null)
            {
                if (_logger.IsVerboseLevelEnabled())
                {
                    _logger.LogVerboseF($"Considering key {preferredDefaultKey.KeyId:B} with expiration date {preferredDefaultKey.ExpirationDate:u} as default key.");
                }

                // if the key has been revoked or is expired, it is no longer a candidate
                if (preferredDefaultKey.IsRevoked || preferredDefaultKey.IsExpired(now) || !CanCreateAuthenticatedEncryptor(preferredDefaultKey))
                {
                    if (_logger.IsVerboseLevelEnabled())
                    {
                        _logger.LogVerboseF($"Key {preferredDefaultKey.KeyId:B} is no longer under consideration as default key because it is expired, revoked, or cannot be deciphered.");
                    }
                    preferredDefaultKey = null;
                }
            }

            // Only the key that has been most recently activated is eligible to be the preferred default,
            // and only if it hasn't expired or been revoked. This is intentional: generating a new key is
            // an implicit signal that we should stop using older keys (even if they're not revoked), so
            // activating a new key should permanently mark all older keys as non-preferred.

            if (preferredDefaultKey != null)
            {
                // Does *any* key in the key ring fulfill the requirement that its activation date is prior
                // to the preferred default key's expiration date (allowing for skew) and that it will
                // remain valid one propagation cycle from now? If so, the caller doesn't need to add a
                // new key.
                callerShouldGenerateNewKey = !allKeys.Any(key =>
                                                          key.ActivationDate <= (preferredDefaultKey.ExpirationDate + _maxServerToServerClockSkew) &&
                                                          !key.IsExpired(now + _keyPropagationWindow) &&
                                                          !key.IsRevoked);

                if (callerShouldGenerateNewKey && _logger.IsVerboseLevelEnabled())
                {
                    _logger.LogVerbose("Default key expiration imminent and repository contains no viable successor. Caller should generate a successor.");
                }

                fallbackKey = null;
                return(preferredDefaultKey);
            }

            // If we got this far, the caller must generate a key now.
            // We should locate a fallback key, which is a key that can be used to protect payloads if
            // the caller is configured not to generate a new key. We should try to make sure the fallback
            // key has propagated to all callers (so its creation date should be before the previous
            // propagation period), and we cannot use revoked keys. The fallback key may be expired.
            fallbackKey = (from key in (from key in allKeys
                                        where key.CreationDate <= now - _keyPropagationWindow
                                        orderby key.CreationDate descending
                                        select key).Concat(from key in allKeys
                                                           orderby key.CreationDate ascending
                                                           select key)
                           where !key.IsRevoked && CanCreateAuthenticatedEncryptor(key)
                           select key).FirstOrDefault();

            if (_logger.IsVerboseLevelEnabled())
            {
                _logger.LogVerbose("Repository contains no viable default key. Caller should generate a key with immediate activation.");
            }

            callerShouldGenerateNewKey = true;
            return(null);
        }
        private BCryptAlgorithmHandle GetHmacAlgorithmHandle(ILogger logger)
        {
            // basic argument checking
            if (String.IsNullOrEmpty(HashAlgorithm))
            {
                throw Error.Common_PropertyCannotBeNullOrEmpty(nameof(HashAlgorithm));
            }

            if (logger.IsVerboseLevelEnabled())
            {
                logger.LogVerboseF($"Opening CNG algorithm '{HashAlgorithm}' from provider '{HashAlgorithmProvider}' with HMAC.");
            }

            BCryptAlgorithmHandle algorithmHandle = null;

            // Special-case cached providers
            if (HashAlgorithmProvider == null)
            {
                if (HashAlgorithm == Constants.BCRYPT_SHA1_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA1; }
                else if (HashAlgorithm == Constants.BCRYPT_SHA256_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA256; }
                else if (HashAlgorithm == Constants.BCRYPT_SHA512_ALGORITHM) { algorithmHandle = CachedAlgorithmHandles.HMAC_SHA512; }
            }

            // Look up the provider dynamically if we couldn't fetch a cached instance
            if (algorithmHandle == null)
            {
                algorithmHandle = BCryptAlgorithmHandle.OpenAlgorithmHandle(HashAlgorithm, HashAlgorithmProvider, hmac: true);
            }

            // Make sure we're using a hash algorithm. We require a minimum 128-bit digest.
            uint digestSize = algorithmHandle.GetHashDigestLength();
            AlgorithmAssert.IsAllowableValidationAlgorithmDigestSize(checked(digestSize * 8));

            // all good!
            return algorithmHandle;
        }