|  | @@ -18,9 +18,11 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  using System;
 | 
	
		
			
				|  |  |  using System.Collections.Generic;
 | 
	
		
			
				|  |  | +using System.Runtime.InteropServices;
 | 
	
		
			
				|  |  |  using System.Threading.Tasks;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  using Grpc.Core.Internal;
 | 
	
		
			
				|  |  | +using Grpc.Core.Logging;
 | 
	
		
			
				|  |  |  using Grpc.Core.Utils;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  namespace Grpc.Core
 | 
	
	
		
			
				|  | @@ -104,20 +106,38 @@ namespace Grpc.Core
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /// <summary>
 | 
	
		
			
				|  |  | +    /// Callback invoked with the expected targetHost and the peer's certificate.
 | 
	
		
			
				|  |  | +    /// If false is returned by this callback then it is treated as a
 | 
	
		
			
				|  |  | +    /// verification failure and the attempted connection will fail.
 | 
	
		
			
				|  |  | +    /// Invocation of the callback is blocking, so any
 | 
	
		
			
				|  |  | +    /// implementation should be light-weight.
 | 
	
		
			
				|  |  | +    /// Note that the callback can potentially be invoked multiple times,
 | 
	
		
			
				|  |  | +    /// concurrently from different threads (e.g. when multiple connections
 | 
	
		
			
				|  |  | +    /// are being created for the same credentials).
 | 
	
		
			
				|  |  | +    /// </summary>
 | 
	
		
			
				|  |  | +    /// <param name="context">The <see cref="T:Grpc.Core.VerifyPeerContext"/> associated with the callback</param>
 | 
	
		
			
				|  |  | +    /// <returns>true if verification succeeded, false otherwise.</returns>
 | 
	
		
			
				|  |  | +    /// Note: experimental API that can change or be removed without any prior notice.
 | 
	
		
			
				|  |  | +    public delegate bool VerifyPeerCallback(VerifyPeerContext context);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /// <summary>
 | 
	
		
			
				|  |  |      /// Client-side SSL credentials.
 | 
	
		
			
				|  |  |      /// </summary>
 | 
	
		
			
				|  |  |      public sealed class SslCredentials : ChannelCredentials
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  | +        static readonly ILogger Logger = GrpcEnvironment.Logger.ForType<SslCredentials>();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          readonly string rootCertificates;
 | 
	
		
			
				|  |  |          readonly KeyCertificatePair keyCertificatePair;
 | 
	
		
			
				|  |  | +        readonly VerifyPeerCallback verifyPeerCallback;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Creates client-side SSL credentials loaded from
 | 
	
		
			
				|  |  |          /// disk file pointed to by the GRPC_DEFAULT_SSL_ROOTS_FILE_PATH environment variable.
 | 
	
		
			
				|  |  |          /// If that fails, gets the roots certificates from a well known place on disk.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  | -        public SslCredentials() : this(null, null)
 | 
	
		
			
				|  |  | +        public SslCredentials() : this(null, null, null)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -125,19 +145,32 @@ namespace Grpc.Core
 | 
	
		
			
				|  |  |          /// Creates client-side SSL credentials from
 | 
	
		
			
				|  |  |          /// a string containing PEM encoded root certificates.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  | -        public SslCredentials(string rootCertificates) : this(rootCertificates, null)
 | 
	
		
			
				|  |  | +        public SslCredentials(string rootCertificates) : this(rootCertificates, null, null)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Creates client-side SSL credentials.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
 | 
	
		
			
				|  |  | +        /// <param name="keyCertificatePair">a key certificate pair.</param>
 | 
	
		
			
				|  |  | +        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair) :
 | 
	
		
			
				|  |  | +            this(rootCertificates, keyCertificatePair, null)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -            
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Creates client-side SSL credentials.
 | 
	
		
			
				|  |  |          /// </summary>
 | 
	
		
			
				|  |  |          /// <param name="rootCertificates">string containing PEM encoded server root certificates.</param>
 | 
	
		
			
				|  |  |          /// <param name="keyCertificatePair">a key certificate pair.</param>
 | 
	
		
			
				|  |  | -        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
 | 
	
		
			
				|  |  | +        /// <param name="verifyPeerCallback">a callback to verify peer's target name and certificate.</param>
 | 
	
		
			
				|  |  | +        /// Note: experimental API that can change or be removed without any prior notice.
 | 
	
		
			
				|  |  | +        public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair, VerifyPeerCallback verifyPeerCallback)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              this.rootCertificates = rootCertificates;
 | 
	
		
			
				|  |  |              this.keyCertificatePair = keyCertificatePair;
 | 
	
		
			
				|  |  | +            this.verifyPeerCallback = verifyPeerCallback;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
	
		
			
				|  | @@ -171,7 +204,54 @@ namespace Grpc.Core
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          internal override ChannelCredentialsSafeHandle CreateNativeCredentials()
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
 | 
	
		
			
				|  |  | +            IntPtr verifyPeerCallbackTag = IntPtr.Zero;
 | 
	
		
			
				|  |  | +            if (verifyPeerCallback != null)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                verifyPeerCallbackTag = new VerifyPeerCallbackRegistration(verifyPeerCallback).CallbackRegistration.Tag;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair, verifyPeerCallbackTag);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private class VerifyPeerCallbackRegistration
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            readonly VerifyPeerCallback verifyPeerCallback;
 | 
	
		
			
				|  |  | +            readonly NativeCallbackRegistration callbackRegistration;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            public VerifyPeerCallbackRegistration(VerifyPeerCallback verifyPeerCallback)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                this.verifyPeerCallback = verifyPeerCallback;
 | 
	
		
			
				|  |  | +                this.callbackRegistration = NativeCallbackDispatcher.RegisterCallback(HandleUniversalCallback);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            public NativeCallbackRegistration CallbackRegistration => callbackRegistration;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            private int HandleUniversalCallback(IntPtr arg0, IntPtr arg1, IntPtr arg2, IntPtr arg3, IntPtr arg4, IntPtr arg5)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return VerifyPeerCallbackHandler(arg0, arg1, arg2 != IntPtr.Zero);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            private int VerifyPeerCallbackHandler(IntPtr targetName, IntPtr peerPem, bool isDestroy)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (isDestroy)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    this.callbackRegistration.Dispose();
 | 
	
		
			
				|  |  | +                    return 0;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    var context = new VerifyPeerContext(Marshal.PtrToStringAnsi(targetName), Marshal.PtrToStringAnsi(peerPem));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    return this.verifyPeerCallback(context) ? 0 : 1;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                catch (Exception e)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    // eat the exception, we must not throw when inside callback from native code.
 | 
	
		
			
				|  |  | +                    Logger.Error(e, "Exception occurred while invoking verify peer callback handler.");
 | 
	
		
			
				|  |  | +                    // Return validation failure in case of exception.
 | 
	
		
			
				|  |  | +                    return 1;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 |