├── .eslintignore ├── phpstan-dev.neon ├── resources └── icons.odp ├── phpstan.neon ├── public └── assets │ ├── base │ └── icons │ │ ├── 2nd.png │ │ ├── face.png │ │ ├── nothing.png │ │ ├── pwless.png │ │ └── fingerprint.png │ ├── js │ ├── authentication.js │ └── webauthn.js │ └── css │ └── webauthn.css ├── bin └── updateMetadata.php ├── src ├── Auth │ ├── Source │ │ ├── Supercharged.php │ │ └── Passwordless.php │ └── Process │ │ └── WebAuthn.php ├── WebAuthn │ ├── StateData.php │ ├── AAGUID.php │ ├── StaticProcessHelper.php │ ├── WebAuthnAuthenticationEvent.php │ ├── Store │ │ └── Database.php │ └── WebAuthnAbstractEvent.php ├── Controller │ ├── Supercharged.php │ ├── PushbackUserPass.php │ ├── ManageToken.php │ ├── Registration.php │ ├── RegProcess.php │ ├── WebAuthn.php │ └── AuthProcess.php └── Store.php ├── docs └── tested_tokens.md ├── routing └── routes │ └── routes.yaml ├── composer.json ├── templates ├── authentication.twig ├── webauthn.twig └── supercharged.twig ├── UPGRADE.md ├── locales ├── en │ └── LC_MESSAGES │ │ └── webauthn.po ├── fr │ └── LC_MESSAGES │ │ └── webauthn.po ├── de │ └── LC_MESSAGES │ │ └── webauthn.po └── lb │ └── LC_MESSAGES │ └── webauthn.po ├── config └── module_webauthn.php.dist ├── README.md └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | config/webauthn-aaguid.json 2 | -------------------------------------------------------------------------------- /phpstan-dev.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 5 3 | paths: 4 | - tests 5 | -------------------------------------------------------------------------------- /resources/icons.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesamlphp/simplesamlphp-module-webauthn/HEAD/resources/icons.odp -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 3 3 | paths: 4 | - src 5 | bootstrapFiles: 6 | - tests/bootstrap.php 7 | -------------------------------------------------------------------------------- /public/assets/base/icons/2nd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesamlphp/simplesamlphp-module-webauthn/HEAD/public/assets/base/icons/2nd.png -------------------------------------------------------------------------------- /public/assets/base/icons/face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesamlphp/simplesamlphp-module-webauthn/HEAD/public/assets/base/icons/face.png -------------------------------------------------------------------------------- /public/assets/base/icons/nothing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesamlphp/simplesamlphp-module-webauthn/HEAD/public/assets/base/icons/nothing.png -------------------------------------------------------------------------------- /public/assets/base/icons/pwless.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesamlphp/simplesamlphp-module-webauthn/HEAD/public/assets/base/icons/pwless.png -------------------------------------------------------------------------------- /public/assets/base/icons/fingerprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesamlphp/simplesamlphp-module-webauthn/HEAD/public/assets/base/icons/fingerprint.png -------------------------------------------------------------------------------- /public/assets/js/authentication.js: -------------------------------------------------------------------------------- 1 | function authenticate() { 2 | setTimeout(function() {document.getElementById("authformSubmit").click();}, 500) 3 | } 4 | 5 | window.addEventListener('DOMContentLoaded', authenticate); 6 | -------------------------------------------------------------------------------- /bin/updateMetadata.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | pushbackAuthsource = $this->authSourceConfig->getString("password_authsource"); 28 | } 29 | 30 | 31 | public function authenticate(array &$state): void 32 | { 33 | $state['saml:AuthnContextClassRef'] = $this->authnContextClassRef; 34 | $state['pushbackAuthsource'] = $this->pushbackAuthsource; 35 | 36 | StaticProcessHelper::prepareStatePasswordlessAuth($this->stateData, $state); 37 | StaticProcessHelper::saveStateAndRedirectSupercharged($state); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/tested_tokens.md: -------------------------------------------------------------------------------- 1 | # The following tokens were tested against the implementation and found to work 2 | 3 | ## Attestation modes (None, Indirect) 4 | 5 | - [Yubikey 5 NFC](https://www.yubico.com/product/yubikey-5-nfc/) 6 | - [Yubikey 5C](https://www.yubico.com/product/yubikey-5c/) 7 | - [Security Key by Yubico](https://support.yubico.com/support/solutions/articles/15000006900-security-key-by-yubico) 8 | - [Security Key NFC by Yubico](https://support.yubico.com/support/solutions/articles/15000019469-security-key-nfc) 9 | - [Feitian BioPass FIDO2](https://www.ftsafe.com/Products/FIDO/Bio) 10 | - [Solo - FIDO2 security key (USB only)](https://solokeys.com/collections/all/products/solo) 11 | - [Solo Tap - FIDO2 security key (USB + NFC)](https://solokeys.com/collections/all/products/solo-tap) 12 | - TouchID (tested on Macbook Pro 2019, macOS 10.15.3, Google Chrome) - AAGUID 'adce000235bcc60a648b0b25f1f05503' not in database yet 13 | - FaceID (tested on an iPhone with iOS 14) 14 | - Android 13 (tested on a Murena Fairphone 5, "Samsung Internet" browser) 15 | 16 | ## Attestation mode None 17 | 18 | - Android 7 (Samsung Galaxy S6, Fingerprint Sensor) - when requesting 19 | indirect/direct, none is delivered instead 20 | - Windows Hello - when requesting indirect/direct, throws Exception because of unimplemented TPM attestation mode 21 | -------------------------------------------------------------------------------- /routing/routes/routes.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | webauthn-registration: 4 | path: /registration 5 | defaults: { 6 | _controller: 'SimpleSAML\Module\webauthn\Controller\Registration::main' 7 | } 8 | methods: [GET, POST] 9 | 10 | webauthn-authprocess: 11 | path: /authprocess 12 | defaults: { 13 | _controller: 'SimpleSAML\Module\webauthn\Controller\AuthProcess::main' 14 | } 15 | methods: [GET, POST] 16 | 17 | webauthn-webauthn: 18 | path: /webauthn 19 | defaults: { 20 | _controller: 'SimpleSAML\Module\webauthn\Controller\WebAuthn::main' 21 | } 22 | methods: [GET, POST] 23 | 24 | webauthn-supercharged: 25 | path: /supercharged 26 | defaults: { 27 | _controller: 'SimpleSAML\Module\webauthn\Controller\Supercharged::main' 28 | } 29 | methods: [GET, POST] 30 | 31 | webauthn-pushbackuserpass: 32 | path: /pushbackuserpass 33 | defaults: { 34 | _controller: 'SimpleSAML\Module\webauthn\Controller\PushbackUserPass::main' 35 | } 36 | methods: [GET, POST] 37 | 38 | webauthn-regprocess: 39 | path: /regprocess 40 | defaults: { 41 | _controller: 'SimpleSAML\Module\webauthn\Controller\RegProcess::main' 42 | } 43 | methods: [GET, POST] 44 | 45 | webauthn-managetoken: 46 | path: /managetoken 47 | defaults: { 48 | _controller: 'SimpleSAML\Module\webauthn\Controller\ManageToken::main' 49 | } 50 | methods: [GET, POST] 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplesamlphp/simplesamlphp-module-webauthn", 3 | "description": "A PHP implementation of a FIDO2 / WebAuthn authentication agent", 4 | "type": "simplesamlphp-module", 5 | "keywords": [ "idp", "fido2", "webauthn", "2fa" ], 6 | "license": "LGPL-2.1-or-later", 7 | "authors": [ 8 | { 9 | "name": "Stefan Winter", 10 | "email": "stefan.winter@restena.lu" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-4": { 15 | "SimpleSAML\\Module\\webauthn\\": "src/" 16 | } 17 | }, 18 | "require": { 19 | "php": "^8.3", 20 | "ext-date": "*", 21 | "ext-hash": "*", 22 | "ext-openssl": "*", 23 | "ext-pdo": "*", 24 | 25 | "simplesamlphp/assert": "~1.9", 26 | "simplesamlphp/simplesamlphp": "~2.5@RC", 27 | "spomky-labs/cbor-php": "~3.2", 28 | "spomky-labs/pki-framework": "~1.4", 29 | "symfony/http-foundation": "~7.4", 30 | "web-auth/cose-lib": "~4.4" 31 | }, 32 | "require-dev": { 33 | "simplesamlphp/simplesamlphp-test-framework": "~1.11" 34 | }, 35 | "config": { 36 | "allow-plugins": { 37 | "composer/package-versions-deprecated": true, 38 | "simplesamlphp/composer-module-installer": true, 39 | "dealerdirect/phpcodesniffer-composer-installer": true, 40 | "phpstan/extension-installer": true, 41 | "simplesamlphp/composer-xmlprovider-installer": true 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/WebAuthn/StateData.php: -------------------------------------------------------------------------------- 1 | standalone registration page, if true => inflow registration. 57 | * Defaults to true. 58 | */ 59 | public bool $useInflowRegistration; 60 | } 61 | -------------------------------------------------------------------------------- /templates/authentication.twig: -------------------------------------------------------------------------------- 1 | {% set pagetitle = '{webauthn:webauthn:page_title}'|trans %} 2 | {% extends "base.twig" %} 3 | 4 | {% block preload %} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
Please first register your token on the token management page before continuing. 26 | {% endif %} 27 | 28 | 29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /public/assets/css/webauthn.css: -------------------------------------------------------------------------------- 1 | div.bounding-tokens { 2 | border: 1px; 3 | border-style: dotted; 4 | margin-bottom: 20px; 5 | padding: 10px; 6 | } 7 | 8 | div.space { 9 | margin: 10px; 10 | } 11 | 12 | li.currenttoken { 13 | font-weight: bold; 14 | color: blue; 15 | } 16 | 17 | li.othertoken { 18 | display: flex; 19 | } 20 | 21 | img.factorlogo { 22 | height: 2em; 23 | padding: 2px; 24 | } 25 | 26 | span.tokencaption { 27 | font-weight: bold; 28 | } 29 | 30 | #containerbase { 31 | display: grid; 32 | grid-template-columns: repeat(3, 45% 10% 45%); 33 | gap: 20px; 34 | } 35 | 36 | #container-1-1 { 37 | grid-column: 1; 38 | grid-row: 1; 39 | } 40 | 41 | #container-1-2 { 42 | grid-column: 1; 43 | grid-row: 2; 44 | } 45 | 46 | #container-1-3 { 47 | grid-column: 1; 48 | grid-row: 3; 49 | align-self: end; 50 | } 51 | 52 | #container-1-4 { 53 | grid-column: 1; 54 | grid-row: 4; 55 | align-self: end; 56 | } 57 | 58 | #container-2 { 59 | grid-column: 2; 60 | grid-row: 1 / 4; 61 | align-self: center; 62 | min-width: 20px; 63 | min-height: 100px; 64 | } 65 | 66 | #container-3-1 { 67 | grid-column: 3; 68 | grid-row: 1; 69 | } 70 | 71 | #container-3-2 { 72 | grid-column: 3; 73 | grid-row: 2; 74 | } 75 | 76 | #container-3-3 { 77 | grid-column: 3; 78 | grid-row: 3; 79 | } 80 | 81 | #container-3-4 { 82 | grid-column: 3; 83 | grid-row: 4; 84 | } 85 | 86 | #label-passwordless { 87 | display: flex; 88 | } 89 | 90 | #span-passwordless { 91 | font-size: small; 92 | display: contents; 93 | } 94 | 95 | #fingerprint { 96 | padding-bottom: 30px; 97 | padding-right: 50px; 98 | } 99 | 100 | #face { 101 | padding-bottom: 30px; 102 | } 103 | 104 | #submit-button { 105 | margin-top: 80px; 106 | } 107 | 108 | #passwordblock { 109 | display: block; 110 | } 111 | -------------------------------------------------------------------------------- /src/Auth/Source/Passwordless.php: -------------------------------------------------------------------------------- 1 | authSourceConfig = Configuration::loadFromArray( 45 | $config, 46 | 'authsources[' . var_export($this->authId, true) . ']', 47 | ); 48 | $this->authnContextClassRef = $this->authSourceConfig->getOptionalString( 49 | "authncontextclassref", 50 | 'urn:rsa:names:tc:SAML:2.0:ac:classes:FIDO', 51 | ); 52 | $moduleConfig = Configuration::getOptionalConfig('module_webauthn.php')->toArray(); 53 | 54 | $initialStateData = new StateData(); 55 | WebAuthn::loadModuleConfig($moduleConfig, $initialStateData); 56 | $this->stateData = $initialStateData; 57 | } 58 | 59 | 60 | public function authenticate(array &$state): void 61 | { 62 | $state['saml:AuthnContextClassRef'] = $this->authnContextClassRef; 63 | 64 | StaticProcessHelper::prepareStatePasswordlessAuth($this->stateData, $state); 65 | StaticProcessHelper::saveStateAndRedirect($state); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade instructions 2 | 3 | ## Upgrade from 0.11.x to 2.0.x 4 | 5 | Note that the database schema has additional columns as of 2.0.0: 6 | 7 | algo INT DEFAULT NULL, 8 | presenceLevel INT DEFAULT NULL, 9 | isResidentKey BOOL DEFAULT NULL, 10 | `hashedId` VARCHAR(100) DEFAULT '---', 11 | 12 | If you have a previous installation of the module, you need to add this column 13 | manually ( 14 | ALTER TABLE credentials ADD COLUMN `algo` INT DEFAULT NULL AFTER `credential`; 15 | ALTER TABLE credentials ADD COLUMN `presenceLevel` INT DEFAULT NULL AFTER `algo`; 16 | ALTER TABLE credentials ADD COLUMN `isResidentKey` BOOL DEFAULT NULL AFTER `presenceLevel`; 17 | ALTER TABLE credentials ADD COLUMN `hashedId` VARCHAR(100) DEFAULT '---' AFTER `friendlyName`; 18 | ). 19 | The updated schema is compatible with the 0.11.x releases, so a roll-back to an 20 | older version is still possible without removing the column. 21 | 22 | Also note that the parameter attribute_username was changed to 23 | identifyingAttribute to achieve better consistency with other authproc filters. 24 | 25 | ## Upgrade from 2.0.x to 2.1.x 26 | 27 | Two more columns were added to record the AAGUID of the authenticator and its 28 | attestation level: 29 | 30 | aaguid VARCHAR(64) DEFAULT NULL, 31 | attLevel ENUM('None','Basic','AttCA') NOT NULL DEFAULT 'None', 32 | 33 | On existing installs, you need to add those with 34 | 35 | ALTER TABLE credentials ADD COLUMN aaguid VARCHAR(64) DEFAULT NULL AFTER `hashedId`; 36 | ALTER TABLE credentials ADD COLUMN attLevel ENUM('None','Basic','Self','AttCA') NOT NULL DEFAULT 'None' AFTER `aaguid`; 37 | 38 | The configuration options around request_tokenmodel morphed into three 39 | attributes that specify which category of authenticators is acceptable for the 40 | deployment at hand: 41 | 42 | minimum_certification_level = "0" means the authenticator model is not important 43 | and corresponds to request_tokenmodel = false. Every other setting will trigger 44 | behaviour matching the previous request_tokenmodel = true. 45 | 46 | ## Upgrade from 2.1.x to 2.2.x 47 | 48 | There are minor schema changes. The following two columns MUST be added before 49 | upgrading: 50 | 51 | ALTER TABLE credentials ADD COLUMN lastUsedTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP() AFTER `attLevel`; 52 | ALTER TABLE credentials ADD COLUMN lastUsedIp VARCHAR(64) DEFAULT NULL AFTER `lastUsedTime`; 53 | 54 | For consistency with new deployments, the following changes SHOULD be executed 55 | to align table definitions to new deployments. The module will not break 56 | if the old definition remains in place, but you may encounter issue #76 then. 57 | 58 | When using MySQL or MariaDB: 59 | 60 | ALTER TABLE credentials MODIFY COLUMN credentialId varchar(1024) CHARACTER SET 'binary' NOT NULL; 61 | 62 | -------------------------------------------------------------------------------- /locales/en/LC_MESSAGES/webauthn.po: -------------------------------------------------------------------------------- 1 | 2 | #, fuzzy 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: SimpleSAMLphp 1.15\n" 6 | "Report-Msgid-Bugs-To: simplesamlphp-translation@googlegroups.com\n" 7 | "POT-Creation-Date: 2016-10-12 09:23+0200\n" 8 | "PO-Revision-Date: 2016-10-14 12:14+0200\n" 9 | "Last-Translator: \n" 10 | "Language: de\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=utf-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: Babel 2.3.4\n" 17 | 18 | msgid "{webauthn.webauthn:passwordlessCaption}" 19 | msgstr "Passwordless Authentication" 20 | 21 | msgid "{webauthn.webauthn:passwordlessProse}" 22 | msgstr "Did you register a biometric or PIN-protected WebAuthN token already? Log in now without username or password!" 23 | 24 | msgid "{webauthn.webauthn:dedicatedManagementPageHeading}" 25 | msgstr "Managing your tokens" 26 | 27 | msgid "{webauthn.webauthn:dedicatedManagementPageHint}" 28 | msgstr "If you need to add or delete tokens, please visit" 29 | 30 | msgid "{webauthn.webauthn:dedicatedManagementPageText}" 31 | msgstr "Token Management" 32 | 33 | msgid "{webauthn.webauthn:superchargeChoice}" 34 | msgstr "- or -" 35 | 36 | msgid "{webauthn:webauthn:heading1}" 37 | msgstr "Two Factor Authentication" 38 | 39 | msgid "{webauthn:webauthn:page_title}" 40 | msgstr "Two factor authentication with FIDO2" 41 | 42 | msgid "{webauthn:webauthn:accountEnabled}" 43 | msgstr "For your account, authentication with a FIDO2/WebAuthn token is required." 44 | 45 | msgid "{webauthn:webauthn:tokenList}" 46 | msgstr "The following tokens are associated with your account:" 47 | 48 | msgid "{webauthn:webauthn:newTokenButton}" 49 | msgstr "Enroll new token" 50 | 51 | msgid "{webauthn:webauthn:newTokenName}" 52 | msgstr "Name for the new token:" 53 | 54 | msgid "{webauthn:webauthn:newTokenDefaultName}" 55 | msgstr "Token registered at" 56 | 57 | msgid "{webauthn:webauthn:authTokenButton}" 58 | msgstr "Authenticate" 59 | 60 | msgid "{webauthn:webauthn:wantsAdd}" 61 | msgstr "After authenticating, I want to register a new token." 62 | 63 | msgid "{webauthn:webauthn:wantsModification}" 64 | msgstr "After authenticating, I want to register a new token or delete an existing one." 65 | 66 | msgid "{webauthn:webauthn:noChange}" 67 | msgstr "Do not change anything." 68 | 69 | msgid "{webauthn:webauthn:removePrefix}" 70 | msgstr "Remove" 71 | 72 | msgid "{webauthn:webauthn:currentToken}" 73 | msgstr "(you authenticated with this token)" 74 | 75 | msgid "title_FIDO_CERTIFICATION_TOO_LOW" 76 | msgstr "FIDO Token not Acceptable" 77 | 78 | msgid "descr_FIDO_CERTIFICATION_TOO_LOW" 79 | msgstr "You attempted to register a FIDO token that does not match company policy. You may need to use a different token." 80 | 81 | msgid "{webauthn:webauthn:registerPasswordless}" 82 | msgstr "Register for Passwordless?" 83 | 84 | msgid "{webauthn:webauthn:registerPasswordlessExplanations}" 85 | msgstr "In Passwordless mode, the token will be protected with a biometric or " 86 | "PIN. From then on, this protection cannot be removed without deleting all " 87 | "login information for all websites." 88 | 89 | msgid "{webauthn:webauthn:tokenRegisterBox}" 90 | msgstr "New Token Registration" -------------------------------------------------------------------------------- /src/WebAuthn/AAGUID.php: -------------------------------------------------------------------------------- 1 | getConfigDir() . '/' . self::AAGUID_CONFIG_FILE; 45 | if (!file_exists($path)) { 46 | Logger::warning("Missing AAGUID configuration file ($path). No device will be recognized."); 47 | return; 48 | } 49 | 50 | $data = file_get_contents($path); 51 | $json = json_decode($data, true); 52 | if (!is_array($json)) { 53 | // there was probably an error decoding the config, log the error and pray for the best 54 | Logger::warning('Broken configuration file "' . $path . '": could not JSON-decode it.'); 55 | } else { 56 | $this->dictionary = $json; 57 | } 58 | } 59 | 60 | 61 | /** 62 | * Get the singleton instance of the AAGUID dictionary. 63 | */ 64 | public static function getInstance(): self 65 | { 66 | if (!isset(self::$instance)) { 67 | self::$instance = new self(); 68 | } 69 | return self::$instance; 70 | } 71 | 72 | 73 | /** 74 | * Determine if an AAGUID is known 75 | * 76 | * @param string $aaguid The AAGUID that we want to check. 77 | * @return bool True if we know about this token, false otherwise. 78 | */ 79 | public function hasToken(string $aaguid): bool 80 | { 81 | $lowerAaguid = strtolower($aaguid); 82 | if (array_key_exists($lowerAaguid, $this->dictionary)) { 83 | return true; 84 | } else { 85 | Logger::info("AAGUID $lowerAaguid not found in dictionary, device is unknown."); 86 | return false; 87 | } 88 | } 89 | 90 | 91 | /** 92 | * Get the information for a given AAGUID. 93 | * 94 | * @param string $aaguid The AAGUID we want to get. 95 | * @return array An array containing information about the given AAGUID, or an empty array if that AAGUID is 96 | * unknown. 97 | */ 98 | public function get(string $aaguid): array 99 | { 100 | if (!$this->hasToken($aaguid)) { 101 | return []; 102 | } 103 | return $this->dictionary[$aaguid]; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /locales/fr/LC_MESSAGES/webauthn.po: -------------------------------------------------------------------------------- 1 | 2 | #, fuzzy 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: SimpleSAMLphp 1.15\n" 6 | "Report-Msgid-Bugs-To: simplesamlphp-translation@googlegroups.com\n" 7 | "POT-Creation-Date: 2016-10-12 09:23+0200\n" 8 | "PO-Revision-Date: 2016-10-14 12:14+0200\n" 9 | "Last-Translator: \n" 10 | "Language: de\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=utf-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: Babel 2.3.4\n" 17 | 18 | msgid "{webauthn.webauthn:passwordlessCaption}" 19 | msgstr "Authentification sans mot de passe" 20 | 21 | msgid "{webauthn.webauthn:passwordlessProse}" 22 | msgstr "Vous avez déjà enregistré un token WebAuthN biométrique ou protégé par un code PIN ? Connectez-vous sans nom d'utilisateur ni mot de passe !" 23 | 24 | msgid "{webauthn.webauthn:dedicatedManagementPageHint}" 25 | msgstr "Pour ajouter ou supprimer des tokens, veuillez consulter" 26 | 27 | msgid "{webauthn.webauthn:dedicatedManagementPageText}" 28 | msgstr "Gestion des tokens" 29 | 30 | msgid "{webauthn.webauthn:superchargeChoice}" 31 | msgstr "- ou -" 32 | 33 | msgid "{webauthn:webauthn:heading1}" 34 | msgstr "Authentification à deux facteurs" 35 | 36 | msgid "{webauthn:webauthn:page_title}" 37 | msgstr "Authentification à deux facteurs avec FIDO2" 38 | 39 | msgid "{webauthn:webauthn:accountEnabled}" 40 | msgstr "Pour votre compte, une authentification avec un token FIDO2/WebAuthn est requise." 41 | 42 | msgid "{webauthn:webauthn:tokenList}" 43 | msgstr "Les tokens suivants sont associés à votre compte :" 44 | 45 | msgid "{webauthn:webauthn:newTokenButton}" 46 | msgstr "Enregistrer un nouveau token" 47 | 48 | msgid "{webauthn:webauthn:newTokenName}" 49 | msgstr "Nom du nouveau token:" 50 | 51 | msgid "{webauthn:webauthn:newTokenDefaultName}" 52 | msgstr "Token enregistré à" 53 | 54 | msgid "{webauthn:webauthn:authTokenButton}" 55 | msgstr "Authentifier" 56 | 57 | msgid "{webauthn:webauthn:wantsAdd}" 58 | msgstr "Après authentification, je souhaite enregistrer un nouveau token." 59 | 60 | msgid "{webauthn:webauthn:wantsModification}" 61 | msgstr "Après authentification, je souhaite enregistrer un nouveau token ou en supprimer un." 62 | 63 | msgid "{webauthn:webauthn:noChange}" 64 | msgstr "Ne rien modifier." 65 | 66 | msgid "{webauthn:webauthn:removePrefix}" 67 | msgstr "Supprimer" 68 | 69 | msgid "{webauthn:webauthn:currentToken}" 70 | msgstr "(vous êtes authentifié-e avec ce token)" 71 | 72 | msgid "title_FIDO_CERTIFICATION_TOO_LOW" 73 | msgstr "Token FIDO non accepté" 74 | 75 | msgid "descr_FIDO_CERTIFICATION_TOO_LOW" 76 | msgstr "Vous tentez d'enregistrer un token FIDO qui ne correspond pas à la politique de l'entreprise. Vous devriez utiliser un autre token. 77 | 78 | msgid "{webauthn:webauthn:registerPasswordless}" 79 | msgstr "Enregistrer sans mot de passe ?" 80 | 81 | msgid "{webauthn:webauthn:registerPasswordlessExplanations}" 82 | msgstr "Sans mot de passe, le token est protégé par biométrie ou " 83 | "PIN. Une telle protection ne peut être supprimée qu'en supprimant simultanément la totalité " 84 | " des informations de connexion de tous les sites Web." 85 | 86 | msgid "{webauthn:webauthn:tokenRegisterBox}" 87 | msgstr "Enregistrement d'un nouveau token" 88 | 89 | msgid "{webauthn.webauthn:dedicatedManagementPageHeading}" 90 | msgstr "Gérer ses tokens" 91 | -------------------------------------------------------------------------------- /locales/de/LC_MESSAGES/webauthn.po: -------------------------------------------------------------------------------- 1 | 2 | #, fuzzy 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: SimpleSAMLphp 1.15\n" 6 | "Report-Msgid-Bugs-To: simplesamlphp-translation@googlegroups.com\n" 7 | "POT-Creation-Date: 2016-10-12 09:23+0200\n" 8 | "PO-Revision-Date: 2016-10-14 12:14+0200\n" 9 | "Last-Translator: \n" 10 | "Language: de\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=utf-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: Babel 2.3.4\n" 17 | 18 | msgid "{webauthn.webauthn:passwordlessCaption}" 19 | msgstr "Passwortlose Authentifikation" 20 | 21 | msgid "{webauthn.webauthn:passwordlessProse}" 22 | msgstr "Haben Sie bereits ein biometrisches oder PIN-geschütztes FIDO2 Token registriert? Loggen Sie sich hier ohne Benutzernamen oder Passwort ein!" 23 | 24 | msgid "{webauthn.webauthn:superchargeChoice}" 25 | msgstr "- oder -" 26 | 27 | msgid "{webauthn.webauthn:dedicatedManagementPageHeading}" 28 | msgstr "Schlüsselverwaltung" 29 | 30 | msgid "{webauthn.webauthn:dedicatedManagementPageHint}" 31 | msgstr "Wenn Sie Schlüssel hinzufügen oder löschen möchten, gehen Sie bitte zur" 32 | 33 | msgid "{webauthn.webauthn:dedicatedManagementPageText}" 34 | msgstr "Verwaltungsseite" 35 | 36 | msgid "{webauthn:webauthn:heading1}" 37 | msgstr "Zwei-Faktor-Authentifizierung" 38 | 39 | msgid "{webauthn:webauthn:page_title}" 40 | msgstr "Zwei-Faktor-Authentifizierung mit FIDO2" 41 | 42 | msgid "{webauthn:webauthn:accountEnabled}" 43 | msgstr "Für Ihr Benutzerkonto ist der Zugang nur mit einem FIDO2/WebAuthn Schlüssel möglich." 44 | 45 | msgid "{webauthn:webauthn:tokenList}" 46 | msgstr "Bei Ihrem Benutzerkonto sind die folgenden Schlüssel registriert:" 47 | 48 | msgid "{webauthn:webauthn:newTokenButton}" 49 | msgstr "Neuen Schlüssel registrieren" 50 | 51 | msgid "{webauthn:webauthn:newTokenName}" 52 | msgstr "Name für den neuen Schlüssel:" 53 | 54 | msgid "{webauthn:webauthn:newTokenDefaultName}" 55 | msgstr "Schlüssel vom" 56 | 57 | msgid "{webauthn:webauthn:authTokenButton}" 58 | msgstr "Authentifizieren" 59 | 60 | msgid "{webauthn:webauthn:wantsAdd}" 61 | msgstr "Nach der Anmeldung möchte ich einen Schlüssel hinzufügen." 62 | 63 | msgid "{webauthn:webauthn:wantsModification}" 64 | msgstr "Nach der Anmeldung möchte ich Schlüssel hinzufügen oder löschen." 65 | 66 | msgid "{webauthn:webauthn:noChange}" 67 | msgstr "Keine Änderungen durchführen." 68 | 69 | msgid "{webauthn:webauthn:removePrefix}" 70 | msgstr "Löschen von" 71 | 72 | msgid "{webauthn:webauthn:currentToken}" 73 | msgstr "(mit diesem Schlüssel sind Sie angemeldet)" 74 | 75 | msgid "title_FIDO_CERTIFICATION_TOO_LOW" 76 | msgstr "FIDO Schlüssel inakzeptabel" 77 | 78 | msgid "descr_FIDO_CERTIFICATION_TOO_LOW" 79 | msgstr "Sie haben versucht, einen FIDO Schlüssel zu registrieren der für die Firma nicht akzeptabel ist. Sie brauchen vielleicht einen anderen Schlüssel." 80 | 81 | msgid "{webauthn:webauthn:registerPasswordless}" 82 | msgstr "Für passwortlose Anmeldung registrieren?" 83 | 84 | msgid "{webauthn:webauthn:registerPasswordlessExplanations}" 85 | msgstr "Im passwortlosen Modus wird der Schlüssel mit einem biometrischen " 86 | "Merkmal order einer PIN geschützt. Dieser Schutz kann dann nicht mehr " 87 | "entfernt werden ohne alle Login-Daten für alle Webseiten zu löschen." 88 | 89 | msgid "{webauthn:webauthn:tokenRegisterBox}" 90 | msgstr "Registrierung eines neuen Schlüssels" 91 | -------------------------------------------------------------------------------- /locales/lb/LC_MESSAGES/webauthn.po: -------------------------------------------------------------------------------- 1 | 2 | #, fuzzy 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: SimpleSAMLphp 2.0\n" 6 | "Report-Msgid-Bugs-To: simplesamlphp-translation@googlegroups.com\n" 7 | "POT-Creation-Date: 2023-04-19 09:23+0200\n" 8 | "PO-Revision-Date: 2023-04-19 12:14+0200\n" 9 | "Last-Translator: Stefan Winter\n" 10 | "Language: lb\n" 11 | "Language-Team: \n" 12 | "Plural-Forms: nplurals=2; plural=(n != 1)\n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=utf-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: Babel 2.3.4\n" 17 | 18 | msgid "{webauthn.webauthn:passwordlessCaption}" 19 | msgstr "Authentificatioun ouni Passwuert" 20 | 21 | msgid "{webauthn.webauthn:passwordlessProse}" 22 | msgstr "Huet dir schons eng biometresch oder PIN-protegéiert WebAuthN token enregistréiert? Loggt iech elo domat an, ouni Benotzernumm oder Passwuert!" 23 | 24 | msgid "{webauthn.webauthn:superchargeChoice}" 25 | msgstr "- oder -" 26 | 27 | msgid "{webauthn.webauthn:dedicatedManagementPageHeading}" 28 | msgstr "Administratioun vun Schlesselen" 29 | 30 | msgid "{webauthn.webauthn:dedicatedManagementPageHint}" 31 | msgstr "Fir Schlesselen bäizepraffen oder ze läschen, besicht w.e.g. de" 32 | 33 | msgid "{webauthn.webauthn:dedicatedManagementPageText}" 34 | msgstr "Schlessel Administratioun" 35 | 36 | msgid "{webauthn:webauthn:heading1}" 37 | msgstr "Authentificatioun mat zwee Fakteuren" 38 | 39 | msgid "{webauthn:webauthn:page_title}" 40 | msgstr "Authentificatioun mat zwee Fakteueren, iwwert FIDO2" 41 | 42 | msgid "{webauthn:webauthn:accountEnabled}" 43 | msgstr "Fir ären Compte ass agestallt, dass dir iech mat engem FIDO2/WebAuthn token aloggen misst." 44 | 45 | msgid "{webauthn:webauthn:tokenList}" 46 | msgstr "Die folgend Tokens sin fir iech enregistréiert:" 47 | 48 | msgid "{webauthn:webauthn:newTokenButton}" 49 | msgstr "Neien token enregistréiern" 50 | 51 | msgid "{webauthn:webauthn:newTokenName}" 52 | msgstr "Numm fir den neien token:" 53 | 54 | msgid "{webauthn:webauthn:newTokenDefaultName}" 55 | msgstr "Token enregistréiert den " 56 | 57 | msgid "{webauthn:webauthn:authTokenButton}" 58 | msgstr "Umellen" 59 | 60 | msgid "{webauthn:webauthn:wantsAdd}" 61 | msgstr "No dem Umellen well ech gären eng neien Token bäipraffen." 62 | 63 | msgid "{webauthn:webauthn:wantsModification}" 64 | msgstr "No dem Umellen well ech gären meng Tokens managen." 65 | 66 | msgid "{webauthn:webauthn:noChange}" 67 | msgstr "Et ass gudd. Näischt änneren w.e.g." 68 | 69 | msgid "{webauthn:webauthn:removePrefix}" 70 | msgstr "Läschen" 71 | 72 | msgid "{webauthn:webauthn:currentToken}" 73 | msgstr "(mat desem Token huet dir iech ugemellt)" 74 | 75 | msgid "title_FIDO_CERTIFICATION_TOO_LOW" 76 | msgstr "FIDO Token ass net akzeptabel" 77 | 78 | msgid "descr_FIDO_CERTIFICATION_TOO_LOW" 79 | msgstr "Dir huet versicht en Token ze benotzen den d'Firmenpolitik net erlaabt. Dir musst vläit en aaneren benotzen." 80 | 81 | msgid "{webauthn:webauthn:registerPasswordless}" 82 | msgstr "Registréieren für Login ouni Passwuert?" 83 | 84 | msgid "{webauthn:webauthn:registerPasswordlessExplanations}" 85 | msgstr "Am Modus ouni Passwuert gett de Schlessel mat engem biometrischen " 86 | "trait oder enger PIN protégéiert. Des Protectioun kann hanno net mei " 87 | "reckgängeg gemaach gin ouni dass all Login Informatiounen fir all Websäit " 88 | "geläscht gin." 89 | 90 | msgid "{webauthn:webauthn:tokenRegisterBox}" 91 | msgstr "Enregistrement vun engem neien Schlessel" -------------------------------------------------------------------------------- /src/WebAuthn/StaticProcessHelper.php: -------------------------------------------------------------------------------- 1 | redirectTrustedURL($url, ['StateId' => $id]); 20 | } 21 | 22 | 23 | public static function saveStateAndRedirectSupercharged(array &$state): void 24 | { 25 | $id = Auth\State::saveState($state, 'webauthn:request'); 26 | $url = Module::getModuleURL('webauthn/supercharged'); 27 | $httpUtils = new Utils\HTTP(); 28 | $httpUtils->redirectTrustedURL($url, ['StateId' => $id]); 29 | } 30 | 31 | 32 | public static function prepareState(StateData $stateData, array &$state): void 33 | { 34 | $state['requestTokenModel'] = $stateData->requestTokenModel; 35 | $state['authenticatorAcceptability2FA'] = [ 36 | 'minCertLevel' => $stateData->minCertLevel2FA, 37 | 'aaguidWhitelist' => $stateData->aaguidWhitelist2FA, 38 | 'attFmtWhitelist' => $stateData->attFmtWhitelist2FA, 39 | ]; 40 | $state['authenticatorAcceptabilityPasswordless'] = [ 41 | 'minCertLevel' => $stateData->minCertLevelPasswordless, 42 | 'aaguidWhitelist' => $stateData->aaguidWhitelistPasswordless, 43 | 'attFmtWhitelist' => $stateData->attFmtWhitelistPasswordless, 44 | ]; 45 | $state['webauthn:store'] = $stateData->store; 46 | $state['FIDO2Tokens'] = $stateData->store->getTokenData($state['Attributes'][$stateData->usernameAttrib][0]); 47 | $state['FIDO2Scope'] = $stateData->scope; 48 | $state['FIDO2DerivedScope'] = $stateData->derivedScope; 49 | $state['FIDO2AttributeStoringUsername'] = $stateData->usernameAttrib; 50 | $state['FIDO2Username'] = $state['Attributes'][$stateData->usernameAttrib][0]; 51 | $state['FIDO2Displayname'] = $state['Attributes'][$stateData->displaynameAttrib][0]; 52 | $state['FIDO2SignupChallenge'] = hash('sha512', random_bytes(64)); 53 | $state['FIDO2WantsRegister'] = false; 54 | $state['FIDO2AuthSuccessful'] = false; 55 | $state['FIDO2PasswordlessAuthMode'] = false; 56 | } 57 | 58 | 59 | public static function prepareStatePasswordlessAuth(StateData $stateData, array &$state): void 60 | { 61 | $state['requestTokenModel'] = $stateData->requestTokenModel; 62 | $state['webauthn:store'] = $stateData->store; 63 | $state['FIDO2Scope'] = $stateData->scope; 64 | $state['FIDO2DerivedScope'] = $stateData->derivedScope; 65 | $state['FIDO2AttributeStoringUsername'] = $stateData->usernameAttrib; 66 | $state['FIDO2SignupChallenge'] = hash('sha512', random_bytes(64)); 67 | $state['FIDO2PasswordlessAuthMode'] = true; 68 | $state['FIDO2AuthSuccessful'] = false; 69 | $state['FIDO2Tokens'] = []; // we don't know which token comes in. 70 | $state['FIDO2Username'] = 'notauthenticated'; 71 | $state['FIDO2Displayname'] = 'User Not Authenticated Yet'; 72 | $state['FIDO2WantsRegister'] = false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/WebAuthn/WebAuthnAuthenticationEvent.php: -------------------------------------------------------------------------------- 1 | 17 | * @package SimpleSAMLphp 18 | */ 19 | class WebAuthnAuthenticationEvent extends WebAuthnAbstractEvent 20 | { 21 | /** 22 | * Initialize the event object. 23 | * 24 | * Validates and parses the configuration. 25 | * 26 | * @param string $pubkeyCredType PublicKeyCredential.type 27 | * @param string $scope the scope of the event 28 | * @param string $challenge the challenge which was used to trigger this event 29 | * @param string $authData the authData binary string 30 | * @param string $clientDataJSON the client data JSON string which is present in all types of events 31 | * @param string $credentialId the credential ID 32 | * @param string $publicKey the public key which is supposed to validate the sig 33 | * (COSE format, still needs to be converted to PEM!) 34 | * @param string $signature the signature value to verify 35 | * @param bool $debugMode print debugging statements? 36 | */ 37 | public function __construct( 38 | string $pubkeyCredType, 39 | string $scope, 40 | string $challenge, 41 | string $authData, 42 | string $clientDataJSON, 43 | string $credentialId, 44 | string $publicKey, 45 | int $algo, 46 | string $signature, 47 | bool $debugMode = false, 48 | ) { 49 | $this->eventType = "AUTH"; 50 | $this->credential = $publicKey; 51 | $this->algo = $algo; 52 | $this->credentialId = $credentialId; 53 | parent::__construct($pubkeyCredType, $scope, $challenge, $authData, $clientDataJSON, $debugMode); 54 | $this->validateSignature($authData . $this->clientDataHash, $signature); 55 | } 56 | 57 | 58 | /** 59 | */ 60 | private function validateSignature(string $sigData, string $signature): void 61 | { 62 | $keyArray = $this->cborDecode(hex2bin($this->credential)); 63 | $keyObject = null; 64 | switch ($this->algo) { 65 | case WebAuthnRegistrationEvent::PK_ALGORITHM_ECDSA: 66 | $keyObject = new Ec2Key($keyArray); 67 | break; 68 | case WebAuthnRegistrationEvent::PK_ALGORITHM_RSA: 69 | $keyObject = new RsaKey($keyArray); 70 | break; 71 | default: 72 | $this->fail("Incoming public key algorithm unknown and not supported!"); 73 | } 74 | $keyResource = openssl_pkey_get_public($keyObject->asPEM()); 75 | if ($keyResource === false) { 76 | $this->fail("Unable to construct public key resource from PEM (was algo type " . $this->algo . ")."); 77 | } 78 | /** 79 | * §7.2 STEP 17: validate signature 80 | */ 81 | $sigcheck = openssl_verify($sigData, $signature, $keyResource, OPENSSL_ALGO_SHA256); 82 | switch ($sigcheck) { 83 | case 1: 84 | $this->pass("Signature validation succeeded!"); 85 | break; 86 | case 0: 87 | $this->fail("Signature validation failed (sigdata = $sigData) (signature = $signature) !"); 88 | break; 89 | default: 90 | $this->fail("There was an error executing the signature check."); 91 | break; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Controller/Supercharged.php: -------------------------------------------------------------------------------- 1 | logger::info('FIDO2 - Accessing Supercharged interface'); 29 | 30 | $stateId = $request->query->get('StateId'); 31 | if ($stateId === null) { 32 | throw new Error\BadRequest('Missing required StateId query parameter.'); 33 | } 34 | 35 | $state = $this->authState::loadState($stateId, 'webauthn:request'); 36 | 37 | $templateFile = 'webauthn:supercharged.twig'; 38 | 39 | // Make, populate and layout consent form 40 | $t = new Template($this->config, $templateFile); 41 | $t->data['UserID'] = $state['FIDO2Username']; 42 | $t->data['FIDO2Tokens'] = $state['FIDO2Tokens']; 43 | // in case IdPs want to override UI and display SP-specific content 44 | $t->data['entityid'] = $state['SPMetadata']['entityid'] ?? 'WEBAUTHN-SP-NONE'; 45 | 46 | $challenge = str_split($state['FIDO2SignupChallenge'], 2); 47 | $configUtils = new Utils\Config(); 48 | $username = str_split( 49 | hash('sha512', $state['FIDO2Username'] . '|' . $configUtils->getSecretSalt()), 50 | 2, 51 | ); 52 | 53 | $challengeEncoded = []; 54 | foreach ($challenge as $oneChar) { 55 | $challengeEncoded[] = hexdec($oneChar); 56 | } 57 | 58 | $credentialIdEncoded = []; 59 | foreach ($state['FIDO2Tokens'] as $number => $token) { 60 | $idSplit = str_split($token[0], 2); 61 | $credentialIdEncoded[$number] = []; 62 | foreach ($idSplit as $credIdBlock) { 63 | $credentialIdEncoded[$number][] = hexdec($credIdBlock); 64 | } 65 | } 66 | 67 | $usernameEncoded = []; 68 | foreach ($username as $oneChar) { 69 | $usernameEncoded[] = hexdec($oneChar); 70 | } 71 | 72 | $frontendData = []; 73 | $frontendData['challengeEncoded'] = $challengeEncoded; 74 | $frontendData['state'] = []; 75 | foreach (['FIDO2Scope','FIDO2Username','FIDO2Displayname','requestTokenModel'] as $stateItem) { 76 | $frontendData['state'][$stateItem] = $state[$stateItem]; 77 | } 78 | 79 | $t->data['showExitButton'] = !array_key_exists('Registration', $state); 80 | $frontendData['usernameEncoded'] = $usernameEncoded; 81 | $frontendData['attestation'] = $state['requestTokenModel'] ? "indirect" : "none"; 82 | $frontendData['credentialIdEncoded'] = $credentialIdEncoded; 83 | $frontendData['FIDO2PasswordlessAuthMode'] = $state['FIDO2PasswordlessAuthMode']; 84 | $t->data['hasPreviouslyDonePasswordless'] = $_COOKIE['SuccessfullyUsedPasswordlessBefore'] ?? "NO"; 85 | $t->data['frontendData'] = json_encode($frontendData); 86 | 87 | $t->data['authForm'] = ""; 88 | $t->data['authURL'] = Module::getModuleURL('webauthn/authprocess?StateId=' . urlencode($stateId)); 89 | $t->data['pushbackURL'] = Module::getModuleURL('webauthn/pushbackuserpass?StateId=' . urlencode($stateId)); 90 | 91 | // dynamically generate the JS code needed for token registration 92 | return $t; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Controller/PushbackUserPass.php: -------------------------------------------------------------------------------- 1 | authState = $authState; 59 | } 60 | 61 | 62 | /** 63 | * Inject the \SimpleSAML\Logger dependency. 64 | * 65 | * @param \SimpleSAML\Logger $logger 66 | */ 67 | public function setLogger(Logger $logger): void 68 | { 69 | $this->logger = $logger; 70 | } 71 | 72 | 73 | /** 74 | * @param \Symfony\Component\HttpFoundation\Request $request 75 | * @return ( 76 | * \Symfony\Component\HttpFoundation\RedirectResponse| 77 | * \SimpleSAML\HTTP\RunnableResponse 78 | * ) A Symfony Response-object. 79 | */ 80 | public function main(Request $request): Response 81 | { 82 | $this->logger::info('FIDO2 Supercharged - Redirecting to username/password validation'); 83 | 84 | $stateId = $request->query->get('StateId'); 85 | if ($stateId === null) { 86 | throw new Error\BadRequest('Missing required StateId query parameter.'); 87 | } 88 | 89 | $state = $this->authState::loadState($stateId, 'webauthn:request'); 90 | 91 | $authsources = Configuration::getConfig('authsources.php')->toArray(); 92 | $authsourceString = $state['pushbackAuthsource']; 93 | $authsourceClass = Auth\Source::getById($authsourceString); 94 | if (is_null($authsourceClass)) { 95 | throw new Exception("password authsource not found"); 96 | } 97 | $classname = get_class($authsourceClass); 98 | class_alias($classname, '\SimpleSAML\Module\webauthn\Auth\Source\AuthSourceOverloader'); 99 | $overrideSource = new class ( 100 | ['AuthId' => $authsourceString], 101 | $authsources[$authsourceString], 102 | ) extends AuthSourceOverloader 103 | { 104 | public function loginOverload(string $username, string $password): array 105 | { 106 | return $this->login($username, $password); 107 | } 108 | }; 109 | 110 | $attribs = $overrideSource->loginOverload( 111 | $request->request->get("username"), 112 | $request->request->get("password"), 113 | ); 114 | 115 | // this is the confirmed username, we store it just like the Passwordless 116 | // one would have been 117 | $state['Attributes'][$state['FIDO2AttributeStoringUsername']] = [ $request->request->get("username") ]; 118 | 119 | // we deliberately do not store any additional attributes - these have 120 | // to be retrieved from the same authproc that would retrieve them 121 | // in Passwordless mode 122 | unset($attribs); 123 | 124 | // now properly return our final state to the framework 125 | return new RunnableResponse([Auth\Source::class, 'completeAuth'], [&$state]); 126 | } 127 | 128 | 129 | public function login(string $username, string $password): array 130 | { 131 | throw new Exception("Ugh ($username, $password)."); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Controller/ManageToken.php: -------------------------------------------------------------------------------- 1 | authState = $authState; 59 | } 60 | 61 | 62 | /** 63 | * Inject the \SimpleSAML\Logger dependency. 64 | * 65 | * @param \SimpleSAML\Logger $logger 66 | */ 67 | public function setLogger(Logger $logger): void 68 | { 69 | $this->logger = $logger; 70 | } 71 | 72 | 73 | /** 74 | * @param \Symfony\Component\HttpFoundation\Request $request 75 | * @return \SimpleSAML\HTTP\RunnableResponse A Symfony Response-object. 76 | */ 77 | public function main(Request $request): RunnableResponse 78 | { 79 | $this->logger::info('FIDO2 - Accessing WebAuthn token management'); 80 | 81 | $stateId = $request->query->get('StateId'); 82 | if ($stateId === null) { 83 | throw new Error\BadRequest('Missing required StateId query parameter.'); 84 | } 85 | 86 | $state = $this->authState::loadState($stateId, 'webauthn:request'); 87 | 88 | $ourState = WebAuthn::workflowStateMachine($state); 89 | if ($ourState !== WebAuthn::STATE_MGMT) { 90 | throw new Exception("Attempt to access the token management page unauthenticated."); 91 | } 92 | 93 | switch ($request->request->get('submit')) { 94 | case "NEVERMIND": 95 | $response = new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]); 96 | break; 97 | case "DELETE": 98 | $credId = $request->request->get('credId'); 99 | if ($state['FIDO2AuthSuccessful'] == $credId) { 100 | throw new Exception("Attempt to delete the currently used credential despite UI preventing this."); 101 | } 102 | 103 | $store = $state['webauthn:store']; 104 | $store->deleteTokenData($credId); 105 | 106 | if (array_key_exists('Registration', $state)) { 107 | foreach ($state['FIDO2Tokens'] as $key => $value) { 108 | if ($state['FIDO2Tokens'][$key][0] == $credId) { 109 | unset($state['FIDO2Tokens'][$key]); 110 | $state['FIDO2Tokens'] = array_values($state['FIDO2Tokens']); 111 | break; 112 | } 113 | } 114 | 115 | $response = new RunnableResponse([StaticProcessHelper::class, 'saveStateAndRedirect'], [&$state]); 116 | } else { 117 | $response = new RunnableResponse([Auth\ProcessingChain::class, 'resumeProcessing'], [$state]); 118 | } 119 | break; 120 | default: 121 | throw new Exception("Unknown submit button state."); 122 | } 123 | 124 | $response->setExpires(new DateTime('Thu, 19 Nov 1981 08:52:00 GMT')); 125 | $response->setCache([ 126 | 'must_revalidate' => true, 127 | 'no_cache' => true, 128 | 'no_store' => true, 129 | 'no_transform' => false, 130 | 'public' => false, 131 | 'private' => false, 132 | ]); 133 | 134 | return $response; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /templates/webauthn.twig: -------------------------------------------------------------------------------- 1 | {% set pagetitle = '{webauthn:webauthn:page_title}'|trans %} 2 | {% extends "base.twig" %} 3 | 4 | {% block preload %} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
{{ '{webauthn.webauthn:passwordlessProse}'|trans }}
29 |{{ 'A service has requested you to authenticate yourself. Please enter your username and password in the form below.'|trans }}
59 |