Merge pull request #71 from nextcloud/enhancement/composer-phpunit
Install deps via composer and add phpunit + nc iterfaces
This commit is contained in:
Коммит
6c19d400e3
|
@ -4,7 +4,5 @@
|
|||
/node_modules
|
||||
|
||||
/nbproject/private/
|
||||
/vendor/yubico/u2flib-server/examples/*
|
||||
/vendor/yubico/u2flib-server/tests/
|
||||
/vendor/yubico/u2flib-server/do-source-release.sh
|
||||
/vendor
|
||||
composer.phar
|
||||
|
|
|
@ -50,9 +50,9 @@ cache:
|
|||
|
||||
before_install:
|
||||
- php --info
|
||||
# Download phpunit 5.7
|
||||
- wget https://phar.phpunit.de/phpunit-5.7.phar -O phpunit
|
||||
- chmod u+x phpunit
|
||||
|
||||
# Install composer deps
|
||||
- composer install
|
||||
|
||||
# XDebug is only needed if we report coverage -> speeds up other builds
|
||||
- if [[ "$PHP_COVERAGE" = "FALSE" ]]; then phpenv config-rm xdebug.ini; fi
|
||||
|
@ -90,9 +90,8 @@ script:
|
|||
|
||||
# Run PHP tests
|
||||
- cd tests
|
||||
- ../phpunit --configuration phpunit.xml
|
||||
- ../vendor/phpunit/phpunit/phpunit --configuration phpunit.xml
|
||||
|
||||
# Publish PHP coverage to scrutinizer
|
||||
- if [[ "$PHP_COVERAGE" = "TRUE" ]]; then wget https://scrutinizer-ci.com/ocular.phar; fi
|
||||
- if [[ "$PHP_COVERAGE" = "TRUE" ]]; then php ocular.phar code-coverage:upload --format=php-clover clover.xml; fi
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -23,6 +23,7 @@ update-composer: composer.phar
|
|||
|
||||
appstore:
|
||||
mkdir -p $(sign_dir)
|
||||
composer install --no-dev
|
||||
rsync -a \
|
||||
--delete \
|
||||
--exclude=.git \
|
||||
|
|
|
@ -1,19 +1,27 @@
|
|||
{
|
||||
"name": "christophwurst/twofactor_u2f",
|
||||
"description": "A two factor provider for U2F devices for Nextcloud",
|
||||
"type": "project",
|
||||
"license": "agplv3",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christoph Wurst",
|
||||
"email": "christoph@winzerhof-wurst.at"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"yubico/u2flib-server": "^1.0.1"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true
|
||||
}
|
||||
"name": "christophwurst/twofactor_u2f",
|
||||
"description": "A two factor provider for U2F devices for Nextcloud",
|
||||
"type": "project",
|
||||
"license": "agplv3",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christoph Wurst",
|
||||
"email": "christoph@winzerhof-wurst.at"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"yubico/u2flib-server": "^1.0.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"christophwurst/nextcloud": "^12.0.0",
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
"classmap-authoritative": true,
|
||||
"platform": {
|
||||
"php": "5.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,7 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInitd5289176fb4b3e86997c3313bff1b499::getLoader();
|
|
@ -1,445 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath.'\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
$length = $this->prefixLengthsPsr4[$first][$search];
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'u2flib_server\\Error' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\RegisterRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\Registration' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\SignRequest' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\U2F' => $vendorDir . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
);
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
|
@ -1,9 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInitd5289176fb4b3e86997c3313bff1b499
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInitd5289176fb4b3e86997c3313bff1b499', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitd5289176fb4b3e86997c3313bff1b499', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitd5289176fb4b3e86997c3313bff1b499::getInitializer($loader));
|
||||
} else {
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
$loader->register(true);
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInitd5289176fb4b3e86997c3313bff1b499
|
||||
{
|
||||
public static $classMap = array (
|
||||
'u2flib_server\\Error' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\RegisterRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\Registration' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\SignRequest' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
'u2flib_server\\U2F' => __DIR__ . '/..' . '/yubico/u2flib-server/src/u2flib_server/U2F.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->classMap = ComposerStaticInitd5289176fb4b3e86997c3313bff1b499::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "yubico/u2flib-server",
|
||||
"version": "1.0.1",
|
||||
"version_normalized": "1.0.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Yubico/php-u2flib-server.git",
|
||||
"reference": "dc318c80b59e62921c210f31b014def26ceebbab"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/dc318c80b59e62921c210f31b014def26ceebbab",
|
||||
"reference": "dc318c80b59e62921c210f31b014def26ceebbab",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-openssl": "*",
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~5.7"
|
||||
},
|
||||
"time": "2017-05-09T07:33:58+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-2-Clause"
|
||||
],
|
||||
"description": "Library for U2F implementation",
|
||||
"homepage": "https://developers.yubico.com/php-u2flib-server"
|
||||
}
|
||||
]
|
|
@ -1,7 +0,0 @@
|
|||
composer.lock
|
||||
vendor/
|
||||
.*.swp
|
||||
php-u2flib-server-*.tar.gz
|
||||
php-u2flib-server-*.tar.gz.sig
|
||||
apidocs/
|
||||
build/
|
|
@ -1,21 +0,0 @@
|
|||
language: php
|
||||
sudo: false
|
||||
php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
- hhvm
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.6
|
||||
env: COVERALLS=true
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
before_script:
|
||||
- composer install
|
||||
|
||||
script:
|
||||
- ./vendor/phpunit/phpunit/phpunit -c phpunit.xml
|
||||
|
||||
after_success:
|
||||
- test -z $COVERALLS || (composer require satooshi/php-coveralls && vendor/bin/coveralls -v)
|
|
@ -1,9 +0,0 @@
|
|||
Author: Yubico
|
||||
Basename: php-u2flib-server
|
||||
Homepage: https://developers.yubico.com/php-u2flib-server
|
||||
License: BSD-2-Clause
|
||||
Name: Native U2F library in PHP
|
||||
Project: php-u2flib-server
|
||||
Summary: Native U2F library in PHP
|
||||
Yubico-Category: U2F projects
|
||||
Travis: https://travis-ci.org/Yubico/php-u2flib-server
|
|
@ -1,26 +0,0 @@
|
|||
Copyright (c) 2014 Yubico AB
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,28 +0,0 @@
|
|||
php-u2flib-server NEWS -- History of user-visible changes.
|
||||
|
||||
* Version 1.0.1 (released 2017-05-09)
|
||||
** Move examples to phps so they don't execute by default
|
||||
** Use common challenge for multiple registrations
|
||||
|
||||
* Version 1.0.0 (released 2016-02-19)
|
||||
** Give an early error on openssl < 1.0
|
||||
** Support devices with initial counter 0
|
||||
** Fixes to examples
|
||||
** Handle errorCode: 0 correctly
|
||||
|
||||
* Version 0.1.0 (released 2015-03-03)
|
||||
** Use openssl for all crypto instead of third party extensions.
|
||||
** Properly check the request challenge on authenticate.
|
||||
** Switch from returning error codes to throwing exceptions.
|
||||
** Stop recommending composer for installation.
|
||||
|
||||
* Version 0.0.2 (released 2014-10-24)
|
||||
** Refactor the API to return objects instead of encoded objects.
|
||||
** Add a second example that uses PDO to store registrations.
|
||||
** Add documentation to the API.
|
||||
** Check that randomness returned is good.
|
||||
** Drop the unneeded mcrypt extension.
|
||||
** More tests.
|
||||
|
||||
* Version 0.0.1 (released 2014-10-16)
|
||||
** Initial release.
|
|
@ -1,34 +0,0 @@
|
|||
php-u2flib-server
|
||||
-----------------
|
||||
|
||||
image:https://travis-ci.org/Yubico/php-u2flib-server.svg?branch=master["Build Status", link="https://travis-ci.org/Yubico/php-u2flib-server"]
|
||||
image:https://coveralls.io/repos/Yubico/php-u2flib-server/badge.svg?branch=master&service=github["Coverage", link="https://coveralls.io/github/Yubico/php-u2flib-server?branch=master"]
|
||||
image:https://scrutinizer-ci.com/g/Yubico/php-u2flib-server/badges/quality-score.png?b=master["Scrutinizer Code Quality", link="https://scrutinizer-ci.com/g/Yubico/php-u2flib-server/?branch=master"]
|
||||
|
||||
=== Introduction ===
|
||||
|
||||
Serverside U2F library for PHP. Provides functionality for registering
|
||||
tokens and authentication with said tokens.
|
||||
|
||||
To read more about U2F and how to use a U2F library, visit
|
||||
link:http://developers.yubico.com/U2F[developers.yubico.com/U2F].
|
||||
|
||||
=== License ===
|
||||
|
||||
The project is licensed under a BSD license. See the file COPYING for
|
||||
exact wording. For any copyright year range specified as YYYY-ZZZZ in
|
||||
this package note that the range specifies every single year in that
|
||||
closed interval.
|
||||
|
||||
=== Dependencies ===
|
||||
|
||||
The only dependency is the openssl extension to PHP that has to be enabled.
|
||||
|
||||
A composer.json is included in the distribution to make things simpler for
|
||||
other project using composer.
|
||||
|
||||
=== Tests ===
|
||||
|
||||
To run the test suite link:https://phpunit.de[PHPUnit] is required. To run it, type:
|
||||
|
||||
$ phpunit
|
|
@ -1 +0,0 @@
|
|||
README
|
|
@ -1,13 +0,0 @@
|
|||
destination: apidocs
|
||||
|
||||
source:
|
||||
- src/u2flib_server
|
||||
|
||||
exclude:
|
||||
- "*/tests/*"
|
||||
|
||||
groups: none
|
||||
|
||||
tree: false
|
||||
|
||||
title: php-u2flib-server API
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name":"yubico/u2flib-server",
|
||||
"description":"Library for U2F implementation",
|
||||
"homepage":"https://developers.yubico.com/php-u2flib-server",
|
||||
"license":"BSD-2-Clause",
|
||||
"require": {
|
||||
"ext-openssl":"*",
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~5.7"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
<phpunit
|
||||
colors="true">
|
||||
<testsuite name="tests">
|
||||
<directory suffix="test.php">tests</directory>
|
||||
</testsuite>
|
||||
<logging>
|
||||
<log type="coverage-clover" target="build/logs/clover.xml"/>
|
||||
</logging>
|
||||
</phpunit>
|
|
@ -1,507 +0,0 @@
|
|||
<?php
|
||||
/* Copyright (c) 2014 Yubico AB
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
namespace u2flib_server;
|
||||
|
||||
/** Constant for the version of the u2f protocol */
|
||||
const U2F_VERSION = "U2F_V2";
|
||||
|
||||
/** Error for the authentication message not matching any outstanding
|
||||
* authentication request */
|
||||
const ERR_NO_MATCHING_REQUEST = 1;
|
||||
|
||||
/** Error for the authentication message not matching any registration */
|
||||
const ERR_NO_MATCHING_REGISTRATION = 2;
|
||||
|
||||
/** Error for the signature on the authentication message not verifying with
|
||||
* the correct key */
|
||||
const ERR_AUTHENTICATION_FAILURE = 3;
|
||||
|
||||
/** Error for the challenge in the registration message not matching the
|
||||
* registration challenge */
|
||||
const ERR_UNMATCHED_CHALLENGE = 4;
|
||||
|
||||
/** Error for the attestation signature on the registration message not
|
||||
* verifying */
|
||||
const ERR_ATTESTATION_SIGNATURE = 5;
|
||||
|
||||
/** Error for the attestation verification not verifying */
|
||||
const ERR_ATTESTATION_VERIFICATION = 6;
|
||||
|
||||
/** Error for not getting good random from the system */
|
||||
const ERR_BAD_RANDOM = 7;
|
||||
|
||||
/** Error when the counter is lower than expected */
|
||||
const ERR_COUNTER_TOO_LOW = 8;
|
||||
|
||||
/** Error decoding public key */
|
||||
const ERR_PUBKEY_DECODE = 9;
|
||||
|
||||
/** Error user-agent returned error */
|
||||
const ERR_BAD_UA_RETURNING = 10;
|
||||
|
||||
/** Error old OpenSSL version */
|
||||
const ERR_OLD_OPENSSL = 11;
|
||||
|
||||
/** @internal */
|
||||
const PUBKEY_LEN = 65;
|
||||
|
||||
class U2F
|
||||
{
|
||||
/** @var string */
|
||||
private $appId;
|
||||
|
||||
/** @var null|string */
|
||||
private $attestDir;
|
||||
|
||||
/** @internal */
|
||||
private $FIXCERTS = array(
|
||||
'349bca1031f8c82c4ceca38b9cebf1a69df9fb3b94eed99eb3fb9aa3822d26e8',
|
||||
'dd574527df608e47ae45fbba75a2afdd5c20fd94a02419381813cd55a2a3398f',
|
||||
'1d8764f0f7cd1352df6150045c8f638e517270e8b5dda1c63ade9c2280240cae',
|
||||
'd0edc9a91a1677435a953390865d208c55b3183c6759c9b5a7ff494c322558eb',
|
||||
'6073c436dcd064a48127ddbf6032ac1a66fd59a0c24434f070d4e564c124c897',
|
||||
'ca993121846c464d666096d35f13bf44c1b05af205f9b4a1e00cf6cc10c5e511'
|
||||
);
|
||||
|
||||
/**
|
||||
* @param string $appId Application id for the running application
|
||||
* @param string|null $attestDir Directory where trusted attestation roots may be found
|
||||
* @throws Error If OpenSSL older than 1.0.0 is used
|
||||
*/
|
||||
public function __construct($appId, $attestDir = null)
|
||||
{
|
||||
if(OPENSSL_VERSION_NUMBER < 0x10000000) {
|
||||
throw new Error('OpenSSL has to be at least version 1.0.0, this is ' . OPENSSL_VERSION_TEXT, ERR_OLD_OPENSSL);
|
||||
}
|
||||
$this->appId = $appId;
|
||||
$this->attestDir = $attestDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get a registration request to send to a user.
|
||||
* Returns an array of one registration request and a array of sign requests.
|
||||
*
|
||||
* @param array $registrations List of current registrations for this
|
||||
* user, to prevent the user from registering the same authenticator several
|
||||
* times.
|
||||
* @return array An array of two elements, the first containing a
|
||||
* RegisterRequest the second being an array of SignRequest
|
||||
* @throws Error
|
||||
*/
|
||||
public function getRegisterData(array $registrations = array())
|
||||
{
|
||||
$challenge = $this->createChallenge();
|
||||
$request = new RegisterRequest($challenge, $this->appId);
|
||||
$signs = $this->getAuthenticateData($registrations);
|
||||
return array($request, $signs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to verify and unpack a registration message.
|
||||
*
|
||||
* @param RegisterRequest $request this is a reply to
|
||||
* @param object $response response from a user
|
||||
* @param bool $includeCert set to true if the attestation certificate should be
|
||||
* included in the returned Registration object
|
||||
* @return Registration
|
||||
* @throws Error
|
||||
*/
|
||||
public function doRegister($request, $response, $includeCert = true)
|
||||
{
|
||||
if( !is_object( $request ) ) {
|
||||
throw new \InvalidArgumentException('$request of doRegister() method only accepts object.');
|
||||
}
|
||||
|
||||
if( !is_object( $response ) ) {
|
||||
throw new \InvalidArgumentException('$response of doRegister() method only accepts object.');
|
||||
}
|
||||
|
||||
if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
|
||||
throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
|
||||
}
|
||||
|
||||
if( !is_bool( $includeCert ) ) {
|
||||
throw new \InvalidArgumentException('$include_cert of doRegister() method only accepts boolean.');
|
||||
}
|
||||
|
||||
$rawReg = $this->base64u_decode($response->registrationData);
|
||||
$regData = array_values(unpack('C*', $rawReg));
|
||||
$clientData = $this->base64u_decode($response->clientData);
|
||||
$cli = json_decode($clientData);
|
||||
|
||||
if($cli->challenge !== $request->challenge) {
|
||||
throw new Error('Registration challenge does not match', ERR_UNMATCHED_CHALLENGE );
|
||||
}
|
||||
|
||||
$registration = new Registration();
|
||||
$offs = 1;
|
||||
$pubKey = substr($rawReg, $offs, PUBKEY_LEN);
|
||||
$offs += PUBKEY_LEN;
|
||||
// decode the pubKey to make sure it's good
|
||||
$tmpKey = $this->pubkey_to_pem($pubKey);
|
||||
if($tmpKey === null) {
|
||||
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
|
||||
}
|
||||
$registration->publicKey = base64_encode($pubKey);
|
||||
$khLen = $regData[$offs++];
|
||||
$kh = substr($rawReg, $offs, $khLen);
|
||||
$offs += $khLen;
|
||||
$registration->keyHandle = $this->base64u_encode($kh);
|
||||
|
||||
// length of certificate is stored in byte 3 and 4 (excluding the first 4 bytes)
|
||||
$certLen = 4;
|
||||
$certLen += ($regData[$offs + 2] << 8);
|
||||
$certLen += $regData[$offs + 3];
|
||||
|
||||
$rawCert = $this->fixSignatureUnusedBits(substr($rawReg, $offs, $certLen));
|
||||
$offs += $certLen;
|
||||
$pemCert = "-----BEGIN CERTIFICATE-----\r\n";
|
||||
$pemCert .= chunk_split(base64_encode($rawCert), 64);
|
||||
$pemCert .= "-----END CERTIFICATE-----";
|
||||
if($includeCert) {
|
||||
$registration->certificate = base64_encode($rawCert);
|
||||
}
|
||||
if($this->attestDir) {
|
||||
if(openssl_x509_checkpurpose($pemCert, -1, $this->get_certs()) !== true) {
|
||||
throw new Error('Attestation certificate can not be validated', ERR_ATTESTATION_VERIFICATION );
|
||||
}
|
||||
}
|
||||
|
||||
if(!openssl_pkey_get_public($pemCert)) {
|
||||
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
|
||||
}
|
||||
$signature = substr($rawReg, $offs);
|
||||
|
||||
$dataToVerify = chr(0);
|
||||
$dataToVerify .= hash('sha256', $request->appId, true);
|
||||
$dataToVerify .= hash('sha256', $clientData, true);
|
||||
$dataToVerify .= $kh;
|
||||
$dataToVerify .= $pubKey;
|
||||
|
||||
if(openssl_verify($dataToVerify, $signature, $pemCert, 'sha256') === 1) {
|
||||
return $registration;
|
||||
} else {
|
||||
throw new Error('Attestation signature does not match', ERR_ATTESTATION_SIGNATURE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to get an authentication request.
|
||||
*
|
||||
* @param array $registrations An array of the registrations to create authentication requests for.
|
||||
* @return array An array of SignRequest
|
||||
* @throws Error
|
||||
*/
|
||||
public function getAuthenticateData(array $registrations)
|
||||
{
|
||||
$sigs = array();
|
||||
$challenge = $this->createChallenge();
|
||||
foreach ($registrations as $reg) {
|
||||
if( !is_object( $reg ) ) {
|
||||
throw new \InvalidArgumentException('$registrations of getAuthenticateData() method only accepts array of object.');
|
||||
}
|
||||
|
||||
$sig = new SignRequest();
|
||||
$sig->appId = $this->appId;
|
||||
$sig->keyHandle = $reg->keyHandle;
|
||||
$sig->challenge = $challenge;
|
||||
$sigs[] = $sig;
|
||||
}
|
||||
return $sigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to verify an authentication response
|
||||
*
|
||||
* @param array $requests An array of outstanding authentication requests
|
||||
* @param array $registrations An array of current registrations
|
||||
* @param object $response A response from the authenticator
|
||||
* @return Registration
|
||||
* @throws Error
|
||||
*
|
||||
* The Registration object returned on success contains an updated counter
|
||||
* that should be saved for future authentications.
|
||||
* If the Error returned is ERR_COUNTER_TOO_LOW this is an indication of
|
||||
* token cloning or similar and appropriate action should be taken.
|
||||
*/
|
||||
public function doAuthenticate(array $requests, array $registrations, $response)
|
||||
{
|
||||
if( !is_object( $response ) ) {
|
||||
throw new \InvalidArgumentException('$response of doAuthenticate() method only accepts object.');
|
||||
}
|
||||
|
||||
if( property_exists( $response, 'errorCode') && $response->errorCode !== 0 ) {
|
||||
throw new Error('User-agent returned error. Error code: ' . $response->errorCode, ERR_BAD_UA_RETURNING );
|
||||
}
|
||||
|
||||
/** @var object|null $req */
|
||||
$req = null;
|
||||
|
||||
/** @var object|null $reg */
|
||||
$reg = null;
|
||||
|
||||
$clientData = $this->base64u_decode($response->clientData);
|
||||
$decodedClient = json_decode($clientData);
|
||||
foreach ($requests as $req) {
|
||||
if( !is_object( $req ) ) {
|
||||
throw new \InvalidArgumentException('$requests of doAuthenticate() method only accepts array of object.');
|
||||
}
|
||||
|
||||
if($req->keyHandle === $response->keyHandle && $req->challenge === $decodedClient->challenge) {
|
||||
break;
|
||||
}
|
||||
|
||||
$req = null;
|
||||
}
|
||||
if($req === null) {
|
||||
throw new Error('No matching request found', ERR_NO_MATCHING_REQUEST );
|
||||
}
|
||||
foreach ($registrations as $reg) {
|
||||
if( !is_object( $reg ) ) {
|
||||
throw new \InvalidArgumentException('$registrations of doAuthenticate() method only accepts array of object.');
|
||||
}
|
||||
|
||||
if($reg->keyHandle === $response->keyHandle) {
|
||||
break;
|
||||
}
|
||||
$reg = null;
|
||||
}
|
||||
if($reg === null) {
|
||||
throw new Error('No matching registration found', ERR_NO_MATCHING_REGISTRATION );
|
||||
}
|
||||
$pemKey = $this->pubkey_to_pem($this->base64u_decode($reg->publicKey));
|
||||
if($pemKey === null) {
|
||||
throw new Error('Decoding of public key failed', ERR_PUBKEY_DECODE );
|
||||
}
|
||||
|
||||
$signData = $this->base64u_decode($response->signatureData);
|
||||
$dataToVerify = hash('sha256', $req->appId, true);
|
||||
$dataToVerify .= substr($signData, 0, 5);
|
||||
$dataToVerify .= hash('sha256', $clientData, true);
|
||||
$signature = substr($signData, 5);
|
||||
|
||||
if(openssl_verify($dataToVerify, $signature, $pemKey, 'sha256') === 1) {
|
||||
$ctr = unpack("Nctr", substr($signData, 1, 4));
|
||||
$counter = $ctr['ctr'];
|
||||
/* TODO: wrap-around should be handled somehow.. */
|
||||
if($counter > $reg->counter) {
|
||||
$reg->counter = $counter;
|
||||
return $reg;
|
||||
} else {
|
||||
throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
|
||||
}
|
||||
} else {
|
||||
throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function get_certs()
|
||||
{
|
||||
$files = array();
|
||||
$dir = $this->attestDir;
|
||||
if($dir && $handle = opendir($dir)) {
|
||||
while(false !== ($entry = readdir($handle))) {
|
||||
if(is_file("$dir/$entry")) {
|
||||
$files[] = "$dir/$entry";
|
||||
}
|
||||
}
|
||||
closedir($handle);
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function base64u_encode($data)
|
||||
{
|
||||
return trim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function base64u_decode($data)
|
||||
{
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return null|string
|
||||
*/
|
||||
private function pubkey_to_pem($key)
|
||||
{
|
||||
if(strlen($key) !== PUBKEY_LEN || $key[0] !== "\x04") {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the public key to binary DER format first
|
||||
* Using the ECC SubjectPublicKeyInfo OIDs from RFC 5480
|
||||
*
|
||||
* SEQUENCE(2 elem) 30 59
|
||||
* SEQUENCE(2 elem) 30 13
|
||||
* OID1.2.840.10045.2.1 (id-ecPublicKey) 06 07 2a 86 48 ce 3d 02 01
|
||||
* OID1.2.840.10045.3.1.7 (secp256r1) 06 08 2a 86 48 ce 3d 03 01 07
|
||||
* BIT STRING(520 bit) 03 42 ..key..
|
||||
*/
|
||||
$der = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01";
|
||||
$der .= "\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42";
|
||||
$der .= "\0".$key;
|
||||
|
||||
$pem = "-----BEGIN PUBLIC KEY-----\r\n";
|
||||
$pem .= chunk_split(base64_encode($der), 64);
|
||||
$pem .= "-----END PUBLIC KEY-----";
|
||||
|
||||
return $pem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Error
|
||||
*/
|
||||
private function createChallenge()
|
||||
{
|
||||
$challenge = openssl_random_pseudo_bytes(32, $crypto_strong );
|
||||
if( $crypto_strong !== true ) {
|
||||
throw new Error('Unable to obtain a good source of randomness', ERR_BAD_RANDOM);
|
||||
}
|
||||
|
||||
$challenge = $this->base64u_encode( $challenge );
|
||||
|
||||
return $challenge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes a certificate where the signature contains unused bits.
|
||||
*
|
||||
* @param string $cert
|
||||
* @return mixed
|
||||
*/
|
||||
private function fixSignatureUnusedBits($cert)
|
||||
{
|
||||
if(in_array(hash('sha256', $cert), $this->FIXCERTS)) {
|
||||
$cert[strlen($cert) - 257] = "\0";
|
||||
}
|
||||
return $cert;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for building a registration request
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class RegisterRequest
|
||||
{
|
||||
/** Protocol version */
|
||||
public $version = U2F_VERSION;
|
||||
|
||||
/** Registration challenge */
|
||||
public $challenge;
|
||||
|
||||
/** Application id */
|
||||
public $appId;
|
||||
|
||||
/**
|
||||
* @param string $challenge
|
||||
* @param string $appId
|
||||
* @internal
|
||||
*/
|
||||
public function __construct($challenge, $appId)
|
||||
{
|
||||
$this->challenge = $challenge;
|
||||
$this->appId = $appId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for building up an authentication request
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class SignRequest
|
||||
{
|
||||
/** Protocol version */
|
||||
public $version = U2F_VERSION;
|
||||
|
||||
/** Authentication challenge */
|
||||
public $challenge;
|
||||
|
||||
/** Key handle of a registered authenticator */
|
||||
public $keyHandle;
|
||||
|
||||
/** Application id */
|
||||
public $appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class returned for successful registrations
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class Registration
|
||||
{
|
||||
/** The key handle of the registered authenticator */
|
||||
public $keyHandle;
|
||||
|
||||
/** The public key of the registered authenticator */
|
||||
public $publicKey;
|
||||
|
||||
/** The attestation certificate of the registered authenticator */
|
||||
public $certificate;
|
||||
|
||||
/** The counter associated with this registration */
|
||||
public $counter = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class, returned on errors
|
||||
*
|
||||
* @package u2flib_server
|
||||
*/
|
||||
class Error extends \Exception
|
||||
{
|
||||
/**
|
||||
* Override constructor and make message and code mandatory
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
* @param \Exception|null $previous
|
||||
*/
|
||||
public function __construct($message, $code, \Exception $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче