Skip to content

Commit f467c0d

Browse files
authored
Merge pull request #51 from gsuberland/ssl-ipc
Add support for SSL
2 parents 17275ca + 05dd958 commit f467c0d

File tree

19 files changed

+709
-8
lines changed

19 files changed

+709
-8
lines changed

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# IpcServiceFramework
44

5-
A .NET Core lightweight inter-process communication framework allowing invoking a service via named pipeline and/or TCP (in a similar way as WCF, which is currently unavailable for .NET Core).
5+
A .NET Core lightweight inter-process communication framework allowing invoking a service via named pipeline and/or TCP (in a similar way as WCF, which is currently unavailable for .NET Core). Secure communication over SSL is also supported.
66

77
Support using primitive or complexe types in service contract.
88

@@ -109,3 +109,55 @@ $ dotnet add package JKang.IpcServiceFramework.Client
109109
```
110110

111111
__Welcome to raise any issue or even provide any suggestion/PR to participate this project!__
112+
113+
## Security
114+
115+
If you are running IPC channels over TCP on an untrusted network, you should consider using SSL. IpcServiceFramework supports SSL on TCP clients and hosts.
116+
117+
### Generate certificates for testing
118+
119+
**Do not use the provided certificates in the project folder.** These are used for example purposes only.
120+
121+
For testing, you can generate a self-signed certificate using the following openssl command:
122+
123+
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.cer -days 365
124+
125+
This generates a key and a certificate that can be used for testing.
126+
127+
### Setting up the SSL endpoint
128+
129+
The endpoint requires a PKCS12 file containing both the certificate and a corresponding private key.
130+
131+
A certificate and key can be combined to a PKCS12 file for use with the server using the following command:
132+
133+
openssl pkcs12 -export -in cert.cer -inkey key.pem -out server.pfx
134+
135+
You will be asked for a password.
136+
137+
You can import the certificate and provide it to the server endpoint using code similar to the following:
138+
139+
var certificate = new X509Certificate2(@"path\to\server.pfx", "password");
140+
serviceHostBuilder.AddTcpEndpoint<ISomeServiceContract>("someEndpoint", ip, port, certificate);
141+
142+
See the ConsoleServer and WebServer projects for more complete examples.
143+
144+
Note: for security and maintenance reasons, we do not recommend that you hard-code the certificate password. It should instead be stored in the application configuration file so that it can be easily changed.
145+
146+
### Safe usage
147+
148+
SSL/TLS is only secure if you use it properly. Here are some tips:
149+
150+
* For production purposes, use a proper server certificate, signed by a real certificate authority (CA) or your organisation's internal CA. Do not use self-signed certificates in production.
151+
* Do not use custom certificate validation callbacks on the client. They are hard to implement correctly and tend to result in security issues.
152+
* Unconditionally returning true in a validation callback provides no security whatsoever against an attacker who can perform man-in-the-middle attacks.
153+
* The callback used in the ConsoleServer project example is not secure. It checks for the correct certificate by hash but does not check its validity, expiry date, revocation status, or other important security properties.
154+
155+
### Client certificates
156+
157+
Client certificates are not currently supported.
158+
159+
## Stream translators
160+
161+
If you want to process the binary data after serialisation or before deserialisation, for example to add a custom handshake when the connection begins, you can do so using a stream translator. Host and client classes allow you to pass a `Func<Stream, Stream>` stream translation callback in their constructors, which can be used to "wrap" a custom stream around the network stream. This is supported on TCP communications both with and without SSL enabled. See the `XorStream` class in the IpcServiceSample.ServiceContracts project for an example of a stream translator.
162+
163+
Stream translators are also useful for logging packets for debugging. See the `LoggingStream` class in the IpcServiceSample.ServiceContracts project for an example of using a stream translator to log traffic.

src/IpcServiceSample.ConsoleClient/Program.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using IpcServiceSample.ServiceContracts;
2+
using IpcServiceSample.ServiceContracts.Helpers;
23
using JKang.IpcServiceFramework;
34
using System;
45
using System.Net;
6+
using System.Net.Security;
7+
using System.Security.Cryptography.X509Certificates;
58
using System.Text;
69
using System.Threading;
710
using System.Threading.Tasks;
@@ -35,6 +38,20 @@ await Task.WhenAll(RunTestsAsync(source.Token), Task.Run(() =>
3538
}
3639
}
3740

41+
private static bool InsecureValidationCallback_TESTONLY(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
42+
{
43+
/* WARNING: Using certificate validation callback can be dangerous. Incorrect implementation can lead to serious security issues.
44+
*
45+
* For example, unconditionally returning true in this function provides no security whatsoever against an attacker who can perform
46+
* man-in-the-middle attacks.
47+
*
48+
* This function is used only for test purposes. It validates only that the correct server certificate is used by the server.
49+
* However, it does not validate the certificate chain or validate that the certificate common name matches the server domain name.
50+
* Do not use this example in a production application.
51+
*/
52+
return certificate.GetCertHashString() == "FA54627C36D3DAEFF69E04B59120992305A7104F";
53+
}
54+
3855
private static async Task RunTestsAsync(CancellationToken cancellationToken)
3956
{
4057
IpcServiceClient<IComputingService> computingClient = new IpcServiceClientBuilder<IComputingService>()
@@ -45,6 +62,18 @@ private static async Task RunTestsAsync(CancellationToken cancellationToken)
4562
.UseTcp(IPAddress.Loopback, 45684)
4663
.Build();
4764

65+
IpcServiceClient<ITestService> secureClient = new IpcServiceClientBuilder<ITestService>()
66+
.UseTcp(IPAddress.Loopback, 44384, "test-ipcsf-secure-server", InsecureValidationCallback_TESTONLY)
67+
.Build();
68+
69+
IpcServiceClient<ITestService> xorTranslatedClient = new IpcServiceClientBuilder<ITestService>()
70+
.UseTcp(IPAddress.Loopback, 45454, s => new XorStream(s))
71+
.Build();
72+
73+
IpcServiceClient<ISystemService> loggedClient = new IpcServiceClientBuilder<ISystemService>()
74+
.UseTcp(IPAddress.Loopback, 45684, s => new LoggingStream(s, "ipc.log"))
75+
.Build();
76+
4877
// test 1: call IPC service method with primitive types
4978
float result1 = await computingClient.InvokeAsync(x => x.AddFloat(1.23f, 4.56f), cancellationToken);
5079
Console.WriteLine($"[TEST 1] sum of 2 floating number is: {result1}");
@@ -96,6 +125,18 @@ private static async Task RunTestsAsync(CancellationToken cancellationToken)
96125
// test 11: call async server function
97126
int sum = await computingClient.InvokeAsync(x => x.SumAsync(1, 1));
98127
Console.WriteLine($"[TEST 11] Called async function: {sum}");
128+
129+
// test 12: call secure service method
130+
generatedId = await secureClient.InvokeAsync(x => x.GenerateId(), cancellationToken);
131+
Console.WriteLine($"[TEST 12] Called secure service method, generated ID is: {generatedId}");
132+
133+
// test 13 call translated service method
134+
generatedId = await xorTranslatedClient.InvokeAsync(x => x.GenerateId(), cancellationToken);
135+
Console.WriteLine($"[TEST 13] Called translated service method, generated ID is: {generatedId}");
136+
137+
// test 14: use a translated stream to log data to a text file
138+
generatedId = await loggedClient.InvokeAsync(x => x.GenerateId(), cancellationToken);
139+
Console.WriteLine($"[TEST 14] Called method using stream translator for logging, generated ID is: {generatedId}");
99140
}
100141
}
101142
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFfTCCA2WgAwIBAgIUCTca4HpnnD5ZL3E3YIiM+meBmLYwDQYJKoZIhvcNAQEL
3+
BQAwTjELMAkGA1UEBhMCWFgxDTALBgNVBAgMBFRlc3QxDTALBgNVBAoMBFRlc3Qx
4+
ITAfBgNVBAMMGHRlc3QtaXBjc2Ytc2VjdXJlLXNlcnZlcjAeFw0xODEyMTIyMjQ4
5+
NDNaFw0yODEyMDkyMjQ4NDNaME4xCzAJBgNVBAYTAlhYMQ0wCwYDVQQIDARUZXN0
6+
MQ0wCwYDVQQKDARUZXN0MSEwHwYDVQQDDBh0ZXN0LWlwY3NmLXNlY3VyZS1zZXJ2
7+
ZXIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsQeBCs8OknFXnlz0i
8+
a6ScSF2cS/zKKpExDfhcLlGRhcXwydu1q3SMumvFgOMqKeBs7oM2JT1VqEWC77D6
9+
Cct+rIAdEw/Km1njYxLjo4buYIwJbNeJT+iZv0sSFJvx+Go15n6QfrFcmVGy52Hs
10+
BkD7SQTSLv9D5UwysNn7i/BcSXZPw272oobYqRKeQQ0aVMCP7f0O1vXcJVVsER0H
11+
YpI0m7BTpIFQbGg8jnjSue0AKQmYyzoroOwW1B7y/lp8W43lCL40ZMiBwZQ6jFFp
12+
OHBB+SxQqzWr6duBxA6F96tlDEXZXCJoOt0nIVXtXcv5q5xpcuiIil21aIVMSw3o
13+
Eo7ccvIm/gTCUnkJ0ahx7DAX1jaTt5Zh8K78z0CXxr5pg+Q/BFdGZ0iaYBtKX+yL
14+
1vcWLusKYhI2IEFgtJXhfjaYm/rhTjS4SCzmAMexWPWJXy/Bu1nh2fSrMMZeWyeu
15+
S/0T60cZav/Pqqat+ReufvxRlpYlq8KfOuVrfbwbYZwR4NyVRDPNhAMgTZngqTbN
16+
5xsqrH1pNspwU8emJOUvH0CWY4/qRr5uJzbNsBS9nbJERAQe0wsPpw8ZOgItmEX/
17+
X562cjGrCXvaQewh2a17kvFq+eEpR/pMs/JBGT22wFVfof0Sy/Fl0w0hHW4QPM7e
18+
+I9mbNuedK4w6vUSqwCK5knpeQIDAQABo1MwUTAdBgNVHQ4EFgQU3Xxk9XFDaGnZ
19+
zck+or/hA5yRJEswHwYDVR0jBBgwFoAU3Xxk9XFDaGnZzck+or/hA5yRJEswDwYD
20+
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAVih/7NFQsbjieZZcxqvk
21+
XhfnMMXtJZiHnTS5gFEtbAwuhCYE2yrhXngGKXSQX2XCcuSzvgVysiV/prN+KVHf
22+
APZXiGjOaCJwPHkiCeJ/bub+cLclnm3dNnoHgmKhIZgv7C/bAnGGdcwlOP76lgg2
23+
c/pEuFyfN1mwEiHjO/ryEHF3KxpGvdlkHZXnSoRP+Ghk0PCmwJkyRaCkn6YbeAq2
24+
+7CVnlMwzCqsIRb2yz/0COWJYTH3w39Ejf5HGGl2TKQ3mt1425m5j/kJdBxCE6cC
25+
epufzBae143qvkDGN4/3Xhb3QGxh7ep9RGXTgPHlQqXMS6gWpa9dJ1wGJb1M4O92
26+
V6c+Gp87mA42/sew1kBERx8KupgM/zTatRO+b5DCRkTuZ/aQvxaod3c++0WeufsC
27+
IFMOGkqYNDot2bAqFQKqg72FXR0OD/Iq4R6ZQNeYUoNH3c4ev3zcevPzaO9wwP06
28+
C5vfp4QivK/3vcjHMxfCEPm3KNls/U6syTTH12RHguGUHsEd7SY4S7n3oJoziXtf
29+
yzKQF+CkDTWC86pjm9bN3AchObYJIbVx+vZnWOzDmOZTE0AshGWxlWTR3TMJLiNr
30+
sgtTfiNEN+6HbKh4aoDAtapwyoGy0LfqoiLHAYjp8ePS1v0Lr80c5yoPDLrfdYHK
31+
ZHQzl1Ey1cFJr0zpx9Bi33I=
32+
-----END CERTIFICATE-----
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
51+
n8O7s8EqosXOZRYQie6zT0ggrSQam1M=
52+
-----END PRIVATE KEY-----

src/IpcServiceSample.ConsoleServer/IpcServiceSample.ConsoleServer.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,11 @@
1616
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
1717
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.0.0" />
1818
</ItemGroup>
19+
20+
<ItemGroup>
21+
<None Update="Certificates\server.pfx">
22+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
23+
</None>
24+
</ItemGroup>
1925

2026
</Project>

src/IpcServiceSample.ConsoleServer/Program.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using IpcServiceSample.ServiceContracts;
2+
using IpcServiceSample.ServiceContracts.Helpers;
23
using JKang.IpcServiceFramework;
34
using Microsoft.Extensions.DependencyInjection;
45
using Microsoft.Extensions.Logging;
56
using System;
67
using System.Net;
8+
using System.Security.Cryptography.X509Certificates;
79
using System.Threading;
810
using System.Threading.Tasks;
911

@@ -20,6 +22,8 @@ static void Main(string[] args)
2022
IIpcServiceHost host = new IpcServiceHostBuilder(services.BuildServiceProvider())
2123
.AddNamedPipeEndpoint<IComputingService>("computingEndpoint", "pipeName")
2224
.AddTcpEndpoint<ISystemService>("systemEndpoint", IPAddress.Loopback, 45684)
25+
.AddTcpEndpoint<ITestService>("secureEndpoint", IPAddress.Loopback, 44384, new X509Certificate2(@"Certificates\server.pfx", "password"))
26+
.AddTcpEndpoint<ITestService>("xorTranslatedEndpoint", IPAddress.Loopback, 45454, s => new XorStream(s))
2327
.Build();
2428

2529
var source = new CancellationTokenSource();
@@ -49,7 +53,8 @@ private static IServiceCollection ConfigureServices(IServiceCollection services)
4953
options.ThreadCount = 2;
5054
})
5155
.AddService<IComputingService, ComputingService>()
52-
.AddService<ISystemService, SystemService>();
56+
.AddService<ISystemService, SystemService>()
57+
.AddService<ITestService, TestService>();
5358
});
5459
}
5560
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using IpcServiceSample.ServiceContracts;
2+
using System;
3+
using System.Globalization;
4+
using System.Linq;
5+
using System.Threading;
6+
7+
namespace IpcServiceSample.ConsoleServer
8+
{
9+
public class TestService : ITestService
10+
{
11+
public Guid GenerateId()
12+
{
13+
return Guid.NewGuid();
14+
}
15+
}
16+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text;
5+
6+
namespace IpcServiceSample.ServiceContracts.Helpers
7+
{
8+
public class LoggingStream : Stream
9+
{
10+
private Stream _baseStream;
11+
private StreamWriter _log;
12+
13+
public LoggingStream(Stream stream, string logFile)
14+
{
15+
_baseStream = stream;
16+
_log = new StreamWriter(logFile) { AutoFlush = true };
17+
}
18+
19+
public override bool CanRead => _baseStream.CanRead;
20+
21+
public override bool CanSeek => _baseStream.CanSeek;
22+
23+
public override bool CanWrite => _baseStream.CanWrite;
24+
25+
public override long Length => _baseStream.Length;
26+
27+
public override long Position { get => _baseStream.Position; set => _baseStream.Position = value; }
28+
29+
private enum StreamOperation { Read, Write };
30+
31+
private void Log(StreamOperation direction, byte[] buffer, int offset, int count)
32+
{
33+
_log.WriteLine($"[{direction.ToString().ToUpper()}: {count}]");
34+
var dump = new StringBuilder();
35+
var hex = new StringBuilder();
36+
var ascii = new StringBuilder();
37+
for (int i = 0; i < count; i++)
38+
{
39+
if (i > 0 && i % 16 == 0)
40+
{
41+
dump.Append(hex.ToString().PadRight(48, ' '));
42+
dump.Append(' ', 3);
43+
dump.Append(ascii);
44+
dump.AppendLine();
45+
hex.Clear();
46+
ascii.Clear();
47+
}
48+
49+
byte c = buffer[offset + i];
50+
51+
hex.AppendFormat("{0:x2} ", c);
52+
53+
if (c >= 32 && c < 255)
54+
ascii.Append((char)c);
55+
else
56+
ascii.Append('.');
57+
}
58+
if (ascii.Length > 0)
59+
{
60+
dump.Append(hex.ToString().PadRight(48, ' '));
61+
dump.Append(' ', 3);
62+
dump.Append(ascii);
63+
dump.AppendLine();
64+
}
65+
66+
_log.WriteLine(dump.ToString());
67+
}
68+
69+
private void Log(string message)
70+
{
71+
_log.WriteLine(message);
72+
}
73+
74+
public override void Flush()
75+
{
76+
Log("[FLUSH]");
77+
_baseStream.Flush();
78+
}
79+
80+
public override int Read(byte[] buffer, int offset, int count)
81+
{
82+
int br = _baseStream.Read(buffer, offset, count);
83+
Log(StreamOperation.Read, buffer, offset, br);
84+
return br;
85+
}
86+
87+
public override long Seek(long offset, SeekOrigin origin)
88+
{
89+
Log($"[SEEK: {origin.ToString()}+{offset}]");
90+
return _baseStream.Seek(offset, origin);
91+
}
92+
93+
public override void SetLength(long value)
94+
{
95+
Log($"[SET LENGTH: {value}]");
96+
_baseStream.SetLength(value);
97+
}
98+
99+
public override void Write(byte[] buffer, int offset, int count)
100+
{
101+
_baseStream.Write(buffer, offset, count);
102+
Log(StreamOperation.Write, buffer, offset, count);
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)