store u2f registration in the database
This commit is contained in:
Родитель
77ae70eac9
Коммит
b2ecea2c82
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче