diff --git a/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs b/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs index 9e5084272..68d5c8985 100644 --- a/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs +++ b/Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs @@ -584,6 +584,36 @@ public NodeId RegisterApplication(ApplicationRecordDataType application) return null; } + /// + /// Checks the provided certificate for validity + /// + /// The DER encoded form of the Certificate to check. + /// The first error encountered when validating the Certificate. + /// When the result expires and should be rechecked. DateTime.MinValue if this is unknown. + public void CheckRevocationStatus(byte[] certificate, + out StatusCode certificateStatus, + out DateTime validityTime) + { + certificateStatus = StatusCodes.Good; + validityTime = DateTime.MinValue; + + if (!IsConnected) + { + Connect(); + } + + var outputArguments = Session.Call( + ExpandedNodeId.ToNodeId(Opc.Ua.Gds.ObjectIds.Directory, Session.NamespaceUris), + ExpandedNodeId.ToNodeId(Opc.Ua.Gds.MethodIds.CertificateDirectoryType_CheckRevocationStatus, Session.NamespaceUris), + certificate); + + if (outputArguments.Count >= 2) + { + certificateStatus = (StatusCode)outputArguments[0]; + validityTime = (DateTime)outputArguments[1]; + } + } + /// /// Updates the application. /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index c5d9c92da..87dd81d3c 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -162,7 +162,7 @@ public override NodeId New(ISystemContext context, NodeState node) } #endregion - #region Private methods + #region Private Methods private NodeId GetTrustListId(NodeId certificateGroupId) { @@ -381,6 +381,8 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context Opc.Ua.Gds.CertificateDirectoryState activeNode = new Opc.Ua.Gds.CertificateDirectoryState(passiveNode.Parent); + activeNode.CheckRevocationStatus = new CheckRevocationStatusMethodState(passiveNode.Parent); + activeNode.Create(context, passiveNode); activeNode.QueryServers.OnCall = new QueryServersMethodStateMethodCallHandler(OnQueryServers); activeNode.QueryApplications.OnCall = new QueryApplicationsMethodStateMethodCallHandler(OnQueryApplications); @@ -395,6 +397,8 @@ 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.CheckRevocationStatus.OnCall = new CheckRevocationStatusMethodStateMethodCallHandler(OnCheckRevocationStatus); + // TODO //activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate); @@ -584,6 +588,41 @@ private ServiceResult OnGetApplication( return ServiceResult.Good; } + private ServiceResult OnCheckRevocationStatus( + ISystemContext context, + MethodState method, + NodeId objectId, + byte[] certificate, + ref StatusCode certificateStatus, + ref DateTime validityTime) + { + AuthorizationHelper.HasAuthenticatedSecureChannel(context); + + //create CertificateValidator initialized with GDS CAs + var certificateValidator = new CertificateValidator(); + var authorities = new CertificateTrustList() { + StorePath = m_globalDiscoveryServerConfiguration.AuthoritiesStorePath, + StoreType = CertificateStoreIdentifier.DetermineStoreType(m_globalDiscoveryServerConfiguration.AuthoritiesStorePath) + }; + certificateValidator.Update(null, authorities, null); + + //TODO return validityTime of Certificate once CertificateValidator supports it + validityTime = DateTime.MinValue; + + using (var x509 = new X509Certificate2(certificate)) + { + try + { + certificateValidator.Validate(x509); + } + catch (ServiceResultException se) + { + certificateStatus = se.StatusCode; + } + } + return ServiceResult.Good; + } + private ServiceResult CheckHttpsDomain(ApplicationRecordDataType application, string commonName) { if (application.ApplicationType == ApplicationType.Client) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs index 4bf0e86e2..f1cbc971c 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -51,95 +51,110 @@ internal static class AuthorizationHelper /// all allowed roles, if wanted include /// If is allowed specifies the id of the Application-Entry to access public static void HasAuthorization(ISystemContext context, IEnumerable roles, [Optional] NodeId applicationId) - { - if (context != null) { - List allowedRoles = roles.ToList(); - bool selfAdmin = allowedRoles.Remove(GdsRole.ApplicationSelfAdmin); - - //if true access is allowed - if (HasRole(context.UserIdentity, allowedRoles)) - return; - - if (selfAdmin) + if (context != null) { - //if true access to own application is allowed - if (CheckSelfAdminPrivilege(context.UserIdentity, applicationId)) + List allowedRoles = roles.ToList(); + bool selfAdmin = allowedRoles.Remove(GdsRole.ApplicationSelfAdmin); + + //if true access is allowed + if (HasRole(context.UserIdentity, allowedRoles)) return; + + if (selfAdmin) + { + //if true access to own application is allowed + if (CheckSelfAdminPrivilege(context.UserIdentity, applicationId)) + return; + } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)} is required to call the method"); } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)} is required to call the method"); } - } - /// - /// Checks if the current session (context) is allowed to access the trust List (has roles CertificateAuthorityAdmin, SecurityAdmin or ) - /// - /// the current - /// path of the trustList, needed to check for Application Self Admin priviledge - /// all supported cert types, needed to check for Application Self Admin priviledge - /// all registered applications , needed to check for Application Self Admin priviledge - /// - public static void HasTrustListAccess(ISystemContext context, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) - { - var roles = new List { GdsRole.CertificateAuthorityAdmin, Role.SecurityAdmin }; - if (HasRole(context.UserIdentity, roles)) - return; - - if (!string.IsNullOrEmpty(trustedStorePath) && certTypeMap != null && applicationsDatabase != null && - CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath, certTypeMap, applicationsDatabase)) - return; - - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)} or ApplicationSelfAdminPrivilege is required to use the TrustList"); - } + /// + /// Checks if the current session (context) is allowed to access the trust List (has roles CertificateAuthorityAdmin, SecurityAdmin or ) + /// + /// the current + /// path of the trustList, needed to check for Application Self Admin priviledge + /// all supported cert types, needed to check for Application Self Admin priviledge + /// all registered applications , needed to check for Application Self Admin priviledge + /// + public static void HasTrustListAccess(ISystemContext context, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) + { + var roles = new List { GdsRole.CertificateAuthorityAdmin, Role.SecurityAdmin }; + if (HasRole(context.UserIdentity, roles)) + return; - private static bool HasRole(IUserIdentity userIdentity, IEnumerable roles) - { - RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; + if (!string.IsNullOrEmpty(trustedStorePath) && certTypeMap != null && applicationsDatabase != null && + CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath, certTypeMap, applicationsDatabase)) + return; - if (identity != null) + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)} or ApplicationSelfAdminPrivilege is required to use the TrustList"); + } + /// + /// Checks if current session (context) is connected using a secure channel + /// + /// the current + /// + public static void HasAuthenticatedSecureChannel(ISystemContext context) { - foreach (Role role in roles) + OperationContext operationContext = (context as SystemContext)?.OperationContext as OperationContext; + if (operationContext != null) { - if ((identity.Roles.Contains(role))) + if (operationContext.ChannelContext?.EndpointDescription?.SecurityMode != MessageSecurityMode.SignAndEncrypt) { - return true; + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Method has to be called from an authenticated secure channel."); } } } - return false; - } + private static bool HasRole(IUserIdentity userIdentity, IEnumerable roles) + { + RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; - private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) - { - if (applicationId is null || applicationId.IsNullNodeId) + if (identity != null) + { + foreach (Role role in roles) + { + if ((identity.Roles.Contains(role))) + { + return true; + } + } + } return false; + } - GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; - if (identity != null) + private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) { - //self Admin only has access to own application - if (identity.ApplicationId == applicationId) + if (applicationId is null || applicationId.IsNullNodeId) + return false; + + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) { - return true; + //self Admin only has access to own application + if (identity.ApplicationId == applicationId) + { + return true; + } } + return false; } - return false; - } - private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) - { - GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; - if (identity != null) + private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) { - foreach (var certType in certTypeMap.Values) + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) { - applicationsDatabase.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); - if (trustedStorePath == trustListId) + foreach (var certType in certTypeMap.Values) { - return true; + applicationsDatabase.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); + if (trustedStorePath == trustListId) + { + return true; + } } } + return false; } - return false; } } -} diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 334b60fbb..c58d2a89c 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -1297,6 +1297,19 @@ public void GetInvalidCertificateStatus() } } + [Test, Order(700)] + public void CheckGoodRevocationStatus() + { + AssertIgnoreTestWithoutGoodRegistration(); + ConnectGDS(false); + foreach (var application in m_goodApplicationTestSet) + { + m_gdsClient.GDSClient.CheckRevocationStatus(application.Certificate, out StatusCode certificateStatus, out DateTime validityTime); + Assert.AreEqual(StatusCodes.Good, certificateStatus.Code); + Assert.NotNull(validityTime); + } + } + [Test, Order(900)] public void UnregisterGoodApplications() { @@ -1308,6 +1321,19 @@ public void UnregisterGoodApplications() } } + [Test, Order(910)] + public void CheckRevocationStatusUnregisteredApplications() + { + AssertIgnoreTestWithoutGoodRegistration(); + ConnectGDS(false); + foreach (var application in m_goodApplicationTestSet) + { + m_gdsClient.GDSClient.CheckRevocationStatus(application.Certificate, out StatusCode certificateStatus, out DateTime validityTime); + Assert.AreEqual(StatusCodes.BadCertificateRevoked, certificateStatus.Code); + Assert.NotNull(validityTime); + } + } + [Test, Order(910)] public void UnregisterInvalidApplications() {