LDAPAccountManager/lam/lib/webauthn.inc

303 lines
10 KiB
PHP

<?php
namespace LAM\LOGIN\WEBAUTHN;
use CBOR\Decoder;
use CBOR\OtherObject\OtherObjectManager;
use CBOR\Tag\TagObjectManager;
use Cose\Algorithm\Manager;
use Cose\Algorithm\Signature\ECDSA\ES256;
use Cose\Algorithm\Signature\ECDSA\ES384;
use Cose\Algorithm\Signature\ECDSA\ES512;
use Cose\Algorithm\Signature\EdDSA\EdDSA;
use Cose\Algorithm\Signature\RSA\RS1;
use Cose\Algorithm\Signature\RSA\RS256;
use Cose\Algorithm\Signature\RSA\RS384;
use Cose\Algorithm\Signature\RSA\RS512;
use \Cose\Algorithms;
use Nyholm\Psr7\Factory\Psr17Factory;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Symfony\Component\HttpFoundation\Request;
use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport;
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
use Webauthn\AttestationStatement\PackedAttestationStatementSupport;
use Webauthn\AttestationStatement\TPMAttestationStatementSupport;
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
use Webauthn\AuthenticatorAttestationResponse;
use Webauthn\AuthenticatorAttestationResponseValidator;
use \Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialLoader;
use \Webauthn\PublicKeyCredentialRpEntity;
use \Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialSourceRepository;
use \Webauthn\PublicKeyCredentialUserEntity;
use \Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs;
use \Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
Copyright (C) 2019 Roland Gruber
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* Manages webauthn requests.
*
* @author Roland Gruber
*/
include_once __DIR__ . '/3rdParty/composer/autoload.php';
/**
* Returns if the given DN is registered for webauthn.
*
* @param string $dn DN
* @return boolean is registered
*/
function isRegistered($dn) {
return false;
}
/**
* Returns a challenge for a new token.
*
* @param string $dn DN
* @param bool $isSelfService is executed in self service
* @return PublicKeyCredentialCreationOptions registration object
*/
function getRegistrationObject($dn, $isSelfService) {
$rpEntity = createRpEntry($isSelfService);
$userEntity = getUserEntity($dn);
$challenge = generateRandomPassword(32);
$credentialParameters = getCredentialParameters();
$timeout = 20000;
$registrationObject = new PublicKeyCredentialCreationOptions(
$rpEntity,
$userEntity,
$challenge,
$credentialParameters,
$timeout,
array(),
new AuthenticatorSelectionCriteria(),
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
new AuthenticationExtensionsClientInputs());
logNewMessage(LOG_DEBUG, json_encode($registrationObject));
return $registrationObject;
}
/**
* Returns the part that identifies the server and application.
*
* @param bool $isSelfService is executed in self service
* @return PublicKeyCredentialRpEntity relying party entry
*/
function createRpEntry($isSelfService) {
$pathPrefix = $isSelfService ? '../' : '';
$icon = $pathPrefix . '../graphics/logo136.png';
if (!$isSelfService) {
$domain = $_SESSION['config']->getTwoFactorAuthenticationDomain();
}
return new PublicKeyCredentialRpEntity(
'LDAP Account Manager', //Name
$domain,
$icon
);
}
/**
* Returns the user entity for the registration.
*
* @param $dn DN
* @return PublicKeyCredentialUserEntity user entity
*/
function getUserEntity($dn) {
return new PublicKeyCredentialUserEntity(
$dn,
$dn,
extractRDNValue($dn),
null
);
}
/**
* Returns the supported credential algorithms.
*
* @return array algorithms
*/
function getCredentialParameters() {
return array(
new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_ES256),
new PublicKeyCredentialParameters('public-key', Algorithms::COSE_ALGORITHM_RS256),
);
}
/**
* Verifies the registration and stores it in the database.
*
* @param PublicKeyCredentialCreationOptions $registration registration object
* @param string $clientResponse client response
* @return bool true if response is valid and registration succeeded
*/
function storeNewRegistration($registration, $clientResponse) {
$decoder = getCborDecoder();
$tokenBindingHandler = new IgnoreTokenBindingHandler();
$attestationSupportManager = getAttestationSupportManager($decoder);
$attestationObjectLoader = getAttestationObjectLoader($attestationSupportManager, $decoder);
$publicKeyCredentialLoader = getPublicKeyCredentialLoader($attestationObjectLoader, $decoder);
$extensionOutputCheckerHandler = getExtensionOutputChecker();
$repository = new PublicKeyCredentialSourceRepositorySQLite();
$responseValidator = new AuthenticatorAttestationResponseValidator(
$attestationSupportManager, $repository, $tokenBindingHandler, $extensionOutputCheckerHandler);
try {
$publicKeyCredential = $publicKeyCredentialLoader->load($clientResponse);
$authenticatorAttestationResponse = $publicKeyCredential->getResponse();
if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) {
logNewMessage(LOG_ERR, 'Invalid webauthn response: ' . $clientResponse);
return false;
}
$symfonyRequest = Request::createFromGlobals();
$psr17Factory = new Psr17Factory();
$psrFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
$psr7Request = $psrFactory->createRequest($symfonyRequest);
$publicKeyCredentialSource = $responseValidator->check($authenticatorAttestationResponse, $registration, $psr7Request);
$repository->saveCredentialSource($publicKeyCredentialSource);
return true;
}
catch (\Throwable $exception) {
logNewMessage(LOG_ERR, 'Webauthn validation failed: ' . $exception->getMessage() . $exception->getTraceAsString());
}
return false;
}
/**
* Returns a CBOR decoder.
*
* @return Decoder decoder
*/
function getCborDecoder() {
return new Decoder(new TagObjectManager(), new OtherObjectManager());
}
/**
* Creates the attestation support manager.
*
* @param Decoder $decoder decoder
* @return AttestationStatementSupportManager manager
*/
function getAttestationSupportManager($decoder) {
$manager = new AttestationStatementSupportManager();
$manager->add(new NoneAttestationStatementSupport());
$manager->add(new FidoU2FAttestationStatementSupport());
$manager->add(new AndroidKeyAttestationStatementSupport($decoder));
$manager->add(new TPMAttestationStatementSupport());
$coseManager = new Manager();
$coseManager->add(new ES256());
$coseManager->add(new ES384());
$coseManager->add(new ES512());
$coseManager->add(new EdDSA());
$coseManager->add(new RS1());
$coseManager->add(new RS256());
$coseManager->add(new RS384);
$coseManager->add(new RS512());
$manager->add(new PackedAttestationStatementSupport($decoder, $coseManager));
return $manager;
}
/**
* Returns the attestation object loader.
*
* @param AttestationStatementSupportManager $manager support manager
* @param Decoder $decoder decoder
* @return AttestationObjectLoader attestation object loader
*/
function getAttestationObjectLoader($manager, $decoder) {
return new AttestationObjectLoader($manager, $decoder);
}
/**
* Creates the public key credential loader.
*
* @param AttestationObjectLoader $attestationObjectLoader attestation object loader
* @param Decoder $decoder decoder
* @return PublicKeyCredentialLoader public key credential loader
*/
function getPublicKeyCredentialLoader($attestationObjectLoader, $decoder) {
return new PublicKeyCredentialLoader($attestationObjectLoader, $decoder);
}
/**
* Returns the extension output checker handler.
* No extensions are checked at this time.
*
* @return ExtensionOutputCheckerHandler handler
*/
function getExtensionOutputChecker() {
return new ExtensionOutputCheckerHandler();
}
/**
* Stores the public key credentials in the SQLite database.
*
* @package LAM\LOGIN\WEBAUTHN
*/
class PublicKeyCredentialSourceRepositorySQLite implements PublicKeyCredentialSourceRepository {
/**
* Finds the public key for the given credential id.
*
* @param string $publicKeyCredentialId credential id
* @return PublicKeyCredentialSource|null credential source
*/
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource {
// TODO: Implement findOneByCredentialId() method.
logNewMessage(LOG_WARNING, 'FIND ONE: ' . $publicKeyCredentialId);
return null;
}
/**
* Finds all credential entries for the given user.
*
* @param PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity credential user entity
* @return PublicKeyCredentialSource[] credential sources
*/
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array {
// TODO: Implement findAllForUserEntity() method.
logNewMessage(LOG_WARNING, 'FIND ALL: ' . json_encode($publicKeyCredentialUserEntity));
return array();
}
/**
* Saves the given credential in the database.
*
* @param PublicKeyCredentialSource $publicKeyCredentialSource credential
*/
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void {
// TODO: Implement saveCredentialSource() method.
logNewMessage(LOG_WARNING, 'SAVE: ' . json_encode($publicKeyCredentialSource));
}
}