UA Ansi C Server Professional  1.3.3.242
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Groups Pages
Security Lesson 1: Securing Connections to Clients with Encryption

This lesson will show how to create application instance certificates and how to sign and encrypt the server's communication to clients. The sources used are based on Lesson 1: Setting up a Basic OPC UA Server Console Application and are extended by the necessary functionality.

Content:

Step 1: Creating an Application Instance Certificate

For being able to sign and/or encrypt a connection with a client, the server needs a X509 application instance certificate and a PKI folder structure for finding certificates, certificate revocation lists and CA certificates.

In our example, the folders and certificates to use are set using defines:

/* PKI configuration */
#define UASERVER_PKI_CONFIGNAME OpcUa_OpenSSL_PKI
#define UASERVER_PRODUCTFOLDER "UaSdkC"
#define UASERVER_PKI_PRIVATEKEY "PKI/CA/private/uaserverc.pem"
#define UASERVER_PKI_SERVERCERT "PKI/CA/certs/uaserverc.der"
#define UASERVER_PKI_CRL "PKI/CA/crl/"
#define UASERVER_PKI_CERTDIR "PKI/CA/certs/"
#define UASERVER_PKI_REJECTED "PKI/CA/rejected/"

Creating the application's certificate is handled in the function CreateCertificates. It uses helper functions from uaserver_p_filesystem.h and uaserver_p_pki.h. The first step is to check if a certificate exists or if it needs to be created. This is done by opening the file - if it succeeds, we don't need to create a new one.

OpcUa_StatusCode CreateCertificates(OpcUa_CharA *szApplicationUri, OpcUa_CharA *szHostname)
{
OpcUa_StatusCode uStatus = OpcUa_Good;
UaServer_File *pFile = OpcUa_Null;
OpcUa_Boolean bCertAvailable = OpcUa_True;
/* Check if certificate and private key exist */
pFile = UaServer_Fopen(UASERVER_PKI_SERVERCERT, "r");
if (pFile != OpcUa_Null)
{
UaServer_Fclose(pFile);
}
else
{
bCertAvailable = OpcUa_False;
}
pFile = UaServer_Fopen(UASERVER_PKI_PRIVATEKEY, "r");
if (pFile != OpcUa_Null)
{
UaServer_Fclose(pFile);
}
else
{
bCertAvailable = OpcUa_False;
}

In case the file does not exist, we create the PKI folder structure given by the defines from above.

/* Create PKI folder structure */
if (bCertAvailable == OpcUa_False)
{
UaServer_Mkdir("PKI");
UaServer_Mkdir("PKI/CA");
UaServer_Mkdir("PKI/CA/certs");
UaServer_Mkdir("PKI/CA/crl");
UaServer_Mkdir("PKI/CA/private");
UaServer_Mkdir("PKI/CA/rejected");
}

After that a key pair is created using a key length of 2048 bit. Also, the subject details and the certificate info is filled with reasonable values. The time the certificate should be valid is set to 5 years.

/* Create certificate and private key if needed */
if (bCertAvailable == OpcUa_False)
{
OpcUa_PkiCertificate* pCertificate = OpcUa_Null;
OpcUa_PkiRsaKeyPair* pSubjectKeyPair = OpcUa_Null;
OpcUa_PkiCertificateInfo certificateInfo;
OpcUa_PkiIdentity subject;
uStatus = UaServer_PkiRsaKeyPair_Create(&pSubjectKeyPair, 2048);
certificateInfo.sURI = szApplicationUri;
certificateInfo.sIP = "";
certificateInfo.sDNS = szHostname;
certificateInfo.sEMail = "";
certificateInfo.validTime = 3600*24*365*5;
subject.sOrganization = "Unified Automation GmbH";
subject.sOrganizationUnit = "";
subject.sLocality = "Nuremberg";
subject.sState = "Bavaria";
subject.sCountry = "DE";
subject.sCommonName = "UaServerC";
subject.sDomainComponent = "";

With the information filled in the helper structs we can now create the self-signed certificate. The new certificate is then stored in DER format as file, the according private key is stored in PEM format.

uStatus = UaServer_PkiCertificate_Create(
&pCertificate,
certificateInfo,
subject,
*pSubjectKeyPair,
subject,
*pSubjectKeyPair);
uStatus = UaServer_PkiCertificate_ToDERFile(pCertificate, UASERVER_PKI_SERVERCERT);
uStatus = UaServer_PkiRsaKeyPair_ToPEMFile(pSubjectKeyPair, UASERVER_PKI_PRIVATEKEY);
UaServer_PkiCertificate_Delete(&pCertificate);
UaServer_PkiRsaKeyPair_Delete(&pSubjectKeyPair);
}
return uStatus;
}

Step 2: Setting the Endpoint to Accept Only Encrypted Connections

For telling the SDK to accept only secure connections, the endpoint configuration needs to be modified. This is done by getting the server's configuration structure using UaServer_GetConfiguration.

First, all preconfigured UserTokenPolicies are removed and an anonymous UserTokenPolicy is created.

/* Get configuration structure of the server */
pServerConfig = UaServer_GetConfiguration(&uaServer);
...
/* Clean preconfigured endpoint configuration */
for (i = 0; i < pServerConfig->uNoOfUserTokenPolicy; i++)
{
OpcUa_UserTokenPolicy_Clear(&pServerConfig->pUserTokenPolicy[i]);
}
OpcUa_Free(pServerConfig->pUserTokenPolicy);
/* Set the endpoint configuration to use anonymous logon */
pServerConfig->uNoOfUserTokenPolicy = 1;
pServerConfig->pUserTokenPolicy = (OpcUa_UserTokenPolicy*)OpcUa_Alloc(sizeof(OpcUa_UserTokenPolicy));
OpcUa_UserTokenPolicy_Initialize(&pServerConfig->pUserTokenPolicy[0]);
pServerConfig->pUserTokenPolicy[0].TokenType = OpcUa_UserTokenType_Anonymous;
OpcUa_String_StrnCat(&pServerConfig->pUserTokenPolicy[0].PolicyId,
OpcUa_String_FromCString("0"),
OPCUA_STRING_LENDONTCARE);

Now all preconfigured security configurations of the endpoint are removed and replaced with one single configuration that requires signing and encryption and uses the SecurityPolicy Basic256.

/* Clean preconfigured security configurations */
for (i = 0; i < pServerConfig->uNoOfSecurityPolicyConfigurations; i++)
{
OpcUa_String_Clear(&pServerConfig->pSecurityPolicyConfigurations[i].sSecurityPolicy);
}
OpcUa_Free(pServerConfig->pSecurityPolicyConfigurations);
/* Set the security configuration to use Basic256 encryption */
pServerConfig->uNoOfSecurityPolicyConfigurations = 1;
(OpcUa_Endpoint_SecurityPolicyConfiguration*)OpcUa_Alloc(sizeof(OpcUa_Endpoint_SecurityPolicyConfiguration));
OpcUa_String_Initialize(&pServerConfig->pSecurityPolicyConfigurations[0].sSecurityPolicy);
OpcUa_String_AttachCopy(&pServerConfig->pSecurityPolicyConfigurations[0].sSecurityPolicy, OpcUa_SecurityPolicy_Basic256);
pServerConfig->pSecurityPolicyConfigurations[0].uMessageSecurityModes = OPCUA_ENDPOINT_MESSAGESECURITYMODE_SIGNANDENCRYPT;

Step 3: Connecting with a Client using Security

To test our configuration we simply try to connect to the server with a client.

Figure 1-1 Add server in UaExpert

Open UaExpert and click 'Add Server...'. In the new dialog double-click '< Double click to Add Server... >' and enter opc.tcp://localhost:4842 as URL.

Figure 1-2 Get Endpoints using UaExpert

Now you can expand the created node and you will see that the server only supports the Basic256 security policy with signing and encryption. Select the Basic256 - Sign & Encrypt node and click 'OK'. On connecting, UaExpert will show a dialog saying that the server's certificate is unknown and if it should be accepted. Set the dialog to accept it permanently and click OK.

Figure 1-3 Security Warning

On connecting, UaExpert will show a dialog saying that the server's certificate is unknown and if it should be accepted. Set the dialog to accept it permanently and click 'OK'. The connection attempt will fail with the status BadCertificateInvalid, as the server did not accept the client's certificate yet.

To accept the client's certificate in the server, navigate to the folder defined by UASERVER_PKI_REJECTED. There will be the client's certificate with the certificate's SHA1 hash as it's filename. Simply move it to the folder defined by UASERVER_PKI_CERTDIR, where all accepted certificates reside. After moving it, you will be able to connect to the server successfully.