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);
+    }
+}