in src/Azure.IIoT.OpcUa.Publisher/src/Services/RuntimeStateReporter.cs [215:366]
private async Task UpdateApiKeyAndCertificateAsync()
{
var apiKeyStore = _stores.Find(s => s.State.TryGetValue(
OpcUa.Constants.TwinPropertyApiKeyKey, out var key) && key.IsString);
if (apiKeyStore != null)
{
ApiKey = (string?)apiKeyStore.State[OpcUa.Constants.TwinPropertyApiKeyKey];
_logger.LogInformation("Api Key exists in {Store} store...", apiKeyStore.Name);
}
if (!string.IsNullOrWhiteSpace(_options.Value.ApiKeyOverride) &&
ApiKey != _options.Value.ApiKeyOverride)
{
Debug.Assert(_stores.Count > 0);
_logger.LogInformation("Using Api Key provided in configuration...");
ApiKey = _options.Value.ApiKeyOverride;
_stores[0].State.Add(OpcUa.Constants.TwinPropertyApiKeyKey, ApiKey);
}
if (string.IsNullOrWhiteSpace(ApiKey))
{
Debug.Assert(_stores.Count > 0);
_logger.LogInformation("Generating new Api Key in {Store} store...",
_stores[0].Name);
ApiKey = RandomNumberGenerator.GetBytes(20).ToBase64String();
_stores[0].State.Add(OpcUa.Constants.TwinPropertyApiKeyKey, ApiKey);
}
var dnsName = Dns.GetHostName();
// The certificate must be in the same store as the api key or else we generate a new one.
if (!(_options.Value.RenewTlsCertificateOnStartup ?? false) &&
apiKeyStore != null &&
apiKeyStore.State.TryGetValue(OpcUa.Constants.TwinPropertyCertificateKey,
out var cert) && cert.IsBytes)
{
try
{
// Load certificate
Certificate?.Dispose();
Certificate = X509CertificateLoader.LoadPkcs12((byte[])cert!, ApiKey);
var now = _timeProvider.GetUtcNow().AddDays(1);
if (now < Certificate.NotAfter && Certificate.HasPrivateKey &&
Certificate.SubjectName.EnumerateRelativeDistinguishedNames()
.Any(a => a.GetSingleElementValue() == dnsName))
{
var renewalAfter = Certificate.NotAfter - now;
_logger.LogInformation(
"Using valid Certificate found in {Store} store (renewal in {Duration})...",
apiKeyStore.Name, renewalAfter);
_renewalTimer.Change(renewalAfter, Timeout.InfiniteTimeSpan);
// Done
return;
}
_logger.LogInformation(
"Certificate found in {Store} store has expired. Generate new...",
apiKeyStore.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Provided Certificate invalid.");
}
}
// Create new certificate
var nowOffset = _timeProvider.GetUtcNow();
var expiration = nowOffset.AddDays(kCertificateLifetimeDays);
Certificate?.Dispose();
Certificate = null;
if (_workload != null)
{
try
{
var certificates = await _workload.CreateServerCertificateAsync(
dnsName, expiration.Date).ConfigureAwait(false);
Debug.Assert(certificates.Count > 0);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
using var certificate = certificates[0];
//
// https://github.com/dotnet/runtime/issues/45680)
// On Windows the certificate in 'result' gives an error
// when used with kestrel: "No credentials are available"
//
Certificate = X509CertificateLoader.LoadCertificate(
certificate.Export(X509ContentType.Pkcs12));
}
else
{
Certificate = certificates[0];
}
if (!Certificate.HasPrivateKey)
{
Certificate.Dispose();
Certificate = null;
_logger.LogWarning(
"Failed to get certificate with private key using workload API.");
}
else
{
_logger.LogInformation(
"Using server certificate with private key from workload API...");
}
}
catch (NotSupportedException nse)
{
_logger.LogWarning("Not supported: {Message}. " +
"Unable to use workload API to obtain the certificate!", nse.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to create certificate using workload API.");
}
}
if (Certificate == null)
{
using var ecdsa = ECDsa.Create();
var req = new CertificateRequest("DC=" + dnsName, ecdsa, HashAlgorithmName.SHA256);
var san = new SubjectAlternativeNameBuilder();
san.AddDnsName(dnsName);
var altDns = _identity?.ModuleId ?? _identity?.DeviceId;
if (!string.IsNullOrEmpty(altDns) &&
!string.Equals(altDns, dnsName, StringComparison.OrdinalIgnoreCase))
{
san.AddDnsName(altDns);
}
req.CertificateExtensions.Add(san.Build());
Certificate = req.CreateSelfSigned(DateTimeOffset.Now, expiration);
Debug.Assert(Certificate.HasPrivateKey);
_logger.LogInformation("Created self-signed ECC server certificate...");
}
Debug.Assert(_stores.Count > 0);
Debug.Assert(ApiKey != null);
apiKeyStore ??= _stores[0];
var pfxCertificate = Certificate.Export(X509ContentType.Pfx, ApiKey);
apiKeyStore.State.AddOrUpdate(OpcUa.Constants.TwinPropertyCertificateKey, pfxCertificate);
var renewalDuration = Certificate.NotAfter - nowOffset.Date - TimeSpan.FromDays(1);
_renewalTimer.Change(renewalDuration, Timeout.InfiniteTimeSpan);
_logger.LogInformation(
"Stored new Certificate in {Store} store (and scheduled renewal after {Duration}).",
apiKeyStore.Name, renewalDuration);
_certificateRenewals++;
}