跳到主要内容

证书生成与配置

前置阅读

1. 生成客户端 PFX

用 OpenSSL (推荐, 跨平台)

# 生成自签证书 + SAN URI
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"

# 转 PFX
openssl pkcs12 -export -out client.pfx -inkey key.pem -in cert.pem -password pass:123456

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

用 C# CertificateManager (复用现有工具)

如果项目里已有 C# 侧, 直接复用其 CertificateManager.GenerateSelfSigned(...), 生成的 PFX/DER 可被 Rust SDK 直接加载.


2. 服务端导入信任

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

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

3. Rust 客户端构造时传 PFX

use darra_opcua::{Session, ConnectionConfig, MessageSecurityMode};

let mut cfg = ConnectionConfig::new("opc.tcp://server:4840");
cfg.security_mode = MessageSecurityMode::SignAndEncrypt;
cfg.client_cert_path = Some("C:/certs/client.pfx".into());
cfg.client_key_path = Some("123456".into()); // PFX 密码
cfg.server_cert_path = None; // None = TOFU (Trust On First Use)
let mut s = Session::with_config(cfg)?;
s.connect()?;

4. 服务端证书 TOFU

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

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

cfg.server_cert_path = Some("C:/certs/server.der".into());

或者先 Discovery::get_endpoints 拿到 server_certificate: Vec<u8>, 落盘后传:

use std::fs::write;
use darra_opcua::Discovery;

let eps = Discovery::get_endpoints("opc.tcp://server:4840", 10_000)?;
let cert_der = &eps[0].server_certificate;
write("server.der", cert_der)?;

cfg.server_cert_path = Some("server.der".into());

故障排查

StatusCode原因解决
BAD_CERTIFICATE_INVALID证书过期 / 未生效重新生成
BAD_SECURITY_CHECKS_FAILED通用安全校验失败看服务端日志查具体原因
BAD_SECURITY_MODE_REJECTED服务端不支持该 SecurityModeDiscovery::get_endpoints 看服务端实际支持哪些
BAD_SECURITY_POLICY_REJECTED服务端不支持 Basic256Sha256服务端配置开启该 Policy

服务端常见但 SDK 未列举的:

  • BadCertificateUntrusted — 服务端没信任客户端 → 把 client.der 拷到服务端 trusted/certs/
  • BadCertificateUriInvalid — 证书 SAN.URI 与 ApplicationUri 不匹配 → 重新生成时确保一致
  • BadCertificateHostNameInvalid — 证书 SAN.DNS 与连接的 host 不匹配 → 加 subjectAltName=DNS:server.example.com

SAN URI 重要性

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

OpenSSL 验证证书 SAN:

openssl x509 -in cert.pem -text -noout | grep -A 3 "Subject Alternative Name"

应输出类似:

X509v3 Subject Alternative Name:
URI:urn:my-company:my-client

时间同步 (NTP)

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

# Windows
w32tm /resync

# Linux
sudo timedatectl set-ntp true

完整加密连接示例

use darra_opcua::{Session, ConnectionConfig, MessageSecurityMode, Discovery};

darra_opcua::initialize()?;

// 1. 拉服务端 endpoints
let eps = Discovery::get_endpoints("opc.tcp://server:4840", 10_000)?;
let ep = eps.iter()
.find(|e| e.security_mode == MessageSecurityMode::SignAndEncrypt)
.ok_or("no SignAndEncrypt endpoint")?;

// 2. 用挑出来的 Endpoint 构造
let mut cfg = ConnectionConfig::new(&ep.endpoint_url);
cfg.security_mode = ep.security_mode;
cfg.security_policy_uri = Some(ep.security_policy_uri.clone());
cfg.username = Some("operator".into());
cfg.password = Some("secret".into());
cfg.client_cert_path = Some("C:/certs/client.pfx".into());
cfg.client_key_path = Some("123456".into());

let mut s = Session::with_config(cfg)?;
s.connect()?;
println!("Connected with {:?}", ep.security_mode);

下一步