Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClientsCertificate feature spec #8823

Merged
merged 21 commits into from
Feb 22, 2020
Merged
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions designs/NuGet-ClientCertificates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
## Client certificates in NuGet

Implements: https://github.com/NuGet/Home/issues/5773
* Status: **Purposed**
* Author(s): [Shkolka Volodymyr](https://github.com/BlackGad)

## Issue

[5773](https://github.com/NuGet/Home/issues/5773) - NuGet cannot restore from HTTPS sources that require Client Certificates

## Background

Developer tools that supports [Client Certificate Authentication](https://blogs.msdn.microsoft.com/kaushal/2015/05/27/client-certificate-authentication-part-1/).
* [NPM](https://docs.npmjs.com/misc/config#cert)
* [Maven](https://maven.apache.org/guides/mini/guide-repository-ssl.html)
* [Teamcity](https://www.jetbrains.com/help/teamcity/using-https-to-access-teamcity-server.html#UsingHTTPStoaccessTeamCityserver-ConfiguringJVMforauthenticationwithclientcertificate)

It is necessary to add client certificate authentication feature support to NuGet.

## Who are the customers

All .NET Core customers.

## Requirements

* Ability to specify client certificates from common [certificate stores](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/working-with-certificates#certificate-stores)
* Ability to specify client certificates from external files in standard [formats](https://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions)
* Ability to specify inline client certificates in Base64 encoded DER certificate format

## Solution

1) Find specified in NuGet configuration file certificates (common implementation for [all products](https://github.com/NuGet/NuGet.Client/tree/dev/src/NuGet.Core/NuGet.Configuration) )
2) Apply them prior any web request to appropriate http handler (product specific)

## NuGet configuration changes

Added new configuration section **clientCertificates** which may have children of 2 types:

**fromStorage** - item for certificate import from [certificate store](https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/working-with-certificates#certificate-stores). Internally uses [X509Certificate2Collection.Find](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2collection.find?view=netframework-4.8#System_Security_Cryptography_X509Certificates_X509Certificate2Collection_Find_System_Security_Cryptography_X509Certificates_X509FindType_System_Object_System_Boolean_) method.
BlackGad marked this conversation as resolved.
Show resolved Hide resolved
Can be configured with 4 Add item's

- Optional Key="**StoreLocation**" Value="[possible values](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storelocation?view=netframework-4.8#fields)".
Equals '**CurrentUser**' by default
- Optional Key="**StoreName**" Value="[possible values](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename?view=netframework-4.8#fields)".
Equals '**My**' by default
- Optional Key="**FindType**" Value="[possible values](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509findtype?view=netframework-4.8#fields)".
Equals '**FindByThumbprint**' by default
- Optional Key="**FindValue**" Value="The search criteria as an object"

- **fromCert** - item for certificate import directly from file (DER encoded x.509, Base-64 encoded x.509, PKCS)
Can be configured with 2 Add item's and item text body

- Key="**Path**" Value="Absolute or relative path to certificate file". Relative path resolve have 2 stages: first relative to configuration file origin, second relative to current application directory.
- Optional Key="**Password**" Value="Encrypted password". Password for certificate file. Encrypted in same manner as PackageSourceCredential password.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the certificate needs a password or hardware key to unlock, and the password is not specified in the config? Will entering the password be supported by an interactive mode restore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current implementation will fail with error on certificate load. It is bit complicated to me introduce such logic with out help.

BlackGad marked this conversation as resolved.
Show resolved Hide resolved
- Items body can be filled with Base-64 encoded x.509 certificate. Start and end of text are trimmed from spaces and new line chars.
BlackGad marked this conversation as resolved.
Show resolved Hide resolved

## Configuration example

```xml
<configuration>
...
<clientCertificates>
<fromStorage>
<!-- Optional. CurrentUser by default -->
<add key="StoreLocation" value="CurrentUser" />
<!-- Optional. My by default -->
<add key="StoreName" value="My" />
<!-- Optional. FindByThumbprint by default -->
<add key="FindType" value="FindByThumbprint" />
<!-- Required. -->
<add key="FindValue" value="4894671ae5aa84840cc1079e89e82d426bc24ec6" />
</fromStorage>
<!-- Form 1 -->
<fromCert>
<!-- Absolute or relative path to certificate file. -->
<add key="Path" value=".\certificate.pfx" />
<!-- Encrypted password -->
<add key="Password" value="..." />
</fromCert>
BlackGad marked this conversation as resolved.
Show resolved Hide resolved
<!-- Form 2 -->
<fromCert>
-----BEGIN CERTIFICATE-----
MIIEjjCCA3agAwIBAgIJIBkBGf8AAAAPMA0GCSqGSIb3DQEBCwUAMIGzMQswCQYD
...
tJl1UvF7GWJd0yNyPVqCCnBY
-----END CERTIFICATE-----
</fromCert>
BlackGad marked this conversation as resolved.
Show resolved Hide resolved
BlackGad marked this conversation as resolved.
Show resolved Hide resolved
</clientCertificates>
...
</configuration>
```

## Configuration components implementation

Added several new SettingItem's to parse configuration above. Certificates search logic encapsulated inside.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implementations are supposed to implement a spec, not the other way around. Either this is too specific information for a spec, or if the point is to propose the new APIs, then it should read like a proposal and if it changes, the PR will have to change to meet the spec.


For high level usage were introduced:

`ClientCertificateProvider` class
* `Provide(settings:ISettings) : IEnumerable<X509Certificate>` - extracts certificates from settings

`ClientCertificates` static singletone certificate storage.
BlackGad marked this conversation as resolved.
Show resolved Hide resolved
* `Store(certificates: IEnumerable<X509Certificate>)` - stores certificate instances
* `SetupClientHandler(httpClientHandler:HttpClientHandler)` - adds stored certificates to HttpClientHandler instance.

## NuGet Cli implementation as example

1) Internally certificates extracted from configuration items on base [Command.Execute](https://github.com/NuGet/NuGet.Client/blob/d4f53c3e523493fcbe35c537cb004e9a3e228abd/src/NuGet.Clients/NuGet.CommandLine/Commands/Command.cs) with `ClientCertificateProvider.Provide` method
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, the spec is supposed to be the "source of truth", so if there's a bug, or if someone decides to rewrite the client from scratch, the specs should contain the information required. The spec shouldn't refer back to implementations, or PRs, to specify how the feature works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plaese, look at new version. Is it better?

2) Extracted certificates stored as singleton inside static `ClientCertificates` class with `ClientCertificates.Store` method.
2) On any [HttpHandlerResourceV3 creation](https://github.com/NuGet/NuGet.Client/blob/d4f53c3e523493fcbe35c537cb004e9a3e228abd/src/NuGet.Core/NuGet.Protocol/HttpSource/HttpHandlerResourceV3Provider.cs) internal `HttpClientHandler` filled with configured certificates with `ClientCertificates.SetupClientHandler` method.

## Implementation pull request

[3098](https://github.com/NuGet/NuGet.Client/pull/3098) Implemented fromStorage and fromCert client certificates