# Secure clients

> How-to guide: Secure the client: choose an endpoint, validate server certificates, protect your own certificate, authenticate users, and use a checklist for production.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/security/client)

**Sample applications:**

- Client.Authentication ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.Authentication), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.Authentication)): This sample demonstrates how to authenticate at an OPC UA server using user name and password.
- Server.Authentication ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server.Authentication), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server.Authentication)): This sample demonstrates how to setup an OPC UA server with a UserName policy using the UserName ACL for custom user name and password authentication.

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](https://www.traeger.de/en/docs/development/opcua/security/client#harden-for-production) summarizes which settings you should change for production. The pages [Understand certificates](https://www.traeger.de/en/docs/development/opcua/security/certificates) and [Users and roles](https://www.traeger.de/en/docs/development/opcua/security/users-roles) describe the basics.

## Default values at a glance

Types used: `OpcClient`, `OpcClientSecurity`, `OpcCertificateStores`, `OpcCertificateSettings`, `OpcTransport`

| Setting | Default value | Recommendation for production |
|---|---|---|
| `Security.EndpointPolicy` | `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_RsaOaep`, or `Aes256_Sha256_RsaPss` |
| `Security.UseOnlySecureEndpoints` | `false` | `true` if no `EndpointPolicy` is set |
| `Security.AutoUpgradeEndpointPolicy` | `false` | `true` together with an `EndpointPolicy` |
| `Security.AutoAcceptUntrustedCertificates` | `true`: the client accepts unknown server certificates | `false`; add server certificates to the `TrustedPeerStore` in advance |
| `Security.VerifyServersCertificateDomains` | `false` | `true` |
| `Security.CertificateValidationRules` | `Default`: the client accepts certificates signed with SHA-1, and backward-compatible validation is active | `RejectSHA1Signed` and `DisableBackwardsCompatibleValidation` |
| `CertificateStores.AutoCreateCertificate` | `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.UserIdentity` | `null`: anonymous authentication | user name and password pair or certificate |
| `Transport` | `MaxMessageSize` 10 MB, `MaxByteStringLength` 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 used: `OpcClient`, `OpcClientSecurity`, `OpcSecurityPolicy`, `OpcSecurityMode`, `OpcSecurityAlgorithm`, `OpcDiscoveryClient`, `OpcEndpointDescription`

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`):

```csharp
client.Security.UseOnlySecureEndpoints = true;

```

```vbnet
client.Security.UseOnlySecureEndpoints = True

```

**Choose 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`:

```csharp
client.Security.UseHighLevelEndpoint = true;
```

```vbnet
client.Security.UseHighLevelEndpoint = True
```

**Set 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`:

```csharp
client.Security.EndpointPolicy = new OpcSecurityPolicy(
        OpcSecurityMode.SignAndEncrypt,
        OpcSecurityAlgorithm.Basic256Sha256);
```

```vbnet
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:

```csharp
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);
```

```vbnet
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:

```csharp
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.
    }
}

```

```vbnet
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 Using

```

**Check after connecting**: Check whether the client actually uses an encrypted endpoint. This is useful above all if no fixed `EndpointPolicy` is set:

```csharp
client.Connect();
 
if (client.UsedEndpoint.Policy.Mode != OpcSecurityMode.SignAndEncrypt) {
    client.Disconnect();
    throw new InvalidOperationException("The server offers no encrypted endpoint.");
}
```

```vbnet
client.Connect()

If client.UsedEndpoint.Policy.Mode <> OpcSecurityMode.SignAndEncrypt Then
    client.Disconnect()
    Throw New InvalidOperationException("The server offers no encrypted endpoint.")
End If
```

## Validate and trust server certificates

Types used: `OpcClient`, `OpcClientSecurity`, `OpcCertificateValidationFailedEventArgs`, `OpcCertificateValidationRules`, `OpcCertificateJudgementEventArgs`, `OpcCertificateImpeachment`, `OpcCertificateStores`, `OpcCertificateStoreInfo`

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:

```csharp
client.Security.VerifyServersCertificateDomains = true;

```

```vbnet
client.Security.VerifyServersCertificateDomains = True

```

**Accept 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:

```csharp
client.Security.AutoAcceptUntrustedCertificates = false;

```

```vbnet
client.Security.AutoAcceptUntrustedCertificates = False

```

**Validate certificates yourself**: Once you have disabled the default acceptance of unknown certificates, you should provide a custom validation of the certificates:

```csharp
client.CertificateValidationFailed += HandleCertificateValidationFailed;
...
private void HandleCertificateValidationFailed(
        object sender,
        OpcCertificateValidationFailedEventArgs e)
{
    if (e.Certificate.Thumbprint == "...")
        e.Accept = true;
}

```

```vbnet
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 Sub

```

**Trust 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`:

```csharp
// 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);

```

```vbnet
'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 If

```

**Stricter 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:

```csharp
client.Security.CertificateValidationRules
        = OpcCertificateValidationRules.RejectSHA1Signed
        | OpcCertificateValidationRules.DisableBackwardsCompatibleValidation;
```

```vbnet
client.Security.CertificateValidationRules _
        = OpcCertificateValidationRules.RejectSHA1Signed _
        Or OpcCertificateValidationRules.DisableBackwardsCompatibleValidation
```

**Handle 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:

```csharp
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;
}
```

```vbnet
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 Sub
```

**Allow 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:

```csharp
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);
}
```

```vbnet
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 Sub

```

## Protect your own certificate

Types used: `OpcClient`, `OpcCertificateManager`, `OpcCertificateSettings`, `OpcCertificateStores`, `OpcCertificateRequestedEventArgs`

The section [Application certificate](https://www.traeger.de/en/docs/development/opcua/client/configuration#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:

```csharp
// 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);
```

```vbnet
'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:

```csharp
client.CertificateStores.AutoCreateCertificate = false;
client.CertificateRequested += HandleCertificateRequested;
...
private void HandleCertificateRequested(
        object sender,
        OpcCertificateRequestedEventArgs e)
{
    e.AlternativeCertificate = OpcCertificateManager.LoadCertificate(
            certificatePath,
            certificatePassword);
}
```

```vbnet
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 Sub

```

**Protect 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](https://www.traeger.de/en/docs/development/opcua/client/configuration#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 used: `OpcClient`, `OpcUserIdentity`, `OpcClientIdentity`, `OpcCertificateIdentity`, `OpcClientSecurity`, `OpcCertificateStores`, `OpcCertificateStoreInfo`

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**:

```csharp
client.Security.UserIdentity = new OpcClientIdentity("userName", "password");

```

```vbnet
client.Security.UserIdentity = New OpcClientIdentity("userName", "password")

```

**Set a user identity with a certificate (with private key)**:

```csharp
client.Security.UserIdentity = new OpcCertificateIdentity(new X509Certificate2("Doe.pfx"));

```

```vbnet
client.Security.UserIdentity = New OpcCertificateIdentity(New X509Certificate2("Doe.pfx"))

```

**Set an anonymous user identity** (already the default configuration):

```csharp
client.Security.UserIdentity = null;

```

```vbnet
client.Security.UserIdentity = Nothing

```

**Change 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`:

```csharp
client.ChangeUserIdentity(new OpcClientIdentity("userName", "password"));
```

```vbnet
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](https://www.traeger.de/en/docs/development/opcua/client/configuration#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](https://www.traeger.de/en/docs/development/opcua/client/opc-classic#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:

1. **Enforce an encrypted endpoint**: Set `EndpointPolicy` with `SignAndEncrypt` and a current algorithm, or enable `UseOnlySecureEndpoints` and check the endpoint used (see [Choose a secure endpoint](https://www.traeger.de/en/docs/development/opcua/security/client#choose-a-secure-endpoint)).
2. **Reject unknown server certificates**: Disable `AutoAcceptUntrustedCertificates`, and add server certificates to the `TrustedPeerStore` or accept them selectively in the event `CertificateValidationFailed` (see [Validate and trust server certificates](https://www.traeger.de/en/docs/development/opcua/security/client#validate-and-trust-server-certificates)).
3. **Validate certificates strictly**: Set `CertificateValidationRules` to `RejectSHA1Signed` and `DisableBackwardsCompatibleValidation`, and enable `VerifyServersCertificateDomains`.
4. **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](https://www.traeger.de/en/docs/development/opcua/security/client#protect-your-own-certificate)).
5. **Authenticate users**: Use a user identity and do not store passwords in the source code (see [Authenticate users](https://www.traeger.de/en/docs/development/opcua/security/client#authenticate-users)).
6. **Do not accept certificate changes unchecked**: Handle the event `CertificateJudgement`, and set `Dismiss` only after your own check.
7. **Do not increase limits unnecessarily**: Adjust the limits under `Transport` only as far as the application requires.
8. **Protect the license key**: Do not store the key in plain text in publicly accessible files or repositories (see [Negligent disclosure](https://www.traeger.de/en/docs/development/opcua/license#negligent-disclosure)).

In summary, a client configured this way looks like this:

```csharp
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);
```

```vbnet
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)
```
