diff --git a/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs b/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
index 68d5c8985..35d59c871 100644
--- a/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
+++ b/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
@@ -648,6 +648,25 @@ public void UnregisterApplication(NodeId applicationId)
applicationId);
}
+ ///
+ /// Revokes a Certificate issued to the Application by the CertificateManager
+ ///
+ /// The application id.
+ /// The certificate to revoke
+ public void RevokeCertificate(NodeId applicationId, byte[] certificate)
+ {
+ if (!IsConnected)
+ {
+ Connect();
+ }
+
+ Session.Call(
+ ExpandedNodeId.ToNodeId(Opc.Ua.Gds.ObjectIds.Directory, Session.NamespaceUris),
+ ExpandedNodeId.ToNodeId(Opc.Ua.Gds.MethodIds.CertificateDirectoryType_RevokeCertificate, Session.NamespaceUris),
+ applicationId,
+ certificate);
+ }
+
///
/// Requests a new certificate.
///
diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
index 87dd81d3c..982915e96 100644
--- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
+++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
@@ -246,19 +246,24 @@ private ICertificateGroup GetGroupForCertificate(byte[] certificate)
return null;
}
- private async Task RevokeCertificateAsync(byte[] certificate)
+ private async Task RevokeCertificateAsync(byte[] certificate)
{
+ bool revoked = false;
if (certificate != null && certificate.Length > 0)
{
ICertificateGroup certificateGroup = GetGroupForCertificate(certificate);
if (certificateGroup != null)
{
- using (var x509 = new X509Certificate2(certificate))
+ using (X509Certificate2 x509 = new X509Certificate2(certificate))
{
try
{
- await certificateGroup.RevokeCertificateAsync(x509).ConfigureAwait(false);
+ Security.Certificates.X509CRL crl = await certificateGroup.RevokeCertificateAsync(x509).ConfigureAwait(false);
+ if (crl != null)
+ {
+ revoked = true;
+ }
}
catch (Exception e)
{
@@ -267,6 +272,7 @@ private async Task RevokeCertificateAsync(byte[] certificate)
}
}
}
+ return revoked;
}
protected async Task InitializeCertificateGroup(CertificateGroupConfiguration certificateGroupConfiguration)
@@ -381,6 +387,7 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context
Opc.Ua.Gds.CertificateDirectoryState activeNode = new Opc.Ua.Gds.CertificateDirectoryState(passiveNode.Parent);
+ activeNode.RevokeCertificate = new RevokeCertificateMethodState(passiveNode);
activeNode.CheckRevocationStatus = new CheckRevocationStatusMethodState(passiveNode.Parent);
activeNode.Create(context, passiveNode);
@@ -397,11 +404,9 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context
activeNode.GetTrustList.OnCall = new GetTrustListMethodStateMethodCallHandler(OnGetTrustList);
activeNode.GetCertificateStatus.OnCall = new GetCertificateStatusMethodStateMethodCallHandler(OnGetCertificateStatus);
activeNode.StartSigningRequest.OnCall = new StartSigningRequestMethodStateMethodCallHandler(OnStartSigningRequest);
+ activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate);
activeNode.CheckRevocationStatus.OnCall = new CheckRevocationStatusMethodStateMethodCallHandler(OnCheckRevocationStatus);
- // TODO
- //activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate);
-
activeNode.CertificateGroups.DefaultApplicationGroup.CertificateTypes.Value = new NodeId[] { Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType };
activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.LastUpdateTime.Value = DateTime.UtcNow;
activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.Writable.Value = false;
@@ -562,6 +567,49 @@ private ServiceResult OnUnregisterApplication(
return ServiceResult.Good;
}
+ private ServiceResult OnRevokeCertificate(
+ ISystemContext context,
+ MethodState method,
+ NodeId objectId,
+ NodeId applicationId,
+ byte[] certificate)
+ {
+ AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdmin);
+
+ if (m_database.GetApplication(applicationId) == null)
+ {
+ return new ServiceResult(StatusCodes.BadNotFound, "The ApplicationId does not refer to a registered application.");
+ }
+ if (certificate == null || certificate.Length == 0)
+ {
+ throw new ServiceResultException(StatusCodes.BadInvalidArgument, "The certificate is not a Certificate for the specified Application that was issued by the CertificateManager.");
+ }
+
+ bool revoked = false;
+ foreach (var certType in m_certTypeMap)
+ {
+ byte[] applicationCertificate;
+
+ if (!m_database.GetApplicationCertificate(applicationId, certType.Value, out applicationCertificate)
+ || applicationCertificate == null
+ || !Utils.IsEqual(applicationCertificate, certificate))
+ {
+ continue;
+ }
+
+ revoked = RevokeCertificateAsync(certificate).Result;
+ if (revoked)
+ {
+ break;
+ }
+ }
+ if (!revoked)
+ {
+ throw new ServiceResultException(StatusCodes.BadInvalidArgument, "The certificate is not a Certificate for the specified Application that was issued by the CertificateManager.");
+ }
+ return ServiceResult.Good;
+ }
+
private ServiceResult OnFindApplications(
ISystemContext context,
MethodState method,
diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs
index f1cbc971c..228591e5a 100644
--- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs
+++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs
@@ -43,6 +43,7 @@ internal static class AuthorizationHelper
internal static List DiscoveryAdmin { get; } = new List { GdsRole.DiscoveryAdmin };
internal static List AuthenticatedUserOrSelfAdmin { get; } = new List { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin };
internal static List CertificateAuthorityAdminOrSelfAdmin { get; } = new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin };
+ internal static List CertificateAuthorityAdmin { get; } = new List { GdsRole.CertificateAuthorityAdmin };
///
/// Checks if the current session (context) has one of the requested roles. If is allowed the applicationId needs to be specified
diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs
index c58d2a89c..eff0358d6 100644
--- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs
+++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs
@@ -1310,6 +1310,24 @@ public void CheckGoodRevocationStatus()
}
}
+ [Test, Order(895)]
+ public void RevokeGoodCertificates()
+ {
+ AssertIgnoreTestWithoutInvalidRegistration();
+ AssertIgnoreTestWithoutGoodNewKeyPairRequest();
+ ConnectGDS(true);
+ foreach (var application in m_goodApplicationTestSet)
+ {
+ m_gdsClient.GDSClient.RevokeCertificate(application.ApplicationRecord.ApplicationId, application.Certificate);
+ }
+ foreach (var application in m_invalidApplicationTestSet)
+ {
+ Assert.That(() => {
+ m_gdsClient.GDSClient.RevokeCertificate(application.ApplicationRecord.ApplicationId, application.Certificate);
+ }, Throws.Exception);
+ }
+ }
+
[Test, Order(900)]
public void UnregisterGoodApplications()
{