diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 7980d6ce1f..33c7e641fe 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -133,6 +133,7 @@ + diff --git a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs index b1a3279e09..c0eb902e64 100644 --- a/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs +++ b/src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs @@ -173,6 +173,8 @@ private static void set_machine_sources(ChocolateyConfiguration config, ConfigFi Name = source.Id, Username = source.UserName, EncryptedPassword = source.Password, + Certificate = source.Certificate, + EncryptedCertificatePassword = source.CertificatePassword, Priority = source.Priority }); } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs index 91c82941db..537425103b 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInfoCommand.cs @@ -53,6 +53,12 @@ public override void configure_argument_parser(OptionSet optionSet, ChocolateyCo "p=|password=", "Password - the user's password to the source. Defaults to empty.", option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.Certificate = option.remove_surrounding_quotes()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.CertificatePassword = option.remove_surrounding_quotes()) ; } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs index 96a5b5b5c7..b631be5bb2 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs @@ -83,6 +83,12 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon .Add("p=|password=", "Password - the user's password to the source. Defaults to empty.", option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.Certificate = option.remove_surrounding_quotes()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.CertificatePassword = option.remove_surrounding_quotes()) .Add("ignorechecksums|ignore-checksums", "IgnoreChecksums - Ignore checksums provided by the package", option => diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs index 2ee9547b66..4f11b84fa1 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs @@ -62,6 +62,12 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon .Add("p=|password=", "Password - the user's password to the source. Defaults to empty.", option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.Certificate = option.remove_surrounding_quotes()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.CertificatePassword = option.remove_surrounding_quotes()) .Add("page=", "Page - the 'page' of results to return. Defaults to return all results.", option => { diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs index 67eb6271b0..378f529d30 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs @@ -45,6 +45,12 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon .Add("p=|password=", "Password - the user's password to the source. Defaults to empty.", option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.Certificate = option.remove_surrounding_quotes()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.CertificatePassword = option.remove_surrounding_quotes()) ; } diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs index 152224eaaa..184af8d731 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateySourceCommand.cs @@ -53,7 +53,13 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon option => configuration.SourceCommand.Username = option.remove_surrounding_quotes()) .Add("p=|password=", "Password - the user's password to the source. Encrypted in chocolatey.config file.", - option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.Certificate = option.remove_surrounding_quotes()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.CertificatePassword = option.remove_surrounding_quotes()) .Add("priority=", "Priority - The priority order of this source as compared to other sources, lower is better. Defaults to 0 (no priority). All priorities above 0 will be evaluated first, then zero-based values will be evaluated in config file order.", option => configuration.SourceCommand.Priority = int.Parse(option.remove_surrounding_quotes())) @@ -107,6 +113,7 @@ choco sources [list]|add|remove|disable|enable [] choco source choco source list choco source add -n=bob -s""https://somewhere/out/there/api/v2/"" + choco source add -n=bob -s""https://somewhere/out/there/api/v2/"" -cert=\Users\bob\bob.pfx choco source add -n=bob -s""https://somewhere/out/there/api/v2/"" -u=bob -p=12345 choco source disable -n=bob choco source enable -n=bob diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index e08651f867..29a32daa97 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -87,6 +87,12 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon .Add("p=|password=", "Password - the user's password to the source. Defaults to empty.", option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + .Add("cert=", + "Client certificate - PFX pathname for an x509 authenticated feeds. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.Certificate = option.remove_surrounding_quotes()) + .Add("cp=|certpassword=", + "Certificate Password - the client certificate's password to the source. Defaults to empty. Available in 0.9.10+.", + option => configuration.SourceCommand.CertificatePassword = option.remove_surrounding_quotes()) .Add("ignorechecksums|ignore-checksums", "IgnoreChecksums - Ignore checksums provided by the package", option => diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index a56f0ca0ed..9c5b360823 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -402,6 +402,8 @@ public sealed class SourcesCommandConfiguration public string Username { get; set; } public string Password { get; set; } public int Priority { get; set; } + public string Certificate { get; set; } + public string CertificatePassword { get; set; } } [Serializable] @@ -412,6 +414,8 @@ public sealed class MachineSourceConfiguration public string Username { get; set; } public string EncryptedPassword { get; set; } public int Priority { get; set; } + public string Certificate { get; set; } + public string EncryptedCertificatePassword { get; set; } } [Serializable] diff --git a/src/chocolatey/infrastructure.app/configuration/ConfigFileSourceSetting.cs b/src/chocolatey/infrastructure.app/configuration/ConfigFileSourceSetting.cs index b3d0d4b856..d1ae12b44f 100644 --- a/src/chocolatey/infrastructure.app/configuration/ConfigFileSourceSetting.cs +++ b/src/chocolatey/infrastructure.app/configuration/ConfigFileSourceSetting.cs @@ -42,5 +42,11 @@ public sealed class ConfigFileSourceSetting [XmlAttribute(AttributeName = "priority")] public int Priority { get; set; } + + [XmlAttribute(AttributeName = "certificate")] + public string Certificate { get; set; } + + [XmlAttribute(AttributeName = "certificatePassword")] + public string CertificatePassword { get; set; } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyClientCertificateProvider.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyClientCertificateProvider.cs new file mode 100644 index 0000000000..735feca51d --- /dev/null +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyClientCertificateProvider.cs @@ -0,0 +1,71 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace chocolatey.infrastructure.app.nuget +{ + using configuration; + using NuGet; + using System; + using System.Linq; + using System.Net; + using System.Security.Cryptography.X509Certificates; + + class ChocolateyClientCertificateProvider : IClientCertificateProvider + { + ChocolateyConfiguration _configuration; + + public ChocolateyClientCertificateProvider(ChocolateyConfiguration configuration) + { + if (configuration == null) + throw new ArgumentNullException("configuration"); + _configuration = configuration; + } + + public X509Certificate GetCertificate(Uri uri) + { + if (uri.OriginalString.StartsWith(_configuration.Sources.TrimEnd('/').ToLower(),StringComparison.InvariantCultureIgnoreCase)) + { + if (!string.IsNullOrWhiteSpace(_configuration.SourceCommand.Certificate)) + { + this.Log().Debug("Using passed in certificate"); + + return new X509Certificate2(_configuration.SourceCommand.Certificate, _configuration.SourceCommand.CertificatePassword); + } + } + + return _configuration.MachineSources.Where(s => + { + var sourceUri = s.Key.TrimEnd('/').ToLower(); + return uri.OriginalString.ToLower().StartsWith(sourceUri) + && !string.IsNullOrWhiteSpace(s.Certificate); + }) + .Select(s => + { + this.Log().Debug("Using machine source certificate"); + try { + var decrypted = string.IsNullOrEmpty(s.EncryptedCertificatePassword) + ? string.Empty + : NugetEncryptionUtility.DecryptString(s.EncryptedCertificatePassword); + return new X509Certificate2(s.Certificate, decrypted); + } catch(Exception x) + { + this.Log().Error("Unable to load the certificate: {0}", x); + return null; + } + }) + .FirstOrDefault(); + } + } +} diff --git a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs index 8318b79f67..e4e7c3073b 100644 --- a/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs +++ b/src/chocolatey/infrastructure.app/nuget/NugetCommon.cs @@ -61,6 +61,7 @@ public static IPackageRepository GetRemoteRepository(ChocolateyConfiguration con // ensure credentials can be grabbed from configuration HttpClient.DefaultCredentialProvider = new ChocolateyNugetCredentialProvider(configuration); + HttpClient.DefaultCertificateProvider = new ChocolateyClientCertificateProvider(configuration); if (!string.IsNullOrWhiteSpace(configuration.Proxy.Location)) { "chocolatey".Log().Debug("Using proxy server '{0}'.".format_with(configuration.Proxy.Location)); diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs index 8df31be235..fd41e19707 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyConfigSettingsService.cs @@ -56,14 +56,14 @@ public IEnumerable source_list(ChocolateyConfiguration configu source.Id, source.Disabled ? " [Disabled]" : string.Empty, source.Value, - string.IsNullOrWhiteSpace(source.UserName) ? string.Empty : "(Authenticated)", + (string.IsNullOrWhiteSpace(source.UserName) && string.IsNullOrWhiteSpace(source.Certificate)) ? string.Empty : "(Authenticated)", source.Priority)); } list.Add(new ChocolateySource { Id = source.Id, Value = source.Value, Disabled = source.Disabled, - Authenticated = string.IsNullOrWhiteSpace(source.Password), + Authenticated = !(string.IsNullOrWhiteSpace(source.UserName) && string.IsNullOrWhiteSpace(source.Certificate)), Priority = source.Priority }); } @@ -81,6 +81,8 @@ public void source_add(ChocolateyConfiguration configuration) Value = configuration.Sources, UserName = configuration.SourceCommand.Username, Password = NugetEncryptionUtility.EncryptString(configuration.SourceCommand.Password), + Certificate = configuration.SourceCommand.Certificate, + CertificatePassword = NugetEncryptionUtility.EncryptString(configuration.SourceCommand.CertificatePassword), Priority = configuration.SourceCommand.Priority }; configFileSettings.Sources.Add(source); @@ -91,10 +93,13 @@ public void source_add(ChocolateyConfiguration configuration) else { var currentPassword = string.IsNullOrWhiteSpace(source.Password) ? null : NugetEncryptionUtility.DecryptString(source.Password); + var currentCertificatePassword = string.IsNullOrWhiteSpace(source.CertificatePassword) ? null : NugetEncryptionUtility.DecryptString(source.CertificatePassword); if (configuration.Sources.is_equal_to(source.Value) && configuration.SourceCommand.Priority == source.Priority && configuration.SourceCommand.Username.is_equal_to(source.UserName) && - configuration.SourceCommand.Password.is_equal_to(currentPassword) + configuration.SourceCommand.Password.is_equal_to(currentPassword) && + configuration.SourceCommand.CertificatePassword.is_equal_to(currentCertificatePassword) && + configuration.SourceCommand.Certificate.is_equal_to(source.Certificate) ) { if (!configuration.QuietOutput) this.Log().Warn(NO_CHANGE_MESSAGE); @@ -105,6 +110,8 @@ public void source_add(ChocolateyConfiguration configuration) source.Priority = configuration.SourceCommand.Priority; source.UserName = configuration.SourceCommand.Username; source.Password = NugetEncryptionUtility.EncryptString(configuration.SourceCommand.Password); + source.CertificatePassword = NugetEncryptionUtility.EncryptString(configuration.SourceCommand.CertificatePassword); + source.Certificate = configuration.SourceCommand.Certificate; _xmlService.serialize(configFileSettings, ApplicationParameters.GlobalConfigFileLocation); if (!configuration.QuietOutput) this.Log().Warn(() => "Updated {0} - {1} (Priority {2})".format_with(source.Id, source.Value, source.Priority));