Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ private static partial int AppleCryptoNative_SslSetTargetName(
[LibraryImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslHandshake")]
internal static partial PAL_TlsHandshakeState SslHandshake(SafeSslHandle sslHandle);

[LibraryImport(Interop.Libraries.AppleCryptoNative)]
private static partial int AppleCryptoNative_SslSetError(
SafeSslHandle sslHandle,
TlsAlertMessage alertMessage,
out int pOSStatus);

[LibraryImport(Interop.Libraries.AppleCryptoNative)]
private static partial int AppleCryptoNative_SslSetAcceptClientCert(SafeSslHandle sslHandle);

Expand Down Expand Up @@ -328,6 +334,25 @@ internal static void SslBreakOnCertRequested(SafeSslHandle sslHandle, bool setBr
throw new SslException();
}

internal static void SslSetError(SafeSslHandle sslHandle, TlsAlertMessage alertMessage)
{
int osStatus;
int result = AppleCryptoNative_SslSetError(sslHandle, alertMessage, out osStatus);

if (result == 1)
{
return;
}

if (result == 0)
{
throw CreateExceptionForOSStatus(osStatus);
}

Debug.Fail($"AppleCryptoNative_SslSetError returned {result}");
throw new SslException();
}

internal static void SslSetCertificate(SafeSslHandle sslHandle, ReadOnlySpan<IntPtr> certChainPtrs)
{
using (SafeCreateHandle cfCertRefs = CoreFoundation.CFArrayCreate(certChainPtrs))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,14 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[
throw new AuthenticationException(SR.Format(SR.net_auth_tls_alert, _lastFrame.AlertDescription.ToString()), token.GetException());
}

if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CertValidationFailed && token.GetException() is Exception certException)
{
// The cert-validation path carries the user-visible exception directly;
// throw it without wrapping to preserve parity with the SendAuthResetSignal
// path used after handshake completion on all platforms.
ExceptionDispatchInfo.Throw(certException);
}

throw new AuthenticationException(SR.net_auth_SSPI, token.GetException());
}
else if (token.Status.ErrorCode == SecurityStatusPalErrorCode.OK)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,13 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
_sslAuthenticationOptions);
}
}

#if TARGET_APPLE
if (token.Status.ErrorCode == SecurityStatusPalErrorCode.CertValidationNeeded)
{
token = VerifyRemoteCertificateAndGenerateNextToken(token);
}
#endif
} while (cachedCreds && _credentialsHandle == null);
}
finally
Expand Down Expand Up @@ -934,6 +941,28 @@ private ProtocolToken GenerateToken(ReadOnlySpan<byte> inputBuffer, out int cons
return token;
}

#if TARGET_APPLE
private ProtocolToken VerifyRemoteCertificateAndGenerateNextToken(ProtocolToken token)
{
// SecureTransport pauses the handshake (errSSL{Server,Client}AuthCompleted) before
// any bytes are produced for the next handshake flight, so the pending-writes buffer
// drained into token should be empty here. Assert to catch any future regression
// that would silently drop handshake bytes.
Debug.Assert(token.Size == 0, "Expected empty payload at CertValidationNeeded pause; dropping non-empty payload would lose handshake bytes.");
token.ReleasePayload();
Comment thread
liveans marked this conversation as resolved.

ProtocolToken alertToken = default;

if (!VerifyRemoteCertificate(_sslAuthenticationOptions.CertificateContext?.Trust, ref alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus))
{
alertToken.Status = new SecurityStatusPal(SecurityStatusPalErrorCode.CertValidationFailed, CreateCertificateValidationException(_sslAuthenticationOptions, sslPolicyErrors, chainStatus));
Comment on lines +945 to +958
return alertToken;
}

return GenerateToken(ReadOnlySpan<byte>.Empty, out _);
}
#endif

internal ProtocolToken Renegotiate()
{
Debug.Assert(_securityContext != null);
Expand Down Expand Up @@ -1244,7 +1273,7 @@ internal bool VerifyRemoteCertificate(
if (!success)
{
#pragma warning disable CS0162 // unreachable code detected (compile time const)
if (SslStreamPal.CanGenerateCustomAlerts && !SslStreamPal.CertValidationInCallback)
if (SslStreamPal.CanGenerateCustomAlertsForContext(_securityContext) && !SslStreamPal.CertValidationInCallback)
{
CreateFatalHandshakeAlertToken(sslPolicyErrors, chain!, ref alertToken);
}
Expand Down Expand Up @@ -1298,6 +1327,17 @@ private void CreateFatalHandshakeAlertToken(SslPolicyErrors sslPolicyErrors, X50
}
}

#if TARGET_APPLE
if (_securityContext is not null && !SslStreamPal.IsAsyncSecurityContext(_securityContext))
{
byte[] alertFrame = TlsFrameHelper.CreateAlertFrame(_lastFrame.Header.Version, (TlsAlertDescription)alertMessage);
if (alertFrame.Length != 0)
{
alertToken.SetPayload(alertFrame);
return;
}
}
#endif
alertToken = GenerateAlertToken();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public static Exception GetException(SecurityStatusPal status)
// due to handshake failures.
internal const bool CanGenerateCustomAlerts = true;

internal static bool CanGenerateCustomAlertsForContext(SafeDeleteContext? _)
{
return CanGenerateCustomAlerts;
}

public static void VerifyPackageInfo()
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ public static Exception GetException(SecurityStatusPal status)
// Since ST is not producing the framed empty message just call this false and avoid the
// special case of an empty array being passed to the `fixed` statement.
internal const bool CanEncryptEmptyMessage = false;
internal const bool CanGenerateCustomAlerts = false;
internal const bool CanGenerateCustomAlerts = true;

internal static bool CanGenerateCustomAlertsForContext(SafeDeleteContext? securityContext)
{
return securityContext is SafeDeleteSslContext;
}

public static void VerifyPackageInfo()
{
Expand Down Expand Up @@ -409,13 +414,7 @@ private static SecurityStatusPal PerformHandshake(SafeSslHandle sslHandle)
return new SecurityStatusPal(SecurityStatusPalErrorCode.ContinueNeeded);
case PAL_TlsHandshakeState.ServerAuthCompleted:
case PAL_TlsHandshakeState.ClientAuthCompleted:
// The standard flow would be to call the verification callback now, and
// possibly abort. But the library is set up to call this "success" and
// do verification between "handshake complete" and "first send/receive".
//
// So, call SslHandshake again to indicate to Secure Transport that we've
// accepted this handshake and it should go into the ready state.
break;
return new SecurityStatusPal(SecurityStatusPalErrorCode.CertValidationNeeded);
case PAL_TlsHandshakeState.ClientCertRequested:
return new SecurityStatusPal(SecurityStatusPalErrorCode.CredentialsNeeded);
case PAL_TlsHandshakeState.ClientHelloReceived:
Expand All @@ -434,11 +433,23 @@ public static SecurityStatusPal ApplyAlertToken(
TlsAlertType alertType,
TlsAlertMessage alertMessage)
{
// There doesn't seem to be an exposed API for writing an alert,
// the API seems to assume that all alerts are generated internally by
// SSLHandshake.
Debug.Assert(CanGenerateCustomAlerts);
return new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
Debug.Assert(alertType == TlsAlertType.Fatal, $"SecureTransport derives the alert level from the OSStatus and emits only fatal alerts; unexpected alertType: {alertType}");

if (securityContext is not SafeDeleteSslContext context)
{
return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError);
}

try
{
Interop.AppleCrypto.SslSetError(context.SslContext, alertMessage);
return new SecurityStatusPal(SecurityStatusPalErrorCode.OK);
}
catch (Exception ex)
{
return new SecurityStatusPal(SecurityStatusPalErrorCode.InternalError, ex);
}
}
#pragma warning restore IDE0060

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public static Exception GetException(SecurityStatusPal status)
internal const bool CanEncryptEmptyMessage = false;
internal const bool CanGenerateCustomAlerts = false;

internal static bool CanGenerateCustomAlertsForContext(SafeDeleteContext? _)
{
return CanGenerateCustomAlerts;
}

public static void VerifyPackageInfo()
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public static Exception GetException(SecurityStatusPal status)
internal const bool CanEncryptEmptyMessage = true;
internal const bool CanGenerateCustomAlerts = true;

internal static bool CanGenerateCustomAlertsForContext(SafeDeleteContext? _)
{
return CanGenerateCustomAlerts;
}

private static readonly byte[] s_sessionTokenBuffer = InitSessionTokenBuffer();

private static byte[] InitSessionTokenBuffer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ public static byte[] CreateAlertFrame(SslProtocols version, TlsAlertDescription
return CreateProtocolVersionAlert(version);
}
#pragma warning disable SYSLIB0039 // TLS 1.0 and 1.1 are obsolete
else if ((int)version > (int)SslProtocols.Tls)
else if ((int)version >= (int)SslProtocols.Tls)
#pragma warning restore SYSLIB0039
{
// Create TLS1.2 alert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ internal enum SecurityStatusPalErrorCode
Renegotiate,
TryAgain,
HandshakeStarted,
CertValidationNeeded,

// Errors
OutOfMemory,
Comment thread
liveans marked this conversation as resolved.
Expand Down Expand Up @@ -74,6 +75,7 @@ internal enum SecurityStatusPalErrorCode
NoRenegotiation,
KeySetDoesNotExist,
ContextExpiredError,
CertValidationFailed,
MutualAuthFailed
Comment thread
liveans marked this conversation as resolved.
}
}
Loading