From 303416080c447dae8729e1ffbe78a366f1f88a71 Mon Sep 17 00:00:00 2001 From: Thibaut Le Guilly Date: Tue, 9 Oct 2018 17:10:22 +0900 Subject: [PATCH] Provide access to verify_peer_callback from C# --- src/csharp/Grpc.Core/ChannelCredentials.cs | 69 +++++++++++++++++-- .../Internal/ChannelCredentialsSafeHandle.cs | 12 +++- .../Internal/NativeMethods.Generated.cs | 6 +- src/csharp/Grpc.Core/VerifyPeerContext.cs | 30 ++++++++ .../SslCredentialsTest.cs | 43 +++++++++++- src/csharp/ext/grpc_csharp_ext.c | 41 +++++++++-- .../Grpc.Core/Internal/native_methods.include | 2 +- 7 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 src/csharp/Grpc.Core/VerifyPeerContext.cs 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)',