585 lines
21 KiB
Go
585 lines
21 KiB
Go
package modbus
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"io"
|
|
"fmt"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// note: these certs and associated keys are self-signed
|
|
// and only meant to be used with this test.
|
|
// PLEASE DO NOT USE THEM FOR ANYTHING ELSE, EVER, as they
|
|
// do not provide any kind of security.
|
|
serverCert string = `-----BEGIN CERTIFICATE-----
|
|
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
|
|
-----END CERTIFICATE-----`
|
|
|
|
serverKey string = `-----BEGIN PRIVATE KEY-----
|
|
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
|
|
yAqUh6zMzzcee67KOWWZMTuPQuu1n/m1
|
|
-----END PRIVATE KEY-----`
|
|
|
|
clientCert string = `
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIFXzCCA0egAwIBAgIUQzQeLPGsr6OmD5NtrsiYMFVwATwwDQYJKoZIhvcNAQEL
|
|
BQAwJjEkMCIGA1UEAwwbVEVTVCBDTElFTlQgQ0VSVCBETyBOT1QgVVNFMB4XDTIw
|
|
MDgyMTA5NTI0NVoXDTQwMDgxNjA5NTI0NVowJjEkMCIGA1UEAwwbVEVTVCBDTElF
|
|
TlQgQ0VSVCBETyBOT1QgVVNFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
|
|
AgEA5yBIvheS8d2T8lBn9BOsdi1mMmhUHyqxdx9YFgwIV0NYb9s3/J83Jf/Focob
|
|
DM4fdy7iuSECX8/KUoymQmn26ivzmI4iLJ0LsBbUhTzMO9lo82Vg18E4Ab4GQVMz
|
|
LWcxnt1wt2EYJ5nq72c1h9K27pIecDDZ9DtBD1j0d3cuoo8HIVzsUfZRp80H9b5H
|
|
WuY2nMPZC3jp6HlsVSHCbkuscs/d7dDGK4tsanmyqVfBNmJhNKvo2GwMM9vf82li
|
|
dh6OwrqUNeMXkDU8vQ/xMGWfZ+Xpu0vXx3pQ5SX3WVDZFMuk7/mMBZwUhnXh+yjC
|
|
R1MVIRJvjijnkFzSSXctoysl/Mrc3QW5QmPmoa8KVWL8pSc5oaMMdX9bXo9omPf1
|
|
XlmjKMvEUu2IbUQcaDFtVAKzR0UGcYEFh/QCIu7WV0pA24DfiX3r0Lv1GHHB6X+3
|
|
+zUH7ZcaajzVQM/crCB/VLQiZODg6EgFtx1woll5hES/I6l9Me7UKySlxY4wTjIk
|
|
k/cwIN6R0dHGDny30fIkOl0vseo6SKtIkApYfe2tAASatbNdpkJQbMjUkm1IlcYj
|
|
ZZdn7yssjLjmAWn6pz19GbW2sAGQ/D4k/rN2hOpCc3NSP2FMIeWwp6TUHUG2ZFck
|
|
79j/Fo2bPNTPiFWxUW6h6gWHDgZg8UFz3FXvaeUM0URFOjsCAwEAAaOBhDCBgTAd
|
|
BgNVHQ4EFgQUZkTFhQZ4vaia2hiIQrZnSfx2nOAwHwYDVR0jBBgwFoAUZkTFhQZ4
|
|
vaia2hiIQrZnSfx2nOAwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCBaAwEwYD
|
|
VR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOC
|
|
AgEAhDVloT4TqZL66A/N8GSbiAALYVM4VoQlaiYiNrtwcNBAKrveN/RJlVxvYC9f
|
|
ybsvHz+wf/UAkWsQqd7pacKMlctNYoavpooO2ketQErpz+Ysmb+yGlL5u9DwQI36
|
|
bsw/sxZjA4uunEM3yySVZf2k5j97HzBhhv24dlgxCPyu5tOAj83bM2QLTc5H7/KZ
|
|
ZEhMcrXN0+QUI9np3WYPKAPMJNODSMGD8mMqpjRufxDH0jhPhX4R4qvhHT+/OrLE
|
|
CwLTwtgZ8BnRS2b16QEGpvT7bu5EWZda4vgXQEeuMpEgUmwPOm2JS9QZguXrhA6u
|
|
Jd/12gbNEowQCt0qig1K2/ouYc3YKvCq/GuDPZnVq0nXEgSom4+g4UpU92zHARSy
|
|
CjfEW+rD9ay0ipzl6wxV09ZoQOoFwztf/AO89gl2CDtcw1J+mB8KcP2Pme+lWZ9m
|
|
mj7+ed+lubE5kBIK/H2EojEUceGmdluqD/T6bUaAR6edLuS0z4MKFTNlbbZq9QiS
|
|
vb6vr137SqCw56gFvYzxxOS2037QHAHk9dZz4+ik6BLXOQmHY1s59y/iAV3CrWwf
|
|
wVi6BS05QtOQW1nzeUU4DyMz4aAuBs88iGqDlipzkMreyYTG/66WpKCp/nezSn5H
|
|
cufNpBGKcE0Ww/H/GgMvKe/nB7HEJQqoAxVDeq75WFiHQrs=
|
|
-----END CERTIFICATE-----
|
|
`
|
|
clientKey string = `-----BEGIN PRIVATE KEY-----
|
|
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
|
|
xmZHFMi6wnlJRHnd4CkzGGWj+WU31+z8xHlqrpWzrsRJK7ZjgfSwOW3x1FS1cesA
|
|
1/ds7JlhcXQDO/4KfjtZAZZcQuSvEAf/b/9TMU25hNXLjeLttZvVUQPSFycsP6mt
|
|
v8+pZi41bah3Pfqgp0Q9IkGcCk8JVnAbc0syYy0CggEATeNNedXh3DJmSG2ijOQX
|
|
Kbdb/asDErzFnWQd6RX/W6JG645KEfS1wo/9OBKEgIRANrP7wl3kXtxiu3EHZ5xD
|
|
obGAhSpHv6qdPvaNNIoBZvmf+I+0sNkQJ8BFTstZVslBZRsMv23D3vmNjgvUvKyr
|
|
Wa86tabN8H4ahnp4XYV4HtwTcdOqSy+Z72qcw83RWGj6owS3iOPDrCLEnihgibMd
|
|
9F726pWyyaU1Omnq4PjwEMUD67GFKBqeAQRtt2597LeNAAASB/HzGiXwPij71a2t
|
|
QijspXUDPzDwqAzI0D5tkSxT/+gNwL5ilpVQwx1bOdhOP6RoJVEnz83GYvsOBN+F
|
|
EQKCAQEAo9j9MG+VCz+loz4fUXIJjC63ypfuRfxTCAIBMn4HzohP8chEcQBlWLCH
|
|
t0WcguYnwsuxGR4Rhx02UZCx3qNxiroBZ9w1NqTk947ZjKuNzqI7IpIqvtJ18op6
|
|
QgQu8piNkf0/etAO0e6IjbZe4WfJCeKsAqE4vCV43baaSiHN/0pfYi6LLJ2YmTF/
|
|
+sYY43naHg3zQTL4JbL4c58ebe4ADj4wIdNJ+/H5JgQf6r14iNjpyc6BJOjFuPyx
|
|
EJHQKb6499HKFua3QuH/kA6Ogfm9o3Lnwx/VO1lPLFteTv1fBKK00C00SkmyIe1p
|
|
iaKCVjivzjP1s/q6adzOOZVlVwm7Xw==
|
|
-----END PRIVATE KEY-----
|
|
`
|
|
)
|
|
|
|
// TestTCPOVerTLSClient tests the TLS layer of the modbus client.
|
|
func TestTCPoverTLSClient(t *testing.T) {
|
|
var err error
|
|
var client *ModbusClient
|
|
var serverKeyPair tls.Certificate
|
|
var clientKeyPair tls.Certificate
|
|
var clientCp *x509.CertPool
|
|
var serverCp *x509.CertPool
|
|
var serverHostPort string
|
|
var serverChan chan string
|
|
var regs []uint16
|
|
|
|
serverChan = make(chan string)
|
|
|
|
// load server and client keypairs
|
|
serverKeyPair, err = tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
|
|
if err != nil {
|
|
t.Errorf("failed to load test server key pair: %v", err)
|
|
return
|
|
}
|
|
|
|
clientKeyPair, err = tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
|
if err != nil {
|
|
t.Errorf("failed to load test client key pair: %v", err)
|
|
return
|
|
}
|
|
|
|
// start with an empty client cert pool initially to reject the server
|
|
// certificate
|
|
clientCp = x509.NewCertPool()
|
|
|
|
// start with an empty server cert pool initially to reject the client
|
|
// certificate
|
|
serverCp = x509.NewCertPool()
|
|
|
|
// start a mock modbus TLS server
|
|
go runMockTLSServer(t, serverKeyPair, serverCp, serverChan)
|
|
|
|
// wait for the test server goroutine to signal its readiness
|
|
// and network location
|
|
serverHostPort = <-serverChan
|
|
|
|
// attempt to create a client without specifying any TLS configuration
|
|
// parameter: should fail
|
|
client, err = NewClient(&ClientConfiguration{
|
|
URL: fmt.Sprintf("tcp+tls://%s", serverHostPort),
|
|
})
|
|
if err != ErrConfigurationError {
|
|
t.Errorf("NewClient() should have failed with %v, got: %v",
|
|
ErrConfigurationError, err)
|
|
}
|
|
|
|
// attempt to create a client without specifying any TLS server
|
|
// cert/CA: should fail
|
|
client, err = NewClient(&ClientConfiguration{
|
|
URL: fmt.Sprintf("tcp+tls://%s", serverHostPort),
|
|
TLSClientCert: &clientKeyPair,
|
|
})
|
|
if err != ErrConfigurationError {
|
|
t.Errorf("NewClient() should have failed with %v, got: %v",
|
|
ErrConfigurationError, err)
|
|
}
|
|
|
|
// attempt to create a client with both client cert+key and server
|
|
// cert/CA: should succeed
|
|
client, err = NewClient(&ClientConfiguration{
|
|
URL: fmt.Sprintf("tcp+tls://%s", serverHostPort),
|
|
TLSClientCert: &clientKeyPair,
|
|
TLSRootCAs: clientCp,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("NewClient() should have succeeded, got: %v", err)
|
|
}
|
|
|
|
// connect to the server: should fail with a TLS error as the server cert
|
|
// is not yet trusted by the client
|
|
err = client.Open()
|
|
if err == nil {
|
|
t.Errorf("Open() should have failed")
|
|
}
|
|
|
|
// now load the server certificate into the client's trusted cert pool
|
|
// to get the client to accept the server's certificate
|
|
if !clientCp.AppendCertsFromPEM([]byte(serverCert)) {
|
|
t.Errorf("failed to load test server cert into cert pool")
|
|
}
|
|
|
|
// connect to the server: should succeed
|
|
// note: client certificates are verified after the handshake procedure
|
|
// has completed, so Open() won't fail even though the client cert
|
|
// is rejected by the server.
|
|
// (see RFC 8446 section 4.6.2 Post Handshake Authentication)
|
|
err = client.Open()
|
|
if err != nil {
|
|
t.Errorf("Open() should have succeeded, got: %v", err)
|
|
}
|
|
|
|
// attempt to read two registers: since the client cert won't pass
|
|
// the validation step yet (no cert in server cert pool),
|
|
// expect a tls error
|
|
regs, err = client.ReadRegisters(0x1000, 2, INPUT_REGISTER)
|
|
if err == nil {
|
|
t.Errorf("ReadRegisters() should have failed")
|
|
}
|
|
client.Close()
|
|
|
|
// now place the client cert in the server's authorized client list
|
|
// to get the client cert past the validation procedure
|
|
if !serverCp.AppendCertsFromPEM([]byte(clientCert)) {
|
|
t.Errorf("failed to load test client cert into cert pool")
|
|
}
|
|
|
|
// connect to the server: should succeed
|
|
err = client.Open()
|
|
if err != nil {
|
|
t.Errorf("Open() should have succeeded, got: %v", err)
|
|
}
|
|
|
|
// attempt to read two registers: should succeed
|
|
regs, err = client.ReadRegisters(0x1000, 2, INPUT_REGISTER)
|
|
if err != nil {
|
|
t.Errorf("ReadRegisters() should have succeeded, got: %v", err)
|
|
}
|
|
if regs[0] != 0x1234 {
|
|
t.Errorf("expected 0x1234 in 1st reg, saw: 0x%04x", regs[0])
|
|
}
|
|
if regs[1] != 0x5678 {
|
|
t.Errorf("expected 0x5678 in 2nd reg, saw: 0x%04x", regs[1])
|
|
}
|
|
|
|
// attempt to read another: should succeed
|
|
regs, err = client.ReadRegisters(0x1002, 1, HOLDING_REGISTER)
|
|
if err != nil {
|
|
t.Errorf("ReadRegisters() should have succeeded, got: %v", err)
|
|
}
|
|
if regs[0] != 0xaabb {
|
|
t.Errorf("expected 0xaabb in 1st reg, saw: 0x%04x", regs[0])
|
|
}
|
|
|
|
// close the connection: should succeed
|
|
err = client.Close()
|
|
if err != nil {
|
|
t.Errorf("Close() should have succeeded, got: %v", err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func TestTLSClientOnServerTimeout(t *testing.T) {
|
|
var err error
|
|
var client *ModbusClient
|
|
var server *ModbusServer
|
|
var serverKeyPair tls.Certificate
|
|
var clientKeyPair tls.Certificate
|
|
var clientCp *x509.CertPool
|
|
var serverCp *x509.CertPool
|
|
var th *tlsTestHandler
|
|
var reg uint16
|
|
|
|
th = &tlsTestHandler{}
|
|
// load server and client keypairs
|
|
serverKeyPair, err = tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
|
|
if err != nil {
|
|
t.Errorf("failed to load test server key pair: %v", err)
|
|
return
|
|
}
|
|
|
|
clientKeyPair, err = tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
|
|
if err != nil {
|
|
t.Errorf("failed to load test client key pair: %v", err)
|
|
return
|
|
}
|
|
|
|
// add those keypairs to their corresponding cert pool
|
|
clientCp = x509.NewCertPool()
|
|
if !clientCp.AppendCertsFromPEM([]byte(serverCert)) {
|
|
t.Errorf("failed to load test server cert into cert pool")
|
|
}
|
|
|
|
serverCp = x509.NewCertPool()
|
|
if !serverCp.AppendCertsFromPEM([]byte(clientCert)) {
|
|
t.Errorf("failed to load client cert into cert pool")
|
|
}
|
|
|
|
|
|
// load the server cert into the client CA cert pool to get the server cert
|
|
// accepted by clients
|
|
clientCp = x509.NewCertPool()
|
|
if !clientCp.AppendCertsFromPEM([]byte(serverCert)) {
|
|
t.Errorf("failed to load test server cert into cert pool")
|
|
}
|
|
|
|
server, err = NewServer(&ServerConfiguration{
|
|
URL: "tcp+tls://[::1]:5802",
|
|
MaxClients: 10,
|
|
TLSServerCert: &serverKeyPair,
|
|
TLSClientCAs: serverCp,
|
|
// disconnect idle clients after 500ms
|
|
Timeout: 500 * time.Millisecond,
|
|
}, th)
|
|
if err != nil {
|
|
t.Errorf("failed to create server: %v", err)
|
|
}
|
|
|
|
err = server.Start()
|
|
if err != nil {
|
|
t.Errorf("failed to start server: %v", err)
|
|
}
|
|
|
|
// create the modbus client
|
|
client, err = NewClient(&ClientConfiguration{
|
|
URL: "tcp+tls://localhost:5802",
|
|
TLSClientCert: &clientKeyPair,
|
|
TLSRootCAs: clientCp,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("failed to create client: %v", err)
|
|
}
|
|
|
|
// connect to the server: should succeed
|
|
err = client.Open()
|
|
if err != nil {
|
|
t.Errorf("Open() should have succeeded, got: %v", err)
|
|
}
|
|
|
|
// write a value to register #3: should succeed
|
|
err = client.WriteRegister(3, 0x0199)
|
|
if err != nil {
|
|
t.Errorf("Write() should have succeeded, got: %v", err)
|
|
}
|
|
|
|
// attempt to read the value back: should succeed
|
|
reg, err = client.ReadRegister(3, HOLDING_REGISTER)
|
|
if err != nil {
|
|
t.Errorf("ReadRegisters() should have succeeded, got: %v", err)
|
|
}
|
|
if reg != 0x0199 {
|
|
t.Errorf("expected 0x0199 in reg #3, saw: 0x%04x", reg)
|
|
}
|
|
|
|
// pause for longer than the server's configured timeout to end up with
|
|
// an open client with a closed underlying TCP socket
|
|
time.Sleep(1 * time.Second)
|
|
|
|
// attempt a read: should fail
|
|
_, err = client.ReadRegister(3, INPUT_REGISTER)
|
|
if err == nil {
|
|
t.Errorf("ReadRegister() should have failed")
|
|
}
|
|
|
|
// cleanup
|
|
client.Close()
|
|
server.Stop()
|
|
|
|
return
|
|
}
|
|
|
|
// runMockTLSServer spins a test TLS server for use with TestTCPoverTLSClient.
|
|
func runMockTLSServer(t *testing.T, serverKeyPair tls.Certificate,
|
|
serverCp *x509.CertPool, serverChan chan string) {
|
|
var err error
|
|
var listener net.Listener
|
|
var sock net.Conn
|
|
var reqCount uint
|
|
var clientCount uint
|
|
var buf []byte
|
|
|
|
// let the OS pick an available port on the loopback interface
|
|
listener, err = tls.Listen("tcp", "localhost:0", &tls.Config{
|
|
// the server will use serverKeyPair (key+cert) to
|
|
// authenticate to the client
|
|
Certificates: []tls.Certificate{serverKeyPair},
|
|
// the server will use the certpool to authenticate the
|
|
// client-side cert
|
|
ClientCAs: serverCp,
|
|
// request client-side authentication and client cert validation
|
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
|
})
|
|
if err != nil {
|
|
t.Errorf("failed to start test server listener: %v", err)
|
|
}
|
|
defer listener.Close()
|
|
|
|
// let the main test goroutine know which port the OS picked
|
|
serverChan <- listener.Addr().String()
|
|
|
|
for err == nil {
|
|
// accept client connections
|
|
sock, err = listener.Accept()
|
|
if err != nil {
|
|
t.Errorf("failed to accept client conn: %v", err)
|
|
break
|
|
}
|
|
|
|
// only proceed with clients passing the tls handshake
|
|
// note: this will reject any client whose cert does not pass the
|
|
// verification step
|
|
err = sock.(*tls.Conn).Handshake()
|
|
if err != nil {
|
|
sock.Close()
|
|
err = nil
|
|
continue
|
|
}
|
|
|
|
clientCount++
|
|
if clientCount > 2 {
|
|
t.Errorf("expected 2 client conns, saw: %v", clientCount)
|
|
}
|
|
|
|
// expect MBAP (modbus/tcp) messages inside the TLS tunnel
|
|
for {
|
|
// expect 12 bytes per request
|
|
buf = make([]byte, 12)
|
|
|
|
_, err = sock.Read(buf)
|
|
if err != nil {
|
|
// ignore EOF errors (clients disconnecting)
|
|
if err != io.EOF {
|
|
t.Errorf("failed to read client request: %v", err)
|
|
}
|
|
sock.Close()
|
|
break
|
|
}
|
|
|
|
reqCount++
|
|
switch reqCount {
|
|
case 1:
|
|
for i, b := range []byte{
|
|
0x00, 0x01, // txn id
|
|
0x00, 0x00, // protocol id
|
|
0x00, 0x06, // length
|
|
0x01, 0x04, // unit id + function code
|
|
0x10, 0x00, // start address
|
|
0x00, 0x02, // quantity
|
|
} {
|
|
if b != buf[i] {
|
|
t.Errorf("expected 0x%02x at pos %v, saw 0x%02x",
|
|
b, i, buf[i])
|
|
}
|
|
}
|
|
|
|
// send a reply
|
|
_, err = sock.Write([]byte{
|
|
0x00, 0x01, // txn id
|
|
0x00, 0x00, // protocol id
|
|
0x00, 0x07, // length
|
|
0x01, 0x04, // unit id + function code
|
|
0x04, // byte count
|
|
0x12, 0x34, // reg #0
|
|
0x56, 0x78, // reg #1
|
|
})
|
|
if err != nil {
|
|
t.Errorf("failed to write reply: %v", err)
|
|
}
|
|
|
|
case 2:
|
|
for i, b := range []byte{
|
|
0x00, 0x02, // txn id
|
|
0x00, 0x00, // protocol id
|
|
0x00, 0x06, // length
|
|
0x01, 0x03, // unit id + function code
|
|
0x10, 0x02, // start address
|
|
0x00, 0x01, // quantity
|
|
} {
|
|
if b != buf[i] {
|
|
t.Errorf("expected 0x%02x at pos %v, saw 0x%02x",
|
|
b, i, buf[i])
|
|
}
|
|
}
|
|
|
|
// send a reply
|
|
_, err = sock.Write([]byte{
|
|
0x00, 0x02, // txn id
|
|
0x00, 0x00, // protocol id
|
|
0x00, 0x05, // length
|
|
0x01, 0x03, // unit id + function code
|
|
0x02, // byte count
|
|
0xaa, 0xbb, // reg #0
|
|
})
|
|
if err != nil {
|
|
t.Errorf("failed to write reply: %v", err)
|
|
}
|
|
|
|
// stop the server after the 2nd request
|
|
listener.Close()
|
|
|
|
default:
|
|
t.Errorf("unexpected request id %v", reqCount)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|