diff options
Diffstat (limited to 'lib/private/Authentication/WebAuthn')
4 files changed, 59 insertions, 120 deletions
diff --git a/lib/private/Authentication/WebAuthn/CredentialRepository.php b/lib/private/Authentication/WebAuthn/CredentialRepository.php index e5c3fcf1618..203f2ef9020 100644 --- a/lib/private/Authentication/WebAuthn/CredentialRepository.php +++ b/lib/private/Authentication/WebAuthn/CredentialRepository.php @@ -3,25 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Authentication\WebAuthn; @@ -61,7 +44,7 @@ class CredentialRepository implements PublicKeyCredentialSourceRepository { }, $entities); } - public function saveAndReturnCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, string $name = null): PublicKeyCredentialEntity { + public function saveAndReturnCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, ?string $name = null, bool $userVerification = false): PublicKeyCredentialEntity { $oldEntity = null; try { @@ -75,19 +58,24 @@ class CredentialRepository implements PublicKeyCredentialSourceRepository { $name = 'default'; } - $entity = PublicKeyCredentialEntity::fromPublicKeyCrendentialSource($name, $publicKeyCredentialSource); + $entity = PublicKeyCredentialEntity::fromPublicKeyCrendentialSource($name, $publicKeyCredentialSource, $userVerification); if ($oldEntity) { $entity->setId($oldEntity->getId()); if ($defaultName) { $entity->setName($oldEntity->getName()); } + + // Don't downgrade UV just because it was skipped during a login due to another key + if ($oldEntity->getUserVerification()) { + $entity->setUserVerification(true); + } } return $this->credentialMapper->insertOrUpdate($entity); } - public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, string $name = null): void { + public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, ?string $name = null): void { $this->saveAndReturnCredentialSource($publicKeyCredentialSource, $name); } } diff --git a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php index 6f97ded483d..6c4bc3ca81b 100644 --- a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php +++ b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Authentication\WebAuthn\Db; @@ -41,6 +23,10 @@ use Webauthn\PublicKeyCredentialSource; * @method void setPublicKeyCredentialId(string $id); * @method string getData(); * @method void setData(string $data); + * + * @since 30.0.0 Add userVerification attribute + * @method bool|null getUserVerification(); + * @method void setUserVerification(bool $userVerification); */ class PublicKeyCredentialEntity extends Entity implements JsonSerializable { /** @var string */ @@ -55,20 +41,25 @@ class PublicKeyCredentialEntity extends Entity implements JsonSerializable { /** @var string */ protected $data; + /** @var bool|null */ + protected $userVerification; + public function __construct() { $this->addType('name', 'string'); $this->addType('uid', 'string'); $this->addType('publicKeyCredentialId', 'string'); $this->addType('data', 'string'); + $this->addType('userVerification', 'boolean'); } - public static function fromPublicKeyCrendentialSource(string $name, PublicKeyCredentialSource $publicKeyCredentialSource): PublicKeyCredentialEntity { + public static function fromPublicKeyCrendentialSource(string $name, PublicKeyCredentialSource $publicKeyCredentialSource, bool $userVerification): PublicKeyCredentialEntity { $publicKeyCredentialEntity = new self(); $publicKeyCredentialEntity->setName($name); $publicKeyCredentialEntity->setUid($publicKeyCredentialSource->getUserHandle()); $publicKeyCredentialEntity->setPublicKeyCredentialId(base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId())); $publicKeyCredentialEntity->setData(json_encode($publicKeyCredentialSource)); + $publicKeyCredentialEntity->setUserVerification($userVerification); return $publicKeyCredentialEntity; } diff --git a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php index 72c557eb53a..fa7304157c8 100644 --- a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php +++ b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialMapper.php @@ -3,26 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Authentication\WebAuthn\Db; diff --git a/lib/private/Authentication/WebAuthn/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php index 744a3fa354a..96dc0719b54 100644 --- a/lib/private/Authentication/WebAuthn/Manager.php +++ b/lib/private/Authentication/WebAuthn/Manager.php @@ -3,27 +3,8 @@ declare(strict_types=1); /** - * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> - * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Joas Schilling <coding@schilljs.com> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OC\Authentication\WebAuthn; @@ -72,7 +53,7 @@ class Manager { CredentialRepository $repository, PublicKeyCredentialMapper $credentialMapper, LoggerInterface $logger, - IConfig $config + IConfig $config, ) { $this->repository = $repository; $this->credentialMapper = $credentialMapper; @@ -83,15 +64,15 @@ class Manager { public function startRegistration(IUser $user, string $serverHost): PublicKeyCredentialCreationOptions { $rpEntity = new PublicKeyCredentialRpEntity( 'Nextcloud', //Name - $this->stripPort($serverHost), //ID + $this->stripPort($serverHost), //ID null //Icon ); $userEntity = new PublicKeyCredentialUserEntity( - $user->getUID(), //Name - $user->getUID(), //ID - $user->getDisplayName() //Display name -// 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon + $user->getUID(), // Name + $user->getUID(), // ID + $user->getDisplayName() // Display name + // 'https://foo.example.co/avatar/123e4567-e89b-12d3-a456-426655440000' //Icon ); $challenge = random_bytes(32); @@ -107,9 +88,10 @@ class Manager { ]; $authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE, + AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED, null, false, - AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED ); return new PublicKeyCredentialCreationOptions( @@ -117,11 +99,10 @@ class Manager { $userEntity, $challenge, $publicKeyCredentialParametersList, - $timeout, - $excludedPublicKeyDescriptors, $authenticatorSelectionCriteria, PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE, - null + $excludedPublicKeyDescriptors, + $timeout, ); } @@ -149,7 +130,7 @@ class Manager { try { // Load the data $publicKeyCredential = $publicKeyCredentialLoader->load($data); - $response = $publicKeyCredential->getResponse(); + $response = $publicKeyCredential->response; // Check if the response is an Authenticator Attestation Response if (!$response instanceof AuthenticatorAttestationResponse) { @@ -162,13 +143,16 @@ class Manager { $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check( $response, $publicKeyCredentialCreationOptions, - $request); + $request, + ['localhost'], + ); } catch (\Throwable $exception) { throw $exception; } // Persist the data - return $this->repository->saveAndReturnCredentialSource($publicKeyCredentialSource, $name); + $userVerification = $response->attestationObject->authData->isUserVerified(); + return $this->repository->saveAndReturnCredentialSource($publicKeyCredentialSource, $name, $userVerification); } private function stripPort(string $serverHost): string { @@ -177,21 +161,25 @@ class Manager { public function startAuthentication(string $uid, string $serverHost): PublicKeyCredentialRequestOptions { // List of registered PublicKeyCredentialDescriptor classes associated to the user - $registeredPublicKeyCredentialDescriptors = array_map(function (PublicKeyCredentialEntity $entity) { + $userVerificationRequirement = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED; + $registeredPublicKeyCredentialDescriptors = array_map(function (PublicKeyCredentialEntity $entity) use (&$userVerificationRequirement) { + if ($entity->getUserVerification() !== true) { + $userVerificationRequirement = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED; + } $credential = $entity->toPublicKeyCredentialSource(); return new PublicKeyCredentialDescriptor( - $credential->getType(), - $credential->getPublicKeyCredentialId() + $credential->type, + $credential->publicKeyCredentialId, ); }, $this->credentialMapper->findAllForUid($uid)); // Public Key Credential Request Options return new PublicKeyCredentialRequestOptions( - random_bytes(32), // Challenge - 60000, // Timeout - $this->stripPort($serverHost), // Relying Party ID - $registeredPublicKeyCredentialDescriptors, // Registered PublicKeyCredentialDescriptor classes - AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED + random_bytes(32), // Challenge + $this->stripPort($serverHost), // Relying Party ID + $registeredPublicKeyCredentialDescriptors, // Registered PublicKeyCredentialDescriptor classes + $userVerificationRequirement, + 60000, // Timeout ); } @@ -213,16 +201,15 @@ class Manager { $tokenBindingHandler, $extensionOutputCheckerHandler, $algorithmManager, - null, - $this->logger, ); + $authenticatorAssertionResponseValidator->setLogger($this->logger); try { $this->logger->debug('Loading publickey credentials from: ' . $data); // Load the data $publicKeyCredential = $publicKeyCredentialLoader->load($data); - $response = $publicKeyCredential->getResponse(); + $response = $publicKeyCredential->response; // Check if the response is an Authenticator Attestation Response if (!$response instanceof AuthenticatorAssertionResponse) { @@ -233,18 +220,17 @@ class Manager { $request = ServerRequest::fromGlobals(); $publicKeyCredentialSource = $authenticatorAssertionResponseValidator->check( - $publicKeyCredential->getRawId(), + $publicKeyCredential->rawId, $response, $publicKeyCredentialRequestOptions, $request, - $uid + $uid, + ['localhost'], ); } catch (\Throwable $e) { throw $e; } - - return true; } @@ -260,14 +246,6 @@ class Manager { } public function isWebAuthnAvailable(): bool { - if (!extension_loaded('bcmath')) { - return false; - } - - if (!extension_loaded('gmp')) { - return false; - } - if (!$this->config->getSystemValueBool('auth.webauthn.enabled', true)) { return false; } |