Skip to content

Commit 5d86589

Browse files
authored
Unify certificate validation code on OSX between SslStream and QuicConnection (#117611)
* Unify certificate validation code on OSX between SslStream and QuicConnection * Fix build * Remove spaces in test case
1 parent 9897a3b commit 5d86589

File tree

7 files changed

+6
-261
lines changed

7 files changed

+6
-261
lines changed

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

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ internal static partial class Interop
1616
{
1717
internal static partial class AppleCrypto
1818
{
19-
private static readonly IdnMapping s_idnMapping = new IdnMapping();
20-
2119
// Read data from connection (or an instance delegate captured context) and write it to data
2220
// dataLength comes in as the capacity of data, goes out as bytes written.
2321
// Note: the true type of dataLength is `size_t*`, but on macOS that's most equal to `void**`
@@ -152,13 +150,6 @@ internal static unsafe partial int SslSetIoCallbacks(
152150
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslRead")]
153151
internal static unsafe partial PAL_TlsIo SslRead(SafeSslHandle sslHandle, byte* writeFrom, int count, out int bytesWritten);
154152

155-
[LibraryImport(Interop.Libraries.AppleCryptoNative)]
156-
private static partial int AppleCryptoNative_SslIsHostnameMatch(
157-
SafeSslHandle handle,
158-
SafeCreateHandle cfHostname,
159-
SafeCFDateHandle cfValidTime,
160-
out int pOSStatus);
161-
162153
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslShutdown")]
163154
internal static partial int SslShutdown(SafeSslHandle sslHandle);
164155

@@ -462,40 +453,6 @@ internal static unsafe int SslCtxSetAlpnProtocol(SafeSslHandle ctx, SslApplicati
462453
protocol.Dispose();
463454
}
464455
}
465-
466-
public static bool SslCheckHostnameMatch(SafeSslHandle handle, string hostName, DateTime notBefore, out int osStatus)
467-
{
468-
int result;
469-
// The IdnMapping converts Unicode input into the IDNA punycode sequence.
470-
// It also does host case normalization. The bypass logic would be something
471-
// like "all characters being within [a-z0-9.-]+"
472-
//
473-
// The SSL Policy (SecPolicyCreateSSL) has been verified as not inherently supporting
474-
// IDNA as of macOS 10.12.1 (Sierra). If it supports low-level IDNA at a later date,
475-
// this code could be removed.
476-
//
477-
// It was verified as supporting case invariant match as of 10.12.1 (Sierra).
478-
string matchName = string.IsNullOrEmpty(hostName) ? string.Empty : s_idnMapping.GetAscii(hostName);
479-
480-
using (SafeCFDateHandle cfNotBefore = CoreFoundation.CFDateCreate(notBefore))
481-
using (SafeCreateHandle cfHostname = CoreFoundation.CFStringCreateWithCString(matchName))
482-
{
483-
result = AppleCryptoNative_SslIsHostnameMatch(handle, cfHostname, cfNotBefore, out osStatus);
484-
}
485-
486-
switch (result)
487-
{
488-
case 0:
489-
return false;
490-
case 1:
491-
return true;
492-
default:
493-
if (NetEventSource.Log.IsEnabled())
494-
NetEventSource.Error(null, $"AppleCryptoNative_SslIsHostnameMatch returned '{result}' for '{hostName}'");
495-
Debug.Fail($"AppleCryptoNative_SslIsHostnameMatch returned {result}");
496-
throw new SslException();
497-
}
498-
}
499456
}
500457
}
501458

src/libraries/System.Net.Security/src/System.Net.Security.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@
442442
Link="Common\Interop\OSX\System.Security.Cryptography.Native.Apple\Interop.X509Chain.cs" />
443443
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs"
444444
Link="Common\Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs" />
445+
<Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.OSX.cs"
446+
Link="Common\System\Net\Security\CertificateValidation.OSX.cs" />
445447
<Compile Include="System\Net\CertificateValidationPal.OSX.cs" />
446448
<Compile Include="System\Net\Security\Pal.Managed\SslProtocolsValidation.cs" />
447449
<Compile Include="System\Net\Security\Pal.OSX\SafeDeleteSslContext.cs" />

src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.OSX.cs

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,14 @@ namespace System.Net
1010
internal static partial class CertificateValidationPal
1111
{
1212
internal static SslPolicyErrors VerifyCertificateProperties(
13-
SafeDeleteContext securityContext,
13+
SafeDeleteContext? _ /*securityContext*/,
1414
X509Chain chain,
15-
X509Certificate2? remoteCertificate,
15+
X509Certificate2 remoteCertificate,
1616
bool checkCertName,
1717
bool isServer,
1818
string? hostName)
1919
{
20-
SslPolicyErrors errors = SslPolicyErrors.None;
21-
22-
if (remoteCertificate == null)
23-
{
24-
errors |= SslPolicyErrors.RemoteCertificateNotAvailable;
25-
}
26-
else
27-
{
28-
if (!chain.Build(remoteCertificate))
29-
{
30-
errors |= SslPolicyErrors.RemoteCertificateChainErrors;
31-
}
32-
33-
if (!isServer && checkCertName)
34-
{
35-
SafeDeleteSslContext sslContext = (SafeDeleteSslContext)securityContext;
36-
37-
if (!Interop.AppleCrypto.SslCheckHostnameMatch(sslContext.SslContext, hostName!, remoteCertificate.NotBefore, out int osStatus))
38-
{
39-
errors |= SslPolicyErrors.RemoteCertificateNameMismatch;
40-
41-
if (NetEventSource.Log.IsEnabled())
42-
NetEventSource.Error(sslContext, $"Cert name validation for '{hostName}' failed with status '{osStatus}'");
43-
}
44-
}
45-
}
46-
47-
return errors;
20+
return CertificateValidation.BuildChainAndVerifyProperties(chain, remoteCertificate, checkCertName, isServer, hostName, Span<byte>.Empty);
4821
}
4922

5023
private static X509Certificate2? GetRemoteCertificate(

src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamSniTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ public static IEnumerable<object[]> HostNameData()
378378
yield return new object[] { "test" };
379379
// max allowed hostname length is 63
380380
yield return new object[] { new string('a', 63) };
381-
yield return new object[] { "\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 ja\u017A\u0144. \u7EA2\u70E7. \u7167\u308A\u713C\u304D" };
381+
yield return new object[] { "\u017C\u00F3\u0142\u0107g\u0119\u015Bl\u0105ja\u017A\u0144.\u7EA2\u70E7.\u7167\u308A\u713C\u304D" };
382382
}
383383
}
384384
}

src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ static const Entry s_cryptoAppleNative[] =
6262
DllImportEntry(AppleCryptoNative_SetKeychainNeverLock)
6363
DllImportEntry(AppleCryptoNative_SslCopyCADistinguishedNames)
6464
DllImportEntry(AppleCryptoNative_SslCopyCertChain)
65-
DllImportEntry(AppleCryptoNative_SslIsHostnameMatch)
6665
DllImportEntry(AppleCryptoNative_SslRead)
6766
DllImportEntry(AppleCryptoNative_SslSetBreakOnCertRequested)
6867
DllImportEntry(AppleCryptoNative_SslSetBreakOnClientAuth)

src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.c

Lines changed: 0 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -416,181 +416,6 @@ PAL_TlsIo AppleCryptoNative_SslRead(SSLContextRef sslContext, uint8_t* buf, uint
416416
return OSStatusToPAL_TlsIo(status);
417417
}
418418

419-
int32_t AppleCryptoNative_SslIsHostnameMatch(SSLContextRef sslContext, CFStringRef cfHostname, CFDateRef notBefore, int32_t* pOSStatus)
420-
{
421-
if (pOSStatus != NULL)
422-
*pOSStatus = noErr;
423-
424-
if (sslContext == NULL || notBefore == NULL || pOSStatus == NULL)
425-
return -1;
426-
427-
if (cfHostname == NULL)
428-
return -2;
429-
430-
SecPolicyRef sslPolicy = SecPolicyCreateSSL(true, cfHostname);
431-
432-
if (sslPolicy == NULL)
433-
return -3;
434-
435-
CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
436-
437-
if (certs == NULL)
438-
return -4;
439-
440-
SecTrustRef existingTrust = NULL;
441-
#pragma clang diagnostic push
442-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
443-
OSStatus osStatus = SSLCopyPeerTrust(sslContext, &existingTrust);
444-
#pragma clang diagnostic pop
445-
446-
if (osStatus != noErr)
447-
{
448-
CFRelease(certs);
449-
*pOSStatus = osStatus;
450-
return -5;
451-
}
452-
453-
CFMutableArrayRef anchors = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
454-
455-
if (anchors == NULL)
456-
{
457-
CFRelease(certs);
458-
CFRelease(existingTrust);
459-
return -6;
460-
}
461-
462-
CFIndex certificateCount = SecTrustGetCertificateCount(existingTrust);
463-
464-
for (CFIndex i = 0; i < certificateCount; i++)
465-
{
466-
#pragma clang diagnostic push
467-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
468-
SecCertificateRef item = SecTrustGetCertificateAtIndex(existingTrust, i);
469-
#pragma clang diagnostic pop
470-
471-
CFArrayAppendValue(certs, item);
472-
473-
// Copy the EE cert into the anchors set, this will make the chain part
474-
// always return true.
475-
if (i == 0)
476-
{
477-
CFArrayAppendValue(anchors, item);
478-
}
479-
}
480-
481-
SecTrustRef trust = NULL;
482-
osStatus = SecTrustCreateWithCertificates(certs, sslPolicy, &trust);
483-
int32_t ret = INT_MIN;
484-
485-
if (osStatus == noErr)
486-
{
487-
osStatus = SecTrustSetAnchorCertificates(trust, anchors);
488-
}
489-
490-
if (osStatus == noErr)
491-
{
492-
osStatus = SecTrustSetVerifyDate(trust, notBefore);
493-
}
494-
495-
if (osStatus == noErr)
496-
{
497-
SecTrustResultType trustResult;
498-
memset(&trustResult, 0, sizeof(SecTrustResultType));
499-
500-
#pragma clang diagnostic push
501-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
502-
osStatus = SecTrustEvaluate(trust, &trustResult);
503-
504-
if (trustResult == kSecTrustResultRecoverableTrustFailure && osStatus == noErr && certificateCount > 1)
505-
{
506-
// If we get recoverable failure, let's try it again with full anchor list.
507-
// We already stored just the first certificate into anchors; now we store the rest.
508-
for (CFIndex i = 1; i < certificateCount; i++)
509-
{
510-
CFArrayAppendValue(anchors, SecTrustGetCertificateAtIndex(existingTrust, i));
511-
}
512-
513-
osStatus = SecTrustSetAnchorCertificates(trust, anchors);
514-
if (osStatus == noErr)
515-
{
516-
memset(&trustResult, 0, sizeof(SecTrustResultType));
517-
osStatus = SecTrustEvaluate(trust, &trustResult);
518-
}
519-
}
520-
#pragma clang diagnostic pop
521-
522-
if (osStatus == noErr && trustResult != kSecTrustResultUnspecified && trustResult != kSecTrustResultProceed)
523-
{
524-
// If evaluation succeeded but result is not trusted try to get details.
525-
CFDictionaryRef detailsAndStuff = SecTrustCopyResult(trust);
526-
527-
if (detailsAndStuff != NULL)
528-
{
529-
CFArrayRef details = CFDictionaryGetValue(detailsAndStuff, CFSTR("TrustResultDetails"));
530-
531-
if (details != NULL && CFArrayGetCount(details) > 0)
532-
{
533-
CFArrayRef statusCodes = CFDictionaryGetValue(CFArrayGetValueAtIndex(details,0), CFSTR("StatusCodes"));
534-
535-
if (statusCodes != NULL)
536-
{
537-
OSStatus status = 0;
538-
// look for first failure to keep it simple. Normally, there will be exactly one.
539-
for (int i = 0; i < CFArrayGetCount(statusCodes); i++)
540-
{
541-
CFNumberGetValue(CFArrayGetValueAtIndex(statusCodes, i), kCFNumberSInt32Type, &status);
542-
if (status != noErr)
543-
{
544-
*pOSStatus = status;
545-
break;
546-
}
547-
}
548-
}
549-
}
550-
551-
CFRelease(detailsAndStuff);
552-
}
553-
}
554-
555-
if (osStatus != noErr)
556-
{
557-
ret = -7;
558-
*pOSStatus = osStatus;
559-
}
560-
else if (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)
561-
{
562-
ret = 1;
563-
}
564-
else if (trustResult == kSecTrustResultDeny || trustResult == kSecTrustResultRecoverableTrustFailure)
565-
{
566-
ret = 0;
567-
}
568-
else
569-
{
570-
ret = -8;
571-
}
572-
}
573-
else
574-
{
575-
*pOSStatus = osStatus;
576-
}
577-
578-
if (trust != NULL)
579-
CFRelease(trust);
580-
581-
if (certs != NULL)
582-
CFRelease(certs);
583-
584-
if (anchors != NULL)
585-
CFRelease(anchors);
586-
587-
if (existingTrust != NULL)
588-
CFRelease(existingTrust);
589-
590-
CFRelease(sslPolicy);
591-
return ret;
592-
}
593-
594419
int32_t AppleCryptoNative_SslShutdown(SSLContextRef sslContext)
595420
{
596421
#pragma clang diagnostic push

src/native/libs/System.Security.Cryptography.Native.Apple/pal_ssl.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,17 +226,6 @@ written: Receives the number of bytes written into buf
226226
PALEXPORT PAL_TlsIo
227227
AppleCryptoNative_SslRead(SSLContextRef sslContext, uint8_t* buf, uint32_t bufLen, uint32_t* written);
228228

229-
/*
230-
Check to see if the server identity certificate for this connection matches the requested hostname.
231-
232-
notBefore: Specify the EE/leaf certificate's notBefore value to prevent a false negative due to
233-
the certificate being expired (or not yet valid).
234-
235-
Returns 1 on match, 0 on mismatch, any other value indicates an invalid state.
236-
*/
237-
PALEXPORT int32_t
238-
AppleCryptoNative_SslIsHostnameMatch(SSLContextRef sslContext, CFStringRef cfHostname, CFDateRef notBefore, int32_t* pOSStatus);
239-
240229
/*
241230
Generate a TLS Close alert to terminate the session.
242231

0 commit comments

Comments
 (0)