Skip to content

Commit 4a44238

Browse files
authored
[Server] Allow Subject Name Change of Application Certificate in GDS Push scenario (#2833)
* allow Subject Name change * modifiy subject names in push test
1 parent 8733a33 commit 4a44238

File tree

11 files changed

+85
-52
lines changed

11 files changed

+85
-52
lines changed

Libraries/Opc.Ua.Configuration/ApplicationInstance.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,10 @@ private async Task<bool> CheckCertificateTypeAsync(
525525

526526
// reload the certificate from disk in the cache.
527527
var passwordProvider = configuration.SecurityConfiguration.CertificatePasswordProvider;
528-
await id.LoadPrivateKeyEx(passwordProvider).ConfigureAwait(false);
528+
await id.LoadPrivateKeyEx(passwordProvider, configuration.ApplicationUri).ConfigureAwait(false);
529529

530530
// load the certificate
531-
X509Certificate2 certificate = await id.Find(true).ConfigureAwait(false);
531+
X509Certificate2 certificate = await id.Find(true, configuration.ApplicationUri).ConfigureAwait(false);
532532

533533
// check that it is ok.
534534
if (certificate != null)
@@ -550,7 +550,7 @@ private async Task<bool> CheckCertificateTypeAsync(
550550
else
551551
{
552552
// check for missing private key.
553-
certificate = await id.Find(false).ConfigureAwait(false);
553+
certificate = await id.Find(false, configuration.ApplicationUri).ConfigureAwait(false);
554554

555555
if (certificate != null)
556556
{
@@ -568,7 +568,7 @@ private async Task<bool> CheckCertificateTypeAsync(
568568
StorePath = id.StorePath,
569569
SubjectName = id.SubjectName
570570
};
571-
certificate = await id2.Find(true).ConfigureAwait(false);
571+
certificate = await id2.Find(true, configuration.ApplicationUri).ConfigureAwait(false);
572572
}
573573

574574
if (certificate != null)
@@ -965,7 +965,7 @@ await id.Certificate.AddToStoreAsync(
965965
}
966966

967967
// reload the certificate from disk.
968-
id.Certificate = await id.LoadPrivateKeyEx(passwordProvider).ConfigureAwait(false);
968+
id.Certificate = await id.LoadPrivateKeyEx(passwordProvider, configuration.ApplicationUri).ConfigureAwait(false);
969969

970970
await configuration.CertificateValidator.UpdateAsync(configuration.SecurityConfiguration).ConfigureAwait(false);
971971

@@ -990,7 +990,7 @@ private static async Task DeleteApplicationInstanceCertificateAsync(ApplicationC
990990
}
991991

992992
// delete certificate and private key.
993-
X509Certificate2 certificate = await id.Find().ConfigureAwait(false);
993+
X509Certificate2 certificate = await id.Find(configuration.ApplicationUri).ConfigureAwait(false);
994994
if (certificate != null)
995995
{
996996
Utils.LogCertificate(TraceMasks.Security, "Deleting application instance certificate and private key.", certificate);

Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -401,9 +401,18 @@ private ServiceResult UpdateCertificate(
401401
X509Utils.CompareDistinguishedName(cert.Certificate.Subject, newCert.Subject) &&
402402
cert.CertificateType == certificateTypeId);
403403

404+
// if no cert was found search by ApplicationUri
405+
if (existingCertIdentifier == null)
406+
{
407+
existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert =>
408+
m_configuration.ApplicationUri == X509Utils.GetApplicationUriFromCertificate(cert.Certificate) &&
409+
cert.CertificateType == certificateTypeId);
410+
}
411+
404412
// if there is no such existing certificate then this is an error
405413
if (existingCertIdentifier == null)
406414
{
415+
407416
throw new ServiceResultException(StatusCodes.BadInvalidArgument, "No existing certificate found for the specified certificate type and subject name.");
408417
}
409418

@@ -428,16 +437,6 @@ private ServiceResult UpdateCertificate(
428437
throw new ServiceResultException(StatusCodes.BadCertificateInvalid, "Certificate data is invalid.");
429438
}
430439

431-
// validate new subject matches the previous subject,
432-
// otherwise application may not be able to find it after restart
433-
// TODO: An issuer may modify the subject of an issued certificate,
434-
// but then the configuration must be updated too!
435-
// NOTE: not a strict requirement here for ASN.1 byte compare
436-
if (!X509Utils.CompareDistinguishedName(existingCertIdentifier.Certificate.Subject, newCert.Subject))
437-
{
438-
throw new ServiceResultException(StatusCodes.BadSecurityChecksFailed, "Subject Name of new certificate doesn't match the application.");
439-
}
440-
441440
// self signed
442441
bool selfSigned = X509Utils.IsSelfSigned(newCert);
443442
if (selfSigned && newIssuerCollection.Count != 0)
@@ -484,7 +483,7 @@ private ServiceResult UpdateCertificate(
484483
}
485484
else
486485
{
487-
X509Certificate2 certWithPrivateKey = existingCertIdentifier.LoadPrivateKeyEx(passwordProvider).Result;
486+
X509Certificate2 certWithPrivateKey = existingCertIdentifier.LoadPrivateKeyEx(passwordProvider, m_configuration.ApplicationUri).Result;
488487
exportableKey = X509Utils.CreateCopyWithPrivateKey(certWithPrivateKey, false);
489488
}
490489

@@ -592,17 +591,18 @@ private ServiceResult CreateSigningRequest(
592591

593592
ServerCertificateGroup certificateGroup = VerifyGroupAndTypeId(certificateGroupId, certificateTypeId);
594593

594+
595+
595596
// identify the existing certificate for which to CreateSigningRequest
596597
// it should be of the same type
597598
CertificateIdentifier existingCertIdentifier = certificateGroup.ApplicationCertificates.FirstOrDefault(cert =>
598599
cert.CertificateType == certificateTypeId);
599600

600-
if (!String.IsNullOrEmpty(subjectName))
601+
if (string.IsNullOrEmpty(subjectName))
601602
{
602-
throw new ArgumentNullException(nameof(subjectName));
603+
subjectName = existingCertIdentifier.Certificate.Subject;
603604
}
604605

605-
606606
certificateGroup.TemporaryApplicationCertificate?.Dispose();
607607
certificateGroup.TemporaryApplicationCertificate = null;
608608

@@ -619,7 +619,7 @@ private ServiceResult CreateSigningRequest(
619619
certWithPrivateKey = CertificateFactory.CreateCertificate(
620620
m_configuration.ApplicationUri,
621621
null,
622-
existingCertIdentifier.Certificate.Subject,
622+
subjectName,
623623
null)
624624
.SetNotBefore(DateTime.Today.AddDays(-1))
625625
.SetNotAfter(DateTime.Today.AddDays(14))
@@ -677,7 +677,7 @@ private ServiceResult ApplyChanges(
677677
await Task.Delay(1000).ConfigureAwait(false);
678678
try
679679
{
680-
await m_configuration.CertificateValidator.UpdateCertificateAsync(m_configuration.SecurityConfiguration).ConfigureAwait(false);
680+
await m_configuration.CertificateValidator.UpdateCertificateAsync(m_configuration.SecurityConfiguration, m_configuration.ApplicationUri).ConfigureAwait(false);
681681
}
682682
catch (Exception ex)
683683
{

Stack/Opc.Ua.Core/Security/Certificates/CertificateIdentifier.cs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,21 +160,21 @@ public X509Certificate2 Certificate
160160
/// <summary>
161161
/// Finds a certificate in a store.
162162
/// </summary>
163-
public Task<X509Certificate2> Find()
163+
public Task<X509Certificate2> Find(string applicationUri = null)
164164
{
165-
return Find(false);
165+
return Find(false, applicationUri);
166166
}
167167

168168
/// <summary>
169169
/// Loads the private key for the certificate with an optional password.
170170
/// </summary>
171-
public Task<X509Certificate2> LoadPrivateKey(string password)
172-
=> LoadPrivateKeyEx(password != null ? new CertificatePasswordProvider(password) : null);
171+
public Task<X509Certificate2> LoadPrivateKey(string password, string applicationUri = null)
172+
=> LoadPrivateKeyEx(password != null ? new CertificatePasswordProvider(password) : null, applicationUri);
173173

174174
/// <summary>
175175
/// Loads the private key for the certificate with an optional password provider.
176176
/// </summary>
177-
public async Task<X509Certificate2> LoadPrivateKeyEx(ICertificatePasswordProvider passwordProvider)
177+
public async Task<X509Certificate2> LoadPrivateKeyEx(ICertificatePasswordProvider passwordProvider, string applicationUri = null)
178178
{
179179
if (this.StoreType != CertificateStoreType.X509Store)
180180
{
@@ -184,7 +184,14 @@ public async Task<X509Certificate2> LoadPrivateKeyEx(ICertificatePasswordProvide
184184
if (store?.SupportsLoadPrivateKey == true)
185185
{
186186
string password = passwordProvider?.GetPassword(this);
187-
m_certificate = await store.LoadPrivateKey(this.Thumbprint, this.SubjectName, this.CertificateType, password).ConfigureAwait(false);
187+
m_certificate = await store.LoadPrivateKey(this.Thumbprint, this.SubjectName, null, this.CertificateType, password).ConfigureAwait(false);
188+
189+
//find certificate by applicationUri instead of subjectName, as the subjectName could have changed after a certificate update
190+
if (m_certificate == null && !string.IsNullOrEmpty(applicationUri))
191+
{
192+
m_certificate = await store.LoadPrivateKey(this.Thumbprint, null, applicationUri, this.CertificateType, password).ConfigureAwait(false);
193+
}
194+
188195
return m_certificate;
189196
}
190197
}
@@ -198,9 +205,10 @@ public async Task<X509Certificate2> LoadPrivateKeyEx(ICertificatePasswordProvide
198205
/// </summary>
199206
/// <remarks>The certificate type is used to match the signature and public key type.</remarks>
200207
/// <param name="needPrivateKey">if set to <c>true</c> the returned certificate must contain the private key.</param>
208+
/// <param name="applicationUri">the application uri in the extensions of the certificate.</param>
201209
/// <returns>An instance of the <see cref="X509Certificate2"/> that is embedded by this instance or find it in
202-
/// the selected store pointed out by the <see cref="StorePath"/> using selected <see cref="SubjectName"/>.</returns>
203-
public async Task<X509Certificate2> Find(bool needPrivateKey)
210+
/// the selected store pointed out by the <see cref="StorePath"/> using selected <see cref="SubjectName"/> or if specified applicationUri.</returns>
211+
public async Task<X509Certificate2> Find(bool needPrivateKey, string applicationUri = null)
204212
{
205213
X509Certificate2 certificate = null;
206214

@@ -222,7 +230,7 @@ public async Task<X509Certificate2> Find(bool needPrivateKey)
222230

223231
X509Certificate2Collection collection = await store.Enumerate().ConfigureAwait(false);
224232

225-
certificate = Find(collection, m_thumbprint, m_subjectName, m_certificateType, needPrivateKey);
233+
certificate = Find(collection, m_thumbprint, m_subjectName, applicationUri, m_certificateType, needPrivateKey);
226234

227235
if (certificate != null)
228236
{
@@ -315,13 +323,15 @@ private static string GetDisplayName(X509Certificate2 certificate)
315323
/// <param name="collection">The collection.</param>
316324
/// <param name="thumbprint">The thumbprint of the certificate.</param>
317325
/// <param name="subjectName">Subject name of the certificate.</param>
326+
/// <param name="applicationUri">ApplicationUri in the SubjectAltNameExtension of the certificate.</param>
318327
/// <param name="certificateType">The certificate type.</param>
319328
/// <param name="needPrivateKey">if set to <c>true</c> [need private key].</param>
320329
/// <returns></returns>
321330
public static X509Certificate2 Find(
322331
X509Certificate2Collection collection,
323332
string thumbprint,
324333
string subjectName,
334+
string applicationUri,
325335
NodeId certificateType,
326336
bool needPrivateKey)
327337
{
@@ -379,6 +389,20 @@ public static X509Certificate2 Find(
379389
}
380390
}
381391

392+
//find by application uri
393+
if (!string.IsNullOrEmpty(applicationUri))
394+
{
395+
foreach (X509Certificate2 certificate in collection)
396+
{
397+
if (applicationUri == X509Utils.GetApplicationUriFromCertificate(certificate) &&
398+
ValidateCertificateType(certificate, certificateType) &&
399+
(!needPrivateKey || certificate.HasPrivateKey))
400+
{
401+
return certificate;
402+
}
403+
}
404+
}
405+
382406
// certificate not found.
383407
return null;
384408
}
@@ -581,7 +605,7 @@ public static bool ValidateCertificateType(X509Certificate2 certificate, NodeId
581605
certificateType == ObjectTypeIds.EccBrainpoolP256r1ApplicationCertificateType)
582606
{
583607
return true;
584-
}
608+
}
585609

586610
break;
587611

@@ -671,7 +695,7 @@ public void DisposeCertificate()
671695
{ ObjectTypes.RsaMinApplicationCertificateType, "RsaMin"},
672696
{ ObjectTypes.ApplicationCertificateType, "Rsa"},
673697
};
674-
#endregion
698+
#endregion
675699

676700
#region Private Methods
677701
/// <summary>
@@ -963,7 +987,7 @@ public Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectNa
963987
}
964988

965989
/// <inheritdoc/>
966-
public Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password)
990+
public Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string applicationUri, NodeId certificateType, string password)
967991
{
968992
return Task.FromResult<X509Certificate2>(null);
969993
}

Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private void InternalUpdate(
180180
/// <summary>
181181
/// Updates the validator with the current state of the configuration.
182182
/// </summary>
183-
public virtual async Task UpdateAsync(SecurityConfiguration configuration)
183+
public virtual async Task UpdateAsync(SecurityConfiguration configuration, string applicationUri = null)
184184
{
185185
if (configuration == null)
186186
{
@@ -226,7 +226,7 @@ public virtual async Task UpdateAsync(SecurityConfiguration configuration)
226226
{
227227
foreach (var applicationCertificate in configuration.ApplicationCertificates)
228228
{
229-
X509Certificate2 certificate = await applicationCertificate.Find(true).ConfigureAwait(false);
229+
X509Certificate2 certificate = await applicationCertificate.Find(true, applicationUri).ConfigureAwait(false);
230230
if (certificate == null)
231231
{
232232
Utils.Trace(Utils.TraceMasks.Security, "Could not find application certificate: {0}", applicationCertificate);
@@ -251,7 +251,7 @@ public virtual async Task UpdateAsync(SecurityConfiguration configuration)
251251
/// <summary>
252252
/// Updates the validator with a new application certificate.
253253
/// </summary>
254-
public virtual async Task UpdateCertificateAsync(SecurityConfiguration securityConfiguration)
254+
public virtual async Task UpdateCertificateAsync(SecurityConfiguration securityConfiguration, string applicationUri = null)
255255
{
256256
await m_semaphore.WaitAsync().ConfigureAwait(false);
257257

@@ -267,15 +267,15 @@ public virtual async Task UpdateCertificateAsync(SecurityConfiguration securityC
267267
foreach (var applicationCertificate in securityConfiguration.ApplicationCertificates)
268268
{
269269
await applicationCertificate.LoadPrivateKeyEx(
270-
securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false);
270+
securityConfiguration.CertificatePasswordProvider, applicationUri).ConfigureAwait(false);
271271
}
272272
}
273273
finally
274274
{
275275
m_semaphore.Release();
276276
}
277277

278-
await UpdateAsync(securityConfiguration).ConfigureAwait(false);
278+
await UpdateAsync(securityConfiguration, applicationUri).ConfigureAwait(false);
279279

280280
lock (m_callbackLock)
281281
{

Stack/Opc.Ua.Core/Security/Certificates/DirectoryCertificateStore.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -427,21 +427,21 @@ public string GetPrivateKeyFilePath(string thumbprint)
427427
[Obsolete("Method is deprecated. Use only for RSA certificates, the replacing LoadPrivateKey with certificateType parameter should be used.")]
428428
public Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string password)
429429
{
430-
return LoadPrivateKey(thumbprint, subjectName, null, password);
430+
return LoadPrivateKey(thumbprint, subjectName, null, null, password);
431431
}
432432

433433
/// <summary>
434434
/// Loads the private key from a PFX file in the certificate store.
435435
/// </summary>
436-
public async Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password)
436+
public async Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string applicationUri, NodeId certificateType, string password)
437437
{
438438
if (NoPrivateKeys || m_privateKeySubdir == null ||
439439
m_certificateSubdir == null || !m_certificateSubdir.Exists)
440440
{
441441
return null;
442442
}
443443

444-
if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName))
444+
if (string.IsNullOrEmpty(thumbprint) && string.IsNullOrEmpty(subjectName) && string.IsNullOrEmpty(applicationUri))
445445
{
446446
return null;
447447
}
@@ -484,6 +484,14 @@ public async Task<X509Certificate2> LoadPrivateKey(string thumbprint, string sub
484484
}
485485
}
486486

487+
if (!string.IsNullOrEmpty(applicationUri))
488+
{
489+
if (!string.Equals(X509Utils.GetApplicationUriFromCertificate(certificate), applicationUri, StringComparison.OrdinalIgnoreCase))
490+
{
491+
continue;
492+
}
493+
}
494+
487495
if (!CertificateIdentifier.ValidateCertificateType(certificate, certificateType))
488496
{
489497
continue;

Stack/Opc.Ua.Core/Security/Certificates/ICertificateStore.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,12 @@ public interface ICertificateStore : IDisposable
110110
/// </summary>
111111
/// <param name="thumbprint">The thumbprint.</param>
112112
/// <param name="subjectName">The certificate subject.</param>
113+
/// <param name="applicationUri">The application uri in the cert extension.</param>
113114
/// <param name="certificateType">The certificate type to load.</param>
114115
/// <param name="password">The certificate password.</param>
115116
/// <remarks>Returns always null if SupportsLoadPrivateKey returns false.</remarks>
116117
/// <returns>The matching certificate with private key</returns>
117-
Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, NodeId certificateType, string password);
118+
Task<X509Certificate2> LoadPrivateKey(string thumbprint, string subjectName, string applicationUri, NodeId certificateType, string password);
118119

119120
/// <summary>
120121
/// Checks if issuer has revoked the certificate.

0 commit comments

Comments
 (0)