среда, 11 октября 2017 г.

HTA: двухфакторная аутентификация без смартфона

В продолжение этого поста написал программку на HTML Application. Брать с экрана QR-коды она, конечно, не может, но по уже готовой строке otpauth://totp/XXX генерировать пин-коды умеет.

Заодно пришлось написать на Javascript функции для SHA1 и HMAC-SHA1. В общем, бесполезный, но интересный опыт.

Текст программки:
<html>

<script language="Javascript">

////////////////////////////////////////////////////////////////////////////////////////////////////
// поле "Логин"
var login = "";
// если пустое, будем искать это значение в поле ключа

// поле "Ключ д/ф аутентификации"
var token = "otpauth://totp/Google:SomeUser?secret=KNXW2ZKTMVRXEZLU&issuer=Google";
// может быть в виде:
//    otpauth://totp/XXX?secret=XXX&issuer=XXX
//    secret=XXXX
//    в явном виде XxXx
////////////////////////////////////////////////////////////////////////////////////////////////////

</script>

<head>
 <title>Pin-код для двухфакторной аутентификации</title>
 <HTA:APPLICATION
  APPLICATIONNAME="Google_Auth"
  maximizeButton="no"
  minimizeButton="no"
  sysMenu="yes"
  contextMenu="yes"
  scroll="no"
 ></HTA:APPLICATION>
</head>

<body onload="self.window_onLoad()">

 <table width="100%" style="color:darkgray">
  <tr>
   <td width="50%"><span id="loginText"></span></td>
   <td width="50%" align="right"><span id="tokenText"></span></td>
  </tr>
 </table>

 <p align="center">Код аутентификатора:<br><span id="pin" align="center" style="font-size:30px;font-weight:bold">------</span></p>

 <table width="100%">
  <tr><td colspan="2" align="center">Текущее время UTC</td></tr>
  <tr><td>Ваш компьютер:</td><td><span id="localDT"></span></td></tr>
  <tr><td>Общемировое:</td><td><span id="globalDT"></span></td></tr>
 </table>

 <p align="center"><button onclick="self.close()">Выход</button></p>
</body>


<script language="Javascript">

var 
 IntervalLength = 30,
 PinTemplate = "000000",
 PinModulo = Math.pow(10, PinTemplate.length),
 sizeOfInt32 = 4;


var globalTimeDiff = 0,
 globalTimeChecked = false,
 xhr = new XMLHttpRequest(),
 previousInterval = 0;


function window_onLoad()
{
 self.resizeTo (350, 290);

 document.getElementById("loginText").innerText = getLoginValue();
 document.getElementById("tokenText").innerText = getTokenValue();

 self.localDate();
 self.setInterval (self.onTimer, 1000);
 self.globalDate();
}


function localDate()
{
 var dt = new Date(),
  D = dt.getUTCDate(),
  M = dt.getUTCMonth()+1,
  Y = dt.getUTCFullYear(),
  H = dt.getUTCHours(),
  m = dt.getUTCMinutes(),
  S = dt.getUTCSeconds(),
  offset = dt.getTimezoneOffset();

 dt.setSeconds(S + globalTimeDiff);

 var
  gD = dt.getUTCDate(),
  gM = dt.getUTCMonth()+1,
  gY = dt.getUTCFullYear(),
  gH = dt.getUTCHours(),
  gm = dt.getUTCMinutes(),
  gS = dt.getUTCSeconds();

 document.getElementById("localDT").innerText = ""
  + ((D < 10) ? "0" : "") + D
  + "." + ((M < 10) ? "0" : "") + M
  + "." + Y
  + " " + ((H < 10) ? "0" : "") + H
  + ":" + ((m < 10) ? "0" : "") + m
  + ":" + ((S < 10) ? "0" : "") + S;
  //+ " (" + (offset/60) + ")";

 document.getElementById("globalDT").innerText = globalTimeChecked ? ""
  + ((gD < 10) ? "0" : "") + gD
  + "." + ((gM < 10) ? "0" : "") + gM
  + "." + gY
  + " " + ((gH < 10) ? "0" : "") + gH
  + ":" + ((gm < 10) ? "0" : "") + gm
  + ":" + ((gS < 10) ? "0" : "") + gS : "----";
}


function globalDate()
{
 xhr.onreadystatechange = function ()
  {
   if (xhr.readyState === 4)
   {
    if (xhr.status === 200)
    {
     var dt = new Date();
     globalTimeDiff = 1*xhr.responseText - dt.getTime()/1000;
     globalTimeChecked = true;
    }
   }
  };
 xhr.open("GET", "http://time.xdy-ydx.ru/text", true);
 xhr.send(null);
}


function onTimer()
{
 localDate();
 generatePin();
}


function setPinCodeText(text)
{
 var e = document.getElementById("pin");
 if (e.innerText != text)
  e.innerText = text;
}


function getLoginValue()
{
 if (login != "")
  return login;

 if (token.substring(0, 15) == "otpauth://totp/")
 {
  var vals = token.substring(15).split("?");
  return vals[0];
 }

 return "";
}


function getTokenValue()
{
 if (token.substring(0, 15) == "otpauth://totp/")
 {
  var vals = token.substring(15).split(/\?|\&/);
  for (var i = 0; i < vals.length; i++)
   if (vals[i].substring(0, 7) == "secret=")
    return Base32_Decode(vals[i].substring(7));
 }

 if (token.substring(0, 7) == "secret=")
  return Base32_Decode(token.substring(7));

 return token;
}


// thanks to
// http://stackoverflow.com/questions/6421950/is-there-a-tutorial-on-how-to-implement-google-authenticator-in-net-apps
function generatePin()
{
 var dt = new Date();
 var interval = Math.floor(dt.getTime()/(1000*IntervalLength));

 if (interval == previousInterval)
  return;
 previousInterval = interval;

 var CounterBytes = pushInt64ToByteArray([], interval);

 var tokenValue = getTokenValue();
 var key = [];
 for (var i = 0; i < tokenValue.length; i++)
  key.push(tokenValue.charCodeAt(i));

 var hash = HMAC(SHA1, key, CounterBytes);
 var offset = hash[hash.length - 1] & 0xF;

 var selectedBytes = hash.slice(offset, offset + sizeOfInt32);
 var selectedInteger =
  (selectedBytes[0] << 24) +
  (selectedBytes[1] << 16) +
  (selectedBytes[2] << 8) +
  selectedBytes[3];

 var truncatedHash = selectedInteger & 0x7FFFFFFF;

 var PinNumber = "" + truncatedHash % PinModulo;

 var Pin = (PinTemplate + PinNumber).substring(PinTemplate.length + PinNumber.length - 6);

 setPinCodeText( Pin );

}


function pushInt32ToByteArray (arr, N)
{
 arr.push( (N >>> 24) & 0xFF );
 arr.push( (N >>> 16) & 0xFF );
 arr.push( (N >>>  8) & 0xFF );
 arr.push( (N >>>  0) & 0xFF );

 return arr;
}


function pushInt64ToByteArray (arr, N)
{
 var lo = N & 0xFFFFFFFF,
  hi = (N - lo) / 0x100000000;

 arr = pushInt32ToByteArray (arr, hi);
 arr = pushInt32ToByteArray (arr, lo);

 return arr;
}



function byteArrayToString (bytes)
{
 var s = "";
 for (var i = 0; i < bytes.length; i++)
  s += ((i == 0) ? "" : " ") + bytes[i].toString(16);
 return s;
}


function SHA1 (bytes)
{

 var shl = function (x, y)
  {

   return ( (x << y) + (x >>> (32-y)) );
  };

 var
  h0 = 0x67452301,
  h1 = 0xEFCDAB89,
  h2 = 0x98BADCFE,
  h3 = 0x10325476,
  h4 = 0xC3D2E1F0;

 buf = [];
 count = 0;
 for (var i = 0; i < bytes.length; i++)
 {
  buf.push(bytes[i]);
  count += 8; if (count == 512) count = 0;
 }

 buf.push(0x80);
 count += 8; if (count == 512) count = 0;

 while (count != 448)
 {
  buf.push(0);
  count += 8; if (count == 512) count = 0;
 }

 buf = pushInt64ToByteArray(buf, bytes.length*8);

 for (var i = 0; i < buf.length; i += 64)
 {
  var w = [];

  for (var j = 0; j < 16; j++)
   w.push( (buf[i+j*4] << 24) + (buf[i+j*4+1] << 16) + (buf[i+j*4+2] << 8) + buf[i+j*4+3] );

  for (var j = 16; j < 80; j++)
   w.push( shl(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1) );

  var
   a = h0,
   b = h1,
   c = h2,
   d = h3,
   e = h4,
   f = 0,
   k = 0,
   x;

  for (var j = 0; j < 80; j++)
  {
   if (j < 20)
   {
    f = (b & c) | ((~b) & d);
    k = 0x5A827999;
   }
   else
   if (j < 40)
   {
    f = b ^ c ^ d;
    k = 0x6ED9EBA1;
   }
   else
   if (j < 60)
   {
    f = (b & c) | (b & d) | (c & d);
    k = 0x8F1BBCDC;
   }
   else
   {
    f = b ^ c ^ d;
    k = 0xCA62C1D6;
   }

   x = ( shl(a, 5) + f + e + k + w[j]) & 0xFFFFFFFF;
   e = d;
   d = c;
   c = shl(b, 30) & 0xFFFFFFFF;
         b = a;
         a = x;
  }

  h0 = (h0 + a) & 0xFFFFFFFF;
  h1 = (h1 + b) & 0xFFFFFFFF;
  h2 = (h2 + c) & 0xFFFFFFFF;
  h3 = (h3 + d) & 0xFFFFFFFF;
  h4 = (h4 + e) & 0xFFFFFFFF;

 }

 var result = [];
 result = pushInt32ToByteArray(result, h0);
 result = pushInt32ToByteArray(result, h1);
 result = pushInt32ToByteArray(result, h2);
 result = pushInt32ToByteArray(result, h3);
 result = pushInt32ToByteArray(result, h4);

 return (result);
}


function HMAC (H, key, msg)
{
 var
  K0 = [],
  count = 0;

 for (var i = 0; i < key.length; i++)
 {
  K0.push(key[i]);
  count++;
 }

 while (count < 64)
 {
  K0.push(0);
  count++;
 }

 var chunk1 = [];

 for (var i = 0; i < K0.length; i++)
  chunk1.push(K0[i] ^ 0x36);

 for (var i = 0; i < msg.length; i++)
  chunk1.push(msg[i]);

 var hash1 = H(chunk1);

 var chunk2 = [];

 for (var i = 0; i < K0.length; i++)
  chunk2.push(K0[i] ^ 0x5c);

 for (var i = 0; i < hash1.length; i++)
  chunk2.push(hash1[i]);

 return H(chunk2);
}

// thanks to http://stackoverflow.com/questions/641361/base32-decoding
function Base32_Decode (key)
{
 var charToValue = function (value)
  {
   //65-90 == uppercase letters
   if (value < 91 && value > 64)
    return (value - 65);

   //50-55 == numbers 2-7
   if (value < 56 && value > 49)
    return (value - 24);

   //97-122 == lowercase letters
   if (value < 123 && value > 96)
    return (value - 97);

   return 0;
  }

 while(key.charAt(key.length-1) == "=")
  key = key.substring(0, key.length-1);

 var result = "";

 var
  curByte = 0,
  bitsRemaining = 8;
  mask = 0;

 for (var i = 0; i < key.length; i++)
 {
  var cValue = charToValue(key.charCodeAt(i));

  if (bitsRemaining > 5)
  {
   mask = cValue << (bitsRemaining - 5);
   curByte = (curByte | mask) & 0xFF;
   bitsRemaining -= 5;
  }
  else
  {
   mask = cValue >>> (5 - bitsRemaining);
   curByte = (curByte | mask) & 0xFF;
   result += String.fromCharCode(curByte);
   curByte = (cValue << (3 + bitsRemaining)) & 0xFF;
   bitsRemaining += 3;
  }
 }

 if ( ((key.length * 5 / 8)|0) != result.length )
   result += String.fromCharCode(curByte);

 return result;
}

</script>

</html>

Комментариев нет:

Отправить комментарий