mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-22 09:37:12 +08:00
620 lines
23 KiB
C#
620 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Windows.Forms;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using Opc.Ua.Client;
|
|
using Opc.Ua.Gds;
|
|
|
|
namespace Opc.Ua.GdsClient
|
|
{
|
|
public partial class ApplicationCertificateControl : UserControl
|
|
{
|
|
public ApplicationCertificateControl()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private GlobalDiscoveryClientConfiguration m_configuration;
|
|
private GlobalDiscoveryServer m_gds;
|
|
private PushConfigurationServer m_server;
|
|
private RegisteredApplication m_application;
|
|
private X509Certificate2 m_certificate;
|
|
private string m_certificatePassword;
|
|
|
|
[DefaultValue(false)]
|
|
public bool RequestNewPrivateKey { get; set; }
|
|
|
|
public async void Initialize(
|
|
GlobalDiscoveryClientConfiguration configuration,
|
|
GlobalDiscoveryServer gds,
|
|
PushConfigurationServer server,
|
|
RegisteredApplication application,
|
|
bool isHttps)
|
|
{
|
|
m_configuration = configuration;
|
|
m_gds = gds;
|
|
m_server = server;
|
|
m_application = application;
|
|
m_certificate = null;
|
|
m_certificatePassword = null;
|
|
|
|
CertificateRequestTimer.Enabled = false;
|
|
RequestProgressLabel.Visible = false;
|
|
ApplyChangesButton.Enabled = false;
|
|
|
|
CertificateControl.ShowNothing();
|
|
|
|
X509Certificate2 certificate = null;
|
|
|
|
if (!isHttps)
|
|
{
|
|
if (server.Endpoint != null && server.Endpoint.Description.ServerCertificate != null)
|
|
{
|
|
certificate = new X509Certificate2(server.Endpoint.Description.ServerCertificate);
|
|
}
|
|
else if (application != null)
|
|
{
|
|
if (!String.IsNullOrEmpty(application.CertificatePublicKeyPath))
|
|
{
|
|
string file = Utils.GetAbsoluteFilePath(application.CertificatePublicKeyPath, true, false, false);
|
|
|
|
if (file != null)
|
|
{
|
|
certificate = new X509Certificate2(file);
|
|
}
|
|
}
|
|
else if (!String.IsNullOrEmpty(application.CertificateStorePath))
|
|
{
|
|
CertificateIdentifier id = new CertificateIdentifier();
|
|
|
|
id.StorePath = application.CertificateStorePath;
|
|
id.StoreType = CertificateStoreIdentifier.DetermineStoreType(id.StoreType);
|
|
id.SubjectName = application.CertificateSubjectName.Replace("localhost", System.Net.Dns.GetHostName());
|
|
|
|
certificate = await id.Find(true);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (application != null)
|
|
{
|
|
if (!String.IsNullOrEmpty(application.HttpsCertificatePublicKeyPath))
|
|
{
|
|
string file = Utils.GetAbsoluteFilePath(application.HttpsCertificatePublicKeyPath, true, false, false);
|
|
|
|
if (file != null)
|
|
{
|
|
certificate = new X509Certificate2(file);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (string disoveryUrl in application.DiscoveryUrl)
|
|
{
|
|
if (Uri.IsWellFormedUriString(disoveryUrl, UriKind.Absolute))
|
|
{
|
|
Uri url = new Uri(disoveryUrl);
|
|
|
|
CertificateIdentifier id = new CertificateIdentifier()
|
|
{
|
|
StoreType = CertificateStoreType.Windows,
|
|
StorePath = "LocalMachine\\My",
|
|
SubjectName = "CN=" + url.DnsSafeHost
|
|
};
|
|
|
|
certificate = await id.Find();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (certificate != null)
|
|
{
|
|
try
|
|
{
|
|
CertificateControl.Tag = certificate.Thumbprint;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
MessageBox.Show(
|
|
Parent,
|
|
"The certificate does not appear to be valid. Please check configuration settings.",
|
|
Parent.Text,
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Error);
|
|
|
|
certificate = null;
|
|
}
|
|
}
|
|
|
|
WarningLabel.Visible = certificate == null;
|
|
|
|
if (certificate != null)
|
|
{
|
|
m_certificate = certificate;
|
|
CertificateControl.ShowValue(null, "Application Certificate", new CertificateWrapper() { Certificate = certificate }, true);
|
|
}
|
|
}
|
|
|
|
private string GetPrivateKeyFormat()
|
|
{
|
|
string privateKeyFormat = "PFX";
|
|
|
|
if (m_application.RegistrationType != RegistrationType.ServerPush)
|
|
{
|
|
if (!String.IsNullOrEmpty(m_application.CertificatePrivateKeyPath))
|
|
{
|
|
if (m_application.CertificatePrivateKeyPath.EndsWith("PEM", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
privateKeyFormat = "PEM";
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
string[] privateKeyFormats = m_server.GetSupportedKeyFormats();
|
|
|
|
if (privateKeyFormats == null || !privateKeyFormats.Contains("PFX"))
|
|
{
|
|
privateKeyFormat = "PEM";
|
|
}
|
|
}
|
|
|
|
return privateKeyFormat;
|
|
}
|
|
|
|
private string[] GetDomainNames()
|
|
{
|
|
List<string> domainNames = new List<string>();
|
|
|
|
if (m_application.DiscoveryUrl != null)
|
|
{
|
|
foreach (var discoveryUrl in m_application.DiscoveryUrl)
|
|
{
|
|
if (Uri.IsWellFormedUriString(discoveryUrl, UriKind.Absolute))
|
|
{
|
|
string name = new Uri(discoveryUrl).DnsSafeHost;
|
|
|
|
if (name == "localhost")
|
|
{
|
|
name = System.Net.Dns.GetHostName();
|
|
}
|
|
|
|
bool found = false;
|
|
|
|
foreach (var domainName in domainNames)
|
|
{
|
|
if (String.Compare(domainName, name, StringComparison.OrdinalIgnoreCase) == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
domainNames.Add(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (domainNames != null && domainNames.Count > 0)
|
|
{
|
|
return domainNames.ToArray();
|
|
}
|
|
|
|
if (m_server != null && m_server.Endpoint != null)
|
|
{
|
|
domainNames.Add(m_server.Endpoint.EndpointUrl.DnsSafeHost);
|
|
return domainNames.ToArray();
|
|
}
|
|
|
|
if (m_certificate != null)
|
|
{
|
|
var names = Utils.GetDomainsFromCertficate(m_certificate);
|
|
|
|
if (names != null && names.Count > 0)
|
|
{
|
|
domainNames.AddRange(names);
|
|
return domainNames.ToArray();
|
|
}
|
|
|
|
var fields = Utils.ParseDistinguishedName(m_certificate.Subject);
|
|
|
|
string name = null;
|
|
|
|
foreach (var field in fields)
|
|
{
|
|
if (field.StartsWith("DC="))
|
|
{
|
|
if (name != null)
|
|
{
|
|
name += ".";
|
|
}
|
|
|
|
name += field.Substring(3);
|
|
}
|
|
}
|
|
|
|
if (names != null)
|
|
{
|
|
domainNames.AddRange(names);
|
|
return domainNames.ToArray();
|
|
}
|
|
}
|
|
|
|
return new string[] { System.Net.Dns.GetHostName() };
|
|
}
|
|
|
|
private string GetSubjectName(string[] domainNames)
|
|
{
|
|
if (m_certificate == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (domainNames != null && domainNames.Length > 0)
|
|
{
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
|
var fields = Utils.ParseDistinguishedName(m_certificate.Subject);
|
|
|
|
foreach (var field in fields)
|
|
{
|
|
if (field.StartsWith("DC="))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (buffer.Length > 0)
|
|
{
|
|
buffer.Append("/");
|
|
}
|
|
|
|
buffer.Append(field);
|
|
}
|
|
|
|
if (buffer.Length > 0)
|
|
{
|
|
buffer.Append("/DC=");
|
|
}
|
|
|
|
buffer.Append(domainNames[0]);
|
|
|
|
return buffer.ToString();
|
|
}
|
|
|
|
return m_certificate.Subject;
|
|
}
|
|
|
|
private void RequestNewButton_Click(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
NodeId requestId = null;
|
|
|
|
bool newPrivateKeyRequired = false;
|
|
|
|
if (!String.IsNullOrEmpty(m_application.CertificatePrivateKeyPath))
|
|
{
|
|
string path = Utils.GetAbsoluteFilePath(m_application.CertificatePrivateKeyPath, true, false, false);
|
|
newPrivateKeyRequired = path == null;
|
|
}
|
|
else if (!String.IsNullOrEmpty(m_application.CertificateStorePath))
|
|
{
|
|
CertificateIdentifier id = new CertificateIdentifier();
|
|
|
|
id.StoreType = CertificateStoreIdentifier.DetermineStoreType(m_application.CertificateStorePath);
|
|
id.StorePath = m_application.CertificateStorePath;
|
|
id.SubjectName = m_application.CertificateSubjectName.Replace("localhost", System.Net.Dns.GetHostName());
|
|
|
|
newPrivateKeyRequired = id.Find(true) == null;
|
|
}
|
|
else if (!String.IsNullOrEmpty(m_application.ServerUrl))
|
|
{
|
|
newPrivateKeyRequired = true;
|
|
}
|
|
|
|
byte[] certificateRequest = null;
|
|
|
|
if (m_application.RegistrationType == RegistrationType.ServerPush)
|
|
{
|
|
byte[] nonce = new byte[32];
|
|
System.Security.Cryptography.RandomNumberGenerator.Create().GetBytes(nonce);
|
|
certificateRequest = m_server.CreateCertificateRequest(null, null, null, false, nonce);
|
|
newPrivateKeyRequired = false;
|
|
|
|
if (m_server.Endpoint != null && m_server.Endpoint.Description.ServerCertificate != null)
|
|
{
|
|
m_certificate = new X509Certificate2(m_server.Endpoint.Description.ServerCertificate);
|
|
}
|
|
}
|
|
|
|
if (newPrivateKeyRequired || RequestNewPrivateKey || m_certificate == null)
|
|
{
|
|
m_certificatePassword = null; // Guid.NewGuid().ToString();
|
|
|
|
string privateKeyFormat = GetPrivateKeyFormat();
|
|
string[] domainNames = GetDomainNames();
|
|
string subjectName = GetSubjectName(domainNames);
|
|
|
|
requestId = m_gds.StartNewKeyPairRequest(
|
|
m_application.ApplicationId,
|
|
null,
|
|
null,
|
|
subjectName,
|
|
domainNames,
|
|
privateKeyFormat,
|
|
m_certificatePassword);
|
|
}
|
|
else
|
|
{
|
|
if (certificateRequest == null)
|
|
{
|
|
var certificate = m_certificate;
|
|
byte[] privateKey = null;
|
|
bool isPemKey = false;
|
|
|
|
if (!certificate.HasPrivateKey)
|
|
{
|
|
if (!String.IsNullOrEmpty(m_application.CertificatePrivateKeyPath))
|
|
{
|
|
string path = Utils.GetAbsoluteFilePath(m_application.CertificatePrivateKeyPath, true, false, false);
|
|
|
|
if (path != null)
|
|
{
|
|
privateKey = File.ReadAllBytes(path);
|
|
isPemKey = path.EndsWith("PEM", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|
|
}
|
|
|
|
certificateRequest = CertificateAuthority.CreateRequest(
|
|
certificate,
|
|
privateKey,
|
|
isPemKey,
|
|
null,
|
|
null,
|
|
null,
|
|
null,
|
|
256);
|
|
}
|
|
|
|
requestId = m_gds.StartSigningRequest(m_application.ApplicationId, null, null, certificateRequest);
|
|
}
|
|
|
|
m_application.CertificateRequestId = requestId.ToString();
|
|
CertificateRequestTimer.Enabled = true;
|
|
RequestProgressLabel.Visible = true;
|
|
WarningLabel.Visible = false;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
MessageBox.Show(Parent.Text + ": " + exception.Message);
|
|
}
|
|
}
|
|
|
|
private async void CertificateRequestTimer_Tick(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
NodeId requestId = NodeId.Parse(m_application.CertificateRequestId);
|
|
|
|
byte[] privateKey = null;
|
|
byte[][] issuerCertificates = null;
|
|
|
|
byte[] certificate = m_gds.FinishRequest(
|
|
m_application.ApplicationId,
|
|
requestId,
|
|
out privateKey,
|
|
out issuerCertificates);
|
|
|
|
if (certificate == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CertificateRequestTimer.Enabled = false;
|
|
RequestProgressLabel.Visible = false;
|
|
|
|
if (m_application.RegistrationType != RegistrationType.ServerPush)
|
|
{
|
|
// save public key.
|
|
if (!String.IsNullOrEmpty(m_application.CertificatePublicKeyPath))
|
|
{
|
|
string file = Utils.GetAbsoluteFilePath(m_application.CertificatePublicKeyPath, true, false, true);
|
|
File.WriteAllBytes(file, certificate);
|
|
}
|
|
|
|
// check if the private was re-used.
|
|
if (privateKey == null || privateKey.Length == 0)
|
|
{
|
|
if (!String.IsNullOrEmpty(m_application.CertificatePrivateKeyPath))
|
|
{
|
|
string path = Utils.GetAbsoluteFilePath(m_application.CertificatePrivateKeyPath, true, true, true);
|
|
|
|
if (path != null)
|
|
{
|
|
if (!m_application.CertificatePrivateKeyPath.EndsWith("PEM", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var x509 = new X509Certificate2(certificate);
|
|
var oldPfx = new X509Certificate2(path, (string)null, X509KeyStorageFlags.Exportable);
|
|
var newPfx = CertificateAuthority.Replace(x509, oldPfx);
|
|
var bytes = newPfx.Export(X509ContentType.Pfx);
|
|
File.WriteAllBytes(path, bytes);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!String.IsNullOrEmpty(m_application.CertificateStorePath) && !String.IsNullOrEmpty(m_application.CertificateSubjectName))
|
|
{
|
|
var x509 = new X509Certificate2(certificate);
|
|
|
|
var cid = new CertificateIdentifier()
|
|
{
|
|
StorePath = m_application.CertificateStorePath,
|
|
SubjectName = m_application.CertificateSubjectName.Replace("localhost", System.Net.Dns.GetHostName())
|
|
};
|
|
|
|
var oldPfx = await cid.Find(true);
|
|
|
|
if (oldPfx != null)
|
|
{
|
|
var newPfx = CertificateAuthority.Replace(x509, oldPfx);
|
|
|
|
using (var store = CertificateStoreIdentifier.OpenStore(m_application.CertificateStorePath))
|
|
{
|
|
await store.Delete(oldPfx.Thumbprint);
|
|
await store.Add(newPfx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// save private key.
|
|
else
|
|
{
|
|
if (!String.IsNullOrEmpty(m_application.CertificatePrivateKeyPath))
|
|
{
|
|
string path = Utils.GetAbsoluteFilePath(m_application.CertificatePrivateKeyPath, true, true, true);
|
|
|
|
if (path != null)
|
|
{
|
|
File.WriteAllBytes(path, privateKey);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!String.IsNullOrEmpty(m_application.CertificateStorePath) && !String.IsNullOrEmpty(m_application.CertificateSubjectName))
|
|
{
|
|
var cid = new CertificateIdentifier()
|
|
{
|
|
StorePath = m_application.CertificateStorePath,
|
|
SubjectName = m_application.CertificateSubjectName
|
|
};
|
|
|
|
var oldCertificate = await cid.Find();
|
|
|
|
using (var store = CertificateStoreIdentifier.OpenStore(m_application.CertificateStorePath))
|
|
{
|
|
if (oldCertificate != null)
|
|
{
|
|
await store.Delete(oldCertificate.Thumbprint);
|
|
}
|
|
|
|
var x509 = new X509Certificate2(privateKey, new System.Security.SecureString(), X509KeyStorageFlags.Exportable);
|
|
x509 = CertificateFactory.Load(x509, true);
|
|
await store.Add(x509);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update trust list.
|
|
if (!String.IsNullOrEmpty(m_application.TrustListStorePath))
|
|
{
|
|
using (ICertificateStore store = CertificateStoreIdentifier.OpenStore(m_application.TrustListStorePath))
|
|
{
|
|
foreach (var issuerCertificate in issuerCertificates)
|
|
{
|
|
var x509 = new X509Certificate2(issuerCertificate);
|
|
|
|
if (store.FindByThumbprint(x509.Thumbprint) == null)
|
|
{
|
|
await store.Add(new X509Certificate2(issuerCertificate));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (privateKey != null && privateKey.Length > 0)
|
|
{
|
|
var x509 = new X509Certificate2(privateKey, m_certificatePassword, X509KeyStorageFlags.Exportable);
|
|
privateKey = x509.Export(X509ContentType.Pfx);
|
|
}
|
|
|
|
bool applyChanges = m_server.UpdateCertificate(null, null, certificate, GetPrivateKeyFormat(), privateKey, issuerCertificates);
|
|
|
|
if (applyChanges)
|
|
{
|
|
MessageBox.Show(
|
|
Parent,
|
|
"The certificate was updated, however, the apply changes command must be sent before the server will use the new certificate.",
|
|
Parent.Text,
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Information);
|
|
|
|
ApplyChangesButton.Enabled = true;
|
|
}
|
|
}
|
|
|
|
m_certificate = new X509Certificate2(certificate);
|
|
CertificateControl.ShowValue(null, "Application Certificate", new CertificateWrapper() { Certificate = m_certificate }, true);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
var sre = exception as ServiceResultException;
|
|
|
|
if (sre != null && sre.StatusCode == StatusCodes.BadNothingToDo)
|
|
{
|
|
return;
|
|
}
|
|
|
|
MessageBox.Show(Parent.Text + ": " + exception.Message);
|
|
CertificateRequestTimer.Enabled = false;
|
|
}
|
|
}
|
|
|
|
private void ApplyChangesButton_Click(object sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
m_server.ApplyChanges();
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
var se = exception as ServiceResultException;
|
|
|
|
if (se == null || se.StatusCode != StatusCodes.BadServerHalted)
|
|
{
|
|
MessageBox.Show(Parent.Text + ": " + exception.Message);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
m_server.Disconnect();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignore.
|
|
}
|
|
}
|
|
|
|
private void Button_MouseEnter(object sender, EventArgs e)
|
|
{
|
|
((Control)sender).BackColor = Color.CornflowerBlue;
|
|
}
|
|
|
|
private void Button_MouseLeave(object sender, EventArgs e)
|
|
{
|
|
((Control)sender).BackColor = Color.MidnightBlue;
|
|
}
|
|
|
|
}
|
|
}
|