enable server push in gds client, add a few test cases

This commit is contained in:
Martin Regen
2017-11-09 12:17:28 +01:00
parent d345c49590
commit bfeee65f8a
8 changed files with 284 additions and 40 deletions
@@ -314,7 +314,7 @@ namespace Opc.Ua.Server
// TODO: role based access
UserIdentity user = context.UserIdentity as UserIdentity;
if (user.TokenType == UserTokenType.Anonymous)
if (user?.TokenType == UserTokenType.Anonymous)
{
throw new ServiceResultException(StatusCodes.BadUserAccessDenied, "Secure Application Administrator access required.");
}
@@ -352,6 +352,7 @@ namespace Opc.Ua.Server
}
#endregion
#region Private Fields
private class ServerCertificateGroup
{
public string BrowseName;
@@ -363,7 +364,6 @@ namespace Opc.Ua.Server
public string TrustedStorePath;
};
#region Private Fields
private ServerConfigurationState m_serverConfigurationNode;
private IList<ServerCertificateGroup> m_certificateGroups;
public readonly string m_rejectedStorePath;
@@ -174,10 +174,12 @@ namespace Opc.Ua.Gds.Client
NodeId requestId = null;
if (!string.IsNullOrEmpty(m_application.CertificateStorePath))
{
CertificateIdentifier id = new CertificateIdentifier();
id.StoreType = CertificateStoreIdentifier.DetermineStoreType(m_application.CertificateStorePath);
id.StorePath = m_application.CertificateStorePath;
id.SubjectName = m_application.CertificateSubjectName.Replace("localhost", Utils.GetHostName());
CertificateIdentifier id = new CertificateIdentifier
{
StoreType = CertificateStoreIdentifier.DetermineStoreType(m_application.CertificateStorePath),
StorePath = m_application.CertificateStorePath,
SubjectName = m_application.CertificateSubjectName.Replace("localhost", Utils.GetHostName())
};
m_certificate = await id.Find(true);
if (m_certificate != null &&
m_certificate.HasPrivateKey)
@@ -216,7 +218,7 @@ namespace Opc.Ua.Gds.Client
else
{
string absoluteCertificatePrivateKeyPath = Utils.GetAbsoluteFilePath(m_application.CertificatePrivateKeyPath, true, false, false);
byte [] pkcsData = File.ReadAllBytes(absoluteCertificatePrivateKeyPath);
byte[] pkcsData = File.ReadAllBytes(absoluteCertificatePrivateKeyPath);
if (m_application.GetPrivateKeyFormat(m_server?.GetSupportedKeyFormats()) == "PFX")
{
csrCertificate = CertificateFactory.CreateCertificateFromPKCS12(pkcsData, m_certificatePassword);
@@ -261,7 +263,7 @@ namespace Opc.Ua.Gds.Client
// request not done yet, try again in a few seconds
return;
}
CertificateRequestTimer.Enabled = false;
RequestProgressLabel.Visible = false;
@@ -400,14 +402,13 @@ namespace Opc.Ua.Gds.Client
}
else
{
#if TODO_SERVERPUSH
if (privateKeyPFX != null && privateKeyPFX.Length > 0)
{
var x509 = new X509Certificate2(privateKeyPFX, m_certificatePassword, X509KeyStorageFlags.Exportable);
privateKeyPFX = x509.Export(X509ContentType.Pfx);
}
bool applyChanges = m_server.UpdateCertificate(null, null, certificate, GetPrivateKeyFormat(), privateKeyPFX, issuerCertificates);
bool applyChanges = m_server.UpdateCertificate(null, null, certificate, (privateKeyPFX != null) ? "PFX" : null, privateKeyPFX, issuerCertificates);
if (applyChanges)
{
MessageBox.Show(
@@ -419,9 +420,6 @@ namespace Opc.Ua.Gds.Client
ApplyChangesButton.Enabled = true;
}
#else
throw new ServiceResultException("Server Push is not yet implemented.");
#endif
}
CertificateControl.ShowValue(null, "Application Certificate", new CertificateWrapper() { Certificate = m_certificate }, true);
@@ -439,7 +437,7 @@ namespace Opc.Ua.Gds.Client
CertificateRequestTimer.Enabled = false;
}
}
private void ApplyChangesButton_Click(object sender, EventArgs e)
{
try
@@ -53,9 +53,8 @@ namespace Opc.Ua.Gds.Client
RegistrationTypeComboBox.Items.Add("Client - Pull Management");
RegistrationTypeComboBox.Items.Add("Server - Pull Management");
#if TODO_SERVERPUSH
RegistrationTypeComboBox.Items.Add("Server - Push Management");
#endif
RegistrationTypeComboBox.SelectedIndex = ServerPullManagement;
m_promptOnRegistrationTypeChange = false;
@@ -408,7 +408,6 @@ namespace Opc.Ua.Gds.Client
private void UpdateStatus(bool error, DateTime time, string status, params object[] args)
{
#if TODO_SERVERPUSH
if (error)
{
ServerStatusIcon.Image = global::Opc.Ua.Gds.Client.Properties.Resources.error;
@@ -422,11 +421,6 @@ namespace Opc.Ua.Gds.Client
ServerStatusLabel.ForeColor = (error) ? Color.Red : Color.Empty;
ServerStatusTime.Text = (time != DateTime.MinValue) ? time.ToLocalTime().ToString("hh:mm:ss") : "---";
ServerStatusTime.ForeColor = (error) ? Color.Red : Color.Empty;
#else
ServerStatusTime.Visible = false;
ServerStatusLabel.Visible = false;
ServerStatusIcon.Visible = false;
#endif
}
private void DisconnectButton_Click(object sender, EventArgs e)
@@ -624,7 +624,7 @@ namespace Opc.Ua.Gds.Client
}
/// <summary>
/// Gets the trust lists method.
/// Gets the certificate status.
/// </summary>
/// <param name="applicationId">The application id.</param>
/// <param name="certificateGroupId">Type of the trust list.</param>
@@ -32,6 +32,7 @@ using System;
using System.IO;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System.Security.Cryptography.X509Certificates;
namespace Opc.Ua.Gds.Client
{
@@ -75,6 +76,18 @@ namespace Opc.Ua.Gds.Client
set { m_adminCredentials = value; }
}
/// <summary>
/// Gets or sets the endpoint URL.
/// </summary>
/// <value>
/// The endpoint URL.
/// </value>
public string EndpointUrl
{
get { return m_endpointUrl; }
set { m_endpointUrl = value; }
}
/// <summary>
/// Raised when admin credentials are required.
/// </summary>
@@ -170,7 +183,7 @@ namespace Opc.Ua.Gds.Client
/// </summary>
public void Connect()
{
Connect((ConfiguredEndpoint)null);
Connect(EndpointUrl);
}
/// <summary>
@@ -311,8 +324,6 @@ namespace Opc.Ua.Gds.Client
/// <summary>
/// Gets the supported key formats.
/// </summary>
/// <param name="publicKeyFormats">The public key formats.</param>
/// <param name="privateKeyFormats">The private key formats.</param>
/// <exception cref="System.InvalidOperationException">Connection to server is not active.</exception>
public string[] GetSupportedKeyFormats()
{
@@ -429,6 +440,7 @@ namespace Opc.Ua.Gds.Client
}
}
/// <summary>
/// Updates the trust list.
/// </summary>
@@ -585,6 +597,40 @@ namespace Opc.Ua.Gds.Client
}
}
/// <summary>
/// Reads the rejected list.
/// </summary>
public X509Certificate2Collection GetRejectedList()
{
if (!IsConnected)
{
Connect();
}
IUserIdentity oldUser = ElevatePermissions();
try
{
var outputArguments = m_session.Call(
ExpandedNodeId.ToNodeId(Opc.Ua.ObjectIds.ServerConfiguration, m_session.NamespaceUris),
ExpandedNodeId.ToNodeId(Opc.Ua.MethodIds.ServerConfiguration_GetRejectedList, m_session.NamespaceUris)
);
byte[][] rawCertificates = (byte[][])outputArguments[0];
X509Certificate2Collection collection = new X509Certificate2Collection();
foreach (var rawCertificate in rawCertificates)
{
collection.Add(new X509Certificate(rawCertificate));
}
return collection;
}
finally
{
RevertPermissions(oldUser);
}
}
/// <summary>
/// Restarts this instance.
/// </summary>
@@ -713,6 +759,7 @@ namespace Opc.Ua.Gds.Client
private ApplicationInstance m_application;
private ConfiguredEndpoint m_endpoint;
private string[] m_preferredLocales;
private string m_endpointUrl;
private Session m_session;
private UserIdentity m_adminCredentials;
#endregion
@@ -38,17 +38,20 @@ namespace Opc.Ua.Gds.Test
public class GlobalDiscoveryTestClient
{
GlobalDiscoveryServerClient m_gds;
GlobalDiscoveryServerClient m_gdsClient;
ServerPushConfigurationClient m_pushClient;
static bool autoAccept = false;
public GlobalDiscoveryServerClient GDSClient { get { return m_gds; } }
public GlobalDiscoveryServerClient GDSClient { get { return m_gdsClient; } }
public ServerPushConfigurationClient PushClient { get { return m_pushClient; } }
public GlobalDiscoveryTestClient(bool _autoAccept)
{
autoAccept = _autoAccept;
}
public async Task ConnectClient()
public async Task ConnectClient(bool gdsClient = true)
{
ApplicationInstance.MessageDlg = new ApplicationMessageDlg();
ApplicationInstance application = new ApplicationInstance
@@ -70,25 +73,42 @@ namespace Opc.Ua.Gds.Test
config.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation);
// get the configuration.
// use same server for gds and push tests
GlobalDiscoveryClientConfiguration gdsClientConfiguration = application.ApplicationConfiguration.ParseExtension<GlobalDiscoveryClientConfiguration>();
m_gds = new GlobalDiscoveryServerClient(application, gdsClientConfiguration);
if (gdsClient)
{
// get the configuration.
m_gdsClient = new GlobalDiscoveryServerClient(application, gdsClientConfiguration);
m_gdsClient.EndpointUrl = gdsClientConfiguration.GlobalDiscoveryServerUrl;
}
else
{
m_pushClient = new ServerPushConfigurationClient(application);
m_pushClient.EndpointUrl = gdsClientConfiguration.GlobalDiscoveryServerUrl;
}
}
public void DisconnectClient()
{
if (m_gds != null)
{
Console.WriteLine("Disconnect Session. Waiting for exit...");
if (m_gdsClient != null)
{
Console.WriteLine("Disconnect Session. Waiting for exit...");
GlobalDiscoveryServerClient gds = m_gds;
m_gds = null;
gds.Disconnect();
GlobalDiscoveryServerClient gdsClient = m_gdsClient;
m_gdsClient = null;
gdsClient.Disconnect();
}
if (m_pushClient != null)
{
ServerPushConfigurationClient pushClient = m_pushClient;
m_pushClient = null;
pushClient.Disconnect();
}
}
private static void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e)
{
if (e.Error.StatusCode == StatusCodes.BadCertificateUntrusted)
@@ -0,0 +1,186 @@
/* ========================================================================
* Copyright (c) 2005-2017 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.Text.RegularExpressions;
using System.Threading;
using NUnit.Framework;
using Opc.Ua;
using Opc.Ua.Gds;
using Opc.Ua.Gds.Client;
using Opc.Ua.Gds.Test;
using Opc.Ua.Test;
using System.Security.Cryptography.X509Certificates;
namespace NUnit.Opc.Ua.Gds.Test
{
[TestFixture]
public class PushTest
{
#region Test Setup
/// <summary>
/// Set up a Global Discovery Server and Client instance and connect the session
/// </summary>
[OneTimeSetUp]
protected void OneTimeSetUp()
{
_serverCapabilities = new ServerCapabilities();
_randomSource = new RandomSource(randomStart);
_dataGenerator = new DataGenerator(_randomSource);
_server = new GlobalDiscoveryTestServer(true);
_server.StartServer().Wait();
_client = new GlobalDiscoveryTestClient(true);
_client.ConnectClient(false).Wait();
_client.PushClient.AdminCredentials = new UserIdentity("appadmin", "demo");
_client.PushClient.Connect();
}
/// <summary>
/// Tear down the Global Discovery Server and disconnect the Client
/// </summary>
[OneTimeTearDown]
protected void OneTimeTearDown()
{
_client.DisconnectClient();
_server.StopServer();
}
#endregion
#region Test Methods
[Test, Order(100)]
public void GetSupportedKeyFormats()
{
_client.PushClient.GetSupportedKeyFormats();
}
[Test, Order(200)]
public void ReadTrustList()
{
TrustListDataType trustList = _client.PushClient.ReadTrustList();
Assert.IsNotNull(trustList);
Assert.IsNotNull(trustList.IssuerCertificates);
Assert.IsNotNull(trustList.IssuerCrls);
Assert.IsNotNull(trustList.TrustedCertificates);
Assert.IsNotNull(trustList.TrustedCrls);
}
[Test, Order(300)]
public void UpdateTrustList()
{
TrustListDataType trustList = _client.PushClient.ReadTrustList();
Assert.IsTrue(_client.PushClient.UpdateTrustList(trustList));
}
[Test, Order(400)]
public void CreateCertificateRequest()
{
byte [] csr = _client.PushClient.CreateCertificateRequest(null,null,null,false,null);
Assert.IsNotNull(csr);
}
[Test, Order(500)]
public void UpdateCertificate()
{
X509Certificate2 cert = CertificateFactory.CreateCertificate(null, null, null, "uri:x:y:z", "TestApp", "CN=Push Server Test", null, 2048, DateTime.UtcNow, 1, 256);
var success = _client.PushClient.UpdateCertificate(null, null, cert.RawData, null, null, null);
}
[Test, Order(500)]
public void GetRejectedList()
{
var collection = _client.PushClient.GetRejectedList();
}
[Test, Order(600)]
public void ApplyChanges()
{
_client.PushClient.ApplyChanges();
}
#endregion
#region Private Methods
private string RandomLocalHost()
{
string localhost = Regex.Replace(_dataGenerator.GetRandomSymbol("en").Trim().ToLower(), @"[^\w\d]", "");
if (localhost.Length >= 12)
{
localhost = localhost.Substring(0, 12);
}
return localhost;
}
private string[] RandomDomainNames()
{
int count = _randomSource.NextInt32(8) + 1;
var result = new string[count];
for (int i = 0; i < count; i++)
{
result[i] = RandomLocalHost();
}
return result;
}
private StringCollection RandomDiscoveryUrl(StringCollection domainNames, int port, string appUri)
{
var result = new StringCollection();
foreach (var name in domainNames)
{
int random = _randomSource.NextInt32(7);
if ((result.Count == 0) || (random & 1) == 0)
{
result.Add(String.Format("opc.tcp://{0}:{1}/{2}", name, (port++).ToString(), appUri));
}
if ((random & 2) == 0)
{
result.Add(String.Format("http://{0}:{1}/{2}", name, (port++).ToString(), appUri));
}
if ((random & 4) == 0)
{
result.Add(String.Format("https://{0}:{1}/{2}", name, (port++).ToString(), appUri));
}
}
return result;
}
#endregion
#region Private Fields
private const int randomStart = 1;
private RandomSource _randomSource;
private DataGenerator _dataGenerator;
private GlobalDiscoveryTestServer _server;
private GlobalDiscoveryTestClient _client;
private ServerCapabilities _serverCapabilities;
#endregion
}
}