Skip to content

Commit 3d2e38a

Browse files
authored
Add option to disable particular TLS signature algorithms (#114741)
* Dirty WIP on Windows * Linux half of the experiment * OpenSSL configure signature algorithm WIP * Fix windows * Rename to EnableRsaPkcsPad * Minor changes * Rename in accordance to approved API shape * Remove unintended change * Set also client sigalgs * Minor changes * Fix build * Add unit test * Fix interaction with TLS session caching * Fix linux build * Fix linux filtering * Code review feedback * Reuse string resource * recognize MLDSA sigalgs * Scope tests to Windows and OSX * Throw PNSE on unsupported platforms, add supported os annotation * Disable tests on Windows Nano * Fix typo * Fix build * fixup! Disable tests on Windows Nano
1 parent e59010b commit 3d2e38a

File tree

19 files changed

+611
-57
lines changed

19 files changed

+611
-57
lines changed

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using System.Buffers;
56
using System.Collections.Concurrent;
67
using System.Collections.Generic;
78
using System.Collections.ObjectModel;
@@ -15,6 +16,7 @@
1516
using System.Security.Authentication.ExtendedProtection;
1617
using System.Security.Cryptography;
1718
using System.Security.Cryptography.X509Certificates;
19+
using System.Text;
1820
using Microsoft.Win32.SafeHandles;
1921

2022
internal static partial class Interop
@@ -28,6 +30,7 @@ internal static partial class OpenSsl
2830
private const int DefaultTlsCacheSizeClient = 500; // since we keep only one TLS Session per hostname, 500 should be enough to cover most scenarios
2931
private const int DefaultTlsCacheSizeServer = -1; // use implementation default
3032
private const SslProtocols FakeAlpnSslProtocol = (SslProtocols)1; // used to distinguish server sessions with ALPN
33+
private static readonly Lazy<string[]> s_defaultSigAlgs = new(GetDefaultSignatureAlgorithms);
3134

3235
private sealed class SafeSslContextCache : SafeHandleCache<SslContextCacheKey, SafeSslContextHandle> { }
3336

@@ -414,6 +417,11 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth
414417
sslHandle.SslContextHandle = sslCtxHandle;
415418
}
416419

420+
if (!sslAuthenticationOptions.AllowRsaPssPadding || !sslAuthenticationOptions.AllowRsaPkcs1Padding)
421+
{
422+
ConfigureSignatureAlgorithms(sslHandle, sslAuthenticationOptions.AllowRsaPssPadding, sslAuthenticationOptions.AllowRsaPkcs1Padding);
423+
}
424+
417425
if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0)
418426
{
419427
if (sslAuthenticationOptions.IsServer)
@@ -516,6 +524,141 @@ internal static SafeSslHandle AllocateSslHandle(SslAuthenticationOptions sslAuth
516524
return sslHandle;
517525
}
518526

527+
internal static string[] GetDefaultSignatureAlgorithms()
528+
{
529+
ushort[] rawAlgs = Interop.Ssl.GetDefaultSignatureAlgorithms();
530+
531+
// The mapping below is taken from STRINT_PAIR signature_tls13_scheme_list and other
532+
// data structures in OpenSSL source code (apps/lib/s_cb.c file).
533+
static string ConvertAlg(ushort rawAlg) => rawAlg switch
534+
{
535+
0x0201 => "rsa_pkcs1_sha1",
536+
0x0203 => "ecdsa_sha1",
537+
0x0401 => "rsa_pkcs1_sha256",
538+
0x0403 => "ecdsa_secp256r1_sha256",
539+
0x0501 => "rsa_pkcs1_sha384",
540+
0x0503 => "ecdsa_secp384r1_sha384",
541+
0x0601 => "rsa_pkcs1_sha512",
542+
0x0603 => "ecdsa_secp521r1_sha512",
543+
0x0804 => "rsa_pss_rsae_sha256",
544+
0x0805 => "rsa_pss_rsae_sha384",
545+
0x0806 => "rsa_pss_rsae_sha512",
546+
0x0807 => "ed25519",
547+
0x0808 => "ed448",
548+
0x0809 => "rsa_pss_pss_sha256",
549+
0x080a => "rsa_pss_pss_sha384",
550+
0x080b => "rsa_pss_pss_sha512",
551+
0x081a => "ecdsa_brainpoolP256r1_sha256",
552+
0x081b => "ecdsa_brainpoolP384r1_sha384",
553+
0x081c => "ecdsa_brainpoolP512r1_sha512",
554+
0x0904 => "mldsa44",
555+
0x0905 => "mldsa65",
556+
0x0906 => "mldsa87",
557+
_ =>
558+
Tls12HashName((byte)(rawAlg >> 8)) is string hashName &&
559+
Tls12SignatureName((byte)rawAlg) is string sigName
560+
? $"{sigName}+{hashName}"
561+
: $"0x{rawAlg:x4}" // this will cause the setter to fail, but at least we get a string representation in the log.
562+
};
563+
564+
static string? Tls12HashName(byte raw) => raw switch
565+
{
566+
0x00 => "none",
567+
0x01 => "MD5",
568+
0x02 => "SHA1",
569+
0x03 => "SHA224",
570+
0x04 => "SHA256",
571+
0x05 => "SHA384",
572+
0x06 => "SHA512",
573+
_ => null
574+
};
575+
576+
static string? Tls12SignatureName(byte raw) => raw switch
577+
{
578+
0x00 => "anonymous",
579+
0x01 => "RSA",
580+
0x02 => "DSA",
581+
0x03 => "ECDSA",
582+
_ => null
583+
};
584+
585+
string[] result = Array.ConvertAll(rawAlgs, ConvertAlg);
586+
if (NetEventSource.Log.IsEnabled())
587+
{
588+
NetEventSource.Info(null, $"Default signature algorithms: {string.Join(":", result)}");
589+
}
590+
591+
return result;
592+
}
593+
594+
internal static unsafe void ConfigureSignatureAlgorithms(SafeSslHandle sslHandle, bool enablePss, bool enablePkcs1)
595+
{
596+
byte[] buffer = ArrayPool<byte>.Shared.Rent(512);
597+
try
598+
{
599+
int index = 0;
600+
601+
foreach (string alg in s_defaultSigAlgs.Value)
602+
{
603+
// includes both rsa_pss_pss_* and rsa_pss_rsae_*
604+
if (alg.StartsWith("rsa_pss_", StringComparison.Ordinal) && !enablePss)
605+
{
606+
continue;
607+
}
608+
609+
if (alg.StartsWith("rsa_pkcs1_", StringComparison.Ordinal) && !enablePkcs1)
610+
{
611+
continue;
612+
}
613+
614+
// Ensure we have enough space for the algorithm name, separator and null terminator.
615+
EnsureSize(ref buffer, index + alg.Length + 2);
616+
617+
if (index > 0)
618+
{
619+
buffer[index++] = (byte)':';
620+
}
621+
622+
index += Encoding.UTF8.GetBytes(alg, buffer.AsSpan(index));
623+
}
624+
buffer[index] = 0; // null terminator
625+
626+
int ret;
627+
fixed (byte* pBuffer = buffer)
628+
{
629+
ret = Interop.Ssl.SslSetSigalgs(sslHandle, pBuffer);
630+
if (ret != 1)
631+
{
632+
throw CreateSslException(SR.Format(SR.net_ssl_set_sigalgs_failed, "server"));
633+
}
634+
635+
ret = Interop.Ssl.SslSetClientSigalgs(sslHandle, pBuffer);
636+
if (ret != 1)
637+
{
638+
throw CreateSslException(SR.Format(SR.net_ssl_set_sigalgs_failed, "client"));
639+
}
640+
}
641+
}
642+
finally
643+
{
644+
ArrayPool<byte>.Shared.Return(buffer);
645+
}
646+
647+
static void EnsureSize(ref byte[] buffer, int size)
648+
{
649+
if (buffer.Length < size)
650+
{
651+
// there are a few dozen algorithms total in existence, so we don't expect the buffer to grow too large.
652+
Debug.Assert(size < 10 * 1024, "The buffer should not grow too large.");
653+
654+
byte[] oldBuffer = buffer;
655+
buffer = ArrayPool<byte>.Shared.Rent(buffer.Length * 2);
656+
oldBuffer.AsSpan().CopyTo(buffer);
657+
ArrayPool<byte>.Shared.Return(oldBuffer);
658+
}
659+
}
660+
}
661+
519662
internal static SecurityStatusPal SslRenegotiate(SafeSslHandle sslContext, out byte[]? outputBuffer)
520663
{
521664
int ret = Interop.Ssl.SslRenegotiate(sslContext, out Ssl.SslErrorCode errorCode);

src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,24 @@ internal static unsafe ReadOnlySpan<byte> SslGetAlpnSelected(SafeSslHandle ssl)
8383
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslRead", SetLastError = true)]
8484
internal static partial int SslRead(SafeSslHandle ssl, ref byte buf, int num, out SslErrorCode error);
8585

86+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetDefaultSignatureAlgorithms")]
87+
private static unsafe partial int GetDefaultSignatureAlgorithms(Span<ushort> algorithms, ref int algorithmCount);
88+
89+
internal static ushort[] GetDefaultSignatureAlgorithms()
90+
{
91+
// 256 algorithms should be more than enough for any use case.
92+
Span<ushort> algorithms = stackalloc ushort[256];
93+
int algorithmCount = algorithms.Length;
94+
int res = GetDefaultSignatureAlgorithms(algorithms, ref algorithmCount);
95+
96+
if (res != 0 || algorithmCount > algorithms.Length)
97+
{
98+
throw Interop.OpenSsl.CreateSslException(SR.net_ssl_get_default_sigalgs_failed);
99+
}
100+
101+
return algorithms.Slice(0, algorithmCount).ToArray();
102+
}
103+
86104
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslRenegotiate")]
87105
internal static partial int SslRenegotiate(SafeSslHandle ssl, out SslErrorCode error);
88106

@@ -186,6 +204,12 @@ internal static SafeSharedX509StackHandle SslGetPeerCertChain(SafeSslHandle ssl)
186204
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetPostHandshakeAuth")]
187205
internal static partial void SslSetPostHandshakeAuth(SafeSslHandle ssl, int value);
188206

207+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetSigalgs")]
208+
internal static unsafe partial int SslSetSigalgs(SafeSslHandle ssl, byte* str);
209+
210+
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslSetClientSigalgs")]
211+
internal static unsafe partial int SslSetClientSigalgs(SafeSslHandle ssl, byte* str);
212+
189213
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Tls13Supported")]
190214
private static partial int Tls13SupportedImpl();
191215

src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ public enum Flags
248248
SCH_CRED_IGNORE_REVOCATION_OFFLINE = 0x1000,
249249
SCH_CRED_CACHE_ONLY_URL_RETRIEVAL_ON_CREATE = 0x2000,
250250
SCH_SEND_ROOT_CERT = 0x40000,
251-
SCH_SEND_AUX_RECORD = 0x00200000,
251+
SCH_SEND_AUX_RECORD = 0x00200000,
252252
SCH_USE_STRONG_CRYPTO = 0x00400000,
253253
SCH_USE_PRESHAREDKEY_ONLY = 0x800000,
254254
SCH_ALLOW_NULL_ENCRYPTION = 0x02000000,
@@ -259,7 +259,7 @@ public enum Flags
259259
internal unsafe struct TLS_PARAMETERS
260260
{
261261
public int cAlpnIds; // Valid for server applications only. Must be zero otherwise. Number of ALPN IDs in rgstrAlpnIds; set to 0 if applies to all.
262-
public IntPtr rgstrAlpnIds; // Valid for server applications only. Must be NULL otherwise. Array of ALPN IDs that the following settings apply to; set to NULL if applies to all.
262+
public UNICODE_STRING* rgstrAlpnIds; // Valid for server applications only. Must be NULL otherwise. Array of ALPN IDs that the following settings apply to; set to NULL if applies to all.
263263
public uint grbitDisabledProtocols; // List protocols you DO NOT want negotiated.
264264
public int cDisabledCrypto; // Number of CRYPTO_SETTINGS structures; set to 0 if there are none.
265265
public CRYPTO_SETTINGS* pDisabledCrypto; // Array of CRYPTO_SETTINGS structures; set to NULL if there are none;
@@ -278,7 +278,7 @@ public enum Flags
278278
internal unsafe struct CRYPTO_SETTINGS
279279
{
280280
public TlsAlgorithmUsage eAlgorithmUsage; // How this algorithm is being used.
281-
public UNICODE_STRING* strCngAlgId; // CNG algorithm identifier.
281+
public UNICODE_STRING strCngAlgId; // CNG algorithm identifier.
282282
public int cChainingModes; // Set to 0 if CNG algorithm does not have a chaining mode.
283283
public UNICODE_STRING* rgstrChainingModes; // Set to NULL if CNG algorithm does not have a chaining mode.
284284
public int dwMinBitLength; // Minimum bit length for the specified CNG algorithm. Set to 0 if not defined or CNG algorithm implies bit length.
@@ -374,7 +374,7 @@ internal static unsafe partial int VerifySignature(
374374
ref CredHandle contextHandle,
375375
in SecBufferDesc input,
376376
uint sequenceNumber,
377-
uint *qualityOfProtection);
377+
uint* qualityOfProtection);
378378

379379
[LibraryImport(Interop.Libraries.SspiCli, SetLastError = true)]
380380
internal static partial int QuerySecurityContextToken(

src/libraries/Common/src/System/Net/Security/SslClientAuthenticationOptionsExtensions.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,18 @@ public static SslClientAuthenticationOptions ShallowClone(this SslClientAuthenti
2929
EncryptionPolicy = options.EncryptionPolicy,
3030
LocalCertificateSelectionCallback = options.LocalCertificateSelectionCallback,
3131
RemoteCertificateValidationCallback = options.RemoteCertificateValidationCallback,
32-
TargetHost = options.TargetHost
32+
TargetHost = options.TargetHost,
33+
#pragma warning disable CA1416 // Ignore SupportedOSPlatform checks, the value will be validated at runtime inside SslStream
34+
AllowRsaPssPadding = options.AllowRsaPssPadding,
35+
AllowRsaPkcs1Padding = options.AllowRsaPkcs1Padding
36+
#pragma warning restore CA1416
3337
};
3438

3539
#if DEBUG
3640
// Try to detect if a property gets added that we're not copying correctly.
3741
// The property count is guard for new properties that also needs to be added above.
3842
PropertyInfo[] properties = typeof(SslClientAuthenticationOptions).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)!;
39-
Debug.Assert(properties.Length == 13);
43+
Debug.Assert(properties.Length == 15);
4044
foreach (PropertyInfo pi in properties)
4145
{
4246
object? origValue = pi.GetValue(options);

0 commit comments

Comments
 (0)