From 214ede94e2ff272072c999278a95ab8db59f2a60 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 11 Oct 2023 14:58:14 +0200 Subject: [PATCH 01/32] implement ApplicationSelfAdmin privilege in GlobalDiscoverySampleServer --- .../GlobalDiscoverySampleServer.cs | 51 +++++++++++++++++++ .../RoleBasedIdentity.cs | 7 ++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 630f0310c2..3d3e36af5f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -33,6 +33,8 @@ using System.Threading; using Opc.Ua.Server; using Opc.Ua.Gds.Server.Database; +using Opc.Ua.Security.Certificates; +using Org.BouncyCastle.Asn1.Cmp; namespace Opc.Ua.Gds.Server { @@ -232,6 +234,55 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role); return; } + + if(session.ClientCertificate != null) + { + GdsRole role = GdsRole.ApplicationSelfAdmin; + if (VerifiyApplicationRegistered(session.ClientCertificate)) + { + Utils.LogInfo("Application accepted based on certificate: {0} as {1}", + session.ClientCertificate.SubjectName.ToString(), role.ToString()); + args.Identity = new RoleBasedIdentity(new UserIdentity(),role); + return; + } + } + } + + //Verifies if an Application is registered with the provided certificate at at the GDS + private bool VerifiyApplicationRegistered(X509Certificate2 applicationInstanceCertificate) + { + bool applicationRegistered = false; + //get access to GDS configuration section to find out ApplicationCertificatesStorePath + GlobalDiscoveryServerConfiguration configuration = Configuration.ParseExtension(); + if (configuration == null) + { + configuration = new GlobalDiscoveryServerConfiguration(); + } + //check if application certificate is in the Store of the GDS + using (ICertificateStore ApplicationsStore = CertificateStoreIdentifier.OpenStore(configuration.ApplicationCertificatesStorePath)) + { + var matchingCerts = ApplicationsStore.FindByThumbprint(applicationInstanceCertificate.Thumbprint).Result; + + if (matchingCerts.Contains(applicationInstanceCertificate)) + applicationRegistered = true; + } + //check if application certificate is revoked + using (ICertificateStore AuthoritiesStore = CertificateStoreIdentifier.OpenStore(configuration.AuthoritiesStorePath)) + { + var crls = AuthoritiesStore.EnumerateCRLs().Result; + foreach (X509CRL crl in crls) + { + var revokedCertificates = crl.RevokedCertificates; + foreach (RevokedCertificate revokedCertificate in revokedCertificates) + { + if(revokedCertificate.SerialNumber == applicationInstanceCertificate.SerialNumber) + { + applicationRegistered = false; + } + } + } + } + return applicationRegistered; } /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs index e8b7f03b08..22c9638188 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs @@ -44,7 +44,12 @@ public enum GdsRole /// /// The GDS application user. /// - ApplicationUser + ApplicationUser, + + /// + /// Can manage the own Certificates & pull trust list + /// + ApplicationSelfAdmin } /// From 7e6bcde865d0de0d230bd90d89b3cf54cfd4e805 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 11 Oct 2023 15:37:53 +0200 Subject: [PATCH 02/32] improve documentation --- .../Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs | 1 + Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 3d3e36af5f..1c5640829c 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -235,6 +235,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg return; } + //check if applicable for application self admin privilege if(session.ClientCertificate != null) { GdsRole role = GdsRole.ApplicationSelfAdmin; diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs index 22c9638188..67d076d2eb 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs @@ -47,7 +47,7 @@ public enum GdsRole ApplicationUser, /// - /// Can manage the own Certificates & pull trust list + /// Can manage the own Certificates and pull trust list /// ApplicationSelfAdmin } From 8d505471715f3cf0616b90be103011c1d7059a47 Mon Sep 17 00:00:00 2001 From: romanett Date: Tue, 17 Oct 2023 12:06:03 +0200 Subject: [PATCH 03/32] fix: enforce ApplicationSelfAdminPrivilege --- .../ApplicationsNodeManager.cs | 35 +++++++++++++--- .../GlobalDiscoverySampleServer.cs | 40 +++++++++++++++---- .../RoleBasedIdentity.cs | 16 ++++++++ 3 files changed, 78 insertions(+), 13 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 5450f48a5b..ce8ff6ec15 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -188,6 +188,29 @@ private void HasApplicationUserAccess(ISystemContext context) } } + /// + /// checks if the given Application can be modified with the current context + /// + /// the current context + /// the application to modify + private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId applicationId) + { + HasApplicationUserAccess(context); + if (context != null) + { + RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + //administrator has full access + if (identity.Role == GdsRole.ApplicationAdmin) + return; + //not administrator only has access to specified application + if (identity.ApplicationId != applicationId) + { + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " + + "Application Administrator access required."); + } + } + } + private NodeId GetTrustListId(NodeId certificateGroupId) { @@ -760,7 +783,7 @@ private ServiceResult OnStartNewKeyPairRequest( string privateKeyPassword, ref NodeId requestId) { - HasApplicationAdminAccess(context); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -879,7 +902,7 @@ private ServiceResult OnStartSigningRequest( byte[] certificateRequest, ref NodeId requestId) { - HasApplicationAdminAccess(context); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -960,7 +983,7 @@ private ServiceResult OnFinishRequest( signedCertificate = null; issuerCertificates = null; privateKey = null; - HasApplicationAdminAccess(context); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); if (application == null) @@ -1126,7 +1149,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - HasApplicationUserAccess(context); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1154,7 +1177,7 @@ public ServiceResult OnGetTrustList( NodeId certificateGroupId, ref NodeId trustListId) { - HasApplicationUserAccess(context); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1187,7 +1210,7 @@ public ServiceResult OnGetCertificateStatus( NodeId certificateTypeId, ref Boolean updateRequired) { - HasApplicationUserAccess(context); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 1c5640829c..7d16efbfd8 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -35,6 +35,8 @@ using Opc.Ua.Gds.Server.Database; using Opc.Ua.Security.Certificates; using Org.BouncyCastle.Asn1.Cmp; +using System.Linq; +using System.Data; namespace Opc.Ua.Gds.Server { @@ -231,25 +233,27 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg // role = GdsRole.ApplicationAdmin; Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, role.ToString()); - args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role); + + string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri; + args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role, applicationUri); return; } //check if applicable for application self admin privilege if(session.ClientCertificate != null) { - GdsRole role = GdsRole.ApplicationSelfAdmin; if (VerifiyApplicationRegistered(session.ClientCertificate)) { - Utils.LogInfo("Application accepted based on certificate: {0} as {1}", - session.ClientCertificate.SubjectName.ToString(), role.ToString()); - args.Identity = new RoleBasedIdentity(new UserIdentity(),role); - return; + ImpersonateAsApplicationSelfAdmin(session, args); } } } - //Verifies if an Application is registered with the provided certificate at at the GDS + /// + /// Verifies if an Application is registered with the provided certificate at at the GDS + /// + /// the certificate to check for + /// private bool VerifiyApplicationRegistered(X509Certificate2 applicationInstanceCertificate) { bool applicationRegistered = false; @@ -334,6 +338,28 @@ private bool VerifyPassword(UserNameIdentityToken userNameToken) // TODO: check username/password permissions return userNameToken.DecryptedPassword == "demo"; } + + /// + /// Impersonates the current Session as ApplicationSelfAdmin + /// + /// the current session + /// the impersonateEventArgs + private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEventArgs args) + { + string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri; + ApplicationRecordDataType[] application = m_database.FindApplications(applicationUri); + if(application == null || application.Length>1 || application.Length<1) + { + Utils.LogInfo("Cannot login based on ApplicationInstanceCertificate, no uniqure result for Application with URI: {0}", applicationUri); + return; + } + NodeId applicationId = application.FirstOrDefault().ApplicationId; + Utils.LogInfo("Application {0} accepted based on ApplicationInstanceCertificate as ApplicationSelfAdmin", + applicationUri); + args.Identity = new RoleBasedIdentity(new UserIdentity(), GdsRole.ApplicationSelfAdmin, applicationId); + return; + } + #endregion #region Private Fields diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs index 67d076d2eb..bab318b7bf 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedIdentity.cs @@ -27,6 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using System; using System.Xml; namespace Opc.Ua.Gds.Server @@ -59,6 +60,7 @@ public class RoleBasedIdentity : IUserIdentity { private IUserIdentity m_identity; private GdsRole m_role; + private NodeId m_applicationId; /// /// Initialize the role based identity. @@ -69,6 +71,12 @@ public RoleBasedIdentity(IUserIdentity identity, GdsRole role) m_role = role; } + public RoleBasedIdentity(IUserIdentity identity, GdsRole role, NodeId applicationId) + :this(identity, role) + { + m_applicationId = applicationId; + } + /// public NodeIdCollection GrantedRoleIds { @@ -84,6 +92,14 @@ public GdsRole Role get { return m_role; } } + /// + /// The applicationId in case the ApplicationSelfAdminPrivilege is used + /// + public NodeId ApplicationId + { + get { return m_applicationId; } + } + /// public string DisplayName { From 8d4671dfe02fbaf10fbf3a518c5367b425f448cf Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Fri, 17 Nov 2023 14:32:41 +0100 Subject: [PATCH 04/32] add Test for ApplicationSelf Admin with disabled flag --- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 31 +++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 7898f5c5c2..be8a41713a 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -85,6 +85,7 @@ protected async Task OneTimeSetUp() m_goodRegistrationOk = false; m_invalidRegistrationOk = false; m_goodNewKeyPairRequestOk = false; + m_runApplicationSelfAdminTests = false; } /// @@ -735,7 +736,9 @@ out byte[][] issuerCertificates } while (requestBusy); } - [Test, Order(511)] + + + [Test, Order(512)] public void FinishInvalidNewKeyPairRequests() { AssertIgnoreTestWithoutInvalidRegistration(); @@ -941,6 +944,31 @@ public void GetGoodCertificateGroupsAndTrustLists() Assert.NotNull(trustList); } } + + if (m_runApplicationSelfAdminTests) + { + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + DisconnectGDS(); + //connect to GDS without Admin Privilege + ConnectGDS(false); + + foreach (var application in m_goodApplicationTestSet) + { + if (application.Certificate != null) + { + var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId); + foreach (var certificateGroup in certificateGroups) + { + var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); + // Opc.Ua.TrustListDataType + var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); + Assert.NotNull(trustList); + } + } + } + + } } [Test, Order(690)] @@ -1094,6 +1122,7 @@ private int GoodServersOnNetworkCount() private bool m_goodRegistrationOk; private bool m_invalidRegistrationOk; private bool m_goodNewKeyPairRequestOk; + private bool m_runApplicationSelfAdminTests; #endregion } From 5dcf8ec6cc4dca751a069367e43f803d1bf93eae Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Thu, 7 Dec 2023 16:05:13 +0100 Subject: [PATCH 05/32] update tests with template for additional required tests --- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 70 +++++++++++++++++++--------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index be8a41713a..47d0843caf 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -85,7 +85,6 @@ protected async Task OneTimeSetUp() m_goodRegistrationOk = false; m_invalidRegistrationOk = false; m_goodNewKeyPairRequestOk = false; - m_runApplicationSelfAdminTests = false; } /// @@ -736,7 +735,7 @@ out byte[][] issuerCertificates } while (requestBusy); } - + [Test, Order(512)] public void FinishInvalidNewKeyPairRequests() @@ -944,31 +943,59 @@ public void GetGoodCertificateGroupsAndTrustLists() Assert.NotNull(trustList); } } + } - if (m_runApplicationSelfAdminTests) - { - AssertIgnoreTestWithoutGoodRegistration(); - AssertIgnoreTestWithoutGoodNewKeyPairRequest(); - DisconnectGDS(); - //connect to GDS without Admin Privilege - ConnectGDS(false); + [Test, Order(620)] + public void FailToGetGoodCertificateGroupsWithoutPriviledges() + { + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); - foreach (var application in m_goodApplicationTestSet) + //connect to GDS without Admin Privilege + ConnectGDS(false); + + foreach (var application in m_goodApplicationTestSet) + { + if (application.Certificate != null) { - if (application.Certificate != null) - { - var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId); - foreach (var certificateGroup in certificateGroups) - { - var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); - // Opc.Ua.TrustListDataType - var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); - Assert.NotNull(trustList); - } - } + var sre = Assert.Throws(() => m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId)); + Assert.NotNull(sre); + Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); + } + } + } + + [Test, Order(630)] + public void GetGoodCertificateGroupsAsSelfAdmin() + { + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + // TODO: connect to GDS as self registered application + // connect with issued certificate + ConnectGDS(false); + + // ensure access to other applications is denied + foreach (var application in m_goodApplicationTestSet) + { + if (application.Certificate != null) + { + var sre = Assert.Throws(() => m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId)); + Assert.NotNull(sre); + Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); + } } + + Assert.Ignore("TODO: Tests for other logic is not implemented yet!"); + + // use self registered application and get the group / trust lists + + // self issue a certificate and read it back + + // self issue a public/private key pair and read it back + + } [Test, Order(690)] @@ -1122,7 +1149,6 @@ private int GoodServersOnNetworkCount() private bool m_goodRegistrationOk; private bool m_invalidRegistrationOk; private bool m_goodNewKeyPairRequestOk; - private bool m_runApplicationSelfAdminTests; #endregion } From dd4bb7f926b2d8f5ee794fb4724260792e472fcb Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 14 Jan 2024 20:06:49 +0100 Subject: [PATCH 06/32] prepate Testing of application self admin privilege --- .../ApplicationsNodeManager.cs | 36 +++++++---- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 28 ++++++-- .../GlobalDiscoveryTestClient.cs | 64 +++++++++++++++++-- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index b34612976a..55f1e9f3a0 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -167,11 +167,14 @@ private void HasApplicationAdminAccess(ISystemContext context) if (context != null) { RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if ((identity == null) || (identity.Role != GdsRole.ApplicationAdmin)) + if (identity != null) { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); + if (identity.Role == GdsRole.ApplicationAdmin) + { + return; + } } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); } } @@ -181,10 +184,14 @@ private void HasApplicationUserAccess(ISystemContext context) { RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - if (identity == null) + if (identity != null) { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required."); + if ((identity.Role == GdsRole.ApplicationUser) || (identity.Role == GdsRole.ApplicationAdmin)) + { + return; + } } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); } } @@ -195,19 +202,22 @@ private void HasApplicationUserAccess(ISystemContext context) /// the application to modify private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId applicationId) { - HasApplicationUserAccess(context); if (context != null) { RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - //administrator has full access - if (identity.Role == GdsRole.ApplicationAdmin) - return; - //not administrator only has access to specified application - if (identity.ApplicationId != applicationId) + if (identity != null) { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " + - "Application Administrator access required."); + //administrator/user has full access + if (identity.Role == GdsRole.ApplicationAdmin || identity.Role == GdsRole.ApplicationUser) + return; + //not administrator/user only has access to own application + if (identity.ApplicationId == applicationId) + { + return; + } } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " + + "Application Administrator/User access required."); } } diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 47d0843caf..1cda45fc40 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -972,9 +972,10 @@ public void GetGoodCertificateGroupsAsSelfAdmin() AssertIgnoreTestWithoutGoodRegistration(); AssertIgnoreTestWithoutGoodNewKeyPairRequest(); - // TODO: connect to GDS as self registered application - // connect with issued certificate - ConnectGDS(false); + + // connect with gds issued certificate + m_gdsClient.RegisterTestClientAtGds(); + ConnectGDS(false, true); // ensure access to other applications is denied foreach (var application in m_goodApplicationTestSet) @@ -988,8 +989,16 @@ public void GetGoodCertificateGroupsAsSelfAdmin() } Assert.Ignore("TODO: Tests for other logic is not implemented yet!"); - + // use self registered application and get the group / trust lists + var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(m_gdsClient.OwnApplicationTestData.ApplicationRecord.ApplicationId); + foreach (var certificateGroup in certificateGroups) + { + var trustListId = m_gdsClient.GDSClient.GetTrustList(m_gdsClient.OwnApplicationTestData.ApplicationRecord.ApplicationId, certificateGroup); + // Opc.Ua.TrustListDataType + var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); + Assert.NotNull(trustList); + } // self issue a certificate and read it back @@ -1089,11 +1098,18 @@ public void ServerLogResult() #endregion #region Private Methods - private void ConnectGDS(bool admin, + private void ConnectGDS(bool admin, bool anonymous = false, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "" ) { - m_gdsClient.GDSClient.AdminCredentials = admin ? m_gdsClient.AdminUser : m_gdsClient.AppUser; + if (anonymous) + { + m_gdsClient.GDSClient.AdminCredentials = m_gdsClient.Anonymous; + } + else + { + m_gdsClient.GDSClient.AdminCredentials = admin ? m_gdsClient.AdminUser : m_gdsClient.AppUser; + } m_gdsClient.GDSClient.Connect(m_gdsClient.GDSClient.EndpointUrl).Wait(); TestContext.Progress.WriteLine($"GDS Client({admin}) connected -- {memberName}"); } diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 218d624ae4..f3d0899f9e 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -49,11 +49,13 @@ public GlobalDiscoveryTestClient(bool autoAccept) public IUserIdentity AppUser { get; private set; } public IUserIdentity AdminUser { get; private set; } + public IUserIdentity Anonymous { get; private set; } + public ApplicationTestData OwnApplicationTestData { get; private set; } public ApplicationConfiguration Configuration { get; private set; } public async Task LoadClientConfiguration(int port = -1) { ApplicationInstance.MessageDlg = new ApplicationMessageDlg(); - ApplicationInstance application = new ApplicationInstance { + m_application = new ApplicationInstance { ApplicationName = "Global Discovery Client", ApplicationType = ApplicationType.Client, ConfigSectionName = "Opc.Ua.GlobalDiscoveryTestClient" @@ -61,7 +63,7 @@ public async Task LoadClientConfiguration(int port = -1) #if USE_FILE_CONFIG // load the application configuration. - Configuration = await application.LoadApplicationConfiguration(false).ConfigureAwait(false); + Configuration = await m_application.LoadApplicationConfiguration(false).ConfigureAwait(false); #else string root = Path.Combine("%LocalApplicationData%", "OPC"); string pkiRoot = Path.Combine(root, "pki"); @@ -74,7 +76,7 @@ public async Task LoadClientConfiguration(int port = -1) }; // build the application configuration. - Configuration = await application + Configuration = await m_application .Build( "urn:localhost:opcfoundation.org:GlobalDiscoveryTestClient", "http://opcfoundation.org/UA/GlobalDiscoveryTestClient") @@ -94,7 +96,7 @@ public async Task LoadClientConfiguration(int port = -1) .Create().ConfigureAwait(false); #endif // check the application certificate. - bool haveAppCertificate = await application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + bool haveAppCertificate = await m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); if (!haveAppCertificate) { throw new Exception("Application instance certificate invalid!"); @@ -115,6 +117,59 @@ public async Task LoadClientConfiguration(int port = -1) AppUser = new UserIdentity(gdsClientConfiguration.AppUserName, gdsClientConfiguration.AppPassword); } AdminUser = new UserIdentity(gdsClientConfiguration.AdminUserName, gdsClientConfiguration.AdminPassword); + Anonymous = new UserIdentity(); + } + /// + /// Register the Test Client at the used GDS, needed to test the ApplicationSelfAdminPrivilege + /// + /// + public bool RegisterTestClientAtGds() + { + //fill application record data type with own Data + OwnApplicationTestData = new ApplicationTestData { + ApplicationRecord = new ApplicationRecordDataType { + ApplicationUri = m_client.Configuration.ApplicationUri, + ApplicationType = m_client.Configuration.ApplicationType, + ProductUri = m_client.Configuration.ProductUri, + ApplicationNames = new LocalizedTextCollection() { new LocalizedText(m_client.Configuration.ApplicationName) }, + ApplicationId = new NodeId(Guid.NewGuid()) + }, + Subject = $"CN={m_client.Configuration.ApplicationName},DC=opc.tcp://localhost,O=OPC Foundation", + }; + + //connect + m_client.AdminCredentials = AdminUser; + m_client.Connect(m_client.EndpointUrl).Wait(); + //register + var id = m_client.RegisterApplication(OwnApplicationTestData.ApplicationRecord); + if (id == null) return false; + OwnApplicationTestData.ApplicationRecord.ApplicationId = id; + //request new Cert + var req_id = m_client.StartNewKeyPairRequest( + OwnApplicationTestData.ApplicationRecord.ApplicationId, + OwnApplicationTestData.CertificateGroupId, + OwnApplicationTestData.CertificateTypeId, + OwnApplicationTestData.Subject, + OwnApplicationTestData.DomainNames, + OwnApplicationTestData.PrivateKeyFormat, + OwnApplicationTestData.PrivateKeyPassword + ); + + if (req_id == null) return false; + OwnApplicationTestData.CertificateRequestId = req_id; + //get cert + byte[] certificate = m_client.FinishRequest( + OwnApplicationTestData.ApplicationRecord.ApplicationId, + OwnApplicationTestData.CertificateRequestId, + out _, + out _ + ); + if (certificate == null) return false; + //apply cert + m_client.Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(certificate); //signed cert + m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + var cert = m_client.Configuration.SecurityConfiguration.ApplicationCertificate; //switch back to non signed cert as CheckApplicationInstanceCertificate can´t find the cert + return true; } public void DisconnectClient() @@ -151,6 +206,7 @@ private static void CertificateValidator_CertificateValidation(CertificateValida } private GlobalDiscoveryServerClient m_client; + private ApplicationInstance m_application; } From 47e3e7adeb16e4e60e265e3cc07c924e67cfb7cb Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 15 Jan 2024 18:04:33 +0100 Subject: [PATCH 07/32] Set new ApplicationCertificate --- .../GlobalDiscoveryTestClient.cs | 145 ++++++++++++------ 1 file changed, 101 insertions(+), 44 deletions(-) diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index f3d0899f9e..812b8d9785 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -52,6 +52,7 @@ public GlobalDiscoveryTestClient(bool autoAccept) public IUserIdentity Anonymous { get; private set; } public ApplicationTestData OwnApplicationTestData { get; private set; } public ApplicationConfiguration Configuration { get; private set; } + #region public methods public async Task LoadClientConfiguration(int port = -1) { ApplicationInstance.MessageDlg = new ApplicationMessageDlg(); @@ -120,55 +121,39 @@ public async Task LoadClientConfiguration(int port = -1) Anonymous = new UserIdentity(); } /// - /// Register the Test Client at the used GDS, needed to test the ApplicationSelfAdminPrivilege - /// - /// +         /// Register the Test Client at the used GDS, needed to test the ApplicationSelfAdminPrivilege +         /// +         /// public bool RegisterTestClientAtGds() { - //fill application record data type with own Data - OwnApplicationTestData = new ApplicationTestData { - ApplicationRecord = new ApplicationRecordDataType { - ApplicationUri = m_client.Configuration.ApplicationUri, - ApplicationType = m_client.Configuration.ApplicationType, - ProductUri = m_client.Configuration.ProductUri, - ApplicationNames = new LocalizedTextCollection() { new LocalizedText(m_client.Configuration.ApplicationName) }, - ApplicationId = new NodeId(Guid.NewGuid()) - }, - Subject = $"CN={m_client.Configuration.ApplicationName},DC=opc.tcp://localhost,O=OPC Foundation", - }; + ApplicationTestData ownApplicationTestData = GetOwnApplicationData(); - //connect m_client.AdminCredentials = AdminUser; - m_client.Connect(m_client.EndpointUrl).Wait(); - //register - var id = m_client.RegisterApplication(OwnApplicationTestData.ApplicationRecord); - if (id == null) return false; - OwnApplicationTestData.ApplicationRecord.ApplicationId = id; - //request new Cert - var req_id = m_client.StartNewKeyPairRequest( - OwnApplicationTestData.ApplicationRecord.ApplicationId, - OwnApplicationTestData.CertificateGroupId, - OwnApplicationTestData.CertificateTypeId, - OwnApplicationTestData.Subject, - OwnApplicationTestData.DomainNames, - OwnApplicationTestData.PrivateKeyFormat, - OwnApplicationTestData.PrivateKeyPassword - ); +            //register +            NodeId id = Register(ownApplicationTestData); + if (id == null) + { + return false; + } + ownApplicationTestData.ApplicationRecord.ApplicationId = id; +            //start Key Pair Request +            NodeId req_id = StartNewKeyPair(ownApplicationTestData); + if (req_id == null) + { + return false; + } + + ownApplicationTestData.CertificateRequestId = req_id; +            //Finish KeyPairRequest +            byte[] certificate, privateKey; + FinishKeyPair(ownApplicationTestData, out certificate, out privateKey); + if (certificate == null || privateKey == null) + { + return false; + } +            //apply cert +            ApplyNewApplicationInstanceCertificate(certificate, privateKey); - if (req_id == null) return false; - OwnApplicationTestData.CertificateRequestId = req_id; - //get cert - byte[] certificate = m_client.FinishRequest( - OwnApplicationTestData.ApplicationRecord.ApplicationId, - OwnApplicationTestData.CertificateRequestId, - out _, - out _ - ); - if (certificate == null) return false; - //apply cert - m_client.Configuration.SecurityConfiguration.ApplicationCertificate = new CertificateIdentifier(certificate); //signed cert - m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); - var cert = m_client.Configuration.SecurityConfiguration.ApplicationCertificate; //switch back to non signed cert as CheckApplicationInstanceCertificate can´t find the cert return true; } @@ -188,7 +173,58 @@ public string ReadLogFile() { return File.ReadAllText(Utils.ReplaceSpecialFolderNames(Configuration.TraceConfiguration.OutputFilePath)); } + #endregion + #region Private Methods + private void ApplyNewApplicationInstanceCertificate(byte[] certificate, byte[] privateKey) + { + var cert = CertificateFactory.CreateCertificateWithPEMPrivateKey( + new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate), + privateKey); + m_client.Configuration.SecurityConfiguration.ApplicationCertificate.RawData = cert.RawData; + m_client.Configuration.SecurityConfiguration.ApplicationCertificate.Thumbprint = cert.Thumbprint; + var store = m_client.Configuration.SecurityConfiguration.ApplicationCertificate.OpenStore(); + store.Add(cert); + m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + } + private void FinishKeyPair(ApplicationTestData ownApplicationTestData, out byte[] certificate, out byte[] privateKey) + { + m_client.Connect(m_client.EndpointUrl).Wait(); +            //get cert +            certificate = m_client.FinishRequest( + ownApplicationTestData.ApplicationRecord.ApplicationId, + ownApplicationTestData.CertificateRequestId, + out privateKey, + out _ + ); + m_client.Disconnect(); + } + + private NodeId StartNewKeyPair(ApplicationTestData ownApplicationTestData) + { + m_client.Connect(m_client.EndpointUrl).Wait(); +            //request new Cert +            var req_id = m_client.StartNewKeyPairRequest( + ownApplicationTestData.ApplicationRecord.ApplicationId, + ownApplicationTestData.CertificateGroupId, + ownApplicationTestData.CertificateTypeId, + ownApplicationTestData.Subject, + ownApplicationTestData.DomainNames, + ownApplicationTestData.PrivateKeyFormat, + ownApplicationTestData.PrivateKeyPassword + ); + + m_client.Disconnect(); + return req_id; + } + + private NodeId Register(ApplicationTestData ownApplicationTestData) + { + m_client.Connect(m_client.EndpointUrl).Wait(); + var id = m_client.RegisterApplication(ownApplicationTestData.ApplicationRecord); + m_client.Disconnect(); + return id; + } private static void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e) { if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted) @@ -205,6 +241,27 @@ private static void CertificateValidator_CertificateValidation(CertificateValida } } + private ApplicationTestData GetOwnApplicationData() + { + ApplicationTestData + //fill application record data type with own Data + ownApplicationTestData = new ApplicationTestData { + ApplicationRecord = new ApplicationRecordDataType { + ApplicationUri = m_client.Configuration.ApplicationUri, + ApplicationType = m_client.Configuration.ApplicationType, + ProductUri = m_client.Configuration.ProductUri, + ApplicationNames = new LocalizedTextCollection() { new LocalizedText(m_client.Configuration.ApplicationName) }, + ApplicationId = new NodeId(Guid.NewGuid()) + }, + PrivateKeyFormat = "PEM", + Subject = $"CN={m_client.Configuration.ApplicationName},DC=opc.tcp://localhost,O=OPC Foundation", + }; + return ownApplicationTestData; + } + + #endregion + + private GlobalDiscoveryServerClient m_client; private ApplicationInstance m_application; From 2ca22ee9b845ecc80795ceaf52bf97ae10c74a80 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Thu, 18 Jan 2024 07:17:43 +0100 Subject: [PATCH 08/32] prepare merge --- .../GlobalDiscoverySampleServer.cs | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 06657a5633..117cbeae80 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -30,13 +30,11 @@ using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; -using System.Threading; using Opc.Ua.Server; using Opc.Ua.Gds.Server.Database; -using Opc.Ua.Security.Certificates; -using Org.BouncyCastle.Asn1.Cmp; +using Opc.Ua.Server.UserDatabase; using System.Linq; -using System.Data; +using Opc.Ua.Security.Certificates; namespace Opc.Ua.Gds.Server { @@ -58,7 +56,7 @@ public GlobalDiscoverySampleServer( IApplicationsDatabase database, ICertificateRequest request, ICertificateGroup certificateGroup, - IUsersDatabase userDatabase, + IUserDatabase userDatabase, bool autoApprove = true, bool createStandardUsers = true ) @@ -210,24 +208,21 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg Utils.LogInfo("SystemConfigurationAdmin Token Accepted: {0}", args.Identity.DisplayName); return; } - switch (m_userDatabase.GetUserRole(userNameToken.UserName)) + IEnumerable roles = m_userDatabase.GetUserRoles(userNameToken.UserName); + //GdsAdmin + if (roles.Contains(GdsRole.ApplicationAdmin)) { - // GDS administrator - case GdsRole.ApplicationAdmin: - { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationAdmin); - Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); - return; - } - - // GDS user - case GdsRole.ApplicationUser: - { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), GdsRole.ApplicationUser); - Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); - return; - } + args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationAdmin }); + Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); + return; } + //GdsUser + if (roles.Contains(GdsRole.ApplicationUser)) + { + args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationUser }); + Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); + return; + } } } @@ -235,16 +230,13 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg X509IdentityToken x509Token = args.NewIdentity as X509IdentityToken; if (x509Token != null) { - GdsRole role = GdsRole.ApplicationUser; VerifyUserTokenCertificate(x509Token.Certificate); // todo: is cert listed in admin list? then // role = GdsRole.ApplicationAdmin; - Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, role.ToString()); - - string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri; - args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), role, applicationUri); + Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, GdsRole.ApplicationUser); + args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), new List { GdsRole.ApplicationUser }); return; } @@ -353,9 +345,9 @@ private bool VerifyPassword(UserNameIdentityToken userNameToken) /// private void RegisterDefaultUsers() { - m_userDatabase.CreateUser("sysadmin", "demo", GdsRole.ApplicationAdmin); - m_userDatabase.CreateUser("appadmin", "demo", GdsRole.ApplicationAdmin); - m_userDatabase.CreateUser("appuser", "demo", GdsRole.ApplicationUser); + m_userDatabase.CreateUser("sysadmin", "demo", new List { GdsRole.ApplicationAdmin, Role.SecurityAdmin, Role.ConfigureAdmin }); + m_userDatabase.CreateUser("appadmin", "demo", new List { GdsRole.ApplicationAdmin }); + m_userDatabase.CreateUser("appuser", "demo", new List { GdsRole.ApplicationUser }); } /// @@ -386,7 +378,7 @@ private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEvent private IApplicationsDatabase m_database = null; private ICertificateRequest m_request = null; private ICertificateGroup m_certificateGroup = null; - private IUsersDatabase m_userDatabase = null; + private IUserDatabase m_userDatabase = null; private bool m_autoApprove; private bool m_createStandardUsers; #endregion From f82296a7db6ad15fd895bfdb6ae75f05f285705e Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Thu, 18 Jan 2024 07:37:17 +0100 Subject: [PATCH 09/32] fix issues from merge --- .../ApplicationsNodeManager.cs | 12 ++---- Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs | 37 ++++++++++++++++++- .../GlobalDiscoverySampleServer.cs | 8 ++-- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 659da1ff7c..80016bc972 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -170,12 +170,8 @@ private void HasApplicationAdminAccess(ISystemContext context) if ((identity == null) || (!identity.Roles.Contains(GdsRole.ApplicationAdmin))) { - if (identity.Role == GdsRole.ApplicationAdmin) - { - return; - } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); } } @@ -187,7 +183,7 @@ private void HasApplicationUserAccess(ISystemContext context) if (identity != null) { - if ((identity.Role == GdsRole.ApplicationUser) || (identity.Role == GdsRole.ApplicationAdmin)) + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (!identity.Roles.Contains(GdsRole.ApplicationUser)))) { return; } @@ -205,11 +201,11 @@ private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId app { if (context != null) { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + GdsRoleBasedIdentity identity = context.UserIdentity as GdsRoleBasedIdentity; if (identity != null) { //administrator/user has full access - if (identity.Role == GdsRole.ApplicationAdmin || identity.Role == GdsRole.ApplicationUser) + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (!identity.Roles.Contains(GdsRole.ApplicationUser)))) return; //not administrator/user only has access to own application if (identity.ApplicationId == applicationId) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs b/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs index 89279b2514..b9e0cffdd1 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs @@ -27,6 +27,9 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices.ComTypes; using Opc.Ua.Server; namespace Opc.Ua.Gds.Server @@ -44,15 +47,45 @@ public class GdsRole : Role /// /// This Role grants rights to register, update and unregister any OPC UA Application. /// - public static Role DiscoveryAdmin { get; } = new Role( NodeId.Null, "DiscoveryAdmin"); + public static Role DiscoveryAdmin { get; } = new Role(NodeId.Null, "DiscoveryAdmin"); /// /// The GDS application user. /// public static Role ApplicationUser { get; } = new Role(NodeId.Null, "ApplicationUser"); - public GdsRole(NodeId roleId, string name): + /// + /// Can manage the own Certificates and pull trust list + /// + public static Role ApplicationSelfAdmin { get; } = new Role(NodeId.Null, "ApplicationSelfAdmin"); + + public GdsRole(NodeId roleId, string name) : base(roleId, name) + { } + } + /// + /// RoleBasedIdentity with additional Property ApplicationId + /// + public class GdsRoleBasedIdentity : RoleBasedIdentity + { + private NodeId m_applicationId; + + public GdsRoleBasedIdentity(IUserIdentity identity, IEnumerable roles, NodeId applicationId) + : base(identity, roles) + { + m_applicationId = applicationId; + } + + public GdsRoleBasedIdentity(IUserIdentity identity, IEnumerable roles) + : base(identity, roles) {} + + /// + /// The applicationId in case the ApplicationSelfAdminPrivilege is used + /// + public NodeId ApplicationId + { + get { return m_applicationId; } + } } } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 117cbeae80..2ab64db984 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -212,14 +212,14 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg //GdsAdmin if (roles.Contains(GdsRole.ApplicationAdmin)) { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationAdmin }); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationAdmin }); Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); return; } //GdsUser if (roles.Contains(GdsRole.ApplicationUser)) { - args.Identity = new RoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationUser }); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationUser }); Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); return; } @@ -236,7 +236,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg // role = GdsRole.ApplicationAdmin; Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, GdsRole.ApplicationUser); - args.Identity = new RoleBasedIdentity(new UserIdentity(x509Token), new List { GdsRole.ApplicationUser }); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(x509Token), new List { GdsRole.ApplicationUser }); return; } @@ -367,7 +367,7 @@ private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEvent NodeId applicationId = application.FirstOrDefault().ApplicationId; Utils.LogInfo("Application {0} accepted based on ApplicationInstanceCertificate as ApplicationSelfAdmin", applicationUri); - args.Identity = new RoleBasedIdentity(new UserIdentity(), GdsRole.ApplicationSelfAdmin, applicationId); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(), new List { GdsRole.ApplicationSelfAdmin }, applicationId); return; } From d8abd53050aa684c44903701836d258d620237d2 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sat, 20 Jan 2024 11:14:10 +0100 Subject: [PATCH 10/32] fix regression from merging master (unintentionally inverted if in ApplicationsNodeManager) --- Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 80016bc972..ff192c5e8f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -183,12 +183,12 @@ private void HasApplicationUserAccess(ISystemContext context) if (identity != null) { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (!identity.Roles.Contains(GdsRole.ApplicationUser)))) + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) { return; } } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required."); } } From dd2f7f0c063d973a9305935a404362e9cb9b40dc Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 21 Jan 2024 18:02:08 +0100 Subject: [PATCH 11/32] use correct DC for Subject of Certificate --- Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 812b8d9785..aee66dbb5d 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -254,7 +254,7 @@ private ApplicationTestData GetOwnApplicationData() ApplicationId = new NodeId(Guid.NewGuid()) }, PrivateKeyFormat = "PEM", - Subject = $"CN={m_client.Configuration.ApplicationName},DC=opc.tcp://localhost,O=OPC Foundation", + Subject = $"CN={m_client.Configuration.ApplicationName},DC=localhost,O=OPC Foundation", }; return ownApplicationTestData; } From cf3d367e23112d1a661570a6441352a1cc53debb Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 21 Jan 2024 21:28:06 +0100 Subject: [PATCH 12/32] fix regression from merge, to many roles were allowed --- .../ApplicationsNodeManager.cs | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index ff192c5e8f..8124ef1498 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -175,6 +175,31 @@ private void HasApplicationAdminAccess(ISystemContext context) } } + private void HasApplicationUserOrSelfAdminAccess(ISystemContext context, NodeId applicationId) + { + if (context != null) + { + RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + + if (identity != null) + { + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) + { + return; + } + } + GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; + if (extendedIdentity != null) + { + //not administrator/user only has access to own application + if (extendedIdentity.ApplicationId == applicationId) + { + return; + } + } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or Application User access required."); + } + } private void HasApplicationUserAccess(ISystemContext context) { if (context != null) @@ -188,7 +213,7 @@ private void HasApplicationUserAccess(ISystemContext context) return; } } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required."); + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or Application User access required."); } } @@ -201,20 +226,24 @@ private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId app { if (context != null) { - GdsRoleBasedIdentity identity = context.UserIdentity as GdsRoleBasedIdentity; - if (identity != null) + RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + //administrator has full access + if ((identity != null) && (identity.Roles.Contains(GdsRole.ApplicationAdmin))) { - //administrator/user has full access - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (!identity.Roles.Contains(GdsRole.ApplicationUser)))) - return; - //not administrator/user only has access to own application - if (identity.ApplicationId == applicationId) + return; + } + GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; + if (extendedIdentity != null) + { + + //not administrator only has access to own application + if (extendedIdentity.ApplicationId == applicationId) { return; } } throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " + - "Application Administrator/User access required."); + "Application Administrator access required."); } } @@ -630,7 +659,7 @@ private ServiceResult OnGetApplication( NodeId applicationId, ref ApplicationRecordDataType application) { - HasApplicationUserAccess(context); + HasApplicationUserOrSelfAdminAccess(context, applicationId); Utils.LogInfo("OnGetApplication: {0}", applicationId); application = m_database.GetApplication(applicationId); return ServiceResult.Good; @@ -1156,7 +1185,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationUserOrSelfAdminAccess(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1184,7 +1213,7 @@ public ServiceResult OnGetTrustList( NodeId certificateGroupId, ref NodeId trustListId) { - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationUserOrSelfAdminAccess(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1217,7 +1246,7 @@ public ServiceResult OnGetCertificateStatus( NodeId certificateTypeId, ref Boolean updateRequired) { - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationUserOrSelfAdminAccess(context, applicationId); var application = m_database.GetApplication(applicationId); From 1a51b432e4dffa2fa89a00d314e8682599c372e4 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Tue, 23 Jan 2024 17:02:51 +0100 Subject: [PATCH 13/32] add Test Cases for KeyPair & Sign with SelfAdminPrivilege --- .../ApplicationsNodeManager.cs | 6 +- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 183 ++++++++++++++++-- .../GlobalDiscoveryTestClient.cs | 56 +++--- 3 files changed, 207 insertions(+), 38 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 8124ef1498..8fcbe092a3 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -197,7 +197,7 @@ private void HasApplicationUserOrSelfAdminAccess(ISystemContext context, NodeId return; } } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or Application User access required."); + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required."); } } private void HasApplicationUserAccess(ISystemContext context) @@ -213,7 +213,7 @@ private void HasApplicationUserAccess(ISystemContext context) return; } } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or Application User access required."); + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required."); } } @@ -1172,7 +1172,7 @@ out privateKeyPassword } m_database.SetApplicationCertificate(applicationId, m_certTypeMap[certificateGroup.CertificateType], signedCertificate); - + m_request.AcceptRequest(requestId, signedCertificate); return ServiceResult.Good; diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 1cda45fc40..9e4ca0c071 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -938,9 +938,11 @@ public void GetGoodCertificateGroupsAndTrustLists() foreach (var certificateGroup in certificateGroups) { var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); - // Opc.Ua.TrustListDataType + + Assert.NotNull(trustListId); + + // Opc.Ua.TrustListDataType -> not possible, this needs ApplicationUserAccess var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); - Assert.NotNull(trustList); } } } @@ -972,39 +974,194 @@ public void GetGoodCertificateGroupsAsSelfAdmin() AssertIgnoreTestWithoutGoodRegistration(); AssertIgnoreTestWithoutGoodNewKeyPairRequest(); - // connect with gds issued certificate - m_gdsClient.RegisterTestClientAtGds(); + var success = m_gdsClient.RegisterTestClientAtGds(); + + if (!success) + { + Assert.Fail("Registering test Client at GDS failed"); + } + ConnectGDS(false, true); // ensure access to other applications is denied - foreach (var application in m_goodApplicationTestSet) + foreach (var testApplication in m_goodApplicationTestSet) { - if (application.Certificate != null) + if (testApplication.Certificate != null) { - var sre = Assert.Throws(() => m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId)); + var sre = Assert.Throws(() => m_gdsClient.GDSClient.GetCertificateGroups(testApplication.ApplicationRecord.ApplicationId)); Assert.NotNull(sre); Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); } } + DisconnectGDS(); - Assert.Ignore("TODO: Tests for other logic is not implemented yet!"); - + var application = m_gdsClient.OwnApplicationTestData; + ConnectGDS(false, true); // use self registered application and get the group / trust lists - var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(m_gdsClient.OwnApplicationTestData.ApplicationRecord.ApplicationId); + var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId); + foreach (var certificateGroup in certificateGroups) { - var trustListId = m_gdsClient.GDSClient.GetTrustList(m_gdsClient.OwnApplicationTestData.ApplicationRecord.ApplicationId, certificateGroup); + var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); // Opc.Ua.TrustListDataType - var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); - Assert.NotNull(trustList); + // var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); //ToDo make it possible to read the trust List with SelfAdminPrivilege + Assert.NotNull(trustListId); } + DisconnectGDS(); + ConnectGDS(false, true); // self issue a certificate and read it back + Assert.Null(application.CertificateRequestId); + X509Certificate2 csrCertificate; + if (application.PrivateKeyFormat == "PFX") + { + csrCertificate = X509Utils.CreateCertificateFromPKCS12(application.PrivateKey, application.PrivateKeyPassword); + } + else + { + csrCertificate = CertificateFactory.CreateCertificateWithPEMPrivateKey(new X509Certificate2(application.Certificate), application.PrivateKey, application.PrivateKeyPassword); + } + byte[] certificateRequest = CertificateFactory.CreateSigningRequest(csrCertificate, application.DomainNames); + csrCertificate.Dispose(); + NodeId requestId = m_gdsClient.GDSClient.StartSigningRequest( + application.ApplicationRecord.ApplicationId, + application.CertificateGroupId, + application.CertificateTypeId, + certificateRequest); + Assert.NotNull(requestId); + application.CertificateRequestId = requestId; + bool requestBusy; + DateTime now = DateTime.UtcNow; + do + { + requestBusy = false; + + + if (application.CertificateRequestId != null) + { + try + { + var certificate = m_gdsClient.GDSClient.FinishRequest( + application.ApplicationRecord.ApplicationId, + application.CertificateRequestId, + out byte[] privateKey, + out byte[][] issuerCertificates + ); + + if (certificate != null) + { + application.CertificateRequestId = null; + + Assert.Null(privateKey); + Assert.NotNull(issuerCertificates); + application.Certificate = certificate; + application.IssuerCertificates = issuerCertificates; + //X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); + //X509TestUtils.VerifyApplicationCertIntegrity(certificate, application.PrivateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); + } + else + { + requestBusy = true; + } + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadNothingToDo && + now.AddMinutes(5) > DateTime.UtcNow) + { + requestBusy = true; + Thread.Sleep(1000); + } + else + { + throw; + } + } + + } + + if (requestBusy) + { + Thread.Sleep(5000); + Console.WriteLine("Waiting for certificate approval"); + } + } while (requestBusy); + + DisconnectGDS(); + ConnectGDS(false, true); // self issue a public/private key pair and read it back + Assert.Null(application.CertificateRequestId); + requestId = m_gdsClient.GDSClient.StartNewKeyPairRequest( + application.ApplicationRecord.ApplicationId, + application.CertificateGroupId, + application.CertificateTypeId, + application.Subject, + application.DomainNames, + application.PrivateKeyFormat, + application.PrivateKeyPassword); + Assert.NotNull(requestId); + application.CertificateRequestId = requestId; + + now = DateTime.UtcNow; + do + { + requestBusy = false; + if (application.CertificateRequestId != null) + { + try + { + byte[] certificate = m_gdsClient.GDSClient.FinishRequest( + application.ApplicationRecord.ApplicationId, + application.CertificateRequestId, + out byte[] privateKey, + out byte[][] issuerCertificates + ); + + if (certificate != null) + { + application.CertificateRequestId = null; + Assert.NotNull(certificate); + Assert.NotNull(privateKey); + Assert.NotNull(issuerCertificates); + application.Certificate = certificate; + application.PrivateKey = privateKey; + application.IssuerCertificates = issuerCertificates; + //X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); + //X509TestUtils.VerifyApplicationCertIntegrity(certificate, privateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); + } + else + { + requestBusy = true; + } + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadNothingToDo && + now.AddMinutes(5) > DateTime.UtcNow) + { + requestBusy = true; + Thread.Sleep(1000); + } + else + { + throw; + } + } + + } + + + if (requestBusy) + { + Thread.Sleep(5000); + Console.WriteLine("Waiting for certificate approval"); + } + + } while (requestBusy); } [Test, Order(690)] diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index aee66dbb5d..6ea179cb03 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -126,33 +126,45 @@ public async Task LoadClientConfiguration(int port = -1)         /// public bool RegisterTestClientAtGds() { - ApplicationTestData ownApplicationTestData = GetOwnApplicationData(); - - m_client.AdminCredentials = AdminUser; -            //register -            NodeId id = Register(ownApplicationTestData); - if (id == null) - { - return false; - } - ownApplicationTestData.ApplicationRecord.ApplicationId = id; -            //start Key Pair Request -            NodeId req_id = StartNewKeyPair(ownApplicationTestData); - if (req_id == null) + try { - return false; - } + OwnApplicationTestData = GetOwnApplicationData(); + + m_client.AdminCredentials = AdminUser; + //register + NodeId id = Register(OwnApplicationTestData); + if (id == null) + { + return false; + } + OwnApplicationTestData.ApplicationRecord.ApplicationId = id; + //start Key Pair Request + NodeId req_id = StartNewKeyPair(OwnApplicationTestData); + if (req_id == null) + { + return false; + } - ownApplicationTestData.CertificateRequestId = req_id; -            //Finish KeyPairRequest -            byte[] certificate, privateKey; - FinishKeyPair(ownApplicationTestData, out certificate, out privateKey); - if (certificate == null || privateKey == null) + OwnApplicationTestData.CertificateRequestId = req_id; + //Finish KeyPairRequest + byte[] certificate, privateKey; + FinishKeyPair(OwnApplicationTestData, out certificate, out privateKey); + if (certificate == null || privateKey == null) + { + return false; + } + //apply cert + ApplyNewApplicationInstanceCertificate(certificate, privateKey); + OwnApplicationTestData.Certificate = certificate; + OwnApplicationTestData.PrivateKey = privateKey; + OwnApplicationTestData.CertificateRequestId = null; + } + catch (ArgumentException e) { + Console.WriteLine("RegisterTestClientAtGds at GDS failed" + e.ToString()); return false; } -            //apply cert -            ApplyNewApplicationInstanceCertificate(certificate, privateKey); + return true; } From 252ad3147d405bfaa70524c51fe424b3d80e5b55 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 24 Jan 2024 21:23:26 +0100 Subject: [PATCH 14/32] fix access rights of OnGetCertificateGroups method --- Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 8fcbe092a3..2cd9780b46 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -1172,7 +1172,7 @@ out privateKeyPassword } m_database.SetApplicationCertificate(applicationId, m_certTypeMap[certificateGroup.CertificateType], signedCertificate); - + m_request.AcceptRequest(requestId, signedCertificate); return ServiceResult.Good; @@ -1185,7 +1185,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - HasApplicationUserOrSelfAdminAccess(context, applicationId); + HasApplicationSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); From 7e74111b4ab93072b3311b245317d272d6a3bd83 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sat, 27 Jan 2024 22:29:48 +0100 Subject: [PATCH 15/32] use hostname for certificate, enable cert validation --- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 8 ++++---- Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 9e4ca0c071..c3d2896211 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -1057,8 +1057,8 @@ out byte[][] issuerCertificates Assert.NotNull(issuerCertificates); application.Certificate = certificate; application.IssuerCertificates = issuerCertificates; - //X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); - //X509TestUtils.VerifyApplicationCertIntegrity(certificate, application.PrivateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); + X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); + X509TestUtils.VerifyApplicationCertIntegrity(certificate, application.PrivateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); } else { @@ -1130,8 +1130,8 @@ out byte[][] issuerCertificates application.Certificate = certificate; application.PrivateKey = privateKey; application.IssuerCertificates = issuerCertificates; - //X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); - //X509TestUtils.VerifyApplicationCertIntegrity(certificate, privateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); + X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); + X509TestUtils.VerifyApplicationCertIntegrity(certificate, privateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); } else { diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 6ea179cb03..110c123397 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -266,7 +266,7 @@ private ApplicationTestData GetOwnApplicationData() ApplicationId = new NodeId(Guid.NewGuid()) }, PrivateKeyFormat = "PEM", - Subject = $"CN={m_client.Configuration.ApplicationName},DC=localhost,O=OPC Foundation", + Subject = $"CN={m_client.Configuration.ApplicationName},DC={Utils.GetHostName()},O=OPC Foundation", }; return ownApplicationTestData; } From 9115f579551478d3333de69a01249c187300096f Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sat, 27 Jan 2024 23:07:24 +0100 Subject: [PATCH 16/32] refactor to 3 separate tests add negative tests --- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 264 ++++++++++++++++++--------- 1 file changed, 175 insertions(+), 89 deletions(-) diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index c3d2896211..6c8ab731e7 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -35,6 +35,8 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using Org.BouncyCastle.Tls; +using static System.Net.Mime.MediaTypeNames; namespace Opc.Ua.Gds.Tests { @@ -85,6 +87,7 @@ protected async Task OneTimeSetUp() m_goodRegistrationOk = false; m_invalidRegistrationOk = false; m_goodNewKeyPairRequestOk = false; + m_gdsRegisteredTestClient = false; } /// @@ -968,16 +971,23 @@ public void FailToGetGoodCertificateGroupsWithoutPriviledges() } } + /// + /// use self registered application and get the group / trust lists + /// [Test, Order(630)] public void GetGoodCertificateGroupsAsSelfAdmin() { AssertIgnoreTestWithoutGoodRegistration(); AssertIgnoreTestWithoutGoodNewKeyPairRequest(); - // connect with gds issued certificate + // register at gds and get gds issued certificate var success = m_gdsClient.RegisterTestClientAtGds(); - if (!success) + if (success) + { + m_gdsRegisteredTestClient = true; + } + else { Assert.Fail("Registering test Client at GDS failed"); } @@ -989,15 +999,17 @@ public void GetGoodCertificateGroupsAsSelfAdmin() { if (testApplication.Certificate != null) { - var sre = Assert.Throws(() => m_gdsClient.GDSClient.GetCertificateGroups(testApplication.ApplicationRecord.ApplicationId)); + var sre = Assert.Throws(() => + m_gdsClient.GDSClient.GetCertificateGroups(testApplication.ApplicationRecord.ApplicationId) + ); Assert.NotNull(sre); Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); } } - DisconnectGDS(); - var application = m_gdsClient.OwnApplicationTestData; - ConnectGDS(false, true); + ApplicationTestData application = m_gdsClient.OwnApplicationTestData; + + // use self registered application and get the group / trust lists var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId); @@ -1009,9 +1021,21 @@ public void GetGoodCertificateGroupsAsSelfAdmin() Assert.NotNull(trustListId); } DisconnectGDS(); + } + + /// + /// self issue a certificate and read it back + /// + [Test, Order(631)] + public void GoodSigningRequestAsSelfAdmin() + { + AssertIgnoreTestWithoutGdsRegisteredTestClient(); + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + + ApplicationTestData application = m_gdsClient.OwnApplicationTestData; ConnectGDS(false, true); - // self issue a certificate and read it back Assert.Null(application.CertificateRequestId); X509Certificate2 csrCertificate; if (application.PrivateKeyFormat == "PFX") @@ -1024,6 +1048,25 @@ public void GetGoodCertificateGroupsAsSelfAdmin() } byte[] certificateRequest = CertificateFactory.CreateSigningRequest(csrCertificate, application.DomainNames); csrCertificate.Dispose(); + + // ensure access to other applications is denied + foreach (var testApplication in m_goodApplicationTestSet) + { + if (testApplication.CertificateRequestId == null) + { + var sre = Assert.Throws(() => + m_gdsClient.GDSClient.StartSigningRequest( + testApplication.ApplicationRecord.ApplicationId, + testApplication.CertificateGroupId, + testApplication.CertificateTypeId, + certificateRequest) + ); + Assert.NotNull(sre); + Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); + } + } + + //own Application is allowed NodeId requestId = m_gdsClient.GDSClient.StartSigningRequest( application.ApplicationRecord.ApplicationId, application.CertificateGroupId, @@ -1037,50 +1080,50 @@ public void GetGoodCertificateGroupsAsSelfAdmin() { requestBusy = false; - - if (application.CertificateRequestId != null) + + if (application.CertificateRequestId != null) + { + try { - try + var certificate = m_gdsClient.GDSClient.FinishRequest( + application.ApplicationRecord.ApplicationId, + application.CertificateRequestId, + out byte[] privateKey, + out byte[][] issuerCertificates + ); + + if (certificate != null) { - var certificate = m_gdsClient.GDSClient.FinishRequest( - application.ApplicationRecord.ApplicationId, - application.CertificateRequestId, - out byte[] privateKey, - out byte[][] issuerCertificates - ); - - if (certificate != null) - { - application.CertificateRequestId = null; - - Assert.Null(privateKey); - Assert.NotNull(issuerCertificates); - application.Certificate = certificate; - application.IssuerCertificates = issuerCertificates; - X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); - X509TestUtils.VerifyApplicationCertIntegrity(certificate, application.PrivateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); - } - else - { - requestBusy = true; - } + application.CertificateRequestId = null; + + Assert.Null(privateKey); + Assert.NotNull(issuerCertificates); + application.Certificate = certificate; + application.IssuerCertificates = issuerCertificates; + X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); + X509TestUtils.VerifyApplicationCertIntegrity(certificate, application.PrivateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); } - catch (ServiceResultException sre) + else { - if (sre.StatusCode == StatusCodes.BadNothingToDo && - now.AddMinutes(5) > DateTime.UtcNow) - { - requestBusy = true; - Thread.Sleep(1000); - } - else - { - throw; - } + requestBusy = true; + } + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadNothingToDo && + now.AddMinutes(5) > DateTime.UtcNow) + { + requestBusy = true; + Thread.Sleep(1000); + } + else + { + throw; } - } + } + if (requestBusy) { Thread.Sleep(5000); @@ -1089,71 +1132,103 @@ out byte[][] issuerCertificates } while (requestBusy); DisconnectGDS(); + } + + /// + /// self issue a public/private key pair and read it back + /// + [Test, Order(632)] + public void GoodKeyPairRequestAsSelfAdmin() + { + AssertIgnoreTestWithoutGdsRegisteredTestClient(); + + ApplicationTestData application = m_gdsClient.OwnApplicationTestData; + ConnectGDS(false, true); - // self issue a public/private key pair and read it back + + // ensure access to other applications is denied + foreach (var testApplication in m_goodApplicationTestSet) + { + if (testApplication.CertificateRequestId == null) + { + var sre = Assert.Throws(() => + m_gdsClient.GDSClient.StartNewKeyPairRequest( + testApplication.ApplicationRecord.ApplicationId, + testApplication.CertificateGroupId, + testApplication.CertificateTypeId, + testApplication.Subject, + testApplication.DomainNames, + testApplication.PrivateKeyFormat, + testApplication.PrivateKeyPassword) + ); + Assert.NotNull(sre); + Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); + } + } + Assert.Null(application.CertificateRequestId); - requestId = m_gdsClient.GDSClient.StartNewKeyPairRequest( + //Start KeyPairRequest + NodeId requestId = m_gdsClient.GDSClient.StartNewKeyPairRequest( application.ApplicationRecord.ApplicationId, - application.CertificateGroupId, + application.CertificateGroupId, application.CertificateTypeId, application.Subject, - application.DomainNames, - application.PrivateKeyFormat, + application.DomainNames, + application.PrivateKeyFormat, application.PrivateKeyPassword); + Assert.NotNull(requestId); application.CertificateRequestId = requestId; - - now = DateTime.UtcNow; + //Finish KeyPairRequest + bool requestBusy; + DateTime now = DateTime.UtcNow; do { requestBusy = false; - if (application.CertificateRequestId != null) + if (application.CertificateRequestId != null) + { + try { - try + byte[] certificate = m_gdsClient.GDSClient.FinishRequest( + application.ApplicationRecord.ApplicationId, + application.CertificateRequestId, + out byte[] privateKey, + out byte[][] issuerCertificates + ); + + if (certificate != null) { - byte[] certificate = m_gdsClient.GDSClient.FinishRequest( - application.ApplicationRecord.ApplicationId, - application.CertificateRequestId, - out byte[] privateKey, - out byte[][] issuerCertificates - ); - - if (certificate != null) - { - application.CertificateRequestId = null; + application.CertificateRequestId = null; - Assert.NotNull(certificate); - Assert.NotNull(privateKey); - Assert.NotNull(issuerCertificates); - application.Certificate = certificate; - application.PrivateKey = privateKey; - application.IssuerCertificates = issuerCertificates; - X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); - X509TestUtils.VerifyApplicationCertIntegrity(certificate, privateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); - } - else - { - requestBusy = true; - } + Assert.NotNull(certificate); + Assert.NotNull(privateKey); + Assert.NotNull(issuerCertificates); + X509TestUtils.VerifySignedApplicationCert(application, certificate, issuerCertificates); + X509TestUtils.VerifyApplicationCertIntegrity(certificate, privateKey, application.PrivateKeyPassword, application.PrivateKeyFormat, issuerCertificates); } - catch (ServiceResultException sre) + else { - if (sre.StatusCode == StatusCodes.BadNothingToDo && - now.AddMinutes(5) > DateTime.UtcNow) - { - requestBusy = true; - Thread.Sleep(1000); - } - else - { - throw; - } + requestBusy = true; } - } - + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadNothingToDo && + now.AddMinutes(5) > DateTime.UtcNow) + { + requestBusy = true; + Thread.Sleep(1000); + } + else + { + throw; + } + } + + } + if (requestBusy) { @@ -1162,6 +1237,8 @@ out byte[][] issuerCertificates } } while (requestBusy); + + DisconnectGDS(); } [Test, Order(690)] @@ -1287,6 +1364,14 @@ private void AssertIgnoreTestWithoutGoodRegistration() } } + private void AssertIgnoreTestWithoutGdsRegisteredTestClient() + { + if (!m_gdsRegisteredTestClient) + { + Assert.Ignore("Test requires the test client to be registered at the GDS and use a GDS signed Certificate."); + } + } + private void AssertIgnoreTestWithoutInvalidRegistration() { if (!m_invalidRegistrationOk) @@ -1321,6 +1406,7 @@ private int GoodServersOnNetworkCount() private IList m_invalidApplicationTestSet; private bool m_goodRegistrationOk; private bool m_invalidRegistrationOk; + private bool m_gdsRegisteredTestClient; private bool m_goodNewKeyPairRequestOk; #endregion } From deba6a994fa21d04f83c53c79d502d55edf541dc Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 28 Jan 2024 11:22:28 +0100 Subject: [PATCH 17/32] add Test to verify unregistered application is denied --- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 43 ++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 6c8ab731e7..2e8a9d1c1c 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -941,7 +941,7 @@ public void GetGoodCertificateGroupsAndTrustLists() foreach (var certificateGroup in certificateGroups) { var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); - + Assert.NotNull(trustListId); // Opc.Ua.TrustListDataType -> not possible, this needs ApplicationUserAccess @@ -1012,12 +1012,12 @@ public void GetGoodCertificateGroupsAsSelfAdmin() // use self registered application and get the group / trust lists var certificateGroups = m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId); - + foreach (var certificateGroup in certificateGroups) { var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); // Opc.Ua.TrustListDataType - // var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); //ToDo make it possible to read the trust List with SelfAdminPrivilege + // var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); //ToDo make it possible to read the trust List with SelfAdminPrivilege Assert.NotNull(trustListId); } DisconnectGDS(); @@ -1141,9 +1141,11 @@ out byte[][] issuerCertificates public void GoodKeyPairRequestAsSelfAdmin() { AssertIgnoreTestWithoutGdsRegisteredTestClient(); + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); ApplicationTestData application = m_gdsClient.OwnApplicationTestData; - + ConnectGDS(false, true); @@ -1241,6 +1243,37 @@ out byte[][] issuerCertificates DisconnectGDS(); } + /// + /// unregister the Client at the GDS and try to read the trust List + /// + [Test, Order(633)] + public void FailToGetGoodCertificateGroupsWithoutSelfAdminPrivilege() + { + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + AssertIgnoreTestWithoutGdsRegisteredTestClient(); + + ConnectGDS(true); + + ApplicationTestData application = m_gdsClient.OwnApplicationTestData; + + //unregister GDS Client + m_gdsClient.GDSClient.UnregisterApplication(application.ApplicationRecord.ApplicationId); + + m_gdsRegisteredTestClient = false; + + DisconnectGDS(); + + //connect as self admin with revoked cert + + ConnectGDS(false, true); + var sre = Assert.Throws(() => + m_gdsClient.GDSClient.GetCertificateGroups(application.ApplicationRecord.ApplicationId) + ); + Assert.NotNull(sre); + Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); + } + [Test, Order(690)] public void GetGoodCertificateStatus() { @@ -1343,7 +1376,7 @@ private void ConnectGDS(bool admin, bool anonymous = false, else { m_gdsClient.GDSClient.AdminCredentials = admin ? m_gdsClient.AdminUser : m_gdsClient.AppUser; - } + } m_gdsClient.GDSClient.Connect(m_gdsClient.GDSClient.EndpointUrl).Wait(); TestContext.Progress.WriteLine($"GDS Client({admin}) connected -- {memberName}"); } From a3a4cacadde712e353fb7db47e53a330ae597193 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 28 Jan 2024 11:34:55 +0100 Subject: [PATCH 18/32] validate client cert URI does match applicationUri --- .../GlobalDiscoverySampleServer.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 2ab64db984..015f7f207b 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -243,7 +243,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg //check if applicable for application self admin privilege if(session.ClientCertificate != null) { - if (VerifiyApplicationRegistered(session.ClientCertificate)) + if (VerifiyApplicationRegistered(session)) { ImpersonateAsApplicationSelfAdmin(session, args); } @@ -253,11 +253,16 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg /// /// Verifies if an Application is registered with the provided certificate at at the GDS /// - /// the certificate to check for + /// the session /// - private bool VerifiyApplicationRegistered(X509Certificate2 applicationInstanceCertificate) + private bool VerifiyApplicationRegistered(Session session) { + X509Certificate2 applicationInstanceCertificate = session.ClientCertificate; bool applicationRegistered = false; + + Uri applicationUri = Utils.ParseUri(session.SessionDiagnostics.ClientDescription.ApplicationUri); + X509Utils.DoesUrlMatchCertificate(applicationInstanceCertificate, applicationUri); + //get access to GDS configuration section to find out ApplicationCertificatesStorePath GlobalDiscoveryServerConfiguration configuration = Configuration.ParseExtension(); if (configuration == null) @@ -359,7 +364,7 @@ private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEvent { string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri; ApplicationRecordDataType[] application = m_database.FindApplications(applicationUri); - if(application == null || application.Length>1 || application.Length<1) + if(application == null || application.Length != 1) { Utils.LogInfo("Cannot login based on ApplicationInstanceCertificate, no uniqure result for Application with URI: {0}", applicationUri); return; From 0bcf34356a95974b237e4c6f851e1c67796f397b Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 29 Jan 2024 19:55:59 +0100 Subject: [PATCH 19/32] add TrustList Access, still need CertificateStores or change of ApplicationsDatabase --- .../ApplicationsNodeManager.cs | 72 ++++++++++++++++++- .../IApplicationsDatabase.cs | 2 +- .../Configuration/ConfigurationNodeManager.cs | 11 +-- .../Opc.Ua.Server/Configuration/TrustList.cs | 7 +- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 6 +- 5 files changed, 84 insertions(+), 14 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 2cd9780b46..15eac86818 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -162,6 +162,8 @@ public override NodeId New(ISystemContext context, NodeState node) #endregion #region Private Methods + + #region AuthorizationHelpers private void HasApplicationAdminAccess(ISystemContext context) { if (context != null) @@ -247,6 +249,68 @@ private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId app } } + private void HasTrustListWriteAccess(ISystemContext context, string trustedStorePath) + { + if (context != null) + { + RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + + if (identity != null) + { + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin))) + { + return; + } + } + GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; + if (extendedIdentity != null) + { + //not administrator only has access to own trust List + foreach (var certType in m_certTypeMap.Values) + { + m_database.GetApplicationTrustLists(extendedIdentity.ApplicationId, certType, out var trustListId); + if (trustedStorePath == trustListId) + { + return; + } + } + } + + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); + } + } + + private void HasTrustListReadAccess(ISystemContext context, string trustedStorePath) + { + if (context != null) + { + RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + + if (identity != null) + { + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) + { + return; + } + } + GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; + if (extendedIdentity != null) + { + //not administrator/user only has access to own trust List + foreach (var certType in m_certTypeMap.Values) + { + m_database.GetApplicationTrustLists(extendedIdentity.ApplicationId, certType, out var trustListId); + if (trustedStorePath == trustListId) + { + return; + } + } + } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); + } + } + #endregion + private NodeId GetTrustListId(NodeId certificateGroupId) { @@ -1173,6 +1237,8 @@ out privateKeyPassword m_database.SetApplicationCertificate(applicationId, m_certTypeMap[certificateGroup.CertificateType], signedCertificate); + m_database.SetApplicationTrustLists(applicationId, m_certTypeMap[certificateGroup.CertificateType], certificateGroup.Configuration.TrustedListPath); + m_request.AcceptRequest(requestId, signedCertificate); return ServiceResult.Good; @@ -1412,11 +1478,13 @@ protected void SetCertificateGroupNodes(ICertificateGroup certificateGroup) certificateGroup.DefaultTrustList, certificateGroup.Configuration.TrustedListPath, certificateGroup.Configuration.IssuerListPath, - new TrustList.SecureAccess(HasApplicationUserAccess), - new TrustList.SecureAccess(HasApplicationAdminAccess)); + new TrustList.SecureAccess(HasTrustListReadAccess), + new TrustList.SecureAccess(HasTrustListWriteAccess)); } } + + private ServiceResult VerifyApprovedState(CertificateRequestState state) { switch (state) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs index 7a2072af21..071872decb 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs @@ -1,4 +1,4 @@ -/* ======================================================================== +/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 1640a59b2c..4fbf4b95fc 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -295,9 +295,10 @@ public NamespaceMetadataState CreateNamespaceMetadataState(string namespaceUri) /// Determine if the impersonated user has admin access. /// /// + /// /// /// - public void HasApplicationSecureAdminAccess(ISystemContext context) + public void HasApplicationSecureAdminAccess(ISystemContext context, string trustedStorePath) { OperationContext operationContext = (context as SystemContext)?.OperationContext as OperationContext; if (operationContext != null) @@ -332,7 +333,7 @@ private ServiceResult UpdateCertificate( byte[] privateKey, ref bool applyChangesRequired) { - HasApplicationSecureAdminAccess(context); + HasApplicationSecureAdminAccess(context, ""); object[] inputArguments = new object[] { certificateGroupId, certificateTypeId, certificate, issuerCertificates, privateKeyFormat, privateKey }; X509Certificate2 newCert = null; @@ -514,7 +515,7 @@ private ServiceResult CreateSigningRequest( byte[] nonce, ref byte[] certificateRequest) { - HasApplicationSecureAdminAccess(context); + HasApplicationSecureAdminAccess(context, ""); ServerCertificateGroup certificateGroup = VerifyGroupAndTypeId(certificateGroupId, certificateTypeId); @@ -539,7 +540,7 @@ private ServiceResult ApplyChanges( IList inputArguments, IList outputArguments) { - HasApplicationSecureAdminAccess(context); + HasApplicationSecureAdminAccess(context, ""); bool disconnectSessions = false; @@ -582,7 +583,7 @@ private ServiceResult GetRejectedList( NodeId objectId, ref byte[][] certificates) { - HasApplicationSecureAdminAccess(context); + HasApplicationSecureAdminAccess(context, ""); using (ICertificateStore store = CertificateStoreIdentifier.OpenStore(m_rejectedStorePath)) { diff --git a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs index a2f7da2a06..3c16d62d50 100644 --- a/Libraries/Opc.Ua.Server/Configuration/TrustList.cs +++ b/Libraries/Opc.Ua.Server/Configuration/TrustList.cs @@ -75,7 +75,8 @@ public TrustList( /// Delegate to validate the access to the trust list. /// /// - public delegate void SecureAccess(ISystemContext context); + /// the path to identify the trustList + public delegate void SecureAccess(ISystemContext context, string trustedStorePath); #endregion #region Private Methods @@ -668,7 +669,7 @@ private void HasSecureReadAccess(ISystemContext context) { if (m_readAccess != null) { - m_readAccess.Invoke(context); + m_readAccess.Invoke(context, m_trustedStorePath); } else { @@ -680,7 +681,7 @@ private void HasSecureWriteAccess(ISystemContext context) { if (m_writeAccess != null) { - m_writeAccess.Invoke(context); + m_writeAccess.Invoke(context, m_trustedStorePath); } else { diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 2e8a9d1c1c..098a6a6bb7 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -977,8 +977,8 @@ public void FailToGetGoodCertificateGroupsWithoutPriviledges() [Test, Order(630)] public void GetGoodCertificateGroupsAsSelfAdmin() { - AssertIgnoreTestWithoutGoodRegistration(); - AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + //AssertIgnoreTestWithoutGoodRegistration(); + //AssertIgnoreTestWithoutGoodNewKeyPairRequest(); // register at gds and get gds issued certificate var success = m_gdsClient.RegisterTestClientAtGds(); @@ -1017,7 +1017,7 @@ public void GetGoodCertificateGroupsAsSelfAdmin() { var trustListId = m_gdsClient.GDSClient.GetTrustList(application.ApplicationRecord.ApplicationId, certificateGroup); // Opc.Ua.TrustListDataType - // var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); //ToDo make it possible to read the trust List with SelfAdminPrivilege + var trustList = m_gdsClient.GDSClient.ReadTrustList(trustListId); //ToDo make it possible to read the trust List with SelfAdminPrivilege Assert.NotNull(trustListId); } DisconnectGDS(); From e550bad5d40a5242826271a93ba61331c2f11d16 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 29 Jan 2024 20:02:05 +0100 Subject: [PATCH 20/32] change LinqApplicationsDatabase --- .../LinqApplicationsDatabase.cs | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs index 6d3cf5208f..0b1209c793 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs @@ -49,7 +49,7 @@ class Application public Application() { Certificate = new Dictionary(); - TrustListId = new Dictionary(); + TrustListId = new Dictionary(); } public uint ID { get; set; } public Guid ApplicationId { get; set; } @@ -59,7 +59,7 @@ public Application() public string ProductUri { get; set; } public string ServerCapabilities { get; set; } public Dictionary Certificate { get; } - public Dictionary TrustListId { get; } + public Dictionary TrustListId { get; } } [Serializable] @@ -711,11 +711,7 @@ string trustListId if (trustListId != null) { - var result2 = (from x in CertificateStores where x.Path == trustListId select x).SingleOrDefault(); - if (result2 != null) - { - result.TrustListId[certificateType] = result2.TrustListId; - } + result.TrustListId[certificateType] = trustListId; } SaveChanges(); } @@ -740,20 +736,8 @@ out string trustListId { return false; } - - Guid trustListGuid; - if (result.TrustListId.TryGetValue(certificateType, out trustListGuid)) - { - var result2 = (from x in CertificateStores where x.TrustListId == trustListGuid select x).SingleOrDefault(); - if (result2 != null) - { - trustListId = result2.Path; - return true; - } - } + return result.TrustListId.TryGetValue(certificateType, out trustListId); } - - return false; } #endregion From 0787a35c13fc2fb063c133cd7f7a7d75af9a8b3b Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 29 Jan 2024 20:22:43 +0100 Subject: [PATCH 21/32] cleanup --- .../ApplicationsNodeManager.cs | 187 +++++++++--------- .../Configuration/ConfigurationNodeManager.cs | 20 +- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 4 +- 3 files changed, 113 insertions(+), 98 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 15eac86818..c0b7f8fd5c 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -164,150 +164,153 @@ public override NodeId New(ISystemContext context, NodeState node) #region Private Methods #region AuthorizationHelpers - private void HasApplicationAdminAccess(ISystemContext context) + private void HasApplicationAdminRole(ISystemContext context) { if (context != null) { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if ((identity == null) || (!identity.Roles.Contains(GdsRole.ApplicationAdmin))) + if (CheckAdminRole(context.UserIdentity)) { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); + return; } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator role required."); } } - - private void HasApplicationUserOrSelfAdminAccess(ISystemContext context, NodeId applicationId) + private void HasApplicationUserRole(ISystemContext context) { if (context != null) { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if (identity != null) - { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) - { - return; - } - } - GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; - if (extendedIdentity != null) + if (CheckUserRole(context.UserIdentity)) { - //not administrator/user only has access to own application - if (extendedIdentity.ApplicationId == applicationId) - { - return; - } + return; } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required."); + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User role required."); } } - private void HasApplicationUserAccess(ISystemContext context) + private void HasApplicationUserRoleOrSelfAdminPrivilge(ISystemContext context, NodeId applicationId) { if (context != null) { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if (identity != null) + if (CheckUserRole(context.UserIdentity)) { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) - { - return; - } + return; } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required."); + if (CheckSelfAdminPrivilege(context.UserIdentity, applicationId)) + { + return; + } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User role required."); } } - /// /// checks if the given Application can be modified with the current context /// /// the current context /// the application to modify - private void HasApplicationSelfAdminPrivilege(ISystemContext context, NodeId applicationId) + private void HasApplicationAdminRoleOrSelfAdminPrivilege(ISystemContext context, NodeId applicationId) { if (context != null) { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - //administrator has full access - if ((identity != null) && (identity.Roles.Contains(GdsRole.ApplicationAdmin))) + if (CheckAdminRole(context.UserIdentity)) { return; } - GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; - if (extendedIdentity != null) + if (CheckSelfAdminPrivilege(context.UserIdentity, applicationId)) { - - //not administrator only has access to own application - if (extendedIdentity.ApplicationId == applicationId) - { - return; - } + return; } throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " + - "Application Administrator access required."); + "Application Administrator role required."); } } - private void HasTrustListWriteAccess(ISystemContext context, string trustedStorePath) { if (context != null) { RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - if (identity != null) + if (CheckAdminRole(context.UserIdentity)) { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin))) - { - return; - } + return; } - GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; - if (extendedIdentity != null) + if (CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath)) { - //not administrator only has access to own trust List - foreach (var certType in m_certTypeMap.Values) - { - m_database.GetApplicationTrustLists(extendedIdentity.ApplicationId, certType, out var trustListId); - if (trustedStorePath == trustListId) - { - return; - } - } + return; } throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); } } - private void HasTrustListReadAccess(ISystemContext context, string trustedStorePath) { if (context != null) { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; + if (CheckUserRole(context.UserIdentity)) + { + return; + } + if (CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath)) + { + return; + } + + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); + } + } + private bool CheckAdminRole(IUserIdentity userIdentity) + { + RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; - if (identity != null) + if (identity != null) + { + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin))) { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) - { - return; - } + return true; } - GdsRoleBasedIdentity extendedIdentity = context.UserIdentity as GdsRoleBasedIdentity; - if (extendedIdentity != null) + } + return false; + } + private bool CheckUserRole(IUserIdentity userIdentity) + { + RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; + + if (identity != null) + { + if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) { - //not administrator/user only has access to own trust List - foreach (var certType in m_certTypeMap.Values) + return true; + } + } + return false; + } + private bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath) + { + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) + { + foreach (var certType in m_certTypeMap.Values) + { + m_database.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); + if (trustedStorePath == trustListId) { - m_database.GetApplicationTrustLists(extendedIdentity.ApplicationId, certType, out var trustListId); - if (trustedStorePath == trustListId) - { - return; - } + return true; } } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); } + return false; + } + private bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) + { + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) + { + + //not administrator only has access to own application + if (identity.ApplicationId == applicationId) + { + return true; + } + } + return false; } #endregion @@ -638,7 +641,7 @@ private ServiceResult OnRegisterApplication( ApplicationRecordDataType application, ref NodeId applicationId) { - HasApplicationAdminAccess(context); + HasApplicationAdminRole(context); Utils.LogInfo("OnRegisterApplication: {0}", application.ApplicationUri); @@ -653,7 +656,7 @@ private ServiceResult OnUpdateApplication( NodeId objectId, ApplicationRecordDataType application) { - HasApplicationAdminAccess(context); + HasApplicationAdminRole(context); Utils.LogInfo("OnUpdateApplication: {0}", application.ApplicationUri); @@ -675,7 +678,7 @@ private ServiceResult OnUnregisterApplication( NodeId objectId, NodeId applicationId) { - HasApplicationAdminAccess(context); + HasApplicationAdminRole(context); Utils.LogInfo("OnUnregisterApplication: {0}", applicationId.ToString()); @@ -710,7 +713,7 @@ private ServiceResult OnFindApplications( string applicationUri, ref ApplicationRecordDataType[] applications) { - HasApplicationUserAccess(context); + HasApplicationUserRole(context); Utils.LogInfo("OnFindApplications: {0}", applicationUri); applications = m_database.FindApplications(applicationUri); return ServiceResult.Good; @@ -723,7 +726,7 @@ private ServiceResult OnGetApplication( NodeId applicationId, ref ApplicationRecordDataType application) { - HasApplicationUserOrSelfAdminAccess(context, applicationId); + HasApplicationUserRoleOrSelfAdminPrivilge(context, applicationId); Utils.LogInfo("OnGetApplication: {0}", applicationId); application = m_database.GetApplication(applicationId); return ServiceResult.Good; @@ -883,7 +886,7 @@ private ServiceResult OnStartNewKeyPairRequest( string privateKeyPassword, ref NodeId requestId) { - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1002,7 +1005,7 @@ private ServiceResult OnStartSigningRequest( byte[] certificateRequest, ref NodeId requestId) { - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1083,7 +1086,7 @@ private ServiceResult OnFinishRequest( signedCertificate = null; issuerCertificates = null; privateKey = null; - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); if (application == null) @@ -1251,7 +1254,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - HasApplicationSelfAdminPrivilege(context, applicationId); + HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1279,7 +1282,7 @@ public ServiceResult OnGetTrustList( NodeId certificateGroupId, ref NodeId trustListId) { - HasApplicationUserOrSelfAdminAccess(context, applicationId); + HasApplicationUserRoleOrSelfAdminPrivilge(context, applicationId); var application = m_database.GetApplication(applicationId); @@ -1312,7 +1315,7 @@ public ServiceResult OnGetCertificateStatus( NodeId certificateTypeId, ref Boolean updateRequired) { - HasApplicationUserOrSelfAdminAccess(context, applicationId); + HasApplicationUserRoleOrSelfAdminPrivilge(context, applicationId); var application = m_database.GetApplication(applicationId); diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 4fbf4b95fc..40587aca81 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -291,6 +291,18 @@ public NamespaceMetadataState CreateNamespaceMetadataState(string namespaceUri) return namespaceMetadataState; } + /// + /// Determine if the impersonated user has admin access. + /// + /// + /// + /// + public void HasApplicationSecureAdminAccess(ISystemContext context) + { + HasApplicationSecureAdminAccess(context); + } + + /// /// Determine if the impersonated user has admin access. /// @@ -333,7 +345,7 @@ private ServiceResult UpdateCertificate( byte[] privateKey, ref bool applyChangesRequired) { - HasApplicationSecureAdminAccess(context, ""); + HasApplicationSecureAdminAccess(context); object[] inputArguments = new object[] { certificateGroupId, certificateTypeId, certificate, issuerCertificates, privateKeyFormat, privateKey }; X509Certificate2 newCert = null; @@ -515,7 +527,7 @@ private ServiceResult CreateSigningRequest( byte[] nonce, ref byte[] certificateRequest) { - HasApplicationSecureAdminAccess(context, ""); + HasApplicationSecureAdminAccess(context); ServerCertificateGroup certificateGroup = VerifyGroupAndTypeId(certificateGroupId, certificateTypeId); @@ -540,7 +552,7 @@ private ServiceResult ApplyChanges( IList inputArguments, IList outputArguments) { - HasApplicationSecureAdminAccess(context, ""); + HasApplicationSecureAdminAccess(context); bool disconnectSessions = false; @@ -583,7 +595,7 @@ private ServiceResult GetRejectedList( NodeId objectId, ref byte[][] certificates) { - HasApplicationSecureAdminAccess(context, ""); + HasApplicationSecureAdminAccess(context); using (ICertificateStore store = CertificateStoreIdentifier.OpenStore(m_rejectedStorePath)) { diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 098a6a6bb7..5713142b9c 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -977,8 +977,8 @@ public void FailToGetGoodCertificateGroupsWithoutPriviledges() [Test, Order(630)] public void GetGoodCertificateGroupsAsSelfAdmin() { - //AssertIgnoreTestWithoutGoodRegistration(); - //AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); // register at gds and get gds issued certificate var success = m_gdsClient.RegisterTestClientAtGds(); From 4d9d1fb55585f99b2ebc444e9d6113d967e0ae93 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 12 Feb 2024 18:38:00 +0100 Subject: [PATCH 22/32] fix failing tests --- .../Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 40587aca81..7a96e0a672 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -299,7 +299,7 @@ public NamespaceMetadataState CreateNamespaceMetadataState(string namespaceUri) /// public void HasApplicationSecureAdminAccess(ISystemContext context) { - HasApplicationSecureAdminAccess(context); + HasApplicationSecureAdminAccess(context, ""); } From ff098a3abe81710bca4e6bc63666f4bca94ffc05 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 14 Feb 2024 18:21:51 +0100 Subject: [PATCH 23/32] add well known roles --- .../ApplicationsDatabaseBase.cs | 0 .../IApplicationsDatabase.cs | 0 .../JsonApplicationsDatabase.cs | 0 .../LinqApplicationsDatabase.cs | 0 .../ApplicationsNodeManager.cs | 188 ++---------------- .../GlobalDiscoverySampleServer.cs | 22 +- .../AuthorizationHelper.cs | 106 ++++++++++ .../{ => RoleBasedUserManagement}/GdsRole.cs | 17 +- .../RoleBasedIdentity.cs | 8 + 9 files changed, 160 insertions(+), 181 deletions(-) rename Libraries/Opc.Ua.Gds.Server.Common/{ => ApplicationsDatabase}/ApplicationsDatabaseBase.cs (100%) rename Libraries/Opc.Ua.Gds.Server.Common/{ => ApplicationsDatabase}/IApplicationsDatabase.cs (100%) rename Libraries/Opc.Ua.Gds.Server.Common/{ => ApplicationsDatabase}/JsonApplicationsDatabase.cs (100%) rename Libraries/Opc.Ua.Gds.Server.Common/{ => ApplicationsDatabase}/LinqApplicationsDatabase.cs (100%) create mode 100644 Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs rename Libraries/Opc.Ua.Gds.Server.Common/{ => RoleBasedUserManagement}/GdsRole.cs (74%) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabaseBase.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/ApplicationsDatabaseBase.cs similarity index 100% rename from Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabaseBase.cs rename to Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/ApplicationsDatabaseBase.cs diff --git a/Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/IApplicationsDatabase.cs similarity index 100% rename from Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs rename to Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/IApplicationsDatabase.cs diff --git a/Libraries/Opc.Ua.Gds.Server.Common/JsonApplicationsDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/JsonApplicationsDatabase.cs similarity index 100% rename from Libraries/Opc.Ua.Gds.Server.Common/JsonApplicationsDatabase.cs rename to Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/JsonApplicationsDatabase.cs diff --git a/Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs similarity index 100% rename from Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs rename to Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index dd1589c8df..6e8619163e 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -162,159 +162,7 @@ public override NodeId New(ISystemContext context, NodeState node) } #endregion - #region Private Methods - - #region AuthorizationHelpers - private void HasApplicationAdminRole(ISystemContext context) - { - if (context != null) - { - if (CheckAdminRole(context.UserIdentity)) - { - return; - } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator role required."); - } - } - private void HasApplicationUserRole(ISystemContext context) - { - if (context != null) - { - if (CheckUserRole(context.UserIdentity)) - { - return; - } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User role required."); - } - } - private void HasApplicationUserRoleOrSelfAdminPrivilge(ISystemContext context, NodeId applicationId) - { - if (context != null) - { - if (CheckUserRole(context.UserIdentity)) - { - return; - } - if (CheckSelfAdminPrivilege(context.UserIdentity, applicationId)) - { - return; - } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User role required."); - } - } - /// - /// checks if the given Application can be modified with the current context - /// - /// the current context - /// the application to modify - private void HasApplicationAdminRoleOrSelfAdminPrivilege(ISystemContext context, NodeId applicationId) - { - if (context != null) - { - if (CheckAdminRole(context.UserIdentity)) - { - return; - } - if (CheckSelfAdminPrivilege(context.UserIdentity, applicationId)) - { - return; - } - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privielge or " + - "Application Administrator role required."); - } - } - private void HasTrustListWriteAccess(ISystemContext context, string trustedStorePath) - { - if (context != null) - { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if (CheckAdminRole(context.UserIdentity)) - { - return; - } - if (CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath)) - { - return; - } - - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); - } - } - private void HasTrustListReadAccess(ISystemContext context, string trustedStorePath) - { - if (context != null) - { - if (CheckUserRole(context.UserIdentity)) - { - return; - } - if (CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath)) - { - return; - } - - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Self Admin Privilege or Application User access required for reading TrustList."); - } - } - private bool CheckAdminRole(IUserIdentity userIdentity) - { - RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; - - if (identity != null) - { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin))) - { - return true; - } - } - return false; - } - private bool CheckUserRole(IUserIdentity userIdentity) - { - RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; - - if (identity != null) - { - if ((identity.Roles.Contains(GdsRole.ApplicationAdmin) || (identity.Roles.Contains(GdsRole.ApplicationUser)))) - { - return true; - } - } - return false; - } - private bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath) - { - GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; - if (identity != null) - { - foreach (var certType in m_certTypeMap.Values) - { - m_database.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); - if (trustedStorePath == trustListId) - { - return true; - } - } - } - return false; - } - private bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) - { - GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; - if (identity != null) - { - - //not administrator only has access to own application - if (identity.ApplicationId == applicationId) - { - return true; - } - } - return false; - } - #endregion - + #region Private methods private NodeId GetTrustListId(NodeId certificateGroupId) { @@ -642,7 +490,7 @@ private ServiceResult OnRegisterApplication( ApplicationRecordDataType application, ref NodeId applicationId) { - HasApplicationAdminRole(context); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.DiscoveryAdmin }); Utils.LogInfo("OnRegisterApplication: {0}", application.ApplicationUri); @@ -657,7 +505,7 @@ private ServiceResult OnUpdateApplication( NodeId objectId, ApplicationRecordDataType application) { - HasApplicationAdminRole(context); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.DiscoveryAdmin }); Utils.LogInfo("OnUpdateApplication: {0}", application.ApplicationUri); @@ -679,7 +527,7 @@ private ServiceResult OnUnregisterApplication( NodeId objectId, NodeId applicationId) { - HasApplicationAdminRole(context); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.DiscoveryAdmin }); Utils.LogInfo("OnUnregisterApplication: {0}", applicationId.ToString()); @@ -714,7 +562,7 @@ private ServiceResult OnFindApplications( string applicationUri, ref ApplicationRecordDataType[] applications) { - HasApplicationUserRole(context); + AuthorizationHelper.HasAuthorization(context, new List { Role.AuthenticatedUser }); Utils.LogInfo("OnFindApplications: {0}", applicationUri); applications = m_database.FindApplications(applicationUri); return ServiceResult.Good; @@ -727,7 +575,7 @@ private ServiceResult OnGetApplication( NodeId applicationId, ref ApplicationRecordDataType application) { - HasApplicationUserRoleOrSelfAdminPrivilge(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }, applicationId); ; Utils.LogInfo("OnGetApplication: {0}", applicationId); application = m_database.GetApplication(applicationId); return ServiceResult.Good; @@ -887,7 +735,7 @@ private ServiceResult OnStartNewKeyPairRequest( string privateKeyPassword, ref NodeId requestId) { - HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ; var application = m_database.GetApplication(applicationId); @@ -1006,7 +854,7 @@ private ServiceResult OnStartSigningRequest( byte[] certificateRequest, ref NodeId requestId) { - HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ; var application = m_database.GetApplication(applicationId); @@ -1087,7 +935,7 @@ private ServiceResult OnFinishRequest( signedCertificate = null; issuerCertificates = null; privateKey = null; - HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ; var application = m_database.GetApplication(applicationId); if (application == null) @@ -1255,7 +1103,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - HasApplicationAdminRoleOrSelfAdminPrivilege(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); var application = m_database.GetApplication(applicationId); @@ -1283,7 +1131,7 @@ public ServiceResult OnGetTrustList( NodeId certificateGroupId, ref NodeId trustListId) { - HasApplicationUserRoleOrSelfAdminPrivilge(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); var application = m_database.GetApplication(applicationId); @@ -1316,7 +1164,7 @@ public ServiceResult OnGetCertificateStatus( NodeId certificateTypeId, ref Boolean updateRequired) { - HasApplicationUserRoleOrSelfAdminPrivilge(context, applicationId); + AuthorizationHelper.HasAuthorization(context, new List { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }, applicationId); var application = m_database.GetApplication(applicationId); @@ -1482,12 +1330,18 @@ protected void SetCertificateGroupNodes(ICertificateGroup certificateGroup) certificateGroup.DefaultTrustList, certificateGroup.Configuration.TrustedListPath, certificateGroup.Configuration.IssuerListPath, - new TrustList.SecureAccess(HasTrustListReadAccess), - new TrustList.SecureAccess(HasTrustListWriteAccess)); + new TrustList.SecureAccess(HasTrustListAccess), + new TrustList.SecureAccess(HasTrustListAccess)); } } - + #region AuthorizationHelpers + private void HasTrustListAccess(ISystemContext context, string trustedStorePath) + { + AuthorizationHelper.HasTrustListAccess(context, trustedStorePath, m_certTypeMap, m_database); + } + #endregion + private ServiceResult VerifyApprovedState(CertificateRequestState state) { diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 015f7f207b..dae7b64773 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -210,19 +210,23 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg } IEnumerable roles = m_userDatabase.GetUserRoles(userNameToken.UserName); //GdsAdmin +#pragma warning disable CS0618 // Type or member is obsolete if (roles.Contains(GdsRole.ApplicationAdmin)) { - args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationAdmin }); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.DiscoveryAdmin, GdsRole.CertificateAuthorityAdmin }); Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); return; } - //GdsUser + + //GdsUser if (roles.Contains(GdsRole.ApplicationUser)) { - args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.ApplicationUser }); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { Role.AuthenticatedUser }); Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); return; - } + } +#pragma warning restore CS0618 // Type or member is obsolete + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), roles); } } @@ -235,8 +239,8 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg // todo: is cert listed in admin list? then // role = GdsRole.ApplicationAdmin; - Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, GdsRole.ApplicationUser); - args.Identity = new GdsRoleBasedIdentity(new UserIdentity(x509Token), new List { GdsRole.ApplicationUser }); + Utils.LogInfo("X509 Token Accepted: {0} as {1}", args.Identity.DisplayName, Role.AuthenticatedUser); + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(x509Token), new List { Role.AuthenticatedUser }); return; } @@ -350,9 +354,9 @@ private bool VerifyPassword(UserNameIdentityToken userNameToken) /// private void RegisterDefaultUsers() { - m_userDatabase.CreateUser("sysadmin", "demo", new List { GdsRole.ApplicationAdmin, Role.SecurityAdmin, Role.ConfigureAdmin }); - m_userDatabase.CreateUser("appadmin", "demo", new List { GdsRole.ApplicationAdmin }); - m_userDatabase.CreateUser("appuser", "demo", new List { GdsRole.ApplicationUser }); + m_userDatabase.CreateUser("sysadmin", "demo", new List { GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin, Role.SecurityAdmin, Role.ConfigureAdmin }); + m_userDatabase.CreateUser("appadmin", "demo", new List { GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin }); + m_userDatabase.CreateUser("appuser", "demo", new List { Role.AuthenticatedUser }); } /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs new file mode 100644 index 0000000000..dfbf23cdf7 --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Opc.Ua.Server; +using Opc.Ua.Gds.Server.Database; + +namespace Opc.Ua.Gds.Server + +{ + internal static class AuthorizationHelper + { + /// + /// Checks if the current session (context) has one of the requested roles. If is allowed the applicationId needs to be specified + /// + /// the current + /// 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 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"); + } + } + + 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 (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"); + } + + private static bool HasRole(IUserIdentity userIdentity, IEnumerable roles) + { + RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; + + if (identity != null) + { + foreach (Role role in roles) + { + if ((identity.Roles.Contains(role))) + { + return true; + } + } + } + return false; + } + + private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) + { + if (applicationId is null || applicationId.IsNullNodeId) + return false; + + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) + { + //self Admin only has access to own application + if (identity.ApplicationId == applicationId) + { + return true; + } + } + return false; + } + + private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) + { + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) + { + foreach (var certType in certTypeMap.Values) + { + applicationsDatabase.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); + if (trustedStorePath == trustListId) + { + return true; + } + } + } + return false; + } + } +} diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs similarity index 74% rename from Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs rename to Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs index b9e0cffdd1..60d190479e 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs @@ -42,20 +42,27 @@ public class GdsRole : Role /// /// The GDS application Administrator. /// + [Obsolete("use well known Roles DiscoveryAdmin, CertificateAuthorityAdmin, RegistrationAuthorityAdmin")] public static Role ApplicationAdmin { get; } = new Role(NodeId.Null, "ApplicationAdmin"); - /// - /// This Role grants rights to register, update and unregister any OPC UA Application. - /// - public static Role DiscoveryAdmin { get; } = new Role(NodeId.Null, "DiscoveryAdmin"); /// /// The GDS application user. /// + [Obsolete("use well known Roles DiscoveryAdmin, CertificateAuthorityAdmin, RegistrationAuthorityAdmin")] public static Role ApplicationUser { get; } = new Role(NodeId.Null, "ApplicationUser"); /// - /// Can manage the own Certificates and pull trust list + /// This Role grants rights to register, update and unregister any OPC UA Application. + /// + public static Role DiscoveryAdmin { get; } = new Role(ExpandedNodeId.ToNodeId(ObjectIds.WellKnownRole_DiscoveryAdmin, new NamespaceTable(new string[] { Namespaces.OpcUa, Namespaces.OpcUaGds })), BrowseNames.WellKnownRole_DiscoveryAdmin); + + public static Role CertificateAuthorityAdmin { get; } = new Role(ExpandedNodeId.ToNodeId(ObjectIds.WellKnownRole_CertificateAuthorityAdmin, new NamespaceTable(new string[] { Namespaces.OpcUa, Namespaces.OpcUaGds })), BrowseNames.WellKnownRole_CertificateAuthorityAdmin); + + public static Role RegistrationAuthorityAdmin { get; } = new Role(ExpandedNodeId.ToNodeId(ObjectIds.WellKnownRole_RegistrationAuthorityAdmin, new NamespaceTable(new string[] { Namespaces.OpcUa, Namespaces.OpcUaGds })), BrowseNames.WellKnownRole_RegistrationAuthorityAdmin); + + /// + /// A privilege to manage the own Certificates and pull trust list /// public static Role ApplicationSelfAdmin { get; } = new Role(NodeId.Null, "ApplicationSelfAdmin"); diff --git a/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs index ac6550f81b..063b0f4c29 100644 --- a/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs +++ b/Libraries/Opc.Ua.Server/RoleBasedUserManagement/RoleBasedIdentity.cs @@ -146,6 +146,14 @@ public override int GetHashCode() /// public static bool operator !=(Role lhs, Role rhs) => !(lhs == rhs); #endregion + /// + /// returns the name of the role + /// + /// the name of the role + public override string ToString() + { + return Name; + } } /// From d955bf05b4b53b761a2e7c6a0a7276a6cb2b46da Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Wed, 14 Feb 2024 19:28:51 +0100 Subject: [PATCH 24/32] add missing roles to standard gds users to fix failing tests --- .../Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs | 3 ++- .../RoleBasedUserManagement/AuthorizationHelper.cs | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index dae7b64773..5c8ccb957d 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -227,6 +227,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg } #pragma warning restore CS0618 // Type or member is obsolete args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), roles); + return; } } @@ -355,7 +356,7 @@ private bool VerifyPassword(UserNameIdentityToken userNameToken) private void RegisterDefaultUsers() { m_userDatabase.CreateUser("sysadmin", "demo", new List { GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin, Role.SecurityAdmin, Role.ConfigureAdmin }); - m_userDatabase.CreateUser("appadmin", "demo", new List { GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin }); + m_userDatabase.CreateUser("appadmin", "demo", new List { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin }); m_userDatabase.CreateUser("appuser", "demo", new List { Role.AuthenticatedUser }); } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs index dfbf23cdf7..705b45a1a0 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -20,7 +20,6 @@ internal static class AuthorizationHelper /// 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(); From 57e561aedaab209594b296af127c092f92b84963 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Fri, 16 Feb 2024 18:33:58 +0100 Subject: [PATCH 25/32] add new Standard Users delete obsolete roles document WellKnownRoles --- .../GlobalDiscoverySampleServer.cs | 19 +++--------------- .../RoleBasedUserManagement/GdsRole.cs | 13 ------------ Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 1 + Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx | Bin 0 -> 8812 bytes 4 files changed, 4 insertions(+), 29 deletions(-) create mode 100644 Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 5c8ccb957d..b58422490a 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -209,23 +209,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg return; } IEnumerable roles = m_userDatabase.GetUserRoles(userNameToken.UserName); - //GdsAdmin -#pragma warning disable CS0618 // Type or member is obsolete - if (roles.Contains(GdsRole.ApplicationAdmin)) - { - args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { GdsRole.DiscoveryAdmin, GdsRole.CertificateAuthorityAdmin }); - Utils.LogInfo("ApplicationAdmin Token Accepted: {0}", args.Identity.DisplayName); - return; - } - //GdsUser - if (roles.Contains(GdsRole.ApplicationUser)) - { - args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), new List { Role.AuthenticatedUser }); - Utils.LogInfo("ApplicationUser Token Accepted: {0}", args.Identity.DisplayName); - return; - } -#pragma warning restore CS0618 // Type or member is obsolete args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), roles); return; } @@ -358,6 +342,9 @@ private void RegisterDefaultUsers() m_userDatabase.CreateUser("sysadmin", "demo", new List { GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin, Role.SecurityAdmin, Role.ConfigureAdmin }); m_userDatabase.CreateUser("appadmin", "demo", new List { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin, GdsRole.DiscoveryAdmin }); m_userDatabase.CreateUser("appuser", "demo", new List { Role.AuthenticatedUser }); + + m_userDatabase.CreateUser("DiscoveryAdmin", "demo", new List { Role.AuthenticatedUser, GdsRole.DiscoveryAdmin }); + m_userDatabase.CreateUser("CertificateAuthorityAdmin", "demo", new List { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin}); } /// diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs index 60d190479e..e8b03b33bc 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs @@ -39,19 +39,6 @@ namespace Opc.Ua.Gds.Server /// public class GdsRole : Role { - /// - /// The GDS application Administrator. - /// - [Obsolete("use well known Roles DiscoveryAdmin, CertificateAuthorityAdmin, RegistrationAuthorityAdmin")] - public static Role ApplicationAdmin { get; } = new Role(NodeId.Null, "ApplicationAdmin"); - - - /// - /// The GDS application user. - /// - [Obsolete("use well known Roles DiscoveryAdmin, CertificateAuthorityAdmin, RegistrationAuthorityAdmin")] - public static Role ApplicationUser { get; } = new Role(NodeId.Null, "ApplicationUser"); - /// /// This Role grants rights to register, update and unregister any OPC UA Application. /// diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index e2d8d3ca71..f2ed58cfc2 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -1381,6 +1381,7 @@ private void ConnectGDS(bool admin, bool anonymous = false, TestContext.Progress.WriteLine($"GDS Client({admin}) connected -- {memberName}"); } + private void DisconnectGDS( [System.Runtime.CompilerServices.CallerMemberName] string memberName = "" ) diff --git a/Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx b/Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b2daea7ff5122293660fa20a6e106f4e9ab533e6 GIT binary patch literal 8812 zcmaJ`1zc2X)2F+;JEReo5NV`YYEh6_NokhuZbV8-LTU*CMFlBgk*=kYmIgsOWC003 z@cLc3zVCbI{LaQXXXZRJ^MB@Y5uiv>O?BllM&4yuvo!(sci*D zq-{m(!5xOjwNdikcZVW*sFU8(5)oRKQKe@^`v-wNI@^WQ)RZEg5hbxwxzc{j+D@J0 zP;-=O6ae0HwTGAPW&xr?Gb^~|%-KL6@+g3?ePC&bP%`SI{0WSS>J)wT82}4(Aw$It z_r_6AeSoeq6Kq5DiN!DZa3s+?%>`KisKCGnAAB!6jmxdO)MY*Ws5*Vf-6)l(FJ46{ z0@(qee^XodXA9)h=EM>B;>MP(=J?TKWkyyw{RvIfbDyQpo`np_lN zcb&Xh6034t@zycNy`J+Cg*{a_J+iCd6DTjF{p!Ph#~N?0&&`E5uJ<#0T?Cmy(V+TM zqsP0DrrB`eA1G2ym2ZUzq!rnnacO)6^w?df%%te?r#L%^+~ZANELoFq`eC=~(&>v~ zwSJHipH#M(JubA(-#bsQK-U5Q)I19~z(qdy0t2Xylw$W^PJHce3*^MxjeL=C(ek$H zgVAf}h}c#0%I|R~UN1rZ6!-xaBA2hV5??3Ws#IV_)I(P+9vpi1oj=IhXVg~5#F{yM zA}N4^gmi+6grxmHv4;N_*4*5E9IV`~k+yB91Idyg3;g)8_23Kxn+u=RYKC3F!|!Uy@kArotJ&hQStp~?!7yg1E=SwlO77o=~+)gAqIf(Dr!2Z zW9DYwSYYd+jrT36^?r|76Pd!% zf9W$|-qWD#$y7?9a@UmcT@>t=Ge0U4n<4rqo4~C_Ge__FJR&YJ6C-ru= ztZ8eJq`n^5A`-n|NZtGmZelyS^_e&^Ad=0gDGU-_#?-VkpdA4`OJ(Aa58;-t?Gzu6 zbcnbIRkAj9Bq7}h559cZCahVHRA4-?;J;Hl!ASfy%7>z`72i7+((DIj_RMzqW=}n8 zBS~{{mZel98n47h`lR|epF|)|;fY698l7m6MMkX{EDx*bB9unKLS>0YH3mJa9AiC) za>nqrT5%0FY4P@P*j$n9%ZAvNZYG@EP}daUG2FT}^(C zWKg*H~PXC+!iMd+pea*imE-_{-5?=ERa}N&K4NCkw8t9-!6sv>*pV# zrOl?j^m11fauiyQkX?%NA7mHOAuFK`*@zHAu#lwH#-zS^h~3)6f0F&pi5)aMij-pg z$fb$3o~1sNz}GixNrfxu&FH$`3yGXAdHjBbB2TM5@9w7pmUNbyU?WPQ|369+Gtkz) zWw{DT@Q5Z?nF~vEiVpwK=%i(s?>7r{j?c~UeL7E8n?ZPb&T`b{SuB(2=qfW67}%OS zJ}*ZIol35FFHs)Fgb-8LQB$aKsxSYP#0^j;xFa;CI0t4J^zIb5PwoJUcVCD}; z$(8o zYdqa_C$sgl3LAbWn#EqI@T`{ou|osT%8rC{|5p?8C7RL(5))Pr``1#Qv){-S5mTP4 zx263U^tY*U`=R!|@z^L*<@`<+@~PYk(* zgROb=ALkNd!^Ty4Z#=bRra!`Fn|8K;=&+s=UWbM~)2zgWtFW-m{BNOw=C9E3$koch z?H`K~|MivEx^i}Hf<%pLcr zpAr67@dh8=G`d78EHxgr_Nn=}xsHil+o@?tf_mY~3Z++J^nZs?(!cEFZe!(WCGhj} z_H`hgO*MhcFp)*>9`g_#^SFGzr%G0mnWotA@_np3Or~YB7}){YF+E5IPjl%_=_tT< z!=IEuau*)rX8vY3P$dNXT(nUSJKl+d>b4?0rChRQp3#$$l8PFa8(oJxZGlwy#Ocrs zpT|hBCo!3)Din=!`~i=ip)w0F0DK)v`2 z;Nn&@&!p9h9R!Cpf;~#JLiI6N(MMKK`oq@qHHjbtrGdn+#xs0FabP9VKl19zrix!78cZKrHM-mnjuL+?_2Z;>p5P<`>UUYIA1J!;)m2r%Yb3PQS-MpV8tvgt zNFG|gydGC293WSz>rdZdU4=yvh4E3ha7VF24=>8HDxCrckR?|0{Lry+ZM z=Ioa<)pJpgrB~tCxs#~!OrQb z`o59p3Zc^4Ak8n&G;q|Wkn4| zPzp~By+%(S9{Wg`{H~ASbmzy`{AG*E8blp~5pme}h4UI?SW4MrtXKG&!1_0gQT>In z>y*&V#>&dwP2lI(PoTlFXIy8*Nv?p#W$@v7IlU^93cXNGiLs{x7d*K$<3$+^s@QEF z!6L$D)#Z~Zsh;D|5@uNzG^_!f>Wg3vaL5mFO59*%@kX3Y0AEV=vf(eSH+Fq*%i(}p zZD9_#`Roo3ihb1dHO%x$+Kq5w8}u6#+P(DSYaDAC1W!YF78YdC{q|F#63G$zxe0(Yl{p{V7K8w&FLp)I2kj4tzs;6?^U3z6R4}5>0 z=&{f{^GEDHs}ZGoT{UxIjmcBFGfUoxJD%i|j10!v_+^BTvQI;LmFI-{lFAR~*w5Y$ z9epl*djes~9htW0GwIS2D+_BFQa|1%-xJ057mhA0cvxA!z_>qppKd@;@RQ_g#*fEap-3F0N3U5Z09DS{67BIiW=g$+bLd@g#}SnB)Wiw*rr(^Jh4eEv?2 zZ`Pzz%Yj7XKT4+Da#)I6P4JyZaw&*?jnaKrb!8TxC467@a<F9EW9t9qmI(V+Qs`shOR%5P(Yv!&M zXEu%pyLUp;0SpvqSNKZiMN`=51$jseh?O33bWrdR{Is{S8QvIM#TGd7%)B?X0=_xk zV&tMqHWV|0Nae8{7N*Lf@z~iC8dMsVIa<^k?uuQUTDZLnwsN^|_%;*$ZcUvMZlJ^d zyZ1x(6ibs!7&Ai&d``oy;x*6nJJu)7n`)`_7C_ifQBSn6l`u_pO?6B`3qwjKI20m+ z+O{$)IUm!`mNxf*RWfS;hV0HTlw_1xlq{4el=M(^NOaV?C_{R|dRLujSf?chHqC80 zR;3Wt5XBH+h(d^Zi#*_-#;!NUR{(=V940ykIt98YIstkG*raBCFD{<*DRTNCcD+B1 zvrgtS_=EC}&v|m9Hi=w81@$hu4VU?Gk!rnA4C#P#2Ge>?GA}(v(Nuxsqt@XDMpti*m@d|N!71epF|iuz6Hxn!tmovz*t< zIjn$RKOS{J_sb-~b>w$$D}3OeyhE{-Jqp+Gqwmj;|DvsodOO5G)`5wDh_bstGamJ` zhgJW($}iva)#jQW4CbGF(e}UbM5q|~dgCU_3yY8e51IwUMwY@+fBtt2bCh?%ld^P7 z{E6t0vLT-dTm+r|R=phR=cdmC`9o{k>;Dk(y6ncl8pqdq3Q5}Qi$ReAC^bZ|bN2?8qIX~e@Eu+08N47)1$+XV^tsswU_KV)* zU}=oSg0QxpMeixGG$vy~*zLaS$h*IGL9?TiOVh1TJQE<;=BV;au16|jljTDG{R(*i z7yEFvF3HOjG#IDed+wqc&*X;=oerT|d%3b)viNz1Dbv-vdc*dbUa$}S) zla~Ro;=+iz!Po||0n#qX;WHoZEa*1K5`Qb1V2S3&IDM1r#v3Rme1XP{$AuQR&vcYy zxE9)nY$2G{_#vBXuQgI?1#GuH>M5HH6f~vLdhLadQwe2MYV(i%=g#&6Qrl_yjNpv( zyOx=d+#mZ=Mt&JJ8inbqGBSl$F_cB%`m?)UnpuOSzBV})hu}h=yJOlZ=f)s}W@K6n--dxs#_@x1NQ``PYut={&X`N#VR$)E=jEnQJT+}+16#gAJ` zSvQxakOlBxEs2-I80i>1-p<$=X>}~|pK!omG_8U!&8-9XvBEDRPd2vAF#WaQSo9^Xno-V=}A8(B)SQ-?hl=}mrFI9a<%Po z&#e$Ps2o}o-4i;_Dtvgkj^tNcKM3QRZ_u05)&RM{@DCI>7u5;C-9mF1t}-yV%`Dk% z-BH_WU7shF1sCZ~JMWgy7#SaFcEnaq9>bMIjL(-GYd)Hg2L+w_QCfctAP^NAb2u%$ zAci!q&wF)*DUy7YG^7#&pL_X;dfReBD!2M&91b+guc}0|zLL3-usQ9gxwhOmklpVXCCPSzNF4Z8UK< zgUd54?LF-S-K}}y^A61cblfbyiS4MpVL%2k6HdRR z?oTy{Q$~pS%|(9`9i<_3CM6NXqNXL{bqqZnT|phi7gS>vJhb$VOIzxm&c~IfoiA<$e~C1 z*8po5Y{F!`0+&4YzfDQvD}Y%-EVNxA&Tax05Lc_89SL2y=5_t&kUVKOh7^&cZuN?H^pWRYIzBPwc+DixH zU0dYCd1ayYwoE4xifRDhs}QQ-g5XJJD22I@I4s1a6dAACY-bl!88t;_PIdF7aO%gE zW*iQs7zkf>s5P_2bJk&Wp}FDJM&>in4-f0^4eLTQ#;CF4^FwJ;s>EYR{6{1Nt6?X3 z76U2O?wI(LMW}bmjvvIg&N@+Uk7#b3Y3D*nAXF`j?KQtijQGpg=#c)2@7>s zRG##76l2V7Hjx>@^4}^f&(k7Y%>7~Dk9zH+DD3T0D`X_3nSTueL^pk8=Ing!q8CXz zkX9z5uK8mwno9lr*J^haZmXwB@l7#E)Yf7wmhtitnQDH#&=x-O2~WIxnLhtRe9~2h zV2Z$hXdLje`A%=MO8NUKz&b1-=hH1*9V&JV`#d>JESsL)$*fue|4&4f_XP9hW~gRE z6PaoSp9Cj6JR32RdGcKMBXiAebvP%_M`gjJ6dz6OEmkUYyCr^EcMg8f{g2vKtM5L( zwIk%;7q>Ta4~uT5u8({jXKr(Z)%ZMJwXSe$xL!`3cvRyg7VPIOm63=8l5Fd^h!X9+ z4Bx3Lrhj22#%|)+{jY_2c6sJFMMUVVwU3q8#!iKG zLE>7QdV5}7-5xvVKOsMIkPIZgGH2t}`R_Ho-;nokbB8$owCKP)oK``yh{f3wtf?72 z%%qX`vEq02juYo31ynSrs9=IWD?@_<`1pttI4S z3B56$sR+_j7XLe^`x_z`>I9bayC-uHWfo6B4`WbZ92Nm3?eI$%9|6(+g1}Bf!UdDFoDbf+|4bbmlKR-4vs>@yr(~@UPFGlYXfG z;_7Vv4Wqx2BwWzi-4DnbprWypOHMERNG;Yd=N$fp^m;Z$WCAjGu8c77ue0g8q-&%A zadNkEa(|@f<80+-a_xTDeYjKW&zPrEao>DBzpM<6Ab`%OaKvr3Gpn6Fuv?VuvT3t$ zC{!K^{fLBT?nTCnqh3u89vyUb!8D$XNT@dz29t0{9q-7VBZ6!;GI^gVo)wOOb zQ7)P}&#(BD0tyZFC7}du?aHb=1F}|kgVlQwWwC+WvBVG8CZE~+c^JsS&9#DYcl&m0 zS*1nZD{LT{gWt|Z3S$+2sQ0nt}_9=xH_n;4kh81o|;-npF$`na+ z40pwZfi}MwKjFGjZACNK5XK=@aK4k^(iP;t7$(KE15- ztYLfZAQ!AWdOmM=S$b4ch%s3%>^*GkzkccJH5PJGsx!SrZnbdEBHAP+F`|OjxxZsO z&&}cinOG2@$JjF7b})NE0$ZicB<;w$-LU73PogWMu?)HPAReA9X~@;&?ZUVQIx-3w z((f+%&7S9V7yX~~M^F7v=9^uS>$d1`a<~G^74z>V>7T4O8wJ;Gliw6|HOed2|ItMG zQ_js|@HGa1(;Lj6a{g>G{3+mOj(uG-{!OG;nfEUP{6_`(PboL^&g*>qH(gvMoBx#Z zU$gT+1>VdvuG7`uBtY>~;D02qe@eKy3SQ?UzbWxb&)=c?S9bCz`^~6-y;S}tz?HxL zX8*Nt{>gnaw*J#CVYD~6E&g67{uFRC>RgB5-(*DhkAVCq<4v&t^Ub03|20HutD|2b S4haeG>L9sl@!ey*{`Y@es54~% literal 0 HcmV?d00001 From 2e68d73f4d6bc1c70cda9b5012b11f91391d9a86 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Sun, 18 Feb 2024 15:54:57 +0100 Subject: [PATCH 26/32] minor optimizations & cleanup & documentation improvements --- .../GlobalDiscoverySampleServer.cs | 28 ++++++------------- .../AuthorizationHelper.cs | 12 ++++++-- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index b58422490a..c9ea9222c0 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -117,8 +117,7 @@ protected override MasterNodeManager CreateMasterNodeManager(IServerInternal ser /// protected override ServerProperties LoadServerProperties() { - ServerProperties properties = new ServerProperties - { + ServerProperties properties = new ServerProperties { ManufacturerName = "Some Company Inc", ProductName = "Global Discovery Server", ProductUri = "http://somecompany.com/GlobalDiscoveryServer", @@ -201,13 +200,6 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg { if (VerifyPassword(userNameToken)) { - if (userNameToken.UserName == "sysadmin") - { - // Server configuration administrator, manages the GDS server security - args.Identity = new SystemConfigurationIdentity(new UserIdentity(userNameToken)); - Utils.LogInfo("SystemConfigurationAdmin Token Accepted: {0}", args.Identity.DisplayName); - return; - } IEnumerable roles = m_userDatabase.GetUserRoles(userNameToken.UserName); args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), roles); @@ -230,7 +222,7 @@ private void SessionManager_ImpersonateUser(Session session, ImpersonateEventArg } //check if applicable for application self admin privilege - if(session.ClientCertificate != null) + if (session.ClientCertificate != null) { if (VerifiyApplicationRegistered(session)) { @@ -264,7 +256,7 @@ private bool VerifiyApplicationRegistered(Session session) var matchingCerts = ApplicationsStore.FindByThumbprint(applicationInstanceCertificate.Thumbprint).Result; if (matchingCerts.Contains(applicationInstanceCertificate)) - applicationRegistered = true; + applicationRegistered = true; } //check if application certificate is revoked using (ICertificateStore AuthoritiesStore = CertificateStoreIdentifier.OpenStore(configuration.AuthoritiesStorePath)) @@ -272,13 +264,9 @@ private bool VerifiyApplicationRegistered(Session session) var crls = AuthoritiesStore.EnumerateCRLs().Result; foreach (X509CRL crl in crls) { - var revokedCertificates = crl.RevokedCertificates; - foreach (RevokedCertificate revokedCertificate in revokedCertificates) + if (crl.IsRevoked(applicationInstanceCertificate)) { - if(revokedCertificate.SerialNumber == applicationInstanceCertificate.SerialNumber) - { - applicationRegistered = false; - } + applicationRegistered = false; } } } @@ -344,7 +332,7 @@ private void RegisterDefaultUsers() m_userDatabase.CreateUser("appuser", "demo", new List { Role.AuthenticatedUser }); m_userDatabase.CreateUser("DiscoveryAdmin", "demo", new List { Role.AuthenticatedUser, GdsRole.DiscoveryAdmin }); - m_userDatabase.CreateUser("CertificateAuthorityAdmin", "demo", new List { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin}); + m_userDatabase.CreateUser("CertificateAuthorityAdmin", "demo", new List { Role.AuthenticatedUser, GdsRole.CertificateAuthorityAdmin }); } /// @@ -356,7 +344,7 @@ private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEvent { string applicationUri = session.SessionDiagnostics.ClientDescription.ApplicationUri; ApplicationRecordDataType[] application = m_database.FindApplications(applicationUri); - if(application == null || application.Length != 1) + if (application == null || application.Length != 1) { Utils.LogInfo("Cannot login based on ApplicationInstanceCertificate, no uniqure result for Application with URI: {0}", applicationUri); return; @@ -367,7 +355,7 @@ private void ImpersonateAsApplicationSelfAdmin(Session session, ImpersonateEvent args.Identity = new GdsRoleBasedIdentity(new UserIdentity(), new List { GdsRole.ApplicationSelfAdmin }, applicationId); return; } - + #endregion #region Private Fields diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs index 705b45a1a0..efb2e79221 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -38,14 +38,22 @@ public static void HasAuthorization(ISystemContext context, IEnumerable ro 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 (CheckSelfAdminPrivilege(context.UserIdentity, trustedStorePath, certTypeMap, applicationsDatabase)) + 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"); From 6ef641488fb19655983010085dbc82b8158d3287 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 26 Feb 2024 10:45:44 +0100 Subject: [PATCH 27/32] push minor code cleanup --- .../ApplicationsNodeManager.cs | 2 +- .../AuthorizationHelper.cs | 33 +++++++++++++++++-- .../RoleBasedUserManagement/GdsRole.cs | 6 ++-- Tests/Opc.Ua.Gds.Tests/ClientTest.cs | 2 -- Tests/Opc.Ua.Gds.Tests/PushTest.cs | 1 - 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 6e8619163e..7c29b01a51 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -1338,7 +1338,7 @@ protected void SetCertificateGroupNodes(ICertificateGroup certificateGroup) #region AuthorizationHelpers private void HasTrustListAccess(ISystemContext context, string trustedStorePath) { - AuthorizationHelper.HasTrustListAccess(context, trustedStorePath, m_certTypeMap, m_database); + AuthorizationHelper.HasTrustListAccess(context, trustedStorePath, m_certTypeMap, m_database); } #endregion diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs index efb2e79221..7218f9279f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -1,11 +1,38 @@ +/* ======================================================================== + * Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Opc.Ua.Server; using Opc.Ua.Gds.Server.Database; +using Opc.Ua.Server; namespace Opc.Ua.Gds.Server diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs index e8b03b33bc..3c0dc41d46 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs @@ -1,5 +1,5 @@ /* ======================================================================== - * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. + * Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * @@ -27,9 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ -using System; using System.Collections.Generic; -using System.Runtime.InteropServices.ComTypes; using Opc.Ua.Server; namespace Opc.Ua.Gds.Server @@ -72,7 +70,7 @@ public GdsRoleBasedIdentity(IUserIdentity identity, IEnumerable roles, Nod public GdsRoleBasedIdentity(IUserIdentity identity, IEnumerable roles) : base(identity, roles) - {} + { } /// /// The applicationId in case the ApplicationSelfAdminPrivilege is used diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index f2ed58cfc2..2b7034f433 100644 --- a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs @@ -35,8 +35,6 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; -using Org.BouncyCastle.Tls; -using static System.Net.Mime.MediaTypeNames; namespace Opc.Ua.Gds.Tests { diff --git a/Tests/Opc.Ua.Gds.Tests/PushTest.cs b/Tests/Opc.Ua.Gds.Tests/PushTest.cs index c31bc12732..8b27723c4c 100644 --- a/Tests/Opc.Ua.Gds.Tests/PushTest.cs +++ b/Tests/Opc.Ua.Gds.Tests/PushTest.cs @@ -28,7 +28,6 @@ * ======================================================================*/ using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; From 5449305c7db9d0df2164ede6976ade9850fc53fa Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 26 Feb 2024 11:53:26 +0100 Subject: [PATCH 28/32] check on some warnings --- .../LinqApplicationsDatabase.cs | 12 ++--- .../ApplicationsNodeManager.cs | 27 +++++----- .../CertificateGroup.cs | 53 +++++++++---------- .../GlobalDiscoveryServerConfiguration.cs | 2 +- .../ICertificateGroup.cs | 6 +-- .../Security/Certificates/X509Utils.cs | 2 +- 6 files changed, 52 insertions(+), 50 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs index 0b1209c793..235b5d2b01 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs @@ -665,7 +665,7 @@ byte[] certificate public override bool GetApplicationCertificate( NodeId applicationId, - string certificateType, + string certificateTypeId, out byte[] certificate) { certificate = null; @@ -685,7 +685,7 @@ public override bool GetApplicationCertificate( throw new ArgumentException("A record with the specified application id does not exist.", nameof(applicationId)); } - if (!application.Certificate.TryGetValue(certificateType, out certificate)) + if (!application.Certificate.TryGetValue(certificateTypeId, out certificate)) { return false; } @@ -695,7 +695,7 @@ public override bool GetApplicationCertificate( public override bool SetApplicationTrustLists( NodeId applicationId, - string certificateType, + string certificateTypeId, string trustListId ) { @@ -711,7 +711,7 @@ string trustListId if (trustListId != null) { - result.TrustListId[certificateType] = trustListId; + result.TrustListId[certificateTypeId] = trustListId; } SaveChanges(); } @@ -721,7 +721,7 @@ string trustListId public override bool GetApplicationTrustLists( NodeId applicationId, - string certificateType, + string certificateTypeId, out string trustListId ) { @@ -736,7 +736,7 @@ out string trustListId { return false; } - return result.TrustListId.TryGetValue(certificateType, out trustListId); + return result.TrustListId.TryGetValue(certificateTypeId, out trustListId); } } #endregion diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 7c29b01a51..171f32d6ef 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -231,13 +231,14 @@ private ICertificateGroup GetGroupForCertificate(byte[] certificate) { if (certificate != null && certificate.Length > 0) { - var x509 = new X509Certificate2(certificate); - - foreach (var certificateGroup in m_certificateGroups.Values) + using (var x509 = new X509Certificate2(certificate)) { - if (X509Utils.CompareDistinguishedName(certificateGroup.Certificate.Subject, x509.Issuer)) + foreach (var certificateGroup in m_certificateGroups.Values) { - return certificateGroup; + if (X509Utils.CompareDistinguishedName(certificateGroup.Certificate.Subject, x509.Issuer)) + { + return certificateGroup; + } } } } @@ -253,14 +254,16 @@ private async Task RevokeCertificateAsync(byte[] certificate) if (certificateGroup != null) { - try + using (var x509 = new X509Certificate2(certificate)) { - var x509 = new X509Certificate2(certificate); - await certificateGroup.RevokeCertificateAsync(x509).ConfigureAwait(false); - } - catch (Exception e) - { - Utils.LogError(e, "Unexpected error revoking certificate. {0} for Authority={1}", new X509Certificate2(certificate).Subject, certificateGroup.Id); + try + { + await certificateGroup.RevokeCertificateAsync(x509).ConfigureAwait(false); + } + catch (Exception e) + { + Utils.LogError(e, "Unexpected error revoking certificate. {0} for Authority={1}", x509.Subject, certificateGroup.Id); + } } } } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs index 1a582e7d60..4f7a29b45f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs @@ -175,25 +175,25 @@ public virtual async Task NewKeyPairRequestAsync( public async virtual Task RevokeCertificateAsync( X509Certificate2 certificate) { - Task crl = RevokeCertificateAsync( + X509CRL crl = await RevokeCertificateAsync( AuthoritiesStorePath, certificate, - null); + null).ConfigureAwait(false); - //Also update TrustedList CRL so registerd Applications can get the new CRL - await crl.ContinueWith((_) => - UpdateAuthorityCertInCertificateStore(Configuration.TrustedListPath) - , TaskContinuationOptions.OnlyOnRanToCompletion).ConfigureAwait(false); - - //Also update TrustedIssuerCertificates Store - if (!String.IsNullOrEmpty(TrustedIssuerCertificatesStorePath)) + // Also update TrustedList CRL so registerd Applications can get the new CRL + if (crl != null) { - await crl.ContinueWith((_) => - UpdateAuthorityCertInCertificateStore(TrustedIssuerCertificatesStorePath) - , TaskContinuationOptions.OnlyOnRanToCompletion).ConfigureAwait(false); + await UpdateAuthorityCertInCertificateStore(Configuration.TrustedListPath).ConfigureAwait(false); + + //Also update TrustedIssuerCertificates Store + if (!String.IsNullOrEmpty(TrustedIssuerCertificatesStorePath)) + { + await UpdateAuthorityCertInCertificateStore(TrustedIssuerCertificatesStorePath).ConfigureAwait(false); + } } - //return crl - return await crl.ConfigureAwait(false); + + // return crl + return crl; } public virtual Task VerifySigningRequestAsync( @@ -319,35 +319,34 @@ string subjectName } DateTime yesterday = DateTime.Today.AddDays(-1); - using (X509Certificate2 newCertificate = CertificateFactory.CreateCertificate(subjectName) + using (X509Certificate2 newCertificate = await CertificateFactory.CreateCertificate(subjectName) .SetNotBefore(yesterday) .SetLifeTime(Configuration.CACertificateLifetime) .SetHashAlgorithm(X509Utils.GetRSAHashAlgorithmName(Configuration.CACertificateHashSize)) .SetCAConstraint() .SetRSAKeySize(Configuration.CACertificateKeySize) .CreateForRSA() - .AddToStore( + .AddToStoreAsync( AuthoritiesStoreType, - AuthoritiesStorePath)) + AuthoritiesStorePath).ConfigureAwait(false)) { // save only public key Certificate = new X509Certificate2(newCertificate.RawData); // initialize revocation list - Task crlTask = RevokeCertificateAsync(AuthoritiesStorePath, newCertificate, null); + X509CRL crl = await RevokeCertificateAsync(AuthoritiesStorePath, newCertificate, null).ConfigureAwait(false); //Update TrustedList Store - await crlTask.ContinueWith((_) => - UpdateAuthorityCertInCertificateStore(Configuration.TrustedListPath) - , TaskContinuationOptions.OnlyOnRanToCompletion).ConfigureAwait(false); - - //Update TrustedIssuerCertificates Store - if (!string.IsNullOrEmpty(TrustedIssuerCertificatesStorePath)) + if (crl != null) { - await crlTask.ContinueWith((_) => - UpdateAuthorityCertInCertificateStore(TrustedIssuerCertificatesStorePath) - , TaskContinuationOptions.OnlyOnRanToCompletion).ConfigureAwait(false); + await UpdateAuthorityCertInCertificateStore(Configuration.TrustedListPath).ConfigureAwait(false); + + // Update TrustedIssuerCertificates Store + if (!string.IsNullOrEmpty(TrustedIssuerCertificatesStorePath)) + { + await UpdateAuthorityCertInCertificateStore(TrustedIssuerCertificatesStorePath).ConfigureAwait(false); + } } return Certificate; } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs index 9a8d4a4bc6..66ac865827 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoveryServerConfiguration.cs @@ -184,7 +184,7 @@ public CertificateGroupConfigurationCollection() { } /// Initializes the collection from another collection. /// /// A collection of values to add to this new collection - /// + /// /// is null. /// public CertificateGroupConfigurationCollection(IEnumerable collection) : base(collection) { } diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs index 147fddd49e..f8fe6055c3 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ICertificateGroup.cs @@ -36,9 +36,9 @@ namespace Opc.Ua.Gds.Server { public class X509Certificate2KeyPair { - public readonly X509Certificate2 Certificate; - public readonly string PrivateKeyFormat; - public readonly byte[] PrivateKey; + public X509Certificate2 Certificate { get; } + public string PrivateKeyFormat { get; } + public byte[] PrivateKey { get; } public X509Certificate2KeyPair(X509Certificate2 certificate, string privateKeyFormat, byte[] privateKey) { diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs index 3be9a480fb..e47f1a4c27 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs @@ -45,7 +45,7 @@ public static IList GetDomainsFromCertificate(X509Certificate2 certifica for (int ii = 0; ii < fields.Count; ii++) { - if (fields[ii].StartsWith("DC=")) + if (fields[ii].StartsWith("DC=", StringComparison.Ordinal)) { if (builder.Length > 0) { From 80cc82bc5d764907dc6e038a8c98117a45c295d1 Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 26 Feb 2024 13:19:53 +0100 Subject: [PATCH 29/32] use discard for unused parameter --- .../Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 515b9aa884..5973b952c3 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -307,10 +307,10 @@ public void HasApplicationSecureAdminAccess(ISystemContext context) /// Determine if the impersonated user has admin access. /// /// - /// + /// /// /// - public void HasApplicationSecureAdminAccess(ISystemContext context, string trustedStorePath) + public void HasApplicationSecureAdminAccess(ISystemContext context, string _) { OperationContext operationContext = (context as SystemContext)?.OperationContext as OperationContext; if (operationContext != null) From c213b0d55b3f5e231ba9084ca18d1568eb68e77f Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Mon, 26 Feb 2024 13:46:08 +0100 Subject: [PATCH 30/32] small cleanup --- .../GlobalDiscoveryTestClient.cs | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 110c123397..8cb03fc7dd 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -30,6 +30,7 @@ using System; using System.IO; using System.Runtime.Serialization; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Opc.Ua.Configuration; using Opc.Ua.Gds.Client; @@ -120,10 +121,10 @@ public async Task LoadClientConfiguration(int port = -1) AdminUser = new UserIdentity(gdsClientConfiguration.AdminUserName, gdsClientConfiguration.AdminPassword); Anonymous = new UserIdentity(); } + /// -         /// Register the Test Client at the used GDS, needed to test the ApplicationSelfAdminPrivilege -         /// -         /// + /// Register the Test Client at the used GDS, needed to test the ApplicationSelfAdminPrivilege + /// public bool RegisterTestClientAtGds() { try @@ -154,7 +155,7 @@ public bool RegisterTestClientAtGds() return false; } //apply cert - ApplyNewApplicationInstanceCertificate(certificate, privateKey); + ApplyNewApplicationInstanceCertificateAsync(certificate, privateKey).Wait(); OwnApplicationTestData.Certificate = certificate; OwnApplicationTestData.PrivateKey = privateKey; OwnApplicationTestData.CertificateRequestId = null; @@ -164,7 +165,7 @@ public bool RegisterTestClientAtGds() Console.WriteLine("RegisterTestClientAtGds at GDS failed" + e.ToString()); return false; } - + return true; } @@ -187,16 +188,17 @@ public string ReadLogFile() } #endregion #region Private Methods - private void ApplyNewApplicationInstanceCertificate(byte[] certificate, byte[] privateKey) + private async Task ApplyNewApplicationInstanceCertificateAsync(byte[] certificate, byte[] privateKey) { - var cert = CertificateFactory.CreateCertificateWithPEMPrivateKey( - new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate), - privateKey); - m_client.Configuration.SecurityConfiguration.ApplicationCertificate.RawData = cert.RawData; - m_client.Configuration.SecurityConfiguration.ApplicationCertificate.Thumbprint = cert.Thumbprint; - var store = m_client.Configuration.SecurityConfiguration.ApplicationCertificate.OpenStore(); - store.Add(cert); - m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); + using (var x509 = new X509Certificate2(certificate)) + { + var certWithPrivateKey = CertificateFactory.CreateCertificateWithPEMPrivateKey(x509, privateKey); + m_client.Configuration.SecurityConfiguration.ApplicationCertificate.RawData = certWithPrivateKey.RawData; + m_client.Configuration.SecurityConfiguration.ApplicationCertificate.Thumbprint = certWithPrivateKey.Thumbprint; + var store = m_client.Configuration.SecurityConfiguration.ApplicationCertificate.OpenStore(); + await store.Add(certWithPrivateKey).ConfigureAwait(false); + } + await m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); } private void FinishKeyPair(ApplicationTestData ownApplicationTestData, out byte[] certificate, out byte[] privateKey) @@ -256,18 +258,18 @@ private static void CertificateValidator_CertificateValidation(CertificateValida private ApplicationTestData GetOwnApplicationData() { ApplicationTestData - //fill application record data type with own Data - ownApplicationTestData = new ApplicationTestData { - ApplicationRecord = new ApplicationRecordDataType { - ApplicationUri = m_client.Configuration.ApplicationUri, - ApplicationType = m_client.Configuration.ApplicationType, - ProductUri = m_client.Configuration.ProductUri, - ApplicationNames = new LocalizedTextCollection() { new LocalizedText(m_client.Configuration.ApplicationName) }, - ApplicationId = new NodeId(Guid.NewGuid()) - }, - PrivateKeyFormat = "PEM", - Subject = $"CN={m_client.Configuration.ApplicationName},DC={Utils.GetHostName()},O=OPC Foundation", - }; + //fill application record data type with own Data + ownApplicationTestData = new ApplicationTestData { + ApplicationRecord = new ApplicationRecordDataType { + ApplicationUri = m_client.Configuration.ApplicationUri, + ApplicationType = m_client.Configuration.ApplicationType, + ProductUri = m_client.Configuration.ProductUri, + ApplicationNames = new LocalizedTextCollection() { new LocalizedText(m_client.Configuration.ApplicationName) }, + ApplicationId = new NodeId(Guid.NewGuid()) + }, + PrivateKeyFormat = "PEM", + Subject = $"CN={m_client.Configuration.ApplicationName},DC={Utils.GetHostName()},O=OPC Foundation", + }; return ownApplicationTestData; } From 5b000028d3e62eb7eb3ac524860c644eba64b1ab Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 26 Feb 2024 17:36:33 +0100 Subject: [PATCH 31/32] fix GDSTestClient --- Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs | 5 ++++- Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs index 4f7a29b45f..7f4227db8f 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs @@ -480,7 +480,10 @@ protected async Task UpdateAuthorityCertInCertificateStore(string path) X509Certificate2Collection certs = await store.FindByThumbprint(certificate.Thumbprint).ConfigureAwait(false); if (certs.Count == 0) { - await store.Add(new X509Certificate2(certificate.RawData)).ConfigureAwait(false); + using (var x509 = new X509Certificate2(certificate.RawData)) + { + await store.Add(x509).ConfigureAwait(false); + } } // delete existing CRL in trusted list diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 8cb03fc7dd..38c641df6f 100644 --- a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs +++ b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs @@ -195,10 +195,10 @@ private async Task ApplyNewApplicationInstanceCertificateAsync(byte[] certificat var certWithPrivateKey = CertificateFactory.CreateCertificateWithPEMPrivateKey(x509, privateKey); m_client.Configuration.SecurityConfiguration.ApplicationCertificate.RawData = certWithPrivateKey.RawData; m_client.Configuration.SecurityConfiguration.ApplicationCertificate.Thumbprint = certWithPrivateKey.Thumbprint; + m_client.Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certWithPrivateKey; var store = m_client.Configuration.SecurityConfiguration.ApplicationCertificate.OpenStore(); await store.Add(certWithPrivateKey).ConfigureAwait(false); } - await m_application.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false); } private void FinishKeyPair(ApplicationTestData ownApplicationTestData, out byte[] certificate, out byte[] privateKey) From 065b3921394921967628d836a09b549c3b368eae Mon Sep 17 00:00:00 2001 From: Roman Ettlinger Date: Mon, 26 Feb 2024 18:30:52 +0100 Subject: [PATCH 32/32] improve calls to authorizationHelper to be cleaner --- .../ApplicationsNodeManager.cs | 22 +-- .../AuthorizationHelper.cs | 133 +++++++++--------- 2 files changed, 80 insertions(+), 75 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 171f32d6ef..c5d9c92da1 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -493,7 +493,7 @@ private ServiceResult OnRegisterApplication( ApplicationRecordDataType application, ref NodeId applicationId) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.DiscoveryAdmin }); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.DiscoveryAdmin); Utils.LogInfo("OnRegisterApplication: {0}", application.ApplicationUri); @@ -508,7 +508,7 @@ private ServiceResult OnUpdateApplication( NodeId objectId, ApplicationRecordDataType application) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.DiscoveryAdmin }); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.DiscoveryAdmin); Utils.LogInfo("OnUpdateApplication: {0}", application.ApplicationUri); @@ -530,7 +530,7 @@ private ServiceResult OnUnregisterApplication( NodeId objectId, NodeId applicationId) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.DiscoveryAdmin }); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.DiscoveryAdmin); Utils.LogInfo("OnUnregisterApplication: {0}", applicationId.ToString()); @@ -565,7 +565,7 @@ private ServiceResult OnFindApplications( string applicationUri, ref ApplicationRecordDataType[] applications) { - AuthorizationHelper.HasAuthorization(context, new List { Role.AuthenticatedUser }); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.AuthenticatedUser); Utils.LogInfo("OnFindApplications: {0}", applicationUri); applications = m_database.FindApplications(applicationUri); return ServiceResult.Good; @@ -578,7 +578,7 @@ private ServiceResult OnGetApplication( NodeId applicationId, ref ApplicationRecordDataType application) { - AuthorizationHelper.HasAuthorization(context, new List { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }, applicationId); ; + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.AuthenticatedUserOrSelfAdmin, applicationId); ; Utils.LogInfo("OnGetApplication: {0}", applicationId); application = m_database.GetApplication(applicationId); return ServiceResult.Good; @@ -738,7 +738,7 @@ private ServiceResult OnStartNewKeyPairRequest( string privateKeyPassword, ref NodeId requestId) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ; + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; var application = m_database.GetApplication(applicationId); @@ -857,7 +857,7 @@ private ServiceResult OnStartSigningRequest( byte[] certificateRequest, ref NodeId requestId) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ; + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; var application = m_database.GetApplication(applicationId); @@ -938,7 +938,7 @@ private ServiceResult OnFinishRequest( signedCertificate = null; issuerCertificates = null; privateKey = null; - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); ; + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; var application = m_database.GetApplication(applicationId); if (application == null) @@ -1106,7 +1106,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); @@ -1134,7 +1134,7 @@ public ServiceResult OnGetTrustList( NodeId certificateGroupId, ref NodeId trustListId) { - AuthorizationHelper.HasAuthorization(context, new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }, applicationId); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); @@ -1167,7 +1167,7 @@ public ServiceResult OnGetCertificateStatus( NodeId certificateTypeId, ref Boolean updateRequired) { - AuthorizationHelper.HasAuthorization(context, new List { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }, applicationId); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.AuthenticatedUserOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); diff --git a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs index 7218f9279f..4bf0e86e28 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -39,6 +39,11 @@ namespace Opc.Ua.Gds.Server { internal static class AuthorizationHelper { + internal static List AuthenticatedUser { get; } = new List { Role.AuthenticatedUser }; + internal static List DiscoveryAdmin { get; } = new List { GdsRole.DiscoveryAdmin }; + internal static List AuthenticatedUserOrSelfAdmin { get; } = new List { Role.AuthenticatedUser, GdsRole.ApplicationSelfAdmin }; + internal static List CertificateAuthorityAdminOrSelfAdmin { get; } = new List { GdsRole.CertificateAuthorityAdmin, GdsRole.ApplicationSelfAdmin }; + /// /// Checks if the current session (context) has one of the requested roles. If is allowed the applicationId needs to be specified /// @@ -46,95 +51,95 @@ 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) { - if (context != null) - { - List allowedRoles = roles.ToList(); - bool selfAdmin = allowedRoles.Remove(GdsRole.ApplicationSelfAdmin); + List allowedRoles = roles.ToList(); + bool selfAdmin = allowedRoles.Remove(GdsRole.ApplicationSelfAdmin); - //if true access is allowed - if (HasRole(context.UserIdentity, allowedRoles)) - return; + //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"); + 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"); } - /// - /// 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; + } + /// + /// 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; + 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"); - } + throw new ServiceResultException(StatusCodes.BadUserAccessDenied, $"At least one of the Roles {string.Join(", ", roles)} or ApplicationSelfAdminPrivilege is required to use the TrustList"); + } - private static bool HasRole(IUserIdentity userIdentity, IEnumerable roles) - { - RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; + private static bool HasRole(IUserIdentity userIdentity, IEnumerable roles) + { + RoleBasedIdentity identity = userIdentity as RoleBasedIdentity; - if (identity != null) + if (identity != null) + { + foreach (Role role in roles) { - foreach (Role role in roles) + if ((identity.Roles.Contains(role))) { - if ((identity.Roles.Contains(role))) - { - return true; - } + return true; } } - return false; } + return false; + } - private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) - { - if (applicationId is null || applicationId.IsNullNodeId) - return false; + private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, NodeId applicationId) + { + if (applicationId is null || applicationId.IsNullNodeId) + return false; - GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; - if (identity != null) + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) + { + //self Admin only has access to own application + if (identity.ApplicationId == applicationId) { - //self Admin only has access to own application - if (identity.ApplicationId == applicationId) - { - return true; - } + return true; } - return false; } + return false; + } - private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) + private static bool CheckSelfAdminPrivilege(IUserIdentity userIdentity, string trustedStorePath, Dictionary certTypeMap, IApplicationsDatabase applicationsDatabase) + { + GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; + if (identity != null) { - GdsRoleBasedIdentity identity = userIdentity as GdsRoleBasedIdentity; - if (identity != null) + foreach (var certType in certTypeMap.Values) { - foreach (var certType in certTypeMap.Values) + applicationsDatabase.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); + if (trustedStorePath == trustListId) { - applicationsDatabase.GetApplicationTrustLists(identity.ApplicationId, certType, out var trustListId); - if (trustedStorePath == trustListId) - { - return true; - } + return true; } } - return false; } + return false; } } +}