git subrepo clone https://github.com/mailcow/mailcow-dockerized.git mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "a832becb"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "a832becb"
git-subrepo: version: "0.4.3"
origin: "???"
commit: "???"
Change-Id: If5be2d621a211e164c9b6577adaa7884449f16b5
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/.gitignore b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/.gitignore
new file mode 100644
index 0000000..0d968f1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/.gitignore
@@ -0,0 +1,7 @@
+composer.lock
+vendor/
+.*.swp
+php-u2flib-server-*.tar.gz
+php-u2flib-server-*.tar.gz.sig
+apidocs/
+build/
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml
new file mode 100644
index 0000000..beade3b
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/.travis.yml
@@ -0,0 +1,23 @@
+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)
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/BLURB b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/BLURB
new file mode 100644
index 0000000..c579742
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/BLURB
@@ -0,0 +1,9 @@
+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
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/COPYING b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/COPYING
new file mode 100644
index 0000000..427c917
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/COPYING
@@ -0,0 +1,26 @@
+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.
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS
new file mode 100644
index 0000000..496175e
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/NEWS
@@ -0,0 +1,34 @@
+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.
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/README b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/README
new file mode 100644
index 0000000..0116a27
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/README
@@ -0,0 +1,34 @@
+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
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/README.adoc b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/README.adoc
new file mode 120000
index 0000000..100b938
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/README.adoc
@@ -0,0 +1 @@
+README
\ No newline at end of file
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/apigen.neon b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/apigen.neon
new file mode 100644
index 0000000..bbb7071
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/apigen.neon
@@ -0,0 +1,13 @@
+destination: apidocs
+
+source:
+ - src/u2flib_server
+
+exclude:
+ - "*/tests/*"
+
+groups: none
+
+tree: false
+
+title: php-u2flib-server API
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json
new file mode 100644
index 0000000..3f2d9ea
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/composer.json
@@ -0,0 +1,18 @@
+{
+ "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"
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/do-source-release.sh b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/do-source-release.sh
new file mode 100755
index 0000000..7e50173
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/do-source-release.sh
@@ -0,0 +1,40 @@
+#!/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
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/assets/u2f-api.js b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/assets/u2f-api.js
new file mode 100644
index 0000000..0f06f50
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/assets/u2f-api.js
@@ -0,0 +1,651 @@
+// 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);
+ });
+};
\ No newline at end of file
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/cli/u2f-server.phps b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/cli/u2f-server.phps
new file mode 100755
index 0000000..8acb66a
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/cli/u2f-server.phps
@@ -0,0 +1,83 @@
+#!/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";
+
+?>
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/localstorage/index.phps b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/localstorage/index.phps
new file mode 100644
index 0000000..d840dd3
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/localstorage/index.phps
@@ -0,0 +1,186 @@
+<?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>
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/pdo/index.phps b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/pdo/index.phps
new file mode 100644
index 0000000..c04d63e
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/examples/pdo/index.phps
@@ -0,0 +1,204 @@
+<?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>
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/phpunit.xml b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/phpunit.xml
new file mode 100644
index 0000000..fa6f08e
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/phpunit.xml
@@ -0,0 +1,9 @@
+<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>
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/psalm.xml b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/psalm.xml
new file mode 100644
index 0000000..6b6234c
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/psalm.xml
@@ -0,0 +1,48 @@
+<?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>
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php
new file mode 100644
index 0000000..8583fff
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/yubico/u2flib-server/src/u2flib_server/U2F.php
@@ -0,0 +1,563 @@
+<?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);
+ }
+}