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 GetCertificates to GDS for Pull Support and ServerConfiguration for Push Support / Fix CheckRevocationStatus #2553

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs
Expand Up @@ -584,6 +584,43 @@
return null;
}

/// <summary>
/// Returns the Certificates assigned to Application and associated with the CertificateGroup.
/// </summary>
/// <param name="applicationId">The identifier assigned to the Application by the GDS.</param>
/// <param name="certificateGroupId">An identifier for the CertificateGroup that the Certificates belong to.
///If null, the CertificateManager shall return the Certificates for all CertificateGroups assigned to the Application.</param>
/// <param name="certificateTypeIds">The CertificateTypes that currently have a Certificate assigned.
/// The length of this list is the same as the length as certificates list.</param>
/// <param name="certificates">A list of DER encoded Certificates assigned to Application.
/// This list only includes Certificates that are currently valid.</param>
public void GetCertificates(
NodeId applicationId,
NodeId certificateGroupId,
out NodeId[] certificateTypeIds,
out byte[][] certificates)
{
certificateTypeIds = Array.Empty<NodeId>();
certificates = Array.Empty<byte[]>();

if (!IsConnected)
{
Connect();

Check warning on line 608 in Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Client.Common/GlobalDiscoveryServerClient.cs#L608

Added line #L608 was not covered by tests
}

var outputArguments = Session.Call(
ExpandedNodeId.ToNodeId(Opc.Ua.Gds.ObjectIds.Directory, Session.NamespaceUris),
ExpandedNodeId.ToNodeId(Opc.Ua.Gds.MethodIds.CertificateDirectoryType_GetCertificates, Session.NamespaceUris),
applicationId,
certificateGroupId);

if (outputArguments.Count >= 2)
{
certificateTypeIds = outputArguments[0] as NodeId[];
certificates = outputArguments[1] as byte[][];
}
}

/// <summary>
/// Checks the provided certificate for validity
/// </summary>
Expand Down
Expand Up @@ -561,6 +561,47 @@
}
}

/// <summary>
/// returns the Certificates assigned to CertificateTypes associated with a CertificateGroup.
/// </summary>
/// <param name="certificateGroupId">The identifier for the CertificateGroup.</param>
/// <param name="certificateTypeIds">The CertificateTypes that currently have a Certificate assigned.
///The length of this list is the same as the length as certificates list.
///An empty list if the CertificateGroup does not have any CertificateTypes.</param>
/// <param name="certificates">A list of DER encoded Certificates assigned to CertificateGroup.
///The certificateType for the Certificate is specified by the corresponding element in the certificateTypes parameter.</param>
public void GetCertificates(
NodeId certificateGroupId,
out NodeId[] certificateTypeIds,
out byte[][] certificates)
{
certificateTypeIds = Array.Empty<NodeId>();
certificates = Array.Empty<byte[]>();
if (!IsConnected)
{
Connect();

Check warning on line 582 in Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Client.Common/ServerPushConfigurationClient.cs#L582

Added line #L582 was not covered by tests
}

IUserIdentity oldUser = ElevatePermissions();
try
{
var outputArguments = m_session.Call(
ExpandedNodeId.ToNodeId(Opc.Ua.ObjectIds.ServerConfiguration, m_session.NamespaceUris),
ExpandedNodeId.ToNodeId(Opc.Ua.MethodIds.ServerConfigurationType_GetCertificates, m_session.NamespaceUris),
certificateGroupId
);
if (outputArguments.Count >= 2)
{
certificateTypeIds = outputArguments[0] as NodeId[];
certificates = outputArguments[1] as byte[][];
}
}
finally
{
RevertPermissions(oldUser);
}
}

/// <summary>
/// Creates the CSR.
/// </summary>
Expand Down
Expand Up @@ -672,7 +672,6 @@ byte[] certificate

Guid id = GetNodeIdGuid(applicationId);

List<byte[]> certificates = new List<byte[]>();

lock (Lock)
{
Expand Down
146 changes: 130 additions & 16 deletions Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs
Expand Up @@ -31,12 +31,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Opc.Ua.Gds.Server.Database;
using Opc.Ua.Gds.Server.Diagnostics;
using Opc.Ua.Server;
using Org.BouncyCastle.Tls;

namespace Opc.Ua.Gds.Server
{
Expand Down Expand Up @@ -389,7 +391,8 @@
Opc.Ua.Gds.CertificateDirectoryState activeNode = new Opc.Ua.Gds.CertificateDirectoryState(passiveNode.Parent);

activeNode.RevokeCertificate = new RevokeCertificateMethodState(passiveNode);
activeNode.CheckRevocationStatus = new CheckRevocationStatusMethodState(passiveNode.Parent);
activeNode.CheckRevocationStatus = new CheckRevocationStatusMethodState(passiveNode);
activeNode.GetCertificates = new GetCertificatesMethodState(passiveNode);

activeNode.Create(context, passiveNode);
activeNode.QueryServers.OnCall = new QueryServersMethodStateMethodCallHandler(OnQueryServers);
Expand All @@ -407,6 +410,7 @@
activeNode.StartSigningRequest.OnCall = new StartSigningRequestMethodStateMethodCallHandler(OnStartSigningRequest);
activeNode.RevokeCertificate.OnCall = new RevokeCertificateMethodStateMethodCallHandler(OnRevokeCertificate);
activeNode.CheckRevocationStatus.OnCall = new CheckRevocationStatusMethodStateMethodCallHandler(OnCheckRevocationStatus);
activeNode.GetCertificates.OnCall = new GetCertificatesMethodStateMethodCallHandler(OnGetCertificates);

activeNode.CertificateGroups.DefaultApplicationGroup.CertificateTypes.Value = new NodeId[] { Opc.Ua.ObjectTypeIds.RsaSha256ApplicationCertificateType };
activeNode.CertificateGroups.DefaultApplicationGroup.TrustList.LastUpdateTime.Value = DateTime.UtcNow;
Expand Down Expand Up @@ -659,31 +663,141 @@
{
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
//TODO return When the result expires and should be rechecked.
validityTime = DateTime.MinValue;

using (var x509 = new X509Certificate2(certificate))
try
{
try
//create chain to validate Certificate against it
var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;

//add GDS Issuer Cert Store Certificates to the Chain validation for consitent behaviour on all Platforms
using (ICertificateStore store = CertificateStoreIdentifier.OpenStore(m_configuration.SecurityConfiguration.TrustedIssuerCertificates.StorePath))
{
certificateValidator.Validate(x509);
chain.ChainPolicy.ExtraStore.AddRange(store.Enumerate().GetAwaiter().GetResult());
}
catch (ServiceResultException se)
using (var x509 = new X509Certificate2(certificate))
{
certificateStatus = se.StatusCode;
if (chain.Build(x509))
{
certificateStatus = StatusCodes.Good;
return ServiceResult.Good;

Check warning on line 686 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L685-L686

Added lines #L685 - L686 were not covered by tests
}
else
{
//Assing certificateStatus for invalid chain if no matching found use StatusCodes.BadCertificateRevoked
switch (chain.ChainStatus.FirstOrDefault().Status)
{
case X509ChainStatusFlags.NotTimeValid:
certificateStatus = StatusCodes.BadCertificateTimeInvalid;
break;

Check warning on line 695 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L694-L695

Added lines #L694 - L695 were not covered by tests
case X509ChainStatusFlags.Revoked:
certificateStatus = StatusCodes.BadCertificateRevoked;
break;

Check warning on line 698 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L697-L698

Added lines #L697 - L698 were not covered by tests
case X509ChainStatusFlags.NotSignatureValid:
certificateStatus = StatusCodes.BadCertificateInvalid;
break;

Check warning on line 701 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L700-L701

Added lines #L700 - L701 were not covered by tests
case X509ChainStatusFlags.NotValidForUsage:
certificateStatus = StatusCodes.BadCertificateUseNotAllowed;
break;

Check warning on line 704 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L703-L704

Added lines #L703 - L704 were not covered by tests
case X509ChainStatusFlags.RevocationStatusUnknown:
certificateStatus = StatusCodes.BadCertificateRevocationUnknown;
break;
case X509ChainStatusFlags.PartialChain:
certificateStatus = StatusCodes.BadCertificateChainIncomplete;
break;

Check warning on line 710 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L709-L710

Added lines #L709 - L710 were not covered by tests
case X509ChainStatusFlags.ExplicitDistrust:
certificateStatus = StatusCodes.BadCertificateUntrusted;
break;

Check warning on line 713 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L712-L713

Added lines #L712 - L713 were not covered by tests
//cases not in the OPC UA Status codes -> default to BadCertificateRevoked
case X509ChainStatusFlags.NoError:
case X509ChainStatusFlags.UntrustedRoot:
case X509ChainStatusFlags.NotTimeNested:
case X509ChainStatusFlags.Cyclic:
case X509ChainStatusFlags.InvalidExtension:
case X509ChainStatusFlags.InvalidPolicyConstraints:
case X509ChainStatusFlags.InvalidBasicConstraints:
case X509ChainStatusFlags.InvalidNameConstraints:
case X509ChainStatusFlags.HasNotSupportedNameConstraint:
case X509ChainStatusFlags.HasNotDefinedNameConstraint:
case X509ChainStatusFlags.HasNotPermittedNameConstraint:
case X509ChainStatusFlags.HasExcludedNameConstraint:
case X509ChainStatusFlags.CtlNotTimeValid:
case X509ChainStatusFlags.CtlNotSignatureValid:
case X509ChainStatusFlags.CtlNotValidForUsage:
case X509ChainStatusFlags.OfflineRevocation:
case X509ChainStatusFlags.NoIssuanceChainPolicy:
case X509ChainStatusFlags.HasNotSupportedCriticalExtension:
case X509ChainStatusFlags.HasWeakSignature:
default:
certificateStatus = StatusCodes.BadCertificateRevoked;
break;
}
}
}
}
catch (CryptographicException)

Check warning on line 741 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L741

Added line #L741 was not covered by tests
{
certificateStatus = StatusCodes.BadCertificateRevoked;
}

Check warning on line 744 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L743-L744

Added lines #L743 - L744 were not covered by tests

return ServiceResult.Good;
}

private ServiceResult OnGetCertificates(
ISystemContext context,
MethodState method,
NodeId objectId,
NodeId applicationId,
NodeId certificateGroupId,
ref NodeId[] certificateTypeIds,
ref byte[][] certificates)
{
AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin);

var certificateTypeIdsList = new List<NodeId>();
var certificatesList = new List<byte[]>();

if (m_database.GetApplication(applicationId) == null)
{
return new ServiceResult(StatusCodes.BadNotFound, "The ApplicationId does not refer to a registered application.");

Check warning on line 765 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L765

Added line #L765 was not covered by tests
}

//If CertificateGroupId is null, the CertificateManager shall return the Certificates for all CertificateGroups assigned to the Application.
if (certificateGroupId == null)
{
foreach (KeyValuePair<NodeId, string> certType in m_certTypeMap)
{
if (m_database.GetApplicationCertificate(applicationId, certType.Value, out byte[] certificate) && certificate != null)
{
certificateTypeIdsList.Add(certType.Key);
certificatesList.Add(certificate);
}
}
}
//get only Certificate of the provided CertificateGroup
else
{
if (!m_certificateGroups.TryGetValue(certificateGroupId, out ICertificateGroup certificateGroup)
|| !m_certTypeMap.TryGetValue(certificateGroup.CertificateType, out string certificateTypeId))

Check warning on line 784 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L784

Added line #L784 was not covered by tests
{
return new ServiceResult(StatusCodes.BadInvalidArgument, "The CertificateGroupId is not recognized or not valid for the Application.");

Check warning on line 786 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L786

Added line #L786 was not covered by tests
}
if (m_database.GetApplicationCertificate(applicationId, certificateTypeId, out byte[] certificate) && certificate != null)
{
certificateTypeIdsList.Add(certificateGroup.CertificateType);
certificatesList.Add(certificate);

Check warning on line 791 in Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs#L790-L791

Added lines #L790 - L791 were not covered by tests
}
}

certificates = certificatesList.ToArray();
certificateTypeIds = certificateTypeIdsList.ToArray();

return ServiceResult.Good;
}

private ServiceResult CheckHttpsDomain(ApplicationRecordDataType application, string commonName)
{
if (application.ApplicationType == ApplicationType.Client)
Expand Down Expand Up @@ -1391,7 +1505,7 @@
handle.Validated = true;
return handle.Node;
}
#endregion
#endregion

#region Overridden Methods
#endregion
Expand Down
41 changes: 41 additions & 0 deletions Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs
Expand Up @@ -105,6 +105,9 @@
case ObjectTypes.ServerConfigurationType:
{
ServerConfigurationState activeNode = new ServerConfigurationState(passiveNode.Parent);

activeNode.GetCertificates = new GetCertificatesMethodState(activeNode);

activeNode.Create(context, passiveNode);

m_serverConfigurationNode = activeNode;
Expand Down Expand Up @@ -202,6 +205,7 @@
m_serverConfigurationNode.CreateSigningRequest.OnCall = new CreateSigningRequestMethodStateMethodCallHandler(CreateSigningRequest);
m_serverConfigurationNode.ApplyChanges.OnCallMethod = new GenericMethodCalledEventHandler(ApplyChanges);
m_serverConfigurationNode.GetRejectedList.OnCall = new GetRejectedListMethodStateMethodCallHandler(GetRejectedList);
m_serverConfigurationNode.GetCertificates.OnCall = new GetCertificatesMethodStateMethodCallHandler(GetCertificates);
m_serverConfigurationNode.ClearChangeMasks(systemContext, true);

// setup certificate group trust list handlers
Expand All @@ -227,6 +231,8 @@
}
}



/// <summary>
/// Gets and returns the <see cref="NamespaceMetadataState"/> node associated with the specified NamespaceUri
/// </summary>
Expand Down Expand Up @@ -611,6 +617,41 @@
return StatusCodes.Good;
}

private ServiceResult GetCertificates(
ISystemContext context,
MethodState method,
NodeId objectId,
NodeId certificateGroupId,
ref NodeId[] certificateTypeIds,
ref byte[][] certificates)
{
HasApplicationSecureAdminAccess(context);

ServerCertificateGroup certificateGroup = m_certificateGroups.FirstOrDefault(group => Utils.IsEqual(group.NodeId, certificateGroupId));
if (certificateGroup == null)
{
throw new ServiceResultException(StatusCodes.BadInvalidArgument, "Certificate group invalid.");
}

NodeId certificateTypeId = certificateGroup.CertificateTypes.FirstOrDefault();

//TODO support multiple Application Instance Certificates
if (certificateTypeId != null)
{
certificateTypeIds = new NodeId[1] {certificateTypeId };
certificates = new byte[1][];
certificates[0] = certificateGroup.ApplicationCertificate.Certificate.GetRawCertData();
}
else
{
certificateTypeIds = new NodeId[0];
certificates = new byte[0][];

Check warning on line 648 in Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs

View check run for this annotation

Codecov / codecov/patch

Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs#L647-L648

Added lines #L647 - L648 were not covered by tests
}

return ServiceResult.Good;
}


private ServerCertificateGroup VerifyGroupAndTypeId(
NodeId certificateGroupId,
NodeId certificateTypeId
Expand Down
10 changes: 7 additions & 3 deletions Tests/Opc.Ua.Gds.Tests/CertificateGroupTests.cs
Expand Up @@ -20,14 +20,18 @@ public class CertificateGroupTests

private string _path;

public CertificateGroupTests()
[SetUp]
public void Setup()
{
_path = Utils.ReplaceSpecialFolderNames("%LocalApplicationData%/OPC/GDS/TestStore");
}

[TearDown]
public void Dispose()
{
Directory.Delete(_path, true);
if (Directory.Exists(_path))
{
Directory.Delete(_path, true);
}
}
#region Test Methods

Expand Down