<data name="net_quic_tls_version_notsupported" xml:space="preserve">
<value>Could not use a TLS version required by Quic. TLS 1.3 may have been disabled in the registry.</value>
</data>
+ <data name="net_quic_empty_cipher_suite" xml:space="preserve">
+ <value>CipherSuitePolicy must specify at least one cipher supported by QUIC.</value>
+ </data>
<!-- Referenced in shared IPEndPointExtensions.cs-->
<data name="net_InvalidAddressFamily" xml:space="preserve">
<value>The AddressFamily {0} is not valid for the {1} end point, use {2} instead.</value>
DEFER_CERTIFICATE_VALIDATION = 0x00000020, // Schannel only currently.
REQUIRE_CLIENT_AUTHENTICATION = 0x00000040, // Schannel only currently.
USE_TLS_BUILTIN_CERTIFICATE_VALIDATION = 0x00000080,
+ SET_ALLOWED_CIPHER_SUITES = 0x00002000,
USE_PORTABLE_CERTIFICATES = 0x00004000,
}
+ [Flags]
+ internal enum QUIC_ALLOWED_CIPHER_SUITE_FLAGS : uint
+ {
+ NONE = 0x0,
+ AES_128_GCM_SHA256 = 0x1,
+ AES_256_GCM_SHA384 = 0x2,
+ CHACHA20_POLY1305_SHA256 = 0x4,
+ }
+
internal enum QUIC_CERTIFICATE_HASH_STORE_FLAGS
{
QUIC_CERTIFICATE_HASH_STORE_FLAG_NONE = 0x0000,
internal IntPtr Reserved; // Currently unused
// TODO: define delegate for AsyncHandler and make proper use of it.
internal IntPtr AsyncHandler;
+ internal QUIC_ALLOWED_CIPHER_SUITE_FLAGS AllowedCipherSuites;
[CustomTypeMarshaller(typeof(CredentialConfig), Features = CustomTypeMarshallerFeatures.UnmanagedResources)]
[StructLayout(LayoutKind.Sequential)]
internal IntPtr Principal;
internal IntPtr Reserved;
internal IntPtr AsyncHandler;
+ internal QUIC_ALLOWED_CIPHER_SUITE_FLAGS AllowedCipherSuites;
public Native(CredentialConfig managed)
{
Principal = Marshal.StringToCoTaskMemUTF8(managed.Principal);
Reserved = managed.Reserved;
AsyncHandler = managed.AsyncHandler;
+ AllowedCipherSuites = managed.AllowedCipherSuites;
}
public CredentialConfig ToManaged()
Certificate = Certificate,
Principal = Marshal.PtrToStringUTF8(Principal)!,
Reserved = Reserved,
- AsyncHandler = AsyncHandler
+ AsyncHandler = AsyncHandler,
+ AllowedCipherSuites = AllowedCipherSuites
};
}
if (options.ClientAuthenticationOptions != null)
{
- if (options.ClientAuthenticationOptions.CipherSuitesPolicy != null)
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_quic_ssl_option, nameof(options.ClientAuthenticationOptions.CipherSuitesPolicy)));
- }
-
#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
if (options.ClientAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.NoEncryption)
{
}
}
- return Create(options, QUIC_CREDENTIAL_FLAGS.CLIENT, certificate: certificate, certificateContext: null, options.ClientAuthenticationOptions?.ApplicationProtocols);
+ return Create(options, QUIC_CREDENTIAL_FLAGS.CLIENT, certificate: certificate, certificateContext: null, options.ClientAuthenticationOptions?.ApplicationProtocols, options.ClientAuthenticationOptions?.CipherSuitesPolicy);
}
public static SafeMsQuicConfigurationHandle Create(QuicOptions options, SslServerAuthenticationOptions? serverAuthenticationOptions, string? targetHost = null)
if (serverAuthenticationOptions != null)
{
- if (serverAuthenticationOptions.CipherSuitesPolicy != null)
- {
- throw new PlatformNotSupportedException(SR.Format(SR.net_quic_ssl_option, nameof(serverAuthenticationOptions.CipherSuitesPolicy)));
- }
-
#pragma warning disable SYSLIB0040 // NoEncryption and AllowNoEncryption are obsolete
if (serverAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.NoEncryption)
{
}
}
- return Create(options, flags, certificate, serverAuthenticationOptions?.ServerCertificateContext, serverAuthenticationOptions?.ApplicationProtocols);
+ return Create(options, flags, certificate, serverAuthenticationOptions?.ServerCertificateContext, serverAuthenticationOptions?.ApplicationProtocols, serverAuthenticationOptions?.CipherSuitesPolicy);
}
// TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer.
// Consider moving bigger logic like this outside of constructor call chains.
- private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate? certificate, SslStreamCertificateContext? certificateContext, List<SslApplicationProtocol>? alpnProtocols)
+ private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate? certificate, SslStreamCertificateContext? certificateContext, List<SslApplicationProtocol>? alpnProtocols, CipherSuitesPolicy? cipherSuitesPolicy)
{
// TODO: some of these checks should be done by the QuicOptions type.
if (alpnProtocols == null || alpnProtocols.Count == 0)
CredentialConfig config = default;
config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback.
+ if (cipherSuitesPolicy != null)
+ {
+ config.Flags |= QUIC_CREDENTIAL_FLAGS.SET_ALLOWED_CIPHER_SUITES;
+ config.AllowedCipherSuites = CipherSuitePolicyToFlags(cipherSuitesPolicy);
+ }
+
if (certificateContext != null)
{
- certificate = (X509Certificate2?) _contextCertificate.GetValue(certificateContext);
- intermediates = (X509Certificate2[]?) _contextChain.GetValue(certificateContext);
+ certificate = (X509Certificate2?)_contextCertificate.GetValue(certificateContext);
+ intermediates = (X509Certificate2[]?)_contextChain.GetValue(certificateContext);
if (certificate == null || intermediates == null)
{
{
X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Add(certificate);
- for (int i= 0; i < intermediates?.Length; i++)
+ for (int i = 0; i < intermediates?.Length; i++)
{
collection.Add(intermediates[i]);
}
return configurationHandle;
}
+
+ private static QUIC_ALLOWED_CIPHER_SUITE_FLAGS CipherSuitePolicyToFlags(CipherSuitesPolicy cipherSuitesPolicy)
+ {
+ QUIC_ALLOWED_CIPHER_SUITE_FLAGS flags = QUIC_ALLOWED_CIPHER_SUITE_FLAGS.NONE;
+
+ foreach (TlsCipherSuite cipher in cipherSuitesPolicy.AllowedCipherSuites)
+ {
+ switch (cipher)
+ {
+ case TlsCipherSuite.TLS_AES_128_GCM_SHA256:
+ flags |= QUIC_ALLOWED_CIPHER_SUITE_FLAGS.AES_128_GCM_SHA256;
+ break;
+ case TlsCipherSuite.TLS_AES_256_GCM_SHA384:
+ flags |= QUIC_ALLOWED_CIPHER_SUITE_FLAGS.AES_256_GCM_SHA384;
+ break;
+ case TlsCipherSuite.TLS_CHACHA20_POLY1305_SHA256:
+ flags |= QUIC_ALLOWED_CIPHER_SUITE_FLAGS.CHACHA20_POLY1305_SHA256;
+ break;
+ case TlsCipherSuite.TLS_AES_128_CCM_SHA256: // not supported by MsQuic (yet?), but QUIC RFC allows it so we ignore it.
+ default:
+ // ignore
+ break;
+ }
+ }
+
+ if (flags == QUIC_ALLOWED_CIPHER_SUITE_FLAGS.NONE)
+ {
+ throw new ArgumentException(SR.net_quic_empty_cipher_suite, nameof(SslClientAuthenticationOptions.CipherSuitesPolicy));
+ }
+
+ return flags;
+ }
}
}
AuthenticationOptions.RemoteCertificateValidationCallback = options.ServerAuthenticationOptions.RemoteCertificateValidationCallback;
AuthenticationOptions.ServerCertificateSelectionCallback = options.ServerAuthenticationOptions.ServerCertificateSelectionCallback;
AuthenticationOptions.ApplicationProtocols = options.ServerAuthenticationOptions.ApplicationProtocols;
+ AuthenticationOptions.CipherSuitesPolicy = options.ServerAuthenticationOptions.CipherSuitesPolicy;
if (options.ServerAuthenticationOptions.ServerCertificate == null && options.ServerAuthenticationOptions.ServerCertificateContext == null &&
options.ServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System.Net.Quic.Implementations;
+using System.Net.Security;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Quic.Tests
+{
+ [ConditionalClass(typeof(QuicTestBase<MsQuicProviderFactory>), nameof(IsSupported))]
+ [Collection(nameof(DisableParallelization))]
+ [SkipOnPlatform(TestPlatforms.Windows, "CipherSuitesPolicy is not supported on Windows")]
+ public class MsQuicCipherSuitesPolicyTests : QuicTestBase<MsQuicProviderFactory>
+ {
+ public MsQuicCipherSuitesPolicyTests(ITestOutputHelper output) : base(output) { }
+
+ private async Task TestConnection(CipherSuitesPolicy serverPolicy, CipherSuitesPolicy clientPolicy)
+ {
+ var listenerOptions = CreateQuicListenerOptions();
+ listenerOptions.ServerAuthenticationOptions.CipherSuitesPolicy = serverPolicy;
+ using QuicListener listener = CreateQuicListener(listenerOptions);
+
+ var clientOptions = CreateQuicClientOptions();
+ clientOptions.ClientAuthenticationOptions.CipherSuitesPolicy = clientPolicy;
+ clientOptions.RemoteEndPoint = listener.ListenEndPoint;
+ using QuicConnection clientConnection = CreateQuicConnection(clientOptions);
+
+ await clientConnection.ConnectAsync();
+ await clientConnection.CloseAsync(0);
+ }
+
+ [Fact]
+ public Task SupportedCipher_Success()
+ {
+ CipherSuitesPolicy policy = new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_AES_128_GCM_SHA256 });
+ return TestConnection(policy, policy);
+ }
+
+ [Theory]
+ [InlineData(new TlsCipherSuite[] { })]
+ [InlineData(new[] { TlsCipherSuite.TLS_AES_128_CCM_8_SHA256 })]
+ public void NoSupportedCiphers_ThrowsArgumentException(TlsCipherSuite[] ciphers)
+ {
+ CipherSuitesPolicy policy = new CipherSuitesPolicy(ciphers);
+ var listenerOptions = CreateQuicListenerOptions();
+ listenerOptions.ServerAuthenticationOptions.CipherSuitesPolicy = policy;
+ Assert.Throws<ArgumentException>(() => CreateQuicListener(listenerOptions));
+
+ var clientOptions = CreateQuicClientOptions();
+ clientOptions.ClientAuthenticationOptions.CipherSuitesPolicy = policy;
+ clientOptions.RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, 5000);
+ Assert.Throws<ArgumentException>(() => CreateQuicConnection(clientOptions));
+ }
+
+ [Fact]
+ public async Task MismatchedCipherPolicies_ConnectAsync_ThrowsQuicException()
+ {
+ await Assert.ThrowsAsync<QuicException>(() => TestConnection(
+ new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_AES_128_GCM_SHA256 }),
+ new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_AES_256_GCM_SHA384 })
+ ));
+ }
+ }
+}
\ No newline at end of file
return CreateQuicListener(options);
}
- private QuicListener CreateQuicListener(QuicListenerOptions options) => new QuicListener(ImplementationProvider, options);
+ internal QuicListener CreateQuicListener(QuicListenerOptions options) => new QuicListener(ImplementationProvider, options);
internal Task<(QuicConnection, QuicConnection)> CreateConnectedQuicConnection(QuicListener listener) => CreateConnectedQuicConnection(null, listener);
internal async Task<(QuicConnection, QuicConnection)> CreateConnectedQuicConnection(QuicClientConnectionOptions? clientOptions, QuicListenerOptions listenerOptions)