Secure connection with ECC
Wire a secured session — Basic256Sha256 with a real certificate, or ECC for the experimental path. The daemon respects --allowed-cert-dirs, so deployment discipline starts with where the certificate files live.
A secured OPC UA session needs three things: a security policy, a security mode, and a client certificate. With the session manager in the picture, two more concerns enter: where the daemon is allowed to load the certificate from, and how the configuration flows from the application to the daemon.
This recipe walks through both the standard RSA case and the ECC
variant. For production today, prefer RSA — the ECC
implementation is per-spec but unproven against commercial
servers. See opcua-client — security.
1 — Place certificates in a restricted directory
The daemon's --allowed-cert-dirs flag is the directory traversal
guard. Set it at deploy time; never point at the application's
working directory.
sudo mkdir -p /etc/opcua/certs
sudo chown opcua:opcua /etc/opcua/certs
sudo chmod 0700 /etc/opcua/certs
# Drop the client cert + key into it (RSA case here)
sudo install -m 0600 -o opcua -g opcua client.pem /etc/opcua/certs/client.pem
sudo install -m 0600 -o opcua -g opcua client.key /etc/opcua/certs/client.key
Start the daemon with the restriction:
vendor/bin/opcua-session-manager \
--socket /var/run/opcua/sessions.sock \
--allowed-cert-dirs /etc/opcua/certs \
--auth-token-file /etc/opcua/daemon.token
Any open command with a clientCertPath outside
/etc/opcua/certs/... is rejected — the daemon refuses to load
the file. This prevents the IPC peer from coercing the daemon
into loading arbitrary host files.
2 — RSA configuration on the application side
use PhpOpcua\SessionManager\Client\ManagedClient;
use PhpOpcua\Client\Security\SecurityPolicy;
use PhpOpcua\Client\Security\SecurityMode;
use PhpOpcua\Client\TrustStore\TrustPolicy;
$client = (new ManagedClient(
socketPath: '/var/run/opcua/sessions.sock',
authToken: getenv('OPCUA_AUTH_TOKEN'),
))
->setSecurityPolicy(SecurityPolicy::Basic256Sha256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setClientCertificate(
certPath: '/etc/opcua/certs/client.pem',
keyPath: '/etc/opcua/certs/client.key',
)
->setUserCredentials('integrations', getenv('OPCUA_PASSWORD'))
->setTrustStorePath('/var/lib/opcua/trust')
->setTrustPolicy(TrustPolicy::FingerprintAndExpiry)
->autoAccept(true); // TOFU — accept the server cert on first contact
$client->connect('opc.tcp://plc.local:4840');
What is happening on the wire:
- The
setClientCertificate()call records the path on theManagedClient. The path is sent to the daemon as part of theopencommand's config. - The daemon's
CommandHandlercanonicalises the path and verifies it sits under/etc/opcua/certs. Rejection raisesInvalidArgumentException("<label> is not in an allowed directory: <path>"), which reaches the client asDaemonException("[InvalidArgumentException] …"). - If accepted, the daemon's
ClientBuilderloads the cert, negotiates the OPC UABasic256Sha256 + SignAndEncryptendpoint, exchanges nonces, derives session keys. - All subsequent OPC UA traffic between the daemon and the server is signed and encrypted; the IPC traffic between the application and the daemon is not encrypted, but local trust covers it.
3 — ECC variant
The ECC policies (EccNistP256, EccNistP384, EccBrainpoolP256r1,
EccBrainpoolP384r1) work the same way at the application level
— swap the policy enum and the certificate.
# ECC client cert (NIST P-256)
openssl ecparam -name prime256v1 -genkey -noout -out client-ecc.key
openssl req -new -key client-ecc.key -out client-ecc.csr \
-subj "/CN=opcua-client"
openssl x509 -req -in client-ecc.csr \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out client-ecc.pem -days 730 \
-extfile <(printf '%s\n' \
"subjectAltName=URI:urn:opcua-client" \
"extendedKeyUsage=clientAuth,serverAuth")
sudo install -m 0600 -o opcua -g opcua client-ecc.pem /etc/opcua/certs/
sudo install -m 0600 -o opcua -g opcua client-ecc.key /etc/opcua/certs/
$client = (new ManagedClient('/var/run/opcua/sessions.sock'))
->setSecurityPolicy(SecurityPolicy::EccNistP256)
->setSecurityMode(SecurityMode::SignAndEncrypt)
->setClientCertificate(
certPath: '/etc/opcua/certs/client-ecc.pem',
keyPath: '/etc/opcua/certs/client-ecc.key',
);
$client->connect('opc.tcp://opcua-test-server:4848'); // ECC endpoint
The curve in the cert must match the policy: P-256 cert with
EccNistP256, P-384 with EccNistP384, etc. Mixing them raises
SecurityException.
Warning
ECC support is experimental in practice. The implementation
follows OPC UA 1.05.4 and is tested against UA-.NETStandard, but
no commercial OPC UA server vendor ships ECC endpoints in
production firmware. For real deployments today, stay on RSA. See
opcua-client — security.
4 — Server certificate trust
The daemon validates the server's certificate against the trust
store configured via setTrustStorePath(). Three policies:
| Policy | Decision |
|---|---|
TrustPolicy::Fingerprint |
SHA-256 fingerprint must be in the store |
TrustPolicy::FingerprintAndExpiry |
Fingerprint match + within notBefore/notAfter |
TrustPolicy::Full |
Full X.509 chain validation against the CA bundle |
For initial setup, autoAccept(true) is the TOFU shortcut — the
daemon records the server's cert on first connect and enforces it
thereafter. Disable autoAccept once you have the fingerprint
captured.
5 — Session reuse with secured sessions
Only some security fields participate in the session key (see ManagedClient · Session reuse for the full table):
securityPolicy,securityMode— in keyclientCertPath— in keyuserCertPath— in keyusername— in keytrustStorePath,trustPolicy,autoAccept,autoAcceptForce— in keypassword,clientKeyPath,caCertPath,userKeyPath— NOT in key (nulled bySessionConfig::sanitized())
Two clients with identical keyed security configuration share
the daemon session — even if they pass different password or
key-path values. Two clients with any difference in a keyed
field — even different path strings pointing at the same file —
get separate sessions. Canonicalise paths and pin
username/clientCertPath/userCertPath in your client factory.
What can go wrong
| Symptom | Cause |
|---|---|
DaemonException: [InvalidArgumentException] <label> is not in an allowed directory |
clientCertPath is outside --allowed-cert-dirs |
SecurityException: certificate parse failed |
Cert file unreadable, malformed, or wrong format |
SecurityException: curve mismatch |
ECC policy curve does not match the cert's curve |
UntrustedCertificateException |
Server cert not in trust store, autoAccept off |
| Sessions not reusing across workers | Path strings differ — canonicalise in factory |
Operational checklist
-
01
Certs in a restricted directory
owned by the daemon user,
0600mode. -
02
`--allowed-cert-dirs`
points at exactly that directory.
-
03
Trust store path
is writable by the daemon user; pre-populate the expected server certs or run
autoAcceptonce and disable. -
04
Auth token
is in
OPCUA_AUTH_TOKEN, not on the CLI. -
05
Session key fields canonicalised
in the application factory so reuse works.