Merge pull request #18591 from jtattermusch/csharp_verify_peer_callback

Provide access to verify_peer_callback from C# (take two)
pull/18601/head^2
Jan Tattermusch 6 years ago committed by GitHub
commit bf7ed7a053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 90
      src/csharp/Grpc.Core/ChannelCredentials.cs
  2. 6
      src/csharp/Grpc.Core/Internal/ChannelCredentialsSafeHandle.cs
  3. 2
      src/csharp/Grpc.Core/Internal/NativeCallbackDispatcher.cs
  4. 6
      src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs
  5. 48
      src/csharp/Grpc.Core/VerifyPeerContext.cs
  6. 51
      src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs
  7. 64
      src/csharp/ext/grpc_csharp_ext.c
  8. 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,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;
}
}
}
}

@ -38,15 +38,15 @@ namespace Grpc.Core.Internal
return creds;
}
public static ChannelCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, KeyCertificatePair keyCertPair)
public static ChannelCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, KeyCertificatePair keyCertPair, IntPtr verifyPeerCallbackTag)
{
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, verifyPeerCallbackTag);
}
else
{
return Native.grpcsharp_ssl_credentials_create(pemRootCerts, null, null);
return Native.grpcsharp_ssl_credentials_create(pemRootCerts, null, null, verifyPeerCallbackTag);
}
}

@ -63,7 +63,7 @@ namespace Grpc.Core.Internal
catch (Exception e)
{
// eat the exception, we must not throw when inside callback from native code.
Logger.Error(e, "Caught exception inside callback from native callback.");
Logger.Error(e, "Caught exception inside callback from native code.");
return 0;
}
}

@ -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, IntPtr verifyPeerCallbackTag);
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, IntPtr verifyPeerCallbackTag);
[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, IntPtr verifyPeerCallbackTag);
[DllImport(ImportName)]
public static extern ChannelCredentialsSafeHandle grpcsharp_composite_channel_credentials_create(ChannelCredentialsSafeHandle channelCreds, CallCredentialsSafeHandle callCreds);

@ -0,0 +1,48 @@
#region Copyright notice and license
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
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="targetName">The target name of the peer.</param>
/// <param name="peerPem">The PEM encoded certificate of the peer.</param>
internal VerifyPeerContext(string targetName, string peerPem)
{
this.TargetName = targetName;
this.PeerPem = peerPem;
}
/// <summary>
/// The target name of the peer.
/// </summary>
public string TargetName { get; }
/// <summary>
/// The PEM encoded certificate of the peer.
/// </summary>
public string PeerPem { get; }
}
}

@ -46,7 +46,8 @@ namespace Grpc.IntegrationTesting
KeyCertificatePair keyCertPair;
public void InitClientAndServer(bool clientAddKeyCertPair,
SslClientCertificateRequestType clientCertRequestType)
SslClientCertificateRequestType clientCertRequestType,
VerifyPeerCallback verifyPeerCallback = null)
{
rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath);
keyCertPair = new KeyCertificatePair(
@ -54,7 +55,7 @@ namespace Grpc.IntegrationTesting
File.ReadAllText(TestCredentials.ServerPrivateKeyPath));
var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, clientCertRequestType);
var clientCredentials = clientAddKeyCertPair ? new SslCredentials(rootCert, keyCertPair) : new SslCredentials(rootCert);
var clientCredentials = new SslCredentials(rootCert, clientAddKeyCertPair ? keyCertPair : null, verifyPeerCallback);
// Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
@ -188,6 +189,52 @@ namespace Grpc.IntegrationTesting
Assert.Throws(typeof(ArgumentNullException), () => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndRequireAndVerify));
}
[Test]
public async Task VerifyPeerCallback_Accepted()
{
string targetNameFromCallback = null;
string peerPemFromCallback = null;
InitClientAndServer(
clientAddKeyCertPair: false,
clientCertRequestType: SslClientCertificateRequestType.DontRequest,
verifyPeerCallback: (ctx) =>
{
targetNameFromCallback = ctx.TargetName;
peerPemFromCallback = ctx.PeerPem;
return true;
});
await CheckAccepted(expectPeerAuthenticated: false);
Assert.AreEqual(TestCredentials.DefaultHostOverride, targetNameFromCallback);
var expectedServerPem = File.ReadAllText(TestCredentials.ServerCertChainPath).Replace("\r", "");
Assert.AreEqual(expectedServerPem, peerPemFromCallback);
}
[Test]
public void VerifyPeerCallback_CallbackThrows_Rejected()
{
InitClientAndServer(
clientAddKeyCertPair: false,
clientCertRequestType: SslClientCertificateRequestType.DontRequest,
verifyPeerCallback: (ctx) =>
{
throw new Exception("VerifyPeerCallback has thrown on purpose.");
});
CheckRejected();
}
[Test]
public void VerifyPeerCallback_Rejected()
{
InitClientAndServer(
clientAddKeyCertPair: false,
clientCertRequestType: SslClientCertificateRequestType.DontRequest,
verifyPeerCallback: (ctx) =>
{
return false;
});
CheckRejected();
}
private async Task CheckAccepted(bool expectPeerAuthenticated)
{
var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });

@ -901,6 +901,21 @@ grpcsharp_server_request_call(grpc_server* server, grpc_completion_queue* cq,
&(ctx->request_metadata), cq, cq, ctx);
}
/* Native callback dispatcher */
typedef int(GPR_CALLTYPE* grpcsharp_native_callback_dispatcher_func)(
void* tag, void* arg0, void* arg1, void* arg2, void* arg3, void* arg4,
void* arg5);
static grpcsharp_native_callback_dispatcher_func native_callback_dispatcher =
NULL;
GPR_EXPORT void GPR_CALLTYPE grpcsharp_native_callback_dispatcher_init(
grpcsharp_native_callback_dispatcher_func func) {
GPR_ASSERT(func);
native_callback_dispatcher = func;
}
/* Security */
static char* default_pem_root_certs = NULL;
@ -927,21 +942,47 @@ grpcsharp_override_default_ssl_roots(const char* pem_root_certs) {
grpc_set_ssl_roots_override_callback(override_ssl_roots_handler);
}
static void grpcsharp_verify_peer_destroy_handler(void* userdata) {
native_callback_dispatcher(userdata, NULL, NULL, (void*)1, NULL, NULL, NULL);
}
static int grpcsharp_verify_peer_handler(const char* target_name,
const char* peer_pem, void* userdata) {
return native_callback_dispatcher(userdata, (void*)target_name,
(void*)peer_pem, (void*)0, NULL, NULL,
NULL);
}
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,
void* verify_peer_callback_tag) {
grpc_ssl_pem_key_cert_pair key_cert_pair;
verify_peer_options verify_options;
grpc_ssl_pem_key_cert_pair* key_cert_pair_ptr = NULL;
verify_peer_options* verify_options_ptr = NULL;
if (key_cert_pair_cert_chain || key_cert_pair_private_key) {
memset(&key_cert_pair, 0, sizeof(key_cert_pair));
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);
key_cert_pair_ptr = &key_cert_pair;
} 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);
}
if (verify_peer_callback_tag != NULL) {
memset(&verify_options, 0, sizeof(verify_peer_options));
verify_options.verify_peer_callback_userdata = verify_peer_callback_tag;
verify_options.verify_peer_destruct = grpcsharp_verify_peer_destroy_handler;
verify_options.verify_peer_callback = grpcsharp_verify_peer_handler;
verify_options_ptr = &verify_options;
}
return grpc_ssl_credentials_create(pem_root_certs, key_cert_pair_ptr,
verify_options_ptr, NULL);
}
GPR_EXPORT void GPR_CALLTYPE
@ -1010,21 +1051,6 @@ grpcsharp_composite_call_credentials_create(grpc_call_credentials* creds1,
return grpc_composite_call_credentials_create(creds1, creds2, NULL);
}
/* Native callback dispatcher */
typedef int(GPR_CALLTYPE* grpcsharp_native_callback_dispatcher_func)(
void* tag, void* arg0, void* arg1, void* arg2, void* arg3, void* arg4,
void* arg5);
static grpcsharp_native_callback_dispatcher_func native_callback_dispatcher =
NULL;
GPR_EXPORT void GPR_CALLTYPE grpcsharp_native_callback_dispatcher_init(
grpcsharp_native_callback_dispatcher_func func) {
GPR_ASSERT(func);
native_callback_dispatcher = func;
}
/* Metadata credentials plugin */
GPR_EXPORT void GPR_CALLTYPE grpcsharp_metadata_credentials_notify_from_plugin(

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