You've already forked lubo_comment_query
WebAuthn初步接好
This commit is contained in:
168
app/Http/Controllers/UserWebAuthnController.php
Normal file
168
app/Http/Controllers/UserWebAuthnController.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl;
|
||||
use App\WebAuthn\WebAuthnService;
|
||||
use Cose\Algorithm\Manager;
|
||||
use Cose\Algorithm\Signature\ECDSA\ES256;
|
||||
use Cose\Algorithm\Signature\RSA\RS256;
|
||||
use Cose\Algorithms;
|
||||
use GuzzleHttp\Psr7\ServerRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Webauthn\AttestationStatement\AttestationObjectLoader;
|
||||
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
|
||||
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
|
||||
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
|
||||
use Webauthn\AuthenticatorAssertionResponse;
|
||||
use Webauthn\AuthenticatorAssertionResponseValidator;
|
||||
use Webauthn\AuthenticatorAttestationResponse;
|
||||
use Webauthn\AuthenticatorAttestationResponseValidator;
|
||||
use Webauthn\AuthenticatorSelectionCriteria;
|
||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use Webauthn\PublicKeyCredentialLoader;
|
||||
use Webauthn\PublicKeyCredentialParameters;
|
||||
use Webauthn\PublicKeyCredentialRequestOptions;
|
||||
use Webauthn\PublicKeyCredentialRpEntity;
|
||||
use Webauthn\PublicKeyCredentialSourceRepository;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
use Webauthn\TokenBinding\IgnoreTokenBindingHandler;
|
||||
|
||||
class UserWebAuthnController extends BaseController
|
||||
{
|
||||
private $TIMEOUT = 45000;
|
||||
private $attestationStatementSupportManager = null;
|
||||
|
||||
public function register_options(Request $request): PublicKeyCredentialCreationOptions
|
||||
{
|
||||
$userEntity = new PublicKeyCredentialUserEntity(
|
||||
$request->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;
|
||||
}
|
||||
}
|
21
app/Models/Casts/TrustPath.php
Normal file
21
app/Models/Casts/TrustPath.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Webauthn\TrustPath\TrustPathLoader;
|
||||
|
||||
class TrustPath implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes): ?\Webauthn\TrustPath\TrustPath
|
||||
{
|
||||
return $value !== null
|
||||
? TrustPathLoader::loadTrustPath(json_decode($value, true))
|
||||
: null;
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return json_encode($value);
|
||||
}
|
||||
}
|
24
app/Models/Casts/Uuid.php
Normal file
24
app/Models/Casts/Uuid.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Ramsey\Uuid\Uuid as UuidConvert;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
class Uuid implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes): ?UuidInterface
|
||||
{
|
||||
if ($value !== null && UuidConvert::isValid($value)) {
|
||||
return UuidConvert::fromString($value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes): ?string
|
||||
{
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
19
app/Models/Casts/WebAuthnBase64.php
Normal file
19
app/Models/Casts/WebAuthnBase64.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Casts;
|
||||
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use function Safe\base64_decode;
|
||||
|
||||
class WebAuthnBase64 implements CastsAttributes
|
||||
{
|
||||
public function get($model, string $key, $value, array $attributes): ?string
|
||||
{
|
||||
return $value !== null ? base64_decode($value) : null;
|
||||
}
|
||||
|
||||
public function set($model, string $key, $value, array $attributes)
|
||||
{
|
||||
return $value !== null ? base64_encode($value) : null;
|
||||
}
|
||||
}
|
68
app/Models/WebauthnCredential.php
Normal file
68
app/Models/WebauthnCredential.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Casts\TrustPath;
|
||||
use App\Models\Casts\Uuid;
|
||||
use App\Models\Casts\WebAuthnBase64;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
|
||||
class WebauthnCredential extends Model
|
||||
{
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $visible = [
|
||||
'id',
|
||||
'name',
|
||||
'type',
|
||||
'attachment_type',
|
||||
'transports',
|
||||
'type_free',
|
||||
'last_used_at',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
];
|
||||
protected $casts = [
|
||||
'aaguid' => 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");
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\WebAuthn\Repository;
|
||||
|
||||
use App\Models\WebauthnCredential;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
use Webauthn\PublicKeyCredentialSourceRepository;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
|
||||
class PublicKeyCredentialSourceRepositoryImpl implements PublicKeyCredentialSourceRepository
|
||||
{
|
||||
|
||||
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
|
||||
{
|
||||
$model = $this->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();
|
||||
}
|
||||
}
|
50
app/WebAuthn/WebAuthnService.php
Normal file
50
app/WebAuthn/WebAuthnService.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\WebAuthn;
|
||||
|
||||
use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl;
|
||||
use Cose\Algorithms;
|
||||
use Webauthn\AuthenticatorSelectionCriteria;
|
||||
use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use Webauthn\PublicKeyCredentialParameters;
|
||||
use Webauthn\PublicKeyCredentialRpEntity;
|
||||
use Webauthn\PublicKeyCredentialSourceRepository;
|
||||
use Webauthn\PublicKeyCredentialUserEntity;
|
||||
|
||||
class WebAuthnService
|
||||
{
|
||||
private static $rpName = "开心鄢的录播查询小站";
|
||||
private static $rpId = "localhost";
|
||||
private static $timeout = 45000;
|
||||
private static $publicKeyCredentialSourceRepositoryInstance = null;
|
||||
public static function createRequestOptions(PublicKeyCredentialUserEntity $userEntity, string $challenge): PublicKeyCredentialCreationOptions
|
||||
{
|
||||
$publicKeyCredentialParametersList = [
|
||||
new PublicKeyCredentialParameters("public-key", Algorithms::COSE_ALGORITHM_ES256),
|
||||
new PublicKeyCredentialParameters("public-key", Algorithms::COSE_ALGORITHM_RS256),
|
||||
];
|
||||
return new PublicKeyCredentialCreationOptions(
|
||||
static::getRpEntity(),
|
||||
$userEntity,
|
||||
$challenge,
|
||||
$publicKeyCredentialParametersList,
|
||||
static::$timeout
|
||||
);
|
||||
}
|
||||
|
||||
public static function getPublicKeyCredentialSourceRepository(): PublicKeyCredentialSourceRepository
|
||||
{
|
||||
if (static::$publicKeyCredentialSourceRepositoryInstance === null) {
|
||||
static::$publicKeyCredentialSourceRepositoryInstance = new PublicKeyCredentialSourceRepositoryImpl();
|
||||
}
|
||||
return static::$publicKeyCredentialSourceRepositoryInstance;
|
||||
}
|
||||
|
||||
private static function getRpEntity(): PublicKeyCredentialRpEntity
|
||||
{
|
||||
return new PublicKeyCredentialRpEntity(
|
||||
static::$rpName,
|
||||
static::$rpId
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user