Repository navigation
Client Security
Configure the client's application identity and explicitly validate the server before relying on a secure Session. These settings are independent of the Session's user identity.
Milo 1.2.0-SNAPSHOT, Java 17. Source baseline 7faaf4f0516c.
The application certificate authenticates the client application during channel/Session establishment. A matching private key proves possession. The server's certificate identifies the remote application. The configured application URI must match the URI in the application's certificate; when an explicit URI is omitted, Milo can derive it from the selected identity.
X509IdentityProvider supplies a certificate user token during Session activation. It identifies the user and proves possession of that user's private key. It does not configure the client application certificate, make the server trust the application, or establish client-side server trust. A server can apply separate trust and authorization policies to both identities.
setCertificateIdentity(keyPair, certificateChain) is the simple path for one application identity. The chain is leaf-first, followed by issuers. The builder checks key/certificate correspondence and infers certificate type; a policy-incompatible identity cannot be selected just because it contains an RSA key. setCertificateGroup(group) is the alternative when the application maintains grouped identities and trust material, including material received through a GDS. The two settings replace each other.
A group supplies its validator unless setCertificateValidator(...) explicitly overrides it. With neither a group validator nor an explicit validator, server certificates are not validated. Supplying an application key and certificate alone is therefore insufficient.
The following configuration uses a server certificate obtained and approved through an independent provisioning process. It selects Basic256Sha256 with SignAndEncrypt, enables all optional validator checks, and enables Session endpoint validation. It returns an unconnected client; the caller must connect and disconnect it using the lifecycle in Connecting.
static OpcUaClient secureClient(
String discoveryUrl,
KeyPair keyPair,
X509Certificate[] certificateChain,
X509Certificate trustedServer)
throws UaException {
var trust = new MemoryTrustListManager();
trust.addTrustedCertificate(trustedServer);
var quarantine = new MemoryCertificateQuarantine();
var validator =
new DefaultClientCertificateValidator(
trust, ValidationCheck.ALL_OPTIONAL_CHECKS, quarantine);
return OpcUaClient.create(
discoveryUrl,
endpoints ->
endpoints.stream()
.filter(
e -> SecurityPolicy.Basic256Sha256.getUri().equals(e.getSecurityPolicyUri()))
.filter(e -> e.getSecurityMode() == MessageSecurityMode.SignAndEncrypt)
.findFirst(),
transport -> {},
config ->
config
.setCertificateIdentity(keyPair, certificateChain)
.setCertificateValidator(validator)
.setSessionEndpointValidationEnabled(true)
.setIdentityProvider(new AnonymousProvider()));
}Do not populate trustedServer by automatically trusting whatever unauthenticated discovery returns. The local fixture uses separately provisioned certificate material, establishes a real secured connection, reads a Good value, and directly verifies that the validator rejects the same server certificate with an empty trust list. This establishes local behavior, not interoperability with a particular vendor.
MemoryTrustListManager and MemoryCertificateQuarantine are suitable for a bounded example. Persist approved trust and rejected-certificate records in production when operational requirements call for it. Issuer certificates help build a chain but do not make it trusted. The path must include a certificate from the trusted list. Approve an application's certificate or an appropriate issuing trust anchor through your provisioning policy.
The client validator quarantines chains that fail trusted-path building. It does not quarantine every validation failure.
| Failure | Status | Quarantined? |
|---|---|---|
| PKIX cannot build a trusted path, including an empty trust list | Can report Bad_SecurityChecksFailed
|
Yes |
| A built path contains no trusted certificate | Bad_CertificateUntrusted |
Yes |
URI mismatch with APPLICATION_URI enabled |
Bad_CertificateUriInvalid |
No |
Hostname mismatch with HOSTNAME enabled |
Bad_CertificateHostNameInvalid |
No |
If you subclass FileBasedTrustListManager or MemoryTrustListManager, put custom trust-list reads in a getSnapshot() override. Default certificate validators read the snapshot directly, so overrides of individual getters do not affect validation. Derive the snapshot from super.getSnapshot() and keep the getters consistent with it. See trust-list migration.
When subclassing DefaultClientCertificateValidator, put custom checks in its four-argument validation override and call the four-argument super with the same profile. An override of only the older three-argument method is bypassed by SDK calls. The validator migration note explains delegation. Subclasses of UsernameProvider, X509IdentityProvider, or CompositeProvider must likewise use the context-based identity method.
DefaultClientCertificateValidator's two-argument constructor enables application-URI checking but omits HOSTNAME and the other optional checks. It logs a warning and does not reject solely for a hostname mismatch. Enable ValidationCheck.HOSTNAME, as in ALL_OPTIONAL_CHECKS, to enforce the hostname check. During CreateSession this check uses the selected endpoint URL, so a host override changes the hostname checked.
Treat quarantine as diagnostic evidence, never automatic approval. Check the failure status, certificate validity, hostnames, application URI, key usage, chain, and policy compatibility before changing trust. For this Basic256Sha256 example, ALL_OPTIONAL_CHECKS requires suitable KeyUsage and ExtendedKeyUsage extensions and usable CRLs for CA-issued certificates. A directly trusted self-signed application certificate has no issuer CRL requirement. Known revocation is enforced even if unknown revocation status is permitted by a less strict configuration.
Discovery and endpoint certificate refresh never authorize a newly advertised certificate. See Recovery and limits for rotation behavior.
Filter transport, policy URI, and message security mode explicitly. Select a policy supported by both Milo's configured crypto providers and the peer; a generated enum or advertised endpoint is not evidence that a vendor combination interoperates. See Security policies for supported policies and provider requirements.
An endpoint's user-token policy can specify its own security policy or inherit the endpoint policy. Ensure the chosen provider has a compatible token policy and sufficient certificate/key material. By default, UsernameProvider selects the first usable advertised username policy. An explicit None policy is usable; an empty policy URI inherits None on a None endpoint. Milo does not prefer token encryption or warn when this selection leaves the password unencrypted inside the token. On a None or Sign-only channel it crosses the wire in cleartext; SignAndEncrypt protects it with channel encryption.
Select SignAndEncrypt, or supply a deterministic policyChooser that selects a compatible encrypted token policy and throws an unchecked exception if none meets your requirements. The chooser receives usable policies, or the full advertised username list when none is usable. Classify inherited policies using the selected endpoint's policy; the chooser receives no endpoint argument.
Either way, validate the server certificate. On a secured OPC TCP channel, configure the client's certificate validator. On a None endpoint, the only check of the certificate that encrypts the password is the validator passed to UsernameProvider. Both two-argument constructors (String password or Supplier<byte[]>) skip that check. Use the corresponding three-argument overload, or the four-argument overload with a chooser, and a real validator when None with encrypted tokens is unavoidable.
The Supplier<byte[]> overloads call the supplier for each Session activation, including reactivation after reconnect, and zero the returned array afterward. Return a fresh password array on each call; reusing the same array supplies a zero-filled password on the next activation.
Anonymous authentication provides no user credential even on an encrypted channel. X.509 user-token policies require proof of private-key possession, so a certificate-token policy resolving to None is not usable for that proof.
The application owns private-key protection, certificate enrollment/renewal, trust approval, persistence, and store lifecycle. Disconnect clients before closing supplied stores or executors; resources shared by several clients need shared ownership. In-memory objects in this example hold no file watchers to close. Production file-backed implementations can own watchers and must be closed according to their contracts. Never replace validation with an insecure validator to suppress a production connection failure.
-
ClientExampleRunner: Inspect the example harness's certificate and identity setup; choose production trust explicitly.
User guide for Milo 1.2.0-RC1 at source 69cb25087, 2026-10-12. Home · Getting started · Examples · Troubleshooting · Release notes
- Home
- Start here
- Client guide
- Server guide
- Feature guides
- Reference
- Operations
- Examples
- Release notes
- Contributing