Initial application configuration
The client application needs an initial configuration that defines and creates the
- used certificate store,
- the application instance certificate,
- and the application instance URI
The default certificate store is the file based OpenSSL PKI store. The store has the following directories and subdirectories
- own
- certs
The public key of the application. Certificates have to be stored in DER format (with file extension .der).
- private
The private key of the application. The private key is encoded in PEM format (with .pem as file extension).
- trusted
- certs
The folder where certificates of trusted applications and trusted Certificate Authorities (CAs) should be stored. Each CA requires one and only one Certificate Revocation List (CRL) in the parallel crl directory. Certificates have to be stored in DER format (with file extension .der).
- crl
The folder where revocation lists for trusted CAs should be stored. A CRL may be empty if no certificates have been revoked yet. Revocation lists have to be stored in DER format (with file extension .crl) or in PEM format (with .pem as file extension).
- issuers
- certs
The folder where issuer certificates are stored. Issuer certificates are CA certificates necessary for the verification of the full trust chain of CA certificates in the trust list. Each CA requires one and only one CRL. The CRL may be empty if no certificates have been revoked yet.
- crl
The folder where revocation lists for issuer CAs should be stored.
The method ClientSecurityInfo::initializePkiProviderOpenSSL() is used to initialize the security context with the file directory paths for trusted and issuer certificates. The security context is used for discovery and connect calls to the server.
On Windows systems the Windows certificate store can be used instead of the file store. The following steps can be used to open the windows certificate store
- Start → Run → mmc
- File → Add Snap-In
- Add
- Select Certificates from the list
- Select location, the default location is LocalComputer
The method ClientSecurityInfo::initializePkiProviderWindows() is used to initialize the security context. It is used for discovery and connect calls to the server. The store location (Location_LocalMachine or Location_CurrentUser) and the store name is passed to initializePkiProviderWindows().
Before the client is used the first time, a client instance certificate with public and private key needs to be created or provided by the administrator of the application. A default certificate can be created during setup or at the first start of the application. The following code creates a certificate. The strings for the identity should be provided by the administrator of the application.
char szHostName[256];
if ( 0 == gethostname(szHostName, 256) )
{
sNodeName = szHostName;
}
identity.commonName =
UaString(
"ProductName@%1").
arg(sNodeName);;
identity.organization = "Organization";
identity.organizationUnit = "Unit";
identity.locality = "LocationName";
identity.state = "State";
identity.country = "DE";
identity.domainComponent = "MyComputer";
info.URI =
UaString(
"urn:%1:UnifiedAutomation::Client_Cpp_SDK").
arg(sNodeName);
info.DNS = sNodeName;
IssuerPrivateKey = keyPair.privateKey();
SubjectPublicKey = keyPair.publicKey();
UaPkiCertificate cert ( info, identity, SubjectPublicKey, identity, IssuerPrivateKey );
The following code is an example for storing the created certificate in the file based OpenSSL PKI store.
dirHelper.mkpath(usClientCertificatePath);
dirHelper.mkpath(usPrivateKeyPath);
cert.toDERFile ( sClientCertificateFile.
toUtf8() );
keyPair.toPEMFile ( sClientPrivateKeyFile.
toUtf8(), 0 );
The following code is an example for storing the created certificate in the Windows certificate store.
WindowsStoreLocation windowsStoreLocation = Location_LocalMachine;
UaString sWindowsStoreName =
"MyApplicationStore";
cert.toWindowsStoreWithPrivateKey(windowsStoreLocation, sWindowsStoreName, keyPair);
sThumbprint = cert.thumbPrint().toHex();
Loading the certificates at start up
The initial application configuration with certificate creation needs to be done once. The client certificate need to be loaded at every start up of the client.
The following code is used to load the client certificate (public and private key) from the file based OpenSSL PKI store.
SessionSecurityInfo sessionSecurityInfo;
UaString sCertificateRevocationListLocation;
UaString sIssuersRevocationListLocation;
uStatus = sessionSecurityInfo.initializePkiProviderOpenSSL(
sCertificateRevocationListLocation,
sCertificateTrustListLocation,
sIssuersRevocationListLocation,
sIssuersCertificatesLocation);
{
}
uStatus = sessionSecurityInfo.loadClientCertificateOpenSSL(
sClientCertificateFile,
sClientPrivateKeyFile);
{
}
The following code is used to load the client certificate (public and private key) from the Windows certificate store.
SessionSecurityInfo sessionSecurityInfo;
WindowsStoreLocation windowsStoreLocation = Location_LocalMachine;
UaString sWindowsStoreName =
"MyApplicationStore";
UaString sThumbprint =
"3FFAD542EED94A17B5726296BAC39EF8EE28004D";
uStatus = sessionSecurityInfo.initializePkiProviderWindows(
windowsStoreLocation,
sWindowsStoreName);
{
}
uStatus = sessionSecurityInfo.loadClientCertificateWindows(sThumbprint);
{
}
Getting security configuration from server
The application instance certificates are used for two different tasks. They are used for application authentication and for message encryption and signing. In addition, there are some consistency checks done during application layer connection establishment with CreateSession and ActivateSession.
For application authentication and message encryption and signing, the certificates must be exchanged before the first message is sent. The client must receive the server certificate before creating a secure communication channel. The typical way of getting the server certificate is via GetEndpoints. The client sends its client certificate with the CreateSecureChannel request, to be available on the server side for the CreateSecureChannel response.
Before a client is able to connect to a server, the required security configuration needs to be requested from the server using Discovery functionality. UaDiscovery::findServers() is used to get a list of available servers from a discovery server. UaDiscovery::getEndpoints() is used to get a list of endpoints from the server. An endpoint contains the URL, the server certificate (public key) and the required security settings.
GetEndpoints does not do any check on the certificate, but application authentication must be checked with the client trust list before the secure channel is created.
By setting the server certificate returned from GetEndpoints() to SessionSecurityInfo::serverCertificate, it is available for message encryption and signing. The check for application authentication on the client side can be done by the client application with SessionSecurityInfo::verifyServerCertificate() before UaSession::connect() is called, or it is executed by the Client SDK if SessionSecurityInfo::doServerCertificateVerify is set to true.
The following code shows how to verify a server certificate and store it if the user accepts the certificate and allows access to the server.
SessionSecurityInfo sessionSecurityInfo;
sessionSecurityInfo.serverCertificate = serverCertificate;
if ( sessionSecurityInfo.verifyServerCertificate().isBad() )
{
cert = cert.
fromDER(derCertificate);
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() );
sessionSecurityInfo.saveServerCertificate(certificateName);
}