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() {