From 8fe470b7876880eb8adc65f71a851ab902dd130b Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sat, 4 Jun 2016 21:00:25 -0500 Subject: [PATCH] (GH-446) Enable FIPS Compliant Algorithms Chocolatey should function in organizations that require FIPS compliant algorithms. Although most of what Chocolatey does with the Crytpo provider is about hashing files and checksums, in the future that could change, so choco needs to have a feature flip to use a compliant algorithm. Provide a helpful warning and error message when the exception detected is about FIPS. This will help folks running into this error have a helpful path forward to getting it enabled. Unfortunately, enabling it by default could have unintended side effects for existing choco installs that have been tracking current package files, plus it does have a bit more of a performance consideration (although still really fast) because it needs to use P/Invoke methods to use the native Windows systems calls. To turn it on, run the following command in 0.9.10+: `choco feature enable -n useFipsCompliantChecksums` --- .../services/FilesServiceSpecs.cs | 4 +- .../cryptography/CrytpoHashProviderSpecs.cs | 3 +- src/chocolatey.tests/chocolatey.tests.csproj | 2 +- ...Provider.cs => CrytpoHashProviderSpecs.cs} | 4 +- .../ApplicationParameters.cs | 1 + .../builders/ConfigurationBuilder.cs | 27 ++++++++ .../configuration/ChocolateyConfiguration.cs | 1 + .../registration/ContainerBinding.cs | 3 +- .../cryptography/CryptoHashProvider.cs | 61 ++++++++++--------- .../cryptography/CryptoHashProviderType.cs | 3 +- .../cryptography/IHashProvider.cs | 8 +++ 11 files changed, 81 insertions(+), 36 deletions(-) rename src/chocolatey.tests/infrastructure/cryptography/{CrytpoHashProvider.cs => CrytpoHashProviderSpecs.cs} (92%) diff --git a/src/chocolatey.tests.integration/infrastructure.app/services/FilesServiceSpecs.cs b/src/chocolatey.tests.integration/infrastructure.app/services/FilesServiceSpecs.cs index f18828d8cb..6a0d452eca 100644 --- a/src/chocolatey.tests.integration/infrastructure.app/services/FilesServiceSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure.app/services/FilesServiceSpecs.cs @@ -39,8 +39,8 @@ public abstract class FilesServiceSpecsBase : TinySpec public override void Context() { - HashProvider = new CryptoHashProvider(FileSystem, CryptoHashProviderType.Md5); - Service = new FilesService(new XmlService(FileSystem, HashProvider), FileSystem, new CryptoHashProvider(FileSystem, CryptoHashProviderType.Md5)); + HashProvider = new CryptoHashProvider(FileSystem); + Service = new FilesService(new XmlService(FileSystem, HashProvider), FileSystem, HashProvider); } } diff --git a/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs index b32d534e50..0c33870c54 100644 --- a/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs @@ -19,6 +19,7 @@ namespace chocolatey.tests.integration.infrastructure.cryptography using System.IO; using System.Reflection; using System.Security.Cryptography; + using chocolatey.infrastructure.app.configuration; using Should; using chocolatey.infrastructure.cryptography; using chocolatey.infrastructure.filesystem; @@ -34,7 +35,7 @@ public abstract class CrytpoHashProviderSpecsBase : TinySpec public override void Context() { FileSystem = new DotNetFileSystem(); - Provider = new CryptoHashProvider(FileSystem,CryptoHashProviderType.Md5); + Provider = new CryptoHashProvider(FileSystem); ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(FileSystem.get_current_assembly_path()), "context"); } } diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 8a7e57f3b6..9629755f66 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -89,7 +89,7 @@ - + diff --git a/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs b/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProviderSpecs.cs similarity index 92% rename from src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs rename to src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProviderSpecs.cs index b7b27a2fc1..cd90d491a9 100644 --- a/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs +++ b/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProviderSpecs.cs @@ -34,7 +34,7 @@ public abstract class CrytpoHashProviderSpecsBase : TinySpec public override void Context() { - Provider = new CryptoHashProvider(FileSystem.Object, CryptoHashProviderType.Md5); + Provider = Provider = new CryptoHashProvider(FileSystem.Object); } } @@ -59,7 +59,7 @@ public override void Because() [Fact] public void should_provide_the_correct_hash_based_on_a_checksum() { - var expected = BitConverter.ToString(MD5.Create().ComputeHash(byteArray)).Replace("-", string.Empty); + var expected = BitConverter.ToString(SHA256.Create().ComputeHash(byteArray)).Replace("-", string.Empty); result.ShouldEqual(expected); } diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index 694fb8e5ca..761ba5fe4e 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -122,6 +122,7 @@ public static class Features public static readonly string FailOnInvalidOrMissingLicense = "failOnInvalidOrMissingLicense"; public static readonly string IgnoreInvalidOptionsSwitches = "ignoreInvalidOptionsSwitches"; public static readonly string UsePackageExitCodes = "usePackageExitCodes"; + public static readonly string UseFipsCompliantChecksums = "useFipsCompliantChecksums"; } public static class Messages diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index 51fa6d90ed..4a08d4f562 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -24,6 +24,8 @@ namespace chocolatey.infrastructure.app.builders using adapters; using attributes; using configuration; + using cryptography; + using domain; using extractors; using filesystem; using information; @@ -81,6 +83,30 @@ public static void set_up_configuration(IList args, ChocolateyConfigurat set_licensed_options(config, license, configFileSettings); // save all changes if there are any set_config_file_settings(configFileSettings, xmlService); + + if (!config.Features.UseFipsCompliantChecksums) + { + var hashprovider = container.GetInstance(); + try + { + hashprovider.set_hash_algorithm(CryptoHashProviderType.Md5); + } + catch (Exception ex) + { + if (!config.CommandName.is_equal_to("feature")) + { + if (ex.InnerException != null && ex.InnerException.Message.contains("FIPS")) + { + "chocolatey".Log().Warn(ChocolateyLoggers.Important, @" +FIPS Mode detected - run 'choco feature enable -n {0}' + to use Chocolatey.".format_with(ApplicationParameters.Features.UseFipsCompliantChecksums)); + throw new ApplicationException("When FIPS Mode is enabled, Chocolatey requires {0} feature also be enabled.".format_with(ApplicationParameters.Features.UseFipsCompliantChecksums)); + } + + throw; + } + } + } } private static ConfigFileSettings get_config_file_settings(IFileSystem fileSystem, IXmlService xmlService) @@ -264,6 +290,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile config.Features.FailOnInvalidOrMissingLicense = set_feature_flag(ApplicationParameters.Features.FailOnInvalidOrMissingLicense, configFileSettings, defaultEnabled: false, description: "Fail On Invalid Or Missing License - allows knowing when a license is expired or not applied to a machine. Available in 0.9.10+."); config.Features.IgnoreInvalidOptionsSwitches = set_feature_flag(ApplicationParameters.Features.IgnoreInvalidOptionsSwitches, configFileSettings, defaultEnabled: true, description: "Ignore Invalid Options/Switches - If a switch or option is passed that is not recognized, should choco fail? Available in 0.9.10+."); config.Features.UsePackageExitCodes = set_feature_flag(ApplicationParameters.Features.UsePackageExitCodes, configFileSettings, defaultEnabled: true, description: "Use Package Exit Codes - Package scripts can provide exit codes. With this on, package exit codes will be what choco uses for exit when non-zero (this value can come from a dependency package). Chocolatey defines valid exit codes as 0, 1605, 1614, 1641, 3010. With this feature off, choco will exit with a 0 or a 1 (matching previous behavior). Available in 0.9.10+."); + config.Features.UseFipsCompliantChecksums = set_feature_flag(ApplicationParameters.Features.UseFipsCompliantChecksums, configFileSettings, defaultEnabled: false, description: "Use FIPS Compliant Checksums - Ensure checksumming done by choco uses FIPS compliant algorithms. Not recommended unless required by FIPS Mode. Enabling on an existing installation could have unintended consequences related to upgrades/uninstalls. Available in 0.9.10+."); config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass."); } diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index 9c5b360823..0f8ccdc51a 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -344,6 +344,7 @@ public sealed class FeaturesConfiguration public bool FailOnInvalidOrMissingLicense { get; set; } public bool IgnoreInvalidOptionsSwitches { get; set; } public bool UsePackageExitCodes { get; set; } + public bool UseFipsCompliantChecksums { get; set; } } //todo: retrofit other command configs this way diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 74e98b69d1..8ef7ddcb68 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -16,6 +16,7 @@ namespace chocolatey.infrastructure.app.registration { using System.Collections.Generic; + using configuration; using infrastructure.events; using infrastructure.tasks; using NuGet; @@ -60,7 +61,7 @@ public void RegisterComponents(Container container) container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); - container.Register(() => new CryptoHashProvider(container.GetInstance(), CryptoHashProviderType.Md5), Lifestyle.Singleton); + container.Register(() => new CryptoHashProvider(container.GetInstance()), Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure/cryptography/CryptoHashProvider.cs b/src/chocolatey/infrastructure/cryptography/CryptoHashProvider.cs index 8df668a917..c57ff63772 100644 --- a/src/chocolatey/infrastructure/cryptography/CryptoHashProvider.cs +++ b/src/chocolatey/infrastructure/cryptography/CryptoHashProvider.cs @@ -16,7 +16,6 @@ namespace chocolatey.infrastructure.cryptography { using System; - using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -24,36 +23,58 @@ namespace chocolatey.infrastructure.cryptography using adapters; using app; using filesystem; - using platforms; using Environment = System.Environment; using HashAlgorithm = adapters.HashAlgorithm; public sealed class CryptoHashProvider : IHashProvider { private readonly IFileSystem _fileSystem; - private readonly IHashAlgorithm _hashAlgorithm; + private IHashAlgorithm _hashAlgorithm; private const int ERROR_LOCK_VIOLATION = 33; private const int ERROR_SHARING_VIOLATION = 32; - public CryptoHashProvider(IFileSystem fileSystem, CryptoHashProviderType providerType) + public void set_hash_algorithm(CryptoHashProviderType algorithmType) { - _fileSystem = fileSystem; + _hashAlgorithm = get_hash_algorithm_static(algorithmType); + } + + private static IHashAlgorithm get_hash_algorithm_static(CryptoHashProviderType algorithmType) + { + + var fipsOnly = false; + try + { + fipsOnly = CryptoConfig.AllowOnlyFipsAlgorithms; + } + catch (Exception ex) + { + "chocolatey".Log().Debug("Unable to get FipsPolicy from CryptoConfig:{0} {1}".format_with(Environment.NewLine, ex.Message)); + } - switch (providerType) + HashAlgorithm hashAlgorithm = null; + switch (algorithmType) { case CryptoHashProviderType.Md5: - _hashAlgorithm = new HashAlgorithm(MD5.Create()); + hashAlgorithm = new HashAlgorithm(MD5.Create()); break; case CryptoHashProviderType.Sha1: - _hashAlgorithm = new HashAlgorithm(SHA1.Create()); + hashAlgorithm = new HashAlgorithm(fipsOnly ? new SHA1Cng() : SHA1.Create()); break; case CryptoHashProviderType.Sha256: - _hashAlgorithm = new HashAlgorithm(SHA256.Create()); + hashAlgorithm = new HashAlgorithm(fipsOnly ? new SHA256Cng() : SHA256.Create()); break; case CryptoHashProviderType.Sha512: - _hashAlgorithm = new HashAlgorithm(SHA512.Create()); + hashAlgorithm = new HashAlgorithm(fipsOnly ? new SHA512Cng() : SHA512.Create()); break; } + + return hashAlgorithm; + } + + public CryptoHashProvider(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + set_hash_algorithm(CryptoHashProviderType.Sha256); } public CryptoHashProvider(IFileSystem fileSystem, IHashAlgorithm hashAlgorithm) @@ -96,27 +117,11 @@ private static bool file_is_locked(Exception exception) return errorCode == ERROR_SHARING_VIOLATION || errorCode == ERROR_LOCK_VIOLATION; } - - + public static string hash_value(string originalText, CryptoHashProviderType providerType) { - HashAlgorithm hashAlgorithm = null; - switch (providerType) - { - case CryptoHashProviderType.Md5: - hashAlgorithm = new HashAlgorithm(MD5.Create()); - break; - case CryptoHashProviderType.Sha1: - hashAlgorithm = new HashAlgorithm(SHA1.Create()); - break; - case CryptoHashProviderType.Sha256: - hashAlgorithm = new HashAlgorithm(SHA256.Create()); - break; - case CryptoHashProviderType.Sha512: - hashAlgorithm = new HashAlgorithm(SHA512.Create()); - break; - } + IHashAlgorithm hashAlgorithm = get_hash_algorithm_static(providerType); if (hashAlgorithm == null) return string.Empty; var hash = hashAlgorithm.ComputeHash(Encoding.ASCII.GetBytes(originalText)); diff --git a/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs b/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs index 77fe3352a9..cbf84abc49 100644 --- a/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs +++ b/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs @@ -2,9 +2,10 @@ namespace chocolatey.infrastructure.cryptography { public enum CryptoHashProviderType { + Unknown, Md5, Sha1, Sha256, - Sha512 + Sha512, } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure/cryptography/IHashProvider.cs b/src/chocolatey/infrastructure/cryptography/IHashProvider.cs index 5356a05097..2c4b9b096b 100644 --- a/src/chocolatey/infrastructure/cryptography/IHashProvider.cs +++ b/src/chocolatey/infrastructure/cryptography/IHashProvider.cs @@ -15,11 +15,19 @@ namespace chocolatey.infrastructure.cryptography { + using adapters; + /// /// A hash provider for hashing files /// public interface IHashProvider { + /// + /// Changes the algorithm + /// + /// Type of the algorithm. + void set_hash_algorithm(CryptoHashProviderType algorithmType); + /// /// Returns a hash of the specified file path. ///