Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,13 +569,14 @@ public static async Task<X509CRL> RevokeCertificateAsync(
bool isCACert = X509Utils.IsCertificateAuthority(certificate);

// find the authority key identifier.

X509AuthorityKeyIdentifierExtension authority =
certificate.FindExtension<X509AuthorityKeyIdentifierExtension>();
string serialNumber;
string keyIdentifier;
if (authority != null)
{
serialNumber = authority.SerialNumber;
keyIdentifier = authority.KeyIdentifier;
}
else
{
Expand Down Expand Up @@ -606,6 +607,12 @@ await X509Utils
certificate.IssuerName,
serialNumber)
.ConfigureAwait(false)
?? await X509Utils
.FindIssuerCAByKeyIdentifierAsync(
store,
certificate.IssuerName,
keyIdentifier)
.ConfigureAwait(false)
?? throw new ServiceResultException(
StatusCodes.BadCertificateInvalid,
"Cannot find issuer certificate in store.");
Expand Down
25 changes: 25 additions & 0 deletions Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,31 @@ public static async Task<X509Certificate2> FindIssuerCABySerialNumberAsync(
return null;
}


/// <summary>
/// Get the certificate issuer by its key identifier.
/// </summary>
public static async Task<X509Certificate2> FindIssuerCAByKeyIdentifierAsync(
ICertificateStore store,
X500DistinguishedName issuer,
string keyIdentifier)
{
X509Certificate2Collection certificates = await store.EnumerateAsync()
.ConfigureAwait(false);
foreach (X509Certificate2 certificate in certificates)
{
if (CompareDistinguishedName(certificate.SubjectName, issuer))
{
X509SubjectKeyIdentifierExtension subject = certificate.FindExtension<X509SubjectKeyIdentifierExtension>();
if (subject != null && Utils.IsEqual(subject.SubjectKeyIdentifier, keyIdentifier))
{
return certificate;
}
}
}
return null;
}

/// <summary>
/// Extension to add a certificate to a <see cref="ICertificateStore"/>.
/// </summary>
Expand Down
54 changes: 54 additions & 0 deletions Tests/Opc.Ua.Gds.Tests/CertificateGroupTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using NUnit.Framework;
Expand Down Expand Up @@ -87,6 +88,59 @@ public async Task TestCreateCACertificateAsyncCertIsInTrustedStoreAsync()
Assert.IsTrue(certs.Count == 1);
}

[Test]
public async Task Test_Ca_Signed_Cert_Can_Be_Revoked()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();

var configuration = new CertificateGroupConfiguration
{
SubjectName = "CN=GDS Test CA, O=OPC Foundation",
BaseStorePath = m_path,
CertificateTypes = [nameof(Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType)]
};
ICertificateGroup certificateGroup = new CertificateGroup(telemetry).Create(
m_path + "/authorities",
configuration);
X509Certificate2 certificate = await certificateGroup
.CreateCACertificateAsync(
configuration.SubjectName,
certificateGroup.CertificateTypes[0])
.ConfigureAwait(false);
Assert.NotNull(certificate);
var certificateStoreIdentifier = new CertificateStoreIdentifier(
configuration.TrustedListPath);
var certificateStoreIdentifier2 = new CertificateStoreIdentifier(
configuration.BaseStorePath);
ICertificateGroup otherCertGroup = new CertificateGroup(telemetry).Create(
m_path + "/authorities",
configuration);
using var authStore = otherCertGroup.AuthoritiesStore.OpenStore(telemetry);
var authStoreCerts = authStore.EnumerateAsync().GetAwaiter().GetResult();

var firstAuthStoreCert = authStoreCerts[0];
var id = new CertificateIdentifier
{
Thumbprint = firstAuthStoreCert.Thumbprint,
StorePath = authStore.StorePath,
StoreType = authStore.StoreType
};
var authCert = id.LoadPrivateKeyAsync(null).GetAwaiter().GetResult();
using ICertificateStore trustedStore = certificateStoreIdentifier.OpenStore(telemetry);
var storeCerts = trustedStore.EnumerateAsync().GetAwaiter().GetResult();
X509Certificate2Collection certs = await trustedStore
.FindByThumbprintAsync(certificate.Thumbprint)
.ConfigureAwait(false);
Assert.IsTrue(certs.Count >= 1);
var signedCert = CertificateBuilder.Create("CN=signedCert")
.SetIssuer(authCert)
.CreateForRSA();
await trustedStore.AddAsync(signedCert).ConfigureAwait(false);
var crl = await certificateGroup.RevokeCertificateAsync(signedCert).ConfigureAwait(false);
Assert.NotNull(crl);
Assert.That(crl.RevokedCertificates.Any(el => el.SerialNumber == signedCert.SerialNumber));
}

[Test]
public async Task TestCreateCACertificateAsyncCertIsInTrustedIssuerStoreAsync()
{
Expand Down
Loading