aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Authentication
diff options
context:
space:
mode:
authorS1m <git@sgougeon.fr>2024-03-24 12:17:10 +0100
committerJoas Schilling <coding@schilljs.com>2024-08-15 11:03:10 +0200
commit9189bc290bd46e103b859c8366b22562d405fb3b (patch)
treea5b36c34818b44c91d06c9714bfe61f4286e7e83 /lib/private/Authentication
parente218d1f98e19307f2bf170e09670e4f7fe61f061 (diff)
downloadnextcloud-server-9189bc290bd46e103b859c8366b22562d405fb3b.tar.gz
nextcloud-server-9189bc290bd46e103b859c8366b22562d405fb3b.zip
feat(webauthn): Add user verification to webauthn challenges
Require user verification if all tokens are registered with UV flag, else discourage it Signed-off-by: S1m <git@sgougeon.fr> Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
Diffstat (limited to 'lib/private/Authentication')
-rw-r--r--lib/private/Authentication/WebAuthn/CredentialRepository.php9
-rw-r--r--lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php11
-rw-r--r--lib/private/Authentication/WebAuthn/Manager.php15
3 files changed, 27 insertions, 8 deletions
diff --git a/lib/private/Authentication/WebAuthn/CredentialRepository.php b/lib/private/Authentication/WebAuthn/CredentialRepository.php
index f32136f9594..203f2ef9020 100644
--- a/lib/private/Authentication/WebAuthn/CredentialRepository.php
+++ b/lib/private/Authentication/WebAuthn/CredentialRepository.php
@@ -44,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 {
@@ -58,13 +58,18 @@ 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);
diff --git a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php
index 443a7985cae..6c4bc3ca81b 100644
--- a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php
+++ b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php
@@ -23,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 */
@@ -37,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/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php
index 007be245992..7aa7a3c8f3a 100644
--- a/lib/private/Authentication/WebAuthn/Manager.php
+++ b/lib/private/Authentication/WebAuthn/Manager.php
@@ -88,8 +88,8 @@ class Manager {
];
$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
- null,
- AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
+ AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
+ AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED,
null,
false,
);
@@ -151,7 +151,8 @@ class Manager {
}
// 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 {
@@ -160,7 +161,11 @@ 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->type,
@@ -173,7 +178,7 @@ class Manager {
random_bytes(32), // Challenge
$this->stripPort($serverHost), // Relying Party ID
$registeredPublicKeyCredentialDescriptors, // Registered PublicKeyCredentialDescriptor classes
- AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
+ $userVerificationRequirement,
60000, // Timeout
);
}