diff options
author | Ralf Nasilowski <Ralf.Nasilowski@ise.de> | 2018-08-16 10:49:31 +0200 |
---|---|---|
committer | Roland Knall <rknall@gmail.com> | 2018-10-16 09:03:43 +0000 |
commit | 9769df50efd830254aef0310562cdf47edd4ada3 (patch) | |
tree | 4e0b386f619e5a85c44a13dac9cf00071bfece1c /test | |
parent | 84fd2d79682278927ce07361d901faed35bd1202 (diff) |
KNX-IP: new KNXnet/IP dissector
The new KNXnet/IP dissector replaces the old KNXnet/IP dissector.
The new KNXnet/IP dissector supports the new KNX features
- A_MemoryExtended services
- A_PropertyExt services
- KNX Data Security
- KNXnet/IP Core V2
- KNXnet/IP Device Management V2
- KNXnet/IP Tunneling V2
- KNXnet/IP Routing V2
- KNXnet/IP Security
Change-Id: I3d1d716ef03d16d2720e6a1fcb23c2243d1cd956
Reviewed-on: https://code.wireshark.org/review/29155
Petri-Dish: Roland Knall <rknall@gmail.com>
Tested-by: Petri Dish Buildbot
Reviewed-by: Peter Wu <peter@lekensteyn.nl>
Reviewed-by: Roland Knall <rknall@gmail.com>
Diffstat (limited to 'test')
-rw-r--r-- | test/captures/knxip_DataSec.pcap | bin | 0 -> 125 bytes | |||
-rw-r--r-- | test/captures/knxip_SecureWrapper.pcap | bin | 0 -> 137 bytes | |||
-rw-r--r-- | test/captures/knxip_TimerNotify.pcap | bin | 0 -> 118 bytes | |||
-rw-r--r-- | test/keys/knx_keyring.xml | 58 | ||||
-rw-r--r-- | test/suite_decryption.py | 115 |
5 files changed, 173 insertions, 0 deletions
diff --git a/test/captures/knxip_DataSec.pcap b/test/captures/knxip_DataSec.pcap Binary files differnew file mode 100644 index 0000000000..7e28cd40e5 --- /dev/null +++ b/test/captures/knxip_DataSec.pcap diff --git a/test/captures/knxip_SecureWrapper.pcap b/test/captures/knxip_SecureWrapper.pcap Binary files differnew file mode 100644 index 0000000000..0be64d8c1a --- /dev/null +++ b/test/captures/knxip_SecureWrapper.pcap diff --git a/test/captures/knxip_TimerNotify.pcap b/test/captures/knxip_TimerNotify.pcap Binary files differnew file mode 100644 index 0000000000..4e7ae3781b --- /dev/null +++ b/test/captures/knxip_TimerNotify.pcap diff --git a/test/keys/knx_keyring.xml b/test/keys/knx_keyring.xml new file mode 100644 index 0000000000..c9b3cdd299 --- /dev/null +++ b/test/keys/knx_keyring.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- All attributes of the Keyring element are mandatory. For details on how to calculate/verify the Signature attribute, see below --> +<!-- "oKGio6SlpqeoqaqrrK2urw==" = $ A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF --> +<!-- "sLGys7S1tre4ubq7vL2+vw==" = $ B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF --> +<Keyring xmlns="http://knx.org/xml/keyring/1" Project="Family Home" Created="2015-03-04T20:55:58.0160546Z" CreatedBy="ETS 5.5 Build 456" Signature="MTIzNDU2Nzg5MDEyMzQ1Ng=="> + <!-- A backbone element is included if the project has a secure IP backbone or the keyring contains at least one IP Routing interface --> + <!-- The Latency and BackboneKey attributes are only included if the project has a secure IP backbone --> + <!-- BackboneKey := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created), Project.BackboneKey as byte[])) --> (1) + <Backbone MulticastAddress="224.0.23.12" Latency="1000" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <!-- There might be 0..n interface elements. Possible types are "USB", "Tunneling" or "Routing". There is no support for "Eiblib/IP". --> + <!-- Depending on the type of interface different interface attributes are possible. --> + <!-- USB interfaces only appear in the keying if used for Data Security communication. They only have the mandatory attribute "IndividualAddress". --> + <Interface Type="USB" IndividualAddress="2.1.56"> + <!-- Interfaces used for Data Security communication have a list of group addresses which will be communicated over this interface. --> + <!-- Each interface group has a mandatory list of trusted senders that are allowed to send telegrams to this group address. --> + <Group Address="3971" Senders="1.1.1 1.1.3 1.1.4"/> + <Group Address="3972" Senders="1.1.2 1.1.4"/> + <Group Address="14271" Senders="1.1.1"/> + </Interface> + <!-- Tunneling interfaces might appear in the keying because either they are used for Data Security communication or access to the interface itself is protected (or both). --> + <!-- KNXnet/IP Tunneling interfaces have as a mandatary attribute the KNX individual address of the host device. --> + <!-- If the Tunneling interface is used for Data Security communication, the attribute "IndividualAddress" is mandatory, otherwise it's optional. --> + <!-- Secured Tunneling interfaces need to have the "UserID" and "Password" attributes. An "Authentication" attribute containing the interface's device authenication code is optional. --> + <!-- Password := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.BusAccess(IA).Password, 24) as byte[])) --> (1) + <!-- Authentication := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.AthenticationCode, 24) as byte[])) --> (1) + <Interface Type="Tunneling" Host="2.1.0" IndividualAddress="2.1.56" UserID="3" Password="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw=" Authentication="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw="/> + <!-- If the Backbone (KNXnet/IP Routing) interface is used for Data Security communication, the attribute "IndividualAddress" is mandatory, otherwise it's optional. --> + <!-- If no "IndividualAddress" attribute is present, backbone interface elements shall be omitted altogether as all necessary information is part of the Backbone element already. --> + <Interface Type="Backbone" IndividualAddress="0.0.123"> + <Group Address="256" Senders="1.1.1 1.1.3 1.1.4"/> + <Group Address="3972" Senders="1.1.2 1.1.4"/> + </Interface> + <!-- The "GroupAddresses" collection shall only be present if the keyring contains interface groups for Data Security communication. --> + <!-- The "Address" and "Key" attributes are mandatory for every "GroupAddresses/Group" element. --> + <!-- Key := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), GroupAddress.Key as byte[])) --> (1) + <GroupAddresses> + <Group Address="256" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <Group Address="3971" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <Group Address="3972" Key="oKGio6SlpqeoqaqrrK2urw=="/> + <Group Address="14271" Key="oKGio6SlpqeoqaqrrK2urw=="/> + </GroupAddresses> + <!-- Keyings exported for the whole project (for backup or diagnostic purposes) shall contain one "Device" entry for every secure device. --> + <!-- For interface keyings the "Devices" list is optional and shall only be present if sequence numbers are known for devices referenced as interface group senders. --> + <!-- The "ToolKey" attribute shall only be present if the keying was exported for the whole project. --> + <!-- The "SeqNr" attribute is optional and shall only be present if a received sequence number for a given device is known. --> + <!-- ToolKey := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), Device.ToolKey as byte[])) --> (1) + <!-- Secured IP-enabled devices need to have the "ManagementPassword" attribute. An "Authentication" attribute containing the device's authenication code is optional. --> + <!-- ManagementPassword := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.ManagementPassword, 24) as byte[])) --> (1) + <!-- Authentication := Base64( AES128-CBC( PBKDF2( HMAC-SHA256, KeyringPassword, "1.keyring.ets.knx.org", 65536, 128), MSB128(SHA256(this.Keyring.Created)), RandomBytes(8)+PKCS#7Padding( Device.AthenticationCode, 24) as byte[])) --> (1) + <!-- The "ManagementPassword" and "Authentication" attributes shall only be present if the keying was exported for the whole project. --> + <Devices> + <Device IndividualAddress="1.1.1" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="45678"/> + <Device IndividualAddress="1.1.2" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="34567"/> + <Device IndividualAddress="1.1.3" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="23456"/> + <Device IndividualAddress="1.1.4" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="12345"/> + <Device IndividualAddress="2.1.0" ToolKey="sLGys7S1tre4ubq7vL2+vw==" SequenceNumber="1234" ManagementPassword="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw=" Authentication="A3BzNDU2Nzg5MDEyMzQnUA3BzNDU2Nzg5MDEyMzQnUw="/> + </Devices> +</Keyring>
\ No newline at end of file diff --git a/test/suite_decryption.py b/test/suite_decryption.py index e393bf4d93..88e92438bb 100644 --- a/test/suite_decryption.py +++ b/test/suite_decryption.py @@ -760,3 +760,118 @@ class case_decrypt_wireguard(subprocesstest.SubprocessTestCase): ], pcap_file='wireguard-psk.pcap') self.assertIn('2\t0', lines) self.assertIn('4\t0', lines) + +class case_decrypt_knxip(subprocesstest.SubprocessTestCase): + # Capture files for these tests contain single telegrams. + # For realistic (live captured) KNX/IP telegram sequences, see: + # https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=14825 + + def test_knxip_data_security_decryption_ok(self): + '''KNX/IP: Data Security decryption OK''' + # capture_file contains KNX/IP ConfigReq DataSec PropExtValueWriteCon telegram + capture_file = os.path.join(config.capture_dir, 'knxip_DataSec.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F', + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' DataSec ')) + self.assertTrue(self.grepOutput(' PropExtValueWriteCon ')) + + def test_knxip_data_security_decryption_fails(self): + '''KNX/IP: Data Security decryption fails''' + # capture_file contains KNX/IP ConfigReq DataSec PropExtValueWriteCon telegram + capture_file = os.path.join(config.capture_dir, 'knxip_DataSec.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:""', # "" is really necessary, otherwise test fails + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' DataSec ')) + self.assertFalse(self.grepOutput(' PropExtValueWriteCon ')) + + def test_knxip_secure_wrapper_decryption_ok(self): + '''KNX/IP: SecureWrapper decryption OK''' + # capture_file contains KNX/IP SecureWrapper RoutingInd telegram + capture_file = os.path.join(config.capture_dir, 'knxip_SecureWrapper.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F', + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' SecureWrapper ')) + self.assertTrue(self.grepOutput(' RoutingInd ')) + + def test_knxip_secure_wrapper_decryption_fails(self): + '''KNX/IP: SecureWrapper decryption fails''' + # capture_file contains KNX/IP SecureWrapper RoutingInd telegram + capture_file = os.path.join(config.capture_dir, 'knxip_SecureWrapper.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:""', # "" is really necessary, otherwise test fails + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' SecureWrapper ')) + self.assertFalse(self.grepOutput(' RoutingInd ')) + + def test_knxip_timer_notify_authentication_ok(self): + '''KNX/IP: TimerNotify authentication OK''' + # capture_file contains KNX/IP TimerNotify telegram + capture_file = os.path.join(config.capture_dir, 'knxip_TimerNotify.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F', + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' TimerNotify ')) + self.assertTrue(self.grepOutput(' OK$')) + + def test_knxip_timer_notify_authentication_fails(self): + '''KNX/IP: TimerNotify authentication fails''' + # capture_file contains KNX/IP TimerNotify telegram + capture_file = os.path.join(config.capture_dir, 'knxip_TimerNotify.pcap') + self.runProcess((config.cmd_tshark, + '-r', capture_file, + '-o', 'kip.key_1:""', # "" is really necessary, otherwise test fails + ), + env=config.test_env) + self.assertTrue(self.grepOutput(' TimerNotify ')) + self.assertFalse(self.grepOutput(' OK$')) + + def test_knxip_keyring_xml_import(self): + '''KNX/IP: keyring.xml import''' + # key_file "keyring.xml" contains KNX decryption keys + key_file = os.path.join(config.key_dir, 'knx_keyring.xml') + # capture_file is empty + capture_file = os.path.join(config.capture_dir, 'empty.pcap') + # Write extracted key info to stdout + self.runProcess((config.cmd_tshark, + '-o', 'kip.key_file:' + key_file, + '-o', 'kip.key_info_file:-', + '-r', capture_file, + ), + env=config.test_env) + self.assertTrue(self.grepOutput('^MCA 224[.]0[.]23[.]12 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 sender 1[.]1[.]1$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 sender 1[.]1[.]3$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 sender 1[.]1[.]4$')) + self.assertTrue(self.grepOutput('^GA 1/7/132 sender 1[.]1[.]2$')) + self.assertTrue(self.grepOutput('^GA 1/7/132 sender 1[.]1[.]4$')) + self.assertTrue(self.grepOutput('^GA 6/7/191 sender 1[.]1[.]1$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 sender 1[.]1[.]1$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 sender 1[.]1[.]3$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 sender 1[.]1[.]4$')) + self.assertTrue(self.grepOutput('^GA 0/1/0 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 1/7/131 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 1/7/132 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^GA 6/7/191 key A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]1 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]1 SeqNr 45678$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]2 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]2 SeqNr 34567$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]3 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]3 SeqNr 23456$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]4 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 1[.]1[.]4 SeqNr 12345$')) + self.assertTrue(self.grepOutput('^IA 2[.]1[.]0 key B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF$')) + self.assertTrue(self.grepOutput('^IA 2[.]1[.]0 SeqNr 1234$')) |