• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #region Copyright notice and license
2 
3 // Copyright 2015-2016 gRPC authors.
4 //
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
8 //
9 //     http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 
17 #endregion
18 
19 using System;
20 using System.Collections.Generic;
21 using System.IO;
22 using System.Linq;
23 using System.Threading;
24 using System.Threading.Tasks;
25 using Google.Protobuf;
26 using Grpc.Core;
27 using Grpc.Core.Utils;
28 using Grpc.Testing;
29 using NUnit.Framework;
30 
31 namespace Grpc.IntegrationTesting
32 {
33     /// <summary>
34     /// Test SSL credentials where server authenticates client
35     /// and client authenticates the server.
36     /// </summary>
37     public class SslCredentialsTest
38     {
39         const string Host = "localhost";
40         const string IsPeerAuthenticatedMetadataKey = "test_only_is_peer_authenticated";
41         Server server;
42         Channel channel;
43         TestService.TestServiceClient client;
44 
45         string rootCert;
46         KeyCertificatePair keyCertPair;
47 
InitClientAndServer(bool clientAddKeyCertPair, SslClientCertificateRequestType clientCertRequestType, VerifyPeerCallback verifyPeerCallback = null)48         public void InitClientAndServer(bool clientAddKeyCertPair,
49                 SslClientCertificateRequestType clientCertRequestType,
50                 VerifyPeerCallback verifyPeerCallback = null)
51         {
52             rootCert = File.ReadAllText(TestCredentials.ClientCertAuthorityPath);
53             keyCertPair = new KeyCertificatePair(
54                 File.ReadAllText(TestCredentials.ServerCertChainPath),
55                 File.ReadAllText(TestCredentials.ServerPrivateKeyPath));
56 
57             var serverCredentials = new SslServerCredentials(new[] { keyCertPair }, rootCert, clientCertRequestType);
58             var clientCredentials = new SslCredentials(rootCert, clientAddKeyCertPair ? keyCertPair : null, verifyPeerCallback);
59 
60             // Disable SO_REUSEPORT to prevent https://github.com/grpc/grpc/issues/10755
61             server = new Server(new[] { new ChannelOption(ChannelOptions.SoReuseport, 0) })
62             {
63                 Services = { TestService.BindService(new SslCredentialsTestServiceImpl()) },
64                 Ports = { { Host, ServerPort.PickUnused, serverCredentials } }
65             };
66             server.Start();
67 
68             var options = new List<ChannelOption>
69             {
70                 new ChannelOption(ChannelOptions.SslTargetNameOverride, TestCredentials.DefaultHostOverride)
71             };
72 
73             channel = new Channel(Host, server.Ports.Single().BoundPort, clientCredentials, options);
74             client = new TestService.TestServiceClient(channel);
75         }
76 
77         [OneTimeTearDown]
Cleanup()78         public void Cleanup()
79         {
80             if (channel != null)
81             {
82                 channel.ShutdownAsync().Wait();
83             }
84             if (server != null)
85             {
86                 server.ShutdownAsync().Wait();
87             }
88         }
89 
90         [Test]
NoClientCert_DontRequestClientCertificate_Accepted()91         public async Task NoClientCert_DontRequestClientCertificate_Accepted()
92         {
93             InitClientAndServer(
94                 clientAddKeyCertPair: false,
95                 clientCertRequestType: SslClientCertificateRequestType.DontRequest);
96 
97             await CheckAccepted(expectPeerAuthenticated: false);
98         }
99 
100         [Test]
ClientWithCert_DontRequestClientCertificate_AcceptedButPeerNotAuthenticated()101         public async Task ClientWithCert_DontRequestClientCertificate_AcceptedButPeerNotAuthenticated()
102         {
103             InitClientAndServer(
104                 clientAddKeyCertPair: true,
105                 clientCertRequestType: SslClientCertificateRequestType.DontRequest);
106 
107             await CheckAccepted(expectPeerAuthenticated: false);
108         }
109 
110         [Test]
NoClientCert_RequestClientCertificateButDontVerify_Accepted()111         public async Task NoClientCert_RequestClientCertificateButDontVerify_Accepted()
112         {
113             InitClientAndServer(
114                 clientAddKeyCertPair: false,
115                 clientCertRequestType: SslClientCertificateRequestType.RequestButDontVerify);
116 
117             await CheckAccepted(expectPeerAuthenticated: false);
118         }
119 
120         [Test]
NoClientCert_RequestClientCertificateAndVerify_Accepted()121         public async Task NoClientCert_RequestClientCertificateAndVerify_Accepted()
122         {
123             InitClientAndServer(
124                 clientAddKeyCertPair: false,
125                 clientCertRequestType: SslClientCertificateRequestType.RequestAndVerify);
126 
127             await CheckAccepted(expectPeerAuthenticated: false);
128         }
129 
130         [Test]
ClientWithCert_RequestAndRequireClientCertificateButDontVerify_Accepted()131         public async Task ClientWithCert_RequestAndRequireClientCertificateButDontVerify_Accepted()
132         {
133             InitClientAndServer(
134                 clientAddKeyCertPair: true,
135                 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireButDontVerify);
136 
137             await CheckAccepted(expectPeerAuthenticated: true);
138             await CheckAuthContextIsPopulated();
139         }
140 
141         [Test]
ClientWithCert_RequestAndRequireClientCertificateAndVerify_Accepted()142         public async Task ClientWithCert_RequestAndRequireClientCertificateAndVerify_Accepted()
143         {
144             InitClientAndServer(
145                 clientAddKeyCertPair: true,
146                 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireAndVerify);
147 
148             await CheckAccepted(expectPeerAuthenticated: true);
149             await CheckAuthContextIsPopulated();
150         }
151 
152         [Test]
NoClientCert_RequestAndRequireClientCertificateButDontVerify_Rejected()153         public void NoClientCert_RequestAndRequireClientCertificateButDontVerify_Rejected()
154         {
155             InitClientAndServer(
156                 clientAddKeyCertPair: false,
157                 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireButDontVerify);
158 
159             CheckRejected();
160         }
161 
162         [Test]
NoClientCert_RequestAndRequireClientCertificateAndVerify_Rejected()163         public void NoClientCert_RequestAndRequireClientCertificateAndVerify_Rejected()
164         {
165             InitClientAndServer(
166                 clientAddKeyCertPair: false,
167                 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireAndVerify);
168 
169             CheckRejected();
170         }
171 
172         [Test]
Constructor_LegacyForceClientAuth()173         public void Constructor_LegacyForceClientAuth()
174         {
175             var creds = new SslServerCredentials(new[] { keyCertPair }, rootCert, true);
176             Assert.AreEqual(SslClientCertificateRequestType.RequestAndRequireAndVerify, creds.ClientCertificateRequest);
177 
178             var creds2 = new SslServerCredentials(new[] { keyCertPair }, rootCert, false);
179             Assert.AreEqual(SslClientCertificateRequestType.DontRequest, creds2.ClientCertificateRequest);
180         }
181 
182         [Test]
Constructor_NullRootCerts()183         public void Constructor_NullRootCerts()
184         {
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));
190         }
191 
192         [Test]
VerifyPeerCallback_Accepted()193         public async Task VerifyPeerCallback_Accepted()
194         {
195             string targetNameFromCallback = null;
196             string peerPemFromCallback = null;
197             InitClientAndServer(
198                 clientAddKeyCertPair: false,
199                 clientCertRequestType: SslClientCertificateRequestType.DontRequest,
200                 verifyPeerCallback: (ctx) =>
201                 {
202                     targetNameFromCallback = ctx.TargetName;
203                     peerPemFromCallback = ctx.PeerPem;
204                     return true;
205                 });
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);
210         }
211 
212         [Test]
VerifyPeerCallback_CallbackThrows_Rejected()213         public void VerifyPeerCallback_CallbackThrows_Rejected()
214         {
215             InitClientAndServer(
216                 clientAddKeyCertPair: false,
217                 clientCertRequestType: SslClientCertificateRequestType.DontRequest,
218                 verifyPeerCallback: (ctx) =>
219                 {
220                     throw new Exception("VerifyPeerCallback has thrown on purpose.");
221                 });
222             CheckRejected();
223         }
224 
225         [Test]
VerifyPeerCallback_Rejected()226         public void VerifyPeerCallback_Rejected()
227         {
228             InitClientAndServer(
229                 clientAddKeyCertPair: false,
230                 clientCertRequestType: SslClientCertificateRequestType.DontRequest,
231                 verifyPeerCallback: (ctx) =>
232                 {
233                     return false;
234                 });
235             CheckRejected();
236         }
237 
CheckAccepted(bool expectPeerAuthenticated)238         private async Task CheckAccepted(bool expectPeerAuthenticated)
239         {
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);
244         }
245 
CheckRejected()246         private void CheckRejected()
247         {
248             var ex = Assert.Throws<RpcException>(() => client.UnaryCall(new SimpleRequest { ResponseSize = 10 }));
249         if (ex.Status.StatusCode != StatusCode.Unavailable & ex.Status.StatusCode != StatusCode.Unknown) {
250                 Assert.Fail("Expect status to be either Unavailable or Unknown");
251             }
252 	}
253 
CheckAuthContextIsPopulated()254         private async Task CheckAuthContextIsPopulated()
255         {
256             var call = client.StreamingInputCall();
257             await call.RequestStream.CompleteAsync();
258             var response = await call.ResponseAsync;
259             Assert.AreEqual(12345, response.AggregatedPayloadSize);
260         }
261 
262         private class SslCredentialsTestServiceImpl : TestService.TestServiceBase
263         {
UnaryCall(SimpleRequest request, ServerCallContext context)264             public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
265             {
266                 context.ResponseTrailers.Add(IsPeerAuthenticatedMetadataKey, context.AuthContext.IsPeerAuthenticated.ToString());
267                 return Task.FromResult(new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) });
268             }
269 
StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)270             public override async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
271             {
272                 var authContext = context.AuthContext;
273                 await requestStream.ForEachAsync(request => TaskUtils.CompletedTask);
274 
275                 Assert.IsTrue(authContext.IsPeerAuthenticated);
276                 Assert.AreEqual("x509_subject_alternative_name", authContext.PeerIdentityPropertyName);
277                 Assert.IsTrue(authContext.PeerIdentity.Count() > 0);
278                 Assert.AreEqual("ssl", authContext.FindPropertiesByName("transport_security_type").First().Value);
279 
280                 return new StreamingInputCallResponse { AggregatedPayloadSize = 12345 };
281             }
282 
CreateZerosPayload(int size)283             private static Payload CreateZerosPayload(int size)
284             {
285                 return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
286             }
287         }
288     }
289 }
290