Last active
January 25, 2023 15:13
-
-
Save Nevercold/f9b7527846c7db72c6bccc7a94867fcb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// just the basics from the docs | |
namespace hmcsw\service\authorization\auth\twoFactor\types\fido2; | |
use Cose\Algorithms; | |
use Cose\Algorithm\Manager; | |
use Lcobucci\Clock\SystemClock; | |
use Nyholm\Psr7\Factory\Psr17Factory; | |
use Symfony\Component\Clock\NativeClock; | |
use Webauthn\PublicKeyCredentialLoader; | |
use Cose\Algorithm\Signature\RSA\PS256; | |
use Cose\Algorithm\Signature\RSA\PS384; | |
use Cose\Algorithm\Signature\RSA\PS512; | |
use Cose\Algorithm\Signature\RSA\RS256; | |
use Cose\Algorithm\Signature\RSA\RS384; | |
use Cose\Algorithm\Signature\RSA\RS512; | |
use hmcsw\service\config\ConfigService; | |
use hmcsw\routing\routes\status\status; | |
use Webauthn\PublicKeyCredentialRpEntity; | |
use Cose\Algorithm\Signature\ECDSA\ES256; | |
use Cose\Algorithm\Signature\ECDSA\ES384; | |
use Cose\Algorithm\Signature\ECDSA\ES512; | |
use Cose\Algorithm\Signature\EdDSA\Ed256; | |
use Cose\Algorithm\Signature\EdDSA\Ed512; | |
use Cose\Algorithm\Signature\ECDSA\ES256K; | |
use Webauthn\PublicKeyCredentialParameters; | |
use Nyholm\Psr7Server\ServerRequestCreator; | |
use Webauthn\TokenBinding\IgnoreTokenBindingHandler; | |
use Webauthn\AuthenticatorAssertionResponseValidator; | |
use Webauthn\AuthenticatorAttestationResponseValidator; | |
use Webauthn\AttestationStatement\AttestationObjectLoader; | |
use Webauthn\AttestationStatement\TPMAttestationStatementSupport; | |
use Webauthn\AttestationStatement\NoneAttestationStatementSupport; | |
use Webauthn\AttestationStatement\AppleAttestationStatementSupport; | |
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; | |
use Webauthn\AttestationStatement\PackedAttestationStatementSupport; | |
use Webauthn\AttestationStatement\AttestationStatementSupportManager; | |
use Webauthn\AttestationStatement\FidoU2FAttestationStatementSupport; | |
use Webauthn\AttestationStatement\AndroidKeyAttestationStatementSupport; | |
use Webauthn\AttestationStatement\AndroidSafetyNetAttestationStatementSupport; | |
class Fido2 | |
{ | |
protected static function rpEntity (): PublicKeyCredentialRpEntity | |
{ | |
return PublicKeyCredentialRpEntity::create(ConfigService::getConfig()['name'], self::getRpId()); | |
} | |
protected static function getRpId (): string | |
{ | |
return ConfigService::getUrl("without_sub"); | |
} | |
protected static function parametersList (): array | |
{ | |
return [PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES256K), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES384), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ES512), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS256), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS384), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_RS512), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS256), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS384), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_PS512), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED256), | |
PublicKeyCredentialParameters::create('public-key', Algorithms::COSE_ALGORITHM_ED512),]; | |
} | |
protected static function publicKeyCredentialLoader (): PublicKeyCredentialLoader | |
{ | |
return PublicKeyCredentialLoader::create(self::attestationObjectLoader()); | |
} | |
protected static function attestationObjectLoader (): AttestationObjectLoader | |
{ | |
return new AttestationObjectLoader(self::attestationStatementSupportManager()); | |
} | |
protected static function attestationStatementSupportManager (): AttestationStatementSupportManager | |
{ | |
$clock = SystemClock::fromSystemTimezone(); | |
$attestationStatementSupportManager = AttestationStatementSupportManager::create(); | |
$attestationStatementSupportManager->add(NoneAttestationStatementSupport::create()); | |
$attestationStatementSupportManager->add(FidoU2FAttestationStatementSupport::create()); | |
$attestationStatementSupportManager->add(AppleAttestationStatementSupport::create()); | |
$androidSafetyNetAttestationStatementSupport = AndroidSafetyNetAttestationStatementSupport::create() | |
// ->enableApiVerification( $psr18Client, $googleApiKey, $psr17RequestFactory) // TODO | |
; | |
$attestationStatementSupportManager->add($androidSafetyNetAttestationStatementSupport); | |
$attestationStatementSupportManager->add(AndroidKeyAttestationStatementSupport::create()); | |
$attestationStatementSupportManager->add(TPMAttestationStatementSupport::create($clock)); | |
$attestationStatementSupportManager->add(PackedAttestationStatementSupport::create(self::coseAlgorithmManager())); | |
return $attestationStatementSupportManager; | |
} | |
protected static function coseAlgorithmManager(): Manager | |
{ | |
$coseAlgorithmManager = new Manager(); | |
$coseAlgorithmManager->add(new ES256()); | |
$coseAlgorithmManager->add(new RS256()); | |
return $coseAlgorithmManager; | |
} | |
protected static function serverRequestCreator (): ServerRequestCreator | |
{ | |
$psr17Factory = self::Psr17Factory(); | |
return new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory); | |
} | |
public static function Psr17Factory():Psr17Factory | |
{ | |
return new Psr17Factory(); | |
} | |
protected static function authenticatorAssertionResponseValidator (): AuthenticatorAssertionResponseValidator | |
{ | |
return AuthenticatorAssertionResponseValidator::create(self::publicKeyCredentialSourceRepository(), // The Credential Repository service | |
null, // The token binding handler | removed deprecated | |
self::extensionOutputCheckerHandler(), // The extension output checker handler | |
self::algorithmManager()); | |
} | |
protected static function publicKeyCredentialSourceRepository (): PublicKeyCredentialSourceRepository | |
{ | |
return new PublicKeyCredentialSourceRepository(); | |
} | |
/** | |
* @deprecated | |
* @return IgnoreTokenBindingHandler | |
*/ | |
protected static function tokenBindingHandler (): IgnoreTokenBindingHandler | |
{ | |
return IgnoreTokenBindingHandler::create(); | |
} | |
protected static function extensionOutputCheckerHandler (): ExtensionOutputCheckerHandler | |
{ | |
return ExtensionOutputCheckerHandler::create(); | |
} | |
protected static function algorithmManager (): Manager | |
{ | |
return Manager::create()->add(ES256::create(), ES256K::create(), ES384::create(), ES512::create(), | |
RS256::create(), RS384::create(), RS512::create(), | |
PS256::create(), PS384::create(), PS512::create(), | |
Ed256::create(), Ed512::create()); | |
} | |
protected static function authenticatorAttestationResponseValidator (): AuthenticatorAttestationResponseValidator | |
{ | |
return AuthenticatorAttestationResponseValidator::create(self::attestationStatementSupportManager(), self::publicKeyCredentialSourceRepository(), null, self::extensionOutputCheckerHandler()); | |
} | |
protected static function publicKeyCredentialUserEntityRepository (): PublicKeyCredentialUserEntityRepository | |
{ | |
return new PublicKeyCredentialUserEntityRepository(); | |
} | |
protected static function createChallengeId (int $length): string | |
{ | |
return random_bytes($length); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace hmcsw\service\authorization\auth\twoFactor\types\fido2; | |
use hmcsw\hmcsw4; | |
use Firebase\JWT\JWT; | |
use hmcsw\utils\UnitUtil; | |
use hmcsw\utils\ByteBuffer; | |
use hmcsw\service\cache\CacheService; | |
use Webauthn\Exception\WebauthnException; | |
use Webauthn\MetadataService\Statement\MetadataStatement; | |
class MetadataStatementRepository extends Fido2 implements \Webauthn\MetadataService\MetadataStatementRepository | |
{ | |
public function findOneByAAGUID (string $aaguid): ?MetadataStatement | |
{ | |
foreach (self::getMetadataService() as $metadata) { | |
if ($metadata['aaguid'] === $aaguid) { | |
return MetadataStatement::createFromArray($metadata['metadataStatement']); | |
} | |
} | |
return null; | |
} | |
protected static function getMetadataService (): array | |
{ | |
return self::queryFidoMetaDataService(); | |
} | |
public static function queryFidoMetaDataService (): array | |
{ | |
$cache = CacheService::get("metaDataStatement", false, true); | |
if($cache['success']){ | |
return $cache['response']; | |
} | |
$devices = []; | |
$url = 'https://mds.fidoalliance.org/'; | |
$raw = null; | |
if (\function_exists('curl_init')) { | |
$ch = \curl_init($url); | |
\curl_setopt($ch, CURLOPT_HEADER, false); | |
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); | |
\curl_setopt($ch, CURLOPT_USERAGENT, hmcsw4::getUserAgent()); | |
$raw = \curl_exec($ch); | |
\curl_close($ch); | |
} else { | |
$raw = \file_get_contents($url); | |
} | |
if (!\is_string($raw)) { | |
throw new WebAuthnException('Unable to query FIDO Alliance Metadata Service'); | |
} | |
$jwt = \explode('.', $raw); | |
if (\count($jwt) !== 3) { | |
throw new WebAuthnException('Invalid JWT from FIDO Alliance Metadata Service'); | |
} | |
[$header, $payload, $hash] = $jwt; | |
$payload = ByteBuffer::fromBase64Url($payload)->getJson(); | |
$count = 0; | |
if (\is_object($payload) && \property_exists($payload, 'entries') && \is_array($payload->entries)) { | |
foreach ($payload->entries as $entry) { | |
if(isset($entry->aaguid)){ | |
$devices[$entry->aaguid] = $entry; | |
} | |
} | |
} | |
CacheService::save("metaDataStatement", json_encode($devices), false, time()+(60*60*24)); | |
return json_decode(json_encode($devices), true); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace hmcsw\service\authorization\auth\twoFactor\types\fido2; | |
use Throwable; | |
use http\Client; | |
use hmcsw\utils\UnitUtil; | |
use hmcsw\service\Services; | |
use hmcsw\service\cache\CacheService; | |
use hmcsw\exception\InternalException; | |
use Webauthn\PublicKeyCredentialSource; | |
use Webauthn\AuthenticatorSelectionCriteria; | |
use Webauthn\Exception\InvalidDataException; | |
use hmcsw\service\authorization\TokenService; | |
use Webauthn\AuthenticatorAttestationResponse; | |
use Webauthn\PublicKeyCredentialCreationOptions; | |
use Webauthn\AttestationStatement\AttestationObjectLoader; | |
use Webauthn\MetadataService\CertificateChain\PhpCertificateChainValidator; | |
class registerFido2 extends Fido2 | |
{ | |
/** | |
* | |
* Creates the Option Json String to start Fido2 Authentication | |
* | |
* @param $accessToken | |
* @return PublicKeyCredentialCreationOptions|array | |
* @throws InternalException|InvalidDataException | |
*/ | |
public static function option ($accessToken): PublicKeyCredentialCreationOptions|array | |
{ | |
$input = json_decode(file_get_contents('php://input'), true); | |
$keyName = $input['keyName'] ?? "Fido2 Key"; // name for key, set by user | |
$mode = $input['mode'] ?? "integrated"; // physical or integrated. User select this on setup. | |
$token = TokenService::getAccessToken($accessToken); // user token validation, ignore this | |
if (!$token['success']) return $token; | |
$user_id = $token['response']['user_id']; | |
$PublicKeyCredentialUserEntityRepository = new PublicKeyCredentialUserEntityRepository(); | |
$PublicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository(); | |
$userEntity = $PublicKeyCredentialUserEntityRepository->findWebauthnUserByUserId($user_id); // create user entity obj | |
$credentialSources = $PublicKeyCredentialSourceRepository->findAllForUserEntity($userEntity); | |
$excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) { | |
return $credential->getPublicKeyCredentialDescriptor(); | |
}, $credentialSources); | |
$challenge = self::createChallengeId(16); | |
if($mode == "physical") { | |
$authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create()->setUserVerification(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED) | |
->setResidentKey(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED) | |
->setAuthenticatorAttachment(AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_CROSS_PLATFORM); <-- Difference | |
} else { | |
$authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create()->setUserVerification(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED) | |
->setResidentKey(AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED) | |
->setAuthenticatorAttachment(AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_PLATFORM); <-- Difference, If this is set to Cross_platform, Windows Hello cannot be added | |
}; | |
$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(self::rpEntity(), $userEntity, $challenge, self::parametersList())->setTimeout(60000)->excludeCredentials(...$excludeCredentials)->setAuthenticatorSelection($authenticatorSelectionCriteria); | |
$publicKeyCredentialCreationOptions->setAttestation(PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_DIRECT); | |
CacheService::delete(UnitUtil::base64_encode("fido2-accessToken-" . $accessToken)); | |
CacheService::save(UnitUtil::base64_encode("fido2-accessToken-" . $accessToken), json_encode(["mode" => $mode, "keyName" => $keyName, | |
"publicKeyCredentialCreationOptions" => $publicKeyCredentialCreationOptions, | |
"userEntity" => $userEntity]), false, time() + 3600); // save in cache. | |
return $publicKeyCredentialCreationOptions; | |
} | |
/** | |
* | |
* Check the Response from the Fido2 Authentication | |
* On success: key will add to user | |
* | |
* @param string $response | |
* @param string $accessToken | |
* @return array | |
*/ | |
public static function action (string $response, string $accessToken): array | |
{ | |
if($response == null){ | |
return ["success" => false, "response" => ["error_code" => 400, "error_message" => "wrong request"]]; | |
} | |
$token = TokenService::getAccessToken($accessToken); | |
if (!$token['success']) return $token; | |
$user_id = $token['response']['user_id']; // valid token again, ignore | |
$cache = CacheService::get(UnitUtil::base64_encode("fido2-accessToken-" . $accessToken)); | |
if (!$cache['success']) return $cache; | |
$cache = $cache['response']; | |
$keyName = $cache['keyName']; | |
$publicKeyCredentialCreationOptions = $cache['publicKeyCredentialCreationOptions']; | |
try { | |
$publicKeyCredential = self::publicKeyCredentialLoader()->load($response); | |
$authenticatorAttestationResponse = $publicKeyCredential->getResponse(); | |
if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) { | |
return ["success" => false, "response" => ["error_code" => 400, "error_message" => "wrong request"]]; | |
} | |
print_r($authenticatorAttestationResponse); | |
$serverRequest = self::serverRequestCreator()->fromGlobals(); | |
$publicKeyCredentialSource = self::authenticatorAttestationResponseValidator() | |
->enableMetadataStatementSupport(new MetadataStatementRepository(), new StatusReportRepository(), new PhpCertificateChainValidator(new \GuzzleHttp\Client(), Fido2::Psr17Factory())) | |
->check($authenticatorAttestationResponse, | |
PublicKeyCredentialCreationOptions::createFromArray($publicKeyCredentialCreationOptions), $serverRequest); <-- Failed in check | |
CacheService::delete(UnitUtil::base64_encode("fido2-accessToken-" . $accessToken)); // ignore, delete cache | |
self::addKey($publicKeyCredentialSource, $user_id, $keyName); // save key | |
return ["success" => true, "code" => 200]; // response to website | |
} catch (Throwable $exception) { | |
return ["success" => false, | |
"response" => ["error_code" => $exception->getCode(), | |
"error_message" => $exception->getMessage(), | |
"error_response" => $exception->getTrace()]]; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment