From 30b38e3f4be0fb69871163e27b8e207aed8a5098 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 8 Aug 2022 02:07:27 +0800 Subject: [PATCH] =?UTF-8?q?WebAuthn=E5=88=9D=E6=AD=A5=E6=8E=A5=E5=A5=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + .../Controllers/UserWebAuthnController.php | 168 ++++ app/Models/Casts/TrustPath.php | 21 + app/Models/Casts/Uuid.php | 24 + app/Models/Casts/WebAuthnBase64.php | 19 + app/Models/WebauthnCredential.php | 68 ++ ...ublicKeyCredentialSourceRepositoryImpl.php | 69 ++ app/WebAuthn/WebAuthnService.php | 50 + build.sh | 1 + composer.json | 4 +- composer.lock | 872 +++++++++++++++++- package.json | 3 +- resources/js/webauthn.js | 14 + routes/web.php | 6 + webpack.mix.js | 8 +- yarn.lock | 5 + 16 files changed, 1329 insertions(+), 5 deletions(-) create mode 100644 app/Http/Controllers/UserWebAuthnController.php create mode 100644 app/Models/Casts/TrustPath.php create mode 100644 app/Models/Casts/Uuid.php create mode 100644 app/Models/Casts/WebAuthnBase64.php create mode 100644 app/Models/WebauthnCredential.php create mode 100644 app/WebAuthn/Repository/PublicKeyCredentialSourceRepositoryImpl.php create mode 100644 app/WebAuthn/WebAuthnService.php create mode 100644 resources/js/webauthn.js diff --git a/.gitignore b/.gitignore index eb003b0..b2b2a54 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ npm-debug.log yarn-error.log /.idea /.vscode + +/public/mix-manifest.json diff --git a/app/Http/Controllers/UserWebAuthnController.php b/app/Http/Controllers/UserWebAuthnController.php new file mode 100644 index 0000000..37c4555 --- /dev/null +++ b/app/Http/Controllers/UserWebAuthnController.php @@ -0,0 +1,168 @@ +user("web")->name, + $request->user("web")->id, + $request->user("web")->name, + ); + $challenge = random_bytes(16); + $request->session()->put("webauthn_register_challenge", $challenge); + return WebAuthnService::createRequestOptions($userEntity, $challenge); + } + + public function login_options(Request $request): PublicKeyCredentialRequestOptions + { + $challenge = random_bytes(32); + $request->session()->put("webauthn_login_challenge", $challenge); + $publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions( + $challenge + ); + $publicKeyCredentialRequestOptions->setUserVerification( + PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED + ); + $publicKeyCredentialRequestOptions->allowCredentials([]); + $publicKeyCredentialRequestOptions->setTimeout($this->TIMEOUT); + return $publicKeyCredentialRequestOptions; + } + + public function register_validate(Request $request) + { + if (!$request->session()->has("webauthn_register_challenge")) { + /// + } + $publicKeyCredential = $this->get_public_key_credential_loader()->loadArray($request->json()->all()); + $authenticatorAttestationResponse = $publicKeyCredential->getResponse(); + if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { + //e.g. process here with a redirection to the public key creation page. + } + $userEntity = new PublicKeyCredentialUserEntity( + $request->user("web")->name, + $request->user("web")->id, + $request->user("web")->name, + ); + $challenge = $request->session()->remove("webauthn_register_challenge"); + $publicKeyCredentialCreationOptions = WebAuthnService::createRequestOptions($userEntity, $challenge); + $publicKeyCredentialSource = $this->get_authn_attestation_response_validator()->check( + $authenticatorAttestationResponse, + $publicKeyCredentialCreationOptions, + ServerRequest::fromGlobals(), + ["localhost"] + ); + $this->get_pub_key_cred_source_repository()->saveCredentialSource($publicKeyCredentialSource); + return $publicKeyCredentialSource; + } + + public function login_validate(Request $request) + { + if (!$request->session()->has("webauthn_login_challenge")) { + /// + } + $publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions( + $request->session()->remove("webauthn_login_challenge") + ); + $publicKeyCredential = $this->get_public_key_credential_loader()->loadArray($request->json()->all()); + $authenticatorAssertionResponse = $publicKeyCredential->getResponse(); + if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) { + //e.g. process here with a redirection to the public key login/MFA page. + } + try { + $publicKeyCredentialSource = $this->get_authn_assertion_response_validator()->check( + $publicKeyCredential->getRawId(), + $authenticatorAssertionResponse, + $publicKeyCredentialRequestOptions, + ServerRequest::fromGlobals(), + $authenticatorAssertionResponse->getUserHandle(), + ["localhost"] + ); + } catch (\Throwable $e) { + return redirect(route("login")); + } + Auth::loginUsingId($publicKeyCredentialSource->getUserHandle()); + return redirect()->intended(); + } + + private function get_authn_attestation_response_validator(): AuthenticatorAttestationResponseValidator + { + $authenticatorAttestationResponseValidator = new AuthenticatorAttestationResponseValidator( + $this->get_attestation_stmt_sup_mgr(), + WebAuthnService::getPublicKeyCredentialSourceRepository(), + new IgnoreTokenBindingHandler(), + new ExtensionOutputCheckerHandler() + ); + return $authenticatorAttestationResponseValidator; + } + + private function get_authn_assertion_response_validator(): AuthenticatorAssertionResponseValidator + { + $algorithmManager = new Manager(); + $algorithmManager->add(new ES256()); + $algorithmManager->add(new RS256()); + $authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator( + WebAuthnService::getPublicKeyCredentialSourceRepository(), + new IgnoreTokenBindingHandler(), + new ExtensionOutputCheckerHandler(), + $algorithmManager + ); + return $authenticatorAssertionResponseValidator; + } + + private function get_public_key_credential_loader(): PublicKeyCredentialLoader + { + $publicKeyCredentialLoader = new PublicKeyCredentialLoader( + $this->get_attestation_loader() + ); + return $publicKeyCredentialLoader; + } + + private function get_attestation_loader(): AttestationObjectLoader + { + $attestationObjectLoader = new AttestationObjectLoader($this->get_attestation_stmt_sup_mgr()); + return $attestationObjectLoader; + } + + private function get_attestation_stmt_sup_mgr() + { + if ($this->attestationStatementSupportManager === null) { + $this->attestationStatementSupportManager = new AttestationStatementSupportManager(); + $this->attestationStatementSupportManager->add(new NoneAttestationStatementSupport()); + } + return $this->attestationStatementSupportManager; + } +} diff --git a/app/Models/Casts/TrustPath.php b/app/Models/Casts/TrustPath.php new file mode 100644 index 0000000..c625f71 --- /dev/null +++ b/app/Models/Casts/TrustPath.php @@ -0,0 +1,21 @@ + Uuid::class, + 'counter' => 'integer', + 'credential_id' => WebAuthnBase64::class, + 'credential_public_key' => WebAuthnBase64::class, + 'transports' => 'array', + 'trust_path' => TrustPath::class, + 'last_used_at' => 'immutable_datetime', + ]; + + public function getPublicKeyCredentialSourceAttribute(): PublicKeyCredentialSource + { + return new PublicKeyCredentialSource( + $this->credential_id, + $this->type, + $this->transports, + $this->attestation_type, + $this->trust_path, + $this->aaguid ?? \Ramsey\Uuid\Uuid::uuid4(), + $this->credential_public_key, + (string) $this->user_id, + $this->counter, + ); + } + + public function setPublicKeyCredentialSourceAttribute(PublicKeyCredentialSource $source): void + { + $this->credential_id = $source->getPublicKeyCredentialId(); + $this->type = $source->getType(); + $this->transports = $source->getTransports(); + $this->attestation_type = $source->getAttestationType(); + $this->trust_path = $source->getTrustPath(); + $this->aaguid = $source->getAaguid(); + $this->credential_public_key = $source->getCredentialPublicKey(); + $this->counter = $source->getCounter(); + $this->user_id = $source->getUserHandle(); + } + + public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(User::class, "user_id", "id"); + } +} diff --git a/app/WebAuthn/Repository/PublicKeyCredentialSourceRepositoryImpl.php b/app/WebAuthn/Repository/PublicKeyCredentialSourceRepositoryImpl.php new file mode 100644 index 0000000..046012e --- /dev/null +++ b/app/WebAuthn/Repository/PublicKeyCredentialSourceRepositoryImpl.php @@ -0,0 +1,69 @@ +findOneModelByCredentialId($publicKeyCredentialId); + if ($model) { + return $model->getPublicKeyCredentialSourceAttribute(); + } else { + return null; + } + } + + /** + * @inheritDoc + */ + public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array + { + if ($publicKeyCredentialUserEntity->getId() == 0) { + $modelList = $this->findAllModelByTypeFree(); + } else { + $modelList = $this->findAllModelByUserId($publicKeyCredentialUserEntity->getId()); + } + return array_map(function (WebauthnCredential $cred) { + return $cred->getPublicKeyCredentialSourceAttribute(); + }, $modelList); + } + + public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void + { + $model = $this->findOneModelByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId()); + if ($model === null) { + $model = new WebauthnCredential(); + $model->setPublicKeyCredentialSourceAttribute($publicKeyCredentialSource); + } + $model->counter += 1; + $model->last_used_at = now(); + $model->save(); + } + + private function findOneModelByCredentialId(string $publicKeyCredentialId): ?WebauthnCredential + { + /** + * @var WebauthnCredential + */ + return WebauthnCredential::query()->where(function ($query) use ($publicKeyCredentialId) { + $query->where("credential_id", "=", base64_encode($publicKeyCredentialId)); + })->first(); + } + + private function findAllModelByTypeFree(): array + { + return WebauthnCredential::query()->where("type_free", "=", "1")->get()->toArray(); + } + + private function findAllModelByUserId(string $userId): array + { + return WebauthnCredential::query()->where("user_id", "=", $userId)->get()->toArray(); + } +} diff --git a/app/WebAuthn/WebAuthnService.php b/app/WebAuthn/WebAuthnService.php new file mode 100644 index 0000000..2990ad7 --- /dev/null +++ b/app/WebAuthn/WebAuthnService.php @@ -0,0 +1,50 @@ +=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Assert/functions.php" + ], + "psr-4": { + "Assert\\": "lib/Assert" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.2" + }, + "time": "2021-12-16T21:41:27+00:00" + }, { "name": "brick/math", "version": "0.9.3", @@ -689,6 +762,87 @@ ], "time": "2020-12-29T14:50:06+00:00" }, + { + "name": "fgrosse/phpasn1", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/fgrosse/PHPASN1.git", + "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/eef488991d53e58e60c9554b09b1201ca5ba9296", + "reference": "eef488991d53e58e60c9554b09b1201ca5ba9296", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "~2.0", + "phpunit/phpunit": "^6.3 || ^7.0 || ^8.0" + }, + "suggest": { + "ext-bcmath": "BCmath is the fallback extension for big integer calculations", + "ext-curl": "For loading OID information from the web if they have not bee defined statically", + "ext-gmp": "GMP is the preferred extension for big integer calculations", + "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "FG\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Friedrich Große", + "email": "friedrich.grosse@gmail.com", + "homepage": "https://github.com/FGrosse", + "role": "Author" + }, + { + "name": "All contributors", + "homepage": "https://github.com/FGrosse/PHPASN1/contributors" + } + ], + "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", + "homepage": "https://github.com/FGrosse/PHPASN1", + "keywords": [ + "DER", + "asn.1", + "asn1", + "ber", + "binary", + "decoding", + "encoding", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/fgrosse/PHPASN1/issues", + "source": "https://github.com/fgrosse/PHPASN1/tree/v2.4.0" + }, + "time": "2021-12-11T12:41:06+00:00" + }, { "name": "fruitcake/laravel-cors", "version": "v2.2.0", @@ -2005,6 +2159,188 @@ ], "time": "2022-04-17T13:12:02+00:00" }, + { + "name": "league/uri", + "version": "6.7.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "2d7c87a0860f3126a39f44a8a9bf2fed402dcfea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/2d7c87a0860f3126a39f44a8a9bf2fed402dcfea", + "reference": "2d7c87a0860f3126a39f44a8a9bf2fed402dcfea", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "league/uri-interfaces": "^2.3", + "php": "^7.4 || ^8.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.3.2", + "nyholm/psr7": "^1.5", + "php-http/psr7-integration-tests": "^1.1", + "phpstan/phpstan": "^1.2.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-strict-rules": "^1.1.0", + "phpunit/phpunit": "^9.5.10", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-fileinfo": "Needed to create Data URI from a filepath", + "ext-intl": "Needed to improve host validation", + "league/uri-components": "Needed to easily manipulate URI objects", + "psr/http-factory": "Needed to use the URI factory" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri/issues", + "source": "https://github.com/thephpleague/uri/tree/6.7.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2022-06-29T09:48:18+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", + "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.19", + "phpstan/phpstan": "^0.12.90", + "phpstan/phpstan-phpunit": "^0.12.19", + "phpstan/phpstan-strict-rules": "^0.12.9", + "phpunit/phpunit": "^8.5.15 || ^9.5" + }, + "suggest": { + "ext-intl": "to use the IDNA feature", + "symfony/intl": "to use the IDNA feature via Symfony Polyfill" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interface for URI representation", + "homepage": "http://github.com/thephpleague/uri-interfaces", + "keywords": [ + "rfc3986", + "rfc3987", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/thephpleague/uri-interfaces/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2021-06-28T04:27:21+00:00" + }, { "name": "monolog/monolog", "version": "2.7.0", @@ -3381,6 +3717,163 @@ ], "time": "2021-09-25T23:10:38+00:00" }, + { + "name": "spomky-labs/base64url", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/base64url.git", + "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.11|^0.12", + "phpstan/phpstan-beberlei-assert": "^0.11|^0.12", + "phpstan/phpstan-deprecation-rules": "^0.11|^0.12", + "phpstan/phpstan-phpunit": "^0.11|^0.12", + "phpstan/phpstan-strict-rules": "^0.11|^0.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Base64Url\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky-Labs/base64url/contributors" + } + ], + "description": "Base 64 URL Safe Encoding/Decoding PHP Library", + "homepage": "https://github.com/Spomky-Labs/base64url", + "keywords": [ + "base64", + "rfc4648", + "safe", + "url" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/base64url/issues", + "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2020-11-03T09:10:25+00:00" + }, + { + "name": "spomky-labs/cbor-php", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/28e2712cfc0b48fae661a48ffc6896d7abe83684", + "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "brick/math": "^0.8.15|^0.9.0", + "ext-mbstring": "*", + "php": ">=7.3" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "ext-json": "*", + "infection/infection": "^0.18|^0.25", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.12", + "roave/security-advisories": "dev-latest", + "symplify/easy-coding-standard": "^10.0" + }, + "suggest": { + "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", + "ext-gmp": "GMP or BCMath extensions will drastically improve the library performance" + }, + "type": "library", + "autoload": { + "psr-4": { + "CBOR\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" + } + ], + "description": "CBOR Encoder/Decoder for PHP", + "keywords": [ + "Concise Binary Object Representation", + "RFC7049", + "cbor" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/cbor-php/issues", + "source": "https://github.com/Spomky-Labs/cbor-php/tree/v2.1.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2021-12-13T12:46:26+00:00" + }, { "name": "swiftmailer/swiftmailer", "version": "v6.3.0", @@ -5825,6 +6318,151 @@ ], "time": "2022-05-21T10:24:18+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v1.3.3", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc", + "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12", + "squizlabs/php_codesniffer": "^3.2", + "thecodingmachine/phpstan-strict-rules": "^0.12" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "files": [ + "deprecated/apc.php", + "deprecated/libevent.php", + "deprecated/mssql.php", + "deprecated/stats.php", + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/ingres-ii.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/msql.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/mysqlndMs.php", + "generated/mysqlndQc.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/password.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pdf.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/simplexml.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "psr-4": { + "Safe\\": [ + "lib/", + "deprecated/", + "generated/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3" + }, + "time": "2020-10-28T17:51:34+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "2.2.4", @@ -6050,6 +6688,238 @@ ], "time": "2022-01-24T18:55:24+00:00" }, + { + "name": "web-auth/cose-lib", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/efa6ec2ba4e840bc1316a493973c9916028afeeb", + "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "beberlei/assert": "^3.2", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "fgrosse/phpasn1": "^2.1", + "php": ">=7.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "COSE", + "RFC8152" + ], + "support": { + "source": "https://github.com/web-auth/cose-lib/tree/v3.3.12" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2021-12-04T12:13:35+00:00" + }, + { + "name": "web-auth/metadata-service", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-metadata-service.git", + "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/ef40d2b7b68c4964247d13fab52e2fa8dbd65246", + "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "beberlei/assert": "^3.2", + "ext-json": "*", + "league/uri": "^6.0", + "php": ">=7.2", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/log": "^1.1" + }, + "suggest": { + "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources", + "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webauthn\\MetadataService\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/metadata-service/contributors" + } + ], + "description": "Metadata Service for FIDO2/Webauthn", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "support": { + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/v3.3.12" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2021-11-21T11:14:31+00:00" + }, + { + "name": "web-auth/webauthn-lib", + "version": "v3.3.12", + "source": { + "type": "git", + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/5ef9b21c8e9f8a817e524ac93290d08a9f065b33", + "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "beberlei/assert": "^3.2", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "fgrosse/phpasn1": "^2.1", + "php": ">=7.2", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "psr/log": "^1.1", + "ramsey/uuid": "^3.8|^4.0", + "spomky-labs/base64url": "^2.0", + "spomky-labs/cbor-php": "^1.0|^2.0", + "symfony/process": "^3.0|^4.0|^5.0", + "thecodingmachine/safe": "^1.1", + "web-auth/cose-lib": "self.version", + "web-auth/metadata-service": "self.version" + }, + "suggest": { + "psr/log-implementation": "Recommended to receive logs from the library", + "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", + "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webauthn\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" + } + ], + "description": "FIDO2/Webauthn Support For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "FIDO2", + "fido", + "webauthn" + ], + "support": { + "source": "https://github.com/web-auth/webauthn-lib/tree/v3.3.12" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2022-02-18T07:13:44+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/package.json b/package.json index 014fd4f..3300776 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "tailwindcss": "^3.1.6" }, "dependencies": { - "@tailwindcss/forms": "^0.5.2" + "@tailwindcss/forms": "^0.5.2", + "@web-auth/webauthn-helper": "^0.0.13" } } diff --git a/resources/js/webauthn.js b/resources/js/webauthn.js new file mode 100644 index 0000000..2f99e3f --- /dev/null +++ b/resources/js/webauthn.js @@ -0,0 +1,14 @@ +import {useRegistration, useLogin} from '@web-auth/webauthn-helper' + +const webauthn_register = useRegistration({ + actionUrl: "/user/webauthn/", + optionsUrl: "/user/webauthn/options", +}) + +const webauthn_login = useLogin({ + actionUrl: "/login/webauthn/", + optionsUrl: "/login/webauthn/options", +}) + +window.webauthn_register = webauthn_register +window.webauthn_login = webauthn_login diff --git a/routes/web.php b/routes/web.php index 431a907..c773144 100644 --- a/routes/web.php +++ b/routes/web.php @@ -26,6 +26,8 @@ Route::post('/upload', ["\\App\\Http\\Controllers\\FileController","upload"]); // 用户部分 Route::get('/login', ["\\App\\Http\\Controllers\\UserController", "login_page"])->name("login"); Route::post('/login', ["\\App\\Http\\Controllers\\UserController", "authenticate"])->name("login.submit"); +Route::post("/login/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_options"])->name("login.webauthn.options"); +Route::post("/login/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnController", "login_validate"])->name("login.webauthn.submit"); Route::get('/register', ["\\App\\Http\\Controllers\\UserController", "register_page"])->name("register"); Route::post('/register', ["\\App\\Http\\Controllers\\UserController", "register"])->name("register.submit"); Route::get('/logout', ["\\App\\Http\\Controllers\\UserController", "logout"])->name("logout"); @@ -52,3 +54,7 @@ Route::prefix("/programs/construct")->middleware("auth:web")->group(function (Ro $router->get('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","edit"])->name("program.construct.append.edit"); $router->post('/append/{append}', ["\\App\\Http\\Controllers\\ProgramAppendConstructController","submit"])->name("program.construct.append.submit"); }); +Route::prefix("/user")->middleware("auth:web")->group(function (Router $router) { + $router->post("/webauthn/options", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_options"])->name("user.webauthn.bind.options"); + $router->post("/webauthn/", ["\\App\\Http\\Controllers\\UserWebAuthnController", "register_validate"])->name("user.webauthn.bind.submit"); +}); diff --git a/webpack.mix.js b/webpack.mix.js index eb449ff..811c11b 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -12,9 +12,13 @@ const mix = require('laravel-mix'); */ mix.js('resources/js/app.js', 'public/js') + .extract(['axios', 'lodash']) + .js('resources/js/webauthn.js', 'public/js') .postCss('resources/css/app.css', 'public/css', [ require('tailwindcss') ]) - .version(); + .disableNotifications(); +if (mix.inProduction()) { + mix.version(); +} -mix.disableNotifications(); diff --git a/yarn.lock b/yarn.lock index a853b96..16435e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1247,6 +1247,11 @@ dependencies: "@types/node" "*" +"@web-auth/webauthn-helper@^0.0.13": + version "0.0.13" + resolved "https://registry.npmmirror.com/@web-auth/webauthn-helper/-/webauthn-helper-0.0.13.tgz#2e9e6f421a3238bee233fe97c6801a6c6354cf7b" + integrity sha512-yEXWTiZSGFYnCVEwOqWgZFrQ4MiW9WGAiAvdRD6NQRlp7sr39THZiGIix/x3y3zmnsUsZZ0iXQA8zG/gPESwzQ== + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"