Skip to content

Commit

Permalink
Add SSL/TLS articles to conceptual docs (#32194)
Browse files Browse the repository at this point in the history
* TLS Best Practices

* SslStream troubleshooting guide

* Migration from .NET Framework?

* Add to TOC

* Code review feedback

* Make heading titles not Title Case

* More format improvements

* Review feedback

* Fix warnings

* Fix ms.author

* Update docs/core/extensions/sslstream-best-practices.md

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>

* Review feedback

* Improve sample

* Apply suggestions from code review

Co-authored-by: David Pine <david.pine@microsoft.com>

* Replace `` by xref

* Fix xref

Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
Co-authored-by: David Pine <david.pine@microsoft.com>
  • Loading branch information
3 people committed Jan 13, 2023
1 parent 7629001 commit 3f37923
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 0 deletions.
144 changes: 144 additions & 0 deletions docs/core/extensions/sslstream-best-practices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title: TLS/SSL best practices
description: Learn the best practices when using SslStream in .NET.
author: rzikm
ms.author: radekzikmund
ms.date: 1/9/2023
---

# TLS/SSL best practices

TLS (Transport Layer Security) is a cryptographic protocol designed to secure communication between two computers over the internet. The TLS protocol is exposed in .NET via the <xref:System.Net.Security.SslStream> class.

This article presents best practices for setting up secure communication between client and server and assumes use of .NET. For best practices with .NET Framework, see [Transport Layer Security (TLS) best practices with the .NET Framework](/dotnet/framework/network-programming/tls).

## Select TLS version

While it is possible to specify the version of the TLS protocol to be used via the <xref:System.Net.Security.SslClientAuthenticationOptions.EnabledSslProtocols> property, it is recommended to defer to the operating system settings by using <xref:System.Security.Authentication.SslProtocols.None> value (this is the default).

Deferring the decision to the OS automatically uses the most recent version of TLS available and lets the application pick up changes after OS upgrades. The operating system may also prevent use of TLS versions which are no longer considered secure.

## Select cipher suites

`SslStream` allows users to specify which cipher suites can be negotiated by the TLS handshake via the <xref:System.Net.Security.CipherSuitesPolicy> class. As with TLS versions, it's recommended to let the OS decide which are the best cipher suites to negotiate with, and, therefore, it's recommended to avoid using <xref:System.Net.Security.CipherSuitesPolicy>.

> [!NOTE]
> <xref:System.Net.Security.CipherSuitesPolicy> is not supported on Windows and attempts to instantiate it will cause <xref:System.NotSupportedException> to be thrown.
## Specify a server certificate

When authenticating as a server, <xref:System.Net.Security.SslStream> requires an <xref:System.Security.Cryptography.X509Certificates.X509Certificate2> instance. It is recommended to always use an <xref:System.Security.Cryptography.X509Certificates.X509Certificate2> instance which also contains the private key.

There are multiple ways that a server certificate can be passed to <xref:System.Net.Security.SslStream>:

- Directly as a parameter to <xref:System.Net.Security.SslStream.AuthenticateAsServerAsync%2A?displayProperty=nameWithType> or via <xref:System.Net.Security.SslServerAuthenticationOptions.ServerCertificate?displayProperty=nameWithType> property
- From a selection callback in <xref:System.Net.Security.SslServerAuthenticationOptions.ServerCertificateSelectionCallback?displayProperty=nameWithType> property
- By passing a <xref:System.Net.Security.SslStreamCertificateContext> in the <xref:System.Net.Security.SslServerAuthenticationOptions.ServerCertificateContext?displayProperty=nameWithType> property

The recommended approach is to use the <xref:System.Net.Security.SslServerAuthenticationOptions.ServerCertificateContext?displayProperty=nameWithType> property. When the certificate is obtained by one of the other two ways, a <xref:System.Net.Security.SslStreamCertificateContext> instance is created internally by the <xref:System.Net.Security.SslStream> implementation. Creating a <xref:System.Net.Security.SslStreamCertificateContext> involves building an <xref:System.Security.Cryptography.X509Certificates.X509Chain> which is a CPU intensive operation. It is more efficient to create a <xref:System.Net.Security.SslStreamCertificateContext> once and reuse it for multiple <xref:System.Net.Security.SslStream> instances.

Reusing <xref:System.Net.Security.SslStreamCertificateContext> instances also enables additional features such us [TLS session resumption](https://datatracker.ietf.org/doc/html/rfc5077) on Linux servers.

## Custom `X509Certificate` validation

There are certain scenarios in which the default certificate validation procedure isn't adequate and some custom validation logic is required. Parts of the validation logic can be customized by specifying <xref:System.Net.Security.SslClientAuthenticationOptions.CertificateChainPolicy?displayProperty=nameWithType> or <xref:System.Net.Security.SslServerAuthenticationOptions.CertificateChainPolicy?displayProperty=nameWithType>. Alternatively, completely custom logic can be provided via the <System.Net.Security.SslClientAuthenticationOptions.RemoteCertificateValidationCallback> property. For more information, see [Custom certificate trust](#custom-certificate-trust).

### Custom certificate trust

When encountering a certificate that wasn't issued by any of the certificate authorities trusted by the machine (including self-signed certificates), the default certificate validation procedure will fail. One possible way to resolve this is to add the necessary issuer certificates to the machine's trusted store. That, however, might affect other applications on the system and is not always possible.

The alternative solution is to specify custom trusted root certificates via an <xref:System.Security.Cryptography.X509Certificates.X509ChainPolicy>. To specify a custom trust list that will be used instead of the system trust list during validation, consider the following example:

```csharp
SslClientAuthenticationOptions clientOptions = new();

clientOptions.CertificateChainPolicy = new X509ChainPolicy()
{
TrustMode = X509ChainTrustMode.CustomRootTrust,
CustomTrustStore =
{
customIssuerCert
}
};
```

Clients configured with the preceding policy would only accept certificates trusted by `customIssuerCert`.

### Ignore specific validation errors

Consider an IoT device without a persistent clock. After powering on, the clock of the device would start many years in the past and, therefore, all certificates would be considered "not yet valid". Consider the following code that shows a validation callback implementation ignoring validity period violations.

```csharp
static bool CustomCertificateValidationCallback(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
// Anything that would have been accepted by default is OK
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}

// If there is something wrong other than a chain processing error, don't trust it.
if (sslPolicyErrors != SslPolicyErrors.RemoteCertificateChainErrors)
{
return false;
}

Debug.Assert(chain is not null);

foreach (X509ChainStatus status in chain.ChainStatus)
{
// If an error other than `NotTimeValid` (or `NoError`) is present, don't trust it.
if ((status.Status & ~X509ChainStatusFlags.NotTimeValid) != X509ChainStatusFlags.NoError)
{
return false;
}
}

return true;
}
```

### Certificate pinning

Another situation where custom certificate validation is necessary is when clients expect servers to use a specific certificate, or a certificate from a small set of known certificates. This practice is known as [certificate pinning](https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning). The following code snippet shows a validation callback which checks that the server presents a certificate with a specific known public key.

```csharp
static bool CustomCertificateValidationCallback(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
// If there is something wrong other than a chain processing error, don't trust it.
if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
return false;
}

Debug.Assert(certificate is not null);

const string ExpectedPublicKey =
"3082010A0282010100C204ECF88CEE04C2B3D850D57058CC9318EB5C" +
"A86849B022B5F9959EB12B2C763E6CC04B604C4CEAB2B4C00F80B6B0" +
"F972C98602F95C415D132B7F71C44BBCE9942E5037A6671C618CF641" +
"42C546D31687279F74EB0A9D11522621736C844C7955E4D16BE8063D" +
"481552ADB328DBAAFF6EFF60954A776B39F124D131B6DD4DC0C4FC53" +
"B96D42ADB57CFEAEF515D23348E72271C7C2147A6C28EA374ADFEA6C" +
"B572B47E5AA216DC69B15744DB0A12ABDEC30F47745C4122E19AF91B" +
"93E6AD2206292EB1BA491C0C279EA3FB8BF7407200AC9208D98C5784" +
"538105CBE6FE6B5498402785C710BB7370EF6918410745557CF9643F" +
"3D2CC3A97CEB931A4C86D1CA850203010001";

return certificate.GetPublicKeyString().Equals(ExpectedPublicKey);
}
```

## Considerations for client certificate validation

Server applications need to be careful when requiring and validating client certificates. Certificates may contain the [AIA (Authority Information Access)](http://www.pkiglobe.org/auth_info_access.html) extension which specifies where the issuer certificate can be downloaded. The server may therefore attempt to download the issuer certificate from external server when building the <xref:System.Security.Cryptography.X509Certificates.X509Chain> for the client certificate. Similarly, servers may need to contact external servers to ensure that the client certificate has not been revoked.

The need to contact external servers when building and validating the <xref:System.Security.Cryptography.X509Certificates.X509Chain> may expose the application to denial of service attacks if the external servers are slow to respond. Therefore, server applications should configure the <xref:System.Security.Cryptography.X509Certificates.X509Chain> building behavior using the <xref:System.Net.Security.SslServerAuthenticationOptions.CertificateChainPolicy>.
13 changes: 13 additions & 0 deletions docs/core/extensions/sslstream-migration-from-framework.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Migrate from .NET Framework to .NET
description: Learn how to migrate code using SslStream in .NET Framework to .NET.
author: rzikm
ms.author: radekzikmund
ms.date: 1/9/2023
---

# Migrate from .NET Framework to .NET

.NET Core brought many improvements as well as breaking changes to how <xref:System.Net.Security.SslStream> works. The most important change related to network security is that the <xref:System.Net.ServicePointManager> class has been mostly obsoleted and affects only the legacy <xref:System.Net.WebRequest> interface.

Since .NET, allowed SSL/TLS protocols and certificate validation callbacks must be configured separately for each <xref:System.Net.Security.SslStream> instance via the <xref:System.Net.Security.SslServerAuthenticationOptions> or <xref:System.Net.Security.SslClientAuthenticationOptions>. In order to configure network security options used in HTTPS in <xref:System.Net.Http.HttpClient>, you need to configure the security options in the underlying handler, such as <xref:System.Net.Http.SocketsHttpHandler.SslOptions?displayName=nameWithType>.
37 changes: 37 additions & 0 deletions docs/core/extensions/sslstream-troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: Troubleshoot SslStream authentication issues
description: Learn how to troubleshoot and investigate issues when performing authentication with SslStream in .NET.
author: rzikm
ms.author: radekzikmund
ms.date: 1/9/2023
---

# Troubleshoot `SslStream` authentication issues

This article presents the most frequent authentication issues when using <xref:System.Net.Security.SslStream> cryptography- and security-related functionalities in .NET are implemented by interop with either the OS API (such as Schannel on Windows) or low-level system libraries (like OpenSSL on Linux). The behavior of .NET application, including exception messages and error codes may therefore change depending on which platform it is run.

Some issues may be easier to investigate and troubleshoot by observing the actual messages exchanged over the wire using tools such as [Wireshark](https://www.wireshark.org) or [tcpdump](https://www.tcpdump.org). These tools can be used to inspect the `ClientHello`, `ServerHello`, and other messages for advertised supported TLS versions allowed and negotiated cipher suites and the certificates exchanged for authentication.

## Intermediate certificates are not sent

During the TLS handshake, the server (and the client too, if client authentication is requested) sends its certificate to prove its identity to the client. In order to validate the authenticity of the certificate, a chain of certificates needs to be built and verified. The root of the chain must be one of the trusted root certificate authority (CA), the certificate of which is stored in the machine certificate store.

If the peer certificate hasn't been issued by one of the trusted CAs an intermediate CA certificate is necessary to build the certificate chain. However, if the intermediate certificate isn't available, it isn't possible to validate the certificate and the TLS handshake may fail.

This issue is most frequently encountered on Windows. Even though the application provided intermediate certificates via the authentication options, they will not be sent to the peer unless they are stored in the Windows certificate store. This limitation is due to the fact that the actual TLS handshake occurs outside of the application process.

For server applications, it is possible to pass an <xref:System.Net.Security.SslStreamCertificateContext> as <xref:System.Net.Security.SslServerAuthenticationOptions.ServerCertificateContext?displayProperty=nameWithType>. During construction of the <xref:System.Net.Security.SslStreamCertificateContext> instance, you can pass additional intermediate certificates and these will be temporarily added into the certificate store.

Unfortunately, for client application the only solution is to add the certificates to the certificate store manually.

## Handshake failed with ephemeral keys

On Windows, you may encounter the `(0x8009030E): No credentials are available in the security package` error message when attempting to use certificates with ephemeral keys. This behavior is due to a bug in the underlying OS API (Schannel). More relevant info and workarounds can be found on the associated [GitHub issue](https://github.com/dotnet/runtime/issues/23749).

## Client and server do not possess a common algorithm

When inspecting the `ClientHello` and `ServerHello` messages, you may find out that there is no cipher suite offered by both client and server or even that some ciphers are not offered even if explicitly configured via <xref:System.Net.Security.CipherSuitesPolicy> (available on Linux only). The underlying TLS library may disable TLS versions and cipher suites which are considered insecure.

On many Linux distributions, the relevant configuration file is located at `/etc/ssl/openssl.cnf`.

On Windows, the [`Enable-TlsCipherSuite`](/powershell/module/tls/enable-tlsciphersuite) and [`Disable-TlsCipherSuite`](/powershell/module/tls/disable-tlsciphersuite) PowerShell cmdlets can be used to configure cipher suites. Individual TLS versions can be enabled/disable by configuring the `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS <version>\{Client|Server}\Enabled` registry key.
8 changes: 8 additions & 0 deletions docs/fundamentals/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2705,6 +2705,14 @@ items:
items:
- name: WebSockets support
href: networking/websockets.md
- name: Security
items:
- name: TLS/SSL best practices
href: ../core/extensions/sslstream-best-practices.md
- name: Troubleshoot SslStream authentication issues
href: ../core/extensions/sslstream-troubleshooting.md
- name: Migrate from .NET Framework to .NET
href: ../core/extensions/sslstream-migration-from-framework.md
- name: File globbing
href: ../core/extensions/file-globbing.md
- name: Primitives library
Expand Down

0 comments on commit 3f37923

Please sign in to comment.