ANSI C Based OPC UA Client/Server/PubSub SDK  1.9.2.463
Lesson 4: Discovery and Secure Connection

Files used in this lesson:

Step 1: Create and Load an Application Instance Certificate

To set up a secure connection, client and server have to exchange and trust each others certificates. Sample client creates the PKI folder structure if it does not exist and creates the application instance certificate.

OpcUa_StatusCode SetupPKIStore(UaBase_Settings* a_pSettings,
const char* a_szHostname,
const char* a_szApplicationUri,
const char* a_szApplicationName,
OpcUa_Boolean a_bIsClient)
{
OpcUa_Boolean bGenerateCertificate = OpcUa_False;
OpcUa_CharA szString[UABASE_PATH_MAX];
OpcUa_CharA szCertificateFile[UABASE_PATH_MAX];
OpcUa_CharA szCertificateKeyFile[UABASE_PATH_MAX];
UaBase_File *pFile = OpcUa_Null;
OpcUa_Boolean bCertAvailable = OpcUa_True;
OpcUa_Int iRet;
OpcUa_InitializeStatus(OpcUa_Module_Utilities, "SetupPKIStore");
UaBase_Settings_ReadBool(a_pSettings, "GenerateCertificate", &bGenerateCertificate, OpcUa_False);
UaBase_Settings_ReadString(a_pSettings, "CertificateFile", szCertificateFile, sizeof(szCertificateFile), "");
UaBase_Settings_ReadString(a_pSettings, "CertificateKeyFile", szCertificateKeyFile, sizeof(szCertificateKeyFile), "");
UaBase_Settings_ReadString(a_pSettings, "TrustListPath", szString, sizeof(szString), "");
iRet = UaBase_MkPath(szString);
if (iRet != UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "Could not create trust list path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError); }
UaBase_Settings_ReadString(a_pSettings, "CRLPath", szString, sizeof(szString), "");
iRet = UaBase_MkPath(szString);
if (iRet != UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "Could not create CRL path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError); }
UaBase_Settings_ReadString(a_pSettings, "IssuerTrustListPath", szString, sizeof(szString), "");
iRet = UaBase_MkPath(szString);
if (iRet != UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "Could not create issuer trust list path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError); }
UaBase_Settings_ReadString(a_pSettings, "IssuerCRLPath", szString, sizeof(szString), "");
iRet = UaBase_MkPath(szString);
if (iRet != UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "Could not create issuer CRL path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError); }
if (bGenerateCertificate == OpcUa_False)
{
OpcUa_ReturnStatusCode;
}
/* check if certificate and private key exist and create new ones if not */
pFile = UaBase_Fopen(szCertificateFile, "r");
if (pFile != NULL)
{
UaBase_Fclose(pFile);
}
else
{
bCertAvailable = OpcUa_False;
}
pFile = UaBase_Fopen(szCertificateKeyFile, "r");
if (pFile != NULL)
{
UaBase_Fclose(pFile);
}
else
{
bCertAvailable = OpcUa_False;
}
pFile = NULL;
if (bCertAvailable == OpcUa_False)
{
OpcUa_PkiCertificate *pCertificate = OpcUa_Null;
OpcUa_PkiRsaKeyPair *pSubjectKeyPair = OpcUa_Null;
OpcUa_PkiCertificateInfo certificateInfo = OPCUA_PKICERTIFICATEINFO_STATICINITIALIZER;
OpcUa_CharA szCommonName[64];
OpcUa_CharA szOrganization[64];
OpcUa_CharA szOrganizationUnit[64];
OpcUa_CharA szLocality[64];
OpcUa_CharA szState[64];
OpcUa_CharA szCountry[3];
OpcUa_CharA szDNSNames[64];
OpcUa_CharA szIPAddresses[64];
OpcUa_CharA szApplicationUri[128];
OpcUa_UInt iYearsValidFor = 0;
OpcUa_UInt iKeyLength = 0;
OpcUa_CharA szSignatureAlgorithm[8] = "";
OpcUa_X509SignatureAlgorithm signatureAlgorithm = OpcUa_X509SignatureAlgorithm_Sha256;
OpcUa_StringA_snprintf(szString, sizeof(szString), "%s", a_szApplicationName);
UaBase_Settings_ReplaceString(szString, sizeof(szString), "[gethostname]", a_szHostname);
UaBase_Settings_ReadString(a_pSettings, "CommonName", szCommonName, sizeof(szCommonName), "");
UaBase_Settings_ReplaceString(szCommonName, sizeof(szCommonName), "[ApplicationName]", szString);
subject.sCommonName = szCommonName;
UaBase_Settings_ReadString(a_pSettings, "Organization", szOrganization, sizeof(szOrganization), "");
subject.sOrganization = szOrganization;
UaBase_Settings_ReadString(a_pSettings, "OrganizationUnit", szOrganizationUnit, sizeof(szOrganizationUnit), "");
subject.sOrganizationUnit = szOrganizationUnit;
UaBase_Settings_ReadString(a_pSettings, "Locality", szLocality, sizeof(szLocality), "");
subject.sLocality = szLocality;
UaBase_Settings_ReadString(a_pSettings, "State", szState, sizeof(szState), "");
subject.sState = szState;
UaBase_Settings_ReadString(a_pSettings, "Country", szCountry, sizeof(szCountry), "");
subject.sCountry = szCountry;
subject.sDomainComponent = "";
OpcUa_StrlCpyA(szApplicationUri, a_szApplicationUri, sizeof(szApplicationUri));
UaBase_Settings_ReplaceString(szApplicationUri, sizeof(szApplicationUri), "[gethostname]", a_szHostname);
certificateInfo.sURI = szApplicationUri;
UaBase_Settings_ReadString(a_pSettings, "IPAddresses", szIPAddresses, sizeof(szIPAddresses), "");
certificateInfo.sIP = szIPAddresses;
UaBase_Settings_ReadString(a_pSettings, "DNSNames", szDNSNames, sizeof(szDNSNames), "");
UaBase_Settings_ReplaceString(szDNSNames, sizeof(szDNSNames), "[gethostname]", a_szHostname);
certificateInfo.sDNS = OpcUa_StrLenA(szDNSNames) > 0 ? szDNSNames : a_szHostname;
certificateInfo.sEMail = "";
UaBase_Settings_ReadUInt(a_pSettings, "YearsValidFor", &iYearsValidFor, 5);
certificateInfo.validTime = 3600 * 24 * 365 * iYearsValidFor;
certificateInfo.extendedKeyUsage = a_bIsClient == OpcUa_False ? OpcUa_ExtendedKeyUsage_ServerAuth : OpcUa_ExtendedKeyUsage_ClientAuth;
UaBase_Settings_ReadUInt(a_pSettings, "KeyLength", &iKeyLength, 2048);
UaBase_Settings_ReadString(a_pSettings, "SignatureAlgorithm", szSignatureAlgorithm, sizeof(szSignatureAlgorithm), "Sha256");
uStatus = UaBase_PkiRsaKeyPair_Create(&pSubjectKeyPair, iKeyLength);
if (OpcUa_IsNotGood(uStatus)) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaBase_PkiRsaKeyPair_Create failed (ret=0x%08x)\n", uStatus); OpcUa_GotoError; }
if (szSignatureAlgorithm[0] != '\0')
{
if (OpcUa_StrCmpA(szSignatureAlgorithm, "Sha1") == 0) signatureAlgorithm = OpcUa_X509SignatureAlgorithm_Sha1;
else if (OpcUa_StrCmpA(szSignatureAlgorithm, "Sha224") == 0) signatureAlgorithm = OpcUa_X509SignatureAlgorithm_Sha224;
else if (OpcUa_StrCmpA(szSignatureAlgorithm, "Sha256") == 0) signatureAlgorithm = OpcUa_X509SignatureAlgorithm_Sha256;
else if (OpcUa_StrCmpA(szSignatureAlgorithm, "Sha384") == 0) signatureAlgorithm = OpcUa_X509SignatureAlgorithm_Sha384;
else if (OpcUa_StrCmpA(szSignatureAlgorithm, "Sha512") == 0) signatureAlgorithm = OpcUa_X509SignatureAlgorithm_Sha512;
}
&pCertificate,
certificateInfo,
subject,
*pSubjectKeyPair,
subject,
*pSubjectKeyPair,
signatureAlgorithm);
if (OpcUa_IsNotGood(uStatus)) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaBase_PkiCertificate_Create failed (ret=0x%08x)\n", uStatus); OpcUa_GotoError; }
/* create path for certificate */
OpcUa_StrlCpyA(szString, szCertificateFile, sizeof(szString));
UaBase_DirName(szString);
iRet = UaBase_MkPath(szString);
if (iRet != UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "Could not create certificate path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError);; }
/* create certificate */
uStatus = UaBase_PkiCertificate_ToDERFile(pCertificate, szCertificateFile);
if (OpcUa_IsNotGood(uStatus)) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaBase_PkiCertificate_ToDERFile failed (ret=0x%08x)\n", uStatus); OpcUa_GotoError; }
/* create path for key */
OpcUa_StrlCpyA(szString, szCertificateKeyFile, sizeof(szString));
UaBase_DirName(szString);
iRet = UaBase_MkPath(szString);
if (iRet != UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "Could not create private key path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError);; }
/* create key */
uStatus = UaBase_PkiRsaKeyPair_ToPEMFile(pSubjectKeyPair, szCertificateKeyFile);
if (OpcUa_IsNotGood(uStatus)) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaBase_PkiRsaKeyPair_ToPEMFile failed (ret=0x%08x)\n", uStatus); OpcUa_GotoError; }
UaBase_PkiRsaKeyPair_Delete(&pSubjectKeyPair);
}
OpcUa_ReturnStatusCode;
OpcUa_BeginErrorHandling;
OpcUa_FinishErrorHandling;
}

Step 2: Collect Information about the Server and Endpoints

Sample client retrieves Endpoints using discovery services FindServers and GetEndpoints

uStatus = UaClient_Discovery_BeginFindServers(sampleDiscovery.pDiscovery,
OpcUa_Null,
&sampleDiscovery.DiscoveryUrl,
0,
OpcUa_Null,
0,
OpcUa_Null,
Sample_FindServers_CB,
&sampleDiscovery);
/* Call GetEndpoints for each server returned by FindServers */
uNoOfDiscoveryUrls = 0;
uNoOfEndpoints = 0;
OpcUa_List_GetNumberOfElements(&sampleDiscovery.lstDiscoveryUrls, &uNoOfDiscoveryUrls);
OpcUa_List_GetNumberOfElements(&sampleDiscovery.lstEndpoints, &uNoOfEndpoints);
if (uNoOfDiscoveryUrls > 0 && sampleDiscovery.pDiscovery->Connected == OpcUa_False)
{
pDiscoveryUrl = OpcUa_List_RemoveFirstElement(&sampleDiscovery.lstDiscoveryUrls);
OpcUa_String_Clear(&sampleDiscovery.DiscoveryUrl);
sampleDiscovery.DiscoveryUrl = *pDiscoveryUrl;
OpcUa_Free(pDiscoveryUrl);
uStatus = UaClient_Discovery_BeginGetEndpoints(sampleDiscovery.pDiscovery,
OpcUa_Null,
&sampleDiscovery.DiscoveryUrl,
0,
OpcUa_Null,
0,
OpcUa_Null,
Sample_GetEndpoints_CB,
&sampleDiscovery);
OpcUa_GotoErrorIfBad(uStatus);
}

Step 3: Setup a Secure Connection

Sample client lets the user choose one endpoint from obtained list of endpoints to set up a secure connection. For chosen endpoint with Security Policy other than "None" sample client verifies the Server Certificate and on successful verification lets user add it to client's trust list. Also, the client certificate should be added to server's trust list.

/* Let user choose an endpoint */
if (uNoOfDiscoveryUrls == 0 && uNoOfEndpoints > 0 && sampleDiscovery.pDiscovery->Connected == OpcUa_False)
{
OpcUa_UInt32 i = 0;
int iChosen = -1;
/* restore original DiscoveryUrl set at application start */
OpcUa_String_CopyTo(&sampleDiscovery.InitialDiscoveryUrl, &sampleDiscovery.DiscoveryUrl);
printf("Select one of the following endpoints by pressing the according key:\n");
OpcUa_List_ResetCurrent(&sampleDiscovery.lstEndpoints);
pEndpoint = OpcUa_List_GetCurrentElement(&sampleDiscovery.lstEndpoints);
while (pEndpoint)
{
const char* szSecurityMode = pEndpoint->SecurityMode == OpcUa_MessageSecurityMode_None ? "None" :
pEndpoint->SecurityMode == OpcUa_MessageSecurityMode_SignAndEncrypt ? "SignAndEncrypt" : "Invalid";
printf("[%u]:\n", i);
printf(" EndpointUrl: %s\n", OpcUa_String_GetRawString(&pEndpoint->EndpointUrl));
printf(" SecurityMode: %s\n", szSecurityMode);
printf(" SecurityMode: %s\n", OpcUa_String_GetRawString(&pEndpoint->SecurityPolicyUri));
pEndpoint = OpcUa_List_GetNextElement(&sampleDiscovery.lstEndpoints);
i++;
}
while (iChosen == -1)
{
iChosen = _getch() - '0';
if (iChosen >= 0 && iChosen < (int)uNoOfEndpoints)
{
i = 0;
OpcUa_List_ResetCurrent(&sampleDiscovery.lstEndpoints);
pEndpoint = OpcUa_List_GetCurrentElement(&sampleDiscovery.lstEndpoints);
while (pEndpoint)
{
if (i == (OpcUa_UInt32)iChosen)
{
printf("Using endpoint #%i\n", i);
/* Set session connect info */
OpcUa_EndpointDescription_Clear(&pSession->EndpointDescription);
pSession->EndpointDescription = *pEndpoint;
OpcUa_EndpointDescription_Initialize(pEndpoint);
#if OPCUA_SUPPORT_PKI
if (OpcUa_String_StrnCmp(&pSession->EndpointDescription.SecurityPolicyUri,
OpcUa_String_FromCString(OpcUa_SecurityPolicy_None),
OPCUA_STRING_LENDONTCARE,
OpcUa_False) != 0)
{
/* Check if certificate is trusted */
if (OpcUa_IsBad(uStatus))
{
UaClient_TrustCertificate(&pClientConfiguration->PkiConfig,
printf("\n");
printf("Stored server certificate in the client trust list\n");
printf("Make sure the client certificate is in server trust list\n\n");
}
}
#endif
if (a_szUrl != OpcUa_Null && OpcUa_StrLenA(a_szUrl) > 0)
{
/* If EndpointURL was passed with -u, use it instead of the returned one. */
uStatus = OpcUa_String_AttachCopy(&pSession->EndpointDescription.EndpointUrl, a_szUrl);
OpcUa_GotoErrorIfBad(uStatus);
}
}
else
{
OpcUa_EndpointDescription_Clear(pEndpoint);
}
OpcUa_Free(pEndpoint);
pEndpoint = OpcUa_List_GetNextElement(&sampleDiscovery.lstEndpoints);
i++;
}
OpcUa_List_Clear(&sampleDiscovery.lstEndpoints);
}
else
{
printf("Invalid choice, please try again\n");
iChosen = -1;
}
}
}
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UA Client: Connecting to %s ...\n", SERVER_ENDPOINT_URL);
uStatus = UaClient_Session_BeginConnect(pSession);

Step 4: Integration in Main loop

while (!(bComplete && pSession->ConnectionStatus == UaClient_ConnectionStatus_Disconnected))
{
/* process sample state machine */
switch (clientContext.State)
{
case State_Idle:
sampleDiscovery.pUserData = &clientContext;
uStatus = UaClient_Discovery_BeginFindServers(sampleDiscovery.pDiscovery,
OpcUa_Null,
&sampleDiscovery.DiscoveryUrl,
0,
OpcUa_Null,
0,
OpcUa_Null,
Sample_FindServers_CB,
&sampleDiscovery);
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_FindServers;
break;
case State_FindServersDone:
/* Call GetEndpoints for each server returned by FindServers */
uNoOfDiscoveryUrls = 0;
uNoOfEndpoints = 0;
OpcUa_List_GetNumberOfElements(&sampleDiscovery.lstDiscoveryUrls, &uNoOfDiscoveryUrls);
OpcUa_List_GetNumberOfElements(&sampleDiscovery.lstEndpoints, &uNoOfEndpoints);
if (uNoOfDiscoveryUrls > 0 && sampleDiscovery.pDiscovery->Connected == OpcUa_False)
{
pDiscoveryUrl = OpcUa_List_RemoveFirstElement(&sampleDiscovery.lstDiscoveryUrls);
OpcUa_String_Clear(&sampleDiscovery.DiscoveryUrl);
sampleDiscovery.DiscoveryUrl = *pDiscoveryUrl;
OpcUa_Free(pDiscoveryUrl);
uStatus = UaClient_Discovery_BeginGetEndpoints(sampleDiscovery.pDiscovery,
OpcUa_Null,
&sampleDiscovery.DiscoveryUrl,
0,
OpcUa_Null,
0,
OpcUa_Null,
Sample_GetEndpoints_CB,
&sampleDiscovery);
OpcUa_GotoErrorIfBad(uStatus);
}
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_GetEndpoints;
break;
case State_GetEndpointsDone:
uNoOfDiscoveryUrls = 0;
uNoOfEndpoints = 0;
OpcUa_List_GetNumberOfElements(&sampleDiscovery.lstDiscoveryUrls, &uNoOfDiscoveryUrls);
OpcUa_List_GetNumberOfElements(&sampleDiscovery.lstEndpoints, &uNoOfEndpoints);
/* Let user choose an endpoint */
if (uNoOfDiscoveryUrls == 0 && uNoOfEndpoints > 0 && sampleDiscovery.pDiscovery->Connected == OpcUa_False)
{
OpcUa_UInt32 i = 0;
int iChosen = -1;
/* restore original DiscoveryUrl set at application start */
OpcUa_String_CopyTo(&sampleDiscovery.InitialDiscoveryUrl, &sampleDiscovery.DiscoveryUrl);
printf("Select one of the following endpoints by pressing the according key:\n");
OpcUa_List_ResetCurrent(&sampleDiscovery.lstEndpoints);
pEndpoint = OpcUa_List_GetCurrentElement(&sampleDiscovery.lstEndpoints);
while (pEndpoint)
{
const char* szSecurityMode = pEndpoint->SecurityMode == OpcUa_MessageSecurityMode_None ? "None" :
pEndpoint->SecurityMode == OpcUa_MessageSecurityMode_SignAndEncrypt ? "SignAndEncrypt" : "Invalid";
printf("[%u]:\n", i);
printf(" EndpointUrl: %s\n", OpcUa_String_GetRawString(&pEndpoint->EndpointUrl));
printf(" SecurityMode: %s\n", szSecurityMode);
printf(" SecurityMode: %s\n", OpcUa_String_GetRawString(&pEndpoint->SecurityPolicyUri));
pEndpoint = OpcUa_List_GetNextElement(&sampleDiscovery.lstEndpoints);
i++;
}
while (iChosen == -1)
{
iChosen = _getch() - '0';
if (iChosen >= 0 && iChosen < (int)uNoOfEndpoints)
{
i = 0;
OpcUa_List_ResetCurrent(&sampleDiscovery.lstEndpoints);
pEndpoint = OpcUa_List_GetCurrentElement(&sampleDiscovery.lstEndpoints);
while (pEndpoint)
{
if (i == (OpcUa_UInt32)iChosen)
{
printf("Using endpoint #%i\n", i);
/* Set session connect info */
OpcUa_EndpointDescription_Clear(&pSession->EndpointDescription);
pSession->EndpointDescription = *pEndpoint;
OpcUa_EndpointDescription_Initialize(pEndpoint);
#if OPCUA_SUPPORT_PKI
if (OpcUa_String_StrnCmp(&pSession->EndpointDescription.SecurityPolicyUri,
OpcUa_String_FromCString(OpcUa_SecurityPolicy_None),
OPCUA_STRING_LENDONTCARE,
OpcUa_False) != 0)
{
/* Check if certificate is trusted */
if (OpcUa_IsBad(uStatus))
{
UaClient_TrustCertificate(&pClientConfiguration->PkiConfig,
printf("\n");
printf("Stored server certificate in the client trust list\n");
printf("Make sure the client certificate is in server trust list\n\n");
}
}
#endif
if (a_szUrl != OpcUa_Null && OpcUa_StrLenA(a_szUrl) > 0)
{
/* If EndpointURL was passed with -u, use it instead of the returned one. */
uStatus = OpcUa_String_AttachCopy(&pSession->EndpointDescription.EndpointUrl, a_szUrl);
OpcUa_GotoErrorIfBad(uStatus);
}
}
else
{
OpcUa_EndpointDescription_Clear(pEndpoint);
}
OpcUa_Free(pEndpoint);
pEndpoint = OpcUa_List_GetNextElement(&sampleDiscovery.lstEndpoints);
i++;
}
OpcUa_List_Clear(&sampleDiscovery.lstEndpoints);
}
else
{
printf("Invalid choice, please try again\n");
iChosen = -1;
}
}
}
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UA Client: Connecting to %s ...\n", SERVER_ENDPOINT_URL);
uStatus = UaClient_Session_BeginConnect(pSession);
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_Connect;
break;
case State_Connected:
uStatus = UaClient_Session_BeginDisconnect(pSession, OpcUa_False);
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_Disconnect;
break;
case State_Disconnected:
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "Sample successfully completed. Terminating now.\n");
bComplete = OpcUa_True;
break;
case State_Error:
uStatus = UaClient_Session_BeginDisconnect(pSession, OpcUa_True);
if (OpcUa_IsBad(uStatus) && uStatus != OpcUa_BadInvalidState) { OpcUa_GotoError; }
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "An error occured. Terminating now.\n");
bComplete = OpcUa_True;
break;
default:
break;
}
/* Process Ua Client events */
uStatus = UaBase_DoCom();
if (OpcUa_IsBad(uStatus))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UaBase_DoCom failed (0x%08x)\n", uStatus);
bComplete = OpcUa_True;
}
}