diff --git a/src/csharp/Grpc.Core/ChannelCredentials.cs b/src/csharp/Grpc.Core/ChannelCredentials.cs
index 3ce32f31b76..103a594bb06 100644
--- a/src/csharp/Grpc.Core/ChannelCredentials.cs
+++ b/src/csharp/Grpc.Core/ChannelCredentials.cs
@@ -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,36 @@ namespace Grpc.Core
}
}
+ ///
+ /// 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. Invocation of the callback is blocking, so any
+ /// implementation should be light-weight.
+ ///
+ /// The associated with the callback
+ /// true if verification succeeded, false otherwise.
+ /// Note: experimental API that can change or be removed without any prior notice.
+ public delegate bool VerifyPeerCallback(VerifyPeerContext context);
+
///
/// Client-side SSL credentials.
///
public sealed class SslCredentials : ChannelCredentials
{
+ static readonly ILogger Logger = GrpcEnvironment.Logger.ForType();
+
readonly string rootCertificates;
readonly KeyCertificatePair keyCertificatePair;
+ readonly VerifyPeerCallback verifyPeerCallback;
+ readonly VerifyPeerCallbackInternal verifyPeerCallbackInternal;
+ readonly GCHandle gcHandle;
///
/// 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.
///
- public SslCredentials() : this(null, null)
+ public SslCredentials() : this(null, null, null)
{
}
@@ -125,19 +143,37 @@ namespace Grpc.Core
/// Creates client-side SSL credentials from
/// a string containing PEM encoded root certificates.
///
- public SslCredentials(string rootCertificates) : this(rootCertificates, null)
+ public SslCredentials(string rootCertificates) : this(rootCertificates, null, null)
{
}
-
+
+ ///
+ /// Creates client-side SSL credentials.
+ ///
+ /// string containing PEM encoded server root certificates.
+ /// a key certificate pair.
+ public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair) :
+ this(rootCertificates, keyCertificatePair, null)
+ {
+ }
+
///
/// Creates client-side SSL credentials.
///
/// string containing PEM encoded server root certificates.
/// a key certificate pair.
- public SslCredentials(string rootCertificates, KeyCertificatePair keyCertificatePair)
+ /// a callback to verify peer's target name and certificate.
+ /// 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;
+ if (verifyPeerCallback != null)
+ {
+ this.verifyPeerCallback = verifyPeerCallback;
+ this.verifyPeerCallbackInternal = this.VerifyPeerCallbackHandler;
+ gcHandle = GCHandle.Alloc(verifyPeerCallbackInternal);
+ }
}
///
@@ -171,7 +207,30 @@ namespace Grpc.Core
internal override ChannelCredentialsSafeHandle CreateNativeCredentials()
{
- return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair);
+ return ChannelCredentialsSafeHandle.CreateSslCredentials(rootCertificates, keyCertificatePair, this.verifyPeerCallbackInternal);
+ }
+
+ private int VerifyPeerCallbackHandler(IntPtr host, IntPtr pem, IntPtr userData, bool isDestroy)
+ {
+ if (isDestroy)
+ {
+ this.gcHandle.Free();
+ return 0;
+ }
+
+ try
+ {
+ var context = new VerifyPeerContext(Marshal.PtrToStringAnsi(host), Marshal.PtrToStringAnsi(pem));
+
+ 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;
+ }
}
}
diff --git a/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
index 11b5d2cc3f6..5e336673153 100644
--- a/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
+++ b/src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
@@ -20,6 +20,12 @@ using System.Threading.Tasks;
namespace Grpc.Core.Internal
{
+ internal delegate int VerifyPeerCallbackInternal(
+ IntPtr targetHost,
+ IntPtr targetPem,
+ IntPtr userData,
+ bool isDestroy);
+
///
/// grpc_channel_credentials from grpc/grpc_security.h
///
@@ -38,15 +44,15 @@ namespace Grpc.Core.Internal
return creds;
}
- public static ChannelCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, KeyCertificatePair keyCertPair)
+ public static ChannelCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, KeyCertificatePair keyCertPair, VerifyPeerCallbackInternal verifyPeerCallback)
{
if (keyCertPair != null)
{
- return Native.grpcsharp_ssl_credentials_create(pemRootCerts, keyCertPair.CertificateChain, keyCertPair.PrivateKey);
+ return Native.grpcsharp_ssl_credentials_create(pemRootCerts, keyCertPair.CertificateChain, keyCertPair.PrivateKey, verifyPeerCallback);
}
else
{
- return Native.grpcsharp_ssl_credentials_create(pemRootCerts, null, null);
+ return Native.grpcsharp_ssl_credentials_create(pemRootCerts, null, null, verifyPeerCallback);
}
}
diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
index b7b9a12d8a3..e09b9813f79 100644
--- a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
+++ b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
@@ -482,7 +482,7 @@ namespace Grpc.Core.Internal
public delegate void grpcsharp_channel_args_set_integer_delegate(ChannelArgsSafeHandle args, UIntPtr index, string key, int value);
public delegate void grpcsharp_channel_args_destroy_delegate(IntPtr args);
public delegate void grpcsharp_override_default_ssl_roots_delegate(string pemRootCerts);
- public delegate ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create_delegate(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
+ public delegate ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create_delegate(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, VerifyPeerCallbackInternal verifyPeerCallback);
public delegate ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create_delegate(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);
public delegate void grpcsharp_channel_credentials_release_delegate(IntPtr credentials);
public delegate ChannelSafeHandle grpcsharp_insecure_channel_create_delegate(string target, ChannelArgsSafeHandle channelArgs);
@@ -676,7 +676,7 @@ namespace Grpc.Core.Internal
public static extern void grpcsharp_override_default_ssl_roots(string pemRootCerts);
[DllImport(ImportName)]
- public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
+ public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, VerifyPeerCallbackInternal verifyPeerCallback);
[DllImport(ImportName)]
public static extern ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);
@@ -972,7 +972,7 @@ namespace Grpc.Core.Internal
public static extern void grpcsharp_override_default_ssl_roots(string pemRootCerts);
[DllImport(ImportName)]
- public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey);
+ public static extern ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, VerifyPeerCallbackInternal verifyPeerCallback);
[DllImport(ImportName)]
public static extern ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);
diff --git a/src/csharp/Grpc.Core/VerifyPeerContext.cs b/src/csharp/Grpc.Core/VerifyPeerContext.cs
new file mode 100644
index 00000000000..b48984b093c
--- /dev/null
+++ b/src/csharp/Grpc.Core/VerifyPeerContext.cs
@@ -0,0 +1,30 @@
+namespace Grpc.Core
+{
+ ///
+ /// Verification context for VerifyPeerCallback.
+ /// Note: experimental API that can change or be removed without any prior notice.
+ ///
+ public class VerifyPeerContext
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// string containing the host name of the peer.
+ /// string containing PEM encoded certificate of the peer.
+ internal VerifyPeerContext(string targetHost, string targetPem)
+ {
+ this.TargetHost = targetHost;
+ this.TargetPem = targetPem;
+ }
+
+ ///
+ /// String containing the host name of the peer.
+ ///
+ public string TargetHost { get; }
+
+ ///
+ /// string containing PEM encoded certificate of the peer.
+ ///
+ public string TargetPem { get; }
+ }
+}
diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
index b3c47c2d8d3..818ac6d1adc 100644
--- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
+++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
@@ -44,17 +44,23 @@ namespace Grpc.IntegrationTesting
string rootCert;
KeyCertificatePair keyCertPair;
+ string certChain;
+ List options;
+ bool isHostEqual;
+ bool isPemEqual;
public void InitClientAndServer(bool clientAddKeyCertPair,
SslClientCertificateRequestType clientCertRequestType)
{
rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath);
+ certChain = File.ReadAllText(TestCredentials.ServerCertChainPath);
+ certChain = certChain.Replace("\r", string.Empty);
keyCertPair = new KeyCertificatePair(
- File.ReadAllText(TestCredentials.ServerCertChainPath),
+ certChain,
File.ReadAllText(TestCredentials.ServerPrivateKeyPath));
var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, clientCertRequestType);
- var clientCredentials = clientAddKeyCertPair ? new SslCredentials(rootCert, keyCertPair) : new SslCredentials(rootCert);
+ var clientCredentials = clientAddKeyCertPair ? new SslCredentials(rootCert, keyCertPair, context => this.VerifyPeerCallback(context, true)) : new SslCredentials(rootCert);
// Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
@@ -64,7 +70,7 @@ namespace Grpc.IntegrationTesting
};
server.Start();
- var options = new List
+ options = new List
{
new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
};
@@ -210,6 +216,37 @@ namespace Grpc.IntegrationTesting
Assert.AreEqual(12345, response.AggregatedPayloadSize);
}
+ [Test]
+ public void VerifyPeerCallbackTest()
+ {
+ InitClientAndServer(true, SslClientCertificateRequestType.RequestAndRequireAndVerify);
+
+ // Force GC collection to verify that the VerifyPeerCallback is not collected. If
+ // it gets collected, this test will hang.
+ GC.Collect();
+
+ client.UnaryCall(new SimpleRequest { ResponseSize = 10 });
+ Assert.IsTrue(isHostEqual);
+ Assert.IsTrue(isPemEqual);
+ }
+
+ [Test]
+ public void VerifyPeerCallbackFailTest()
+ {
+ InitClientAndServer(true, SslClientCertificateRequestType.RequestAndRequireAndVerify);
+ var clientCredentials = new SslCredentials(rootCert, keyCertPair, context => this.VerifyPeerCallback(context, false));
+ var failingChannel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
+ var failingClient = new TestService.TestServiceClient(failingChannel);
+ Assert.Throws(() => failingClient.UnaryCall(new SimpleRequest { ResponseSize = 10 }));
+ }
+
+ private bool VerifyPeerCallback(VerifyPeerContext context, bool returnValue)
+ {
+ isHostEqual = TestCredentials.DefaultHostOverride == context.TargetHost;
+ isPemEqual = certChain == context.TargetPem;
+ return returnValue;
+ }
+
private class SslCredentialsTestServiceImpl : TestService.TestServiceBase
{
public override Task UnaryCall(SimpleRequest request, ServerCallContext context)
diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c
index dc690a6a608..5f80d3417a5 100644
--- a/src/csharp/ext/grpc_csharp_ext.c
+++ b/src/csharp/ext/grpc_csharp_ext.c
@@ -927,20 +927,53 @@ grpcsharp_override_default_ssl_roots(const char* pem_root_certs) {
grpc_set_ssl_roots_override_callback(override_ssl_roots_handler);
}
+typedef int(GPR_CALLTYPE* grpcsharp_verify_peer_func)(const char* target_host,
+ const char* target_pem,
+ void* userdata,
+ int32_t isDestroy);
+
+static void grpcsharp_verify_peer_destroy_handler(void* userdata) {
+ grpcsharp_verify_peer_func callback =
+ (grpcsharp_verify_peer_func)(intptr_t)userdata;
+ callback(NULL, NULL, NULL, 1);
+}
+
+static int grpcsharp_verify_peer_handler(const char* target_host,
+ const char* target_pem,
+ void* userdata) {
+ grpcsharp_verify_peer_func callback =
+ (grpcsharp_verify_peer_func)(intptr_t)userdata;
+ return callback(target_host, target_pem, NULL, 0);
+}
+
+
GPR_EXPORT grpc_channel_credentials* GPR_CALLTYPE
grpcsharp_ssl_credentials_create(const char* pem_root_certs,
const char* key_cert_pair_cert_chain,
- const char* key_cert_pair_private_key) {
+ const char* key_cert_pair_private_key,
+ grpcsharp_verify_peer_func verify_peer_func) {
grpc_ssl_pem_key_cert_pair key_cert_pair;
+ verify_peer_options verify_options;
+ verify_peer_options* p_verify_options = NULL;
+ if (verify_peer_func != NULL) {
+ verify_options.verify_peer_callback_userdata =
+ (void*)(intptr_t)verify_peer_func;
+ verify_options.verify_peer_destruct =
+ grpcsharp_verify_peer_destroy_handler;
+ verify_options.verify_peer_callback = grpcsharp_verify_peer_handler;
+ p_verify_options = &verify_options;
+ }
+
if (key_cert_pair_cert_chain || key_cert_pair_private_key) {
key_cert_pair.cert_chain = key_cert_pair_cert_chain;
key_cert_pair.private_key = key_cert_pair_private_key;
- return grpc_ssl_credentials_create(pem_root_certs, &key_cert_pair, NULL,
- NULL);
+ return grpc_ssl_credentials_create(pem_root_certs, &key_cert_pair,
+ p_verify_options, NULL);
} else {
GPR_ASSERT(!key_cert_pair_cert_chain);
GPR_ASSERT(!key_cert_pair_private_key);
- return grpc_ssl_credentials_create(pem_root_certs, NULL, NULL, NULL);
+ return grpc_ssl_credentials_create(pem_root_certs, NULL, p_verify_options,
+ NULL);
}
}
diff --git a/templates/src/csharp/Grpc.Core/Internal/native_methods.include b/templates/src/csharp/Grpc.Core/Internal/native_methods.include
index b7a8e285488..4cb71730529 100644
--- a/templates/src/csharp/Grpc.Core/Internal/native_methods.include
+++ b/templates/src/csharp/Grpc.Core/Internal/native_methods.include
@@ -44,7 +44,7 @@ native_method_signatures = [
'void grpcsharp_channel_args_set_integer(ChannelArgsSafeHandle args, UIntPtr index, string key, int value)',
'void grpcsharp_channel_args_destroy(IntPtr args)',
'void grpcsharp_override_default_ssl_roots(string pemRootCerts)',
- 'ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey)',
+ 'ChannelCredentialsSafeHandle grpcsharp_ssl_credentials_create(string pemRootCerts, string keyCertPairCertChain, string keyCertPairPrivateKey, VerifyPeerCallbackInternal verifyPeerCallback)',
'ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds)',
'void grpcsharp_channel_credentials_release(IntPtr credentials)',
'ChannelSafeHandle grpcsharp_insecure_channel_create(string target, ChannelArgsSafeHandle channelArgs)',