diff --git a/docs/core/extensions/sslstream-best-practices.md b/docs/core/extensions/sslstream-best-practices.md new file mode 100644 index 0000000000000..93ffad7c21176 --- /dev/null +++ b/docs/core/extensions/sslstream-best-practices.md @@ -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 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 property, it is recommended to defer to the operating system settings by using 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 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 . + +> [!NOTE] +> is not supported on Windows and attempts to instantiate it will cause to be thrown. + +## Specify a server certificate + +When authenticating as a server, requires an instance. It is recommended to always use an instance which also contains the private key. + +There are multiple ways that a server certificate can be passed to : + +- Directly as a parameter to or via property +- From a selection callback in property +- By passing a in the property + +The recommended approach is to use the property. When the certificate is obtained by one of the other two ways, a instance is created internally by the implementation. Creating a involves building an which is a CPU intensive operation. It is more efficient to create a once and reuse it for multiple instances. + +Reusing 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 or . Alternatively, completely custom logic can be provided via the 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 . 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 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 may expose the application to denial of service attacks if the external servers are slow to respond. Therefore, server applications should configure the building behavior using the . diff --git a/docs/core/extensions/sslstream-migration-from-framework.md b/docs/core/extensions/sslstream-migration-from-framework.md new file mode 100644 index 0000000000000..348817500a6bf --- /dev/null +++ b/docs/core/extensions/sslstream-migration-from-framework.md @@ -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 works. The most important change related to network security is that the class has been mostly obsoleted and affects only the legacy interface. + +Since .NET, allowed SSL/TLS protocols and certificate validation callbacks must be configured separately for each instance via the or . In order to configure network security options used in HTTPS in , you need to configure the security options in the underlying handler, such as . diff --git a/docs/core/extensions/sslstream-troubleshooting.md b/docs/core/extensions/sslstream-troubleshooting.md new file mode 100644 index 0000000000000..8e69cfb841766 --- /dev/null +++ b/docs/core/extensions/sslstream-troubleshooting.md @@ -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 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 as . During construction of the 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 (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 \{Client|Server}\Enabled` registry key. diff --git a/docs/fundamentals/toc.yml b/docs/fundamentals/toc.yml index 2042862795461..09c4e2c846692 100644 --- a/docs/fundamentals/toc.yml +++ b/docs/fundamentals/toc.yml @@ -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