1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2024-12-23 02:04:46 +02:00

[Web] remove u2f lib

This commit is contained in:
FreddleSpl0it 2023-03-13 08:58:39 +01:00 committed by DerLinkman
parent 0a77cad2dd
commit 9d8c1a01ac
No known key found for this signature in database
GPG Key ID: F109FD97469550A2
31 changed files with 3 additions and 2358 deletions

View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "139c1e5dec323144cd778ce80fd1847e",
"content-hash": "6c4a1dce9b5f22b95c5cd87ecf0eb333",
"packages": [
{
"name": "bshaffer/oauth2-server-php",
@ -541,56 +541,6 @@
],
"time": "2022-02-13T18:13:33+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "php-mime-mail-parser/php-mime-mail-parser",
"version": "7.0.0",
@ -1677,47 +1627,6 @@
}
],
"time": "2022-09-28T08:42:51+00:00"
},
{
"name": "yubico/u2flib-server",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/Yubico/php-u2flib-server.git",
"reference": "55d813acf68212ad2cadecde07551600d6971939"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/55d813acf68212ad2cadecde07551600d6971939",
"reference": "55d813acf68212ad2cadecde07551600d6971939",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": ">= 1",
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"vimeo/psalm": "^0|^1|^2"
},
"type": "library",
"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",
"support": {
"issues": "https://github.com/Yubico/php-u2flib-server/issues",
"source": "https://github.com/Yubico/php-u2flib-server/tree/1.0.2"
},
"time": "2018-09-07T08:16:44+00:00"
}
],
"packages-dev": [],

View File

@ -11,9 +11,4 @@ return array(
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'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',
);

View File

@ -174,11 +174,6 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
'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)

View File

@ -551,59 +551,6 @@
],
"install-path": "../nesbot/carbon"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.100",
"version_normalized": "9.99.100.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"time": "2020-10-15T08:29:30+00:00",
"type": "library",
"installation-source": "dist",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"support": {
"email": "info@paragonie.com",
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"install-path": "../paragonie/random_compat"
},
{
"name": "php-mime-mail-parser/php-mime-mail-parser",
"version": "7.0.0",
@ -1730,46 +1677,6 @@
}
],
"install-path": "../twig/twig"
},
{
"name": "yubico/u2flib-server",
"version": "1.0.2",
"version_normalized": "1.0.2.0",
"source": {
"type": "git",
"url": "https://github.com/Yubico/php-u2flib-server.git",
"reference": "55d813acf68212ad2cadecde07551600d6971939"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Yubico/php-u2flib-server/zipball/55d813acf68212ad2cadecde07551600d6971939",
"reference": "55d813acf68212ad2cadecde07551600d6971939",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": ">= 1",
"php": ">=5.6"
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"vimeo/psalm": "^0|^1|^2"
},
"time": "2018-09-07T08:16:44+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",
"install-path": "../yubico/u2flib-server"
}
],
"dev": true,

View File

@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd',
'reference' => 'a7e309f1c89c5579653fae00ec9655bb5f992ec6',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => '8e0b1d8aee4af02311692cb031695cc2ac3850fd',
'reference' => 'a7e309f1c89c5579653fae00ec9655bb5f992ec6',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -103,15 +103,6 @@
'aliases' => array(),
'dev_requirement' => false,
),
'paragonie/random_compat' => array(
'pretty_version' => 'v9.99.100',
'version' => '9.99.100.0',
'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
'type' => 'library',
'install_path' => __DIR__ . '/../paragonie/random_compat',
'aliases' => array(),
'dev_requirement' => false,
),
'php-mime-mail-parser/php-mime-mail-parser' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
@ -253,14 +244,5 @@
'aliases' => array(),
'dev_requirement' => false,
),
'yubico/u2flib-server' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => '55d813acf68212ad2cadecde07551600d6971939',
'type' => 'library',
'install_path' => __DIR__ . '/../yubico/u2flib-server',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Paragon Initiative Enterprises
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.

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
php -dphar.readonly=0 "$basedir/other/build_phar.php" $*

View File

@ -1,34 +0,0 @@
{
"name": "paragonie/random_compat",
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"random",
"polyfill",
"pseudorandom"
],
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"support": {
"issues": "https://github.com/paragonie/random_compat/issues",
"email": "info@paragonie.com",
"source": "https://github.com/paragonie/random_compat"
},
"require": {
"php": ">= 7"
},
"require-dev": {
"vimeo/psalm": "^1",
"phpunit/phpunit": "4.*|5.*"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
}
}

View File

@ -1,5 +0,0 @@
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
+h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
-----END PUBLIC KEY-----

View File

@ -1,11 +0,0 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2.0.22 (MingW32)
iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
=B6+8
-----END PGP SIGNATURE-----

View File

@ -1,32 +0,0 @@
<?php
/**
* Random_* Compatibility Library
* for using the new PHP 7 random_* API in PHP 5 projects
*
* @version 2.99.99
* @released 2018-06-06
*
* The MIT License (MIT)
*
* Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
*
* 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.
*/
// NOP

View File

@ -1,57 +0,0 @@
<?php
$dist = dirname(__DIR__).'/dist';
if (!is_dir($dist)) {
mkdir($dist, 0755);
}
if (file_exists($dist.'/random_compat.phar')) {
unlink($dist.'/random_compat.phar');
}
$phar = new Phar(
$dist.'/random_compat.phar',
FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME,
'random_compat.phar'
);
rename(
dirname(__DIR__).'/lib/random.php',
dirname(__DIR__).'/lib/index.php'
);
$phar->buildFromDirectory(dirname(__DIR__).'/lib');
rename(
dirname(__DIR__).'/lib/index.php',
dirname(__DIR__).'/lib/random.php'
);
/**
* If we pass an (optional) path to a private key as a second argument, we will
* sign the Phar with OpenSSL.
*
* If you leave this out, it will produce an unsigned .phar!
*/
if ($argc > 1) {
if (!@is_readable($argv[1])) {
echo 'Could not read the private key file:', $argv[1], "\n";
exit(255);
}
$pkeyFile = file_get_contents($argv[1]);
$private = openssl_get_privatekey($pkeyFile);
if ($private !== false) {
$pkey = '';
openssl_pkey_export($private, $pkey);
$phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
/**
* Save the corresponding public key to the file
*/
if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
$details = openssl_pkey_get_details($private);
file_put_contents(
$dist.'/random_compat.phar.pubkey',
$details['key']
);
}
} else {
echo 'An error occurred reading the private key from OpenSSL.', "\n";
exit(255);
}
}

View File

@ -1,9 +0,0 @@
<?php
require_once 'lib/byte_safe_strings.php';
require_once 'lib/cast_to_int.php';
require_once 'lib/error_polyfill.php';
require_once 'other/ide_stubs/libsodium.php';
require_once 'lib/random.php';
$int = random_int(0, 65536);

View File

@ -1,19 +0,0 @@
<?xml version="1.0"?>
<psalm
autoloader="psalm-autoload.php"
stopOnFirstError="false"
useDocblockTypes="true"
>
<projectFiles>
<directory name="lib" />
</projectFiles>
<issueHandlers>
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<DuplicateClass errorLevel="info" />
<InvalidOperand errorLevel="info" />
<UndefinedConstant errorLevel="info" />
<MissingReturnType errorLevel="info" />
<InvalidReturnType errorLevel="info" />
</issueHandlers>
</psalm>

View File

@ -1,7 +0,0 @@
composer.lock
vendor/
.*.swp
php-u2flib-server-*.tar.gz
php-u2flib-server-*.tar.gz.sig
apidocs/
build/

View File

@ -1,23 +0,0 @@
language: php
sudo: false
php:
- 7.0
- 7.1
- 7.2
- hhvm
matrix:
include:
- php: 5.6
env: COVERALLS=true
allow_failures:
- php: hhvm
before_script:
- composer install
script:
- ./vendor/bin/psalm
- ./vendor/phpunit/phpunit/phpunit -c phpunit.xml
after_success:
- test -z $COVERALLS || (composer require satooshi/php-coveralls && vendor/bin/coveralls -v)

View File

@ -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

View File

@ -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.

View File

@ -1,34 +0,0 @@
php-u2flib-server NEWS -- History of user-visible changes.
* Version 1.0.2 (released 2018-09-07)
** Additional error checks.
** Add user presence check.
** Support single files for attestation root.
** Type safety, CSPRNG, avoid chr().
* 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.

View File

@ -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

View File

@ -1 +0,0 @@
README

View File

@ -1,13 +0,0 @@
destination: apidocs
source:
- src/u2flib_server
exclude:
- "*/tests/*"
groups: none
tree: false
title: php-u2flib-server API

View File

@ -1,18 +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":"*",
"paragonie/random_compat": ">= 1",
"php": ">=5.6"
},
"autoload": {
"classmap": ["src/"]
},
"require-dev": {
"phpunit/phpunit": "~5.7",
"vimeo/psalm": "^0|^1|^2"
}
}

View File

@ -1,40 +0,0 @@
#!/bin/sh
set -e
VERSION=$1
PGP_KEYID=$2
if [ "x$PGP_KEYID" = "x" ]; then
echo "try with $0 VERSION PGP_KEYID"
echo "example: $0 0.0.1 B2168C0A"
exit
fi
if ! head -3 NEWS | grep -q "Version $VERSION .released `date -I`"; then
echo "You need to update date/version in NEWS"
exit
fi
if [ "x$YUBICO_GITHUB_REPO" = "x" ]; then
echo "you need to define YUBICO_GITHUB_REPO"
exit
fi
releasename=php-u2flib-server-${VERSION}
git push
git tag -u ${PGP_KEYID} -m $VERSION $VERSION
git push --tags
tmpdir=`mktemp -d /tmp/release.XXXXXX`
releasedir=${tmpdir}/${releasename}
mkdir -p $releasedir
git archive $VERSION --format=tar | tar -xC $releasedir
git2cl > $releasedir/ChangeLog
cd $releasedir
apigen generate
cd -
tar -cz --directory=$tmpdir --file=${releasename}.tar.gz $releasename
gpg --detach-sign --default-key $PGP_KEYID ${releasename}.tar.gz
$YUBICO_GITHUB_REPO/publish php-u2flib-server $VERSION ${releasename}.tar.gz*
rm -rf $tmpdir

View File

@ -1,651 +0,0 @@
// Copyright 2014-2015 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
/**
* @fileoverview The U2F api.
*/
'use strict';
/** Namespace for the U2F api.
* @type {Object}
*/
var u2f = u2f || {};
/**
* The U2F extension id
* @type {string}
* @const
*/
u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd';
/**
* Message types for messsages to/from the extension
* @const
* @enum {string}
*/
u2f.MessageTypes = {
'U2F_REGISTER_REQUEST': 'u2f_register_request',
'U2F_SIGN_REQUEST': 'u2f_sign_request',
'U2F_REGISTER_RESPONSE': 'u2f_register_response',
'U2F_SIGN_RESPONSE': 'u2f_sign_response'
};
/**
* Response status codes
* @const
* @enum {number}
*/
u2f.ErrorCodes = {
'OK': 0,
'OTHER_ERROR': 1,
'BAD_REQUEST': 2,
'CONFIGURATION_UNSUPPORTED': 3,
'DEVICE_INELIGIBLE': 4,
'TIMEOUT': 5
};
/**
* A message type for registration requests
* @typedef {{
* type: u2f.MessageTypes,
* signRequests: Array<u2f.SignRequest>,
* registerRequests: ?Array<u2f.RegisterRequest>,
* timeoutSeconds: ?number,
* requestId: ?number
* }}
*/
u2f.Request;
/**
* A message for registration responses
* @typedef {{
* type: u2f.MessageTypes,
* responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse),
* requestId: ?number
* }}
*/
u2f.Response;
/**
* An error object for responses
* @typedef {{
* errorCode: u2f.ErrorCodes,
* errorMessage: ?string
* }}
*/
u2f.Error;
/**
* Data object for a single sign request.
* @typedef {{
* version: string,
* challenge: string,
* keyHandle: string,
* appId: string
* }}
*/
u2f.SignRequest;
/**
* Data object for a sign response.
* @typedef {{
* keyHandle: string,
* signatureData: string,
* clientData: string
* }}
*/
u2f.SignResponse;
/**
* Data object for a registration request.
* @typedef {{
* version: string,
* challenge: string,
* appId: string
* }}
*/
u2f.RegisterRequest;
/**
* Data object for a registration response.
* @typedef {{
* registrationData: string,
* clientData: string
* }}
*/
u2f.RegisterResponse;
// Low level MessagePort API support
/**
* Sets up a MessagePort to the U2F extension using the
* available mechanisms.
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
*/
u2f.getMessagePort = function(callback) {
if (typeof chrome != 'undefined' && chrome.runtime) {
// The actual message here does not matter, but we need to get a reply
// for the callback to run. Thus, send an empty signature request
// in order to get a failure response.
var msg = {
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
signRequests: []
};
chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() {
if (!chrome.runtime.lastError) {
// We are on a whitelisted origin and can talk directly
// with the extension.
u2f.getChromeRuntimePort_(callback);
} else {
// chrome.runtime was available, but we couldn't message
// the extension directly, use iframe
u2f.getIframePort_(callback);
}
});
} else if (u2f.isAndroidChrome_()) {
u2f.getAuthenticatorPort_(callback);
} else {
// chrome.runtime was not available at all, which is normal
// when this origin doesn't have access to any extensions.
u2f.getIframePort_(callback);
}
};
/**
* Detect chrome running on android based on the browser's useragent.
* @private
*/
u2f.isAndroidChrome_ = function() {
var userAgent = navigator.userAgent;
return userAgent.indexOf('Chrome') != -1 &&
userAgent.indexOf('Android') != -1;
};
/**
* Connects directly to the extension via chrome.runtime.connect
* @param {function(u2f.WrappedChromeRuntimePort_)} callback
* @private
*/
u2f.getChromeRuntimePort_ = function(callback) {
var port = chrome.runtime.connect(u2f.EXTENSION_ID,
{'includeTlsChannelId': true});
setTimeout(function() {
callback(new u2f.WrappedChromeRuntimePort_(port));
}, 0);
};
/**
* Return a 'port' abstraction to the Authenticator app.
* @param {function(u2f.WrappedAuthenticatorPort_)} callback
* @private
*/
u2f.getAuthenticatorPort_ = function(callback) {
setTimeout(function() {
callback(new u2f.WrappedAuthenticatorPort_());
}, 0);
};
/**
* A wrapper for chrome.runtime.Port that is compatible with MessagePort.
* @param {Port} port
* @constructor
* @private
*/
u2f.WrappedChromeRuntimePort_ = function(port) {
this.port_ = port;
};
/**
* Format a return a sign request.
* @param {Array<u2f.SignRequest>} signRequests
* @param {number} timeoutSeconds
* @param {number} reqId
* @return {Object}
*/
u2f.WrappedChromeRuntimePort_.prototype.formatSignRequest_ =
function(signRequests, timeoutSeconds, reqId) {
return {
type: u2f.MessageTypes.U2F_SIGN_REQUEST,
signRequests: signRequests,
timeoutSeconds: timeoutSeconds,
requestId: reqId
};
};
/**
* Format a return a register request.
* @param {Array<u2f.SignRequest>} signRequests
* @param {Array<u2f.RegisterRequest>} signRequests
* @param {number} timeoutSeconds
* @param {number} reqId
* @return {Object}
*/
u2f.WrappedChromeRuntimePort_.prototype.formatRegisterRequest_ =
function(signRequests, registerRequests, timeoutSeconds, reqId) {
return {
type: u2f.MessageTypes.U2F_REGISTER_REQUEST,
signRequests: signRequests,
registerRequests: registerRequests,
timeoutSeconds: timeoutSeconds,
requestId: reqId
};
};
/**
* Posts a message on the underlying channel.
* @param {Object} message
*/
u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) {
this.port_.postMessage(message);
};
/**
* Emulates the HTML 5 addEventListener interface. Works only for the
* onmessage event, which is hooked up to the chrome.runtime.Port.onMessage.
* @param {string} eventName
* @param {function({data: Object})} handler
*/
u2f.WrappedChromeRuntimePort_.prototype.addEventListener =
function(eventName, handler) {
var name = eventName.toLowerCase();
if (name == 'message' || name == 'onmessage') {
this.port_.onMessage.addListener(function(message) {
// Emulate a minimal MessageEvent object
handler({'data': message});
});
} else {
console.error('WrappedChromeRuntimePort only supports onMessage');
}
};
/**
* Wrap the Authenticator app with a MessagePort interface.
* @constructor
* @private
*/
u2f.WrappedAuthenticatorPort_ = function() {
this.requestId_ = -1;
this.requestObject_ = null;
}
/**
* Launch the Authenticator intent.
* @param {Object} message
*/
u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) {
var intentLocation = /** @type {string} */ (message);
document.location = intentLocation;
};
/**
* Emulates the HTML 5 addEventListener interface.
* @param {string} eventName
* @param {function({data: Object})} handler
*/
u2f.WrappedAuthenticatorPort_.prototype.addEventListener =
function(eventName, handler) {
var name = eventName.toLowerCase();
if (name == 'message') {
var self = this;
/* Register a callback to that executes when
* chrome injects the response. */
window.addEventListener(
'message', self.onRequestUpdate_.bind(self, handler), false);
} else {
console.error('WrappedAuthenticatorPort only supports message');
}
};
/**
* Callback invoked when a response is received from the Authenticator.
* @param function({data: Object}) callback
* @param {Object} message message Object
*/
u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ =
function(callback, message) {
var messageObject = JSON.parse(message.data);
var intentUrl = messageObject['intentURL'];
var errorCode = messageObject['errorCode'];
var responseObject = null;
if (messageObject.hasOwnProperty('data')) {
responseObject = /** @type {Object} */ (
JSON.parse(messageObject['data']));
responseObject['requestId'] = this.requestId_;
}
/* Sign responses from the authenticator do not conform to U2F,
* convert to U2F here. */
responseObject = this.doResponseFixups_(responseObject);
callback({'data': responseObject});
};
/**
* Fixup the response provided by the Authenticator to conform with
* the U2F spec.
* @param {Object} responseData
* @return {Object} the U2F compliant response object
*/
u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ =
function(responseObject) {
if (responseObject.hasOwnProperty('responseData')) {
return responseObject;
} else if (this.requestObject_['type'] != u2f.MessageTypes.U2F_SIGN_REQUEST) {
// Only sign responses require fixups. If this is not a response
// to a sign request, then an internal error has occurred.
return {
'type': u2f.MessageTypes.U2F_REGISTER_RESPONSE,
'responseData': {
'errorCode': u2f.ErrorCodes.OTHER_ERROR,
'errorMessage': 'Internal error: invalid response from Authenticator'
}
};
}
/* Non-conformant sign response, do fixups. */
var encodedChallengeObject = responseObject['challenge'];
if (typeof encodedChallengeObject !== 'undefined') {
var challengeObject = JSON.parse(atob(encodedChallengeObject));
var serverChallenge = challengeObject['challenge'];
var challengesList = this.requestObject_['signData'];
var requestChallengeObject = null;
for (var i = 0; i < challengesList.length; i++) {
var challengeObject = challengesList[i];
if (challengeObject['keyHandle'] == responseObject['keyHandle']) {
requestChallengeObject = challengeObject;
break;
}
}
}
var responseData = {
'errorCode': responseObject['resultCode'],
'keyHandle': responseObject['keyHandle'],
'signatureData': responseObject['signature'],
'clientData': encodedChallengeObject
};
return {
'type': u2f.MessageTypes.U2F_SIGN_RESPONSE,
'responseData': responseData,
'requestId': responseObject['requestId']
}
};
/**
* Base URL for intents to Authenticator.
* @const
* @private
*/
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ =
'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE';
/**
* Format a return a sign request.
* @param {Array<u2f.SignRequest>} signRequests
* @param {number} timeoutSeconds (ignored for now)
* @param {number} reqId
* @return {string}
*/
u2f.WrappedAuthenticatorPort_.prototype.formatSignRequest_ =
function(signRequests, timeoutSeconds, reqId) {
if (!signRequests || signRequests.length == 0) {
return null;
}
/* TODO(fixme): stash away requestId, as the authenticator app does
* not return it for sign responses. */
this.requestId_ = reqId;
/* TODO(fixme): stash away the signRequests, to deal with the legacy
* response format returned by the Authenticator app. */
this.requestObject_ = {
'type': u2f.MessageTypes.U2F_SIGN_REQUEST,
'signData': signRequests,
'requestId': reqId,
'timeout': timeoutSeconds
};
var appId = signRequests[0]['appId'];
var intentUrl =
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
';S.appId=' + encodeURIComponent(appId) +
';S.eventId=' + reqId +
';S.challenges=' +
encodeURIComponent(
JSON.stringify(this.getBrowserDataList_(signRequests))) + ';end';
return intentUrl;
};
/**
* Get the browser data objects from the challenge list
* @param {Array} challenges list of challenges
* @return {Array} list of browser data objects
* @private
*/
u2f.WrappedAuthenticatorPort_
.prototype.getBrowserDataList_ = function(challenges) {
return challenges
.map(function(challenge) {
var browserData = {
'typ': 'navigator.id.getAssertion',
'challenge': challenge['challenge']
};
var challengeObject = {
'challenge' : browserData,
'keyHandle' : challenge['keyHandle']
};
return challengeObject;
});
};
/**
* Format a return a register request.
* @param {Array<u2f.SignRequest>} signRequests
* @param {Array<u2f.RegisterRequest>} enrollChallenges
* @param {number} timeoutSeconds (ignored for now)
* @param {number} reqId
* @return {Object}
*/
u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ =
function(signRequests, enrollChallenges, timeoutSeconds, reqId) {
if (!enrollChallenges || enrollChallenges.length == 0) {
return null;
}
// Assume the appId is the same for all enroll challenges.
var appId = enrollChallenges[0]['appId'];
var registerRequests = [];
for (var i = 0; i < enrollChallenges.length; i++) {
var registerRequest = {
'challenge': enrollChallenges[i]['challenge'],
'version': enrollChallenges[i]['version']
};
if (enrollChallenges[i]['appId'] != appId) {
// Only include the appId when it differs from the first appId.
registerRequest['appId'] = enrollChallenges[i]['appId'];
}
registerRequests.push(registerRequest);
}
var registeredKeys = [];
if (signRequests) {
for (i = 0; i < signRequests.length; i++) {
var key = {
'keyHandle': signRequests[i]['keyHandle'],
'version': signRequests[i]['version']
};
// Only include the appId when it differs from the appId that's
// being registered now.
if (signRequests[i]['appId'] != appId) {
key['appId'] = signRequests[i]['appId'];
}
registeredKeys.push(key);
}
}
var request = {
'type': u2f.MessageTypes.U2F_REGISTER_REQUEST,
'appId': appId,
'registerRequests': registerRequests,
'registeredKeys': registeredKeys,
'requestId': reqId,
'timeoutSeconds': timeoutSeconds
};
var intentUrl =
u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ +
';S.request=' + encodeURIComponent(JSON.stringify(request)) +
';end';
/* TODO(fixme): stash away requestId, this is is not necessary for
* register requests, but here to keep parity with sign.
*/
this.requestId_ = reqId;
return intentUrl;
};
/**
* Sets up an embedded trampoline iframe, sourced from the extension.
* @param {function(MessagePort)} callback
* @private
*/
u2f.getIframePort_ = function(callback) {
// Create the iframe
var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID;
var iframe = document.createElement('iframe');
iframe.src = iframeOrigin + '/u2f-comms.html';
iframe.setAttribute('style', 'display:none');
document.body.appendChild(iframe);
var channel = new MessageChannel();
var ready = function(message) {
if (message.data == 'ready') {
channel.port1.removeEventListener('message', ready);
callback(channel.port1);
} else {
console.error('First event on iframe port was not "ready"');
}
};
channel.port1.addEventListener('message', ready);
channel.port1.start();
iframe.addEventListener('load', function() {
// Deliver the port to the iframe and initialize
iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]);
});
};
// High-level JS API
/**
* Default extension response timeout in seconds.
* @const
*/
u2f.EXTENSION_TIMEOUT_SEC = 30;
/**
* A singleton instance for a MessagePort to the extension.
* @type {MessagePort|u2f.WrappedChromeRuntimePort_}
* @private
*/
u2f.port_ = null;
/**
* Callbacks waiting for a port
* @type {Array<function((MessagePort|u2f.WrappedChromeRuntimePort_))>}
* @private
*/
u2f.waitingForPort_ = [];
/**
* A counter for requestIds.
* @type {number}
* @private
*/
u2f.reqCounter_ = 0;
/**
* A map from requestIds to client callbacks
* @type {Object.<number,(function((u2f.Error|u2f.RegisterResponse))
* |function((u2f.Error|u2f.SignResponse)))>}
* @private
*/
u2f.callbackMap_ = {};
/**
* Creates or retrieves the MessagePort singleton to use.
* @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback
* @private
*/
u2f.getPortSingleton_ = function(callback) {
if (u2f.port_) {
callback(u2f.port_);
} else {
if (u2f.waitingForPort_.length == 0) {
u2f.getMessagePort(function(port) {
u2f.port_ = port;
u2f.port_.addEventListener('message',
/** @type {function(Event)} */ (u2f.responseHandler_));
// Careful, here be async callbacks. Maybe.
while (u2f.waitingForPort_.length)
u2f.waitingForPort_.shift()(u2f.port_);
});
}
u2f.waitingForPort_.push(callback);
}
};
/**
* Handles response messages from the extension.
* @param {MessageEvent.<u2f.Response>} message
* @private
*/
u2f.responseHandler_ = function(message) {
var response = message.data;
var reqId = response['requestId'];
if (!reqId || !u2f.callbackMap_[reqId]) {
console.error('Unknown or missing requestId in response.');
return;
}
var cb = u2f.callbackMap_[reqId];
delete u2f.callbackMap_[reqId];
cb(response['responseData']);
};
/**
* Dispatches an array of sign requests to available U2F tokens.
* @param {Array<u2f.SignRequest>} signRequests
* @param {function((u2f.Error|u2f.SignResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.sign = function(signRequests, callback, opt_timeoutSeconds) {
u2f.getPortSingleton_(function(port) {
var reqId = ++u2f.reqCounter_;
u2f.callbackMap_[reqId] = callback;
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
var req = port.formatSignRequest_(signRequests, timeoutSeconds, reqId);
port.postMessage(req);
});
};
/**
* Dispatches register requests to available U2F tokens. An array of sign
* requests identifies already registered tokens.
* @param {Array<u2f.RegisterRequest>} registerRequests
* @param {Array<u2f.SignRequest>} signRequests
* @param {function((u2f.Error|u2f.RegisterResponse))} callback
* @param {number=} opt_timeoutSeconds
*/
u2f.register = function(registerRequests, signRequests,
callback, opt_timeoutSeconds) {
u2f.getPortSingleton_(function(port) {
var reqId = ++u2f.reqCounter_;
u2f.callbackMap_[reqId] = callback;
var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ?
opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC);
var req = port.formatRegisterRequest_(
signRequests, registerRequests, timeoutSeconds, reqId);
port.postMessage(req);
});
};

View File

@ -1,83 +0,0 @@
#!/usr/bin/php
<?php
/* Copyright (c) 2015 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.
*/
/**
* This is a basic example of a u2f-server command line that can be used
* with the u2f-host binary to perform regitrations and authentications.
*/
require_once('../../src/u2flib_server/U2F.php');
$options = getopt("rao:R:");
$mode;
$challenge;
$response;
$result;
$regs;
if(array_key_exists('r', $options)) {
$mode = "register";
} elseif(array_key_exists('a', $options)) {
if(!array_key_exists('R', $options)) {
print "a registration must be supplied with -R";
exit(1);
}
$regs = json_decode('[' . $options['R'] . ']');
$mode = "authenticate";
} else {
print "-r or -a must be used\n";
exit(1);
}
if(!array_key_exists('o', $options)) {
print "origin must be supplied with -o\n";
exit(1);
}
$u2f = new u2flib_server\U2F($options['o']);
if($mode === "register") {
$challenge = $u2f->getRegisterData();
} elseif($mode === "authenticate") {
$challenge = $u2f->getAuthenticateData($regs);
}
print json_encode($challenge[0]) . "\n";
$response = fgets(STDIN);
if($mode === "register") {
$result = $u2f->doRegister($challenge[0], json_decode($response));
} elseif($mode === "authenticate") {
$result = $u2f->doAuthenticate($challenge, $regs, json_decode($response));
}
print json_encode($result) . "\n";
?>

View File

@ -1,186 +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.
*/
/**
* This is a minimal example of U2F registration and authentication.
* The data that has to be stored between registration and authentication
* is stored in browser localStorage, so there's nothing real-world
* about this.
*/
require_once('../../src/u2flib_server/U2F.php');
$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://";
$u2f = new u2flib_server\U2F($scheme . $_SERVER['HTTP_HOST']);
?>
<html>
<head>
<title>PHP U2F Demo</title>
<script src="../assets/u2f-api.js"></script>
<script>
function addRegistration(reg) {
var existing = localStorage.getItem('u2fregistration');
var regobj = JSON.parse(reg);
var data = null;
if(existing) {
data = JSON.parse(existing);
if(Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
if(data[i].keyHandle === regobj.keyHandle) {
data.splice(i,1);
break;
}
}
data.push(regobj);
} else {
data = null;
}
}
if(data == null) {
data = [regobj];
}
localStorage.setItem('u2fregistration', JSON.stringify(data));
}
<?php
function fixupArray($data) {
$ret = array();
$decoded = json_decode($data);
foreach ($decoded as $d) {
$ret[] = json_encode($d);
}
return $ret;
}
if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(isset($_POST['startRegister'])) {
$regs = json_decode($_POST['registrations']) ? : array();
list($data, $reqs) = $u2f->getRegisterData($regs);
echo "var request = " . json_encode($data) . ";\n";
echo "var signs = " . json_encode($reqs) . ";\n";
?>
setTimeout(function() {
console.log("Register: ", request);
u2f.register([request], signs, function(data) {
var form = document.getElementById('form');
var reg = document.getElementById('doRegister');
var req = document.getElementById('request');
console.log("Register callback", data);
if(data.errorCode && data.errorCode != 0) {
alert("registration failed with errror: " + data.errorCode);
return;
}
reg.value=JSON.stringify(data);
req.value=JSON.stringify(request);
form.submit();
});
}, 1000);
<?php
} else if($_POST['doRegister']) {
try {
$data = $u2f->doRegister(json_decode($_POST['request']), json_decode($_POST['doRegister']));
echo "var registration = '" . json_encode($data) . "';\n";
?>
addRegistration(registration);
alert("registration successful!");
<?php
} catch(u2flib_server\Error $e) {
echo "alert('error:" . $e->getMessage() . "');\n";
}
} else if(isset($_POST['startAuthenticate'])) {
$regs = json_decode($_POST['registrations']);
$data = $u2f->getAuthenticateData($regs);
echo "var registrations = " . $_POST['registrations'] . ";\n";
echo "var request = " . json_encode($data) . ";\n";
?>
setTimeout(function() {
console.log("sign: ", request);
u2f.sign(request, function(data) {
var form = document.getElementById('form');
var reg = document.getElementById('doAuthenticate');
var req = document.getElementById('request');
var regs = document.getElementById('registrations');
console.log("Authenticate callback", data);
reg.value=JSON.stringify(data);
req.value=JSON.stringify(request);
regs.value=JSON.stringify(registrations);
form.submit();
});
}, 1000);
<?php
} else if($_POST['doAuthenticate']) {
$reqs = json_decode($_POST['request']);
$regs = json_decode($_POST['registrations']);
try {
$data = $u2f->doAuthenticate($reqs, $regs, json_decode($_POST['doAuthenticate']));
echo "var registration = '" . json_encode($data) . "';\n";
echo "addRegistration(registration);\n";
echo "alert('Authentication successful, counter:" . $data->counter . "');\n";
} catch(u2flib_server\Error $e) {
echo "alert('error:" . $e->getMessage() . "');\n";
}
}
}
?>
</script>
</head>
<body>
<form method="POST" id="form">
<button name="startRegister" type="submit">Register</button>
<input type="hidden" name="doRegister" id="doRegister"/>
<button name="startAuthenticate" type="submit" id="startAuthenticate">Authenticate</button>
<input type="hidden" name="doAuthenticate" id="doAuthenticate"/>
<input type="hidden" name="request" id="request"/>
<input type="hidden" name="registrations" id="registrations"/>
</form>
<p>
<span id="registered">0</span> Authenticators currently registered.
</p>
<script>
var reg = localStorage.getItem('u2fregistration');
var auth = document.getElementById('startAuthenticate');
if(reg == null) {
auth.disabled = true;
} else {
var regs = document.getElementById('registrations');
decoded = JSON.parse(reg);
if(!Array.isArray(decoded)) {
auth.disabled = true;
} else {
regs.value = reg;
console.log("set the registrations to : ", reg);
var regged = document.getElementById('registered');
regged.innerHTML = decoded.length;
}
}
</script>
</body>
</html>

View File

@ -1,204 +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.
*/
/**
* This is a simple example using PDO and a sqlite database for storing
* registrations. It supports multiple registrations associated with each user.
*/
require_once('../../src/u2flib_server/U2F.php');
$dbfile = '/var/tmp/u2f-pdo.sqlite';
$pdo = new PDO("sqlite:$dbfile");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$pdo->exec("create table if not exists users (id integer primary key, name varchar(255))");
$pdo->exec("create table if not exists registrations (id integer primary key, user_id integer, keyHandle varchar(255), publicKey varchar(255), certificate text, counter integer)");
$scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://";
$u2f = new u2flib_server\U2F($scheme . $_SERVER['HTTP_HOST']);
session_start();
function createAndGetUser($name) {
global $pdo;
$sel = $pdo->prepare("select * from users where name = ?");
$sel->execute(array($name));
$user = $sel->fetch();
if(!$user) {
$ins = $pdo->prepare("insert into users (name) values(?)");
$ins->execute(array($name));
$sel->execute(array($name));
$user = $sel->fetch();
}
return $user;
}
function getRegs($user_id) {
global $pdo;
$sel = $pdo->prepare("select * from registrations where user_id = ?");
$sel->execute(array($user_id));
return $sel->fetchAll();
}
function addReg($user_id, $reg) {
global $pdo;
$ins = $pdo->prepare("insert into registrations (user_id, keyHandle, publicKey, certificate, counter) values (?, ?, ?, ?, ?)");
$ins->execute(array($user_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
}
function updateReg($reg) {
global $pdo;
$upd = $pdo->prepare("update registrations set counter = ? where id = ?");
$upd->execute(array($reg->counter, $reg->id));
}
?>
<html>
<head>
<title>PHP U2F example</title>
<script src="../assets/u2f-api.js"></script>
<script>
<?php
if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!$_POST['username']) {
echo "alert('no username provided!');";
} else if(!isset($_POST['action']) && !isset($_POST['register2']) && !isset($_POST['authenticate2'])) {
echo "alert('no action provided!');";
} else {
$user = createAndGetUser($_POST['username']);
if(isset($_POST['action'])) {
switch($_POST['action']):
case 'register':
try {
$data = $u2f->getRegisterData(getRegs($user->id));
list($req,$sigs) = $data;
$_SESSION['regReq'] = json_encode($req);
echo "var req = " . json_encode($req) . ";";
echo "var sigs = " . json_encode($sigs) . ";";
echo "var username = '" . $user->name . "';";
?>
setTimeout(function() {
console.log("Register: ", req);
u2f.register([req], sigs, function(data) {
var form = document.getElementById('form');
var reg = document.getElementById('register2');
var user = document.getElementById('username');
console.log("Register callback", data);
if(data.errorCode && errorCode != 0) {
alert("registration failed with errror: " + data.errorCode);
return;
}
reg.value = JSON.stringify(data);
user.value = username;
form.submit();
});
}, 1000);
<?php
} catch( Exception $e ) {
echo "alert('error: " . $e->getMessage() . "');";
}
break;
case 'authenticate':
try {
$reqs = json_encode($u2f->getAuthenticateData(getRegs($user->id)));
$_SESSION['authReq'] = $reqs;
echo "var req = $reqs;";
echo "var username = '" . $user->name . "';";
?>
setTimeout(function() {
console.log("sign: ", req);
u2f.sign(req, function(data) {
var form = document.getElementById('form');
var auth = document.getElementById('authenticate2');
var user = document.getElementById('username');
console.log("Authenticate callback", data);
auth.value=JSON.stringify(data);
user.value = username;
form.submit();
});
}, 1000);
<?php
} catch( Exception $e ) {
echo "alert('error: " . $e->getMessage() . "');";
}
break;
endswitch;
} else if($_POST['register2']) {
try {
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_POST['register2']));
addReg($user->id, $reg);
} catch( Exception $e ) {
echo "alert('error: " . $e->getMessage() . "');";
} finally {
$_SESSION['regReq'] = null;
}
} else if($_POST['authenticate2']) {
try {
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), getRegs($user->id), json_decode($_POST['authenticate2']));
updateReg($reg);
echo "alert('success: " . $reg->counter . "');";
} catch( Exception $e ) {
echo "alert('error: " . $e->getMessage() . "');";
} finally {
$_SESSION['authReq'] = null;
}
}
}
}
?>
</script>
</head>
<body>
<form method="POST" id="form">
username: <input name="username" id="username"/><br/>
register: <input value="register" name="action" type="radio"/><br/>
authenticate: <input value="authenticate" name="action" type="radio"/><br/>
<input type="hidden" name="register2" id="register2"/>
<input type="hidden" name="authenticate2" id="authenticate2"/>
<button type="submit">Submit!</button>
</form>
</body>
</html>

View File

@ -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>

View File

@ -1,48 +0,0 @@
<?xml version="1.0"?>
<psalm
totallyTyped="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config file:///mnt/share/php-u2flib-server/vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<DeprecatedProperty errorLevel="info" />
<DeprecatedClass errorLevel="info" />
<DeprecatedInterface errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<MisplacedRequiredParam errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />
<MissingParamType errorLevel="info" />
<RedundantCondition errorLevel="info" />
<DocblockTypeContradiction errorLevel="suppress" />
<RedundantConditionGivenDocblockType errorLevel="suppress" />
<UnresolvableInclude errorLevel="info" />
<RawObjectIteration errorLevel="info" />
<!-- psalm seems to wrongly complain about this, set the errorLevel to info for now -->
<UndefinedConstant errorLevel="info" />
</issueHandlers>
</psalm>

View File

@ -1,563 +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";
/** Constant for the type value in registration clientData */
const REQUEST_TYPE_REGISTER = "navigator.id.finishEnrollment";
/** Constant for the type value in authentication clientData */
const REQUEST_TYPE_AUTHENTICATE = "navigator.id.getAssertion";
/** 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;
/** Error for the origin not matching the appId */
const ERR_NO_MATCHING_ORIGIN = 12;
/** Error for the type in clientData being invalid */
const ERR_BAD_TYPE = 13;
/** Error for bad user presence byte value */
const ERR_BAD_USER_PRESENCE = 14;
/** @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 );
}
if(isset($cli->typ) && $cli->typ !== REQUEST_TYPE_REGISTER) {
throw new Error('ClientData type is invalid', ERR_BAD_TYPE);
}
if(isset($cli->origin) && $cli->origin !== $request->appId) {
throw new Error('App ID does not match the origin', ERR_NO_MATCHING_ORIGIN);
}
$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 = pack('C', 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.');
}
/** @var Registration $reg */
$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);
if(isset($decodedClient->typ) && $decodedClient->typ !== REQUEST_TYPE_AUTHENTICATE) {
throw new Error('ClientData type is invalid', ERR_BAD_TYPE);
}
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 );
}
if(isset($decodedClient->origin) && $decodedClient->origin !== $req->appId) {
throw new Error('App ID does not match the origin', ERR_NO_MATCHING_ORIGIN);
}
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) {
$upb = unpack("Cupb", substr($signData, 0, 1));
if($upb['upb'] !== 1) {
throw new Error('User presence byte value is invalid', ERR_BAD_USER_PRESENCE );
}
$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 self::castObjectToRegistration($reg);
} else {
throw new Error('Counter too low.', ERR_COUNTER_TOO_LOW );
}
} else {
throw new Error('Authentication failed', ERR_AUTHENTICATION_FAILURE );
}
}
/**
* @param object $object
* @return Registration
*/
protected static function castObjectToRegistration($object)
{
$reg = new Registration();
if (property_exists($object, 'publicKey')) {
$reg->publicKey = $object->publicKey;
}
if (property_exists($object, 'certificate')) {
$reg->certificate = $object->certificate;
}
if (property_exists($object, 'counter')) {
$reg->counter = $object->counter;
}
if (property_exists($object, 'keyHandle')) {
$reg->keyHandle = $object->keyHandle;
}
return $reg;
}
/**
* @return array
*/
private function get_certs()
{
$files = array();
$dir = $this->attestDir;
if($dir !== null && is_dir($dir) && $handle = opendir($dir)) {
while(false !== ($entry = readdir($handle))) {
if(is_file("$dir/$entry")) {
$files[] = "$dir/$entry";
}
}
closedir($handle);
} elseif (is_file("$dir")) {
$files[] = "$dir";
}
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 = random_bytes(32);
$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, true)) {
$cert[strlen($cert) - 257] = "\0";
}
return $cert;
}
}
/**
* Class for building a registration request
*
* @package u2flib_server
*/
class RegisterRequest
{
/** @var string Protocol version */
public $version = U2F_VERSION;
/** @var string Registration challenge */
public $challenge;
/** @var string 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
{
/** @var string Protocol version */
public $version = U2F_VERSION;
/** @var string Authentication challenge */
public $challenge = '';
/** @var string Key handle of a registered authenticator */
public $keyHandle = '';
/** @var string Application id */
public $appId = '';
}
/**
* Class returned for successful registrations
*
* @package u2flib_server
*/
class Registration
{
/** @var string The key handle of the registered authenticator */
public $keyHandle = '';
/** @var string The public key of the registered authenticator */
public $publicKey = '';
/** @var string The attestation certificate of the registered authenticator */
public $certificate = '';
/** @var int 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);
}
}