mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-22 08:11:44 +00:00
10_ZWave.pm: security added w/o wakeup modifications (Forum #38587)
git-svn-id: https://svn.fhem.de/fhem/trunk@9203 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
c1e818663f
commit
687bc1bea2
@ -224,7 +224,11 @@ my %zwave_class = (
|
||||
'(hex($1)&0x40 ? ", identical":", different")',
|
||||
"^..600a(.*)"=> 'ZWave_mcCapability($hash, $1)' } },
|
||||
ZIP_PORTAL => { id => '61' },
|
||||
DOOR_LOCK => { id => '62' },
|
||||
DOOR_LOCK => { id => '62',
|
||||
get => { doorLockOperation => '02',
|
||||
doorLockConfiguration => '05'},
|
||||
parse => { "..6203(.*)" => 'ZWave_DoorLockOperationReport($hash, $1)',
|
||||
"..6206(.*)" => 'ZWave_DoorLockConfigReport($hash, $1)'} },
|
||||
USER_CODE => { id => '63',
|
||||
set => { userCode => 'ZWave_userCodeSet("%s")' },
|
||||
get => { userCode => "02%02x" },
|
||||
@ -361,13 +365,18 @@ my %zwave_class = (
|
||||
AV_RENDERER_STATUS => { id => '96' },
|
||||
AV_CONTENT_SEARCH_MD => { id => '97' },
|
||||
SECURITY => { id => '98',
|
||||
set => { "secKey" => '(undef, "06%s")',
|
||||
"secScheme" => "0400",
|
||||
"secNonce" => "40" },
|
||||
get => { "secSupported" => "02" },
|
||||
parse => { "..9803(.*)" => '"secSupported:$1"',
|
||||
set => { "secScheme" => 'ZWave_sec($hash, "0400")',
|
||||
"sendNonce" => 'ZWave_secCreateNonce($hash)',
|
||||
"secEncap" => 'ZWave_sec($hash, "%s")' },
|
||||
get => { "secSupported" => 'ZWave_sec($hash, "02")' ,
|
||||
"secNonce" => 'ZWave_sec($hash, "40")'},
|
||||
parse => { "..9803(.*)" => 'ZWave_secSupported($hash, $1)',
|
||||
"..9805(.*)" => 'ZWave_secureInit($hash, $1)', # secScheme
|
||||
"..9880(.*)" => 'ZWave_secureInit($hash, $1)' } },
|
||||
"..9807" => 'ZWave_secNetWorkKeyVerify($hash)',
|
||||
"..9840" => 'ZWave_secNonceRequestReceived($hash)',
|
||||
"..9880(.*)" => 'ZWave_secNonceReceived($hash, $1)',
|
||||
"..9881(.*)" => 'ZWave_secDecrypt($hash, $1, 0)',
|
||||
"..98c1(.*)" => 'ZWave_secDecrypt($hash, $1, 1)' } },
|
||||
AV_TAGGING_MD => { id => '99' },
|
||||
IP_CONFIGURATION => { id => '9a' },
|
||||
ASSOCIATION_COMMAND_CONFIGURATION
|
||||
@ -416,6 +425,8 @@ use vars qw(%zwave_deviceSpecial);
|
||||
init => { ORDER=>50, CMD => '"get $NAME zwavePlusInfo"' } } }
|
||||
);
|
||||
|
||||
my $crypt_Rijndael = 0;
|
||||
|
||||
sub
|
||||
ZWave_Initialize($)
|
||||
{
|
||||
@ -427,10 +438,18 @@ ZWave_Initialize($)
|
||||
$hash->{UndefFn} = "ZWave_Undef";
|
||||
$hash->{ParseFn} = "ZWave_Parse";
|
||||
$hash->{AttrList} = "IODev do_not_notify:1,0 noExplorerFrames:1,0 ".
|
||||
"ignore:1,0 dummy:1,0 showtime:1,0 classes vclasses $readingFnAttributes";
|
||||
"ignore:1,0 dummy:1,0 showtime:1,0 classes vclasses ".
|
||||
"secure_classes $readingFnAttributes";
|
||||
map { $zwave_id2class{lc($zwave_class{$_}{id})} = $_ } keys %zwave_class;
|
||||
|
||||
$hash->{FW_detailFn} = "ZWave_fhemwebFn";
|
||||
|
||||
eval { require Crypt::Rijndael; };
|
||||
if($@) {
|
||||
Log 3, "ZWave: cannot load Crypt::Rijndael, SECURITY class disabled";
|
||||
} else {
|
||||
$crypt_Rijndael = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -661,7 +680,21 @@ ZWave_Cmd($$@)
|
||||
} else {
|
||||
my $len = sprintf("%02x", length($cmdFmt)/2+1);
|
||||
$data = "13$id$len$cmdId${cmdFmt}$cmdEf"; # 13==SEND_DATA
|
||||
}
|
||||
|
||||
$data .= $id; # callback=>id
|
||||
|
||||
if ($data =~ m/(......)(....)(.*)(....)/) {
|
||||
my $cc_cmd=$2;
|
||||
my $payload=$3;
|
||||
|
||||
#check message here for needed encryption (SECURITY)
|
||||
if (ZWave_isSecureClass($hash, $cc_cmd)) {
|
||||
my $interceptedMSG = $cc_cmd . $payload;
|
||||
# message stored in reading, will be processed when nonce arrives
|
||||
ZWave_putSecMsg($hash, $interceptedMSG);
|
||||
return ZWave_Get($hash, $name, "secNonce");
|
||||
}
|
||||
}
|
||||
|
||||
if($baseClasses =~ m/WAKE_UP/) {
|
||||
@ -669,19 +702,18 @@ ZWave_Cmd($$@)
|
||||
my @arr = ();
|
||||
$baseHash->{WakeUp} = \@arr;
|
||||
}
|
||||
my $awake = ($baseHash->{lastMsgTimestamp} &&
|
||||
gettimeofday() - $baseHash->{lastMsgTimestamp} < 3);
|
||||
my $tdiff = gettimeofday() - $baseHash->{lastMsgTimestamp};
|
||||
my $awake = ($baseHash->{lastMsgTimestamp} && $tdiff < 3);
|
||||
|
||||
if(!$awake) {
|
||||
push @{$baseHash->{WakeUp}}, $data.$id;
|
||||
if(!$awake && ($data !~ m/......9880.*/) ) {
|
||||
Log3 $name, 5, "Device not awake (tdiff= $tdiff),".
|
||||
" command ($data) stored in sendstack";
|
||||
push @{$baseHash->{WakeUp}}, $data;
|
||||
return (AttrVal($name,"verbose",3) > 2 ?
|
||||
"Scheduled for sending after WAKEUP" : undef);
|
||||
}
|
||||
|
||||
} else {
|
||||
$data .= $id; # callback=>id
|
||||
|
||||
}
|
||||
|
||||
IOWrite($hash, "00", $data);
|
||||
|
||||
my $val;
|
||||
@ -981,6 +1013,111 @@ ZWave_multilevelParse($$$)
|
||||
int(@{$ml->{st}}) > $sc ? $ml->{st}->[$sc] : "");
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_DoorLockOperationReport($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport received: $arg";
|
||||
|
||||
if ($arg !~ m/(..)(..)(..)(..)(..)/) {
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport wrong format received";
|
||||
}
|
||||
my $DoorLockMode = $1;
|
||||
my $DoorLockHandleModes = hex($2);
|
||||
my $DoorCondition = hex($3);
|
||||
my $DoorLockTimeoutMinutes = hex($4);
|
||||
my $DoorLockTimeoutSeconds = hex($5);
|
||||
|
||||
my $dlm ="Door is ";
|
||||
if ($DoorLockMode eq '00') {
|
||||
$dlm .= "unsecured";
|
||||
} elsif ($DoorLockMode eq '01') {
|
||||
$dlm .= "unsecured with timeout";
|
||||
} elsif ($DoorLockMode eq '10') {
|
||||
$dlm .= "unsecured for inside door handles";
|
||||
} elsif ($DoorLockMode eq '11') {
|
||||
$dlm .= "unsecured for inside door handles with timeout";
|
||||
} elsif ($DoorLockMode eq '20') {
|
||||
$dlm .= "unsecured for outside door handles";
|
||||
} elsif ($DoorLockMode eq '21') {
|
||||
$dlm .= "unsecured for outside door handles with timeout";
|
||||
} elsif ($DoorLockMode eq 'ff') {
|
||||
$dlm .= "secured";
|
||||
}
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport DoorLockMode = $dlm";
|
||||
|
||||
my $odlhm = sprintf ("%04b", ($DoorLockHandleModes & 0xf0)>>4);
|
||||
my $idlhm = sprintf ("%04b", ($DoorLockHandleModes & 0x0f));
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport OutsideDoorHandles: $odlhm,".
|
||||
" InsideDoorHandles: $idlhm";
|
||||
|
||||
my $dc_door = 'Door ' . ($DoorCondition & 0x01) ? 'open' : 'closed';
|
||||
my $dc_bolt = 'Bolt ' . ($DoorCondition & 0x02) ? 'locked' : 'unlocked';
|
||||
my $dc_latch = 'Latch ' . ($DoorCondition & 0x04) ? 'open' : 'closed';
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport Door Conditions: $dc_door ".
|
||||
"$dc_bolt $dc_latch";
|
||||
|
||||
if ($DoorLockTimeoutMinutes == 254) {
|
||||
$DoorLockTimeoutMinutes = 'not_supported';
|
||||
}
|
||||
if ($DoorLockTimeoutSeconds == 254) {
|
||||
$DoorLockTimeoutSeconds = 'not_supported';
|
||||
}
|
||||
|
||||
my $to = 'Timeout ' . ' min=' . $DoorLockTimeoutMinutes . ' sec=' .
|
||||
$DoorLockTimeoutSeconds;
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport $to";
|
||||
|
||||
return "DoorLockOperation: $arg $dlm outsidehandles $odlhm insidehandles ".
|
||||
"$idlhm $dc_door $dc_bolt $dc_latch $to";
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_DoorLockConfigReport($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name, 5, "$name: DoorLockConfigurationReport received: $arg";
|
||||
|
||||
if ($arg !~ m/(..)(..)(..)(..)/) {
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport wrong format received";
|
||||
}
|
||||
my $OperationMode = $1;
|
||||
my $DoorLockHandleStates = hex($2);
|
||||
my $DoorLockTimeoutMinutes = hex($3);
|
||||
my $DoorLockTimeoutSeconds = hex($4);
|
||||
|
||||
my $ot ='';
|
||||
if ($OperationMode eq '01') {
|
||||
$ot = "Constant ";
|
||||
} elsif ($OperationMode eq '02') {
|
||||
$ot = "Timed";
|
||||
}
|
||||
$ot .= ' operation';
|
||||
Log3 $name, 0, "$name: DoorLockConfigurationReport $ot";
|
||||
|
||||
my $odlhs = sprintf ("%04b", ($DoorLockHandleStates & 0xf0)>>4);
|
||||
my $idlhs = sprintf ("%04b", ($DoorLockHandleStates & 0x0f));
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport OutsideDoorHandles: $odlhs, ".
|
||||
"InsideDoorHandles: $idlhs";
|
||||
|
||||
if ($DoorLockTimeoutMinutes == 254) {
|
||||
$DoorLockTimeoutMinutes = 'not_supported';
|
||||
}
|
||||
if ($DoorLockTimeoutSeconds == 254) {
|
||||
$DoorLockTimeoutSeconds = 'not_supported';
|
||||
}
|
||||
|
||||
my $to = 'Timeout ' . ' min=' . $DoorLockTimeoutMinutes . ' sec=' .
|
||||
$DoorLockTimeoutSeconds;
|
||||
|
||||
Log3 $name, 0, "$name: DoorLockOperationReport $to";
|
||||
|
||||
return "DoorLockConfiguration: $arg $ot outsidehandles $odlhs ".
|
||||
"insidehandles $idlhs $to";
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_SetClasses($$$$)
|
||||
{
|
||||
@ -1660,43 +1797,554 @@ ZWave_sensorbinaryV2Parse($$)
|
||||
":".$value;
|
||||
}
|
||||
|
||||
##############################################
|
||||
# SECURITY (start)
|
||||
##############################################
|
||||
|
||||
sub
|
||||
ZWave_secureInit(@)
|
||||
{
|
||||
my ($hash, $param) = @_;
|
||||
my $iodev = $hash->{IODev};
|
||||
my $name = $hash->{NAME};
|
||||
if (!ZWave_secIsEnabled($hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hash->{secStatus} = 0 if(!$hash->{secStatus});
|
||||
my $status = ++$hash->{secStatus};
|
||||
|
||||
my @stTxt = ( "secScheme", "secKey", "secNonce", "done");
|
||||
my @stTxt = ( "secScheme", "secNonceRequest");
|
||||
my $stTxt = ($status > int(@stTxt) ? "ERR" : $stTxt[$status-1]);
|
||||
Log3 $iodev, 4, "*** $hash->{NAME}: secureInit status $status/$stTxt";
|
||||
|
||||
if($status == 1) {
|
||||
ZWave_Set($hash, $name, "secScheme");
|
||||
return ""; # not evaluated
|
||||
|
||||
} elsif($status == 2) {
|
||||
Log3 $iodev, 4, "secScheme report: $param";
|
||||
my $key = AttrVal($iodev->{NAME}, "networkKey", "");
|
||||
ZWave_Set($hash, $name, ("secKey", $key));
|
||||
return undef; # No Event/Reading
|
||||
|
||||
} elsif($status == 3) {
|
||||
#IOWrite($hash, "secKey ACK", "");
|
||||
ZWave_Set($hash, $name, "secNonce");
|
||||
return undef; # No Event/Reading
|
||||
|
||||
ZWave_Get($hash, $name, "secNonce");
|
||||
return undef;
|
||||
} else {
|
||||
Log3 $iodev, 4, "secNonce report: $param";
|
||||
Log3 $name, 5, "$name: secureInit called with invalid status";
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_isSecureClass($$)
|
||||
{
|
||||
my ($hash, $cc_cmd) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if ($cc_cmd =~m/(..)(..)/) {
|
||||
my ($cc, $cmd) = ($1, $2);
|
||||
my $cc_name = $zwave_id2class{lc($cc)};
|
||||
my $sec_classes = AttrVal($name, "secure_classes", "");
|
||||
|
||||
if ($sec_classes =~ m/$cc_name/) {
|
||||
Log3 $name, 5, "$name: $cc_name is a secured class!";
|
||||
return (1);
|
||||
}
|
||||
|
||||
# some SECURITY commands need to be encrypted allways
|
||||
if ($cc eq '98') {
|
||||
if ($cmd eq '02' || # SupportedGet
|
||||
$cmd eq '06' || # NetworkKeySet
|
||||
$cmd eq '08' ){ # SchemeInherit
|
||||
Log3 $name, 5, "$name: Security commands will be encrypted!";
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
# these SECURITY commands should not be encrypted
|
||||
# SchemeGet = 0x04, NonceGet = 0x40, NonceReport = 0x80
|
||||
# MessageEncap = 0x81 is already encrypted
|
||||
# MessageEncapNonceGet = 0xc1 is already encrypted
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
ZWave_secSupported($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $iodev = $hash->{IODev};
|
||||
my $id = $hash->{id};
|
||||
|
||||
if (!ZWave_secIsEnabled($hash)) {
|
||||
return;
|
||||
};
|
||||
|
||||
Log3 $name, 5, "$name: Secured Classes Supported: $arg";
|
||||
|
||||
if ($arg =~ m/(..)(.*)/) {
|
||||
if ($1 ne '00') {
|
||||
Log3 $name, 1, "$name: Multi part message report for secure classes ".
|
||||
"can not be handled!";
|
||||
}
|
||||
my @sec_classes;
|
||||
my $sec_classes = $2;
|
||||
for my $sec_classId (grep /../, split(/(..)/, lc($sec_classes))) {
|
||||
push @sec_classes, $zwave_id2class{lc($sec_classId)} ?
|
||||
$zwave_id2class{lc($sec_classId)} : "UNKNOWN_".lc($sec_classId);
|
||||
}
|
||||
$attr{$name}{secure_classes} = join(" ", @sec_classes)
|
||||
if (@sec_classes && !$attr{$name}{secure_classes});
|
||||
}
|
||||
|
||||
if ($iodev->{secInitName} && $hash->{secStatus}) {
|
||||
# Secure inclusion is finished, remove readings and execute "normal" init
|
||||
delete $iodev->{secInitName};
|
||||
delete $hash->{secStatus};
|
||||
return ZWave_execInits($hash, 0);
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secNonceReceived($$)
|
||||
{
|
||||
my ($hash, $r_nonce_hex) = @_;
|
||||
my $iodev = $hash->{IODev};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (!ZWave_secIsEnabled($hash))
|
||||
{
|
||||
return;
|
||||
}
|
||||
setReadingsVal($hash, "received_nonce", $r_nonce_hex, TimeNow());
|
||||
|
||||
# If a nonce is received during secure_Include, send the networkkey...
|
||||
if ($hash->{secStatus} && ($hash->{secStatus} == 2)) {
|
||||
my $key_hex = AttrVal($iodev->{NAME}, "networkKey", "");
|
||||
my $mynonce_hex = substr (ZWave_secCreateNonce($hash), 2, 16);
|
||||
my $cryptedNetworkKeyMsg = ZWave_secNetworkkeySet($r_nonce_hex,
|
||||
$mynonce_hex, $key_hex, $hash->{id});
|
||||
ZWave_Set($hash, $name, ("secEncap", $cryptedNetworkKeyMsg));
|
||||
$hash->{secStatus}++;
|
||||
readingsSingleUpdate($hash, "SECURITY", 'INITIALIZING (Networkkey sent)',0);
|
||||
Log3 $name, 5, "$name: SECURITY initializing, networkkey sent";
|
||||
|
||||
# start timer here to check state if networkkey was not verified
|
||||
InternalTimer(gettimeofday()+25, "ZWave_secTestNetworkkeyVerify", $hash, 0);
|
||||
return undef;
|
||||
}
|
||||
|
||||
# if nonce is received, we should have stored a message for encryption
|
||||
my $secMsg = ZWave_getSecMsg($hash);
|
||||
if (!$secMsg) {
|
||||
Log3 $name, 1, "$name: Error, nonce reveived but no stored command for ".
|
||||
"encryption found";
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $enc = ZWave_secEncrypt($hash, $r_nonce_hex, $secMsg);
|
||||
ZWave_Set($hash, $name, ("secEncap", $enc));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
ZWave_putSecMsg_old ($$)
|
||||
{
|
||||
my ($hash, $s) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $secMsg = ReadingsVal($name, "secMsg", undef);
|
||||
if ($secMsg) {
|
||||
my @secMsg = split (' ', $secMsg);
|
||||
push(@secMsg, $s);
|
||||
$secMsg = join(" ",@secMsg);
|
||||
} else {
|
||||
$secMsg = $s;
|
||||
}
|
||||
setReadingsVal($hash, "secMsg", $secMsg, TimeNow());
|
||||
Log3 $name, 3, "$name SECURITY: $s stored for encryption";
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_putSecMsg ($$)
|
||||
{
|
||||
my ($hash, $s) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (!$hash->{secMsg}) {
|
||||
my @arr = ();
|
||||
$hash->{secMsg} = \@arr;
|
||||
}
|
||||
push @{$hash->{secMsg}}, $s;
|
||||
Log3 $name, 3, "$name SECURITY: $s stored for encryption";
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_getSecMsg ($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $secMsg = $hash->{secMsg};
|
||||
if ($secMsg && @{$secMsg}) {
|
||||
my $ret = shift(@{$secMsg});
|
||||
if ($ret) {
|
||||
Log3 $name, 3, "$name SECURITY: $ret retrieved for encryption";
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
Log3 $name, 1, "$name: no stored commands in Internal secMsg found";
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secNonceRequestReceived ($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
if (!ZWave_secIsEnabled($hash)) {
|
||||
return;
|
||||
}
|
||||
return ZWave_Set($hash, $hash->{NAME}, "sendNonce");
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secIsEnabled ($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $secStatus = ReadingsVal($hash->{NAME}, "SECURITY", "DISABLED");
|
||||
if ($secStatus =~ m/DISABLED/) {
|
||||
Log3 $hash->{NAME}, 1, "$hash->{NAME} SECURITY $secStatus (command dropped)";
|
||||
return (0);
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_sec ($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
return (ZWave_secIsEnabled($hash) ? ("", $arg) : ("",'00'));
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secCreateNonce($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
if (ZWave_secIsEnabled($hash)) {
|
||||
my $nonce = ZWave_getNonce();
|
||||
setReadingsVal($hash, "send_nonce", $nonce, TimeNow());
|
||||
return ("",'80'.$nonce);
|
||||
} else {
|
||||
return ("", '00');
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_getNonce()
|
||||
{
|
||||
my $nonce='';
|
||||
for (my $i = 0; $i <8; $i++) {
|
||||
$nonce .= sprintf "%02x",int(rand(256));
|
||||
}
|
||||
return $nonce;
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secNetWorkKeyVerify ($)
|
||||
{
|
||||
my($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $iodev = $hash->{IODev};
|
||||
|
||||
if (!ZWave_secIsEnabled($hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#Log3 $iodev, 4, "$name: NetworkKeyVerify received, SECURITY is enabled";
|
||||
readingsSingleUpdate($hash, "SECURITY", 'ENABLED', 0);
|
||||
Log3 $name, 2, "$name: SECURITY enabled, networkkey was verified";
|
||||
ZWave_Get($hash, $name, ("secSupported"));
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secTestNetworkkeyVerify ($)
|
||||
{
|
||||
my($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $sec_status = ReadingsVal($name, "SECURITY", undef);
|
||||
if ($sec_status !~ m/ENABLED/) {
|
||||
readingsSingleUpdate($hash, "SECURITY",
|
||||
'DISABLED (networkkey not verified and timer expired)', 0);
|
||||
Log3 $name, 1, "$name: SECURITY disabled, networkkey was not verified ".
|
||||
"and timer expired";
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secEncrypt($$$)
|
||||
{
|
||||
my ($hash, $r_nonce_hex, $plain) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $iodev = $hash->{IODev};
|
||||
my $id = $hash->{id};
|
||||
|
||||
my $init_enc_key = pack 'H*', 'a' x 32;
|
||||
my $init_auth_key = pack 'H*', '5' x 32;
|
||||
|
||||
my $s_nonce_hex = ZWave_getNonce();
|
||||
my $iv = pack 'H*', $s_nonce_hex . $r_nonce_hex;
|
||||
my $key = pack 'H*', AttrVal($iodev->{NAME}, "networkKey", "");
|
||||
my $enc_key = ZWave_secEncryptECB($key, $init_enc_key);
|
||||
my $auth_key = ZWave_secEncryptECB($key, $init_auth_key);
|
||||
|
||||
my $seq = '00'; # Sequencebyte -> need to be calculated for longer messages
|
||||
my $msg_hex = $seq . $plain;
|
||||
my $out_hex = ZWave_secEncryptOFB ($enc_key, $iv, $msg_hex);
|
||||
|
||||
my $auth_msg_hex = '8101';
|
||||
$auth_msg_hex .= sprintf "%02x", hex($hash->{id});
|
||||
$auth_msg_hex .= sprintf "%02x", (length ($out_hex))/2;
|
||||
$auth_msg_hex .= $out_hex;
|
||||
|
||||
Log3 $name, 5, "$name: secEncrypt plain:$msg_hex enc:$out_hex";
|
||||
|
||||
my $auth_code_hex = ZWave_secGenerateAuth($auth_key, $iv, $auth_msg_hex);
|
||||
my $enc_msg_hex = '81' . $s_nonce_hex . $out_hex . substr($r_nonce_hex, 0, 2)
|
||||
. $auth_code_hex;
|
||||
return $enc_msg_hex;
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secDecrypt($$$)
|
||||
{
|
||||
my ($hash, $data, $newnonce) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $iodev = $hash->{IODev};
|
||||
|
||||
if (!ZWave_secIsEnabled($hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
my $init_enc_key = pack 'H*', 'a' x 32;
|
||||
my $init_auth_key = pack 'H*', '5' x 32;
|
||||
|
||||
my $key = pack 'H*', AttrVal($iodev->{NAME}, "networkKey", "");
|
||||
my $enc_key = ZWave_secEncryptECB($key, $init_enc_key);
|
||||
my $auth_key = ZWave_secEncryptECB($key, $init_auth_key);
|
||||
|
||||
my $s_nonce_hex = ReadingsVal($name, "send_nonce", undef);
|
||||
if (!$s_nonce_hex) {
|
||||
Log3 $name, 1, "$name: Error, no send_nonce to decrypt message available";
|
||||
return "";
|
||||
}
|
||||
|
||||
readingsSingleUpdate($hash, "send_nonce", undef, 0);
|
||||
|
||||
# encrypted message format:
|
||||
# data= bcb328fe5d924a402b2901fc2699cc3bcacd30e0
|
||||
# bcb328fe5d924a40 = 8 byte r_nonce
|
||||
# 2b2901 = encrypted message, variable length
|
||||
# fc = s_nonce-Id (= first byte of s_nonce)
|
||||
# 2699cc3bcacd30e0 = 8 byte authentification code
|
||||
if ($data !~ m/^(................)(.*)(..)(................)$/) {
|
||||
Log3 $name, 1, "$name: Error, wrong format of encrypted msg";
|
||||
#return (undef, undef);
|
||||
return "";
|
||||
}
|
||||
my ($r_nonce_hex, $msg_hex, $s_nonce_id_hex, $auth_code_hex) = ($1, $2, $3, $4);
|
||||
|
||||
my $iv = pack 'H*', $r_nonce_hex . $s_nonce_hex;
|
||||
my $out_hex = ZWave_secEncryptOFB ($enc_key, $iv, $msg_hex);
|
||||
|
||||
Log3 $name, 5, "$name: secDecrypt: decrypted cmd $out_hex";
|
||||
|
||||
# decoding sequence information
|
||||
# bit 76 reseved
|
||||
# bit 5 second frame (0x20)
|
||||
# bit 4 sequenced (0x10)
|
||||
# bit 3210 sequenceCounter (0x0f)
|
||||
my $seq = hex(substr ($out_hex, 0,2));
|
||||
my $sequenced = (($seq & 0x10) ? 1 : 0);
|
||||
my $secondFrame = (($seq & 0x20) ? 1 : 0);
|
||||
my $sequenceCounter = sprintf "%02x", ($seq & 0x0f);
|
||||
|
||||
Log3 $name, 5, "$name: secDecrypt: Sequencebyte $seq, sequenced ".
|
||||
"$sequenced, secondFrame $secondFrame, sequenceCounter $sequenceCounter";
|
||||
|
||||
# Rebuild message for authentification check
|
||||
# 81280103 '81' . <from-id> . <to-id> . <len> . <encrMsg>
|
||||
my $my_msg_hex = ($newnonce ? 'c1' : '81');
|
||||
$my_msg_hex .= sprintf "%02x", hex($hash->{id});
|
||||
$my_msg_hex .= '01';
|
||||
$my_msg_hex .= sprintf "%02x", (length ($msg_hex))/2;
|
||||
$my_msg_hex .= $msg_hex;
|
||||
|
||||
my $my_auth_code_hex = ZWave_secGenerateAuth($auth_key, $iv, $my_msg_hex);
|
||||
Log3 $name, 5, "$name: secDecrypt: calculated Authentication code ".
|
||||
"$my_auth_code_hex";
|
||||
|
||||
$out_hex = substr($out_hex, 2,length($out_hex)-2);
|
||||
|
||||
if ($auth_code_hex eq $my_auth_code_hex) {
|
||||
if ($sequenced && !$secondFrame){ # first frame of sequence message
|
||||
ZWave_secStoreFirstFrame($hash, $sequenceCounter, $out_hex);
|
||||
} else { # not first frame or not sequenced
|
||||
if ($sequenced && $secondFrame){
|
||||
my $firstFrame = ZWave_secRetrieveFirstFrame ($hash, $sequenceCounter);
|
||||
if ($firstFrame) {
|
||||
$out_hex = $firstFrame . $out_hex;
|
||||
} else {
|
||||
Log3 $name, 1, "$name: secDecrypt: first frame of message (sequence ".
|
||||
"$sequenceCounter) for decryption not found!";
|
||||
}
|
||||
}
|
||||
my $decryptedCmd = '000400';
|
||||
$decryptedCmd .= sprintf "%02x", hex($hash->{id});
|
||||
$decryptedCmd .= sprintf "%02x", (length ($out_hex))/2;
|
||||
$decryptedCmd .= $out_hex;
|
||||
|
||||
Log3 $name, 5, "$name: secDecrypt: parsing $decryptedCmd";
|
||||
ZWave_Parse($iodev, $decryptedCmd, undef);
|
||||
}
|
||||
} else {
|
||||
Log3 $name, 1, "$name: secDecrypt: Authentification code not verified, "
|
||||
."command $out_hex will be dropped!";
|
||||
if ($sequenced && $secondFrame){
|
||||
ZWave_secRetrieveFirstFrame ($hash, $sequenceCounter);
|
||||
}
|
||||
}
|
||||
|
||||
if ($newnonce == 1) {
|
||||
ZWave_Set($hash, $hash->{NAME}, "sendNonce");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secStoreFirstFrame ($$$) {
|
||||
my ($hash, $seqcnt, $framedata) = @_;
|
||||
my $framename = "Frame_$seqcnt";
|
||||
$hash->{$framename} = $framedata;
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secRetrieveFirstFrame ($$) {
|
||||
my ($hash, $seqcnt) = @_;
|
||||
my $framename = "Frame_$seqcnt";
|
||||
if ($hash->{$framename}) {
|
||||
my $ret = $hash->{$framename};
|
||||
if ($ret) {
|
||||
$hash->{$framename} = undef;
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
Log3 $hash->{NAME}, 1, "$hash->{NAME}: first frame of message (sequence ".
|
||||
"$seqcnt) for decryption not found!";
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secEncryptECB ($$)
|
||||
{
|
||||
my ($key, $iv) = @_;
|
||||
# $key and $iv as 'packed' hex-strings
|
||||
my $cipher_ecb = Crypt::Rijndael->new ($key, Crypt::Rijndael::MODE_ECB() );
|
||||
return $cipher_ecb->encrypt($iv);
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secEncryptOFB ($$$)
|
||||
{
|
||||
my ($key, $iv, $in_hex) = @_;
|
||||
# $key and $iv as 'packed' hex-strings, $in_hex as hex-string
|
||||
my $cipher_ofb = Crypt::Rijndael->new($key,
|
||||
Crypt::Rijndael::MODE_OFB() );
|
||||
$cipher_ofb->set_iv($iv);
|
||||
|
||||
# make sure that the blocksize is 16 bytes / 32 characters
|
||||
my $in_len = length($in_hex);
|
||||
if ($in_len % 32) {
|
||||
$in_hex .= '0' x (32 - ($in_len % 32));
|
||||
}
|
||||
my $out_hex = unpack 'H*', $cipher_ofb->encrypt(pack 'H*', $in_hex);
|
||||
return (substr ($out_hex, 0, $in_len));
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secGenerateAuth ($$$)
|
||||
{
|
||||
my ($key, $iv, $msg_hex) = @_;
|
||||
|
||||
my $cipher_ecb = Crypt::Rijndael->new ($key, Crypt::Rijndael::MODE_ECB() );
|
||||
my $enc_iv = ZWave_secEncryptECB($key, $iv);
|
||||
|
||||
# make sure that the blocksize is 16 bytes / 32 characters
|
||||
my $msg_len = length($msg_hex);
|
||||
if ($msg_len % 32) {
|
||||
$msg_hex .= '0' x (32 - ($msg_len % 32));
|
||||
}
|
||||
|
||||
my $temp=0;
|
||||
my $buff=0;
|
||||
my $buff_hex="";
|
||||
# xOR first block with encrypted iv
|
||||
# encrypt the result, repeat for all blocks using encrypted output
|
||||
# as input for xOR of next block
|
||||
for (my $i = 0; $i < (length($msg_hex)/32); $i++) {
|
||||
$buff_hex = substr($msg_hex, $i*32, 32);
|
||||
$buff = pack 'H*', $buff_hex;
|
||||
$temp = $buff ^ $enc_iv;
|
||||
$enc_iv = $cipher_ecb->encrypt($temp);
|
||||
};
|
||||
# only 8 byte used for message authentification code
|
||||
return unpack 'H16', $enc_iv;
|
||||
}
|
||||
|
||||
sub
|
||||
ZWave_secNetworkkeySet ($$$$)
|
||||
{
|
||||
my ($nonce_hex, $mynonce_hex, $key_plain_hex, $id_hex) = @_;
|
||||
my $name ="ZWave_secNetworkkeySet";
|
||||
|
||||
# The NetworkKeySet command message will be encrcpted and authentificated
|
||||
# with temporary keys that are created with the networkkey and default
|
||||
# keys for encryption and authentification as given below.
|
||||
my $init_enc_key = pack 'H*', 'a' x 32;
|
||||
my $init_auth_key = pack 'H*', '5' x 32;
|
||||
my $key_zero = pack 'H*', '0' x 32;
|
||||
my $nonce = pack 'H*', $nonce_hex;
|
||||
my $mynonce = pack 'H*', $mynonce_hex;
|
||||
|
||||
my $enc_key = ZWave_secEncryptECB($key_zero, $init_enc_key);
|
||||
my $auth_key = ZWave_secEncryptECB($key_zero, $init_auth_key);
|
||||
|
||||
my $iv = pack 'H*', $mynonce_hex . $nonce_hex;
|
||||
|
||||
# build 'plain-text' message to be encrypted
|
||||
# 0x00 = sequence byte -> only one frame
|
||||
# 0x98 = Security Class
|
||||
# 0x06 = NetworkKeySet
|
||||
$key_plain_hex = '009806'.$key_plain_hex;
|
||||
my $out_hex = ZWave_secEncryptOFB($enc_key, $iv, $key_plain_hex);
|
||||
|
||||
############ MAC generation ############################
|
||||
# build message for encryption
|
||||
# command, source-id, target-id
|
||||
# 0x81="Security_Message_Encapsulation" 0x01=Souce-ID (Controller = 0x01)
|
||||
my $in_hex = '8101' . $id_hex;
|
||||
$in_hex .= sprintf "%02x", length($out_hex)/2; # length of command
|
||||
$in_hex .= $out_hex; # encrypted network key
|
||||
|
||||
my $auth_hex = ZWave_secGenerateAuth ($auth_key, $iv, $in_hex);
|
||||
|
||||
# build encrypted message
|
||||
# Command Class will be added during sending -> do not prepend
|
||||
# 0x81 = Security_Message_Encapsulation
|
||||
$out_hex = '81' . $mynonce_hex . $out_hex . substr($nonce_hex, 0, 2) .
|
||||
$auth_hex;
|
||||
|
||||
return $out_hex;
|
||||
}
|
||||
|
||||
##############################################
|
||||
#AH: SECURITY (end)
|
||||
##############################################
|
||||
|
||||
sub
|
||||
ZWave_getHash($$$)
|
||||
@ -1741,11 +2389,13 @@ ZWave_wakeupTimer($)
|
||||
if($now - $hash->{lastMsgTimestamp} > 1) { # wakeupNoMoreInformation
|
||||
if($hash->{STATE} ne "TRANSMIT_NO_ACK") {
|
||||
my $nodeId = $hash->{id};
|
||||
my $cmdEf = (AttrVal($hash->{NAME}, "noExplorerFrames", 0) == 0 ? "25" : "05");
|
||||
my $cmdEf = (AttrVal($hash->{NAME},"noExplorerFrames",0)==0 ? "25":"05");
|
||||
IOWrite($hash, "00", "13${nodeId}028408${cmdEf}$nodeId");
|
||||
}
|
||||
|
||||
} else {
|
||||
InternalTimer($now+0.1, "ZWave_wakeupTimer", $hash, 0);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1761,11 +2411,11 @@ ZWave_sendWakeup($)
|
||||
Log3 $hash, 4, "Sending stored command: $wuCmd";
|
||||
}
|
||||
@{$hash->{WakeUp}}=();
|
||||
InternalTimer(gettimeofday()+0.1, "ZWave_wakeupTimer", $hash, 0);
|
||||
}
|
||||
|
||||
InternalTimer(gettimeofday()+01, "ZWave_wakeupTimer", $hash, 0);
|
||||
}
|
||||
|
||||
|
||||
###################################
|
||||
# 0004000a03250300 (sensor binary off for id 11)
|
||||
# { ZWave_Parse($defs{zd}, "0004000c028407", "") }
|
||||
@ -1787,10 +2437,6 @@ ZWave_Parse($$@)
|
||||
$cmd = $zw_func_id{$cmd} if($zw_func_id{$cmd});
|
||||
if($cmd eq "ZW_SEND_DATA") {
|
||||
Log3 $ioName, 2, "ERROR: cannot SEND_DATA: $arg" if($arg != 1);
|
||||
my $si = $iodev->{secInitName};
|
||||
ZWave_secureInit($defs{$si}) # No extra response for set networkKey
|
||||
if($si && $defs{$si} && $defs{$si}{secStatus} &&
|
||||
$defs{$si}{secStatus} == 2);
|
||||
return "";
|
||||
}
|
||||
if($cmd eq "SERIAL_API_SET_TIMEOUTS" && $arg =~ m/(..)(..)/) {
|
||||
@ -1827,22 +2473,40 @@ ZWave_Parse($$@)
|
||||
}
|
||||
|
||||
if($evt eq "protocolDone" && $arg =~ m/(..)../) {# done comes at addNode off
|
||||
delete $iodev->{secInitName};
|
||||
my $dh = $modules{ZWave}{defptr}{"$homeId $1"};
|
||||
return "" if(!$dh);
|
||||
|
||||
$dh->{lastMsgTimestamp} = gettimeofday();
|
||||
|
||||
if($iodev->{addSecure}) {
|
||||
readingsSingleUpdate($dh, "SECURITY",
|
||||
"INITIALIZING (starting secure inclusion)", 0);
|
||||
my $classes = AttrVal($dh->{NAME}, "classes", "");
|
||||
if($classes =~ m/SECURITY/) {
|
||||
if ($crypt_Rijndael == 1) {
|
||||
my $key = AttrVal($ioName, "networkKey", "");
|
||||
if($key) {
|
||||
$iodev->{secInitName} = $dh->{NAME};
|
||||
Log3 $ioName, 2, "ZWAVE Starting secure init";
|
||||
return ZWave_secureInit($dh);
|
||||
} else {
|
||||
Log3 $ioName, 2, "No secure inclusion as $ioName has no networkKey";
|
||||
Log3 $ioName,1,"No secure inclusion as $ioName has no networkKey";
|
||||
readingsSingleUpdate($dh, "SECURITY",
|
||||
'DISABLED (Networkkey not found)', 0);
|
||||
Log3 $dh->{NAME}, 1, "$dh->{NAME}: SECURITY disabled, ".
|
||||
"networkkey not found";
|
||||
}
|
||||
} else {
|
||||
readingsSingleUpdate($dh, "SECURITY",
|
||||
'DISABLED (Module Crypt_Rijndael not found)', 0);
|
||||
Log3 $dh->{NAME}, 1, "$dh->{NAME}: SECURITY disabled, module ".
|
||||
"Crypt_Rijndael not found";
|
||||
}
|
||||
} else {
|
||||
readingsSingleUpdate($dh, "SECURITY",
|
||||
'DISABLED (SECURITY not supported by device)', 0);
|
||||
Log3 $dh->{NAME}, 1, "$dh->{NAME}: secure inclusion failed, ".
|
||||
"SECURITY disabled, device does not support SECURITY command class";
|
||||
}
|
||||
}
|
||||
return ZWave_execInits($dh, 0);
|
||||
@ -2093,6 +2757,7 @@ s2Hex($)
|
||||
href="http://www.z-wave.com">www.z-wave.com</a> on details for this device family.
|
||||
This module is a client of the <a href="#ZWDongle">ZWDongle</a> module, which
|
||||
is directly attached to the controller via USB or TCP/IP.
|
||||
To use the SECURITY features, the Crypt-Rijndael perl module is needed.
|
||||
<br><br>
|
||||
<a name="ZWavedefine"></a>
|
||||
<b>Define</b>
|
||||
@ -2291,6 +2956,25 @@ s2Hex($)
|
||||
Parameters are: groupId, sceneId, dimmingDuration.
|
||||
</li>
|
||||
|
||||
<br><br><b>Class SECURITY</b>
|
||||
<li>secScheme<br>
|
||||
(internaly used to) set the security scheme '00'
|
||||
</li>
|
||||
<li>sendNonce<br>
|
||||
(internaly used to) send a security NONCE to the device
|
||||
</li>
|
||||
<li>secEncap<br>
|
||||
(internaly used to) send an encrypted message to the device
|
||||
</li>
|
||||
<li>Notes:<br>
|
||||
This class needs the installation of the perl module Crypt::Rijndael
|
||||
and a defined networkkey in the attributes of the ZWDongle device<br>
|
||||
Currently a secure inclusion can only be started from the command input
|
||||
with "set <ZWDongle_device_name> addNode <On|nwOn> sec"<br>
|
||||
These commands are only described here for completeness of the
|
||||
documentation, but are not intended for manual usage. These commands
|
||||
will be removed from the interface in future version.</li>
|
||||
|
||||
<br><br><b>Class SWITCH_ALL</b>
|
||||
<li>swaIncludeNone<br>
|
||||
the device does not react to swaOn and swaOff commands</li>
|
||||
@ -2413,6 +3097,11 @@ s2Hex($)
|
||||
specific config commands are available.
|
||||
</li>
|
||||
|
||||
<br><br><b>Class DOOR_LOCK</b>
|
||||
<li>doorLockConfiguration cfgAddress<br>
|
||||
return the configuration of the door lock.<br>
|
||||
</li>
|
||||
|
||||
<br><br><b>Class HRV_STATUS</b>
|
||||
<li>hrvStatus<br>
|
||||
report the current status (temperature, etc)
|
||||
@ -2508,6 +3197,26 @@ s2Hex($)
|
||||
returns the settings for a given group. Parameter is groupId
|
||||
</li>
|
||||
|
||||
<br><br><b>Class SECURITY</b>
|
||||
<li>secSupported<br>
|
||||
(internaly used to) request the command classes that are supported
|
||||
with SECURITY
|
||||
</li>
|
||||
<li>secNonce<br>
|
||||
(internaly used to) request a security NONCE from the device
|
||||
</li>
|
||||
<li>secEncap<br>
|
||||
(internaly used to) send an encrypted message to the device
|
||||
</li>
|
||||
<li>Notes:<br>
|
||||
This class needs the installation of the perl module Crypt::Rijndael and
|
||||
a defined networkkey in the attributes of the ZWDongle device<br>
|
||||
Currently a secure inclusion can only be started from the command input
|
||||
with "set <ZWDongle_device_name> addNode <On|nwOn> sec"<br>
|
||||
These commands are only described here for completeness of the
|
||||
documentation, but are not intended for manual usage. These commands
|
||||
will be removed from the interface in future version.</li>
|
||||
|
||||
<br><br><b>Class SENSOR_ALARM</b>
|
||||
<li>alarm alarmType<br>
|
||||
return the nodes alarm status of the requested alarmType. 00 = GENERIC,
|
||||
@ -2597,6 +3306,11 @@ s2Hex($)
|
||||
set/get commands depends on it. It contains a space separated list of
|
||||
class names (capital letters).
|
||||
</li>
|
||||
<li><a href="#secure_classes">secure_classes</a>
|
||||
This attribute is the result of the "get DEVICE secSupported" command. It
|
||||
contains a space seperated list of the the command classes that
|
||||
supported with SECURITY.
|
||||
</li>
|
||||
<li><a href="#vclasses">vclasses</a>
|
||||
This is the result of the "set DEVICE versionClassRequest" command, and
|
||||
contains the version information for each of the supported classes.
|
||||
@ -2719,6 +3433,10 @@ s2Hex($)
|
||||
<br><br><b>Class SCENE_CONTROLLER_CONF</b>
|
||||
<li>group_Id:scene dimmingDuration</li>
|
||||
|
||||
<br><br><b>Class SECURITY</b>
|
||||
<li>none<br>
|
||||
Note: the class security should work transparent to the sytem and is not
|
||||
intended to generate event</li>
|
||||
|
||||
<br><br><b>Class SENSOR_ALARM</b>
|
||||
<li>alarm_type_X:level Y node $nodeID seconds $seconds</li>
|
||||
|
Loading…
x
Reference in New Issue
Block a user