How-to guide
Secure clients
Secure the client: choose an endpoint, validate server certificates, protect your own certificate, authenticate users, and use a checklist for production.
How-to guide
Secure clients
This page collects all settings a client uses to secure its connections to a server. The default values of the OPC UA .NET SDK are designed for an easy start and are not suitable for production. The section Harden for production summarizes which settings you should change for production. The pages Understand certificates and Users and roles describe the basics.
Default values at a glance
Types usedOpcClientOpcClientSecurityOpcCertificateStoresOpcCertificateSettingsOpcTransport
| Setting | Default value | Recommendation for production |
|---|---|---|
Security. | null: the client chooses the endpoint with the lowest level, usually an endpoint with the mode None | Set a policy with SignAndEncrypt and Basic256Sha256, Aes128_Sha256_Rsa, or Aes256_Sha256_Rsa |
Security. | false | true if no EndpointPolicy is set |
Security. | false | true together with an EndpointPolicy |
Security. | true: the client accepts unknown server certificates | false; add server certificates to the TrustedPeerStore in advance |
Security. | false | true |
Security. | Default: the client accepts certificates signed with SHA-1, and backward-compatible validation is active | RejectSHA1Signed and Disable |
Certificate | true: if there is no certificate, the client creates a new one (up to version 2.58 also if the existing one is invalid) | false, and use a managed certificate |
| Automatically created certificate | self-signed, key length 2048 bits, valid for 600 months | certificate from your own certificate authority with a lifetime according to your policy |
Security. | null: anonymous authentication | user name and password pair or certificate |
Transport | MaxMessageSize 10 MB, Max 5 MB, MaxStringLength and MaxArrayLength 65535 each | increase only as far as the application needs |
Insecure default values
With the default values, the client usually connects without encryption and accepts the certificate of any server it does not know. An attacker in the network can then pose as the server (man in the middle), read and modify the transferred data, and obtain the credentials sent to it. Change these settings before you go to production.
Choose a secure endpoint
Types usedOpcClientOpcClientSecurityOpcSecurityPolicyOpcSecurityModeOpcSecurityAlgorithmOpcDiscoveryClientOpcEndpointDescription
By default, the client chooses the endpoint with the simplest security configuration from the server endpoints offered, that is, the endpoint with the lowest level. According to the OPC Foundation, the level of the policy of an endpoint is a relative measure of the security mechanisms the endpoint uses. By definition, an endpoint with a higher level is therefore more secure than an endpoint with a lower level. This way, the client usually chooses an endpoint with the mode None. If the server does not offer one, the client chooses an endpoint with Sign or SignAndEncrypt.
Consider only encrypted endpoints: If the client should consider only encrypted endpoints, set the property UseOnlySecureEndpoints to true. The client then chooses only endpoints with the mode SignAndEncrypt (enumeration OpcSecurityMode):
client.Security.UseOnlySecureEndpoints = true;client.Security.UseOnlySecureEndpoints = TrueChoose the endpoint with the highest level: If the client should choose the endpoint that defines the highest policy level, set the property UseHighLevelEndpoint to true:
client.Security.UseHighLevelEndpoint = true;client.Security.UseHighLevelEndpoint = TrueSet a specific security configuration: If the client should choose an endpoint with a specific security configuration, set the property EndpointPolicy as in the following example. The client then uses only an endpoint with exactly this OpcSecurityMode and OpcSecurityAlgorithm. Choose a current algorithm: Basic128Rsa15 and Basic256 are considered deprecated, and Basic256Sha256, Aes128_Sha256_RsaOaep, or Aes256_Sha256_RsaPss, for example, are preferable. If you do not specify an algorithm (OpcSecurityAlgorithm.Auto), the client uses Basic256Sha256 for Sign and SignAndEncrypt. It then does not connect to a server that offers only Basic256 or Basic128Rsa15. In this case, the client ignores the properties UseOnlySecureEndpoints and UseHighLevelEndpoint:
client.Security.EndpointPolicy = new OpcSecurityPolicy(
OpcSecurityMode.SignAndEncrypt,
OpcSecurityAlgorithm.Basic256Sha256);client.Security.EndpointPolicy = New OpcSecurityPolicy( _
OpcSecurityMode.SignAndEncrypt, _
OpcSecurityAlgorithm.Basic256Sha256)Fall back to a more secure endpoint: If the client should fall back to a more secure endpoint when the server does not offer the specified policy, also set the property AutoUpgradeEndpointPolicy to true. The property UsedEndpoint returns the endpoint the client actually uses after the connection is established:
client.Security.EndpointPolicy = new OpcSecurityPolicy(
OpcSecurityMode.SignAndEncrypt,
OpcSecurityAlgorithm.Basic256Sha256);
// Use the next better endpoint in case the server does not offer the policy.
client.Security.AutoUpgradeEndpointPolicy = true;
client.Connect();
Console.WriteLine("Used endpoint: {0}", client.UsedEndpoint);client.Security.EndpointPolicy = New OpcSecurityPolicy( _
OpcSecurityMode.SignAndEncrypt, _
OpcSecurityAlgorithm.Basic256Sha256)
'Use the next better endpoint in case the server does not offer the policy.
client.Security.AutoUpgradeEndpointPolicy = True
client.Connect()
Console.WriteLine("Used endpoint: {0}", client.UsedEndpoint)To examine the endpoints of a server, use the class OpcDiscoveryClient. The property SupportedTokenTypes of the endpoint description returns the kinds of user identities an endpoint supports:
using (var client = new OpcDiscoveryClient("opc.tcp://localhost:4840/")) {
var endpoints = client.DiscoverEndpoints();
foreach (var endpoint in endpoints) {
// Your code to operate on each endpoint.
}
}Using client As New OpcDiscoveryClient("opc.tcp://localhost:4840/")
Dim endpoints = client.DiscoverEndpoints()
For Each endpoint in endpoints
'Your code to operate on each endpoint.
Next
End UsingCheck after connecting: Check whether the client actually uses an encrypted endpoint. This is useful above all if no fixed EndpointPolicy is set:
client.Connect();
if (client.UsedEndpoint.Policy.Mode != OpcSecurityMode.SignAndEncrypt) {
client.Disconnect();
throw new InvalidOperationException("The server offers no encrypted endpoint.");
}client.Connect()
If client.UsedEndpoint.Policy.Mode <> OpcSecurityMode.SignAndEncrypt Then
client.Disconnect()
Throw New InvalidOperationException("The server offers no encrypted endpoint.")
End IfValidate and trust server certificates
Types usedOpcClientOpcClientSecurityOpcCertificateValidationFailedEventArgsOpcCertificateValidationRulesOpcCertificateJudgementEventArgsOpcCertificateImpeachmentOpcCertificateStoresOpcCertificateStoreInfo
When the connection is established, a server sends its certificate to the client for authentication. Based on the server certificate, the client decides whether it really connects to this server and thus trusts it.
Check the domains in the certificate: If you enable the property VerifyServersCertificateDomains, the client also checks the domains stored in the server certificate. By default, this property is disabled:
client.Security.VerifyServersCertificateDomains = true;client.Security.VerifyServersCertificateDomains = TrueAccept only trusted certificates: If the client should accept only trusted certificates, disable the default acceptance of unknown certificates. Even with acceptance enabled, the client does not automatically accept expired, revoked, or otherwise invalid certificates:
client.Security.AutoAcceptUntrustedCertificates = false;client.Security.AutoAcceptUntrustedCertificates = FalseValidate certificates yourself: Once you have disabled the default acceptance of unknown certificates, you should provide a custom validation of the certificates:
client.CertificateValidationFailed += HandleCertificateValidationFailed;
...
private void HandleCertificateValidationFailed(
object sender,
OpcCertificateValidationFailedEventArgs e)
{
if (e.Certificate.Thumbprint == "...")
e.Accept = true;
}AddHandler client.CertificateValidationFailed, AddressOf HandleCertificateValidationFailed
...
Private Sub HandleCertificateValidationFailed( _
ByVal sender As Object, _
ByVal e As OpcCertificateValidationFailedEventArgs)
If e.Certificate.Thumbprint = "..." Then
e.Accept = True
End If
End SubTrust a certificate manually: If the client has classified a server certificate as untrusted, you can declare it as trusted manually. To do this, store it in the TrustedPeerStore:
// In context of the event handler the sender is an OpcClient.
var client = (OpcClient)sender;
if (!client.CertificateStores.TrustedPeerStore.Contains(e.Certificate))
client.CertificateStores.TrustedPeerStore.Add(e.Certificate);'In context of the event handler the sender is an OpcClient.
Dim client = CType(sender, OpcClient)
If Not client.CertificateStores.TrustedPeerStore.Contains(e.Certificate) Then
client.CertificateStores.TrustedPeerStore.Add(e.Certificate)
End IfStricter rules for validating certificates: The property CertificateValidationRules sets them. With RejectSHA1Signed, the client rejects certificates that were signed with SHA-1. With DisableBackwardsCompatibleValidation, it validates according to the currently recommended rules. By default, both rules are disabled:
client.Security.CertificateValidationRules
= OpcCertificateValidationRules.RejectSHA1Signed
| OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;client.Security.CertificateValidationRules _
= OpcCertificateValidationRules.RejectSHA1Signed _
Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidationHandle a certificate change: If the server uses a different certificate during an existing connection than when the connection was established, the client raises the event CertificateJudgement. If you do not handle it, this situation leads to an exception. With the property Dismiss of the event arguments, you can dismiss the objection:
client.CertificateJudgement += HandleCertificateJudgement;
...
private static void HandleCertificateJudgement(
object sender,
OpcCertificateJudgementEventArgs e)
{
// The server uses a different certificate than when the connection was established.
if (e.Impeachment == OpcCertificateImpeachment.Changed
&& e.ImpeachedCertificate.Thumbprint == "...")
e.Dismiss = true;
}AddHandler client.CertificateJudgement, AddressOf HandleCertificateJudgement
...
Private Shared Sub HandleCertificateJudgement( _
ByVal sender As Object, _
ByVal e As OpcCertificateJudgementEventArgs)
'The server uses a different certificate than when the connection was established.
If e.Impeachment = OpcCertificateImpeachment.Changed _
AndAlso e.ImpeachedCertificate.Thumbprint = "..." Then
e.Dismiss = True
End If
End SubAllow only a specific server: If the client should not accept unknown certificates across the board, but you also do not want to store every certificate in the TrustedPeerStore in advance, the client can accept the certificate of the expected server in the event CertificateValidationFailed. It recognizes the certificate by its thumbprint. The property Result states the reason for the objection. Accept only certificates with the result Untrusted. Results such as Revoked, TimeInvalid, InvalidDomain, or ChainIncomplete indicate a certificate that is revoked, expired, issued for another computer, or cannot be fully validated:
client.CertificateValidationFailed += HandleCertificateValidationFailed;
...
private void HandleCertificateValidationFailed(
object sender,
OpcCertificateValidationFailedEventArgs e)
{
// Accept only the expected server, and only if its certificate is merely unknown.
e.Accept = e.Result == OpcCertificateValidationResult.Untrusted
&& string.Equals(
e.Certificate.Thumbprint,
expectedThumbprint,
StringComparison.OrdinalIgnoreCase);
}AddHandler client.CertificateValidationFailed, AddressOf HandleCertificateValidationFailed
...
Private Sub HandleCertificateValidationFailed( _
ByVal sender As Object, _
ByVal e As OpcCertificateValidationFailedEventArgs)
'Accept only the expected server, and only if its certificate is merely unknown.
e.Accept = e.Result = OpcCertificateValidationResult.Untrusted _
AndAlso String.Equals( _
e.Certificate.Thumbprint, _
expectedThumbprint, _
StringComparison.OrdinalIgnoreCase)
End SubProtect your own certificate
Types usedOpcClientOpcCertificateManagerOpcCertificateSettingsOpcCertificateStoresOpcCertificateRequestedEventArgs
The section Application certificate describes how a certificate is loaded, created, and stored. For security, the following points matter.
Set the properties of the certificate: A certificate created by the SDK is self-signed, uses a 2048-bit key, and is valid for 600 months. You set the key length (KeySize) and the validity in months (Lifetime) through the class OpcCertificateSettings. For production, use a certificate issued by the certificate authority of your company:
// The key size is specified in bits, the lifetime in months.
var settings = new OpcCertificateSettings(client.ApplicationName) {
KeySize = 4096,
Lifetime = 24
};
client.Certificate = OpcCertificateManager.CreateCertificate(settings);'The key size is specified in bits, the lifetime in months.
Dim settings = New OpcCertificateSettings(client.ApplicationName) With { _
.KeySize = 4096, _
.Lifetime = 24 _
}
client.Certificate = OpcCertificateManager.CreateCertificate(settings)Do not replace a certificate silently: Up to version 2.58, the following applies: If the property AutoCreateCertificate is active and the client finds no valid certificate when it connects, it deletes the previous certificate from the ApplicationStore and creates a new, self-signed certificate. This happens, for example, when the certificate has expired. Servers that trusted the previous certificate then reject the client.
As of version 2.59, the client never replaces an existing but invalid certificate automatically. Instead, it raises the event CertificateRequested. The property Reason of the event data states the reason: Invalid, UseNotAllowed, Revoked, TimeInvalid, IssuerUseNotAllowed, IssuerRevoked, or IssuerTimeInvalid. For an expired certificate, for example, it is TimeInvalid. The property AutoCreateCertificate of the event data is false in this case. Unless the handler provides another certificate, the client keeps using the existing certificate. As of version 2.59, the client uses a certificate that the handler provides through the property AlternativeCertificate when it connects, also on later connects. The certificate must contain its private key.
Provide a certificate yourself through the event CertificateRequested and disable automatic creation:
client.CertificateStores.AutoCreateCertificate = false;
client.CertificateRequested += HandleCertificateRequested;
...
private void HandleCertificateRequested(
object sender,
OpcCertificateRequestedEventArgs e)
{
e.AlternativeCertificate = OpcCertificateManager.LoadCertificate(
certificatePath,
certificatePassword);
}client.CertificateStores.AutoCreateCertificate = False
AddHandler client.CertificateRequested, AddressOf HandleCertificateRequested
...
Private Sub HandleCertificateRequested( _
ByVal sender As Object, _
ByVal e As OpcCertificateRequestedEventArgs)
e.AlternativeCertificate = OpcCertificateManager.LoadCertificate( _
certificatePath, _
certificatePassword)
End SubProtect the private key: The ApplicationStore contains the certificate together with its private key. The SDK does not set access permissions for the files of the certificate stores. Therefore, put the certificate stores in a directory that only the user account of the application can access. The section Configure the application and session describes how to configure the certificate stores. Load an existing certificate from a password-protected .pfx file.
Authenticate users
Types usedOpcClientOpcUserIdentityOpcClientIdentityOpcCertificateIdentityOpcClientSecurityOpcCertificateStoresOpcCertificateStoreInfo
If a server expects information about the identity of the user in addition to the client certificate, set the identity through the property UserIdentity. You can choose identities with user name and password or with a certificate. If the server supports an anonymous user identity, you do not have to set a specific identity.
Set a user identity with user name and password:
client.Security.UserIdentity = new OpcClientIdentity("userName", "password");client.Security.UserIdentity = New OpcClientIdentity("userName", "password")Set a user identity with a certificate (with private key):
client.Security.UserIdentity = new OpcCertificateIdentity(new X509Certificate2("Doe.pfx"));client.Security.UserIdentity = New OpcCertificateIdentity(New X509Certificate2("Doe.pfx"))Set an anonymous user identity (already the default configuration):
client.Security.UserIdentity = null;client.Security.UserIdentity = NothingChange the user identity at runtime: The ChangeUserIdentity method logs on another user in the existing session without closing the connection. With null, the client continues as an anonymous user. If the server rejects the identity, the method throws an OpcException with the status of the server, for example BadUserAccessDenied. The session and the previous identity then remain. Only after a successful change does the UserIdentity property return the new identity. If the client is not connected, the method throws an OpcException with the status BadServerNotConnected:
client.ChangeUserIdentity(new OpcClientIdentity("userName", "password"));client.ChangeUserIdentity(New OpcClientIdentity("userName", "password"))Transfer of the password: The client encrypts the password of a user name and password identity with the certificate of the server, provided that the server specifies a security policy for its user tokens. A server developed with this SDK does so even on endpoints with the mode None. With servers from other vendors, the password can be transferred unencrypted on such endpoints. In addition, this protection is only as good as the validation of the server certificate: if the client accepts unknown certificates automatically, an attacker posing as the server can decrypt the password. Therefore, authenticate users only through endpoints with the mode SignAndEncrypt, and disable the property AutoAcceptUntrustedCertificates for them. Do not store passwords in the source code. Read them at runtime from protected storage instead.
Data transfer limits
The limits for the size of messages, strings, and arrays also protect the client from oversized responses of a server. Increase them only as far as the application requires (see Data transfer limits).
OPC Classic and DCOM
For connections to OPC Classic servers, the security settings of DCOM apply. The section Remote servers and DCOM security of the page OPC Classic describes how to set the logon and the authentication level.
Harden for production
Check the following points before you go to production:
- Enforce an encrypted endpoint: Set
EndpointPolicywithSignAndEncryptand a current algorithm, or enableUseOnlySecureEndpointsand check the endpoint used (see Choose a secure endpoint). - Reject unknown server certificates: Disable
AutoAcceptUntrustedCertificates, and add server certificates to theTrustedPeerStoreor accept them selectively in the eventCertificateValidationFailed(see Validate and trust server certificates). - Validate certificates strictly: Set
CertificateValidationRulestoRejectSHA1SignedandDisableBackwardsCompatibleValidation, and enableVerifyServersCertificateDomains. - Manage your own certificate: Use a certificate with an appropriate key length and lifetime, disable
AutoCreateCertificate, and put the certificate stores in a protected directory (see Protect your own certificate). - Authenticate users: Use a user identity and do not store passwords in the source code (see Authenticate users).
- Do not accept certificate changes unchecked: Handle the event
CertificateJudgement, and setDismissonly after your own check. - Do not increase limits unnecessarily: Adjust the limits under
Transportonly as far as the application requires. - Protect the license key: Do not store the key in plain text in publicly accessible files or repositories (see Negligent disclosure).
In summary, a client configured this way looks like this:
var client = new OpcClient("opc.tcp://192.168.0.10:4840/");
// Only an encrypted endpoint with a current algorithm.
client.Security.EndpointPolicy = new OpcSecurityPolicy(
OpcSecurityMode.SignAndEncrypt,
OpcSecurityAlgorithm.Basic256Sha256);
client.Security.AutoUpgradeEndpointPolicy = true;
// Trust only known server certificates and check them strictly.
client.Security.AutoAcceptUntrustedCertificates = false;
client.Security.VerifyServersCertificateDomains = true;
client.Security.CertificateValidationRules
= OpcCertificateValidationRules.RejectSHA1Signed
| OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;
// Use a managed certificate instead of a generated one.
client.CertificateStores.AutoCreateCertificate = false;
client.Certificate = OpcCertificateManager.LoadCertificate(certificatePath, certificatePassword);
// Log on as a user instead of anonymously.
client.Security.UserIdentity = new OpcClientIdentity(userName, password);Dim client = New OpcClient("opc.tcp://192.168.0.10:4840/")
'Only an encrypted endpoint with a current algorithm.
client.Security.EndpointPolicy = New OpcSecurityPolicy( _
OpcSecurityMode.SignAndEncrypt, _
OpcSecurityAlgorithm.Basic256Sha256)
client.Security.AutoUpgradeEndpointPolicy = True
'Trust only known server certificates and check them strictly.
client.Security.AutoAcceptUntrustedCertificates = False
client.Security.VerifyServersCertificateDomains = True
client.Security.CertificateValidationRules _
= OpcCertificateValidationRules.RejectSHA1Signed _
Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidation
'Use a managed certificate instead of a generated one.
client.CertificateStores.AutoCreateCertificate = False
client.Certificate = OpcCertificateManager.LoadCertificate(certificatePath, certificatePassword)
'Log on as a user instead of anonymously.
client.Security.UserIdentity = New OpcClientIdentity(userName, password)
