Update website

This commit is contained in:
Guilhem Lavaux 2024-11-23 20:45:29 +01:00
parent 41ce1aa076
commit ea0eb1c6e0
4222 changed files with 721797 additions and 14 deletions

View file

@ -0,0 +1,9 @@
<?php
namespace PragmaRX\Google2FA\Exceptions\Contracts;
use Throwable;
interface Google2FA extends Throwable
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace PragmaRX\Google2FA\Exceptions\Contracts;
use Throwable;
interface IncompatibleWithGoogleAuthenticator extends Throwable
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace PragmaRX\Google2FA\Exceptions\Contracts;
use Throwable;
interface InvalidAlgorithm extends Throwable
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace PragmaRX\Google2FA\Exceptions\Contracts;
use Throwable;
interface InvalidCharacters extends Throwable
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace PragmaRX\Google2FA\Exceptions\Contracts;
use Throwable;
interface SecretKeyTooShort extends Throwable
{
}

View file

@ -0,0 +1,10 @@
<?php
namespace PragmaRX\Google2FA\Exceptions;
use Exception;
use PragmaRX\Google2FA\Exceptions\Contracts\Google2FA as Google2FAExceptionContract;
class Google2FAException extends Exception implements Google2FAExceptionContract
{
}

View file

@ -0,0 +1,11 @@
<?php
namespace PragmaRX\Google2FA\Exceptions;
use PragmaRX\Google2FA\Exceptions\Contracts\Google2FA as Google2FAExceptionContract;
use PragmaRX\Google2FA\Exceptions\Contracts\IncompatibleWithGoogleAuthenticator as IncompatibleWithGoogleAuthenticatorExceptionContract;
class IncompatibleWithGoogleAuthenticatorException extends Google2FAException implements Google2FAExceptionContract, IncompatibleWithGoogleAuthenticatorExceptionContract
{
protected $message = 'This secret key is not compatible with Google Authenticator.';
}

View file

@ -0,0 +1,11 @@
<?php
namespace PragmaRX\Google2FA\Exceptions;
use PragmaRX\Google2FA\Exceptions\Contracts\Google2FA as Google2FAExceptionContract;
use PragmaRX\Google2FA\Exceptions\Contracts\InvalidAlgorithm as InvalidAlgorithmExceptionContract;
class InvalidAlgorithmException extends Google2FAException implements Google2FAExceptionContract, InvalidAlgorithmExceptionContract
{
protected $message = 'Invalid HMAC algorithm.';
}

View file

@ -0,0 +1,11 @@
<?php
namespace PragmaRX\Google2FA\Exceptions;
use PragmaRX\Google2FA\Exceptions\Contracts\Google2FA as Google2FAExceptionContract;
use PragmaRX\Google2FA\Exceptions\Contracts\InvalidCharacters as InvalidCharactersExceptionContract;
class InvalidCharactersException extends Google2FAException implements Google2FAExceptionContract, InvalidCharactersExceptionContract
{
protected $message = 'Invalid characters in the base32 string.';
}

View file

@ -0,0 +1,11 @@
<?php
namespace PragmaRX\Google2FA\Exceptions;
use PragmaRX\Google2FA\Exceptions\Contracts\Google2FA as Google2FAExceptionContract;
use PragmaRX\Google2FA\Exceptions\Contracts\SecretKeyTooShort as SecretKeyTooShortExceptionContract;
class SecretKeyTooShortException extends Google2FAException implements Google2FAExceptionContract, SecretKeyTooShortExceptionContract
{
protected $message = 'Secret key is too short. Must be at least 16 base32 characters';
}

View file

@ -0,0 +1,495 @@
<?php
namespace PragmaRX\Google2FA;
use PragmaRX\Google2FA\Exceptions\InvalidAlgorithmException;
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
use PragmaRX\Google2FA\Support\Base32;
use PragmaRX\Google2FA\Support\Constants;
use PragmaRX\Google2FA\Support\QRCode;
class Google2FA
{
use QRCode;
use Base32;
/**
* Algorithm.
*
* @var string
*/
protected $algorithm = Constants::SHA1;
/**
* Length of the Token generated.
*
* @var int
*/
protected $oneTimePasswordLength = 6;
/**
* Interval between key regeneration.
*
* @var int
*/
protected $keyRegeneration = 30;
/**
* Secret.
*
* @var string
*/
protected $secret;
/**
* Window.
*
* @var int
*/
protected $window = 1; // Keys will be valid for 60 seconds
/**
* Find a valid One Time Password.
*
* @param string $secret
* @param string $key
* @param int|null $window
* @param int $startingTimestamp
* @param int $timestamp
* @param int|null $oldTimestamp
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return bool|int
*/
public function findValidOTP(
$secret,
$key,
$window,
$startingTimestamp,
$timestamp,
$oldTimestamp = null
) {
for (;
$startingTimestamp <= $timestamp + $this->getWindow($window);
$startingTimestamp++
) {
if (
hash_equals($this->oathTotp($secret, $startingTimestamp), $key)
) {
return is_null($oldTimestamp)
? true
: $startingTimestamp;
}
}
return false;
}
/**
* Generate the HMAC OTP.
*
* @param string $secret
* @param int $counter
*
* @return string
*/
protected function generateHotp($secret, $counter)
{
return hash_hmac(
$this->getAlgorithm(),
pack('N*', 0, $counter), // Counter must be 64-bit int
$secret,
true
);
}
/**
* Generate a digit secret key in base32 format.
*
* @param int $length
* @param string $prefix
*
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
*
* @return string
*/
public function generateSecretKey($length = 16, $prefix = '')
{
return $this->generateBase32RandomKey($length, $prefix);
}
/**
* Get the current one time password for a key.
*
* @param string $secret
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return string
*/
public function getCurrentOtp($secret)
{
return $this->oathTotp($secret, $this->getTimestamp());
}
/**
* Get the HMAC algorithm.
*
* @return string
*/
public function getAlgorithm()
{
return $this->algorithm;
}
/**
* Get key regeneration.
*
* @return int
*/
public function getKeyRegeneration()
{
return $this->keyRegeneration;
}
/**
* Get OTP length.
*
* @return int
*/
public function getOneTimePasswordLength()
{
return $this->oneTimePasswordLength;
}
/**
* Get secret.
*
* @param string|null $secret
*
* @return string
*/
public function getSecret($secret = null)
{
return is_null($secret) ? $this->secret : $secret;
}
/**
* Returns the current Unix Timestamp divided by the $keyRegeneration
* period.
*
* @return int
**/
public function getTimestamp()
{
return (int) floor(microtime(true) / $this->keyRegeneration);
}
/**
* Get a list of valid HMAC algorithms.
*
* @return array
*/
protected function getValidAlgorithms()
{
return [
Constants::SHA1,
Constants::SHA256,
Constants::SHA512,
];
}
/**
* Get the OTP window.
*
* @param null|int $window
*
* @return int
*/
public function getWindow($window = null)
{
return is_null($window) ? $this->window : $window;
}
/**
* Make a window based starting timestamp.
*
* @param int|null $window
* @param int $timestamp
* @param int|null $oldTimestamp
*
* @return mixed
*/
private function makeStartingTimestamp($window, $timestamp, $oldTimestamp = null)
{
return is_null($oldTimestamp)
? $timestamp - $this->getWindow($window)
: max($timestamp - $this->getWindow($window), $oldTimestamp + 1);
}
/**
* Get/use a starting timestamp for key verification.
*
* @param string|int|null $timestamp
*
* @return int
*/
protected function makeTimestamp($timestamp = null)
{
if (is_null($timestamp)) {
return $this->getTimestamp();
}
return (int) $timestamp;
}
/**
* Takes the secret key and the timestamp and returns the one time
* password.
*
* @param string $secret Secret key in binary form.
* @param int $counter Timestamp as returned by getTimestamp.
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return string
*/
public function oathTotp($secret, $counter)
{
if (strlen($secret) < 8) {
throw new SecretKeyTooShortException();
}
$secret = $this->base32Decode($this->getSecret($secret));
return str_pad(
$this->oathTruncate($this->generateHotp($secret, $counter)),
$this->getOneTimePasswordLength(),
'0',
STR_PAD_LEFT
);
}
/**
* Extracts the OTP from the SHA1 hash.
*
* @param string $hash
*
* @return string
**/
public function oathTruncate($hash)
{
$offset = ord($hash[strlen($hash) - 1]) & 0xF;
$temp = unpack('N', substr($hash, $offset, 4));
$temp = $temp[1] & 0x7FFFFFFF;
return substr(
(string) $temp,
-$this->getOneTimePasswordLength()
);
}
/**
* Remove invalid chars from a base 32 string.
*
* @param string $string
*
* @return string|null
*/
public function removeInvalidChars($string)
{
return preg_replace(
'/[^'.Constants::VALID_FOR_B32.']/',
'',
$string
);
}
/**
* Setter for the enforce Google Authenticator compatibility property.
*
* @param mixed $enforceGoogleAuthenticatorCompatibility
*
* @return $this
*/
public function setEnforceGoogleAuthenticatorCompatibility(
$enforceGoogleAuthenticatorCompatibility
) {
$this->enforceGoogleAuthenticatorCompatibility = $enforceGoogleAuthenticatorCompatibility;
return $this;
}
/**
* Set the HMAC hashing algorithm.
*
* @param mixed $algorithm
*
* @throws \PragmaRX\Google2FA\Exceptions\InvalidAlgorithmException
*
* @return \PragmaRX\Google2FA\Google2FA
*/
public function setAlgorithm($algorithm)
{
// Default to SHA1 HMAC algorithm
if (!in_array($algorithm, $this->getValidAlgorithms())) {
throw new InvalidAlgorithmException();
}
$this->algorithm = $algorithm;
return $this;
}
/**
* Set key regeneration.
*
* @param mixed $keyRegeneration
*/
public function setKeyRegeneration($keyRegeneration)
{
$this->keyRegeneration = $keyRegeneration;
}
/**
* Set OTP length.
*
* @param mixed $oneTimePasswordLength
*/
public function setOneTimePasswordLength($oneTimePasswordLength)
{
$this->oneTimePasswordLength = $oneTimePasswordLength;
}
/**
* Set secret.
*
* @param mixed $secret
*/
public function setSecret($secret)
{
$this->secret = $secret;
}
/**
* Set the OTP window.
*
* @param mixed $window
*/
public function setWindow($window)
{
$this->window = $window;
}
/**
* Verifies a user inputted key against the current timestamp. Checks $window
* keys either side of the timestamp.
*
* @param string $key User specified key
* @param string $secret
* @param null|int $window
* @param null|int $timestamp
* @param null|int $oldTimestamp
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return bool|int
*/
public function verify(
$key,
$secret,
$window = null,
$timestamp = null,
$oldTimestamp = null
) {
return $this->verifyKey(
$secret,
$key,
$window,
$timestamp,
$oldTimestamp
);
}
/**
* Verifies a user inputted key against the current timestamp. Checks $window
* keys either side of the timestamp.
*
* @param string $secret
* @param string $key User specified key
* @param int|null $window
* @param null|int $timestamp
* @param null|int $oldTimestamp
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return bool|int
*/
public function verifyKey(
$secret,
$key,
$window = null,
$timestamp = null,
$oldTimestamp = null
) {
$timestamp = $this->makeTimestamp($timestamp);
return $this->findValidOTP(
$secret,
$key,
$window,
$this->makeStartingTimestamp($window, $timestamp, $oldTimestamp),
$timestamp,
$oldTimestamp
);
}
/**
* Verifies a user inputted key against the current timestamp. Checks $window
* keys either side of the timestamp, but ensures that the given key is newer than
* the given oldTimestamp. Useful if you need to ensure that a single key cannot
* be used twice.
*
* @param string $secret
* @param string $key User specified key
* @param int|null $oldTimestamp The timestamp from the last verified key
* @param int|null $window
* @param int|null $timestamp
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return bool|int
*/
public function verifyKeyNewer(
$secret,
$key,
$oldTimestamp,
$window = null,
$timestamp = null
) {
return $this->verifyKey(
$secret,
$key,
$window,
$timestamp,
$oldTimestamp
);
}
}

View file

@ -0,0 +1,208 @@
<?php
namespace PragmaRX\Google2FA\Support;
use ParagonIE\ConstantTime\Base32 as ParagonieBase32;
use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException;
use PragmaRX\Google2FA\Exceptions\InvalidCharactersException;
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
trait Base32
{
/**
* Enforce Google Authenticator compatibility.
*/
protected $enforceGoogleAuthenticatorCompatibility = true;
/**
* Calculate char count bits.
*
* @param string $b32
*
* @return int
*/
protected function charCountBits($b32)
{
return strlen($b32) * 8;
}
/**
* Generate a digit secret key in base32 format.
*
* @param int $length
* @param string $prefix
*
* @throws \Exception
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return string
*/
public function generateBase32RandomKey($length = 16, $prefix = '')
{
$secret = $prefix ? $this->toBase32($prefix) : '';
$secret = $this->strPadBase32($secret, $length);
$this->validateSecret($secret);
return $secret;
}
/**
* Decodes a base32 string into a binary string.
*
* @param string $b32
*
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*
* @return string
*/
public function base32Decode($b32)
{
$b32 = strtoupper($b32);
$this->validateSecret($b32);
return ParagonieBase32::decodeUpper($b32);
}
/**
* Check if the string length is power of two.
*
* @param string $b32
*
* @return bool
*/
protected function isCharCountNotAPowerOfTwo($b32)
{
return (strlen($b32) & (strlen($b32) - 1)) !== 0;
}
/**
* Pad string with random base 32 chars.
*
* @param string $string
* @param int $length
*
* @throws \Exception
*
* @return string
*/
private function strPadBase32($string, $length)
{
for ($i = 0; $i < $length; $i++) {
$string .= substr(
Constants::VALID_FOR_B32_SCRAMBLED,
$this->getRandomNumber(),
1
);
}
return $string;
}
/**
* Encode a string to Base32.
*
* @param string $string
*
* @return string
*/
public function toBase32($string)
{
$encoded = ParagonieBase32::encodeUpper($string);
return str_replace('=', '', $encoded);
}
/**
* Get a random number.
*
* @param int $from
* @param int $to
*
* @throws \Exception
*
* @return int
*/
protected function getRandomNumber($from = 0, $to = 31)
{
return random_int($from, $to);
}
/**
* Validate the secret.
*
* @param string $b32
*
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
* @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
*/
protected function validateSecret($b32)
{
$this->checkForValidCharacters($b32);
$this->checkGoogleAuthenticatorCompatibility($b32);
$this->checkIsBigEnough($b32);
}
/**
* Check if the secret key is compatible with Google Authenticator.
*
* @param string $b32
*
* @throws IncompatibleWithGoogleAuthenticatorException
*/
protected function checkGoogleAuthenticatorCompatibility($b32)
{
if (
$this->enforceGoogleAuthenticatorCompatibility &&
$this->isCharCountNotAPowerOfTwo($b32) // Google Authenticator requires it to be a power of 2 base32 length string
) {
throw new IncompatibleWithGoogleAuthenticatorException();
}
}
/**
* Check if all secret key characters are valid.
*
* @param string $b32
*
* @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
*/
protected function checkForValidCharacters($b32)
{
if (
preg_replace('/[^'.Constants::VALID_FOR_B32.']/', '', $b32) !==
$b32
) {
throw new InvalidCharactersException();
}
}
/**
* Check if secret key length is big enough.
*
* @param string $b32
*
* @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
*/
protected function checkIsBigEnough($b32)
{
// Minimum = 128 bits
// Recommended = 160 bits
// Compatible with Google Authenticator = 256 bits
if (
$this->charCountBits($b32) < 128
) {
throw new SecretKeyTooShortException();
}
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace PragmaRX\Google2FA\Support;
class Constants
{
/**
* Characters valid for Base 32.
*/
const VALID_FOR_B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
/**
* Characters valid for Base 32, scrambled.
*/
const VALID_FOR_B32_SCRAMBLED = '234567QWERTYUIOPASDFGHJKLZXCVBNM';
/**
* SHA1 algorithm.
*/
const SHA1 = 'sha1';
/**
* SHA256 algorithm.
*/
const SHA256 = 'sha256';
/**
* SHA512 algorithm.
*/
const SHA512 = 'sha512';
}

View file

@ -0,0 +1,34 @@
<?php
namespace PragmaRX\Google2FA\Support;
trait QRCode
{
/**
* Creates a QR code url.
*
* @param string $company
* @param string $holder
* @param string $secret
*
* @return string
*/
public function getQRCodeUrl($company, $holder, $secret)
{
return 'otpauth://totp/'.
rawurlencode($company).
':'.
rawurlencode($holder).
'?secret='.
$secret.
'&issuer='.
rawurlencode($company).
'&algorithm='.
rawurlencode(strtoupper($this->getAlgorithm())).
'&digits='.
rawurlencode(strtoupper((string) $this->getOneTimePasswordLength())).
'&period='.
rawurlencode(strtoupper((string) $this->getKeyRegeneration())).
'';
}
}