Skip to content

Commit

Permalink
Add certificate password provider interface to support password prote…
Browse files Browse the repository at this point in the history
…cted pfx files. (#1291)

* Add a certificate password provider.
  • Loading branch information
mregen committed Feb 23, 2021
1 parent edec055 commit 46ada27
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>$(AppTargetFrameWork)</TargetFramework>
<AssemblyName>NetCoreReferenceServer</AssemblyName>
<AssemblyName>ConsoleReferenceServer</AssemblyName>
<OutputType>Exe</OutputType>
<PackageId>ConsoleReferenceServer</PackageId>
<Company>OPC Foundation</Company>
Expand Down
83 changes: 47 additions & 36 deletions Applications/ConsoleReferenceServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Quickstarts.ReferenceServer
{
/// <summary>
/// A dialog which asks for user input.
/// </summary>
public class ApplicationMessageDlg : IApplicationMessageDlg
{
private string m_message = string.Empty;
Expand Down Expand Up @@ -80,6 +84,9 @@ public override async Task<bool> ShowAsync()
}
}

/// <summary>
/// The error code why the server exited.
/// </summary>
public enum ExitCode : int
{
Ok = 0,
Expand All @@ -89,21 +96,26 @@ public enum ExitCode : int
ErrorInvalidCommandLine = 0x100
};

public class Program
/// <summary>
/// The program.
/// </summary>
public static class Program
{
public static int Main(string[] args)
public static async Task<int> Main(string[] args)
{
Console.WriteLine("{0} OPC UA Reference Server", Utils.IsRunningOnMono() ? "Mono" : ".Net Core");

// command line options
bool showHelp = false;
bool autoAccept = false;
bool console = false;
string password = null;

Mono.Options.OptionSet options = new Mono.Options.OptionSet {
{ "h|help", "show this message and exit", h => showHelp = h != null },
{ "a|autoaccept", "auto accept certificates (for testing only)", a => autoAccept = a != null },
{ "c|console", "log trace to console", c => console = c != null }
{ "c|console", "log trace to console", c => console = c != null },
{ "p|password=", "optional password for private key", (string p) => password = p }
};

try
Expand Down Expand Up @@ -131,10 +143,14 @@ public static int Main(string[] args)
return (int)ExitCode.ErrorInvalidCommandLine;
}

MyRefServer server = new MyRefServer(autoAccept, console);
server.Run();
var server = new MyRefServer() {
AutoAccept = autoAccept,
LogConsole = console,
Password = password
};
await server.Run().ConfigureAwait(false);

return (int)MyRefServer.ExitCode;
return (int)server.ExitCode;
}
}

Expand All @@ -143,33 +159,28 @@ public class MyRefServer
private ReferenceServer m_server;
private Task m_status;
private DateTime m_lastEventTime;
private bool m_autoAccept = false;
private bool m_logConsole = false;
private static ExitCode s_exitCode;
public bool LogConsole { get; set; } = false;
public bool AutoAccept { get; set; } = false;
public string Password { get; set; } = null;
public ExitCode ExitCode { get; private set; }

public MyRefServer(bool autoAccept, bool logConsole)
{
m_autoAccept = autoAccept;
m_logConsole = logConsole;
}

public void Run()
public async Task Run()
{
try
{
s_exitCode = ExitCode.ErrorServerNotStarted;
ConsoleSampleServer().Wait();
ExitCode = ExitCode.ErrorServerNotStarted;
await StartConsoleReferenceServerAsync().ConfigureAwait(false);
Console.WriteLine("Server started. Press Ctrl-C to exit...");
s_exitCode = ExitCode.ErrorServerRunning;
ExitCode = ExitCode.ErrorServerRunning;
}
catch (Exception ex)
{
Console.WriteLine("Exception: {0}", ex.Message);
s_exitCode = ExitCode.ErrorServerException;
ExitCode = ExitCode.ErrorServerException;
return;
}

ManualResetEvent quitEvent = new ManualResetEvent(false);
var quitEvent = new ManualResetEvent(false);
try
{
Console.CancelKeyPress += (sender, eArgs) => {
Expand Down Expand Up @@ -198,18 +209,16 @@ public void Run()
}
}

s_exitCode = ExitCode.Ok;
ExitCode = ExitCode.Ok;
}

public static ExitCode ExitCode { get => s_exitCode; }

private void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e)
{
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
{
if (m_autoAccept)
if (AutoAccept)
{
if (!m_logConsole)
if (!LogConsole)
{
Console.WriteLine("Accepted Certificate: {0}", e.Certificate.Subject);
}
Expand All @@ -218,27 +227,29 @@ private void CertificateValidator_CertificateValidation(CertificateValidator val
return;
}
}
if (!m_logConsole)
if (!LogConsole)
{
Console.WriteLine("Rejected Certificate: {0} {1}", e.Error, e.Certificate.Subject);
}
Utils.Trace(Utils.TraceMasks.Security, "Rejected Certificate: {0} {1}", e.Error, e.Certificate.Subject);
}

private async Task ConsoleSampleServer()
private async Task StartConsoleReferenceServerAsync()
{
ApplicationInstance.MessageDlg = new ApplicationMessageDlg();
CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(Password);
ApplicationInstance application = new ApplicationInstance {
ApplicationName = "Quickstart Reference Server",
ApplicationType = ApplicationType.Server,
ConfigSectionName = Utils.IsRunningOnMono() ? "Quickstarts.MonoReferenceServer" : "Quickstarts.ReferenceServer"
ConfigSectionName = Utils.IsRunningOnMono() ? "Quickstarts.MonoReferenceServer" : "Quickstarts.ReferenceServer",
CertificatePasswordProvider = PasswordProvider
};

// load the application configuration.
ApplicationConfiguration config = await application.LoadApplicationConfiguration(false).ConfigureAwait(false);

var loggerConfiguration = new Serilog.LoggerConfiguration();
if (m_logConsole)
if (LogConsole)
{
loggerConfiguration.WriteTo.Console(restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Warning);
}
Expand Down Expand Up @@ -275,7 +286,7 @@ private async Task ConsoleSampleServer()
}

// start the status thread
m_status = Task.Run(new Action(StatusThread));
m_status = Task.Run(new Action(StatusThreadAsync));

// print notification on session events
m_server.CurrentInstance.SessionManager.SessionActivated += EventStatus;
Expand All @@ -294,24 +305,24 @@ private void PrintSessionStatus(Session session, string reason, bool lastContact
lock (session.DiagnosticsLock)
{
StringBuilder item = new StringBuilder();
item.Append(string.Format("{0,9}:{1,20}:", reason, session.SessionDiagnostics.SessionName));
item.AppendFormat("{0,9}:{1,20}:", reason, session.SessionDiagnostics.SessionName);
if (lastContact)
{
item.Append(string.Format("Last Event:{0:HH:mm:ss}", session.SessionDiagnostics.ClientLastContactTime.ToLocalTime()));
item.AppendFormat("Last Event:{0:HH:mm:ss}", session.SessionDiagnostics.ClientLastContactTime.ToLocalTime());
}
else
{
if (session.Identity != null)
{
item.Append(string.Format(":{0,20}", session.Identity.DisplayName));
item.AppendFormat(":{0,20}", session.Identity.DisplayName);
}
item.Append(string.Format(":{0}", session.Id));
item.AppendFormat(":{0}", session.Id);
}
Console.WriteLine(item.ToString());
}
}

private async void StatusThread()
private async void StatusThreadAsync()
{
while (m_server != null)
{
Expand Down
53 changes: 32 additions & 21 deletions Libraries/Opc.Ua.Configuration/ApplicationInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ public ApplicationConfiguration ApplicationConfiguration
/// Get or set the message dialog.
/// </summary>
public static IApplicationMessageDlg MessageDlg { get; set; }

/// <summary>
/// Get or set the certificate password provider.
/// </summary>
public ICertificatePasswordProvider CertificatePasswordProvider { get; set; }
#endregion

#region Public Methods
Expand Down Expand Up @@ -177,7 +182,7 @@ public async Task Start(ServerBase server)

if (m_applicationConfiguration == null)
{
await LoadApplicationConfiguration(false);
await LoadApplicationConfiguration(false).ConfigureAwait(false);
}

if (m_applicationConfiguration.CertificateValidator != null)
Expand Down Expand Up @@ -227,7 +232,8 @@ public void Stop()
string filePath,
ApplicationType applicationType,
Type configurationType,
bool applyTraceSettings)
bool applyTraceSettings,
ICertificatePasswordProvider certificatePasswordProvider = null)
{
Utils.Trace(Utils.TraceMasks.Information, "Loading application configuration file. {0}", filePath);

Expand All @@ -238,7 +244,9 @@ public void Stop()
new System.IO.FileInfo(filePath),
applicationType,
configurationType,
applyTraceSettings);
applyTraceSettings,
certificatePasswordProvider)
.ConfigureAwait(false);

if (configuration == null)
{
Expand All @@ -253,7 +261,7 @@ public void Stop()
if (!silent && MessageDlg != null)
{
MessageDlg.Message("Load Application Configuration: " + e.Message);
await MessageDlg.ShowAsync();
await MessageDlg.ShowAsync().ConfigureAwait(false);
}

Utils.Trace(e, "Could not load configuration file. {0}", filePath);
Expand All @@ -266,7 +274,9 @@ public void Stop()
/// </summary>
public async Task<ApplicationConfiguration> LoadApplicationConfiguration(string filePath, bool silent)
{
ApplicationConfiguration configuration = await LoadAppConfig(silent, filePath, ApplicationType, ConfigurationType, true);
ApplicationConfiguration configuration = await LoadAppConfig(
silent, filePath, ApplicationType, ConfigurationType, true, CertificatePasswordProvider)
.ConfigureAwait(false);

if (configuration == null)
{
Expand All @@ -284,7 +294,9 @@ public async Task<ApplicationConfiguration> LoadApplicationConfiguration(string
public async Task<ApplicationConfiguration> LoadApplicationConfiguration(bool silent)
{
string filePath = ApplicationConfiguration.GetFilePathFromAppConfig(ConfigSectionName);
ApplicationConfiguration configuration = await LoadAppConfig(silent, filePath, ApplicationType, ConfigurationType, true);
ApplicationConfiguration configuration = await LoadAppConfig(
silent, filePath, ApplicationType, ConfigurationType, true, CertificatePasswordProvider)
.ConfigureAwait(false);

if (configuration == null)
{
Expand Down Expand Up @@ -320,15 +332,12 @@ public async Task<ApplicationConfiguration> LoadApplicationConfiguration(bool si
ushort lifeTimeInMonths)
{
Utils.Trace(Utils.TraceMasks.Information, "Checking application instance certificate.");

ApplicationConfiguration configuration = null;

if (m_applicationConfiguration == null)
{
await LoadApplicationConfiguration(silent);
await LoadApplicationConfiguration(silent).ConfigureAwait(false);
}

configuration = m_applicationConfiguration;
ApplicationConfiguration configuration = m_applicationConfiguration;
bool certificateValid = false;

// find the existing certificate.
Expand All @@ -340,17 +349,17 @@ public async Task<ApplicationConfiguration> LoadApplicationConfiguration(bool si
"Configuration file does not specify a certificate.");
}

X509Certificate2 certificate = await id.Find(true);
X509Certificate2 certificate = await id.Find(true).ConfigureAwait(false);

// check that it is ok.
if (certificate != null)
{
certificateValid = await CheckApplicationInstanceCertificate(configuration, certificate, silent, minimumKeySize);
certificateValid = await CheckApplicationInstanceCertificate(configuration, certificate, silent, minimumKeySize).ConfigureAwait(false);
}
else
{
// check for missing private key.
certificate = await id.Find(false);
certificate = await id.Find(false).ConfigureAwait(false);

if (certificate != null)
{
Expand All @@ -367,7 +376,7 @@ public async Task<ApplicationConfiguration> LoadApplicationConfiguration(bool si
id2.StoreType = id.StoreType;
id2.StorePath = id.StorePath;
id2.SubjectName = id.SubjectName;
certificate = await id2.Find(true);
certificate = await id2.Find(true).ConfigureAwait(false);
}

if (certificate != null)
Expand Down Expand Up @@ -641,13 +650,15 @@ ushort lifeTimeInMonths
serverDomainNames)
.SetLifeTime(lifeTimeInMonths)
.SetRSAKeySize(keySize)
.CreateForRSA()
.AddToStore(
id.StoreType,
id.StorePath
);
.CreateForRSA();

id.Certificate = certificate;
var passwordProvider = configuration.SecurityConfiguration.CertificatePasswordProvider;
certificate.AddToStore(
id.StoreType,
id.StorePath,
passwordProvider?.GetPassword(id)
);

// ensure the certificate is trusted.
if (configuration.SecurityConfiguration.AddAppCertToTrustedStore)
Expand All @@ -660,7 +671,7 @@ ushort lifeTimeInMonths
Utils.Trace(Utils.TraceMasks.Information, "Certificate created. Thumbprint={0}", certificate.Thumbprint);

// reload the certificate from disk.
await configuration.SecurityConfiguration.ApplicationCertificate.LoadPrivateKey(null);
await configuration.SecurityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(passwordProvider);

return certificate;
}
Expand Down

0 comments on commit 46ada27

Please sign in to comment.