跳到主要内容

证书生成与配置

前置阅读

1. 生成客户端 PFX

用 OpenSSL (推荐, 跨平台一致)

# 1. 生成私钥 + 自签证书 (PEM)
openssl req -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=MyClient/O=Darra/C=CN" \
-addext "subjectAltName=URI:urn:my-company:my-client"

# 2. 转 PFX (PKCS#12, 含私钥+公钥+证书链)
openssl pkcs12 -export -out client.pfx -inkey key.pem -in cert.pem \
-password pass:123456

# 3. 导出 DER 格式公钥 (供服务端导入信任)
openssl x509 -in cert.pem -outform DER -out client.der

用 keytool (JDK 自带)

keytool -genkeypair -alias darra-client \
-keyalg RSA -keysize 2048 -validity 365 \
-dname "CN=MyClient, O=Darra, C=CN" \
-ext "san=uri:urn:my-company:my-client" \
-keystore client.pfx -storetype PKCS12 \
-storepass 123456 -keypass 123456

2. 服务端导入信任

把客户端公钥 (client.derclient.pem) 拷贝到服务端的"受信任客户端证书"目录:

Server路径
Darra SimulatorServer<simulator>/pki/trusted/certs/
open62541 example<server>/pki/trusted/certs/
UaExpert设置 → Certificates → Trust

3. 客户端构造时传 PFX

简化 — 加密快捷工厂 (推荐)

import com.darra.opcua.*;

try (OpcUaSession ua = OpcUaSession.secureSignAndEncrypt(
"opc.tcp://server:4840", "C:\\certs\\client.pfx")) {
ua.connect();
}

完整构造

try (OpcUaSession ua = new OpcUaSession(
"opc.tcp://server:4840",
Enums.MessageSecurityMode.SignAndEncrypt,
/* user */ null, /* password */ null,
/* sessionTimeoutMs */ 600_000,
/* requestTimeoutMs */ 10_000,
/* connectTimeoutMs */ 10_000,
/* keepAliveIntervalMs */ 10_000,
/* autoReconnect */ true,
/* autoPublishOnConnect */ true,
/* clientCertPath */ "C:\\certs\\client.pfx",
/* clientKeyPath */ "123456", // PFX 密码
/* serverCertPath */ null, // null = TOFU
/* securityPolicyUri */ null // null = 自动 Basic256Sha256
)) {
ua.connect();
}

4. 服务端证书 TOFU

第一次连接时, 客户端会信任服务端证书并保存指纹. 之后每次连接校验指纹一致, 不一致 (中间人) 拒绝.

如果不想 TOFU, 显式传服务端证书 DER 路径:

new OpcUaSession(endpoint, mode, user, pwd,
600_000, 10_000, 10_000, 10_000, true, true,
"client.pfx", "123456",
"C:\\certs\\server.der", // serverCertPath 显式指定
null);

故障排查

StatusCode原因解决
BadCertificateUntrusted服务端没信任客户端把 client.der 拷到服务端 trusted/certs/
BadCertificateInvalid证书过期 / 未生效重新生成
BadCertificateUriInvalid证书 SAN.URI 与 ApplicationUri 不匹配重新生成时确保 applicationUri 一致
BadCertificateHostNameInvalid证书 SAN.DNS 与连接的 host 不匹配生成时加 subjectAltName=DNS:server.example.com
BadSecurityChecksFailed通用安全校验失败看服务端日志查具体原因
BadSecurityModeRejected服务端不支持该 SecurityModeOpcUaDiscovery.getEndpoints 看服务端实际支持哪些
BadSecurityPolicyRejected服务端不支持 Basic256Sha256服务端配置开启该 Policy

SAN URI 重要性

OPC UA 校验客户端证书 SAN 中必须有 URI: 字段, 且与 ApplicationUri 一致. 这是防伪造的关键, 必须正确设置.

用 OpenSSL 验证 PFX:

# 看证书内容
openssl pkcs12 -in client.pfx -nokeys -password pass:123456 | openssl x509 -text

# 应该能看到:
# X509v3 Subject Alternative Name:
# URI:urn:my-company:my-client

或用 keytool:

keytool -list -v -keystore client.pfx -storepass 123456

时间同步 (NTP)

加密模式下双方校验时间戳, 偏差超过 ~10 分钟会被拒绝. 生产环境必须配 NTP, 时间偏差控制在秒级.

# Windows
w32tm /resync

# Linux
sudo timedatectl set-ntp true

完整示例 — 用 GetEndpoints 自动挑加密 endpoint

import com.darra.opcua.*;
import java.util.List;

public class SecureConnectDemo {
public static void main(String[] args) {
// 1. Discovery
List<EndpointDescription> eps =
OpcUaDiscovery.getEndpoints("opc.tcp://server:4840");

// 2. 挑 SignAndEncrypt + Basic256Sha256
EndpointDescription pick = eps.stream()
.filter(ep -> ep.getSecurityMode() == Enums.MessageSecurityMode.SignAndEncrypt
&& ep.getSecurityPolicyUri() != null
&& ep.getSecurityPolicyUri().endsWith("#Basic256Sha256"))
.findFirst()
.orElse(null);

if (pick == null) {
System.err.println("No suitable encrypted endpoint");
return;
}

// 3. 加密连接
try (OpcUaSession ua = OpcUaSession.secureSignAndEncrypt(
pick.getEndpointUrl(), "C:\\certs\\client.pfx")) {
ua.connect();
System.out.println("Connected with " + ua.getSecurityMode());
} catch (OpcUaException ex) {
System.err.println("Secure connect failed: " + ex.statusCode);
}
}
}

下一步