Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Capability for Dotnetty to use Thumbprint for SSL #3629

Merged
merged 2 commits into from
Oct 25, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions src/core/Akka.Remote.Tests/Transport/DotNettySslSupportSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//-----------------------------------------------------------------------

using System;
using System.Security.Cryptography.X509Certificates;
using Akka.Actor;
using Akka.Configuration;
using Akka.TestKit;
Expand Down Expand Up @@ -51,6 +52,34 @@ private static Config TestConfig(string certPath, string password)
}");
}

private static Config TestThumbprintConfig(string thumbPrint)
{
var config = ConfigurationFactory.ParseString(@"
akka {
loglevel = DEBUG
actor.provider = ""Akka.Remote.RemoteActorRefProvider,Akka.Remote""
remote {
dot-netty.tcp {
port = 0
hostname = ""127.0.0.1""
enable-ssl = ""true""
log-transport = true
}
}
}");
return false
? config
: config.WithFallback(@"akka.remote.dot-netty.tcp.ssl {
suppress-validation = ""true""
certificate {
use-thumprint-over-file = true
thumbprint = """ + thumbPrint + @"""
store-location = ""current-user""
store-name = ""My""
}
}");
}

private ActorSystem sys2;
private Address address1;
private Address address2;
Expand All @@ -69,12 +98,29 @@ private void Setup(string certPath, string password)
echoPath = new RootActorPath(address2) / "user" / "echo";
}

private void SetupThumbprint(string certPath, string password)
{
InstallCert();
sys2 = ActorSystem.Create("sys2", TestThumbprintConfig(Thumbprint));
InitializeLogger(sys2);

var echo = sys2.ActorOf(Props.Create<Echo>(), "echo");

address1 = RARP.For(Sys).Provider.DefaultAddress;
address2 = RARP.For(sys2).Provider.DefaultAddress;
echoPath = new RootActorPath(address2) / "user" / "echo";
}

#endregion

// WARNING: YOU NEED TO RUN TEST IN ADMIN MODE IN ORDER TO ADD/REMOVE CERTIFICATES TO CERT STORE!
public DotNettySslSupportSpec(ITestOutputHelper output) : base(TestConfig(ValidCertPath, Password), output)
{
}

private string Thumbprint { get; set; }



[Fact]
public void Secure_transport_should_be_possible_between_systems_sharing_the_same_certificate()
Expand All @@ -89,6 +135,25 @@ public void Secure_transport_should_be_possible_between_systems_sharing_the_same
probe.ExpectMsg("hello");
}

[Fact]
public void Secure_transport_should_be_possible_between_systems_using_thumbprint()
{
// skip this test due to linux/mono certificate issues
if (IsMono) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. I think we would much rather have this test just run on mono/linux. And have us manually suppress the test in our CI env. Then let it silently complete as if everything is ok. @Aaronontheweb your thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Danthar unfortunately, skipping specific tests in some environments from within our build script is really tricky. We do this type of short-circuiting elsewhere inside the Akka.Remote test suite for the same reason.

While I agree it's not 100% clean, it's probably the best route for getting the job done.

Danthar marked this conversation as resolved.
Show resolved Hide resolved
try
{
SetupThumbprint(ValidCertPath, Password);

var probe = CreateTestProbe();
Sys.ActorSelection(echoPath).Tell("hello", probe.Ref);
probe.ExpectMsg("hello");
}
finally
{
RemoveCert();
}
}

[Fact]
public void Secure_transport_should_NOT_be_possible_between_systems_using_SSL_and_one_not_using_it()
{
Expand All @@ -112,8 +177,55 @@ protected override void Dispose(bool disposing)
{
Shutdown(sys2, TimeSpan.FromSeconds(3));
}

}

private void InstallCert()
{
var store = new X509Store("My", StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadWrite);


var cert = new X509Certificate2(ValidCertPath, Password);
Thumbprint = cert.Thumbprint;
store.Add(cert);
}
finally
{
#if NET452 //netstandard 1.6 doesn't have close on store
store.Close();
#else
#endif
}

}


private void RemoveCert()
{
var store = new X509Store("My", StoreLocation.CurrentUser);
try
{



store.Open(OpenFlags.ReadWrite);
var certs = store.Certificates.Find(X509FindType.FindByThumbprint, Thumbprint, false);
if (certs.Count > 0)
{
store.Remove(certs[0]);
}
}
finally
{
#if NET452 //NetStandard1.6 doesn't have close on store.
store.Close();
#else
#endif
}
}
public class Echo : ReceiveActor
{
public Echo()
Expand Down
19 changes: 19 additions & 0 deletions src/core/Akka.Remote/Configuration/Remote.conf
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,25 @@ akka {
# Available flags include:
# default-key-set | exportable | machine-key-set | persist-key-set | user-key-set | user-protected
# flags = [ "default-key-set" ]

# To use a Thumbprint instead of a file, set this to true
# And specify a thumprint and it's storage location below
use-thumprint-over-file = false
to11mtm marked this conversation as resolved.
Show resolved Hide resolved

# Valid Thumprint required (if use-thumbprint-over-file is true)
# A typical thumbprint is a format similar to: "45df32e258c92a7abf6c112e54912ab15bbb9eb0"
# On Windows machines, The thumprint for an installed certificate can be located
# By using certlm.msc and opening the certificate under the 'Details' tab.
thumpbrint = ""
to11mtm marked this conversation as resolved.
Show resolved Hide resolved

# The Store name. Under windows The most common option is "My", which indicates the personal store.
# See System.Security.Cryptography.X509Certificates.StoreName for other common values.
store-name = ""

# Valid options : local-machine or current-user
# current-user indicates a certificate stored under the user's account
# local-machine indicates a certificate stored at an operating system level (potentially shared by users)
store-location = "current-user"
}
suppress-validation = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,38 @@ public static SslSettings Create(Config config)
{
if (config == null) throw new ArgumentNullException(nameof(config), "DotNetty SSL HOCON config was not found (default path: `akka.remote.dot-netty.Ssl`)");

var flagsRaw = config.GetStringList("certificate.flags");
var flags = flagsRaw.Aggregate(X509KeyStorageFlags.DefaultKeySet, (flag, str) => flag | ParseKeyStorageFlag(str));

return new SslSettings(
certificatePath: config.GetString("certificate.path"),
certificatePassword: config.GetString("certificate.password"),
flags: flags,
suppressValidation: config.GetBoolean("suppress-validation", false));


if (config.GetBoolean("certificate.use-thumprint-over-file", false))
{
return new SslSettings(config.GetString("certificate.thumbprint"),
config.GetString("certificate.store-name"),
ParseStoreLocationName(config.GetString("certificate.store-location")),
config.GetBoolean("suppress-validation", false));

}
else
{
var flagsRaw = config.GetStringList("certificate.flags");
var flags = flagsRaw.Aggregate(X509KeyStorageFlags.DefaultKeySet, (flag, str) => flag | ParseKeyStorageFlag(str));

return new SslSettings(
certificatePath: config.GetString("certificate.path"),
certificatePassword: config.GetString("certificate.password"),
flags: flags,
suppressValidation: config.GetBoolean("suppress-validation", false));
}

}

private static StoreLocation ParseStoreLocationName(string str)
{
switch (str)
{
case "local-machine": return StoreLocation.LocalMachine;
case "current-user": return StoreLocation.CurrentUser;
default: throw new ArgumentException($"Unrecognized flag in X509 certificate config [{str}]. Available flags: local-machine | current-user");
}
}

private static X509KeyStorageFlags ParseKeyStorageFlag(string str)
Expand Down Expand Up @@ -296,6 +320,36 @@ public SslSettings()
SuppressValidation = false;
}

public SslSettings(string certificateThumbprint, string storeName, StoreLocation storeLocation, bool suppressValidation)
{

var store = new X509Store(storeName, storeLocation);
try
{
store.Open(OpenFlags.ReadOnly);


var find = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, !suppressValidation);
if (find.Count == 0)
{
throw new ArgumentException(
"Could not find Valid certificate for thumbprint (by default it can be found under `akka.remote.dot-netty.tcp.ssl.certificate.thumpbrint`. Also check akka.remote.dot-netty.tcp.ssl.certificate.store-name and akka.remote.dot-netty.tcp.ssl.certificate.store-location)");
}

Certificate = find[0];
SuppressValidation = suppressValidation;
}
finally
{
#if NET45 //netstandard1.6 doesn't have close on store.
store.Close();
#else
#endif

}

}

public SslSettings(string certificatePath, string certificatePassword, X509KeyStorageFlags flags, bool suppressValidation)
{
if (string.IsNullOrEmpty(certificatePath))
Expand Down