C++ Based OPC UA Client/Server SDK  1.5.5.355
Lesson 4: Discovery and Secure Connection

Step 1: Collect Information about the Server Using UaDiscovery::findServers, UaDiscovery::getEndpoints

The method discover uses the class UaClientSdk::UaDiscovery to retrieve information about the server. The class UaDiscovery provides two methods:

findServers
Gets a list of servers known by the discovery server.
getEndpoints
Gets a list of endpoints supported by the server.

We will need this list of endpoints later on in this lesson to set up a secure connection with the server.

Add the following code to sampleclient.h:

...
// OPC UA service calls
UaStatus discover(); // Add this line
UaStatus connect();
UaStatus disconnect();
...

Add the following code to sampleclient.cpp:

#include "sampleclient.h"
#include "uasession.h"
#include "samplesubscription.h"
#include "configuration.h"
#include "uadiscovery.h" // Add this line
...
// New code begins
UaStatus SampleClient::discover()
{
UaStatus result;
UaDiscovery discovery;
ServiceSettings serviceSettings;
ClientSecurityInfo clientSecurityInfo;
UaApplicationDescriptions applicationDescriptions;
UaEndpointDescriptions endpointDescriptions;
OpcUa_UInt32 i, j;
OpcUa_Int32 k;
UaString sTemp;
// get a list of available servers
printf("\nCall FindServers on Url %s\n", m_pConfiguration->getDiscoveryUrl().toUtf8());
result = discovery.findServers(
serviceSettings,
m_pConfiguration->getDiscoveryUrl(),
clientSecurityInfo,
applicationDescriptions);
if (result.isGood())
{
// print list of servers
printf("\nFindServers succeeded\n");
for (i = 0; i < applicationDescriptions.length(); i++)
{
printf("** Application [%d] **********************************************************\n", i);
sTemp = &applicationDescriptions[i].ApplicationUri;
printf(" ApplicationUri %s\n", sTemp.toUtf8());
sTemp = &applicationDescriptions[i].ApplicationName.Text;
printf(" ApplicationName %s\n", sTemp.toUtf8());
for (k = 0; k < applicationDescriptions[i].NoOfDiscoveryUrls; k++)
{
UaString sDiscoveryUrl(applicationDescriptions[i].DiscoveryUrls[k]);
printf("** DiscoveryUrl [%s] ***********************\n", sDiscoveryUrl.toUtf8());
// for each server get a list of available endpoints
result = discovery.getEndpoints(
serviceSettings,
sDiscoveryUrl,
clientSecurityInfo,
endpointDescriptions);
if (result.isGood())
{
// print list of Endpoints
for (j = 0; j < endpointDescriptions.length(); j++)
{
printf("** Endpoint[%d] ***********************************************\n", j);
sTemp = &endpointDescriptions[j].EndpointUrl;
printf(" Endpoint URL %s\n", sTemp.toUtf8());
sTemp = &endpointDescriptions[j].SecurityPolicyUri;
printf(" Security Policy %s\n", sTemp.toUtf8());
sTemp = "Invalid";
if ( endpointDescriptions[j].SecurityMode == OpcUa_MessageSecurityMode_None )
{
sTemp = "None";
}
if ( endpointDescriptions[j].SecurityMode == OpcUa_MessageSecurityMode_Sign )
{
sTemp = "Sign";
}
if ( endpointDescriptions[j].SecurityMode == OpcUa_MessageSecurityMode_SignAndEncrypt )
{
sTemp = "SignAndEncrypt";
}
printf(" Security Mode %s\n", sTemp.toUtf8());
printf("**************************************************************\n");
}
}
else
{
printf("GetEndpoints failed with status %s\n", result.toString().toUtf8());
}
printf("************************************************************************\n", i);
}
printf("******************************************************************************\n", i);
}
}
else
{
printf("FindServers failed with status %s\n", result.toString().toUtf8());
}
return result;
}
// New code ends
UaStatus SampleClient::connect()
{
...
}

The method discover calls UaDiscovery::findServers on the discovery URL provided in the configuration file. The Discovery Server returns a list of servers. On each servers in this list, UaDiscovery::getEndpoints is called. The list of servers and provided endpoints is displayed in the console. In our case, findServers is called on the UaServerCpp.

Now we update main for calling discover. The discovered information is only displayed in the console so far. It will be used for a secure connection in Step 3.

Replace the following code in client_cpp_sdk_tutorial.cpp

// Loading configuration succeeded
if (status.isGood())
{
// set configuration
pMyClient->setConfiguration(pMyConfiguration);
// Connect to OPC UA Server
status = pMyClient->connect();
}
else
{
delete pMyConfiguration;
pMyConfiguration = NULL;
}

with

// Loading configuration succeeded - use discovery to get information about the server
if (status.isGood())
{
// set configuration
pMyClient->setConfiguration(pMyConfiguration);
// Discover
status = pMyClient->discover();
}
else
{
delete pMyConfiguration;
pMyConfiguration = NULL;
}
// Discovery succeeded - now we connect
if (status.isGood())
{
// Wait for user command.
printf("\nPress Enter to connect\n");
getchar();
// Connect to OPC UA Server
status = pMyClient->connect();
}

and remove the marked lines (which are no longer needed to show the functionality):

// Connect succeeded
if (status.isGood())
{
...
// Write values
status = pMyClient->write();
// Code to remove begins
// Wait for user command.
printf("\nPress Enter to do a simple browse\n");
getchar();
// Simple Browse
status = pMyClient->browseSimple();
// Wait for user command.
printf("\nPress Enter to browse with continuation point\n");
getchar();
// Browse with continuation point
status = pMyClient->browseContinuationPoint();
// Code to remove ends
// Wait for user command.
printf("\nPress Enter to disconnect\n");
getchar();
// Disconnect from OPC UA Server
status = pMyClient->disconnect();
}

When compiling and running the application, we can see that the UaServerCpp provides three different endpoints with different security policies:

Call FindServers on Url opc.tcp://localhost:48010
FindServers succeeded
Application [0] **********************************************************
ApplicationUri urn:vbox-xp-sp3:UnifiedAutomation:UaServerCpp
ApplicationName UaServerCpp@vbox-xp-sp3
DiscoveryUrl [opc.tcp://vbox-xp-sp3:48010] ***********************
Endpoint[0] ***********************************************
Endpoint URL opc.tcp://vbox-xp-sp3:48010
Security Policy http://opcfoundation.org/UA/SecurityPolicy#None
Security Mode None
Endpoint[1] ***********************************************
Endpoint URL opc.tcp://vbox-xp-sp3:48010
Security Policy http://opcfoundation.org/UA/SecurityPolicy#Basic256
Security Mode Sign
Endpoint[2] ***********************************************
Endpoint URL opc.tcp://vbox-xp-sp3:48010
Security Policy http://opcfoundation.org/UA/SecurityPolicy#Basic256
Security Mode SignAndEncrypt
Press Enter to connect with security

Step 2: Create and Load an Application Instance Certificate

To set up a secure connection, client and server have to exchange and trust each others certificates. In this step we will create a self-signed certificate for our client and set up the PKI directory structure.

Add the following code to configuration.h

...
using namespace UaClientSdk; // Add this line
class Configuration
{
UA_DISABLE_COPY(Configuration);
public:
...
// load configuration from file to fill connection parameters, NamespaceArray and NodeIds
UaStatus loadConfiguration(const UaString& sConfigurationFile);
// New code begins
// create the folder structure to handle certificates and load or create a client certificate.
UaStatus setupSecurity(SessionSecurityInfo& sessionSecurityInfo);
// New code ends
// update the namespace indexes for all nodeIds and update the internal namespaceArray
UaStatus updateNamespaceIndexes(const UaStringArray& namespaceArray);
...
private:
...
// New code begins
// Certificate handling
UaString m_certificateTrustListLocation;
UaString m_certificateRevocationListLocation;
UaString m_issuersCertificatesLocation;
UaString m_issuersRevocationListLocation;
UaString m_clientCertificateFile;
UaString m_clientPrivateKeyFile;
// New code ends
};

Include the following header files in configuration.cpp:

#include "configuration.h"
#include "uasettings.h"
#include "uadir.h" // Add this line
#include "uapkicertificate.h" // Add this line

Add the following code to Configuration::loadConfiguration. The paths for creating the directory structure for storing certificates are loaded from the configuration file.

...
pSettings->beginGroup("UaSampleConfig");
// New code begins
// Certificate and trust list locations
value = pSettings->value("CertificateTrustListLocation");
m_certificateTrustListLocation = value.toString();
value = pSettings->value("CertificateRevocationListLocation");
m_certificateRevocationListLocation = value.toString();
value = pSettings->value("IssuersCertificatesLocation");
m_issuersCertificatesLocation = value.toString();
value = pSettings->value("IssuersRevocationListLocation");
m_issuersRevocationListLocation = value.toString();
value = pSettings->value("ClientCertificate");
m_clientCertificateFile = value.toString();
value = pSettings->value("ClientPrivateKey");
m_clientPrivateKeyFile = value.toString();
// New code ends
// Application Name
value = pSettings->value("ApplicationName", UaString());
m_applicationName = value.toString();
...

Implement the helper method Configuration::setupSecurity which will be used in Step 3: Set up a Secure Connection.

UaStatus Configuration::setupSecurity(SessionSecurityInfo& sessionSecurityInfo)
{
UaStatus result;
// create directories
UaDir dirHelper("");
UaUniString usClientCertificatePath(dirHelper.filePath(UaDir::fromNativeSeparators(m_clientCertificateFile.toUtf16())));
dirHelper.mkpath(usClientCertificatePath);
UaUniString usPrivateKeyPath(dirHelper.filePath(UaDir::fromNativeSeparators(m_clientPrivateKeyFile.toUtf16())));
dirHelper.mkpath(usPrivateKeyPath);
UaUniString usTrustListLocationPath(dirHelper.filePath(UaDir::fromNativeSeparators(m_certificateTrustListLocation.toUtf16())));
dirHelper.mkpath(usTrustListLocationPath);
UaUniString usRevocationListPath(dirHelper.filePath(UaDir::fromNativeSeparators(m_certificateRevocationListLocation.toUtf16())));
dirHelper.mkpath(usRevocationListPath);
// try to load the client certificate
UaPkiCertificate clientCertificate = UaPkiCertificate::fromDERFile(m_clientCertificateFile);
// certificate doesn't exists - we create a new one
if (clientCertificate.isNull())
{
// Create the certificate
UaPkiRsaKeyPair keyPair ( 1024 );
UaPkiPrivateKey IssuerPrivateKey;
UaPkiPublicKey SubjectPublicKey;
UaPkiIdentity identity;
UaString sNodeName;
char szHostName[256];
if ( 0 == UA_GetHostname(szHostName, 256) )
{
sNodeName = szHostName;
}
identity.commonName = UaString("Client_Cpp_SDK@%1").arg(sNodeName);
identity.organization = "Organization";
identity.organizationUnit = "Unit";
identity.locality = "LocationName";
identity.state = "State";
identity.country = "DE";
identity.domainComponent = sNodeName;
info.URI = UaString("urn:%1:%2:%3").arg(sNodeName).arg(COMPANY_NAME).arg(PRODUCT_NAME);
info.DNSNames.create(1);
sNodeName.copyTo(&info.DNSNames[0]);
info.validTime = 3600*24*365*5; // 5 years in seconds
IssuerPrivateKey = keyPair.privateKey();
SubjectPublicKey = keyPair.publicKey();
// create a self signed certificate
UaPkiCertificate cert ( info, identity, SubjectPublicKey, identity, IssuerPrivateKey );
// save public key to file
cert.toDERFile ( m_clientCertificateFile.toUtf8() );
// save private key to file
keyPair.toPEMFile ( m_clientPrivateKeyFile.toUtf8(), 0 );
}
// initialize the PKI provider for using OpenSSL
result = sessionSecurityInfo.initializePkiProviderOpenSSL(
m_certificateRevocationListLocation,
m_certificateTrustListLocation,
m_issuersRevocationListLocation,
m_issuersCertificatesLocation);
if (result.isBad())
{
printf("*******************************************************\n");
printf("** setupSecurity failed!\n");
printf("** Could not initialize PKI\n");
printf("*******************************************************\n");
return result;
}
// load client certificate and private key
result = sessionSecurityInfo.loadClientCertificateOpenSSL(
m_clientCertificateFile,
m_clientPrivateKeyFile);
/*********************************************************************/
if (result.isBad())
{
printf("*******************************************************\n");
printf("** setupSecurity failed!\n");
printf("** Could not load Client certificate\n");
printf("** Connect will work only without security\n");
printf("*******************************************************\n");
return result;
}
return result;
}

First, the folder structure for storing certificates are created using the classes UaDir and UaUniString.

For certificate handling, the SDK contains the class UaPkiCertificate. If no certificate exists, a new one is created. UaPkiIdentity and UaPkiCertificateInfo are used to store the necessary information. An RSA key pair and a self-signed certificate are created and stored in the appropriate directories.

Finally, the PKI provider is initialized and the client certificate as well as the private key are loaded.

Step 3: Set up a Secure Connection

In this step we will add the method connectSecure for establishing a secure connection and some helper methods.

Add the following code to sampleclient.h:

...
public:
...
// OPC UA service calls
UaStatus discover();
UaStatus connect();
UaStatus connectSecure(); // Add this line
UaStatus disconnect();
UaStatus browseSimple();
...
private:
// helper methods
UaStatus browseInternal(const UaNodeId& nodeToBrowse, OpcUa_UInt32
maxReferencesToReturn);
// New code begins
UaStatus connectInternal(const UaString& serverUrl, SessionSecurityInfo& sessionSecurityInfo);
UaStatus findSecureEndpoint(SessionSecurityInfo& sessionSecurityInfo);
UaStatus checkServerCertificateTrust(SessionSecurityInfo& sessionSecurityInfo);
// New code ends
void printBrowseResults(const UaReferenceDescriptions& referenceDescriptions);
// New code begins
void printCertificateData(const UaByteString& serverCertificate);
int userAcceptCertificate();
// New code ends

Add the follwoing include to sampleclient.cpp:

#include "sampleclient.h"
#include "uasession.h"
#include "samplesubscription.h"
#include "configuration.h"
#include "uadiscovery.h"
#include "uapkicertificate.h" // Add this line

Add a new method SampleClient::connectInternal as shown in the sample code below. You will notice that it contains code which was previously used in the method SampleClient::connect with some modifications. We will use it as a helper method in the methods connect and connectSecure to establish the connection to the server.

UaStatus SampleClient::connectInternal(const UaString& serverUrl, SessionSecurityInfo& sessionSecurityInfo)
{
// Content from old method connect inserted
UaStatus result;
// Provide information about the client
SessionConnectInfo sessionConnectInfo;
UaString sNodeName("unknown_host");
char szHostName[256];
if (0 == UA_GetHostname(szHostName, 256))
{
sNodeName = szHostName;
}
// Fill session connect info with data from configuration
sessionConnectInfo.sApplicationName = m_pConfiguration->getApplicationName();
// Use the host name to generate a unique application URI
sessionConnectInfo.sApplicationUri = UaString("urn:%1:%2:%3").arg(sNodeName).arg(COMPANY_NAME).arg(PRODUCT_NAME);
sessionConnectInfo.sProductUri = UaString("urn:%1:%2").arg(COMPANY_NAME).arg(PRODUCT_NAME);
sessionConnectInfo.sSessionName = sessionConnectInfo.sApplicationUri;
sessionConnectInfo.bAutomaticReconnect = m_pConfiguration->getAutomaticReconnect();
sessionConnectInfo.bRetryInitialConnect = m_pConfiguration->getRetryInitialConnect();
// Connect to server
printf("\nConnecting to %s\n", serverUrl.toUtf8());
result = m_pSession->connect(
serverUrl,
sessionConnectInfo,
sessionSecurityInfo,
this);
if (result.isGood())
{
printf("Connect succeeded\n");
}
else
{
printf("Connect failed with status %s\n", result.toString().toUtf8());
}
return result;
}

Replace the method connect with the following code. Note that the new helper method connectInternal is used.

UaStatus SampleClient::connect()
{
// Security settings are not initialized - we connect without security for now
SessionSecurityInfo sessionSecurityInfo;
return connectInternal(m_pConfiguration->getServerUrl(), sessionSecurityInfo);
}

Add a new method connectSecure():

UaStatus SampleClient::connectSecure()
{
UaStatus result;
SessionSecurityInfo sessionSecurityInfo;
//************************************************
// Step (1) - Load or create a client certificate
//************************************************
// Secure connections require a certificate for both the client and the server
// setupSecurity loads the client certificate and initializes the certificate store
result = m_pConfiguration->setupSecurity(sessionSecurityInfo);
//************************************************
// Step (2) - Find a secure Endpoint on the server
//************************************************
// This is normally done the first time the server is used
// The settings ServerCertificate, SecurityPolicyUri and SecurityMode should be stored
// with other connection information like the URL to be reused in later connects
if (result.isGood())
{
result = findSecureEndpoint(sessionSecurityInfo);
}
//************************************************
// Step (3) - Validate the server certificate
//************************************************
// This step is normally neccessary at first connect (like Step 2)
// If the server certificate is in the trust list the check succeeds at connect
if (result.isGood())
{
result = checkServerCertificateTrust(sessionSecurityInfo);
}
if (result.isGood())
{
result = connectInternal(m_pConfiguration->getServerUrl(), sessionSecurityInfo);
if (result.isBad())
{
printf("********************************************************************************************\n");
printf("Connect with security failed. Make sure the client certificate is in the servers trust list.\n");
printf("********************************************************************************************\n");
}
}
return result;
}

Apart from the actual connect by calling the helper method connectInternal, the connection establishment can be divided into three steps:

Step 1: Load or create a client certificate
Here, the helper method setupSecurity from Step 2 is used.
Step 2: Find a secure Endpoint on the server
A server usually provides different endpoints (see Step 1. The helper method findSecureEndpoint picks the most secure one.
Step 3: Validate the server certificate
To establish a secure connection client and server have to exchange certificates and trust each other’s. The helper method checkServerCertificateTrust is used to check and trust or reject the server’s certificate.

Finally, connectInternal is used to connect to the endpoint returned by findSecureEndpoint.

Add the helper method SampleClient::findSecureEndpoint to sampleclient.cpp:

UaStatus SampleClient::findSecureEndpoint(SessionSecurityInfo& sessionSecurityInfo)
{
UaStatus result;
ServiceSettings serviceSettings;
ClientSecurityInfo clientSecurityInfo;
UaEndpointDescriptions endpointDescriptions;
UaDiscovery discovery;
OpcUa_UInt32 bestSecurityIndex = 0;
UaString sTemp;
printf("\nTry to find secure Endpoint on: %s\n", m_pConfiguration->getServerUrl().toUtf8());
result = discovery.getEndpoints(
serviceSettings,
m_pConfiguration->getServerUrl(),
clientSecurityInfo,
endpointDescriptions);
if (result.isGood())
{
OpcUa_Byte securityLevel = 0;
OpcUa_UInt32 i;
// select the endpoint with the best security
for (i = 0; i < endpointDescriptions.length(); i++)
{
if (endpointDescriptions[i].SecurityLevel > securityLevel)
{
bestSecurityIndex = i;
securityLevel = endpointDescriptions[i].SecurityLevel;
}
}
// check if the endpoint we found is a secure endpoint
if (endpointDescriptions[bestSecurityIndex].SecurityMode < OpcUa_MessageSecurityMode_Sign)
{
printf("No secure endpoint available on server\n");
result = OpcUa_BadSecurityConfig;
}
// print the secure endpoint we found
else
{
printf("Endpoint with best security found:\n");
sTemp = &endpointDescriptions[bestSecurityIndex].EndpointUrl;
printf(" Endpoint URL %s\n", sTemp.toUtf8());
sTemp = &endpointDescriptions[bestSecurityIndex].SecurityPolicyUri;
printf(" Security Policy %s\n", sTemp.toUtf8());
sTemp = "Invalid";
if ( endpointDescriptions[bestSecurityIndex].SecurityMode == OpcUa_MessageSecurityMode_None )
{
sTemp = "None";
}
if ( endpointDescriptions[bestSecurityIndex].SecurityMode == OpcUa_MessageSecurityMode_Sign )
{
sTemp = "Sign";
}
if ( endpointDescriptions[bestSecurityIndex].SecurityMode == OpcUa_MessageSecurityMode_SignAndEncrypt )
{
sTemp = "SignAndEncrypt";
}
printf(" Security Mode %s\n", sTemp.toUtf8());
// The following setting should be stored together with the server configuration
// set server certificate
sessionSecurityInfo.serverCertificate = endpointDescriptions[bestSecurityIndex].ServerCertificate;
// set security mode and security policy
sessionSecurityInfo.sSecurityPolicy = endpointDescriptions[bestSecurityIndex].SecurityPolicyUri;
sessionSecurityInfo.messageSecurityMode = endpointDescriptions[bestSecurityIndex].SecurityMode;
}
}
else
{
printf("GetEndpoints failed with status %s\n", result.toString().toUtf8());
}
return result;
}

The helper method findSecureEndpoints calls Discovery::getEndpoints (see Step 1). Then the most secure endpoint is picked.

Add helper method checkServerCertificateTrust:

UaStatus SampleClient::checkServerCertificateTrust(SessionSecurityInfo& sessionSecurityInfo)
{
UaStatus result;
// Handling validation errors requires the following steps:
// 1. show certificate and ask the user to accept the certificate
// 2. user can either reject, accept temporary or accept permanently
// 3. when accepting permanently copy the server certificate to the clients trust list
if (sessionSecurityInfo.verifyServerCertificate().isBad())
{
printf("\n");
printf("\n");
printf("-------------------------------------------------------\n");
printf("- The following certificate is not trusted yet -\n");
printf("-------------------------------------------------------\n");
printCertificateData(sessionSecurityInfo.serverCertificate);
printf("\n");
printf("'y' + Enter if you want to trust the certificate temporarily.\n");
printf("'p' + Enter if you want to trust the certificate permanently an copy the server certificate into the client trust list.\n");
printf("Enter if you don't want to trust the certificate.\n");
int accept = userAcceptCertificate();
if (accept == 1)
{
printf("Certificate was acceppted temporarily.\n");
// we already validated the certificate above
// skip validation during connect since we only accept temporarily
sessionSecurityInfo.doServerCertificateVerify = OpcUa_False;
}
else if (accept == 2)
{
// copy the server certificate into the client trust list
UaPkiCertificate cert = UaPkiCertificate::fromDER(sessionSecurityInfo.serverCertificate);
UaString sThumbprint = cert.thumbPrint().toHex();
result = sessionSecurityInfo.saveServerCertificate(sThumbprint);
if (result.isGood())
{
printf("Certificate was accepted permanently.\n");
}
else
{
printf("Failed to accept certifcate permanently :%s\n", result.toString().toUtf8());
}
// We stored the certificate in the trust list - certificate can be validated during connect
sessionSecurityInfo.doServerCertificateVerify = OpcUa_True;
}
else
{
printf("Certificate was rejected by user.\n");
result = OpcUa_BadCertificateUntrusted;
}
}
return result;
}

verifyServerCertificate is used to check if the server’s certificate is already in the client’s trust list.

If not, it is displayed using the helper function printCertificateData. Then the user has three options:

  1. trust the certificate temporarily
  2. trust the certificate permanently
  3. not trust the certificate

The helper method userAcceptCertificate is used to read the user’s choice.

If the user chooses to accept the certificate temporarily, SessionSecurityInfo::doServerCertificateVerify is set to OpcUa_False. This means that the SDK does not check if the certificate is in the clients trust list when connecting, because it has already been checked by the user (and won’t be copied to the trust list).

If the option to accept the certificate permanently has been picked, it is copied to the client’s trust list. SessionSecurityInfo::doServerCertificateVerify is set to OpcUa_True (because the certificate can be validated during connect now that it’s in the trust list).

Add helper method printCertificateData to sampleclient.cpp which is used by checkServerCertificateTrust to display the server’s certificate.

void SampleClient::printCertificateData(const UaByteString& serverCertificate)
{
// Assign certificate byte string to UaPkiCertificate class
UaPkiCertificate cert = UaPkiCertificate::fromDER(serverCertificate);
printf("- CommonName %s\n", cert.commonName().toUtf8() );
printf("- Issuer.commonName %s\n", cert.issuer().commonName.toUtf8() );
printf("- Issuer.organization %s\n", cert.issuer().organization.toUtf8() );
printf("- Issuer.organizationUnit %s\n", cert.issuer().organizationUnit.toUtf8() );
printf("- Issuer.state %s\n", cert.issuer().state.toUtf8() );
printf("- Issuer.country %s\n", cert.issuer().country.toUtf8() );
printf("- ValidFrom %s\n", cert.validFrom().toString().toUtf8() );
printf("- ValidTo %s\n", cert.validTo().toString().toUtf8() );
}

Add helper method userAcceptCertificate to read the user input when asked what to do with the server’s certificate.

int SampleClient::userAcceptCertificate()
{
int result = 0;
int ch = getchar();
if (ch == 'y' || ch == 'Y')
{
result = 1;
}
else if (ch == 'p' || ch == 'P')
{
result = 2;
}
else
{
result = 0;
}
// empty read buffer from console
while (ch != '\n')
{
ch = getchar();
}
return result;
}

Modify main to establish a secure connection:

Replace

// Discovery succeeded - now we connect
if (status.isGood())
{
// Wait for user command.
printf("\nPress Enter to connect\n");
getchar();
// Connect to OPC UA Server
status = pMyClient->connect();
}

with

// Discovery succeeded - now we connect
if (status.isGood())
{
// Wait for user command.
printf("\nPress Enter to connect with security\n");
getchar();
// Connect to OPC UA Server using a secure connection
status = pMyClient->connectSecure();
}

Finally, modify main to connect with security:

...
// Discovery succeeded - now we connect
if (status.isGood())
{
// Wait for user command.
printf("\nPress Enter to connect with security\n"); // Change this line
getchar();
// Connect to OPC UA Server using a secure connection // Change this line
status = pMyClient->connectSecure(); // Change this line
}
...