forked from osmocom/wireshark
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>
This commit is contained in:
parent
84fd2d7968
commit
9769df50ef
|
@ -405,6 +405,8 @@ set(DISSECTOR_PUBLIC_HEADERS
|
|||
packet-juniper.h
|
||||
packet-jxta.h
|
||||
packet-kerberos.h
|
||||
packet-knxip.h
|
||||
packet-knxip_decrypt.h
|
||||
packet-l2tp.h
|
||||
packet-lapdm.h
|
||||
packet-lbm.h
|
||||
|
@ -762,6 +764,7 @@ set(DISSECTOR_SRC
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/packet-ccsds.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-cdp.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-cell_broadcast.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-cemi.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-ceph.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-cfdp.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-cfm.c
|
||||
|
@ -1243,7 +1246,8 @@ set(DISSECTOR_SRC
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/packet-kismet.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-klm.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-knet.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-knxnetip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-knxip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-knxip_decrypt.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-kpasswd.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-kt.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/packet-l1-events.c
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
|||
/* packet-knxip.h
|
||||
* Routines for KNXnet/IP dissection
|
||||
* Copyright 2004, Jan Kessler <kessler@ise.de>
|
||||
*
|
||||
* Ethereal - Network traffic analyzer
|
||||
* By Gerald Combs <gerald@ethereal.com>
|
||||
* Copyright 1998 Gerald Combs
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#ifndef PACKET_KNXIP_H
|
||||
#define PACKET_KNXIP_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <epan/expert.h>
|
||||
#include <epan/packet.h>
|
||||
#include <epan/proto.h>
|
||||
#include <epan/ipproto.h>
|
||||
#include <epan/prefs.h>
|
||||
#include <epan/tvbuff.h>
|
||||
|
||||
#include "packet-knxip_decrypt.h"
|
||||
|
||||
#define KIP_ERROR &ei_knxip_error
|
||||
#define KIP_WARNING &ei_knxip_warning
|
||||
|
||||
extern expert_field ei_knxip_error;
|
||||
extern expert_field ei_knxip_warning;
|
||||
|
||||
extern guint8 knxip_host_protocol;
|
||||
extern guint8 knxip_error;
|
||||
|
||||
#define MAX_KNX_DECRYPTION_KEYS 10
|
||||
|
||||
extern guint8 knx_decryption_keys[ MAX_KNX_DECRYPTION_KEYS ][ KNX_KEY_LENGTH ];
|
||||
extern guint8 knx_decryption_key_count;
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
||||
*
|
||||
* Local variables:
|
||||
* c-basic-offset: 2
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vi: set shiftwidth=2 tabstop=8 expandtab:
|
||||
* :indentSize=2:tabSize=8:noTabs=true:
|
||||
*/
|
|
@ -0,0 +1,811 @@
|
|||
/* packet-knxip_decrypt.c
|
||||
* Decryption keys and decryption functions for KNX/IP Dissector
|
||||
* Copyright 2018, ise GmbH <Ralf.Nasilowski@ise.de>
|
||||
*
|
||||
* Wireshark - Network traffic analyzer
|
||||
* By Gerald Combs <gerald@wireshark.org>
|
||||
* Copyright 1998 Gerald Combs
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
// Activate g_debug output with environment variable: G_MESSAGES_DEBUG=packet-knxip
|
||||
#define G_LOG_DOMAIN "packet-knxip"
|
||||
|
||||
#include <wsutil/file_util.h>
|
||||
#include "proto.h"
|
||||
#include "packet-knxip_decrypt.h"
|
||||
#include <epan/wmem/wmem.h>
|
||||
#include <wsutil/wsgcrypt.h>
|
||||
#include <wsutil/strtoi.h>
|
||||
|
||||
#define TEXT_BUFFER_SIZE 128
|
||||
|
||||
#define IPA_SIZE 4 // = size of IPv4 address
|
||||
|
||||
#define BASE64_KNX_KEY_LENGTH 24 // = length of base64 encoded KNX key
|
||||
|
||||
struct knx_keyring_mca_keys* knx_keyring_mca_keys;
|
||||
struct knx_keyring_ga_keys* knx_keyring_ga_keys;
|
||||
struct knx_keyring_ga_senders* knx_keyring_ga_senders;
|
||||
struct knx_keyring_ia_keys* knx_keyring_ia_keys;
|
||||
struct knx_keyring_ia_seqs* knx_keyring_ia_seqs;
|
||||
|
||||
// Encrypt 16-byte block via AES
|
||||
static void encrypt_block( const guint8 key[ KNX_KEY_LENGTH ], const guint8 plain[ KNX_KEY_LENGTH ], guint8 p_crypt[ KNX_KEY_LENGTH ] )
|
||||
{
|
||||
gcry_cipher_hd_t cryptor = NULL;
|
||||
gcry_cipher_open( &cryptor, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_CBC, 0 );
|
||||
gcry_cipher_setkey( cryptor, key, KNX_KEY_LENGTH );
|
||||
gcry_cipher_encrypt( cryptor, p_crypt, KNX_KEY_LENGTH, plain, KNX_KEY_LENGTH );
|
||||
gcry_cipher_close( cryptor );
|
||||
}
|
||||
|
||||
// Create B_0 for CBC-MAC
|
||||
static void build_b0( guint8 p_result[ KNX_KEY_LENGTH ], const guint8* nonce, guint8 nonce_length )
|
||||
{
|
||||
DISSECTOR_ASSERT( nonce_length <= KNX_KEY_LENGTH );
|
||||
if( nonce_length ) memcpy( p_result, nonce, nonce_length );
|
||||
memset( p_result + nonce_length, 0, KNX_KEY_LENGTH - nonce_length );
|
||||
}
|
||||
|
||||
// Create Ctr_0 for CCM encryption/decryption
|
||||
static void build_ctr0( guint8 p_result[ KNX_KEY_LENGTH ], const guint8* nonce, guint8 nonce_length )
|
||||
{
|
||||
build_b0( p_result, nonce, nonce_length );
|
||||
p_result[ KNX_KEY_LENGTH - 2 ] = 0xFF;
|
||||
}
|
||||
|
||||
// Calculate MAC for KNX IP Security or KNX Data Security
|
||||
void knx_ccm_calc_cbc_mac( guint8* p_mac, const guint8 key[ KNX_KEY_LENGTH ],
|
||||
const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length,
|
||||
const guint8 b_0[ KNX_KEY_LENGTH ] )
|
||||
{
|
||||
guint8 plain[ KNX_KEY_LENGTH ];
|
||||
guint8 b_pos;
|
||||
|
||||
// Add B_0
|
||||
memcpy( plain, b_0, KNX_KEY_LENGTH );
|
||||
encrypt_block( key, plain, p_mac );
|
||||
|
||||
// Add a_length
|
||||
plain[ 0 ] = (guint8) ((a_length >> 8) ^ p_mac[ 0 ]);
|
||||
plain[ 1 ] = (guint8) ((a_length & 0xFF) ^ p_mac[ 1 ]);
|
||||
b_pos = 2;
|
||||
|
||||
// Add a_bytes directly followed by p_bytes
|
||||
while( a_length || p_length )
|
||||
{
|
||||
while( a_length && b_pos < KNX_KEY_LENGTH )
|
||||
{
|
||||
plain[ b_pos ] = *a_bytes++ ^ p_mac[ b_pos ];
|
||||
--a_length;
|
||||
++b_pos;
|
||||
}
|
||||
|
||||
while( p_length && b_pos < KNX_KEY_LENGTH )
|
||||
{
|
||||
plain[ b_pos ] = *p_bytes++ ^ p_mac[ b_pos ];
|
||||
--p_length;
|
||||
++b_pos;
|
||||
}
|
||||
|
||||
while( b_pos < KNX_KEY_LENGTH )
|
||||
{
|
||||
plain[ b_pos ] = p_mac[ b_pos ];
|
||||
++b_pos;
|
||||
}
|
||||
|
||||
encrypt_block( key, plain, p_mac );
|
||||
|
||||
b_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate MAC for KNX IP Security, using 6-byte Sequence ID
|
||||
void knxip_ccm_calc_cbc_mac( guint8* p_mac, const guint8 key[ KNX_KEY_LENGTH ],
|
||||
const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length,
|
||||
const guint8* nonce, guint8 nonce_length )
|
||||
{
|
||||
guint8 b_0[ KNX_KEY_LENGTH ];
|
||||
build_b0( b_0, nonce, nonce_length );
|
||||
b_0[ KNX_KEY_LENGTH - 2 ] = (guint8) (p_length >> 8);
|
||||
b_0[ KNX_KEY_LENGTH - 1 ] = (guint8) (p_length & 0xFF);
|
||||
knx_ccm_calc_cbc_mac( p_mac, key, a_bytes, a_length, p_bytes, p_length, b_0 );
|
||||
}
|
||||
|
||||
// Encrypt for KNX IP Security or KNX Data Security
|
||||
guint8* knx_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length,
|
||||
const guint8* mac, guint8 mac_length, const guint8 ctr_0[ KNX_KEY_LENGTH ], guint8 s0_bytes_used_for_mac )
|
||||
{
|
||||
if( p_length >= 0 && !(p_length && !p_bytes) )
|
||||
{
|
||||
// NB: mac_length = 16 (for IP Security), or 4 (for Data Security)
|
||||
|
||||
guint8* result = p_result ? p_result : (guint8*) wmem_alloc( wmem_packet_scope(), p_length + mac_length );
|
||||
|
||||
guint8* dest = result;
|
||||
|
||||
guint8 ctr[ KNX_KEY_LENGTH ];
|
||||
guint8 mask[ KNX_KEY_LENGTH ];
|
||||
guint8 mask_0[ KNX_KEY_LENGTH ];
|
||||
guint8 b_pos;
|
||||
|
||||
// Encrypt ctr_0 for mac
|
||||
memcpy( ctr, ctr_0, KNX_KEY_LENGTH );
|
||||
encrypt_block( key, ctr, mask_0 );
|
||||
|
||||
// Encrypt p_bytes with rest of S_0, only if mac_length < 16.
|
||||
b_pos = s0_bytes_used_for_mac;
|
||||
while (p_length && b_pos < KNX_KEY_LENGTH )
|
||||
{
|
||||
*dest++ = mask_0[b_pos++] ^ *p_bytes++;
|
||||
--p_length;
|
||||
}
|
||||
|
||||
// Encrypt p_bytes
|
||||
while( p_length )
|
||||
{
|
||||
// Increment and encrypt ctr
|
||||
++ctr[ KNX_KEY_LENGTH - 1 ];
|
||||
encrypt_block( key, ctr, mask );
|
||||
|
||||
// Encrypt input block via encrypted ctr
|
||||
b_pos = 0;
|
||||
while( p_length && b_pos < KNX_KEY_LENGTH )
|
||||
{
|
||||
*dest++ = mask[ b_pos++] ^ *p_bytes++;
|
||||
--p_length;
|
||||
}
|
||||
}
|
||||
|
||||
if( mac )
|
||||
{
|
||||
if( mac_length > KNX_KEY_LENGTH )
|
||||
{
|
||||
mac_length = KNX_KEY_LENGTH;
|
||||
}
|
||||
|
||||
// Encrypt and append mac
|
||||
b_pos = 0;
|
||||
while( mac_length )
|
||||
{
|
||||
*dest++ = mask_0[ b_pos++] ^ *mac++;
|
||||
--mac_length;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Encrypt for KNX IP Security (with 16-byte MAC and Nonce based on 6-byte Sequence ID)
|
||||
guint8* knxip_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length,
|
||||
const guint8* mac, const guint8* nonce, guint8 nonce_length )
|
||||
{
|
||||
guint8 ctr_0[ KNX_KEY_LENGTH ];
|
||||
build_ctr0( ctr_0, nonce, nonce_length );
|
||||
return knx_ccm_encrypt( p_result, key, p_bytes, p_length, mac, KNX_KEY_LENGTH, ctr_0, KNX_KEY_LENGTH );
|
||||
}
|
||||
|
||||
// Decrypt for KNX-IP Security (with 16-byte MAC and Nonce based on 6-byte Sequence ID)
|
||||
guint8* knxip_ccm_decrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* crypt, gint crypt_length,
|
||||
const guint8* nonce, guint8 nonce_length )
|
||||
{
|
||||
gint p_length = crypt_length - KNX_KEY_LENGTH;
|
||||
guint8 ctr_0[ KNX_KEY_LENGTH ];
|
||||
build_ctr0( ctr_0, nonce, nonce_length );
|
||||
return knx_ccm_encrypt( p_result, key, crypt, p_length, crypt + p_length, KNX_KEY_LENGTH, ctr_0, KNX_KEY_LENGTH );
|
||||
}
|
||||
|
||||
static void fprintf_hex( FILE* f, const guint8* data, guint8 length )
|
||||
{
|
||||
for( ; length; --length ) fprintf( f, " %02X", *data++ );
|
||||
fputc( '\n', f );
|
||||
}
|
||||
|
||||
static void clear_keyring_data( void )
|
||||
{
|
||||
while( knx_keyring_mca_keys )
|
||||
{
|
||||
struct knx_keyring_mca_keys* mca_key = knx_keyring_mca_keys;
|
||||
knx_keyring_mca_keys = mca_key->next;
|
||||
wmem_free( wmem_epan_scope(), mca_key );
|
||||
}
|
||||
|
||||
while( knx_keyring_ga_keys )
|
||||
{
|
||||
struct knx_keyring_ga_keys* ga_key = knx_keyring_ga_keys;
|
||||
knx_keyring_ga_keys = ga_key->next;
|
||||
wmem_free( wmem_epan_scope(), ga_key );
|
||||
}
|
||||
|
||||
while( knx_keyring_ga_senders )
|
||||
{
|
||||
struct knx_keyring_ga_senders* ga_sender = knx_keyring_ga_senders;
|
||||
knx_keyring_ga_senders = ga_sender->next;
|
||||
wmem_free( wmem_epan_scope(), ga_sender );
|
||||
}
|
||||
|
||||
while( knx_keyring_ia_keys )
|
||||
{
|
||||
struct knx_keyring_ia_keys* ia_key = knx_keyring_ia_keys;
|
||||
knx_keyring_ia_keys = ia_key->next;
|
||||
wmem_free( wmem_epan_scope(), ia_key );
|
||||
}
|
||||
|
||||
while( knx_keyring_ia_seqs )
|
||||
{
|
||||
struct knx_keyring_ia_seqs* ia_seq = knx_keyring_ia_seqs;
|
||||
knx_keyring_ia_seqs = ia_seq->next;
|
||||
wmem_free( wmem_epan_scope(), ia_seq );
|
||||
}
|
||||
}
|
||||
|
||||
// Read IP address
|
||||
static void read_ip_addr( guint8 result[ 4 ], const gchar* text )
|
||||
{
|
||||
ws_in4_addr value = 0;
|
||||
if( ws_inet_pton4( text, &value ) )
|
||||
memcpy( result, &value, 4 );
|
||||
else
|
||||
memset( result, 0, 4 );
|
||||
}
|
||||
|
||||
// Read KNX group address
|
||||
static guint16 read_ga( const gchar* text )
|
||||
{
|
||||
guint a[ 3 ];
|
||||
gint n = sscanf( text, "%u/%u/%u", a, a + 1, a + 2 );
|
||||
return
|
||||
(n == 1) ? (guint16) a[ 0 ] :
|
||||
(n == 2) ? (guint16) ((a[ 0 ] << 11) | a[ 1 ]) :
|
||||
(n == 3) ? (guint16) ((a[ 0 ] << 11) | (a[ 1 ] << 8) | a[ 2 ]) :
|
||||
0;
|
||||
}
|
||||
|
||||
// Read KNX individual address
|
||||
static guint16 read_ia( const gchar* text )
|
||||
{
|
||||
guint a[ 3 ];
|
||||
gint n = sscanf( text, "%u.%u.%u", a, a + 1, a + 2 );
|
||||
return
|
||||
(n == 1) ? (guint16) a[ 0 ] :
|
||||
(n == 2) ? (guint16) ((a[ 0 ] << 8) | a[ 1 ]) :
|
||||
(n == 3) ? (guint16) ((a[ 0 ] << 12) | (a[ 1 ] << 8) | a[ 2 ]) :
|
||||
0;
|
||||
}
|
||||
|
||||
// Read 6-byte sequence number from decimal representation
|
||||
static guint64 read_seq( const gchar* text )
|
||||
{
|
||||
guint64 result;
|
||||
return ws_strtou64( text, NULL, &result ) ? result : 0;
|
||||
}
|
||||
|
||||
// Decrypt key
|
||||
static void decrypt_key( guint8 key[] _U_, guint8 password_hash[] _U_, guint8 created_hash[] _U_ )
|
||||
{
|
||||
// TODO: decrypt as AES128-CBC(key, password_hash, created_hash)
|
||||
}
|
||||
|
||||
// Decode and decrypt key
|
||||
static void decode_and_decrypt_key( guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ], const gchar* text, guint8 password_hash[], guint8 created_hash[] )
|
||||
{
|
||||
gsize out_len;
|
||||
g_snprintf( (gchar*) key, BASE64_KNX_KEY_LENGTH + 1, "%s", text );
|
||||
g_base64_decode_inplace( (gchar*) key, &out_len );
|
||||
decrypt_key( key, password_hash, created_hash );
|
||||
}
|
||||
|
||||
// Add MCA <-> key association
|
||||
static void add_mca_key( const guint8 mca[ IPA_SIZE ], const gchar* text, guint8 password_hash[], guint8 created_hash[], FILE* f2 )
|
||||
{
|
||||
gint text_length = (gint) strlen( text );
|
||||
|
||||
if( text_length == BASE64_KNX_KEY_LENGTH )
|
||||
{
|
||||
guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ];
|
||||
struct knx_keyring_mca_keys** mca_keys_next;
|
||||
struct knx_keyring_mca_keys* mca_key;
|
||||
|
||||
decode_and_decrypt_key( key, text, password_hash, created_hash );
|
||||
|
||||
mca_keys_next = &knx_keyring_mca_keys;
|
||||
|
||||
while( (mca_key = *mca_keys_next) != NULL )
|
||||
{
|
||||
if( memcmp( mca_key->mca, mca, IPA_SIZE ) == 0 )
|
||||
{
|
||||
if( memcmp( mca_key->key, key, KNX_KEY_LENGTH ) == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mca_keys_next = &mca_key->next;
|
||||
}
|
||||
|
||||
if( f2 )
|
||||
{
|
||||
fprintf( f2, "MCA %u.%u.%u.%u key", mca[ 0 ], mca[ 1 ], mca[ 2 ], mca[ 3 ] );
|
||||
fprintf_hex( f2, key, KNX_KEY_LENGTH );
|
||||
}
|
||||
|
||||
mca_key = (struct knx_keyring_mca_keys*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_mca_keys ) );
|
||||
|
||||
if( mca_key )
|
||||
{
|
||||
mca_key->next = NULL;
|
||||
memcpy( mca_key->mca, mca, IPA_SIZE );
|
||||
memcpy( mca_key->key, key, KNX_KEY_LENGTH );
|
||||
|
||||
*mca_keys_next = mca_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add GA <-> key association
|
||||
static void add_ga_key( guint16 ga, const gchar* text, guint8 password_hash[], guint8 created_hash[], FILE* f2 )
|
||||
{
|
||||
gint text_length = (gint) strlen( text );
|
||||
|
||||
if( text_length == BASE64_KNX_KEY_LENGTH )
|
||||
{
|
||||
guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ];
|
||||
struct knx_keyring_ga_keys** ga_keys_next;
|
||||
struct knx_keyring_ga_keys* ga_key;
|
||||
|
||||
decode_and_decrypt_key( key, text, password_hash, created_hash );
|
||||
|
||||
ga_keys_next = &knx_keyring_ga_keys;
|
||||
|
||||
while( (ga_key = *ga_keys_next) != NULL )
|
||||
{
|
||||
if( ga_key->ga == ga )
|
||||
{
|
||||
if( memcmp( ga_key->key, key, KNX_KEY_LENGTH ) == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ga_keys_next = &ga_key->next;
|
||||
}
|
||||
|
||||
if( f2 )
|
||||
{
|
||||
fprintf( f2, "GA %u/%u/%u key", (ga >> 11) & 0x1F, (ga >> 8) & 0x7, ga & 0xFF );
|
||||
fprintf_hex( f2, key, KNX_KEY_LENGTH );
|
||||
}
|
||||
|
||||
ga_key = (struct knx_keyring_ga_keys*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ga_keys ) );
|
||||
|
||||
if( ga_key )
|
||||
{
|
||||
ga_key->next = NULL;
|
||||
ga_key->ga = ga;
|
||||
memcpy( ga_key->key, key, KNX_KEY_LENGTH );
|
||||
|
||||
*ga_keys_next = ga_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add GA <-> sender association
|
||||
static void add_ga_sender( guint16 ga, const gchar* text, FILE* f2 )
|
||||
{
|
||||
guint16 ia = read_ia( text );
|
||||
struct knx_keyring_ga_senders** ga_senders_next = &knx_keyring_ga_senders;
|
||||
struct knx_keyring_ga_senders* ga_sender;
|
||||
|
||||
while( (ga_sender = *ga_senders_next) != NULL )
|
||||
{
|
||||
if( ga_sender->ga == ga )
|
||||
{
|
||||
if( ga_sender->ia == ia )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ga_senders_next = &ga_sender->next;
|
||||
}
|
||||
|
||||
if( f2 )
|
||||
{
|
||||
fprintf( f2, "GA %u/%u/%u sender %u.%u.%u\n", (ga >> 11) & 0x1F, (ga >> 8) & 0x7, ga & 0xFF, (ia >> 12) & 0xF, (ia >> 8) & 0xF, ia & 0xFF );
|
||||
}
|
||||
|
||||
ga_sender = (struct knx_keyring_ga_senders*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ga_senders ) );
|
||||
|
||||
if( ga_sender )
|
||||
{
|
||||
ga_sender->next = NULL;
|
||||
ga_sender->ga = ga;
|
||||
ga_sender->ia = ia;
|
||||
|
||||
*ga_senders_next = ga_sender;
|
||||
}
|
||||
}
|
||||
|
||||
// Add IA <-> key association
|
||||
static void add_ia_key( guint16 ia, const gchar* text, guint8 password_hash[], guint8 created_hash[], FILE* f2 )
|
||||
{
|
||||
gint text_length = (gint) strlen( text );
|
||||
|
||||
if( text_length == BASE64_KNX_KEY_LENGTH )
|
||||
{
|
||||
guint8 key[ BASE64_KNX_KEY_LENGTH + 1 ];
|
||||
struct knx_keyring_ia_keys** ia_keys_next;
|
||||
struct knx_keyring_ia_keys* ia_key;
|
||||
|
||||
decode_and_decrypt_key( key, text, password_hash, created_hash );
|
||||
|
||||
ia_keys_next = &knx_keyring_ia_keys;
|
||||
|
||||
while( (ia_key = *ia_keys_next) != NULL )
|
||||
{
|
||||
if( ia_key->ia == ia )
|
||||
{
|
||||
if( memcmp( ia_key->key, key, KNX_KEY_LENGTH ) == 0 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ia_keys_next = &ia_key->next;
|
||||
}
|
||||
|
||||
if( f2 )
|
||||
{
|
||||
fprintf( f2, "IA %u.%u.%u key", (ia >> 12) & 0xF, (ia >> 8) & 0xF, ia & 0xFF );
|
||||
fprintf_hex( f2, key, KNX_KEY_LENGTH );
|
||||
}
|
||||
|
||||
ia_key = (struct knx_keyring_ia_keys*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ia_keys ) );
|
||||
|
||||
if( ia_key )
|
||||
{
|
||||
ia_key->next = NULL;
|
||||
ia_key->ia = ia;
|
||||
memcpy( ia_key->key, key, KNX_KEY_LENGTH );
|
||||
|
||||
*ia_keys_next = ia_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add IA <-> sequence number association
|
||||
static void add_ia_seq( guint16 ia, const gchar* text, FILE* f2 )
|
||||
{
|
||||
guint64 seq = read_seq( text );
|
||||
|
||||
struct knx_keyring_ia_seqs** ia_seqs_next = &knx_keyring_ia_seqs;
|
||||
struct knx_keyring_ia_seqs* ia_seq;
|
||||
|
||||
while( (ia_seq = *ia_seqs_next) != NULL )
|
||||
{
|
||||
if( ia_seq->ia == ia )
|
||||
{
|
||||
if( ia_seq->seq == seq )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ia_seqs_next = &ia_seq->next;
|
||||
}
|
||||
|
||||
if( f2 )
|
||||
{
|
||||
fprintf( f2, "IA %u.%u.%u SeqNr %" G_GINT64_MODIFIER "u\n", (ia >> 12) & 0xF, (ia >> 8) & 0xF, ia & 0xFF, seq );
|
||||
}
|
||||
|
||||
ia_seq = (struct knx_keyring_ia_seqs*) wmem_alloc( wmem_epan_scope(), sizeof( struct knx_keyring_ia_seqs ) );
|
||||
|
||||
if( ia_seq )
|
||||
{
|
||||
ia_seq->next = NULL;
|
||||
ia_seq->ia = ia;
|
||||
ia_seq->seq = seq;
|
||||
|
||||
*ia_seqs_next = ia_seq;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate PBKDF2(HMAC-SHA256, password, "1.keyring.ets.knx.org", 65536, 128)
|
||||
static void make_password_hash( guint8 password_hash[] _U_, const gchar* password _U_ )
|
||||
{
|
||||
// TODO: password_hash = PBKDF2(HMAC-SHA256, password, "1.keyring.ets.knx.org", 65536, 128)
|
||||
}
|
||||
|
||||
// Calculate MSB128(SHA256(created))
|
||||
static void make_created_hash( guint8 created_hash[] _U_, const gchar* created _U_ )
|
||||
{
|
||||
// TODO: created_hash = MSB128(SHA256(created))
|
||||
}
|
||||
|
||||
// Read KNX security key info from keyring XML file.
|
||||
//
|
||||
// An example keyring XML file is
|
||||
// "test/keys/knx_keyring.xml".
|
||||
//
|
||||
// Corresponding test is
|
||||
// suite_decryption.case_decrypt_knxip.test_knxip_keyring_xml_import
|
||||
//
|
||||
// We do not use LibXml2 here, because
|
||||
// (1) we want to be platform independent,
|
||||
// (2) we just want to extract some data from the keyring XML file,
|
||||
// (3) we want to avoid the complicated recursive DOM processing implied by LibXml2.
|
||||
//
|
||||
// Resulting decoded and decrypted 16-byte keys with context info are optionally written to a "key info" text file.
|
||||
// This may be useful, as these keys are not directly available from the keyring XML file .
|
||||
void read_knx_keyring_xml_file( const gchar* key_file, const gchar* password, const gchar* key_info_file )
|
||||
{
|
||||
// Clear old keyring data
|
||||
clear_keyring_data();
|
||||
|
||||
// Read new data from keyring XML file
|
||||
FILE* f = ws_fopen( key_file, "r" );
|
||||
|
||||
// Optionally write extracted data to key info file
|
||||
FILE* f2 = (!key_info_file || !*key_info_file) ? NULL :
|
||||
(strcmp( key_info_file, "-" ) == 0) ? stdout :
|
||||
ws_fopen( key_info_file, "w" );
|
||||
|
||||
if( f )
|
||||
{
|
||||
guint8 backbone_mca[ IPA_SIZE ];
|
||||
guint8 backbone_mca_valid = 0;
|
||||
guint16 group_ga = 0;
|
||||
guint8 group_ga_valid = 0;
|
||||
guint16 device_ia = 0;
|
||||
guint8 device_ia_valid = 0;
|
||||
gchar name[ TEXT_BUFFER_SIZE ];
|
||||
gchar value[ TEXT_BUFFER_SIZE ];
|
||||
guint8 password_hash[ KNX_KEY_LENGTH ];
|
||||
guint8 created_hash[ KNX_KEY_LENGTH ];
|
||||
gchar tag_name[ TEXT_BUFFER_SIZE ];
|
||||
guint8 tag_name_done = 0;
|
||||
guint8 tag_end = 0;
|
||||
guint8 in_tag = 0;
|
||||
|
||||
memset( backbone_mca, 0, IPA_SIZE );
|
||||
*name = '\0';
|
||||
*value = '\0';
|
||||
memset( password_hash, 0, KNX_KEY_LENGTH );
|
||||
memset( created_hash, 0, KNX_KEY_LENGTH );
|
||||
*tag_name = '\0';
|
||||
|
||||
make_password_hash( password_hash, password );
|
||||
|
||||
g_debug( "%s:", key_file );
|
||||
|
||||
gint c = fgetc( f );
|
||||
|
||||
while( c >= 0 )
|
||||
{
|
||||
if( c == '<' ) // tag start
|
||||
{
|
||||
in_tag = 1;
|
||||
tag_end = 0;
|
||||
*tag_name = 0;
|
||||
tag_name_done = 0;
|
||||
*name = '\0';
|
||||
*value = '\0';
|
||||
}
|
||||
else if( c == '>' ) // tag end
|
||||
{
|
||||
in_tag = 0;
|
||||
}
|
||||
else if( c == '/' )
|
||||
{
|
||||
if( in_tag ) // "</" or "/>"
|
||||
{
|
||||
tag_end = 1;
|
||||
*tag_name = 0;
|
||||
tag_name_done = 0;
|
||||
*name = '\0';
|
||||
*value = '\0';
|
||||
}
|
||||
}
|
||||
else if( g_ascii_isalpha( c ) || c == '_' ) // possibly tag name, or attribute name
|
||||
{
|
||||
size_t length = 0;
|
||||
name[ length++ ] = (gchar) c;
|
||||
while( (c = fgetc( f )) >= 0 )
|
||||
{
|
||||
if( g_ascii_isalnum( c ) || c == '_' )
|
||||
{
|
||||
if( length < sizeof name + 1 )
|
||||
{
|
||||
name[ length++ ] = (gchar) c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
name[ length ] = '\0';
|
||||
*value = '\0';
|
||||
|
||||
if( !tag_name_done ) // tag name
|
||||
{
|
||||
g_snprintf( tag_name, sizeof tag_name, "%s", name );
|
||||
*name = '\0';
|
||||
tag_name_done = 1;
|
||||
}
|
||||
else // Check for name="value" construct
|
||||
{
|
||||
while( c >= 0 && g_ascii_isspace( c ) ) c = fgetc( f );
|
||||
|
||||
if( c == '=' )
|
||||
{
|
||||
while( (c = fgetc( f )) >= 0 && g_ascii_isspace( c ) );
|
||||
|
||||
if( c == '"' )
|
||||
{
|
||||
length = 0;
|
||||
|
||||
while( (c = fgetc( f )) >= 0 )
|
||||
{
|
||||
if( c == '"' )
|
||||
{
|
||||
c = fgetc( f );
|
||||
if( c != '"' )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( length < sizeof value - 1 )
|
||||
{
|
||||
value[ length++ ] = (gchar) c;
|
||||
}
|
||||
}
|
||||
|
||||
value[ length ] = 0;
|
||||
|
||||
if( !tag_end )
|
||||
{
|
||||
// Found name="value" construct between < and >
|
||||
g_debug( "%s %s=%s", tag_name, name, value );
|
||||
|
||||
// Process name/value pair
|
||||
if( strcmp( tag_name, "Keyring" ) == 0 )
|
||||
{
|
||||
if( strcmp( name, "Created" ) == 0 )
|
||||
{
|
||||
make_created_hash( created_hash, value );
|
||||
}
|
||||
}
|
||||
else if( strcmp( tag_name, "Backbone" ) == 0 )
|
||||
{
|
||||
group_ga_valid = 0;
|
||||
device_ia_valid = 0;
|
||||
|
||||
if( strcmp( name, "MulticastAddress" ) == 0 )
|
||||
{
|
||||
read_ip_addr( backbone_mca, value );
|
||||
backbone_mca_valid = 1;
|
||||
}
|
||||
else if( strcmp( name, "Key" ) == 0 )
|
||||
{
|
||||
if( backbone_mca_valid )
|
||||
{
|
||||
add_mca_key( backbone_mca, value, password_hash, created_hash, f2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( strcmp( tag_name, "Group" ) == 0 )
|
||||
{
|
||||
backbone_mca_valid = 0;
|
||||
device_ia_valid = 0;
|
||||
|
||||
if( strcmp( name, "Address" ) == 0 )
|
||||
{
|
||||
group_ga = read_ga( value );
|
||||
group_ga_valid = 1;
|
||||
}
|
||||
else if( strcmp( name, "Key" ) == 0 )
|
||||
{
|
||||
if( group_ga_valid )
|
||||
{
|
||||
add_ga_key( group_ga, value, password_hash, created_hash, f2 );
|
||||
}
|
||||
}
|
||||
else if( strcmp( name, "Senders" ) == 0 )
|
||||
{
|
||||
if( group_ga_valid )
|
||||
{
|
||||
// Add senders given by space separated list of KNX IAs
|
||||
static const gchar delim[] = " ,";
|
||||
const gchar* token = strtok( value, delim );
|
||||
while( token )
|
||||
{
|
||||
add_ga_sender( group_ga, token, f2 );
|
||||
token = strtok( NULL, delim );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( strcmp( tag_name, "Device" ) == 0 )
|
||||
{
|
||||
backbone_mca_valid = 0;
|
||||
group_ga_valid = 0;
|
||||
|
||||
if( strcmp( name, "IndividualAddress" ) == 0 )
|
||||
{
|
||||
device_ia = read_ia( value );
|
||||
device_ia_valid = 1;
|
||||
}
|
||||
else if( strcmp( name, "ToolKey" ) == 0 )
|
||||
{
|
||||
if( device_ia_valid )
|
||||
{
|
||||
add_ia_key( device_ia, value, password_hash, created_hash, f2 );
|
||||
}
|
||||
}
|
||||
else if( strcmp( name, "SequenceNumber" ) == 0 )
|
||||
{
|
||||
if( device_ia_valid )
|
||||
{
|
||||
add_ia_seq( device_ia, value, f2 );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
backbone_mca_valid = 0;
|
||||
group_ga_valid = 0;
|
||||
device_ia_valid = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( c < 0 ) // EOF
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( !g_ascii_isspace( c ) )
|
||||
{
|
||||
tag_name_done = 1;
|
||||
*name = '\0';
|
||||
*value = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
c = fgetc( f );
|
||||
}
|
||||
|
||||
fclose( f );
|
||||
}
|
||||
|
||||
if( f2 && f2 != stdout )
|
||||
{
|
||||
fclose( f2 );
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
||||
*
|
||||
* Local variables:
|
||||
* c-basic-offset: 2
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vi: set shiftwidth=2 tabstop=8 expandtab:
|
||||
* :indentSize=2:tabSize=8:noTabs=true:
|
||||
*/
|
|
@ -0,0 +1,97 @@
|
|||
/* packet-knxip_decrypt.h
|
||||
* Decryption keys and decryption functions for KNX/IP Dissector
|
||||
* Copyright 2018, ise GmbH <Ralf.Nasilowski@ise.de>
|
||||
*
|
||||
* Wireshark - Network traffic analyzer
|
||||
* By Gerald Combs <gerald@wireshark.org>
|
||||
* Copyright 1998 Gerald Combs
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
#ifndef KNXIP_CRYPT_H
|
||||
#define KNXIP_CRYPT_H
|
||||
|
||||
#define KNX_KEY_LENGTH 16
|
||||
|
||||
// Calculate MAC for KNX IP Security or KNX Data Security
|
||||
void knx_ccm_calc_cbc_mac( guint8 p_mac[ KNX_KEY_LENGTH ], const guint8 key[ KNX_KEY_LENGTH ],
|
||||
const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length,
|
||||
const guint8 b_0[ KNX_KEY_LENGTH ] );
|
||||
|
||||
// Calculate MAC for KNX IP Security
|
||||
void knxip_ccm_calc_cbc_mac( guint8 p_mac[ KNX_KEY_LENGTH ], const guint8 key[ KNX_KEY_LENGTH ],
|
||||
const guint8* a_bytes, gint a_length, const guint8* p_bytes, gint p_length,
|
||||
const guint8* nonce, guint8 nonce_length );
|
||||
|
||||
// Encrypt for KNX IP Security or KNX Data Security
|
||||
guint8* knx_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length,
|
||||
const guint8* mac, guint8 mac_length, const guint8 ctr_0[ KNX_KEY_LENGTH ], guint8 s0_bytes_used_for_mac);
|
||||
|
||||
// Encrypt for KNX IP Security
|
||||
guint8* knxip_ccm_encrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* p_bytes, gint p_length,
|
||||
const guint8 mac[ KNX_KEY_LENGTH ], const guint8* nonce, guint8 nonce_length );
|
||||
|
||||
// Decrypt for KNX IP Security
|
||||
guint8* knxip_ccm_decrypt( guint8* p_result, const guint8 key[ KNX_KEY_LENGTH ], const guint8* crypt, gint crypt_length,
|
||||
const guint8* nonce, guint8 nonce_length );
|
||||
|
||||
// For importing keyring.XML file exported from ETS:
|
||||
|
||||
struct knx_keyring_mca_keys
|
||||
{
|
||||
struct knx_keyring_mca_keys* next;
|
||||
guint8 mca[ 4 ]; // IP multicast address
|
||||
guint8 key[ KNX_KEY_LENGTH ]; // encryption key
|
||||
};
|
||||
|
||||
struct knx_keyring_ga_keys
|
||||
{
|
||||
struct knx_keyring_ga_keys* next;
|
||||
guint16 ga; // KNX GA
|
||||
guint8 key[ KNX_KEY_LENGTH ]; // encryption key
|
||||
};
|
||||
|
||||
struct knx_keyring_ga_senders
|
||||
{
|
||||
struct knx_keyring_ga_senders* next;
|
||||
guint16 ga; // KNX GA
|
||||
guint16 ia; // sending KNX IA
|
||||
};
|
||||
|
||||
struct knx_keyring_ia_keys
|
||||
{
|
||||
struct knx_keyring_ia_keys* next;
|
||||
guint16 ia; // KNX IA
|
||||
guint8 key[ KNX_KEY_LENGTH ]; // encryption key
|
||||
};
|
||||
|
||||
struct knx_keyring_ia_seqs
|
||||
{
|
||||
struct knx_keyring_ia_seqs* next;
|
||||
guint16 ia; // KNX IA
|
||||
guint64 seq; // 6-byte sequence number
|
||||
};
|
||||
|
||||
extern struct knx_keyring_mca_keys* knx_keyring_mca_keys;
|
||||
extern struct knx_keyring_ga_keys* knx_keyring_ga_keys;
|
||||
extern struct knx_keyring_ga_senders* knx_keyring_ga_senders;
|
||||
extern struct knx_keyring_ia_keys* knx_keyring_ia_keys;
|
||||
extern struct knx_keyring_ia_seqs* knx_keyring_ia_seqs;
|
||||
|
||||
// Read KNX security keys from keyring XML file (exported from ETS)
|
||||
void read_knx_keyring_xml_file( const gchar* key_file, const gchar* password, const gchar* key_info_file );
|
||||
|
||||
#endif // KNXIP_CRYPT_H
|
||||
|
||||
/*
|
||||
* Editor modelines - https://www.wireshark.org/tools/modelines.html
|
||||
*
|
||||
* Local variables:
|
||||
* c-basic-offset: 2
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: nil
|
||||
* End:
|
||||
*
|
||||
* vi: set shiftwidth=2 tabstop=8 expandtab:
|
||||
* :indentSize=2:tabSize=8:noTabs=true:
|
||||
*/
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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>
|
|
@ -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$'))
|
||||
|
|
Loading…
Reference in New Issue