Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDS: add Method CheckRevocationStatus to Client & Server #2499

Merged
merged 12 commits into from
Feb 28, 2024
30 changes: 30 additions & 0 deletions Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,36 @@ public NodeId RegisterApplication(ApplicationRecordDataType application)
return null;
}

/// <summary>
/// Checks the provided certificate for validity
/// </summary>
/// <param name="certificate">The DER encoded form of the Certificate to check.</param>
/// <param name="certificateStatus">The first error encountered when validating the Certificate.</param>
/// <param name="validityTime">When the result expires and should be rechecked. DateTime.MinValue if this is unknown.</param>
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];
}
}

/// <summary>
/// Updates the application.
/// </summary>
Expand Down
53 changes: 53 additions & 0 deletions Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,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);
Expand All @@ -418,6 +420,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);
romanett marked this conversation as resolved.
Show resolved Hide resolved

// TODO
//activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate);

Expand Down Expand Up @@ -607,6 +611,55 @@ private ServiceResult OnGetApplication(
return ServiceResult.Good;
}

private ServiceResult OnCheckRevocationStatus(
ISystemContext context,
MethodState method,
NodeId objectId,
byte[] certificate,
ref StatusCode certificateStatus,
ref DateTime validityTime)
{
//Check if connected using a secure channel
romanett marked this conversation as resolved.
Show resolved Hide resolved
OperationContext operationContext = (context as SystemContext)?.OperationContext as OperationContext;
if (operationContext != null)
{
if (operationContext.ChannelContext?.EndpointDescription?.SecurityMode != MessageSecurityMode.SignAndEncrypt)
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Method has to be called from an authenticated secure channel.");
}
}

//create CertificateValidator initialized with GDS CAs
var certificateValidator = new CertificateValidator();

var gdsCAs = new CertificateTrustList() {
StorePath = m_globalDiscoveryServerConfiguration.AuthoritiesStorePath,
StoreType = CertificateStoreIdentifier.DetermineStoreType(m_globalDiscoveryServerConfiguration.AuthoritiesStorePath)
};

certificateValidator.Update(null, gdsCAs, null);

romanett marked this conversation as resolved.
Show resolved Hide resolved



validityTime = DateTime.MinValue;
romanett marked this conversation as resolved.
Show resolved Hide resolved

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)
Expand Down
26 changes: 26 additions & 0 deletions Tests/Opc.Ua.Gds.Tests/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,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);
romanett marked this conversation as resolved.
Show resolved Hide resolved
Assert.AreEqual(StatusCodes.Good, certificateStatus.Code);
Assert.NotNull(validityTime);
}
}

[Test, Order(900)]
public void UnregisterGoodApplications()
{
Expand All @@ -979,6 +992,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()
{
Expand Down
Loading