OPC UA Foundation SDK: Server does not have an instance certificate assigned

3.6k views Asked by At

I am using the Foundation SDK with C#, trying to launch a simple server in a minimal fashion.

Here is my attempt sofar.

public void StartServer()
{
    var config = new ApplicationConfiguration
    {
        ApplicationName = "TestServer",
        ApplicationType = ApplicationType.Client,
        SecurityConfiguration = new SecurityConfiguration
        {
            ApplicationCertificate = new CertificateIdentifier
            {
                StoreType = @"Windows", 
                StorePath = @"CurrentUser\My",
                SubjectName = Utils.Format(@"CN={0}, DC={1}", "TestServer", Dns.GetHostName())
            }, 
            TrustedPeerCertificates = new CertificateTrustList
            {
                StoreType = @"Windows", 
                StorePath = @"CurrentUser\TrustedPeople",
            }, 
            NonceLength = 32, 
            AutoAcceptUntrustedCertificates = true,
            ConfigureFirewall = false
        },
        TransportConfigurations = new TransportConfigurationCollection(),
        TransportQuotas = new TransportQuotas { OperationTimeout = 15000 },
        ServerConfiguration = new ServerConfiguration
        {
            SecurityPolicies = new ServerSecurityPolicyCollection
            {
                new ServerSecurityPolicy
                {
                    SecurityLevel = 0,
                    SecurityMode = MessageSecurityMode.None,
                    SecurityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None"
                }
            },
            UserTokenPolicies = new UserTokenPolicyCollection
            {
                new UserTokenPolicy { TokenType = UserTokenType.Anonymous }
            },
            DiagnosticsEnabled = true,
            MaxSessionCount = 100,
            MinSessionTimeout = 5000,
            MaxSessionTimeout = 10000,
            MaxBrowseContinuationPoints = 10,
            MaxQueryContinuationPoints = 10,
            MaxHistoryContinuationPoints = 100,
            MaxRequestAge = 600000,
            MinPublishingInterval = 100,
            MaxPublishingInterval = 3600000,
            PublishingResolution = 50,
            MaxSubscriptionLifetime = 3600000,
            MaxMessageQueueSize = 10,
            MaxNotificationQueueSize = 100,
            MaxNotificationsPerPublish = 1000,
            MinMetadataSamplingInterval = 1000
        }
    };
    config.Validate(ApplicationType.Server);

    var server = new MyCustomServer();

    server.Start(config);
}

When I try to call the method, I get the following exception:

Opc.Ua.ServiceResultException: Server does not have an instance certificate assigned.
   à Opc.Ua.ServerBase.OnServerStarting(ApplicationConfiguration configuration) dans ...\OPC Foundation\Stack\Core\Stack\Server\ServerBase.cs:ligne 1607
   à Opc.Ua.Server.StandardServer.OnServerStarting(ApplicationConfiguration configuration) dans ...\OPC Foundation\SampleApplications\SDK\Server\Server\StandardServer.cs:ligne 2628
   à Opc.Ua.ServerBase.Start(ApplicationConfiguration configuration) dans ...\OPC Foundation\Stack\Core\Stack\Server\ServerBase.cs:ligne 232
   à SlimFixtures.ServerDriver.StartServer() dans ...\ServerDriver.cs:ligne 71

What am I doing wrong?

2

There are 2 answers

0
Andrew Cullen On

So you found that servers based on foundation code always need a certificate. Creating a self-signed certificate is easy, and does not need Admin login if you are using the Current User/My Windows store.

You can call this extension method after you validate:

config.Validate(ApplicationType.Server);
config.EnsureApplicationCertificate();

elsewhere

public static class ServiceExtensions
{
    /// <summary>
    /// Ensures the application certificate is present and valid.
    /// </summary>
    public static void EnsureApplicationCertificate(this ApplicationConfiguration configuration)
    {
        const ushort keySize = 1024;
        const ushort lifetimeInMonths = 300;

        if (configuration == null)
        {
            throw new ArgumentNullException("configuration");
        }
        bool errorFlag = false;
        string hostName = Dns.GetHostName();
        var serverDomainNames = configuration.GetServerDomainNames();
        var applicationCertificate = configuration.SecurityConfiguration.ApplicationCertificate;
        var certificate = applicationCertificate.Find(true);
        if (certificate != null)
        {
            // if cert found then check domains
            var domainsFromCertficate = Utils.GetDomainsFromCertficate(certificate);
            foreach (string serverDomainName in serverDomainNames)
            {
                if (Utils.FindStringIgnoreCase(domainsFromCertficate, serverDomainName))
                {
                    continue;
                }
                if (String.Equals(serverDomainName, "localhost", StringComparison.OrdinalIgnoreCase))
                {
                    if (Utils.FindStringIgnoreCase(domainsFromCertficate, hostName))
                    {
                        continue;
                    }
                    var hostEntry = Dns.GetHostEntry(hostName);
                    if (hostEntry.Aliases.Any(alias => Utils.FindStringIgnoreCase(domainsFromCertficate, alias)))
                    {
                        continue;
                    }
                    if (hostEntry.AddressList.Any(ipAddress => Utils.FindStringIgnoreCase(domainsFromCertficate, ipAddress.ToString())))
                    {
                        continue;
                    }
                }
                Trace.TraceInformation("The application is configured to use domain '{0}' which does not appear in the certificate.", serverDomainName);
                errorFlag = true;
            } // end for
            // if no errors and keySizes match
            if (!errorFlag && (keySize == certificate.PublicKey.Key.KeySize))
            {
                return; // cert okay
            }
        }
        // if we get here then we'll create a new cert
        if (certificate == null)
        {
            certificate = applicationCertificate.Find(false);
            if (certificate != null)
            {
                Trace.TraceInformation("Matching certificate with SubjectName '{0}' found but without a private key.", applicationCertificate.SubjectName);
            }
        }
        // lets check if there is any to delete
        if (certificate != null)
        {
            using (var store2 = applicationCertificate.OpenStore())
            {
                store2.Delete(certificate.Thumbprint);
            }
        }
        if (serverDomainNames.Count == 0)
        {
            serverDomainNames.Add(hostName);
        }
        CertificateFactory.CreateCertificate(applicationCertificate.StoreType, applicationCertificate.StorePath, configuration.ApplicationUri, configuration.ApplicationName, null, serverDomainNames, keySize, lifetimeInMonths);
        Trace.TraceInformation("Created new certificate with SubjectName '{0}', in certificate store '{1}'.", applicationCertificate.SubjectName, applicationCertificate.StorePath);
        configuration.CertificateValidator.Update(configuration.SecurityConfiguration);
    }

}
0
Gimly On

With a more recent version of the library, there is a built-in option to check the application instance certificate. It's available on the ApplicationInstance class.

Here's how you'd use it:

var applicationConfiguration = new ApplicationConfiguration
{
    ApplicationName = "Aggregation server",
    ...
};

await applicationConfiguration.Validate(ApplicationType.ClientAndServer);

var applicationInstance = new ApplicationInstance(applicationConfiguration);

// This call will check that the application instance certificate exists, and will create it if not
var result =
    await applicationInstance.CheckApplicationInstanceCertificate(false, CertificateFactory.DefaultKeySize);

var server = new AggregationServer();
await applicationInstance.Start(server);
System.Console.ReadKey();
server.Stop();