Crypt site credential password

This commit is contained in:
Jeremy Benoist 2017-06-11 23:05:19 +02:00
parent 9de9f1e5ce
commit 906424c1b6
No known key found for this signature in database
GPG key ID: BCA73962457ACC3C
15 changed files with 169 additions and 9 deletions

1
.gitignore vendored
View file

@ -56,3 +56,4 @@ app/Resources/build/
# Test-generated files
admin-export.json
specialexport.json
/data/site-credentials-secret-key.txt

View file

@ -39,7 +39,7 @@ class Version20170501115751 extends AbstractMigration implements ContainerAwareI
$table->addColumn('user_id', 'integer');
$table->addColumn('host', 'string', ['length' => 255]);
$table->addColumn('username', 'string', ['length' => 255]);
$table->addColumn('password', 'string', ['length' => 255]);
$table->addColumn('password', 'text');
$table->addColumn('createdAt', 'datetime');
$table->addIndex(['user_id'], 'idx_user');
$table->setPrimaryKey(['id']);

View file

@ -26,6 +26,7 @@ wallabag_core:
fetching_error_message: |
wallabag can't retrieve contents for this article. Please <a href="http://doc.wallabag.org/en/user/errors_during_fetching.html#how-can-i-help-to-fix-that">troubleshoot this issue</a>.
api_limit_mass_actions: 10
encryption_key_path: "%kernel.root_dir%/../data/site-credentials-secret-key.txt"
default_internal_settings:
-
name: share_public

View file

@ -73,7 +73,7 @@
"kphoen/rulerz-bundle": "~0.13",
"guzzlehttp/guzzle": "^5.3.1",
"doctrine/doctrine-migrations-bundle": "^1.0",
"paragonie/random_compat": "~1.0",
"paragonie/random_compat": "~2.0",
"craue/config-bundle": "~2.0",
"mnapoli/piwik-twig-extension": "^1.0",
"ocramius/proxy-manager": "1.*",
@ -83,7 +83,8 @@
"javibravo/simpleue": "^1.0",
"symfony/dom-crawler": "^3.1",
"friendsofsymfony/jsrouting-bundle": "^1.6",
"bdunogier/guzzle-site-authenticator": "^1.0.0@dev"
"bdunogier/guzzle-site-authenticator": "^1.0.0@dev",
"defuse/php-encryption": "^2.1"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "~2.2",

View file

@ -313,6 +313,8 @@ class InstallCommand extends ContainerAwareCommand
$this
->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]);
return $this;
}
/**

View file

@ -45,6 +45,8 @@ class SiteCredentialController extends Controller
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$credential->setPassword($this->get('wallabag_core.helper.crypto_proxy')->crypt($credential->getPassword()));
$em = $this->getDoctrine()->getManager();
$em->persist($credential);
$em->flush($credential);

View file

@ -63,6 +63,8 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end()
->scalarNode('encryption_key_path')
->end()
->end()
;

View file

@ -29,6 +29,7 @@ class WallabagCoreExtension extends Extension
$container->setParameter('wallabag_core.fetching_error_message_title', $config['fetching_error_message_title']);
$container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']);
$container->setParameter('wallabag_core.default_internal_settings', $config['default_internal_settings']);
$container->setParameter('wallabag_core.site_credentials.encryption_key_path', $config['encryption_key_path']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');

View file

@ -46,8 +46,7 @@ class SiteCredential
* @var string
*
* @Assert\NotBlank()
* @Assert\Length(max=255)
* @ORM\Column(name="password", type="string", length=255)
* @ORM\Column(name="password", type="text")
*/
private $password;

View file

@ -0,0 +1,86 @@
<?php
namespace Wallabag\CoreBundle\Helper;
use Psr\Log\LoggerInterface;
use Defuse\Crypto\Key;
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
/**
* This is a proxy to crypt and decrypt password used by SiteCredential entity.
* BTW, It might be re-use for sth else.
*/
class CryptoProxy
{
private $logger;
private $encryptionKey;
public function __construct($encryptionKeyPath, LoggerInterface $logger)
{
$this->logger = $logger;
if (!file_exists($encryptionKeyPath)) {
$key = Key::createNewRandomKey();
file_put_contents($encryptionKeyPath, $key->saveToAsciiSafeString());
chmod($encryptionKeyPath, 0600);
}
$this->encryptionKey = file_get_contents($encryptionKeyPath);
}
/**
* Ensure the given value will be crypted.
*
* @param string $secretValue Secret valye to crypt
*
* @return string
*/
public function crypt($secretValue)
{
$this->logger->debug('Crypto: crypting value: '.$this->mask($secretValue));
return Crypto::encrypt($secretValue, $this->loadKey());
}
/**
* Ensure the given crypted value will be decrypted.
*
* @param string $cryptedValue The value to be decrypted
*
* @return string
*/
public function decrypt($cryptedValue)
{
$this->logger->debug('Crypto: decrypting value: '.$this->mask($cryptedValue));
try {
return Crypto::decrypt($cryptedValue, $this->loadKey());
} catch (WrongKeyOrModifiedCiphertextException $e) {
throw new \RuntimeException('Decrypt fail: '.$e->getMessage());
}
}
/**
* Load the private key.
*
* @return string
*/
private function loadKey()
{
return Key::loadFromAsciiSafeString($this->encryptionKey);
}
/**
* Keep first and last character and put some stars in between.
*
* @param string $value Value to mask
*
* @return string
*/
private function mask($value)
{
return $value[0].'*****'.$value[strlen($value) - 1];
}
}

View file

@ -2,11 +2,20 @@
namespace Wallabag\CoreBundle\Repository;
use Wallabag\CoreBundle\Helper\CryptoProxy;
/**
* SiteCredentialRepository.
*/
class SiteCredentialRepository extends \Doctrine\ORM\EntityRepository
{
private $cryptoProxy;
public function setCrypto(CryptoProxy $cryptoProxy)
{
$this->cryptoProxy = $cryptoProxy;
}
/**
* Retrieve one username/password for the given host and userId.
*
@ -17,12 +26,21 @@ class SiteCredentialRepository extends \Doctrine\ORM\EntityRepository
*/
public function findOneByHostAndUser($host, $userId)
{
return $this->createQueryBuilder('s')
$res = $this->createQueryBuilder('s')
->select('s.username', 's.password')
->where('s.host = :hostname')->setParameter('hostname', $host)
->andWhere('s.user = :userId')->setParameter('userId', $userId)
->setMaxResults(1)
->getQuery()
->getOneOrNullResult();
if (null === $res) {
return;
}
// decrypt password before returning it
$res['password'] = $this->cryptoProxy->decrypt($res['password']);
return $res;
}
}

View file

@ -126,6 +126,8 @@ services:
factory: [ "@doctrine.orm.default_entity_manager", getRepository ]
arguments:
- WallabagCoreBundle:SiteCredential
calls:
- [ setCrypto, [ "@wallabag_core.helper.crypto_proxy" ] ]
wallabag_core.helper.entries_export:
class: Wallabag\CoreBundle\Helper\EntriesExport
@ -208,3 +210,9 @@ services:
wallabag_core.entry.download_images.client:
class: GuzzleHttp\Client
wallabag_core.helper.crypto_proxy:
class: Wallabag\CoreBundle\Helper\CryptoProxy
arguments:
- "%wallabag_core.site_credentials.encryption_key_path%"
- "@logger"

View file

@ -1341,7 +1341,7 @@ class EntryControllerTest extends WallabagCoreTestCase
$credential = new SiteCredential($user);
$credential->setHost('monde-diplomatique.fr');
$credential->setUsername('foo');
$credential->setPassword('bar');
$credential->setPassword($client->getContainer()->get('wallabag_core.helper.crypto_proxy')->crypt('bar'));
$em->persist($credential);
$em->flush();

View file

@ -6,12 +6,11 @@ use Monolog\Handler\TestHandler;
use Monolog\Logger;
use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfig;
use Graby\SiteConfig\SiteConfig as GrabySiteConfig;
use PHPUnit_Framework_TestCase;
use Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
class GrabySiteConfigBuilderTest extends PHPUnit_Framework_TestCase
class GrabySiteConfigBuilderTest extends \PHPUnit_Framework_TestCase
{
/** @var \Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder */
protected $builder;

View file

@ -0,0 +1,40 @@
<?php
namespace Tests\Wallabag\CoreBundle\Helper;
use Psr\Log\NullLogger;
use Monolog\Logger;
use Monolog\Handler\TestHandler;
use Wallabag\CoreBundle\Helper\CryptoProxy;
class CryptoProxyTest extends \PHPUnit_Framework_TestCase
{
public function testCrypto()
{
$logHandler = new TestHandler();
$logger = new Logger('test', [$logHandler]);
$crypto = new CryptoProxy(sys_get_temp_dir().'/'.uniqid('', true).'.txt', $logger);
$crypted = $crypto->crypt('test');
$decrypted = $crypto->decrypt($crypted);
$this->assertSame('test', $decrypted);
$records = $logHandler->getRecords();
$this->assertCount(2, $records);
$this->assertContains('Crypto: crypting value', $records[0]['message']);
$this->assertContains('Crypto: decrypting value', $records[1]['message']);
}
/**
* @expectedException RuntimeException
* @expectedExceptionMessage Decrypt fail
*
* @return [type] [description]
*/
public function testDecryptBadValue()
{
$crypto = new CryptoProxy(sys_get_temp_dir().'/'.uniqid('', true).'.txt', new NullLogger());
$crypto->decrypt('badvalue');
}
}