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 97% rename from Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs rename to Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/IApplicationsDatabase.cs index 7a2072af21..071872decb 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/IApplicationsDatabase.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/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.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 97% rename from Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs rename to Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/LinqApplicationsDatabase.cs index 6d3cf5208f..235b5d2b01 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/LinqApplicationsDatabase.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsDatabase/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] @@ -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,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[certificateTypeId] = trustListId; } SaveChanges(); } @@ -725,7 +721,7 @@ string trustListId public override bool GetApplicationTrustLists( NodeId applicationId, - string certificateType, + string certificateTypeId, out string trustListId ) { @@ -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(certificateTypeId, out trustListId); } - - return false; } #endregion diff --git a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs index 85e67afc0e..c5d9c92da1 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/ApplicationsNodeManager.cs @@ -162,33 +162,7 @@ public override NodeId New(ISystemContext context, NodeState node) } #endregion - #region Private Methods - private void HasApplicationAdminAccess(ISystemContext context) - { - if (context != null) - { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if ((identity == null) || (!identity.Roles.Contains(GdsRole.ApplicationAdmin))) - { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application Administrator access required."); - } - } - } - - private void HasApplicationUserAccess(ISystemContext context) - { - if (context != null) - { - RoleBasedIdentity identity = context.UserIdentity as RoleBasedIdentity; - - if (identity == null) - { - throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Application User access required."); - } - } - } - + #region Private methods private NodeId GetTrustListId(NodeId certificateGroupId) { @@ -257,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; + } } } } @@ -279,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); + } } } } @@ -516,7 +493,7 @@ private ServiceResult OnRegisterApplication( ApplicationRecordDataType application, ref NodeId applicationId) { - HasApplicationAdminAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.DiscoveryAdmin); Utils.LogInfo("OnRegisterApplication: {0}", application.ApplicationUri); @@ -531,7 +508,7 @@ private ServiceResult OnUpdateApplication( NodeId objectId, ApplicationRecordDataType application) { - HasApplicationAdminAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.DiscoveryAdmin); Utils.LogInfo("OnUpdateApplication: {0}", application.ApplicationUri); @@ -553,7 +530,7 @@ private ServiceResult OnUnregisterApplication( NodeId objectId, NodeId applicationId) { - HasApplicationAdminAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.DiscoveryAdmin); Utils.LogInfo("OnUnregisterApplication: {0}", applicationId.ToString()); @@ -588,7 +565,7 @@ private ServiceResult OnFindApplications( string applicationUri, ref ApplicationRecordDataType[] applications) { - HasApplicationUserAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.AuthenticatedUser); Utils.LogInfo("OnFindApplications: {0}", applicationUri); applications = m_database.FindApplications(applicationUri); return ServiceResult.Good; @@ -601,7 +578,7 @@ private ServiceResult OnGetApplication( NodeId applicationId, ref ApplicationRecordDataType application) { - HasApplicationUserAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.AuthenticatedUserOrSelfAdmin, applicationId); ; Utils.LogInfo("OnGetApplication: {0}", applicationId); application = m_database.GetApplication(applicationId); return ServiceResult.Good; @@ -761,7 +738,7 @@ private ServiceResult OnStartNewKeyPairRequest( string privateKeyPassword, ref NodeId requestId) { - HasApplicationAdminAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; var application = m_database.GetApplication(applicationId); @@ -880,7 +857,7 @@ private ServiceResult OnStartSigningRequest( byte[] certificateRequest, ref NodeId requestId) { - HasApplicationAdminAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; var application = m_database.GetApplication(applicationId); @@ -961,7 +938,7 @@ private ServiceResult OnFinishRequest( signedCertificate = null; issuerCertificates = null; privateKey = null; - HasApplicationAdminAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); ; var application = m_database.GetApplication(applicationId); if (application == null) @@ -1115,6 +1092,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; @@ -1127,7 +1106,7 @@ public ServiceResult OnGetCertificateGroups( NodeId applicationId, ref NodeId[] certificateGroupIds) { - HasApplicationUserAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); @@ -1155,7 +1134,7 @@ public ServiceResult OnGetTrustList( NodeId certificateGroupId, ref NodeId trustListId) { - HasApplicationUserAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.CertificateAuthorityAdminOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); @@ -1188,7 +1167,7 @@ public ServiceResult OnGetCertificateStatus( NodeId certificateTypeId, ref Boolean updateRequired) { - HasApplicationUserAccess(context); + AuthorizationHelper.HasAuthorization(context, AuthorizationHelper.AuthenticatedUserOrSelfAdmin, applicationId); var application = m_database.GetApplication(applicationId); @@ -1354,11 +1333,19 @@ protected void SetCertificateGroupNodes(ICertificateGroup certificateGroup) certificateGroup.DefaultTrustList, certificateGroup.Configuration.TrustedListPath, certificateGroup.Configuration.IssuerListPath, - new TrustList.SecureAccess(HasApplicationUserAccess), - new TrustList.SecureAccess(HasApplicationAdminAccess)); + 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) { switch (state) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs b/Libraries/Opc.Ua.Gds.Server.Common/CertificateGroup.cs index 1a582e7d60..7f4227db8f 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; } @@ -481,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/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs b/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs deleted file mode 100644 index 89279b2514..0000000000 --- a/Libraries/Opc.Ua.Gds.Server.Common/GdsRole.cs +++ /dev/null @@ -1,58 +0,0 @@ -/* ======================================================================== - * Copyright (c) 2005-2020 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 Opc.Ua.Server; - -namespace Opc.Ua.Gds.Server -{ - /// - /// The supported roles in a GDS server. - /// - public class GdsRole : Role - { - /// - /// The GDS application Administrator. - /// - 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. - /// - public static Role ApplicationUser { get; } = new Role(NodeId.Null, "ApplicationUser"); - - public GdsRole(NodeId roleId, string name): - base(roleId, name) - {} - } -} diff --git a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs index 927afc1821..c9ea9222c0 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs +++ b/Libraries/Opc.Ua.Gds.Server.Common/GlobalDiscoverySampleServer.cs @@ -30,11 +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.Server.UserDatabase; using System.Linq; +using Opc.Ua.Security.Certificates; namespace Opc.Ua.Gds.Server { @@ -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,28 +200,10 @@ 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); - //GdsAdmin - if (roles.Contains(GdsRole.ApplicationAdmin)) - { - 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; - } + + args.Identity = new GdsRoleBasedIdentity(new UserIdentity(userNameToken), roles); + return; } } @@ -235,10 +216,61 @@ 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 RoleBasedIdentity(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; } + + //check if applicable for application self admin privilege + if (session.ClientCertificate != null) + { + if (VerifiyApplicationRegistered(session)) + { + ImpersonateAsApplicationSelfAdmin(session, args); + } + } + } + + /// + /// Verifies if an Application is registered with the provided certificate at at the GDS + /// + /// the session + /// + 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) + { + 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) + { + if (crl.IsRevoked(applicationInstanceCertificate)) + { + applicationRegistered = false; + } + } + } + return applicationRegistered; } /// @@ -295,10 +327,35 @@ 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 { 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 }); + } + + /// + /// 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) + { + 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 GdsRoleBasedIdentity(new UserIdentity(), new List { GdsRole.ApplicationSelfAdmin }, applicationId); + return; } + #endregion #region Private Fields 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/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..4bf0e86e28 --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/AuthorizationHelper.cs @@ -0,0 +1,145 @@ +/* ======================================================================== + * 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 Opc.Ua.Gds.Server.Database; +using Opc.Ua.Server; + +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 + /// + /// 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"); + } + } + /// + /// 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; + + 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/RoleBasedUserManagement/GdsRole.cs b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs new file mode 100644 index 0000000000..3c0dc41d46 --- /dev/null +++ b/Libraries/Opc.Ua.Gds.Server.Common/RoleBasedUserManagement/GdsRole.cs @@ -0,0 +1,83 @@ +/* ======================================================================== + * 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.Collections.Generic; +using Opc.Ua.Server; + +namespace Opc.Ua.Gds.Server +{ + /// + /// The supported roles in a GDS server. + /// + public class GdsRole : Role + { + /// + /// 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"); + + 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.Server/Configuration/ConfigurationNodeManager.cs b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs index 4497c61d42..5973b952c3 100644 --- a/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Configuration/ConfigurationNodeManager.cs @@ -298,6 +298,19 @@ public NamespaceMetadataState CreateNamespaceMetadataState(string namespaceUri) /// /// public void HasApplicationSecureAdminAccess(ISystemContext context) + { + HasApplicationSecureAdminAccess(context, ""); + } + + + /// + /// Determine if the impersonated user has admin access. + /// + /// + /// + /// + /// + public void HasApplicationSecureAdminAccess(ISystemContext context, string _) { OperationContext operationContext = (context as SystemContext)?.OperationContext as OperationContext; if (operationContext != null) 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/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; + } } /// 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) { diff --git a/Tests/Opc.Ua.Gds.Tests/ClientTest.cs b/Tests/Opc.Ua.Gds.Tests/ClientTest.cs index 88385ad2c2..2b7034f433 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_gdsRegisteredTestClient = false; } /// @@ -735,7 +736,9 @@ out byte[][] issuerCertificates } while (requestBusy); } - [Test, Order(511)] + + + [Test, Order(512)] public void FinishInvalidNewKeyPairRequests() { AssertIgnoreTestWithoutInvalidRegistration(); @@ -936,13 +939,339 @@ 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); } } } + [Test, Order(620)] + public void FailToGetGoodCertificateGroupsWithoutPriviledges() + { + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + + //connect to GDS without Admin Privilege + ConnectGDS(false); + + 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()); + + } + } + } + + /// + /// use self registered application and get the group / trust lists + /// + [Test, Order(630)] + public void GetGoodCertificateGroupsAsSelfAdmin() + { + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + + // register at gds and get gds issued certificate + var success = m_gdsClient.RegisterTestClientAtGds(); + + if (success) + { + m_gdsRegisteredTestClient = true; + } + else + { + Assert.Fail("Registering test Client at GDS failed"); + } + + ConnectGDS(false, true); + + // ensure access to other applications is denied + foreach (var testApplication in m_goodApplicationTestSet) + { + if (testApplication.Certificate != null) + { + var sre = Assert.Throws(() => + m_gdsClient.GDSClient.GetCertificateGroups(testApplication.ApplicationRecord.ApplicationId) + ); + Assert.NotNull(sre); + Assert.AreEqual(StatusCodes.BadUserAccessDenied, sre.StatusCode, sre.Result.ToString()); + } + } + + ApplicationTestData application = m_gdsClient.OwnApplicationTestData; + + + // 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 + 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); + 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(); + + // 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, + 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(); + } + + /// + /// self issue a public/private key pair and read it back + /// + [Test, Order(632)] + public void GoodKeyPairRequestAsSelfAdmin() + { + AssertIgnoreTestWithoutGdsRegisteredTestClient(); + AssertIgnoreTestWithoutGoodRegistration(); + AssertIgnoreTestWithoutGoodNewKeyPairRequest(); + + ApplicationTestData application = m_gdsClient.OwnApplicationTestData; + + + ConnectGDS(false, true); + + // 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); + //Start KeyPairRequest + NodeId 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; + + //Finish KeyPairRequest + bool requestBusy; + DateTime 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); + 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); + + 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() { @@ -1034,15 +1363,23 @@ 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}"); } + private void DisconnectGDS( [System.Runtime.CompilerServices.CallerMemberName] string memberName = "" ) @@ -1059,6 +1396,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) @@ -1093,6 +1438,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 } diff --git a/Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx b/Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx new file mode 100644 index 0000000000..b2daea7ff5 Binary files /dev/null and b/Tests/Opc.Ua.Gds.Tests/GDSwellKnownRoles.xlsx differ diff --git a/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs b/Tests/Opc.Ua.Gds.Tests/GlobalDiscoveryTestClient.cs index 218d624ae4..38c641df6f 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; @@ -49,11 +50,14 @@ 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; } + #region public methods 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 +65,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 +78,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 +98,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 +119,55 @@ 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() + { + try + { + 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) + { + return false; + } + //apply cert + ApplyNewApplicationInstanceCertificateAsync(certificate, privateKey).Wait(); + OwnApplicationTestData.Certificate = certificate; + OwnApplicationTestData.PrivateKey = privateKey; + OwnApplicationTestData.CertificateRequestId = null; + } + catch (ArgumentException e) + { + Console.WriteLine("RegisterTestClientAtGds at GDS failed" + e.ToString()); + return false; + } + + + return true; } public void DisconnectClient() @@ -133,7 +186,59 @@ public string ReadLogFile() { return File.ReadAllText(Utils.ReplaceSpecialFolderNames(Configuration.TraceConfiguration.OutputFilePath)); } + #endregion + #region Private Methods + private async Task ApplyNewApplicationInstanceCertificateAsync(byte[] certificate, byte[] privateKey) + { + 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; + m_client.Configuration.SecurityConfiguration.ApplicationCertificate.Certificate = certWithPrivateKey; + var store = m_client.Configuration.SecurityConfiguration.ApplicationCertificate.OpenStore(); + await store.Add(certWithPrivateKey).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) @@ -150,7 +255,29 @@ 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", + }; + return ownApplicationTestData; + } + + #endregion + + private GlobalDiscoveryServerClient m_client; + private ApplicationInstance m_application; } 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;