<?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($val0strlen($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"$keytrue);
    }

    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 $messagetrue), 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 $messagefalse), true);

    return 
$result;
}

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

    for (
$i=1strlen($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($result0$outlen);
}

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

    for (
$i=1strlen($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($result0$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($infileSQLITE3_OPEN_READWRITE);
if (
$db_in == null)
{
    echo 
"Unable to read " $infile;
    return;
}

$db_out load_database($outfileSQLITE3_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], $url1000256/8);
    
$good_key good_pkdbf2($argv[$i+7], $url1000256/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_keyhex2bin($entries[$a]['login']), false);

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

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

            
$password decrypt($bad_keyhex2bin($entries[$a]['password']), false);

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

            
$login encrypt($good_key$loginfalse);
            
$password encrypt($good_key$passwordfalse);

            
add_entry($db_outbin2hex($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";

?>