Skip to content

Commit

Permalink
GDS: add Method RevokeCertificate to Client and Server (#2497)
Browse files Browse the repository at this point in the history
Implement the method RefokeCertificate from Spec: [7.9.6 RevokeCertificate (https://reference.opcfoundation.org/GDS/v105/docs/7.9.6)]
  • Loading branch information
romanett authored Feb 29, 2024
1 parent 8d2be39 commit 76fe9c9
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 6 deletions.
19 changes: 19 additions & 0 deletions Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,25 @@ public void UnregisterApplication(NodeId applicationId)
applicationId);
}

/// <summary>
/// Revokes a Certificate issued to the Application by the CertificateManager
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="certificate">The certificate to revoke</param>
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);
}

/// <summary>
/// Requests a new certificate.
/// </summary>
Expand Down
60 changes: 54 additions & 6 deletions Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,19 +246,24 @@ private ICertificateGroup GetGroupForCertificate(byte[] certificate)
return null;
}

private async Task RevokeCertificateAsync(byte[] certificate)
private async Task<bool> 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)
{
Expand All @@ -267,6 +272,7 @@ private async Task RevokeCertificateAsync(byte[] certificate)
}
}
}
return revoked;
}

protected async Task<ICertificateGroup> InitializeCertificateGroup(CertificateGroupConfiguration certificateGroupConfiguration)
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal static class AuthorizationHelper
internal static List<Role> DiscoveryAdmin { get; } = new List<Role> { GdsRole.DiscoveryAdmin };
internal static List<Role> AuthenticatedUserOrSelfAdmin { get; } = new List<Role> { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin };
internal static List<Role> CertificateAuthorityAdminOrSelfAdmin { get; } = new List<Role> { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin };
internal static List<Role> CertificateAuthorityAdmin { get; } = new List<Role> { GdsRole.CertificateAuthorityAdmin };

/// <summary>
/// Checks if the current session (context) has one of the requested roles. If <see cref="GdsRole.ApplicationSelfAdmin"/> is allowed the applicationId needs to be specified
Expand Down
18 changes: 18 additions & 0 deletions Tests/Opc.Ua.Gds.Tests/ClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down

0 comments on commit 76fe9c9

Please sign in to comment.