in src/Azure.IIoT.OpcUa.Publisher/src/Stack/Services/OpcUaApplication.cs [479:637]
private async Task<ApplicationConfiguration> BuildAsync()
{
Debug.Assert(!string.IsNullOrWhiteSpace(_options.Value.ApplicationName));
var appInstance = new ApplicationInstance
{
ApplicationName = _options.Value.ApplicationName,
ApplicationType = Opc.Ua.ApplicationType.Client
};
Exception innerException = new InvalidConfigurationException("Missing network.");
for (var attempt = 0; attempt < 60; attempt++)
{
// wait with the configuration until network is up
if (!NetworkInterface.GetIsNetworkAvailable())
{
_logger.LogWarning("Network not available...");
await Task.Delay(3000).ConfigureAwait(false);
continue;
}
var hostname = !string.IsNullOrWhiteSpace(_identity) ?
Uri.CheckHostName(_identity) != UriHostNameType.Unknown ? _identity :
#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms
new IPAddress(SHA1.HashData(Encoding.UTF8.GetBytes(_identity))
.AsSpan()[..16], 0).ToString() :
#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms
Utils.GetHostName();
var applicationUri = _options.Value.ApplicationUri;
if (_options.Value.Security.TryUseConfigurationFromExistingAppCert == true)
{
(applicationUri, appInstance.ApplicationName, hostname) =
await UpdateFromExistingCertificateAsync(
applicationUri, appInstance.ApplicationName, hostname,
_options.Value.Security).ConfigureAwait(false);
}
if (applicationUri == null)
{
applicationUri = $"urn:{hostname}:opc-publisher";
}
else
{
applicationUri = applicationUri.Replace("urn:localhost", $"urn:{hostname}",
StringComparison.Ordinal);
}
var appBuilder = appInstance.Build(applicationUri, _options.Value.ProductUri)
.SetTransportQuotas(ToTransportQuotas(_options.Value.Quotas))
.AsClient();
try
{
var appConfig = await BuildSecurityConfigurationAsync(appBuilder,
_options.Value.Security, appInstance.ApplicationConfiguration, hostname)
.ConfigureAwait(false);
var ownCertificate =
appConfig.SecurityConfiguration.ApplicationCertificate.Certificate;
if (ownCertificate == null)
{
_logger.LogInformation(
"No application own certificate found. Creating a self-signed " +
"own certificate valid since yesterday for {DefaultLifeTime} months, " +
"with a {DefaultKeySize} bit key and {DefaultHashSize} bit hash.",
CertificateFactory.DefaultLifeTime,
CertificateFactory.DefaultKeySize,
CertificateFactory.DefaultHashSize);
}
else
{
_logger.LogInformation(
"Own certificate Subject '{Subject}' (Thumbprint: {Tthumbprint}) loaded.",
ownCertificate.Subject, ownCertificate.Thumbprint);
}
var hasAppCertificate =
await appInstance.CheckApplicationInstanceCertificates(true).ConfigureAwait(false);
if (!hasAppCertificate ||
appConfig.SecurityConfiguration.ApplicationCertificate.Certificate == null)
{
_logger.LogError("Failed to load or create application own certificate.");
throw new InvalidConfigurationException(
"OPC UA application own certificate invalid");
}
if (ownCertificate == null)
{
ownCertificate =
appConfig.SecurityConfiguration.ApplicationCertificate.Certificate;
_logger.LogInformation(
"Own certificate Subject '{Subject}' (Thumbprint: {Thumbprint}) created.",
ownCertificate.Subject, ownCertificate.Thumbprint);
}
await ShowCertificateStoreInformationAsync(appConfig).ConfigureAwait(false);
return appInstance.ApplicationConfiguration;
}
catch (Exception e)
{
_logger.LogInformation(
"Error {Message} while configuring OPC UA stack - retry...", e.Message);
_logger.LogDebug(e, "Detailed error while configuring OPC UA stack.");
innerException = e;
await Task.Delay(3000).ConfigureAwait(false);
}
}
_logger.LogCritical("Failed to configure OPC UA stack - exit.");
throw new InvalidProgramException("OPC UA stack configuration not possible.",
innerException);
async ValueTask<(string?, string, string)> UpdateFromExistingCertificateAsync(
string? applicationUri, string appName, string hostName, SecurityOptions options)
{
try
{
var now = _timeProvider.GetUtcNow();
if (options.ApplicationCertificates?.StorePath != null &&
options.ApplicationCertificates.StoreType != null)
{
using var certStore = CertificateStoreIdentifier.CreateStore(
options.ApplicationCertificates.StoreType);
certStore.Open(options.ApplicationCertificates.StorePath, false);
var certs = await certStore.Enumerate().ConfigureAwait(false);
var subjects = new List<string>();
foreach (var cert in certs.Where(c => c != null).OrderBy(c => c.NotAfter))
{
// Select first certificate that has valid information
options.ApplicationCertificates.SubjectName = cert.Subject;
var name = cert.SubjectName.EnumerateRelativeDistinguishedNames()
.Where(dn => dn.GetSingleElementType().FriendlyName == "CN")
.Select(dn => dn.GetSingleElementValue())
.FirstOrDefault(dn => dn != null);
if (name != null)
{
appName = name;
}
var san = cert.FindExtension<X509SubjectAltNameExtension>();
var uris = san?.Uris;
var hostNames = san?.DomainNames;
if (uris != null && hostNames != null &&
uris.Count > 0 && hostNames.Count > 0)
{
return (uris[0], appName, hostNames[0]);
}
_logger.LogDebug(
"Found invalid certificate for {Subject} [{Thumbprint}].",
cert.Subject, cert.Thumbprint);
}
}
_logger.LogDebug("Could not find a certificate to take information from.");
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to find a certificate to take information from.");
}
return (applicationUri, appName, hostName);
}
}