store u2f registration in the database

This commit is contained in:
Christoph Wurst 2016-08-26 14:45:53 +02:00
Родитель 77ae70eac9
Коммит b2ecea2c82
8 изменённых файлов: 242 добавлений и 38 удалений

60
appinfo/database.xml Normal file
Просмотреть файл

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
<charset>utf8</charset>
<table>
<name>*dbprefix*twofactor_u2f_registrations</name>
<declaration>
<field>
<name>id</name>
<type>integer</type>
<autoincrement>1</autoincrement>
<default>0</default>
<notnull>true</notnull>
<length>4</length>
</field>
<field>
<name>user_id</name>
<type>text</type>
<default></default>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>key_handle</name>
<type>text</type>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>public_key</name>
<type>text</type>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>certificate</name>
<type>text</type>
<notnull>true</notnull>
<length>255</length>
</field>
<field>
<name>counter</name>
<type>integer</type>
<notnull>true</notnull>
<length>4</length>
</field>
<index>
<name>u2f_registrations_user_id</name>
<unique>true</unique>
<field>
<name>user_id</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>
</table>
</database>

Просмотреть файл

@ -5,7 +5,7 @@
<description>A two factor provider for U2F devices</description>
<licence>agpl</licence>
<author>Christoph Wurst</author>
<version>0.0.1</version>
<version>0.0.2</version>
<namespace>TwoFactor_U2F</namespace>
<category>tools</category>

Просмотреть файл

@ -28,14 +28,16 @@
method: 'POST'
}).done(function (data) {
this.doRegister(data.req, data.sigs);
}.bind(this));
}.bind(this)).fail(function() {
OC.Notification.showTemporary('server error while trying to add U2F device');
});
},
doRegister: function (req, sigs) {
console.log('doRegister', req, sigs);
u2f.register([req], sigs, function (data) {
console.log(data.errorCode);
if (data.errorCode && data.errorCode !== 0) {
alert("registration failed with errror: " + data.errorCode);
OC.Notification.showTemporary('U2F device registration failed (error code ' + data.errorCode + ')');
return;
}
this.finishRegister(data);
@ -50,6 +52,8 @@
}).then(function() {
OC.Notification.showTemporary('U2F device successfully registered');
console.log('registration finished');
}).fail(function() {
OC.Notification.showTemporary('server error while trying to complete U2F device registration');
});
}
});

Просмотреть файл

@ -17,15 +17,20 @@ require_once(__DIR__ . '/../../vendor/yubico/u2flib-server/src/u2flib_server/U2F
use OCA\TwoFactor_U2F\Service\U2FManager;
use OCP\AppFramework\Controller;
use OCP\IRequest;
use OCP\IUserSession;
class SettingsController extends Controller {
/** @var U2FManager */
private $manager;
public function __construct($appName, IRequest $request, U2FManager $manager) {
/** @var IUserSession */
private $userSession;
public function __construct($appName, IRequest $request, U2FManager $manager, IUserSession $userSession) {
parent::__construct($appName, $request);
$this->manager = $manager;
$this->userSession = $userSession;
}
/**
@ -33,7 +38,7 @@ class SettingsController extends Controller {
* @UseSession
*/
public function startRegister() {
return $this->manager->startRegistration($user);
return $this->manager->startRegistration($this->userSession->getUser());
}
/**
@ -43,7 +48,7 @@ class SettingsController extends Controller {
* @param string $clientData
*/
public function finishRegister($registrationData, $clientData) {
$this->manager->finishRegistration($registrationData, $clientData);
$this->manager->finishRegistration($this->userSession->getUser(), $registrationData, $clientData);
}
}

49
lib/Db/Registration.php Normal file
Просмотреть файл

@ -0,0 +1,49 @@
<?php
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2016
*/
namespace OCA\TwoFactor_U2F\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
/**
* @method string getUserId()
* @method void setUserId(string $userId)
* @method string getKeyHandle()
* @method void setKeyHandle(string $keyHandle)
* @method string getPublicKey()
* @method void setPublicKey(string $publicKey)
* @method string getCertificate()
* @method void setCertificate(string $Certificate)
* @method int getCounter()
* @method void setCounter(int $counter)
*/
class Registration extends Entity implements JsonSerializable {
protected $userId;
protected $keyHandle;
protected $publicKey;
protected $certificate;
protected $counter;
public function jsonSerialize() {
return [
'id' => $this->getId(),
'userId' => $this->getUserId(),
'keyHandle' => $this->getKeyHandle(),
'publicKey' => $this->getPublicKey(),
'certificate' => $this->getCertificate(),
'counter' => $this->getCounter(),
];
}
}

Просмотреть файл

@ -0,0 +1,70 @@
<?php
/**
* Nextcloud - U2F 2FA
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright Christoph Wurst 2016
*/
namespace OCA\TwoFactor_U2F\Db;
use OCP\AppFramework\Db\Mapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDb;
use OCP\IUser;
class RegistrationMapper extends Mapper {
public function __construct(IDb $db) {
parent::__construct($db, 'twofactor_u2f_registrations');
}
/**
* @param IUser $user
* @param int $id
* @return Registration
*/
public function findRegistration(IUser $user, $id) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'key_handle', 'public_key', 'certificate', 'counter')
->from('twofactor_u2f_registrations')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID())))
->andWhere($qb->expr()->eq('id', $qb->createNamedParameter($id)));
$result = $qb->execute();
$row = $result->fetch();
$result->closeCursor();
return Registration::fromRow($row);
}
/**
* @param IUser $user
* @return Registration[]
*/
public function findRegistrations(IUser $user) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('id', 'user_id', 'key_handle', 'public_key', 'certificate', 'counter')
->from('twofactor_u2f_registrations')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID())));
$result = $qb->execute();
$rawRegistrations = $result->fetchAll();
$result->closeCursor();
$registrations = array_map(function ($row) {
return Registration::fromRow($row);
}, $rawRegistrations);
return $registrations;
}
}

Просмотреть файл

@ -69,7 +69,7 @@ class U2FProvider implements IProvider {
* @return Template
*/
public function getTemplate(IUser $user) {
$reqs = $this->manager->startAuthenticate();
$reqs = $this->manager->startAuthenticate($user);
$tmpl = new Template('twofactor_u2f', 'challenge');
$tmpl->assign('reqs', $reqs);
@ -83,7 +83,7 @@ class U2FProvider implements IProvider {
* @param string $challenge
*/
public function verifyChallenge(IUser $user, $challenge) {
return $this->manager->finishAuthenticate($challenge);
return $this->manager->finishAuthenticate($user, $challenge);
}
/**

Просмотреть файл

@ -15,49 +15,56 @@ namespace OCA\TwoFactor_U2F\Service;
require_once(__DIR__ . '/../../vendor/yubico/u2flib-server/src/u2flib_server/U2F.php');
use InvalidArgumentException;
use OC;
use OCA\TwoFactor_U2F\Db\Registration;
use OCA\TwoFactor_U2F\Db\RegistrationMapper;
use OCP\ILogger;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use u2flib_server\Error;
use u2flib_server\U2F;
class U2FManager {
/** @var RegistrationMapper */
private $mapper;
/** @var ISession */
private $session;
/** @var ILogger */
private $logger;
public function __construct(ISession $session, ILogger $logger) {
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(RegistrationMapper $mapper, ISession $session, ILogger $logger, IURLGenerator $urlGenerator) {
$this->mapper = $mapper;
$this->session = $session;
$this->logger = $logger;
$this->urlGenerator = $urlGenerator;
}
private function getU2f() {
return new U2F(OC::$server->getURLGenerator()->getAbsoluteURL('/'));
return new U2F($this->urlGenerator->getAbsoluteURL('/'));
}
private function getRegistrations(IUser $user) {
$registrations = $this->mapper->findRegistrations($user);
$registrationObjects = array_map(function (Registration $registration) {
return (object) $registration->jsonSerialize();
}, $registrations);
return $registrationObjects;
}
public function isEnabled(IUser $user) {
// TODO: save in DB
return file_exists('/tmp/yubi');
$registrations = $this->mapper->findRegistrations($user);
return count($registrations) > 0;
}
private function getRegs() {
if (!file_exists('/tmp/yubi')) {
return [];
}
return [json_decode(file_get_contents('/tmp/yubi'))];
}
private function setReg($data) {
file_put_contents('/tmp/yubi', json_encode($data));
}
public function startRegistration(IUser $user = null) {
public function startRegistration(IUser $user) {
$u2f = $this->getU2f();
$data = $u2f->getRegisterData($this->getRegs());
$data = $u2f->getRegisterData($this->getRegistrations($user));
list($req, $sigs) = $data;
$this->logger->debug(json_encode($req));
@ -66,42 +73,48 @@ class U2FManager {
$this->session->set('twofactor_u2f_regReq', json_encode($req));
return [
'req' => $req,
'sigs' => $sigs,
'username' => 'user', // TODO
'req' => $req,
'sigs' => $sigs,
];
}
public function finishRegistration($registrationData, $clientData) {
public function finishRegistration(IUser $user, $registrationData, $clientData) {
$this->logger->debug($registrationData);
$this->logger->debug($clientData);
$u2f = $this->getU2f();
$regReq = json_decode($this->session->get('twofactor_u2f_regReq'));
$regResp = [
'registrationData' => $registrationData,
'clientData' => $clientData,
'registrationData' => $registrationData,
'clientData' => $clientData,
];
$reg = $u2f->doRegister($regReq, (object) $regResp);
$this->setReg($reg);
$registration = new Registration();
$registration->setUserId($user->getUID());
$registration->setKeyHandle($reg->keyHandle);
$registration->setPublicKey($reg->publicKey);
$registration->setCertificate($reg->certificate);
$registration->setCounter($reg->counter);
$this->mapper->insert($registration);
$this->logger->debug(json_encode($reg));
}
public function startAuthenticate() {
public function startAuthenticate(IUser $user) {
$u2f = $this->getU2f();
$reqs = $u2f->getAuthenticateData($this->getRegs());
$reqs = $u2f->getAuthenticateData($this->getRegistrations($user));
$this->session->set('twofactor_u2f_authReq', json_encode($reqs));
return $reqs;
}
public function finishAuthenticate($challenge) {
public function finishAuthenticate(IUser $user, $challenge) {
$u2f = $this->getU2f();
$registrations = $this->getRegistrations($user);
$authReq = json_decode($this->session->get('twofactor_u2f_authReq'));
try {
$reg = $u2f->doAuthenticate($authReq, $this->getRegs(), json_decode($challenge));
$reg = $u2f->doAuthenticate($authReq, $registrations, json_decode($challenge));
} catch (InvalidArgumentException $ex) {
$this->logger->warning('U2F auth failed: ' . $ex->getMessage());
return false;
@ -109,7 +122,10 @@ class U2FManager {
$this->logger->warning('U2F auth failed: ' . $ex->getMessage());
return false;
}
$this->setReg($reg);
$registration = $this->mapper->findRegistration($user, $reg->id);
$registration->setCounter($reg->counter);
$this->mapper->update($registration);
return true;
}