@@ -436,4 +436,231 @@ private SslSettings(string certificatePath, string certificatePassword, X509KeyS
436436 RequireMutualAuthentication = requireMutualAuthentication ;
437437 }
438438 }
439+
440+ /// <summary>
441+ /// INTERNAL API
442+ ///
443+ /// Helper class for building human-readable error messages for TLS/SSL certificate validation failures.
444+ /// Provides detailed diagnostics and actionable suggestions for common certificate issues.
445+ /// </summary>
446+ internal static class TlsErrorMessageBuilder
447+ {
448+ /// <summary>
449+ /// Builds a detailed error message for SSL policy errors encountered during TLS handshake.
450+ /// </summary>
451+ /// <param name="errors">The SSL policy errors from certificate validation callback</param>
452+ /// <param name="certificate">The certificate that failed validation (may be null)</param>
453+ /// <param name="chain">The X509 chain used for validation (may be null)</param>
454+ /// <returns>A human-readable error message with diagnostics and suggestions</returns>
455+ public static string BuildSslPolicyErrorMessage (
456+ System . Net . Security . SslPolicyErrors errors ,
457+ X509Certificate2 ? certificate ,
458+ X509Chain ? chain )
459+ {
460+ var message = new System . Text . StringBuilder ( ) ;
461+ message . AppendLine ( "TLS/SSL certificate validation failed:" ) ;
462+
463+ // Interpret SslPolicyErrors flags
464+ if ( ( errors & System . Net . Security . SslPolicyErrors . None ) != System . Net . Security . SslPolicyErrors . None )
465+ {
466+ if ( ( errors & System . Net . Security . SslPolicyErrors . RemoteCertificateNotAvailable ) != 0 )
467+ {
468+ message . AppendLine ( " - Remote certificate not available" ) ;
469+ message . AppendLine ( " Suggestion: Ensure the remote endpoint provides a valid TLS certificate" ) ;
470+ }
471+
472+ if ( ( errors & System . Net . Security . SslPolicyErrors . RemoteCertificateNameMismatch ) != 0 )
473+ {
474+ message . AppendLine ( " - Remote certificate name mismatch" ) ;
475+ message . AppendLine ( " Suggestion: Verify certificate CN/SAN matches the target hostname" ) ;
476+ if ( certificate != null )
477+ {
478+ var cn = certificate . GetNameInfo ( X509NameType . DnsName , false ) ;
479+ message . AppendLine ( $ " Certificate CN: { cn } ") ;
480+ }
481+ }
482+
483+ if ( ( errors & System . Net . Security . SslPolicyErrors . RemoteCertificateChainErrors ) != 0 )
484+ {
485+ message . AppendLine ( " - Certificate chain validation errors" ) ;
486+
487+ if ( chain != null && chain . ChainStatus . Length > 0 )
488+ {
489+ var chainStatusMsg = BuildX509ChainStatusMessage ( chain . ChainStatus ) ;
490+ message . Append ( chainStatusMsg ) ;
491+ }
492+ else
493+ {
494+ message . AppendLine ( " Suggestion: Certificate chain cannot be validated. " +
495+ "Install required intermediate CA certificates." ) ;
496+ }
497+ }
498+ }
499+
500+ // Add certificate details if available
501+ if ( certificate != null )
502+ {
503+ message . AppendLine ( $ "\n Certificate Details:") ;
504+ message . AppendLine ( $ " Subject: { certificate . Subject } ") ;
505+ message . AppendLine ( $ " Issuer: { certificate . Issuer } ") ;
506+ message . AppendLine ( $ " Thumbprint: { certificate . Thumbprint } ") ;
507+ message . AppendLine ( $ " Valid From: { certificate . NotBefore : yyyy-MM-dd HH:mm:ss} ") ;
508+ message . AppendLine ( $ " Valid To: { certificate . NotAfter : yyyy-MM-dd HH:mm:ss} ") ;
509+ message . AppendLine ( $ " Has Private Key: { certificate . HasPrivateKey } ") ;
510+ }
511+
512+ return message . ToString ( ) . TrimEnd ( ) ;
513+ }
514+
515+ /// <summary>
516+ /// Builds a detailed message explaining X509 chain status errors.
517+ /// </summary>
518+ /// <param name="chainStatus">Array of chain status from X509Chain validation</param>
519+ /// <returns>Human-readable explanation of chain errors with suggestions</returns>
520+ public static string BuildX509ChainStatusMessage ( X509ChainStatus [ ] chainStatus )
521+ {
522+ var message = new System . Text . StringBuilder ( ) ;
523+
524+ foreach ( var status in chainStatus )
525+ {
526+ // Skip "NoError" status
527+ if ( status . Status == X509ChainStatusFlags . NoError )
528+ continue ;
529+
530+ message . AppendLine ( $ " - { status . Status } : { status . StatusInformation } ") ;
531+
532+ // Add specific suggestions based on chain status
533+ var suggestion = GetChainStatusSuggestion ( status . Status ) ;
534+ if ( ! string . IsNullOrEmpty ( suggestion ) )
535+ {
536+ message . AppendLine ( $ " Suggestion: { suggestion } ") ;
537+ }
538+ }
539+
540+ return message . ToString ( ) ;
541+ }
542+
543+ /// <summary>
544+ /// Maps X509ChainStatusFlags to actionable suggestions for fixing the issue.
545+ /// </summary>
546+ private static string GetChainStatusSuggestion ( X509ChainStatusFlags status )
547+ {
548+ return status switch
549+ {
550+ X509ChainStatusFlags . NotTimeValid =>
551+ "Certificate has expired or is not yet valid. Check system clock and certificate validity period." ,
552+
553+ X509ChainStatusFlags . NotTimeNested =>
554+ "Certificate validity period does not nest correctly within the chain." ,
555+
556+ X509ChainStatusFlags . Revoked =>
557+ "Certificate has been revoked. Contact certificate issuer." ,
558+
559+ X509ChainStatusFlags . NotSignatureValid =>
560+ "Certificate signature is invalid. Certificate may be corrupted." ,
561+
562+ X509ChainStatusFlags . NotValidForUsage =>
563+ "Certificate is not valid for the intended usage. Check Extended Key Usage (EKU) extensions." ,
564+
565+ X509ChainStatusFlags . UntrustedRoot =>
566+ "Certificate chain terminates in an untrusted root. Install root CA certificate in Trusted Root Certification Authorities store." ,
567+
568+ X509ChainStatusFlags . RevocationStatusUnknown =>
569+ "Revocation status cannot be determined. Check network connectivity to CRL/OCSP endpoints." ,
570+
571+ X509ChainStatusFlags . Cyclic =>
572+ "Certificate chain contains a cycle. Certificate configuration is invalid." ,
573+
574+ X509ChainStatusFlags . InvalidExtension =>
575+ "Certificate contains an invalid extension." ,
576+
577+ X509ChainStatusFlags . InvalidPolicyConstraints =>
578+ "Certificate policy constraints are invalid." ,
579+
580+ X509ChainStatusFlags . InvalidBasicConstraints =>
581+ "Basic constraints are invalid. CA certificate may be missing CA:TRUE constraint." ,
582+
583+ X509ChainStatusFlags . InvalidNameConstraints =>
584+ "Name constraints in certificate are invalid." ,
585+
586+ X509ChainStatusFlags . HasNotSupportedNameConstraint =>
587+ "Certificate contains name constraints that are not supported." ,
588+
589+ X509ChainStatusFlags . HasNotDefinedNameConstraint =>
590+ "Certificate has undefined name constraints." ,
591+
592+ X509ChainStatusFlags . HasNotPermittedNameConstraint =>
593+ "Certificate name violates name constraints." ,
594+
595+ X509ChainStatusFlags . HasExcludedNameConstraint =>
596+ "Certificate name is explicitly excluded by name constraints." ,
597+
598+ X509ChainStatusFlags . PartialChain =>
599+ "Certificate chain is incomplete. Install all intermediate CA certificates from your certificate provider." ,
600+
601+ X509ChainStatusFlags . CtlNotTimeValid =>
602+ "Certificate Trust List (CTL) is not time-valid." ,
603+
604+ X509ChainStatusFlags . CtlNotSignatureValid =>
605+ "Certificate Trust List (CTL) signature is invalid." ,
606+
607+ X509ChainStatusFlags . CtlNotValidForUsage =>
608+ "Certificate Trust List (CTL) is not valid for this usage." ,
609+
610+ X509ChainStatusFlags . OfflineRevocation =>
611+ "Revocation checking is offline. Enable network access or disable revocation checking for testing." ,
612+
613+ X509ChainStatusFlags . NoIssuanceChainPolicy =>
614+ "Certificate does not have a valid issuance policy." ,
615+
616+ X509ChainStatusFlags . ExplicitDistrust =>
617+ "Certificate is explicitly distrusted. Remove from Distrusted Certificates store if this is incorrect." ,
618+
619+ X509ChainStatusFlags . HasNotSupportedCriticalExtension =>
620+ "Certificate has an unsupported critical extension." ,
621+
622+ X509ChainStatusFlags . HasWeakSignature =>
623+ "Certificate uses a weak signature algorithm (e.g., SHA1). Use SHA256 or stronger." ,
624+
625+ _ => string . Empty
626+ } ;
627+ }
628+
629+ /// <summary>
630+ /// Builds an error message for TLS handshake exceptions.
631+ /// Attempts to extract meaningful information from CryptographicException and AuthenticationException.
632+ /// </summary>
633+ public static string BuildTlsHandshakeErrorMessage ( Exception exception , bool isClient )
634+ {
635+ var role = isClient ? "Client" : "Server" ;
636+ var message = new System . Text . StringBuilder ( ) ;
637+
638+ message . AppendLine ( $ "TLS handshake failed ({ role } side):") ;
639+ message . AppendLine ( $ " Error: { exception . Message } ") ;
640+
641+ // Provide role-specific suggestions
642+ if ( isClient )
643+ {
644+ message . AppendLine ( "\n Client-side TLS troubleshooting:" ) ;
645+ message . AppendLine ( " - Verify server certificate is trusted (install root CA if using self-signed)" ) ;
646+ message . AppendLine ( " - Check certificate hostname matches connection target" ) ;
647+ message . AppendLine ( " - For mutual TLS, ensure client certificate is configured, accessible, and trusted by server" ) ;
648+ message . AppendLine ( " - Server and client certificates must have compatible trust chains" ) ;
649+ }
650+ else
651+ {
652+ message . AppendLine ( "\n Server-side TLS troubleshooting:" ) ;
653+ message . AppendLine ( " - Verify server certificate has accessible private key" ) ;
654+ message . AppendLine ( " - For mutual TLS, check if client is providing a certificate" ) ;
655+ message . AppendLine ( " - Review certificate validation requirements (suppress-validation for testing)" ) ;
656+ }
657+
658+ if ( exception . InnerException != null )
659+ {
660+ message . AppendLine ( $ "\n Inner Exception: { exception . InnerException . Message } ") ;
661+ }
662+
663+ return message . ToString ( ) . TrimEnd ( ) ;
664+ }
665+ }
439666}
0 commit comments