1 #region Copyright notice and license
3 // Copyright 2015-2016 gRPC authors.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
20 using System.Collections.Generic;
23 using System.Threading;
24 using System.Threading.Tasks;
25 using Google.Protobuf;
27 using Grpc.Core.Utils;
29 using NUnit.Framework;
31 namespace Grpc.IntegrationTesting
34 /// Test SSL credentials where server authenticates client
35 /// and client authenticates the server.
37 public class SslCredentialsTest
39 const string Host = "localhost";
40 const string IsPeerAuthenticatedMetadataKey = "test_only_is_peer_authenticated";
43 TestService.TestServiceClient client;
46 KeyCertificatePair keyCertPair;
48 public void InitClientAndServer(bool clientAddKeyCertPair,
49 SslClientCertificateRequestType clientCertRequestType,
50 VerifyPeerCallback verifyPeerCallback = null)
52 rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath);
53 keyCertPair = new KeyCertificatePair(
54 File.ReadAllText(TestCredentials.ServerCertChainPath),
55 File.ReadAllText(TestCredentials.ServerPrivateKeyPath));
57 var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, clientCertRequestType);
58 var clientCredentials = new SslCredentials(rootCert, clientAddKeyCertPair ? keyCertPair : null, verifyPeerCallback);
60 // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
61 server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
63 Services = { TestService.BindService(new SslCredentialsTestServiceImpl()) },
64 Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
68 var options = new List<ChannelOption>
70 new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
73 channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
74 client = new TestService.TestServiceClient(channel);
82 channel.ShutdownAsync().Wait();
86 server.ShutdownAsync().Wait();
91 public async Task NoClientCert_DontRequestClientCertificate_Accepted()
94 clientAddKeyCertPair: false,
95 clientCertRequestType: SslClientCertificateRequestType.DontRequest);
97 await CheckAccepted(expectPeerAuthenticated: false);
101 public async Task ClientWithCert_DontRequestClientCertificate_AcceptedButPeerNotAuthenticated()
104 clientAddKeyCertPair: true,
105 clientCertRequestType: SslClientCertificateRequestType.DontRequest);
107 await CheckAccepted(expectPeerAuthenticated: false);
111 public async Task NoClientCert_RequestClientCertificateButDontVerify_Accepted()
114 clientAddKeyCertPair: false,
115 clientCertRequestType: SslClientCertificateRequestType.RequestButDontVerify);
117 await CheckAccepted(expectPeerAuthenticated: false);
121 public async Task NoClientCert_RequestClientCertificateAndVerify_Accepted()
124 clientAddKeyCertPair: false,
125 clientCertRequestType: SslClientCertificateRequestType.RequestAndVerify);
127 await CheckAccepted(expectPeerAuthenticated: false);
131 public async Task ClientWithCert_RequestAndRequireClientCertificateButDontVerify_Accepted()
134 clientAddKeyCertPair: true,
135 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireButDontVerify);
137 await CheckAccepted(expectPeerAuthenticated: true);
138 await CheckAuthContextIsPopulated();
142 public async Task ClientWithCert_RequestAndRequireClientCertificateAndVerify_Accepted()
145 clientAddKeyCertPair: true,
146 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireAndVerify);
148 await CheckAccepted(expectPeerAuthenticated: true);
149 await CheckAuthContextIsPopulated();
153 public void NoClientCert_RequestAndRequireClientCertificateButDontVerify_Rejected()
156 clientAddKeyCertPair: false,
157 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireButDontVerify);
163 public void NoClientCert_RequestAndRequireClientCertificateAndVerify_Rejected()
166 clientAddKeyCertPair: false,
167 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireAndVerify);
173 public void Constructor_LegacyForceClientAuth()
175 var creds = new SslServerCredentials(new[] { keyCertPair }, rootCert, true);
176 Assert.AreEqual(SslClientCertificateRequestType.RequestAndRequireAndVerify, creds.ClientCertificateRequest);
178 var creds2 = new SslServerCredentials(new[] { keyCertPair }, rootCert, false);
179 Assert.AreEqual(SslClientCertificateRequestType.DontRequest, creds2.ClientCertificateRequest);
183 public void Constructor_NullRootCerts()
185 var keyCertPairs = new[] { keyCertPair };
186 Assert.DoesNotThrow(() => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.DontRequest));
187 Assert.DoesNotThrow(() => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndVerify));
188 Assert.DoesNotThrow(() => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndRequireButDontVerify));
189 Assert.Throws(typeof(ArgumentNullException), () => new SslServerCredentials(keyCertPairs, null, SslClientCertificateRequestType.RequestAndRequireAndVerify));
193 public async Task VerifyPeerCallback_Accepted()
195 string targetNameFromCallback = null;
196 string peerPemFromCallback = null;
198 clientAddKeyCertPair: false,
199 clientCertRequestType: SslClientCertificateRequestType.DontRequest,
200 verifyPeerCallback: (ctx) =>
202 targetNameFromCallback = ctx.TargetName;
203 peerPemFromCallback = ctx.PeerPem;
206 await CheckAccepted(expectPeerAuthenticated: false);
207 Assert.AreEqual(TestCredentials.DefaultHostOverride, targetNameFromCallback);
208 var expectedServerPem = File.ReadAllText(TestCredentials.ServerCertChainPath).Replace("\r", "");
209 Assert.AreEqual(expectedServerPem, peerPemFromCallback);
213 public void VerifyPeerCallback_CallbackThrows_Rejected()
216 clientAddKeyCertPair: false,
217 clientCertRequestType: SslClientCertificateRequestType.DontRequest,
218 verifyPeerCallback: (ctx) =>
220 throw new Exception("VerifyPeerCallback has thrown on purpose.");
226 public void VerifyPeerCallback_Rejected()
229 clientAddKeyCertPair: false,
230 clientCertRequestType: SslClientCertificateRequestType.DontRequest,
231 verifyPeerCallback: (ctx) =>
238 private async Task CheckAccepted(bool expectPeerAuthenticated)
240 var call = client.UnaryCallAsync(new SimpleRequest { ResponseSize = 10 });
241 var response = await call;
242 Assert.AreEqual(10, response.Payload.Body.Length);
243 Assert.AreEqual(expectPeerAuthenticated.ToString(), call.GetTrailers().First((entry) => entry.Key == IsPeerAuthenticatedMetadataKey).Value);
246 private void CheckRejected()
248 var ex = Assert.Throws<RpcException>(() => client.UnaryCall(new SimpleRequest { ResponseSize = 10 }));
249 Assert.AreEqual(StatusCode.Unavailable, ex.Status.StatusCode);
252 private async Task CheckAuthContextIsPopulated()
254 var call = client.StreamingInputCall();
255 await call.RequestStream.CompleteAsync();
256 var response = await call.ResponseAsync;
257 Assert.AreEqual(12345, response.AggregatedPayloadSize);
260 private class SslCredentialsTestServiceImpl : TestService.TestServiceBase
262 public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
264 context.ResponseTrailers.Add(IsPeerAuthenticatedMetadataKey, context.AuthContext.IsPeerAuthenticated.ToString());
265 return Task.FromResult(new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) });
268 public override async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
270 var authContext = context.AuthContext;
271 await requestStream.ForEachAsync(request => TaskUtils.CompletedTask);
273 Assert.IsTrue(authContext.IsPeerAuthenticated);
274 Assert.AreEqual("x509_subject_alternative_name", authContext.PeerIdentityPropertyName);
275 Assert.IsTrue(authContext.PeerIdentity.Count() > 0);
276 Assert.AreEqual("ssl", authContext.FindPropertiesByName("transport_security_type").First().Value);
278 return new StreamingInputCallResponse { AggregatedPayloadSize = 12345 };
281 private static Payload CreateZerosPayload(int size)
283 return new Payload { Body = ByteString.CopyFrom(new byte[size]) };