Imported Upstream version 1.21.0
[platform/upstream/grpc.git] / src / csharp / Grpc.IntegrationTesting / SslCredentialsTest.cs
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
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]
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]
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]
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]
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]
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]
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]
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]
153         public void NoClientCert_RequestAndRequireClientCertificateButDontVerify_Rejected()
154         {
155             InitClientAndServer(
156                 clientAddKeyCertPair: false,
157                 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireButDontVerify);
158
159             CheckRejected();
160         }
161
162         [Test]
163         public void NoClientCert_RequestAndRequireClientCertificateAndVerify_Rejected()
164         {
165             InitClientAndServer(
166                 clientAddKeyCertPair: false,
167                 clientCertRequestType: SslClientCertificateRequestType.RequestAndRequireAndVerify);
168
169             CheckRejected();
170         }
171
172         [Test]
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]
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]
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]
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]
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
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
246         private void CheckRejected()
247         {
248             var ex = Assert.Throws<RpcException>(() => client.UnaryCall(new SimpleRequest { ResponseSize = 10 }));
249             Assert.AreEqual(StatusCode.Unavailable, ex.Status.StatusCode);
250         }
251
252         private async Task CheckAuthContextIsPopulated()
253         {
254             var call = client.StreamingInputCall();
255             await call.RequestStream.CompleteAsync();
256             var response = await call.ResponseAsync;
257             Assert.AreEqual(12345, response.AggregatedPayloadSize);
258         }
259
260         private class SslCredentialsTestServiceImpl : TestService.TestServiceBase
261         {
262             public override Task<SimpleResponse> UnaryCall(SimpleRequest request, ServerCallContext context)
263             {
264                 context.ResponseTrailers.Add(IsPeerAuthenticatedMetadataKey, context.AuthContext.IsPeerAuthenticated.ToString());
265                 return Task.FromResult(new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) });
266             }
267
268             public override async Task<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> requestStream, ServerCallContext context)
269             {
270                 var authContext = context.AuthContext;
271                 await requestStream.ForEachAsync(request => TaskUtils.CompletedTask);
272
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);
277
278                 return new StreamingInputCallResponse { AggregatedPayloadSize = 12345 };
279             }
280
281             private static Payload CreateZerosPayload(int size)
282             {
283                 return new Payload { Body = ByteString.CopyFrom(new byte[size]) };
284             }
285         }
286     }
287 }