98_GoogleAuth.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. # $Id: 98_GoogleAuth.pm 13155 2017-01-20 16:01:24Z betateilchen $
  2. # License & technical informations
  3. =for comment
  4. #
  5. ################################################################
  6. #
  7. # This script is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # The GNU General Public License can be found at
  13. # http://www.gnu.org/copyleft/gpl.html.
  14. # A copy is found in the textfile GPL.txt and important notices to the license
  15. # from the author is found in LICENSE.txt distributed with these scripts.
  16. #
  17. # This script is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU General Public License for more details.
  21. #
  22. # This copyright notice MUST APPEAR in all copies of the script!
  23. #
  24. # Homepage: http://www.fhem.de
  25. #
  26. ################################################################
  27. #
  28. # Module 98_GoogleAuth.pm
  29. # written by pandabaer_de, revised by betateilchen
  30. #
  31. # based on informations from this website
  32. # https://blog.darkpan.com/article/6/Perl-and-Google-Authenticator.html
  33. #
  34. # requires additional perl modules
  35. # Convert::Base32 Authen::OATH Crypt::URandom
  36. #
  37. # on Debian systems, use apt-get to install appropriate packages
  38. # libconvert-base32-perl libauthen-oath-perl libcrypt-urandom-perl
  39. #
  40. ################################################################
  41. #
  42. =cut
  43. # Development history
  44. =for comment
  45. #
  46. # 2017-01-15 - first commit to ./contrib
  47. #
  48. # 2017-01-15 - added: direct QR display after set
  49. # added: attribute ga_qrSize
  50. # added: FW_detailFn
  51. # added: attribute ga_labelName
  52. # added: reading lastCheck
  53. # removed: reading qr_url
  54. # added: show link to qrcode and key for manual use
  55. # in device details
  56. # added: set command "revoke" to prevent overwrite
  57. # of existing key
  58. # added: attribute ga_showKey
  59. # attribute ga_showLink
  60. # added: function gAuth(<device>,<token>) for easy use
  61. # added: FW_summaryFn
  62. # added: commandref documentation EN
  63. #
  64. # 2017-01-15 - published to FHEM
  65. # fixed: problem on iOS if label contains spaces
  66. # added: issuer=FHEM in qr-code
  67. #
  68. # 2017-01-16 - added: attributes ga_showQR, ga_strictCheck
  69. # removed: FW_summaryFn (not really useful)
  70. #
  71. =cut
  72. package main;
  73. use strict;
  74. use warnings;
  75. use Convert::Base32;
  76. use Authen::OATH;
  77. use URI::Escape;
  78. use Crypt::URandom qw( urandom );
  79. sub GoogleAuth_Initialize($) {
  80. my ($hash) = @_;
  81. $hash->{DefFn} = "GoogleAuth_Define";
  82. $hash->{DeleteFn} = "GoogleAuth_Delete";
  83. $hash->{SetFn} = "GoogleAuth_Set";
  84. $hash->{GetFn} = "GoogleAuth_Get";
  85. $hash->{FW_detailFn} = "GoogleAuth_Detail";
  86. $hash->{AttrList} = "ga_labelName ".
  87. "ga_qrSize:100x100,200x200,300x300,400x400 ".
  88. "ga_showKey:0,1 ga_showLink:0,1 ga_showQR:1,0 ".
  89. "ga_strictCheck:0,1 ".
  90. "$readingFnAttributes";
  91. }
  92. sub GoogleAuth_Define($$) {
  93. my ($hash, $def) = @_;
  94. my $name = $hash->{NAME};
  95. my @a = split("[ \t][ \t]*", $def);
  96. return "Usage: Use Google Authenticator" if(@a != 2);
  97. Log3($hash,4,"googleAuth $name: defined");
  98. readingsSingleUpdate($hash,'state','defined',1);
  99. return undef;
  100. }
  101. sub GoogleAuth_Delete($$) {
  102. my ($hash,$name) = @_;
  103. setKeyValue("googleAuth$name",undef);
  104. }
  105. sub GoogleAuth_Set($$@) {
  106. my ($hash, $name, $cmd, @args) = @_;
  107. my $usage = "Unknown argument, choose one of new:noArg revoke:noArg";
  108. if($cmd eq "new") {
  109. #SOURCE: https://blog.darkpan.com/article/6/Perl-and-Google-Authenticator.html
  110. return "Please revoke existing key first!" if defined(getKeyValue("googleAuth$name"));
  111. my $secret_bytes = urandom(50);
  112. my $secret_base32 = encode_base32( $secret_bytes );
  113. Log3($hash,5,"googleAuth $name: secret_bytes=$secret_bytes");
  114. Log3($hash,5,"googleAuth $name: set secret_base32=$secret_base32");
  115. setKeyValue("googleAuth$name",$secret_base32); # write to fhem keystore
  116. readingsSingleUpdate($hash,'state','active',1);
  117. } elsif ($cmd eq "revoke") {
  118. setKeyValue("googleAuth$name",undef);
  119. readingsSingleUpdate($hash,'state','defined',1);
  120. } else {
  121. return $usage
  122. }
  123. return undef;
  124. }
  125. sub GoogleAuth_Get($$@) {
  126. my ($hash, $name, $cmd, $given_token) = @_;
  127. my $usage = "Unknown argument, choose one of check";
  128. if ($cmd eq "check") {
  129. return "Token missing!" unless (defined($given_token) && $given_token);
  130. $given_token = _ga_make_token_6($given_token);
  131. Log3($hash,4,"googleAuth $name: given: $given_token");
  132. my $secret_base32 = getKeyValue("googleAuth$name"); # read from fhem keystore
  133. Log3($hash,5,"googleAuth $name: get secret_base32=$secret_base32");
  134. $secret_base32 = decode_base32($secret_base32);
  135. Log3($hash,5,"googleAuth $name: secret_bytes=$secret_base32");
  136. my $oath = Authen::OATH->new;
  137. my @possible;
  138. if (AttrVal($name,'ga_strictCheck',0) == 1) {
  139. @possible = _ga_make_token_6($oath->totp($secret_base32));
  140. } else {
  141. @possible = map { _ga_make_token_6($oath->totp($secret_base32, $_)) } time-30, time, time+30;
  142. }
  143. Log3($hash,4,"googleAuth $name: possible: ".join ' ',@possible);
  144. my $result = (grep /^$given_token$/, @possible) ? 1 : -1;
  145. readingsSingleUpdate($hash,'lastResult',$result,0);
  146. Log3($hash,4,"googleAuth $name: result: $result");
  147. return $result;
  148. }
  149. return $usage;
  150. }
  151. sub GoogleAuth_Detail($@) {
  152. my ($FW_wname, $name, $room, $pageHash) = @_;
  153. my $qr_url = _ga_make_url($name);
  154. my $secret_base32 = getKeyValue("googleAuth$name"); # read from fhem keystore
  155. return unless defined($qr_url);
  156. my $ret = "<table>";
  157. $ret .= "<tr><td rowspan=2>";
  158. $ret .= "<a href=\"$qr_url\"><img src=\"$qr_url\"><\/a>"
  159. if AttrVal($name,'ga_showQR',1);
  160. $ret .= "</td>";
  161. $ret .= "<td><br>&nbsp;<a href=\"$qr_url\">Link to QR code<\/a><\/td>"
  162. if AttrVal($name,'ga_showLink',0);
  163. $ret .= "</tr>";
  164. $ret .= "<tr><td>&nbsp;Key (for manual use):<br>&nbsp;$secret_base32</td><tr>"
  165. if AttrVal($name,'ga_showKey',0);
  166. $ret .= "</table>";
  167. return $ret;
  168. }
  169. # helper functions
  170. sub _ga_make_url($) {
  171. my ($name) = @_;
  172. my $label = AttrVal($name,'ga_labelName',"FHEM Authentication $name");
  173. $label =~ s/\s/\%20/g;
  174. my $qrsize = AttrVal($name,'ga_qrSize','200x200');
  175. my $secret_base32 = getKeyValue("googleAuth$name");
  176. return undef unless defined($secret_base32);
  177. my $url = "otpauth://totp/$label?secret=$secret_base32&issuer=FHEM";
  178. my $qr_url = "https://chart.googleapis.com/chart?cht=qr&chs=$qrsize"."&chl=";
  179. $qr_url .= uri_escape($url);
  180. return $qr_url;
  181. }
  182. sub _ga_make_token_6($) {
  183. my $token = shift;
  184. while (length $token < 6) {
  185. $token = "0$token";
  186. }
  187. return $token;
  188. }
  189. sub gAuth($$) {
  190. my($name,$token) = @_;
  191. return CommandGet(undef,"$name check $token");
  192. }
  193. 1;
  194. =pod
  195. =item helper
  196. =item summary Module to use GoogleAuthenticator
  197. =item summary_DE Modul zur Nutzung von GoogleAuthenticator
  198. =begin html
  199. <a name="GoogleAuth"></a>
  200. <h3>GoogleAuth</h3>
  201. <ul>
  202. GoogleAuthenticator provides two-factor-authentication using one-time-passwords (token).<br/>
  203. These tokens are generated using the mobile app „Google Authenticator“ for example on a smartphone.<br/>
  204. See <a href="https://en.wikipedia.org/wiki/Google_Authenticator">https://en.wikipedia.org/wiki/Google_Authenticator</a>
  205. for more informations.<br/>
  206. <br/>
  207. <br/>
  208. <b>Prerequesits</b><br/>
  209. <br/>
  210. <li>The fhem implementation of the Google Authenticator is credited to the following publication:<br/>
  211. <a href="https://blog.darkpan.com/article/6/Perl-and-Google-Authenticator.html">https://blog.darkpan.com/article/6/Perl-and-Google-Authenticator.html</a></li>
  212. <br/>
  213. <li>Module uses following additional Perl modules:<br/>
  214. <br/>
  215. <ul><code>Convert::Base32 Authen::OATH Crypt::URandom</code></ul>
  216. <br/>
  217. If not already installed in your environment, please install them using appropriate commands from your environment.<br/>
  218. <br/>
  219. Package installation in debian environments:<br/>
  220. <br/>
  221. <ul><code>apt-get install libconvert-base32-perl libauthen-oath-perl libcrypt-urandom-perl</code></ul></li>
  222. <br/>
  223. <br/>
  224. <a name="GoogleAuthdefine"></a>
  225. <b>Define</b><br/><br/>
  226. <ul>
  227. <code>define &lt;name&gt; GoogleAuth</code><br/>
  228. <br/>
  229. Example:<br/><br/>
  230. <ul><code>define googleAuth GoogleAuth</code><br/></ul>
  231. </ul>
  232. <br/>
  233. <br/>
  234. <a name="GoogleAuthset"></a>
  235. <b>Set Commands</b><br/><br/>
  236. <ul>
  237. <li><code>set &lt;name&gt; new</code><br/>
  238. <br/>
  239. Generates a new secret key and displays the corresponding QR image.<br/>
  240. Using the photo function of the Google Authenticator app,<br/>
  241. this QR image can be used to transfer the secret key to the app.
  242. </li>
  243. <br/>
  244. <li><code>set &lt;name&gt; revoke</code><br/>
  245. <br/>
  246. Remove existing key.<br/>
  247. <b>You can not create a new key before</b> an existing key was deleted.<br/>
  248. </li>
  249. </ul>
  250. <br/>
  251. <br/>
  252. <a name="GoogleAuthget"></a>
  253. <b>Get Commands</b><br/><br/>
  254. <ul>
  255. <li><code>get &lt;name&gt; check &lt;token&gt;</code><br/>
  256. <br/>
  257. Check the validity of a given token; return value is 1 for a valid token, otherwise -1.<br/>
  258. <ul>
  259. <li>Token always consists of six numerical digits and will change every 30 seconds.</li>
  260. <li>Token is valid if it matches one of three tokens calculated by FHEM<br/>
  261. using three timestamps: -30 seconds, now and +30 seconds.<br/>
  262. This behavior can be changed by attribute ga_strictCheck.</li>
  263. </ul>
  264. <br/>
  265. </li>
  266. <li><code>gAuth(&lt;name&gt;,&lt;token&gt;)</code><br/>
  267. <br/>
  268. For easy use in your own functions you can call function gAuth(),<br/>
  269. which will return same result codes as the "get" command.
  270. </li>
  271. </ul>
  272. <br/>
  273. <br/>
  274. <a name="GoogleAuthattr"></a>
  275. <b>Attributes</b><br/><br/>
  276. <ul>
  277. <li><b>ga_labelName</b> - define a Name to identify PassCode inside the app.<br/>
  278. <b>Do not use any special characters,</b> except SPACE, in this attribute!</li>
  279. <li><b>ga_qrSize</b> - select image size of qr code</li>
  280. <li><b>ga_showKey</b> - show key for manual use if set to 1</li>
  281. <li><b>ga_showLink</b> - show link to qr code if set to 1</li>
  282. <li><b>ga_showQR</b> - show qr code if set to 1</li>
  283. <li><b>ga_strictCheck</b><br/>
  284. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AttrVal = 1 : check given token against one token<br/>
  285. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AttrVal = 0 : check given token against three tokens(default)</li>
  286. </ul>
  287. <br/>
  288. <br/>
  289. <a name="GoogleAuthreadings"></a>
  290. <b>Generated Readings/Events</b><br/><br/>
  291. <ul>
  292. <li><b>lastResult</b> - contains result from last token check</li>
  293. <li><b>state</b> - "active" if a key is set, otherwise "defined"</li>
  294. </ul>
  295. <br/>
  296. <br/>
  297. </ul>
  298. =end html
  299. =begin html_DE
  300. <a name="GoogleAuth"></a>
  301. <h3>GoogleAuth</h3>
  302. <ul>
  303. Sorry, keine deutsche Dokumentation vorhanden.<br/><br/>
  304. Die englische Doku gibt es hier: <a href='commandref.html#GoogleAuth'>GoogleAuth</a><br/>
  305. </ul>
  306. =end html_DE
  307. =cut