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 RevokeCertificate to Client and Server #2497

Merged
merged 6 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -618,6 +618,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
62 changes: 56 additions & 6 deletions Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,27 +246,33 @@ 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)
{
Utils.LogError(e, "Unexpected error revoking certificate. {0} for Authority={1}", x509.Subject, certificateGroup.Id);
Utils.LogError(e, "Unexpected error revoking certificate. {0} for Authority={1}", new X509Certificate2(certificate).Subject, certificateGroup.Id);
}
romanett marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
return revoked;
}

protected async Task<ICertificateGroup> InitializeCertificateGroup(CertificateGroupConfiguration certificateGroupConfiguration)
Expand Down Expand Up @@ -381,6 +387,8 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context

Opc.Ua.Gds.CertificateDirectoryState activeNode = new Opc.Ua.Gds.CertificateDirectoryState(passiveNode.Parent);

activeNode.RevokeCertificate = new RevokeCertificateMethodState(passiveNode);

activeNode.Create(context, passiveNode);
activeNode.QueryServers.OnCall = new QueryServersMethodStateMethodCallHandler(OnQueryServers);
activeNode.QueryApplications.OnCall = new QueryApplicationsMethodStateMethodCallHandler(OnQueryApplications);
Expand All @@ -395,8 +403,7 @@ protected override NodeState AddBehaviourToPredefinedNode(ISystemContext context
activeNode.GetTrustList.OnCall = new GetTrustListMethodStateMethodCallHandler(OnGetTrustList);
activeNode.GetCertificateStatus.OnCall = new GetCertificateStatusMethodStateMethodCallHandler(OnGetCertificateStatus);
activeNode.StartSigningRequest.OnCall = new StartSigningRequestMethodStateMethodCallHandler(OnStartSigningRequest);
// TODO
//activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate);
activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate);
romanett marked this conversation as resolved.
Show resolved Hide resolved

activeNode.CertificateGroups.DefaultApplicationGroup.CertificateTypes.Value = new NodeId[] { Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType };
activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.LastUpdateTime.Value = DateTime.UtcNow;
Expand Down Expand Up @@ -558,6 +565,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.");
}

romanett marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1297,6 +1297,24 @@ public void GetInvalidCertificateStatus()
}
}

[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
Loading