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