Skip to content

Server Security

Kevin Herron edited this page Oct 5, 2026 · 10 revisions

Provision an application identity, enforce peer trust, and expose compatible secured endpoints.

Milo 1.2.0-SNAPSHOT, Java 17. API baseline 7faaf4f0516c.

Establish the application identity

A server needs its own private key and application certificate chain for secured endpoints. Preserve the key across ordinary restarts, protect it in storage, and include the persistent application URI and advertised DNS/IP names in the certificate SANs. User credentials and X.509 user tokens are separate identities.

CertificateGroup combines certificate types and identities with a trust list, validator, and rejected-certificate quarantine. DefaultCertificateGroup is store-backed; DefaultCertificateGroup.forIdentity(...) wraps an existing key pair/chain with supplied trust components. Register groups under the NodeIds of their CertificateGroupType instances, such as NodeIds.ServerConfiguration_CertificateGroups_DefaultApplicationGroup, not the CertificateGroupType type-definition NodeId. new DefaultCertificateManager(group) registers that default instance automatically.

For managed certificate selection, use EndpointConfig.Builder.setEndpointCertificateConfig(EndpointCertificateConfig) to choose a registered group and optionally a certificate type. A secure endpoint with neither a fixed certificate nor a selection request selects from the DefaultApplicationGroup. The security policy determines which identities are compatible; see Security policies. Omit setCertificate(...) when the endpoint must follow certificate rotation.

The four-argument DefaultCertificateGroup constructor supports only RsaSha256ApplicationCertificateType. To use ECC, pass the intended certificate-type NodeIds through its five-argument constructor and provision matching identities. Adding an ECC endpoint does not add certificate types or generate missing material.

A provisioning workflow is:

  1. Open a KeyStoreCertificateStore and a FileBasedTrustListManager for application-owned persistent paths; choose secret handling and file permissions appropriate to deployment.
  2. Create a quarantine and DefaultServerCertificateValidator using that trust list.
  3. Construct a DefaultCertificateGroup with those components and register it in the manager.
  4. If self-provisioning, use a CertificateFactory implementation and createMissingCertificates(group) to install missing supported types. It does not replace material it cannot read. If certificates are issued externally, install them through group.updateCertificate(...) instead.
  5. Set the certificate manager and compatible endpoint security policies/modes on the server configuration. Confirm the application URI matches the certificate. Resolution omits secure endpoints with a warning for unusable providers, failed managed identity selection, or incompatible fixed certificates under enhanced policies; compare getBoundEndpoints() with the required listening endpoints after startup. See Server configuration.
  6. Establish trust at both peers and connect using the intended secure policy. A successful discovery response does not prove secure Session establishment.

ExampleServer shows component wiring. Its temporary directories, sample passwords, anonymous endpoints, and permissive X.509 user validator are demonstration choices, not a production security policy.

Trust and rejection

A trust list separates trusted peer/CA certificates, issuer certificates, and CRLs. Issuer material helps build chains but is not automatically a trusted anchor. Treat rejected certificates as evidence to inspect, not an auto-approval queue. Verify the peer identity through an independent channel before trusting it.

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.

This in-memory fragment is useful for a local validation fixture. Import all three classes from org.eclipse.milo.opcua.stack.core.security. It intentionally does not persist trust or keys:

var trustList = new MemoryTrustListManager();
var rejected = new MemoryCertificateQuarantine();
var validator = new DefaultServerCertificateValidator(trustList, rejected);

With an empty trust list, validating an unknown application certificate fails with Bad_SecurityChecksFailed and records it in quarantine. Explicitly adding the verified peer certificate permits validation; removing it causes a later validation to fail again. This exercises the trust boundary with locally generated material, not vendor interoperability.

When subclassing DefaultServerCertificateValidator, put custom checks in its four-argument validation override and preserve the profile when calling super. An override of only the older three-argument method is bypassed by SDK calls. See the validator migration note.

The two-argument validator constructor uses ValidationCheck.NO_OPTIONAL_CHECKS, which still includes the application URI check when a URI is provided. Select additional validation checks deliberately. Established revocation is always enforced; REVOCATION_LISTS additionally requires usable revocation information for CA-issued paths. Supply and refresh CRLs according to your PKI. Missing revocation data must not be confused with proof that a certificate is unrevoked.

Before trust is established the server masks detailed path errors as Bad_SecurityChecksFailed; revoked/unavailable-revocation errors are also masked. Inspect validator debug logs for the underlying cause. Once the path is trusted, other failures keep their specific status, including Bad_CertificateUriInvalid for an application URI mismatch. A client's validation of the server certificate is separate and may report specific statuses to that client application. Quarantine is per group; the manager's rejected list is the de-duplicated union.

Policies and user authentication

Choose a policy supported by both peers and by the JVM providers, certificate type, and installed key. Legacy RSA policies including Aes256_Sha256_RsaPss work with built-in Java 17 providers; enhanced ECC and RSA-DH policies have additional requirements. Supported ECC URIs have _AesGcm or _ChaChaPoly suffixes. Suffix-less ECC URIs are not aliases. See Security policies for the exact supported families and compatibility limits.

Set advertised user-token policies and the corresponding identity validator together. Use UsernameIdentityValidator for application-owned credential checks, X509IdentityValidator for a real certificate identity policy, and CompositeValidator when accepting multiple mechanisms. Protect username tokens with the advertised policy/channel requirements and do not expose real credentials on a None endpoint. Authentication must be followed by authorization.

Rotation and Push management

For an endpoint using managed selection without a fixed certificate, install a renewed identity into its group and call server.resetEndpointDescriptionCache() so later advertisements resolve current material. An endpoint built with setCertificate(X509Certificate) and no selection request keeps advertising that fixed certificate after the reset. If the certificate manager no longer contains the old identity, new secure channels naming its thumbprint fail with Bad_CertificateInvalid. Use managed selection without setCertificate(...) for endpoints you intend to rotate.

Test client rediscovery, trust updates, and reconnect. Updating a store does not by itself specify how your deployment retires established channels or Sessions. Recalculation does not rebind sockets: an endpoint newly satisfiable after provisioning may be advertised without a new listening socket; an endpoint bound at startup also stays bound if it later becomes unresolvable. Changing the bound set requires a fresh server lifecycle.

The baseline contains generated ServerConfigurationType/certificate-management Methods, but no complete production Push management service handlers. Their presence in the address space does not make UpdateCertificate, CreateSigningRequest, TrustList writes, or ApplyChanges operational. An application implementing Push must supply authorization, validation, persistence, transactional application, channel/session consequences, and auditing. For a GDS Pull workflow, use the separate GDS client integration and keep scheduling/installation under application control.

Resource ownership

Applications own groups, certificate stores, trust-list managers, quarantines, and their file watchers. Replacing/removing a group does not close its resources. Stop server activity and concurrent lookups before closing owned components; close only resources your application created. Coordinate writes from multiple store instances/processes. A keystore's atomic file replacement does not provide a cross-process lock. Persisted trust-list writes can fail and later directory reloads reconcile actual files, so monitor failures and verify the installed state.

Examples and reference

Related pages

Server SDK · Access control · Security policies · GDS client

Clone this wiki locally