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.
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");
if (iRet !=
UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR,
"Could not create trust list path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError); }
if (iRet !=
UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR,
"Could not create CRL path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError); }
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); }
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;
}
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_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_ReplaceString(szCommonName, sizeof(szCommonName), "[ApplicationName]", szString);
subject.sCommonName = szCommonName;
subject.sOrganization = szOrganization;
subject.sOrganizationUnit = szOrganizationUnit;
subject.sLocality = szLocality;
subject.sState = szState;
subject.sCountry = szCountry;
subject.sDomainComponent = "";
OpcUa_StrlCpyA(szApplicationUri, a_szApplicationUri, sizeof(szApplicationUri));
UaBase_Settings_ReplaceString(szApplicationUri, sizeof(szApplicationUri), "[gethostname]", a_szHostname);
certificateInfo.sURI = szApplicationUri;
certificateInfo.
sIP = szIPAddresses;
UaBase_Settings_ReplaceString(szDNSNames, sizeof(szDNSNames), "[gethostname]", a_szHostname);
certificateInfo.
sDNS = OpcUa_StrLenA(szDNSNames) > 0 ? szDNSNames : a_szHostname;
certificateInfo.sEMail = "";
certificateInfo.validTime = 3600 * 24 * 365 * iYearsValidFor;
certificateInfo.extendedKeyUsage = a_bIsClient == OpcUa_False ? OpcUa_ExtendedKeyUsage_ServerAuth : OpcUa_ExtendedKeyUsage_ClientAuth;
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; }
OpcUa_StrlCpyA(szString, szCertificateFile, sizeof(szString));
if (iRet !=
UABASE_SUCCESS) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR,
"Could not create certificate path (ret=%i)\n", iRet); OpcUa_GotoErrorWithStatus(OpcUa_BadInternalError);; }
if (OpcUa_IsNotGood(uStatus)) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaBase_PkiCertificate_ToDERFile failed (ret=0x%08x)\n", uStatus); OpcUa_GotoError; }
OpcUa_StrlCpyA(szString, szCertificateKeyFile, sizeof(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);; }
if (OpcUa_IsNotGood(uStatus)) { OpcUa_Trace(OPCUA_TRACE_LEVEL_ERROR, "UaBase_PkiRsaKeyPair_ToPEMFile failed (ret=0x%08x)\n", uStatus); OpcUa_GotoError; }
}
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
OpcUa_Null,
&sampleDiscovery.DiscoveryUrl,
0,
OpcUa_Null,
0,
OpcUa_Null,
Sample_FindServers_CB,
&sampleDiscovery);
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);
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.
if (uNoOfDiscoveryUrls == 0 && uNoOfEndpoints > 0 && sampleDiscovery.pDiscovery->Connected == OpcUa_False)
{
OpcUa_UInt32 i = 0;
int iChosen = -1;
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)
{
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);
OpcUa_EndpointDescription_Initialize(pEndpoint);
#if OPCUA_SUPPORT_PKI
OpcUa_String_FromCString(OpcUa_SecurityPolicy_None),
OPCUA_STRING_LENDONTCARE,
OpcUa_False) != 0)
{
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)
{
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);
Step 4: Integration in Main loop
{
switch (clientContext.State)
{
case State_Idle:
sampleDiscovery.pUserData = &clientContext;
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:
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);
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);
if (uNoOfDiscoveryUrls == 0 && uNoOfEndpoints > 0 && sampleDiscovery.pDiscovery->Connected == OpcUa_False)
{
OpcUa_UInt32 i = 0;
int iChosen = -1;
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)
{
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);
OpcUa_EndpointDescription_Initialize(pEndpoint);
#if OPCUA_SUPPORT_PKI
OpcUa_String_FromCString(OpcUa_SecurityPolicy_None),
OPCUA_STRING_LENDONTCARE,
OpcUa_False) != 0)
{
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)
{
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);
OpcUa_GotoErrorIfBad(uStatus);
clientContext.State = State_Connect;
break;
case State_Connected:
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:
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;
}
if (OpcUa_IsBad(uStatus))
{
OpcUa_Trace(OPCUA_TRACE_LEVEL_WARNING, "UaBase_DoCom failed (0x%08x)\n", uStatus);
bComplete = OpcUa_True;
}
}