Provide access to verify_peer_callback from C#

pull/18591/head
Thibaut Le Guilly 6 years ago committed by Jan Tattermusch
parent 2780136fcf
commit 303416080c
  1. 69
      src/csharp/Grpc.Core/ChannelCredentials.cs
  2. 12
      src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
  3. 6
      src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
  4. 30
      src/csharp/Grpc.Core/VerifyPeerContext.cs
  5. 43
      src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
  6. 41
      src/csharp/ext/grpc_csharp_ext.c
  7. 2
      templates/src/csharp/Grpc.Core/Internal/native_methods.include

@ -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
}
}
/// <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. Invocation of the callback is blocking, so any
/// implementation should be light-weight.
/// </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;
readonly VerifyPeerCallbackInternal verifyPeerCallbackInternal;
readonly GCHandle gcHandle;
/// <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 +143,37 @@ 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;
if (verifyPeerCallback != null)
{
this.verifyPeerCallback = verifyPeerCallback;
this.verifyPeerCallbackInternal = this.VerifyPeerCallbackHandler;
gcHandle = GCHandle.Alloc(verifyPeerCallbackInternal);
}
}
/// <summary>
@ -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;
}
}
}

@ -20,6 +20,12 @@ using System.Threading.Tasks;
namespace Grpc.Core.Internal
{
internal delegate int VerifyPeerCallbackInternal(
IntPtr targetHost,
IntPtr targetPem,
IntPtr userData,
bool isDestroy);
/// <summary>
/// grpc_channel_credentials from <c>grpc/grpc_security.h</c>
/// </summary>
@ -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);
}
}

@ -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);

@ -0,0 +1,30 @@
namespace Grpc.Core
{
/// <summary>
/// Verification context for VerifyPeerCallback.
/// Note: experimental API that can change or be removed without any prior notice.
/// </summary>
public class VerifyPeerContext
{
/// <summary>
/// Initializes a new instance of the <see cref="T:Grpc.Core.VerifyPeerContext"/> class.
/// </summary>
/// <param name="targetHost">string containing the host name of the peer.</param>
/// <param name="targetPem">string containing PEM encoded certificate of the peer.</param>
internal VerifyPeerContext(string targetHost, string targetPem)
{
this.TargetHost = targetHost;
this.TargetPem = targetPem;
}
/// <summary>
/// String containing the host name of the peer.
/// </summary>
public string TargetHost { get; }
/// <summary>
/// string containing PEM encoded certificate of the peer.
/// </summary>
public string TargetPem { get; }
}
}

@ -44,17 +44,23 @@ namespace Grpc.IntegrationTesting
string rootCert;
KeyCertificatePair keyCertPair;
string certChain;
List<ChannelOption> 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<ChannelOption>
options = new List<ChannelOption>
{
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<RpcException>(() => 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<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)

@ -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);
}
}

@ -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)',

Loading…
Cancel
Save