From db0e21a5cbeb105a6878bb8c0b63bc6a23acee4b Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 14 May 2018 14:00:29 -0700 Subject: Expose certificate request type in SslServerCredentials. --- .../Grpc.Core/Internal/NativeMethods.Generated.cs | 6 +- .../Internal/ServerCredentialsSafeHandle.cs | 4 +- src/csharp/Grpc.Core/ServerCredentials.cs | 97 ++++++++++++-- .../Grpc.IntegrationTesting/SslCredentialsTest.cs | 143 +++++++++++++++++++-- src/csharp/ext/grpc_csharp_ext.c | 6 +- .../Internal/NativeMethods.Generated.cs.template | 2 +- 6 files changed, 225 insertions(+), 33 deletions(-) diff --git a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs index 153a52f947..a45cbe4107 100644 --- a/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs +++ b/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs @@ -505,7 +505,7 @@ namespace Grpc.Core.Internal public delegate void grpcsharp_redirect_log_delegate(GprLogDelegate callback); public delegate CallCredentialsSafeHandle grpcsharp_metadata_credentials_create_from_plugin_delegate(NativeMetadataInterceptor interceptor); public delegate void grpcsharp_metadata_credentials_notify_from_plugin_delegate(IntPtr callbackPtr, IntPtr userData, MetadataArraySafeHandle metadataArray, StatusCode statusCode, string errorDetails); - public delegate ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create_delegate(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, int forceClientAuth); + public delegate ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create_delegate(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, SslClientCertificateRequestType clientCertificateRequest); public delegate void grpcsharp_server_credentials_release_delegate(IntPtr credentials); public delegate ServerSafeHandle grpcsharp_server_create_delegate(ChannelArgsSafeHandle args); public delegate void grpcsharp_server_register_completion_queue_delegate(ServerSafeHandle server, CompletionQueueSafeHandle cq); @@ -752,7 +752,7 @@ namespace Grpc.Core.Internal public static extern void grpcsharp_metadata_credentials_notify_from_plugin(IntPtr callbackPtr, IntPtr userData, MetadataArraySafeHandle metadataArray, StatusCode statusCode, string errorDetails); [DllImport(ImportName)] - public static extern ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, int forceClientAuth); + public static extern ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, SslClientCertificateRequestType clientCertificateRequest); [DllImport(ImportName)] public static extern void grpcsharp_server_credentials_release(IntPtr credentials); @@ -1045,7 +1045,7 @@ namespace Grpc.Core.Internal public static extern void grpcsharp_metadata_credentials_notify_from_plugin(IntPtr callbackPtr, IntPtr userData, MetadataArraySafeHandle metadataArray, StatusCode statusCode, string errorDetails); [DllImport(ImportName)] - public static extern ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, int forceClientAuth); + public static extern ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, SslClientCertificateRequestType clientCertificateRequest); [DllImport(ImportName)] public static extern void grpcsharp_server_credentials_release(IntPtr credentials); diff --git a/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs b/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs index 545e581f94..5f8c95c4ea 100644 --- a/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs +++ b/src/csharp/Grpc.Core/Internal/ServerCredentialsSafeHandle.cs @@ -32,13 +32,13 @@ namespace Grpc.Core.Internal { } - public static ServerCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, bool forceClientAuth) + public static ServerCredentialsSafeHandle CreateSslCredentials(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, SslClientCertificateRequestType clientCertificateRequest) { GrpcPreconditions.CheckArgument(keyCertPairCertChainArray.Length == keyCertPairPrivateKeyArray.Length); return Native.grpcsharp_ssl_server_credentials_create(pemRootCerts, keyCertPairCertChainArray, keyCertPairPrivateKeyArray, new UIntPtr((ulong)keyCertPairCertChainArray.Length), - forceClientAuth ? 1 : 0); + clientCertificateRequest); } protected override bool ReleaseHandle() diff --git a/src/csharp/Grpc.Core/ServerCredentials.cs b/src/csharp/Grpc.Core/ServerCredentials.cs index 703f9ff6b3..056311e0c0 100644 --- a/src/csharp/Grpc.Core/ServerCredentials.cs +++ b/src/csharp/Grpc.Core/ServerCredentials.cs @@ -57,6 +57,60 @@ namespace Grpc.Core } } + /// + /// Modes of requesting client's SSL certificate by the server. + /// Corresponds to grpc_ssl_client_certificate_request_type. + /// + public enum SslClientCertificateRequestType { + /// + /// Server does not request client certificate. + /// The certificate presented by the client is not checked by the server at + /// all. (A client may present a self signed or signed certificate or not + /// present a certificate at all and any of those option would be accepted) + /// + DontRequestClientCertificate = 0, + /// + /// Server requests client certificate but does not enforce that the client + /// presents a certificate. + /// If the client presents a certificate, the client authentication is left to + /// the application (the necessary metadata will be available to the + /// application via authentication context properties, see grpc_auth_context). + /// The client's key certificate pair must be valid for the SSL connection to + /// be established. + /// + RequestClientCertificateButDontVerify, + /// + /// Server requests client certificate but does not enforce that the client + /// presents a certificate. + /// If the client presents a certificate, the client authentication is done by + /// the gRPC framework. (For a successful connection the client needs to either + /// present a certificate that can be verified against the root certificate + /// configured by the server or not present a certificate at all) + /// The client's key certificate pair must be valid for the SSL connection to + /// be established. + /// + RequestClientCertificateAndVerify, + /// + /// Server requests client certificate and enforces that the client presents a + /// certificate. + /// If the client presents a certificate, the client authentication is left to + /// the application (the necessary metadata will be available to the + /// application via authentication context properties, see grpc_auth_context). + /// The client's key certificate pair must be valid for the SSL connection to + /// be established. + /// + RequestAndRequireClientCertificateButDontVerify, + /// + /// Server requests client certificate and enforces that the client presents a + /// certificate. + /// The cerificate presented by the client is verified by the gRPC framework. + /// (For a successful connection the client needs to present a certificate that + /// can be verified against the root certificate configured by the server) + /// The client's key certificate pair must be valid for the SSL connection to + /// be established. + /// + RequestAndRequireClientCertificateAndVerify, + } /// /// Server-side SSL credentials. /// @@ -64,35 +118,45 @@ namespace Grpc.Core { readonly IList keyCertificatePairs; readonly string rootCertificates; - readonly bool forceClientAuth; + readonly SslClientCertificateRequestType clientCertificateRequest; /// /// Creates server-side SSL credentials. /// /// Key-certificates to use. /// PEM encoded client root certificates used to authenticate client. - /// If true, client will be rejected unless it proves its unthenticity using against rootCertificates. + /// Deprecated, use clientCertificateRequest overload instead. public SslServerCredentials(IEnumerable keyCertificatePairs, string rootCertificates, bool forceClientAuth) + : this(keyCertificatePairs, rootCertificates, forceClientAuth ? SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify : SslClientCertificateRequestType.DontRequestClientCertificate) + { + } + + /// + /// Creates server-side SSL credentials. + /// + /// Key-certificates to use. + /// PEM encoded client root certificates used to authenticate client. + /// Options for requesting and verification of client certificate. + public SslServerCredentials(IEnumerable keyCertificatePairs, string rootCertificates, SslClientCertificateRequestType clientCertificateRequest) { this.keyCertificatePairs = new List(keyCertificatePairs).AsReadOnly(); GrpcPreconditions.CheckArgument(this.keyCertificatePairs.Count > 0, "At least one KeyCertificatePair needs to be provided."); - if (forceClientAuth) + if (clientCertificateRequest == SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify) { GrpcPreconditions.CheckNotNull(rootCertificates, - "Cannot force client authentication unless you provide rootCertificates."); + "Cannot require and verify client certificate unless you provide rootCertificates."); } this.rootCertificates = rootCertificates; - this.forceClientAuth = forceClientAuth; + this.clientCertificateRequest = clientCertificateRequest; } /// /// Creates server-side SSL credentials. - /// This constructor should be use if you do not wish to autheticate client - /// using client root certificates. + /// This constructor should be use if you do not wish to autheticate client at all. /// /// Key-certificates to use. - public SslServerCredentials(IEnumerable keyCertificatePairs) : this(keyCertificatePairs, null, false) + public SslServerCredentials(IEnumerable keyCertificatePairs) : this(keyCertificatePairs, null, SslClientCertificateRequestType.DontRequestClientCertificate) { } @@ -119,13 +183,24 @@ namespace Grpc.Core } /// - /// If true, the authenticity of client check will be enforced. + /// Deprecated. If true, the authenticity of client check will be enforced. /// public bool ForceClientAuthentication { get { - return this.forceClientAuth; + return this.clientCertificateRequest == SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify; + } + } + + /// + /// Mode of requesting certificate from client by the server. + /// + public SslClientCertificateRequestType ClientCertificateRequest + { + get + { + return this.clientCertificateRequest; } } @@ -139,7 +214,7 @@ namespace Grpc.Core certChains[i] = keyCertificatePairs[i].CertificateChain; keys[i] = keyCertificatePairs[i].PrivateKey; } - return ServerCredentialsSafeHandle.CreateSslCredentials(rootCertificates, certChains, keys, forceClientAuth); + return ServerCredentialsSafeHandle.CreateSslCredentials(rootCertificates, certChains, keys, clientCertificateRequest); } } } diff --git a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs index 152d8feab9..713c4ea72a 100644 --- a/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs +++ b/src/csharp/Grpc.IntegrationTesting/SslCredentialsTest.cs @@ -37,20 +37,24 @@ namespace Grpc.IntegrationTesting public class SslCredentialsTest { const string Host = "localhost"; + const string IsPeerAuthenticatedMetadataKey = "test_only_is_peer_authenticated"; Server server; Channel channel; TestService.TestServiceClient client; - [OneTimeSetUp] - public void Init() + string rootCert; + KeyCertificatePair keyCertPair; + + public void InitClientAndServer(bool clientAddKeyCertPair, + SslClientCertificateRequestType clientCertRequestType) { - var rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath); - var keyCertPair = new KeyCertificatePair( + rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath); + keyCertPair = new KeyCertificatePair( File.ReadAllText(TestCredentials.ServerCertChainPath), File.ReadAllText(TestCredentials.ServerPrivateKeyPath)); - var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, true); - var clientCredentials = new SslCredentials(rootCert, keyCertPair); + var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, clientCertRequestType); + var clientCredentials = clientAddKeyCertPair ? new SslCredentials(rootCert, keyCertPair) : new SslCredentials(rootCert); // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755 server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) }) @@ -72,19 +76,133 @@ namespace Grpc.IntegrationTesting [OneTimeTearDown] public void Cleanup() { - channel.ShutdownAsync().Wait(); - server.ShutdownAsync().Wait(); + if (channel != null) + { + channel.ShutdownAsync().Wait(); + } + if (server != null) + { + server.ShutdownAsync().Wait(); + } } [Test] - public void AuthenticatedClientAndServer() + public async Task NoClientCert_DontRequestClientCertificate_Accepted() { - var response = client.UnaryCall(new SimpleRequest { ResponseSize = 10 }); - Assert.AreEqual(10, response.Payload.Body.Length); + InitClientAndServer( + clientAddKeyCertPair: false, + clientCertRequestType: SslClientCertificateRequestType.DontRequestClientCertificate); + + await CheckAccepted(expectPeerAuthenticated: false); } [Test] - public async Task AuthContextIsPopulated() + public async Task ClientWithCert_DontRequestClientCertificate_AcceptedButPeerNotAuthenticated() + { + InitClientAndServer( + clientAddKeyCertPair: true, + clientCertRequestType: SslClientCertificateRequestType.DontRequestClientCertificate); + + await CheckAccepted(expectPeerAuthenticated: false); + } + + [Test] + public async Task NoClientCert_RequestClientCertificateButDontVerify_Accepted() + { + InitClientAndServer( + clientAddKeyCertPair: false, + clientCertRequestType: SslClientCertificateRequestType.RequestClientCertificateButDontVerify); + + await CheckAccepted(expectPeerAuthenticated: false); + } + + [Test] + public async Task NoClientCert_RequestClientCertificateAndVerify_Accepted() + { + InitClientAndServer( + clientAddKeyCertPair: false, + clientCertRequestType: SslClientCertificateRequestType.RequestClientCertificateAndVerify); + + await CheckAccepted(expectPeerAuthenticated: false); + } + + [Test] + public async Task ClientWithCert_RequestAndRequireClientCertificateButDontVerify_Accepted() + { + InitClientAndServer( + clientAddKeyCertPair: true, + clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireClientCertificateButDontVerify); + + await CheckAccepted(expectPeerAuthenticated: true); + await CheckAuthContextIsPopulated(); + } + + [Test] + public async Task ClientWithCert_RequestAndRequireClientCertificateAndVerify_Accepted() + { + InitClientAndServer( + clientAddKeyCertPair: true, + clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify); + + await CheckAccepted(expectPeerAuthenticated: true); + await CheckAuthContextIsPopulated(); + } + + [Test] + public void NoClientCert_RequestAndRequireClientCertificateButDontVerify_Rejected() + { + InitClientAndServer( + clientAddKeyCertPair: false, + clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireClientCertificateButDontVerify); + + CheckRejected(); + } + + [Test] + public void NoClientCert_RequestAndRequireClientCertificateAndVerify_Rejected() + { + InitClientAndServer( + clientAddKeyCertPair: false, + clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify); + + CheckRejected(); + } + + [Test] + public void Constructor_LegacyForceClientAuth() + { + var creds = new SslServerCredentials(new[] { keyCertPair }, rootCert, true); + Assert.AreEqual(SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify, creds.ClientCertificateRequest); + + var creds2 = new SslServerCredentials(new[] { keyCertPair }, rootCert, false); + Assert.AreEqual(SslClientCertificateRequestType.DontRequestClientCertificate, creds2.ClientCertificateRequest); + } + + [Test] + public void Constructor_NullRootCerts() + { + var keyCertPairs = new[] { keyCertPair }; + new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.DontRequestClientCertificate); + new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestClientCertificateAndVerify); + new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndRequireClientCertificateButDontVerify); + Assert.Throws(typeof(ArgumentNullException), () => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndRequireClientCertificateAndVerify)); + } + + private async Task CheckAccepted(bool expectPeerAuthenticated) + { + var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 }); + var response = await call; + Assert.AreEqual(10, response.Payload.Body.Length); + Assert.AreEqual(expectPeerAuthenticated.ToString(), call.GetTrailers().First((entry) => entry.Key == IsPeerAuthenticatedMetadataKey).Value); + } + + private void CheckRejected() + { + var ex = Assert.Throws(() => client.UnaryCall(new SimpleRequest { ResponseSize = 10 })); + Assert.AreEqual(StatusCode.Unavailable, ex.Status.StatusCode); + } + + private async Task CheckAuthContextIsPopulated() { var call = client.StreamingInputCall(); await call.RequestStream.CompleteAsync(); @@ -96,6 +214,7 @@ namespace Grpc.IntegrationTesting { public override Task UnaryCall(SimpleRequest request, ServerCallContext context) { + context.ResponseTrailers.Add(IsPeerAuthenticatedMetadataKey, context.AuthContext.IsPeerAuthenticated.ToString()); return Task.FromResult(new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) }); } diff --git a/src/csharp/ext/grpc_csharp_ext.c b/src/csharp/ext/grpc_csharp_ext.c index 87a2516f8d..d20b38c930 100644 --- a/src/csharp/ext/grpc_csharp_ext.c +++ b/src/csharp/ext/grpc_csharp_ext.c @@ -964,7 +964,7 @@ GPR_EXPORT grpc_server_credentials* GPR_CALLTYPE grpcsharp_ssl_server_credentials_create( const char* pem_root_certs, const char** key_cert_pair_cert_chain_array, const char** key_cert_pair_private_key_array, size_t num_key_cert_pairs, - int force_client_auth) { + grpc_ssl_client_certificate_request_type client_request_type) { size_t i; grpc_server_credentials* creds; grpc_ssl_pem_key_cert_pair* key_cert_pairs = @@ -981,9 +981,7 @@ grpcsharp_ssl_server_credentials_create( } creds = grpc_ssl_server_credentials_create_ex( pem_root_certs, key_cert_pairs, num_key_cert_pairs, - force_client_auth - ? GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY - : GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE, + client_request_type, NULL); gpr_free(key_cert_pairs); return creds; diff --git a/templates/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs.template b/templates/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs.template index 8ce2a57323..774fc2c56f 100644 --- a/templates/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs.template +++ b/templates/src/csharp/Grpc.Core/Internal/NativeMethods.Generated.cs.template @@ -73,7 +73,7 @@ 'void grpcsharp_redirect_log(GprLogDelegate callback)', 'CallCredentialsSafeHandle grpcsharp_metadata_credentials_create_from_plugin(NativeMetadataInterceptor interceptor)', 'void grpcsharp_metadata_credentials_notify_from_plugin(IntPtr callbackPtr, IntPtr userData, MetadataArraySafeHandle metadataArray, StatusCode statusCode, string errorDetails)', - 'ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, int forceClientAuth)', + 'ServerCredentialsSafeHandle grpcsharp_ssl_server_credentials_create(string pemRootCerts, string[] keyCertPairCertChainArray, string[] keyCertPairPrivateKeyArray, UIntPtr numKeyCertPairs, SslClientCertificateRequestType clientCertificateRequest)', 'void grpcsharp_server_credentials_release(IntPtr credentials)', 'ServerSafeHandle grpcsharp_server_create(ChannelArgsSafeHandle args)', 'void grpcsharp_server_register_completion_queue(ServerSafeHandle server, CompletionQueueSafeHandle cq)', -- cgit v1.2.3