Skip to content

Commit

Permalink
(GH-399) client certificate support
Browse files Browse the repository at this point in the history
Extends authentication by adding options to sources and commands
to specify client certificate and its password, as well as
implementing new IClientCertificateProvider from choco/nuget-chocolatey
to lookup those options based on the Uri requested.
These options are complimentary and can be used in addition to
username/password assuming the server implements it.
+ unit tests fix to use lowercased %ComSpec% when looking for cmd.exe
  • Loading branch information
eugene.tolmachev authored and ferventcoder committed May 29, 2016
1 parent d79e897 commit 6f79d85
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/chocolatey/chocolatey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
<Compile Include="infrastructure.app\domain\SourceCommandType.cs" />
<Compile Include="infrastructure.app\events\PostRunMessage.cs" />
<Compile Include="infrastructure.app\nuget\ChocolateyNugetCredentialProvider.cs" />
<Compile Include="infrastructure.app\nuget\ChocolateyClientCertificateProvider.cs" />
<Compile Include="infrastructure.app\nuget\NugetPush.cs" />
<Compile Include="infrastructure.app\runners\GenericRunner.cs" />
<Compile Include="infrastructure.app\services\AutomaticUninstallerService.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down Expand Up @@ -107,6 +113,7 @@ choco sources [list]|add|remove|disable|enable [<options/switches>]
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
1 change: 1 addition & 0 deletions src/chocolatey/infrastructure.app/nuget/NugetCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@ public IEnumerable<ChocolateySource> 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
});
}
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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));
Expand Down

0 comments on commit 6f79d85

Please sign in to comment.