보안 연결 문제
Modbus/TCP Security의 실패는 세 단계에서 납니다. 메시지의 앞부분으로 단계를 구분합니다.
| 단계 | 메시지 형태 | 언제 |
|---|---|---|
| 인증서 파일 읽기 | Config(...), Crypto(...), Io(...) |
connect에서 TLS를 시작하기 전 |
| TLS 핸드셰이크 | Security("Handshake(...)") |
connect 중 |
| 연결 뒤 | Io(Other, "...SSL alert ..."), 예외 01 |
첫 요청이나 권한 검사에서 |
PDComm은 보안 연결이 실패해도 평문 TCP나 인증 전용 모드로 낮추지 않습니다. 인증서 검증도 끌 수 없습니다.
인증서 파일
섹션 제목: “인증서 파일”| 메시지 | 원인과 해결 |
|---|---|
Config("Chain must include its root CA") |
chain 파일이 루트 CA로 끝나지 않습니다. 자기 인증서, 중간 CA, 루트 CA 순서로 이어 붙입니다. |
Config("Device certificate must be X509v3") |
장치 인증서가 X.509 v1입니다. v3으로 다시 발급합니다. OpenSSL 3의 x509 -req는 기본으로 v3을 만듭니다. |
Config("Empty certificate chain") |
chain 파일에 인증서가 없습니다. |
Config("Revocation checking requires CRLs") |
require_revocation이 true인데 crls가 비어 있습니다. |
Config("Cannot load CRL") |
CRL 파일을 읽을 수 없거나 형식이 틀렸습니다. |
Crypto(ErrorStack([... "calling fopen(<경로>, r)" ...])) |
private_key나 trust 경로의 파일이 없습니다. 메시지 안의 경로를 확인합니다. |
Crypto(ErrorStack([... reason: "key values mismatch" ...])) |
private_key가 chain의 첫 인증서와 짝이 아닙니다. |
Io(Os { code: 2, kind: NotFound, message: "No such file or directory" }) |
chain 파일이 없습니다. |
Invalid Security configuration |
chain, private_key, trust 중 빈 값이 있거나 mode가 틀렸습니다. 직렬 구성에 security를 넣어도 이 오류입니다. |
TLS 핸드셰이크
섹션 제목: “TLS 핸드셰이크”| 메시지 | 원인과 해결 |
|---|---|
Security("Handshake(\"error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:...\")") |
클라이언트가 서버 인증서를 검증하지 못했습니다. 아래 표를 봅니다. |
Security("Handshake(\"...ssl/tls alert handshake failure...SSL alert number 40\")") |
양쪽의 mode가 다르거나 공통 암호 방식이 없습니다. 양쪽을 같은 모드로 맞춥니다. authentication_only는 RSA 인증서가 필요합니다. |
Security("Handshake(\"unexpected EOF\")") |
상대가 TLS를 쓰지 않습니다. 평문 Modbus TCP 서버에 보안 클라이언트로 접속했습니다. |
Io(ConnectionRefused, ...) |
서버가 그 포트에서 기다리지 않습니다. 보안 서버의 포트(예: 1802, 802)를 확인합니다. |
certificate verify failed의 흔한 원인은 다음과 같습니다.
| 원인 | 확인 |
|---|---|
| 호스트 이름 불일치 | 클라이언트의 hostname(기본 localhost)이 서버 인증서 SAN에 있어야 합니다. IP로 접속하면 hostname에 그 IP를 넣고 SAN에도 IP: 항목이 있어야 합니다. |
| 신뢰하지 않는 CA | 클라이언트의 trust가 서버 인증서를 발급한 CA여야 합니다. |
| 만료되었거나 아직 유효하지 않은 인증서 | openssl x509 -in server.pem -noout -dates로 유효 기간과 PC 시계를 확인합니다. |
| v3이 아니거나 역할 확장이 잘못된 인증서 | 장치 인증서는 X.509v3이어야 하고, 역할 확장은 UTF8String 하나여야 합니다. |
서버 쪽에서 클라이언트 인증서 검증이 실패하면, TLS 1.3에서는 클라이언트의 connect가 먼저 성공하고 첫 요청에서 실패합니다(다음 절).
연결 뒤의 실패
섹션 제목: “연결 뒤의 실패”| 메시지 | 원인과 해결 |
|---|---|
Io(Other, "error:0A000414:SSL routines:ssl3_read_bytes:ssl/tls alert certificate revoked:...SSL alert number 44") |
서버가 CRL에서 클라이언트 인증서의 폐기를 확인했습니다. 새 인증서를 발급받습니다. |
Io(Other, "error:0A000418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:...SSL alert number 48") |
서버가 클라이언트 인증서를 발급한 CA를 신뢰하지 않습니다. 서버의 trust가 클라이언트 인증서를 발급한 CA인지 확인합니다. |
Io(ConnectionReset, "Connection reset by peer (os error 54)") |
평문 클라이언트가 보안 서버에 접속했습니다. 클라이언트에 security를 넣습니다. |
응답이 예외 01(예: [134,1]) |
TLS는 정상이지만 서버의 역할 정책이 그 기능을 허용하지 않습니다. 서버의 roles와 null_role, 클라이언트 인증서의 역할을 확인합니다. |
이런 실패 뒤 state의 connected는 false입니다. 원인을 고친 뒤 다시 connect합니다.
역할 확인
섹션 제목: “역할 확인”클라이언트 인증서의 역할은 다음 명령으로 봅니다.
openssl x509 -in reader.pem -noout -text | grep -A1 '50316' 1.3.6.1.4.1.50316.802.1: ..reader- 확장이 없으면 서버는
null_role을 적용합니다. - 역할 이름은 대소문자를 구분하며, 값 전체가 이름 하나입니다.
reader,operator는 역할 두 개가 아니라reader,operator라는 이름 하나입니다.
인증서 만들기부터 다시 확인하려면 Modbus/TCP Security 튜토리얼을 따릅니다. 문의할 때는 개인 키를 보내지 않고, 인증서의 지문과 유효 기간, 오류 메시지만 보냅니다.