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); }
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)); } }
/// <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))); }
/// <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); }
/// <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))); }
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); }
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; }