ANSI C UA Server SDK  1.5.0.312
 All Data Structures Functions Variables Typedefs Enumerations Enumerator Modules 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 with 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.

Files used in this lesson:

Step 1: Creating an Application Instance Certificate

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

Add the following include directory:
<SDK_INSTALL_DIR>/third-party/win32/vs2008sp1/openssl/inc32

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

/* PKI configuration */
#define UASERVER_PKI_CONFIGNAME OPCUA_P_PKI_TYPE_OPENSSL
#define UASERVER_PKI_SERVERCERT "pki/own/uaservercert.der"
#define UASERVER_PKI_PRIVATEKEY "pki/own/uaserverkey.nopass.pem"
#define UASERVER_PKI_CERTDIR "pki/trusted/certs/"
#define UASERVER_PKI_CRLDIR "pki/trusted/crl/"
#define UASERVER_PKI_REJECTED "pki/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 this is successful, we don’t need to create a new one.

OpcUa_StatusCode CreateCertificates(OpcUa_CharA *szApplicationUri, OpcUa_CharA *szHostname)
{
OpcUa_StatusCode uStatus = OpcUa_Good;
UaBase_File *pFile = OpcUa_Null;
OpcUa_Boolean bCertAvailable = OpcUa_True;
OpcUa_Int iRet;
/* Check if certificate and private key exist */
pFile = UaBase_Fopen(UASERVER_PKI_SERVERCERT, "r");
if (pFile != OpcUa_Null)
{
UaBase_Fclose(pFile);
}
else
{
bCertAvailable = OpcUa_False;
}
pFile = UaBase_Fopen(UASERVER_PKI_PRIVATEKEY, "r");
if (pFile != OpcUa_Null)
{
UaBase_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)
{
iRet = UaBase_MkPath("pki/own");
if (iRet != UABASE_SUCCESS) {printf("Could not create certificate path (ret=%i)\n", iRet);}
iRet = UaBase_MkPath(UASERVER_PKI_CERTDIR);
if (iRet != UABASE_SUCCESS) {printf("Could not create trust list path (ret=%i)\n", iRet);}
iRet = UaBase_MkPath(UASERVER_PKI_CRLDIR);
if (iRet != UABASE_SUCCESS) {printf("Could not create CRL path (ret=%i)\n", iRet);}
iRet = UaBase_MkPath(UASERVER_PKI_REJECTED);
if (iRet != UABASE_SUCCESS) {printf("Could not create rejected certificate path (ret=%i)\n", iRet);}
}

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 for is set to 5 years.

/* Create certificate and private key if needed */
if (bCertAvailable == OpcUa_False)
{
OpcUa_StatusCode ret = OpcUa_Good;
OpcUa_PkiCertificate* pCertificate = OpcUa_Null;
OpcUa_PkiRsaKeyPair* pSubjectKeyPair = OpcUa_Null;
OpcUa_PkiCertificateInfo certificateInfo;
ret = UaBase_PkiRsaKeyPair_Create(&pSubjectKeyPair, 2048);
if (OpcUa_IsNotGood(ret)) {printf("UaBase_PkiRsaKeyPair_Create failed (ret=0x%08x)\n", ret);}
certificateInfo.sURI = szApplicationUri;
certificateInfo.sIP = "";
certificateInfo.sDNS = (OpcUa_StringA)szHostname;
certificateInfo.sEMail = "";
certificateInfo.validTime = 3600*24*365*5;
subject.sCommonName = UASERVER_APPLICATIONNAME;
subject.sOrganization = UASERVER_MANUFACTURERNAME;
subject.sOrganizationUnit = "";
subject.sLocality = "Nuremberg";
subject.sState = "Bavaria";
subject.sCountry = "DE";
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 corresponding private key is stored in PEM format.

&pCertificate,
certificateInfo,
subject,
*pSubjectKeyPair,
subject,
*pSubjectKeyPair);
if (OpcUa_IsNotGood(ret)) {printf("UaBase_PkiCertificate_Create failed (ret=0x%08x)\n", ret);}
ret = UaBase_PkiCertificate_ToDERFile(pCertificate, UASERVER_PKI_SERVERCERT);
if (OpcUa_IsNotGood(ret)) {printf("UaBase_PkiCertificate_ToDERFile failed (ret=0x%08x)\n", ret);}
ret = UaBase_PkiRsaKeyPair_ToPEMFile(pSubjectKeyPair, UASERVER_PKI_PRIVATEKEY);
if (OpcUa_IsNotGood(ret)) {printf("UaBase_PkiRsaKeyPair_ToPEMFile failed (ret=0x%08x)\n", ret);}
UaBase_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 set accordingly. This is done by getting the server’s configuration structure using UaServer_GetConfiguration.

First, an anonymous UserTokenPolicy is created.

/* Get configuration structure of the server */
pServerConfig = UaServer_GetConfiguration(&uaServer);
...
/* Set the endpoint configuration to use anonymous logon */
pEndpoint->uNoOfUserTokenPolicy = 1;
pEndpoint->pUserTokenPolicy = OpcUa_Alloc(pEndpoint->uNoOfUserTokenPolicy * sizeof(OpcUa_UserTokenPolicy));
OpcUa_UserTokenPolicy_Initialize(&pEndpoint->pUserTokenPolicy[0]);
pEndpoint->pUserTokenPolicy[0].TokenType = OpcUa_UserTokenType_Anonymous;
OpcUa_String_AttachReadOnly(&pEndpoint->pUserTokenPolicy[0].PolicyId, "Anonymous");

Now a security configuration that requires signing and encryption and uses the SecurityPolicy Basic256 is set.

/* Set the endpoint configuration to use Basic256 / SIGNANDENCRYPT */
pEndpoint->uNoOfSecurityPolicyConfigurations = 1;
pEndpoint->pSecurityPolicyConfigurations = OpcUa_Alloc(sizeof(OpcUa_Endpoint_SecurityPolicyConfiguration));
OpcUa_MemSet(pEndpoint->pSecurityPolicyConfigurations, 0, sizeof(OpcUa_Endpoint_SecurityPolicyConfiguration));
OpcUa_String_AttachReadOnly(&pEndpoint->pSecurityPolicyConfigurations[0].sSecurityPolicy, OpcUa_SecurityPolicy_Basic256);
pEndpoint->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.

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

Figure 1-1 Add server in UaExpert

gettingstarted1_lesson_security01_addserver_with_uaexpert.png

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”.

Figure 1-2 Get Endpoints using UaExpert

gettingstarted1_lesson_security01_getendpoints_with_uaexpert.png

On connecting, UaExpert will show a dialog saying that the server’s certificate is unknown and whether it should be accepted. Set the dialog to accept it permanently and click “OK”.

Figure 1-3 Security Warning

gettingstarted1_lesson_security01_certificate_warning_uaexpert.png

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 you can find the client’s certificate with the certificate’s SHA1 hash as its file name. 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.