# $Id$ # License & technical informations =for comment # ################################################################ # # This script is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html. # A copy is found in the textfile GPL.txt and important notices to the license # from the author is found in LICENSE.txt distributed with these scripts. # # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # This copyright notice MUST APPEAR in all copies of the script! # # Homepage: http://www.fhem.de # ################################################################ # # Module 98_GoogleAuth.pm # written by pandabaer_de, revised by betateilchen # # based on informations from this website # https://blog.darkpan.com/article/6/Perl-and-Google-Authenticator.html # # requires additional perl modules # Convert::Base32 Authen::OATH Crypt::URandom # # on Debian systems, use apt-get to install appropriate packages # libconvert-base32-perl libauthen-oath-perl libcrypt-urandom-perl # ################################################################ # =cut # Development history =for comment # # 2017-01-15 - first commit to ./contrib # # 2017-01-15 - added: direct QR display after set # added: attribute ga_qrSize # added: FW_detailFn # added: attribute ga_labelName # added: reading lastCheck # removed: reading qr_url # added: show link to qrcode and key for manual use # in device details # added: set command "revoke" to prevent overwrite # of existing key # added: attribute ga_showKey # attribute ga_showLink # added: function gAuth(,) for easy use # added: FW_summaryFn # added: commandref documentation EN # # 2017-01-15 - published to FHEM # fixed: problem on iOS if label contains spaces # added: issuer=FHEM in qr-code # # 2017-01-16 - added: attributes ga_showQR, ga_strictCheck # removed: FW_summaryFn (not really useful) # =cut package main; use strict; use warnings; use Convert::Base32; use Authen::OATH; use URI::Escape; use Crypt::URandom qw( urandom ); sub GoogleAuth_Initialize { my ($hash) = @_; $hash->{DefFn} = "GoogleAuth_Define"; $hash->{DeleteFn} = "GoogleAuth_Delete"; $hash->{SetFn} = "GoogleAuth_Set"; $hash->{GetFn} = "GoogleAuth_Get"; $hash->{FW_detailFn} = "GoogleAuth_Detail"; $hash->{AttrList} = "ga_labelName ". "ga_qrSize:100x100,200x200,300x300,400x400 ". "ga_showKey:0,1 ga_showLink:0,1 ga_showQR:1,0 ". "ga_strictCheck:0,1 ". "$readingFnAttributes"; } sub GoogleAuth_Define { my ($hash, $def) = @_; my $name = $hash->{NAME}; my @a = split("[ \t][ \t]*", $def); return "Usage: Use Google Authenticator" if(@a != 2); Log3($hash,4,"googleAuth $name: defined"); readingsSingleUpdate($hash,'state','defined',1); return; } sub GoogleAuth_Delete { my ($hash,$name) = @_; setKeyValue("googleAuth$name",undef); } sub GoogleAuth_Set { my ($hash, $name, $cmd, @args) = @_; my $usage = "Unknown argument, choose one of new:noArg revoke:noArg"; if($cmd eq "new") { #SOURCE: https://blog.darkpan.com/article/6/Perl-and-Google-Authenticator.html return "Please revoke existing key first!" if defined(getKeyValue("googleAuth$name")); my $secret_bytes = urandom(50); my $secret_base32 = encode_base32( $secret_bytes ); Log3($hash,5,"googleAuth $name: secret_bytes=$secret_bytes"); Log3($hash,5,"googleAuth $name: set secret_base32=$secret_base32"); setKeyValue("googleAuth$name",$secret_base32); # write to fhem keystore readingsSingleUpdate($hash,'state','active',1); } elsif ($cmd eq "revoke") { setKeyValue("googleAuth$name",undef); readingsSingleUpdate($hash,'state','defined',1); } else { return $usage } return; } sub GoogleAuth_Get { my ($hash, $name, $cmd, $given_token) = @_; my $usage = "Unknown argument, choose one of check"; if ($cmd eq "check") { return "Token missing!" unless (defined($given_token) && $given_token); $given_token = _ga_make_token_6($given_token); Log3($hash,4,"googleAuth $name: given: $given_token"); my $secret_base32 = getKeyValue("googleAuth$name"); # read from fhem keystore Log3($hash,5,"googleAuth $name: get secret_base32=$secret_base32"); $secret_base32 = decode_base32($secret_base32); Log3($hash,5,"googleAuth $name: secret_bytes=$secret_base32"); my $oath = Authen::OATH->new; my @possible; if (AttrVal($name,'ga_strictCheck',0) == 1) { @possible = _ga_make_token_6($oath->totp($secret_base32)); } else { @possible = map { _ga_make_token_6($oath->totp($secret_base32, $_)) } time-30, time, time+30; } Log3($hash,4,"googleAuth $name: possible: ".join ' ',@possible); my $result = (grep /^$given_token$/, @possible) ? 1 : -1; readingsSingleUpdate($hash,'lastResult',$result,0); Log3($hash,4,"googleAuth $name: result: $result"); return $result; } return $usage; } sub GoogleAuth_Detail { my ($FW_wname, $name, $room, $pageHash) = @_; my $qr_url = _ga_make_url($name); my $secret_base32 = getKeyValue("googleAuth$name"); # read from fhem keystore return unless defined($qr_url); my $ret = ""; $ret .= ""; $ret .= ""; $ret .= "" if AttrVal($name,'ga_showKey',0); $ret .= "
"; $ret .= "<\/a>" if AttrVal($name,'ga_showQR',1); $ret .= "
 Link to QR code<\/a><\/td>" if AttrVal($name,'ga_showLink',0); $ret .= "
 Key (for manual use):
 $secret_base32
"; return $ret; } # helper functions sub _ga_make_url { my ($name) = @_; my $label = AttrVal($name,'ga_labelName',"FHEM Authentication $name"); $label =~ s/\s/\%20/g; my $qrsize = AttrVal($name,'ga_qrSize','200x200'); my $secret_base32 = getKeyValue("googleAuth$name"); return unless defined($secret_base32); my $url = "otpauth://totp/$label?secret=$secret_base32&issuer=FHEM"; my $qr_url = "https://chart.googleapis.com/chart?cht=qr&chs=$qrsize"."&chl="; $qr_url .= uri_escape($url); return $qr_url; } sub _ga_make_token_6 { my $token = shift; while (length $token < 6) { $token = "0$token"; } return $token; } sub gAuth { my($name,$token) = @_; return CommandGet(undef,"$name check $token"); } 1; =pod =item helper =item summary Module to use GoogleAuthenticator =item summary_DE Modul zur Nutzung von GoogleAuthenticator =begin html

GoogleAuth

=end html =begin html_DE

GoogleAuth

=end html_DE =cut