-
Notifications
You must be signed in to change notification settings - Fork 96
/
Copy pathserver_tls_test.go
572 lines (508 loc) · 17 KB
/
server_tls_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
package modbus
import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"testing"
"time"
)
const (
clientCertWithRoleOID string = `
-----BEGIN CERTIFICATE-----
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
hG79y2z0UfKJstN+
-----END CERTIFICATE-----
`
clientKeyWithRoleOID string = `
-----BEGIN PRIVATE KEY-----
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
isPLG4c6aPGxSbHirNfl6tBSngDy+A==
-----END PRIVATE KEY-----
`
)
// TestTLSServer tests the TLS layer of the modbus server.
func TestTLSServer(t *testing.T) {
var err error
var server *ModbusServer
var serverKeyPair tls.Certificate
var client1KeyPair tls.Certificate
var client2KeyPair tls.Certificate
var clientCp *x509.CertPool
var serverCp *x509.CertPool
var th *tlsTestHandler
var c1 *ModbusClient
var c2 *ModbusClient
var regs []uint16
var coils []bool
th = &tlsTestHandler{}
// load server keypair (from client_tls_test.go)
serverKeyPair, err = tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
if err != nil {
t.Errorf("failed to load test server key pair: %v", err)
return
}
// load the first client keypair (from client_tls_test.go)
// this client cert doesn't have any Modbus Role extension
client1KeyPair, err = tls.X509KeyPair([]byte(clientCert), []byte(clientKey))
if err != nil {
t.Errorf("failed to load test client key pair: %v", err)
return
}
// load the second client keypair (defined above)
// this client cert has an "operator2" Modbus Role extension
client2KeyPair, err = tls.X509KeyPair(
[]byte(clientCertWithRoleOID), []byte(clientKeyWithRoleOID))
if err != nil {
t.Errorf("failed to load test client key pair: %v", err)
return
}
// 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")
}
// start with an empty server cert pool initially to reject the client
// certificate
serverCp = x509.NewCertPool()
server, err = NewServer(&ServerConfiguration{
URL: "tcp+tls://localhost:5802",
MaxClients: 2,
TLSServerCert: &serverKeyPair,
TLSClientCAs: serverCp,
}, 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 2 modbus clients
c1, err = NewClient(&ClientConfiguration{
URL: "tcp+tls://localhost:5802",
TLSClientCert: &client1KeyPair,
TLSRootCAs: clientCp,
})
if err != nil {
t.Errorf("failed to create client: %v", err)
}
c2, err = NewClient(&ClientConfiguration{
URL: "tcp+tls://localhost:5802",
TLSClientCert: &client2KeyPair,
TLSRootCAs: clientCp,
})
if err != nil {
t.Errorf("failed to create client: %v", err)
}
// attempt to connect and use the first client. since its cert
// is not trusted by the server, a TLS error should occur on the first
// request.
err = c1.Open()
if err != nil {
t.Errorf("c1.Open() should have succeeded")
}
coils, err = c1.ReadCoils(0, 5)
if err == nil {
t.Error("c1.ReadCoils() should have failed")
}
c1.Close()
// now place both client certs in the server's authorized client list
// to get them past the TLS client cert validation procedure
if !serverCp.AppendCertsFromPEM([]byte(clientCert)) {
t.Errorf("failed to load client#1 cert into cert pool")
}
if !serverCp.AppendCertsFromPEM([]byte(clientCertWithRoleOID)) {
t.Errorf("failed to load client#2 cert into cert pool")
}
// connect both clients: should succeed
err = c1.Open()
if err != nil {
t.Error("c1.Open() should have succeeded")
}
err = c2.Open()
if err != nil {
t.Error("c2.Open() should have succeeded")
}
// client #2 (with 'operator2' role) should have read/write access to coils while
// client #1 (without role) should only be able to read.
err = c1.WriteCoil(0, true)
if err != ErrIllegalFunction {
t.Errorf("c1.WriteCoil() should have failed with %v, got: %v",
ErrIllegalFunction, err)
}
coils, err = c1.ReadCoils(0, 5)
if err != nil {
t.Errorf("c1.ReadCoils() should have succeeded, got: %v", err)
}
if coils[0] {
t.Errorf("coils[0] should have been false")
}
err = c2.WriteCoil(0, true)
if err != nil {
t.Errorf("c2.WriteCoil() should have succeeded, got: %v", err)
}
coils, err = c2.ReadCoils(0, 5)
if err != nil {
t.Errorf("c2.ReadCoils() should have succeeded, got: %v", err)
}
if !coils[0] {
t.Errorf("coils[0] should have been true")
}
coils, err = c1.ReadCoils(0, 5)
if err != nil {
t.Errorf("c1.ReadCoils() should have succeeded, got: %v", err)
}
if !coils[0] {
t.Errorf("coils[0] should have been true")
}
// client #1 should only be allowed access to holding registers of unit id #1
// while client#2 should be allowed access to holding registers of unit ids #1 and #4
c1.SetUnitId(1)
err = c1.WriteRegister(2, 100)
if err != nil {
t.Errorf("c1.WriteRegister() should have succeeded, got: %v", err)
}
c1.SetUnitId(4)
err = c1.WriteRegister(2, 200)
if err != ErrIllegalFunction {
t.Errorf("c1.WriteRegister() should have failed with %v, got: %v",
ErrIllegalFunction, err)
}
c2.SetUnitId(1)
regs, err = c2.ReadRegisters(1, 2, HOLDING_REGISTER)
if err != nil {
t.Errorf("c2.ReadRegisters() should have succeeded, got: %v", err)
}
if regs[0] != 0 || regs[1] != 100 {
t.Errorf("unexpected register values: %v", regs)
}
c2.SetUnitId(4)
err = c2.WriteRegister(2, 200)
if err != nil {
t.Errorf("c2.WriteRegister() should have succeeded, got: %v", err)
}
regs, err = c2.ReadRegisters(1, 2, HOLDING_REGISTER)
if err != nil {
t.Errorf("c2.ReadRegisters() should have succeeded, got: %v", err)
}
if regs[0] != 0 || regs[1] != 200 {
t.Errorf("unexpected register values: %v", regs)
}
// close the server and all client connections
server.Stop()
// make sure all underlying TCP client connections have been freed
time.Sleep(10 * time.Millisecond)
server.lock.Lock()
if len(server.tcpClients) != 0 {
t.Errorf("expected 0 client connections, saw: %v", len(server.tcpClients))
}
server.lock.Unlock()
// cleanup
c1.Close()
c2.Close()
return
}
type tlsTestHandler struct {
coils [10]bool
holdingId1 [10]uint16
holdingId4 [10]uint16
}
func (th *tlsTestHandler) HandleCoils(req *CoilsRequest) (res []bool, err error) {
// coils access is allowed to any client with a valid cert, but
// the "operator2" role is required to write
if req.IsWrite && req.ClientRole != "operator2" {
err = ErrIllegalFunction
return
}
if req.Addr + req.Quantity > uint16(len(th.coils)) {
err = ErrIllegalDataAddress
return
}
for i := 0; i < int(req.Quantity); i++ {
if req.IsWrite {
th.coils[int(req.Addr) + i] = req.Args[i]
}
res = append(res, th.coils[int(req.Addr) + i])
}
return
}
func (th *tlsTestHandler) HandleDiscreteInputs(req *DiscreteInputsRequest) (res []bool, err error) {
// there are no digital inputs on this device
err = ErrIllegalDataAddress
return
}
func (th *tlsTestHandler) HandleHoldingRegisters(req *HoldingRegistersRequest) (res []uint16, err error) {
// gate unit id #4 behind the "operator2" role while access to unit id #1
// is allowed to any valid cert
if req.UnitId == 0x04 {
if req.ClientRole != "operator2" {
err = ErrIllegalFunction
return
}
if req.Addr + req.Quantity > uint16(len(th.holdingId4)) {
err = ErrIllegalDataAddress
return
}
for i := 0; i < int(req.Quantity); i++ {
if req.IsWrite {
th.holdingId4[int(req.Addr) + i] = req.Args[i]
}
res = append(res, th.holdingId4[int(req.Addr) + i])
}
} else if req.UnitId == 0x01 {
if req.Addr + req.Quantity > uint16(len(th.holdingId1)) {
err = ErrIllegalDataAddress
return
}
for i := 0; i < int(req.Quantity); i++ {
if req.IsWrite {
th.holdingId1[int(req.Addr) + i] = req.Args[i]
}
res = append(res, th.holdingId1[int(req.Addr) + i])
}
} else {
err = ErrIllegalFunction
return
}
return
}
func (th *tlsTestHandler) HandleInputRegisters(req *InputRegistersRequest) (res []uint16, err error) {
// there are no inputs registers on this device
err = ErrIllegalDataAddress
return
}
func TestServerExtractRole(t *testing.T) {
var ms *ModbusServer
var pemBlock *pem.Block
var x509Cert *x509.Certificate
var err error
var role string
ms = &ModbusServer{
logger: newLogger("test-server-role-extraction", nil),
}
// load a client cert without role OID
pemBlock, _ = pem.Decode([]byte(clientCert))
if err != nil {
t.Errorf("failed to decode client cert: %v", err)
return
}
x509Cert, err = x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Errorf("failed to parse client cert: %v", err)
return
}
// calling extractRole on a cert without role extension should return an
// empty string (see R-23 of the MBAPS spec)
role = ms.extractRole(x509Cert)
if role != "" {
t.Errorf("role should have been empty, got: '%s'", role)
}
// load a certificate with a single role extension of "operator2"
pemBlock, _ = pem.Decode([]byte(clientCertWithRoleOID))
if err != nil {
t.Errorf("failed to decode client cert: %v", err)
return
}
x509Cert, err = x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Errorf("failed to parse client cert: %v", err)
return
}
role = ms.extractRole(x509Cert)
if role != "operator2" {
t.Errorf("role should have been 'operator2', got: '%s'", role)
}
// build a certificate with multiple Modbus Role extensions: they should
// all be rejected
x509Cert = &x509.Certificate{
Extensions: []pkix.Extension{
{
Id: modbusRoleOID,
Value: []byte{
0x0c, 0x04, 0x66, 0x77, 0x67, 0x78,
// ^ ASN1:UTF8String
// ^ length
// ^ 4-byte string 'fwgx'
},
},
{
Id: modbusRoleOID,
Value: []byte{
0x0c, 0x02, 0x66, 0x67,
// ^ ASN1:UTF8String
// ^ length
// ^ 2-byte string 'fwwf'
},
},
},
}
role = ms.extractRole(x509Cert)
if role != "" {
t.Errorf("role should have been empty, got: '%s'", role)
}
// build a certificate with a single Modbus Role extension of the wrong
// type: the role should be rejected
x509Cert = &x509.Certificate{
Extensions: []pkix.Extension{
{
Id: modbusRoleOID,
Value: []byte{
0x13, 0x04, 0x66, 0x77, 0x67, 0x78,
// ^ ASN1:PrintableString
// ^ length
// ^ 4-byte string 'fwgx'
},
},
},
}
role = ms.extractRole(x509Cert)
if role != "" {
t.Errorf("role should have been empty, got: '%s'", role)
}
// build a certificate with a single, short Modbus Role extension: the role
// should be rejected
x509Cert = &x509.Certificate{
Extensions: []pkix.Extension{
{
Id: modbusRoleOID,
Value: []byte{
0x0c,
// ^ ASN1:UTF8String
// ^ missing length + payload bytes
},
},
},
}
role = ms.extractRole(x509Cert)
if role != "" {
t.Errorf("role should have been empty, got: '%s'", role)
}
// build a certificate with one bad Modbus Role extension (short) and one
// valid: they should both be rejected
x509Cert = &x509.Certificate{
Extensions: []pkix.Extension{
{
Id: modbusRoleOID,
Value: []byte{
0x0c,
// ^ ASN1:UTF8String
// ^ missing length + payload bytes
},
},
{
Id: modbusRoleOID,
Value: []byte{
0x0c, 0x02, 0x66, 0x67,
// ^ ASN1:UTF8String
// ^ length
// ^ 2-byte string 'fwwf'
},
},
},
}
role = ms.extractRole(x509Cert)
if role != "" {
t.Errorf("role should have been empty, got: '%s'", role)
}
// build a certificate with a single, valid Modbus Role extension: it should be
// accepted
x509Cert = &x509.Certificate{
Extensions: []pkix.Extension{
{
Id: modbusRoleOID,
Value: []byte{
0x0c, 0x04, 0x66, 0x77, 0x67, 0x78,
// ^ ASN1:UTF8String
// ^ length
// ^ 4-byte string 'fwgx'
},
},
},
}
role = ms.extractRole(x509Cert)
if role != "fwgx" {
t.Errorf("role should have been 'fwgx', got: '%s'", role)
}
return
}