<?php
/*
  Copyright (C) 2013 Grégory Soutadé
  
  This file is part of gPass.
  
  gPass 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 3 of the License, or
  (at your option) any later version.
  
  gPass 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.
  
  You should have received a copy of the GNU General Public License
  along with gPass.  If not, see <http://www.gnu.org/licenses/>.
*/

function usage()
{
    echo "gPass script migration from v0.1 to v0.2\n";
    echo "usage : ./gpass_migrate_0_1.php --in <infile> --out <outfile> --url <url> masterkey1 [masterkey2, ...]\n";
    echo "url is the one given to Firefox addon (usually site_url/user example : gpass-demo.soutade.fr/demo)\n";
}

function open_crypto($mkey)
{
    $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');

    if ($td == false)
        die("Unable to open mcrypt");

    $ret = mcrypt_generic_init($td, $mkey, '0000000000000000');

    if ($ret < 0)
    {
        echo "Unable to set key $ret";
        return null;
    }

    return $td;
}

function decrypt($mkey, $val, $salted)
{
    $td = open_crypto($mkey);

    if ($td == null) return;

    $val = mdecrypt_generic($td, $val);

    // Remove 0 added by encrypt
    $val = str_replace("\0", '', $val);

    // Remove salt
    if ($salted)
        $val = substr($val, 0, strlen($val)-3);
    
    return $val;
}

function encrypt($mkey, $val, $salted)
{
    $td = open_crypto($mkey);

    if ($td == null) return;

    if ($salted)
    {
        $val .= dechex(rand(256,4095)); //between 0x100 and 0xfff
    }

    while ((strlen($val) % 16))
        $val .= chr(0);

    $val = mcrypt_generic($td, $val);

    return $val;
}

function add_entry($db, $login, $password)
{
    $count = $db->querySingle("SELECT COUNT(*) FROM gpass WHERE login='" . $login . "'");

    if ($count != 0)
    {
        return false;
    }

    $result = $db->query("INSERT INTO gpass ('login', 'password') VALUES ('" . $login . "', '" . $password . "')");

    return true;
}

function list_entries($db)
{
    $res = array();
    $db_res = $db->query("SELECT * FROM gpass");

    while (($db_row = $db_res->fetchArray()))
    {
        $row = array('login' => $db_row['login'], 'password' => $db_row['password']);
        array_push($res, $row);
    }

    return $res;
}

function load_database($file, $permission)
{
    try {
        $db = new SQLite3($file, $permission);
    }
    catch(Exception $e)
    {
        return null;
    }

    return $db;
}

function good_hmac256($key, $message) {
    $ipad = "";
    $opad = "";

    if (strlen($key) > 512/8)
    {
        $key = hash("sha256", $key, true);
    }

    for($i=0; $i<512/8; $i++)
    {
    if ($i >= strlen($key))
    {
        $ipad .= chr(0x36);
        $opad .= chr(0x5c);
    }
    else
    {
        $ipad .= chr(ord($key[$i]) ^ 0x36);
            $opad .= chr(ord($key[$i]) ^ 0x5c);
    }
    }

    $result = hash("sha256", $opad . hash("sha256", $ipad . $message, true), true);

    return $result;
}

function bad_hmac256($key, $message) {
    $ipad = "";
    $opad = "";

    for($i=0; $i<strlen($key); $i++)
    {
        $ipad .= chr(ord($key[$i]) ^ 0x36);
        $opad .= chr(ord($key[$i]) ^ 0x5c);
    }

    while (strlen($ipad) < 512/8)
    {
        $ipad .= chr(0x36);
        $opad .= chr(0x5c);
    }

    $result = hash("sha256", $opad . hash("sha256", $ipad . $message, false), true);

    return $result;
}

function bad_pkdbf2 ($password, $salt, $iterations, $outlen) {
    $result = "";
    $temp = "";
    $temp2 = "";
    $temp_res = "";
    $temp_res2 = "";

    for ($i=1; strlen($result) < $outlen; $i++)
    {
    $temp = bad_hmac256($salt . 
                            chr(($i & 0xff000000) >> 24) .
                            chr(($i & 0x00ff0000) >> 16) .
                            chr(($i & 0x0000ff00) >>  8) .
                            chr(($i & 0x000000ff) >>  0)
                            , $password);
    $temp_res = $temp;

    for($a=1; $a<$iterations; $a++)
    {
        $temp2 = bad_hmac256($temp, $password);
        $temp_res2 = "";
        for($b = 0; $b<strlen($temp_res); $b++)
        $temp_res2 .= chr(ord($temp_res[$b]) ^ ord($temp2[$b]));
        $temp_res = $temp_res2;
        $temp = $temp2;
    }
        
    $result .= $temp_res;
    }
    
    return substr($result, 0, $outlen);
}

function good_pkdbf2 ($password, $salt, $iterations, $outlen) {
    $result = "";
    $temp = "";
    $temp2 = "";
    $temp_res = "";
    $temp_res2 = "";

    for ($i=1; strlen($result) < $outlen; $i++)
    {
    $temp = good_hmac256($password,
                             $salt . 
                             chr(($i & 0xff000000) >> 24) .
                             chr(($i & 0x00ff0000) >> 16) .
                             chr(($i & 0x0000ff00) >>  8) .
                             chr(($i & 0x000000ff) >>  0)
            );
    $temp_res = $temp;

    for($a=1; $a<$iterations; $a++)
    {
        $temp2 = good_hmac256($password, $temp);
        $temp_res2 = "";
        for($b = 0; $b<strlen($temp_res); $b++)
        $temp_res2 .= chr(ord($temp_res[$b]) ^ ord($temp2[$b]));
        $temp_res = $temp_res2;
        $temp = $temp2;
    }
        
    $result .= $temp_res;
    }
    
    return substr($result, 0, $outlen);
}

function startsWith($haystack, $needle)
{
    return $needle === "" || strpos($haystack, $needle) === 0;
}

if (!isset($argv[1]) || $argv[1] != "--in" ||
    !isset($argv[3]) || $argv[3] != "--out" ||
    !isset($argv[5]) || $argv[5] != "--url" ||
    count($argv) < 8)
{
    usage();
    return;
}

$infile = $argv[2];
$outfile = $argv[4];
$url = $argv[6];
$nb_mkeys = count($argv)-7;

if ($infile == $outfile)
{
    echo "--in must be different from --out\n";
    return;
}

$db_in = load_database($infile, SQLITE3_OPEN_READWRITE);
if ($db_in == null)
{
    echo "Unable to read " + $infile;
    return;
}

$db_out = load_database($outfile, SQLITE3_OPEN_READWRITE|SQLITE3_OPEN_CREATE);
if ($db_out == null)
{
    echo "Unable to open " + $outfile;
    return;
}
try
{
    $db_out->query("CREATE TABLE gpass(login VARCHAR(512) PRIMARY KEY, password VARCHAR(512))");
}
catch(Exception $e)
{}

$entries = list_entries($db_in);
$db_in->close();
$nb_decrypted = 0;

for($i=0; $i<$nb_mkeys; $i++)
{
    $bad_key = bad_pkdbf2($argv[$i+7], $url, 1000, 256/8);
    $good_key = good_pkdbf2($argv[$i+7], $url, 1000, 256/8);

    /* echo "Bad key " . bin2hex($bad_key) . "\n"; */
    /* echo "Good key " . bin2hex($good_key) . "\n"; */

    for($a=0; $a<count($entries); $a++)
    {
        if ($entries[$a]['login'] == '') continue;

        /* echo "Try to decrypt " . $entries[$a]['login'] . "\n"; */

        $login = decrypt($bad_key, hex2bin($entries[$a]['login']), false);

        /* echo "Res " . $login . "\n"; */

        if (startsWith($login, "@@"))
        {
            echo "One login found " . $login . "\n";
            $entries[$a]['login'] = "";

            $password = decrypt($bad_key, hex2bin($entries[$a]['password']), false);

            /* echo "Password " . $password . "\n"; */

            $login = encrypt($good_key, $login, false);
            $password = encrypt($good_key, $password, false);

            add_entry($db_out, bin2hex($login), bin2hex($password));
            $nb_decrypted++;
        }
    }
}

$db_out->close();

echo "\n\n" . $nb_decrypted . "/" . count($entries) . " logins decrypted\n\n";

echo "\nDon't foreget to update index.php for each user !\n" ;
echo "That's all folks !\n\n";

?>