|
4 | 4 | using System; |
5 | 5 | using System.Collections.Generic; |
6 | 6 | using System.IO.Pipelines; |
| 7 | +using System.Linq; |
7 | 8 | using System.Net.Security; |
| 9 | +using System.Runtime.InteropServices; |
8 | 10 | using System.Security.Authentication; |
9 | 11 | using System.Security.Cryptography.X509Certificates; |
10 | 12 | using Microsoft.AspNetCore.Connections; |
@@ -429,6 +431,90 @@ public void FallsBackToHttpsConnectionAdapterServerCertificateSelectorOverServer |
429 | 431 | Assert.Same(selectorCertificate, options.ServerCertificate); |
430 | 432 | } |
431 | 433 |
|
| 434 | + [Fact] |
| 435 | + public void CloneSslOptionsClonesAllProperties() |
| 436 | + { |
| 437 | + var propertyNames = typeof(SslServerAuthenticationOptions).GetProperties().Select(property => property.Name).ToList(); |
| 438 | + |
| 439 | + CipherSuitesPolicy cipherSuitesPolicy = null; |
| 440 | + |
| 441 | + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
| 442 | + { |
| 443 | + // The CipherSuitesPolicy ctor throws a PlatformNotSupportedException on Windows. |
| 444 | + cipherSuitesPolicy = new CipherSuitesPolicy(new[] { TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 }); |
| 445 | + } |
| 446 | + |
| 447 | + // Set options properties to non-default values to verify they're copied. |
| 448 | + var options = new SslServerAuthenticationOptions |
| 449 | + { |
| 450 | + // Defaults to true |
| 451 | + AllowRenegotiation = false, |
| 452 | + // Defaults to null |
| 453 | + ApplicationProtocols = new List<SslApplicationProtocol> { SslApplicationProtocol.Http3 }, |
| 454 | + // Defaults to X509RevocationMode.NoCheck |
| 455 | + CertificateRevocationCheckMode = X509RevocationMode.Offline, |
| 456 | + // Defaults to null |
| 457 | + CipherSuitesPolicy = cipherSuitesPolicy, |
| 458 | + // Defaults to false |
| 459 | + ClientCertificateRequired = true, |
| 460 | + // Defaults to SslProtocols.None |
| 461 | + EnabledSslProtocols = SslProtocols.Tls13 | SslProtocols.Tls11, |
| 462 | + // Defaults to EncryptionPolicy.RequireEncryption |
| 463 | + EncryptionPolicy = EncryptionPolicy.NoEncryption, |
| 464 | + // Defaults to null |
| 465 | + RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, |
| 466 | + // Defaults to null |
| 467 | + ServerCertificate = new X509Certificate2(), |
| 468 | + // Defaults to null |
| 469 | + ServerCertificateContext = SslStreamCertificateContext.Create(_x509Certificate2, additionalCertificates: null, offline: true), |
| 470 | + // Defaults to null |
| 471 | + ServerCertificateSelectionCallback = (sender, serverName) => null, |
| 472 | + }; |
| 473 | + |
| 474 | + var clonedOptions = SniOptionsSelector.CloneSslOptions(options); |
| 475 | + |
| 476 | + Assert.NotSame(options, clonedOptions); |
| 477 | + |
| 478 | + Assert.Equal(options.AllowRenegotiation, clonedOptions.AllowRenegotiation); |
| 479 | + Assert.True(propertyNames.Remove(nameof(options.AllowRenegotiation))); |
| 480 | + |
| 481 | + // Ensure the List<SslApplicationProtocol> is also cloned since it could be modified by a user callback. |
| 482 | + Assert.NotSame(options.ApplicationProtocols, clonedOptions.ApplicationProtocols); |
| 483 | + Assert.Equal(Assert.Single(options.ApplicationProtocols), Assert.Single(clonedOptions.ApplicationProtocols)); |
| 484 | + Assert.True(propertyNames.Remove(nameof(options.ApplicationProtocols))); |
| 485 | + |
| 486 | + Assert.Equal(options.CertificateRevocationCheckMode, clonedOptions.CertificateRevocationCheckMode); |
| 487 | + Assert.True(propertyNames.Remove(nameof(options.CertificateRevocationCheckMode))); |
| 488 | + |
| 489 | + Assert.Same(options.CipherSuitesPolicy, clonedOptions.CipherSuitesPolicy); |
| 490 | + Assert.True(propertyNames.Remove(nameof(options.CipherSuitesPolicy))); |
| 491 | + |
| 492 | + Assert.Equal(options.ClientCertificateRequired, clonedOptions.ClientCertificateRequired); |
| 493 | + Assert.True(propertyNames.Remove(nameof(options.ClientCertificateRequired))); |
| 494 | + |
| 495 | + Assert.Equal(options.EnabledSslProtocols, clonedOptions.EnabledSslProtocols); |
| 496 | + Assert.True(propertyNames.Remove(nameof(options.EnabledSslProtocols))); |
| 497 | + |
| 498 | + Assert.Equal(options.EncryptionPolicy, clonedOptions.EncryptionPolicy); |
| 499 | + Assert.True(propertyNames.Remove(nameof(options.EncryptionPolicy))); |
| 500 | + |
| 501 | + Assert.Same(options.RemoteCertificateValidationCallback, clonedOptions.RemoteCertificateValidationCallback); |
| 502 | + Assert.True(propertyNames.Remove(nameof(options.RemoteCertificateValidationCallback))); |
| 503 | + |
| 504 | + // Technically the ServerCertificate could be reset/reimported, but I'm hoping this is uncommon. Trying to clone the certificate and/or context seems risky. |
| 505 | + Assert.Same(options.ServerCertificate, clonedOptions.ServerCertificate); |
| 506 | + Assert.True(propertyNames.Remove(nameof(options.ServerCertificate))); |
| 507 | + |
| 508 | + Assert.Same(options.ServerCertificateContext, clonedOptions.ServerCertificateContext); |
| 509 | + Assert.True(propertyNames.Remove(nameof(options.ServerCertificateContext))); |
| 510 | + |
| 511 | + Assert.Same(options.ServerCertificateSelectionCallback, clonedOptions.ServerCertificateSelectionCallback); |
| 512 | + Assert.True(propertyNames.Remove(nameof(options.ServerCertificateSelectionCallback))); |
| 513 | + |
| 514 | + // Ensure we've checked every property. When new properties get added, we'll have to update this test along with the CloneSslOptions implementation. |
| 515 | + Assert.Empty(propertyNames); |
| 516 | + } |
| 517 | + |
432 | 518 | private class MockCertificateConfigLoader : ICertificateConfigLoader |
433 | 519 | { |
434 | 520 | public Dictionary<object, string> CertToPathDictionary { get; } = new Dictionary<object, string>(ReferenceEqualityComparer.Instance); |
|
0 commit comments