few bugfixes, fido/u2f still WIP

This commit is contained in:
2024-04-07 18:29:33 +02:00
parent 0974ac9260
commit 6c551b08d8
19 changed files with 164 additions and 67 deletions

View File

@@ -255,8 +255,11 @@ abstract class Request {
$this->success = $req->execute(["method" => self::getEndpoint()]);
$this->lastError = $req->getLastError();
if (!$this->success) {
$res = $req->getResult();
if (!$this->context->getUser()) {
$this->result["loggedIn"] = false;
} else if (isset($res["twoFactorToken"])) {
$this->result["twoFactorToken"] = $res["twoFactorToken"];
}
return false;
}
@@ -284,7 +287,7 @@ abstract class Request {
// this should actually not occur, how to handle this case?
$this->success = $success;
}
} catch (\Error $err) {
} catch (\Throwable $err) {
http_response_code(500);
$this->createError($err->getMessage());
$this->logger->error($err->getMessage());

View File

@@ -61,7 +61,6 @@ namespace Core\API\TFA {
use Core\API\Parameter\StringType;
use Core\API\TfaAPI;
use Core\Driver\SQL\Condition\Compare;
use Core\Driver\SQL\Query\Insert;
use Core\Objects\Context;
use Core\Objects\TwoFactor\AttestationObject;
@@ -265,10 +264,7 @@ namespace Core\API\TFA {
$settings = $this->context->getSettings();
$relyingParty = $settings->getSiteName();
$sql = $this->context->getSQL();
// TODO: for react development, localhost / HTTP_HOST is required, otherwise a DOMException is thrown
$domain = parse_url($settings->getBaseUrl(), PHP_URL_HOST);
// $domain = "localhost";
if (!$clientDataJSON || !$attestationObjectRaw) {
$challenge = null;
@@ -329,12 +325,13 @@ namespace Core\API\TFA {
return $this->createError("Unsupported key type. Expected: -7");
}
$twoFactorToken->authenticate();
$this->success = $twoFactorToken->confirmKeyBased($sql, base64_encode($authData->getCredentialID()), $publicKey) !== false;
$this->lastError = $sql->getLastError();
if ($this->success) {
$this->result["twoFactorToken"] = $twoFactorToken->jsonSerialize();
$this->context->invalidateSessions();
$this->context->invalidateSessions(true);
}
}

View File

@@ -146,6 +146,7 @@ namespace Core\API\User {
use Core\Driver\SQL\Condition\Compare;
use Core\Driver\SQL\Condition\CondIn;
use Core\Driver\SQL\Expression\JsonArrayAgg;
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
use ImagickException;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\GpgKey;
@@ -374,6 +375,12 @@ namespace Core\API\User {
$this->result["loggedIn"] = false;
$userGroups = [];
} else {
$twoFactorToken = $currentUser->getTwoFactorToken();
if ($twoFactorToken instanceof KeyBasedTwoFactorToken && !$twoFactorToken->hasChallenge()) {
$twoFactorToken->generateChallenge();
}
$this->result["loggedIn"] = true;
$userGroups = array_keys($currentUser->getGroups());
$this->result["user"] = $currentUser->jsonSerialize();
@@ -629,7 +636,7 @@ namespace Core\API\User {
$this->result["loggedIn"] = true;
$this->result["user"] = $user->jsonSerialize();
$this->result["session"] = $session->jsonSerialize();
$this->result["session"] = $session->jsonSerialize(["expires", "csrfToken"]);
$this->result["logoutIn"] = $session->getExpiresSeconds();
$this->check2FA($tfaToken);
$this->success = true;
@@ -1310,6 +1317,7 @@ namespace Core\API\User {
}
$settings = $this->context->getSettings();
$siteName = htmlspecialchars($settings->getSiteName());
$baseUrl = htmlspecialchars($settings->getBaseUrl());
$token = htmlspecialchars(urlencode($token));
$url = "$baseUrl/confirmGPG?token=$token";
@@ -1317,14 +1325,12 @@ namespace Core\API\User {
"you imported a GPG public key for end-to-end encrypted mail communication. " .
"To confirm the key and verify, you own the corresponding private key, please click on the following link. " .
"The link is active for one hour.<br><br>" .
"<a href='$url'>$url</a><br>
Best Regards<br>" .
$settings->getSiteName() . " Administration";
"<a href='$url'>$url</a><br>Best Regards<br>$siteName Administration";
$sendMail = new \Core\API\Mail\Send($this->context);
$this->success = $sendMail->execute(array(
"to" => $currentUser->getEmail(),
"subject" => $settings->getSiteName() . " - Confirm GPG-Key",
"subject" => "[$siteName] Confirm GPG-Key",
"body" => $mailBody,
"gpgFingerprint" => $gpgKey->getFingerprint()
));

View File

@@ -29,7 +29,7 @@ namespace Core\API\Visitors {
class ProcessVisit extends VisitorsAPI {
public function __construct(Context $context, bool $externalCall = false) {
parent::__construct($context, $externalCall, array(
"cookie" => new StringType("cookie")
"cookie" => new StringType("cookie", 26)
));
$this->isPublic = false;
}

View File

@@ -91,6 +91,7 @@ class Context {
}
public function sendCookies(): void {
// TODO: what will we do, when there is a domain mismatch? forbid access or just send cookies for the current domain? or should we send a redirect?
$domain = $this->getSettings()->getDomain();
$this->language->sendCookie($domain);
$this->session?->sendCookie($domain);

View File

@@ -2,6 +2,7 @@
namespace Core\Objects\TwoFactor;
use CBOR\MapObject;
use Core\Objects\ApiObject;
class AttestationObject extends ApiObject {
@@ -9,14 +10,14 @@ class AttestationObject extends ApiObject {
use CBORDecoder;
private string $format;
private array $statement;
private MapObject $statement;
private AuthenticationData $authData;
public function __construct(string $buffer) {
$data = $this->decode($buffer)->getNormalizedData();
$data = $this->decode($buffer);
$this->format = $data["fmt"];
$this->statement = $data["attStmt"];
$this->authData = new AuthenticationData($data["authData"]);
$this->authData = new AuthenticationData($data["authData"]->getValue());
}
public function jsonSerialize(): array {

View File

@@ -6,31 +6,47 @@ use Core\Objects\ApiObject;
class AuthenticationData extends ApiObject {
use CBORDecoder;
const FLAG_USER_PRESENT = 1;
const FLAG_USER_VERIFIED = 4;
const FLAG_ATTESTED_DATA_INCLUDED = 64;
const FLAG_EXTENSION_DATA_INCLUDED = 128;
private string $rpIDHash;
private int $flags;
private int $counter;
private int $signCount;
private string $aaguid;
private string $credentialID;
private array $extensions;
private PublicKey $publicKey;
public function __construct(string $buffer) {
if (strlen($buffer) < 32 + 1 + 4) {
$bufferLength = strlen($buffer);
if ($bufferLength < 32 + 1 + 4) {
throw new \Exception("Invalid authentication data buffer size");
}
$offset = 0;
$this->rpIDHash = substr($buffer, $offset, 32); $offset += 32;
$this->flags = ord($buffer[$offset]); $offset += 1;
$this->counter = unpack("N", $buffer, $offset)[1]; $offset += 4;
$this->flags = ord(substr($buffer, $offset, 1)); $offset += 1;
$this->signCount = unpack("N", substr($buffer, $offset, 4))[1]; $offset += 4;
if (strlen($buffer) >= $offset + 4 + 2) {
if ($this->attestedCredentialData()) {
$this->aaguid = substr($buffer, $offset, 16); $offset += 16;
$credentialIdLength = unpack("n", $buffer, $offset)[1]; $offset += 2;
$credentialIdLength = unpack("n", substr($buffer, $offset, 4))[1]; $offset += 2;
$this->credentialID = substr($buffer, $offset, $credentialIdLength); $offset += $credentialIdLength;
$credentialData = substr($buffer, $offset);
$this->publicKey = new PublicKey($credentialData);
if ($offset < $bufferLength) {
$publicKeyData = $this->decode(substr($buffer, $offset));
$this->publicKey = new PublicKey($publicKeyData);
// TODO: we should add $publicKeyData->length to $offset, but it's not implemented yet?;
}
}
if ($this->hasExtensionData()) {
// not supported yet
}
}
@@ -38,7 +54,7 @@ class AuthenticationData extends ApiObject {
return [
"rpIDHash" => base64_encode($this->rpIDHash),
"flags" => $this->flags,
"counter" => $this->counter,
"signCount" => $this->signCount,
"aaguid" => base64_encode($this->aaguid),
"credentialID" => base64_encode($this->credentialID),
"publicKey" => $this->publicKey->jsonSerialize()
@@ -54,26 +70,26 @@ class AuthenticationData extends ApiObject {
}
public function isUserPresent(): bool {
return boolval($this->flags & (1 << 0));
return boolval($this->flags & self::FLAG_USER_PRESENT);
}
public function isUserVerified(): bool {
return boolval($this->flags & (1 << 2));
return boolval($this->flags & self::FLAG_USER_VERIFIED);
}
public function attestedCredentialData(): bool {
return boolval($this->flags & (1 << 6));
return boolval($this->flags & self::FLAG_ATTESTED_DATA_INCLUDED);
}
public function hasExtensionData(): bool {
return boolval($this->flags & (1 << 7));
return boolval($this->flags & self::FLAG_EXTENSION_DATA_INCLUDED);
}
public function getPublicKey(): PublicKey {
return $this->publicKey;
}
public function getCredentialID() {
public function getCredentialID(): string {
return $this->credentialID;
}
}

View File

@@ -2,14 +2,13 @@
namespace Core\Objects\TwoFactor;
use CBOR\Decoder;
use CBOR\StringStream;
trait CBORDecoder {
protected function decode(string $buffer): \CBOR\CBORObject {
$objectManager = new \CBOR\OtherObject\OtherObjectManager();
$tagManager = new \CBOR\Tag\TagObjectManager();
$decoder = new \CBOR\Decoder($tagManager, $objectManager);
$decoder = Decoder::create();
return $decoder->decode(new StringStream($buffer));
}

View File

@@ -37,7 +37,7 @@ class KeyBasedTwoFactorToken extends TwoFactorToken {
}
public function hasChallenge(): bool {
return isset($this->challenge);
return isset($this->challenge) && !empty($this->challenge);
}
public function getChallenge(): string {

View File

@@ -2,26 +2,24 @@
namespace Core\Objects\TwoFactor;
use CBOR\MapObject;
use Core\Objects\ApiObject;
class PublicKey extends ApiObject {
use CBORDecoder;
private int $keyType;
private int $usedAlgorithm;
private int $curveType;
private string $xCoordinate;
private string $yCoordinate;
public function __construct(?string $cborData = null) {
if ($cborData) {
$data = $this->decode($cborData)->getNormalizedData();
$this->keyType = $data["1"];
$this->usedAlgorithm = $data["3"];
$this->curveType = $data["-1"];
$this->xCoordinate = $data["-2"];
$this->yCoordinate = $data["-3"];
public function __construct(?MapObject $publicKeyData = null) {
if ($publicKeyData) {
$this->keyType = $publicKeyData["1"]->getValue();
$this->usedAlgorithm = $publicKeyData["3"]->getValue();
$this->curveType = $publicKeyData["-1"]->getValue();
$this->xCoordinate = $publicKeyData["-2"]->getValue();
$this->yCoordinate = $publicKeyData["-3"]->getValue();
}
}
@@ -53,8 +51,8 @@ class PublicKey extends ApiObject {
public function getNormalizedData(): array {
return [
"1" => $this->keyType,
"3" => $this->usedAlgorithm,
"1" => $this->keyType,
"3" => $this->usedAlgorithm,
"-1" => $this->curveType,
"-2" => $this->xCoordinate,
"-3" => $this->yCoordinate,