diff --git a/composer.json b/composer.json index 1cc87a5..1ead76c 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "require": { "psr/clock": "1.0.0", "chillerlan/php-qrcode": "5.0.1", - "paragonie/constant_time_encoding": "2.6.3" + "paragonie/constant_time_encoding": "2.6.3", + "psr/log": "^3.0" }, "require-dev": { "phpunit/phpunit": "^11" diff --git a/devel.sh b/devel.sh index db03ec4..0a6eb4c 100755 --- a/devel.sh +++ b/devel.sh @@ -1,8 +1,8 @@ #!/bin/bash function tidy { - rm -vf {./,tests/,src/{./,AntiCSRF/,Clock/,Hash/,Request/,Required/,RFC/,Session/,URI/,Workflow/}}*~ - rm -vf {./,tests/,src/{./,AntiCSRF/,Clock/,Hash/,Request/,Required/,RFC/,Session/,URI/,Workflow/}}#*# + rm -vf {./,tests/,src/{./,AntiCSRF/,Clock/,Hash/,Logger/,Request/,Required/,RFC/,Session/,URI/,Workflow/}}*~ + rm -vf {./,tests/,src/{./,AntiCSRF/,Clock/,Hash/,Logger/,Request/,Required/,RFC/,Session/,URI/,Workflow/}}#*# } function clean { diff --git a/src/Factory.php b/src/Factory.php index e756011..428d8fa 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -2,20 +2,13 @@ namespace BradyMcD\TAATP; -use BradyMcD\TAATP\AntiCSRFInterface; -use PSR\Clock\ClockInterface; -use BradyMcD\TAATP\HashInterface; use BradyMcD\TAATP\Required\PersistenceInterface; use BradyMcD\TAATP\Required\RequestInterface; -use BradyMcD\TAATP\SessionInterface; -use BradyMcD\TAATP\AntiCSRF\Base as AntiCSRF; -use BradyMcD\TAATP\Clock\Request as RequestClock; -use BradyMcD\TAATP\Clock\Base as Clock; -use BradyMcD\TAATP\Hash\HMAC_SHA1; -use BradyMcD\TAATP\Session\Base as Session; -use BradyMcD\TAATP\Workflow\UserManagement; use BradyMcD\TAATP\Workflow\Authenticate; +use BradyMcD\TAATP\Workflow\UserManagement; +use BradyMcD\TAATP\HashConfig; +use BradyMcD\TAATP\ModuleConfig; /** * The primary entrypoint of the module. @@ -26,21 +19,10 @@ class Factory public function __construct( private PersistenceInterface $db, private RequestInterface $request, - private ?SessionInterface $session, - private ?ClockInterface $csrfClock, - private ?AntiCSRFInterface $csrf, - private ?ClockInterface $totpClock, - private ?HashInterface $hash, + private ModuleConfig $moduleConfig = new moduleConfig, + private HashConfig $hashConfig = new HashConfig, ) { - !is_null($this->session) ? : $this->session = new Session(); - !is_null($this->csrfClock) ? : $this->csrfClock = new RequestClock(); - !is_null($this->csrf) ? : $this->csrf = new AntiCSRF( - $this->session, - $this->csrfClock - ); - !is_null($this->totpClock) ? : $this->totpClock = new Clock(); - !is_null($this->hash) ? : $this->hash = new HMAC_SHA1(); } public function userManagement(mixed $userIndex): UserManagement @@ -48,10 +30,9 @@ class Factory return new UserManagement( $this->db, $this->request, - $this->csrf, - $this->session, $userIndex, - $this->hash + $this->moduleConfig, + $this->hashConfig ); } @@ -63,10 +44,9 @@ class Factory return new Authenticate( $this->db, $this->request, - $this->csrf, - $this->session, $userIndex, - $this->hash + $this->moduleConfig, + $this->hashConfig ); } } diff --git a/src/Hash/HMAC_SHA1.php b/src/Hash/BuiltinHash.php similarity index 61% rename from src/Hash/HMAC_SHA1.php rename to src/Hash/BuiltinHash.php index d6d19ed..3dbd657 100644 --- a/src/Hash/HMAC_SHA1.php +++ b/src/Hash/BuiltinHash.php @@ -5,14 +5,22 @@ use BradyMcD\TAATP\HashInterface; use ParagonIE\ConstantTime\Base32; /** @SuppressWarnings(PHPMD.CamelCaseClassName)*/ -class HMAC_SHA1 implements HashInterface +class BuiltinHash implements HashInterface { + const DEFAULT_SECRET_SIZE = 32; + + public function __construct(private readonly string $hashName) + { + (\array_search($this->hashName, \hash_hmac_algos())) ? : throw \ValueError( + $this->hashName . " is not the name of a built-in hash function"); + } + /** @SuppressWarnings(PHPMD.StaticAccess) */ public function hash(string $key, string $val): string { $key = Base32::decode($key); - return \hash_hmac("sha1", \hex2bin($val), $key, true); + return \hash_hmac($this->hashName, \hex2bin($val), $key, true); } public function hashNumeric(string $key, int $val): string @@ -20,9 +28,9 @@ class HMAC_SHA1 implements HashInterface return $this->hash($key, \dechex($val)); } - public static function hashType(): string + public function hashType(): string { - return "SHA1"; + return \strtoupper($this->hashName); } /** @SuppressWarnings(PHPMD.StaticAccess) */ diff --git a/src/HashConfig.php b/src/HashConfig.php new file mode 100644 index 0000000..d2a1758 --- /dev/null +++ b/src/HashConfig.php @@ -0,0 +1,46 @@ +hash)) + { + $this->hash = new BuiltinHash($this->hash); + } + } + + /** + * The Google Authenticator app does not handle customization to the hash function + * In testing as of February 2024 it would not accept otpauth URIs with any field beyond + the secret and issuer, failing with a generic error message + * Currently this is the same as the default constructor but is future proof. + */ + public static function googleAuthenticator(): TotpConfig + { + return new Self( + "SHA1", + new Clock(), + 30, + 6, + ); + } +} + +?> diff --git a/src/HashInterface.php b/src/HashInterface.php index f765852..f2cf14f 100644 --- a/src/HashInterface.php +++ b/src/HashInterface.php @@ -6,7 +6,7 @@ interface HashInterface { public function hash(string $key, string $val): string; public function hashNumeric(string $key, int $val): string; - public static function hashType(): string; + public function hashType(): string; public static function keygen(): string; } diff --git a/src/Logger/Base.php b/src/Logger/Base.php new file mode 100644 index 0000000..a4d4a0e --- /dev/null +++ b/src/Logger/Base.php @@ -0,0 +1,56 @@ + diff --git a/src/ModuleConfig.php b/src/ModuleConfig.php new file mode 100644 index 0000000..63a7329 --- /dev/null +++ b/src/ModuleConfig.php @@ -0,0 +1,38 @@ +csrf) ? : $this->csrf = new AntiCSRF( + $this->session, + new RequestClock() + ); + } +} + +?> diff --git a/src/Workflow/Authenticate.php b/src/Workflow/Authenticate.php index 677fa40..5b23a71 100644 --- a/src/Workflow/Authenticate.php +++ b/src/Workflow/Authenticate.php @@ -2,11 +2,10 @@ namespace BradyMcD\TAATP\Workflow; -use BradyMcD\TAATP\AntiCSRFInterface; -use BradyMcD\TAATP\HashInterface; use BradyMcD\TAATP\Required\PersistenceInterface; use BradyMcD\TAATP\Required\RequestInterface; -use BradyMcD\TAATP\SessionInterface; +use BradyMcD\TAATP\HashConfig; +use BradyMcD\TAATP\ModuleConfig; use BradyMcD\TAATP\WorkflowInterface; use BradyMcD\TAATP\URI\Otpauth; @@ -17,10 +16,9 @@ class Authenticate implements WorkflowInterface public function __construct( private PersistenceInterface $db, private RequestInterface $request, - private AntiCSRFInterface $csrf, - private SessionInterface $session, private mixed $userIndex, - private HashInterface $hash, + private ModuleConfig $taatp, + private HashConfig $totp, ) { } @@ -33,14 +31,14 @@ class Authenticate implements WorkflowInterface function emitStr(): string { $html = "
"; $values = [ - "%frm" => $this->ri->formProps("authenticate"), + "%frm" => $this->request->formProps("authenticate"), ]; return \str_replace(\array_keys($values), $values, $html); @@ -48,21 +46,36 @@ class Authenticate implements WorkflowInterface function response(): bool { - if (!this->csrf->match()) + if (!this->taatp.csrf->match()) { + $this->taatp.logger->info("Anti-csrf token failure."); return false; } $pUri = $this->db->getSecret($this->userIndex); + try + { + $algorithm = new BuiltinHash($pUri.algorithm); + } + catch (\InvalidArgumentException $e) + { + $this->taatp.logger->alert( + "Unable to authenticate user:" . + $db->userString($userIndex) . + ". otp configuration on database refers to unsopported algorithm:" . + $pUri.algorithm); + return false; + } + $totp = _6238( $pUri.secret, $pUri.period, $this->db->getLastTime($this->userIndex), 2, $pUri.digits, - $this->clock, - $this->hash + $this->totp.clock, + $algorithm ); $flag = $totp.validate($this->ri->getResp("totp_challenge")); diff --git a/src/Workflow/UserManagement.php b/src/Workflow/UserManagement.php index 5bfa905..6c37a90 100644 --- a/src/Workflow/UserManagement.php +++ b/src/Workflow/UserManagement.php @@ -2,11 +2,10 @@ namespace BradyMcD\TAATP\Workflow; -use BradyMcD\TAATP\AntiCSRFInterface; -use BradyMcD\TAATP\HashInterface; use BradyMcD\TAATP\Required\PersistenceInterface; use BradyMcD\TAATP\Required\RequestInterface; -use BradyMcD\TAATP\SessionInterface; +use BradyMcD\TAATP\HashConfig; +use BradyMcD\TAATP\ModuleConfig; use BradyMcD\TAATP\WorkflowInterface; use BradyMcD\TAATP\URI\Otpauth; @@ -20,10 +19,9 @@ class UserManagement implements WorkflowInterface public function __construct( private PersistenceInterface $db, private RequestInterface $request, - private AntiCSRFInterface $csrf, - private SessionInterface $session, private mixed $userIndex, - private HashInterface $hash, + private ModuleConfig $moduleConfig, + private HashConfig $hashConfig, ) { } @@ -36,7 +34,7 @@ class UserManagement implements WorkflowInterface private function viewEnrollForm(): string { $html = "