Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/test export-ignore
/phpunit.xml export-ignore
/psalm.xml export-ignore
28 changes: 15 additions & 13 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
name: PHP Composer

on:
push: ~
pull_request: ~

on: [push, pull_request]
jobs:
build:
name: Run tests on ${{ matrix.php }}
runs-on: ubuntu-latest

strategy:
matrix:
php: [ '7.2', '7.3', '7.4', '8.0' ]
php: [ '7.4', '8.0', '8.1', '8.2' ]

steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
- name: Checkout
uses: actions/checkout@v3

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2, psalm
tools: psalm

- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- run: composer install --no-progress --prefer-dist --no-suggest
- name: Install Composer dependencies
run: composer install --no-progress

- run: psalm --output-format=github
if: ${{ matrix.php == '8.0' }}
- name: Run Psalm
run: psalm --output-format=github
if: ${{ matrix.php == '8.2' }}

- run: vendor/bin/phpunit
- name: Run PHPUnit
run: vendor/bin/phpunit
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.idea/
/vendor/
/composer.lock
.phpunit.result.cache
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [Unreleased]
### Added
- Support for PHP 8.
- Native property type declarations.
- `GoogleAuth::getQRCode()` and `GoogleAuth::makeQRCodeMessage()` methods.
- HiddenString support for secret key.
- `GoogleAuth->defaultQRCodeSize` property (replaces the removed width and height properties).

### Changed
- PHP 7.4+ is now required.
- Renamed `FIDOU2F` class to `OneTime`.
- Updated [BaconQrCode](https://github.com/Bacon/BaconQrCode) dependency to v2.
This version has a slightly different API for rendering QR code images.
- Test files are now excluded from Composer package.
- Unified internal code for HOTP value generation.

### Removed
- `GoogleAuth->defaultQRCodeWidth` and `GoogleAuth->defaultQRCodeHeight` properties.
- Unused internal `rawOutput` option.


## [0.2.2] - 2016-06-17
### Changed
- Appended HTTP query string in QR code.


## [0.2.1] - 2016-06-17
### Changed
- `TOTP` and `HOTP` classes now implement `OTPInterface`.


## [0.2.0] - 2016-06-16
### Added
- Support for HOTP and Google Authenticator.
- Range check to ensure that code length is between 1 and 10.

### Changed
- Replaced giant switch statement with `**` operator.
- Improved readme.


## [0.1.0] - 2016-06-13
- Initial pre-release


[Unreleased]: https://github.com/paragonie/multi_factor/compare/v0.2.2...HEAD
[0.2.2]: https://github.com/paragonie/multi_factor/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/paragonie/multi_factor/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/paragonie/multi_factor/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/paragonie/multi_factor/tree/v0.1.0
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ needs.

## Requirements

* PHP 7.2+
* PHP 7.4+
* As per [Paragon Initiative Enterprise's commitment to open source](https://paragonie.com/blog/2016/04/go-php-7-our-commitment-maintaining-our-open-source-projects),
all new software will no longer be written for PHP 5.

Expand All @@ -22,12 +22,23 @@ composer require paragonie/multi-factor

## Example Usage

### Display QR code

```php
<?php
use ParagonIE\MultiFactor\OneTime;
use ParagonIE\MultiFactor\OTP\TOTP;
use ParagonIE\MultiFactor\Vendor\GoogleAuth;

$seed = random_bytes(20);
$auth = new GoogleAuth($seed);
$auth->makeQRCode(null, 'php://output', '[email protected]', 'Issuer', 'Label');
```

### Validate two-factor code

```php
<?php
use ParagonIE\MultiFactor\OneTime;
use ParagonIE\MultiFactor\OTP\TOTP;

// You can use TOTP or HOTP
$otp = new OneTime($seed, new TOTP());
Expand Down
10 changes: 5 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
"source": "https://github.com/paragonie/multi_factor"
},
"require": {
"php": ">=7.2",
"php": ">=7.4",
"bacon/bacon-qr-code": "^2",
"paragonie/constant_time_encoding": "^2",
"paragonie/hidden-string": "^1"
"paragonie/hidden-string": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^8",
"psalm/plugin-phpunit": "^0.15",
"vimeo/psalm": "^4.4"
"phpunit/phpunit": "^9.5",
"psalm/plugin-phpunit": "^0.18.4",
"vimeo/psalm": "^5.4"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
Expand Down
5 changes: 0 additions & 5 deletions src/MultiFactorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,11 @@ interface MultiFactorInterface
{
/**
* This should return a one-time password the user should enter.
*
* @return string
*/
public function generateCode(): string;

/**
* This should validate a code for a particular user.
*
* @param string $code
* @return bool
*/
public function validateCode(string $code): bool;
}
98 changes: 43 additions & 55 deletions src/OTP/HOTP.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,15 @@
*/
class HOTP implements OTPInterface
{
/**
* @var string
*/
protected $algo;
protected string $algo;
protected int $length;

/**
* @var int
*/
protected $length;

/**
* HOTP constructor.
*
* @param int $length How many digits should each HOTP be?
* @param string $algo Hash function to use
*/
public function __construct(
int $length = 6,
string $algo = 'sha1'
) {
public function __construct(int $length = 6, string $algo = 'sha1')
{
$this->length = $length;
$this->algo = $algo;
}
Expand All @@ -47,24 +36,54 @@ public function __construct(
* @return string
* @throws \OutOfRangeException
*/
public function getCode(
$sharedSecret,
int $counterValue
): string {
if ($this->length < 1 || $this->length > 10) {
public function getCode($sharedSecret, int $counterValue): string
{
$key = is_string($sharedSecret) ? $sharedSecret : $sharedSecret->getString();
$msg = $this->getTValue($counterValue);
return self::generateHOTPValue($this->length, $key, $this->algo, $msg);
}

public function getLength(): int
{
return $this->length;
}

/**
* Get the binary T value
*/
protected function getTValue(int $counter): string
{
$hex = \str_pad(
\dechex($counter),
16,
'0',
STR_PAD_LEFT
);

return Hex::decode($hex);
}

/**
* @internal
* @ref https://tools.ietf.org/html/rfc4226
*/
public static function generateHOTPValue(int $length, string $key, string $algo, string $data): string
{
if ($length < 1 || $length > 10) {
throw new \OutOfRangeException(
'Length must be between 1 and 10, as a consequence of RFC 6238.'
);
}
$msg = $this->getTValue($counterValue, true);
$bytes = \hash_hmac($this->algo, $msg, is_string($sharedSecret) ? $sharedSecret : $sharedSecret->getString(), true);

$bytes = \hash_hmac($algo, $data, $key, true);
$byteLen = Binary::safeStrlen($bytes);

// Per the RFC
/** @var int $offset */
$offset = \unpack('C', $bytes[$byteLen - 1])[1];
$offset &= 0x0f;

/** @var array{0: int, 1: int, 2: int, 3: int} $unpacked */
$unpacked = \array_values(
\unpack('C*', Binary::safeSubstr($bytes, $offset, 4))
);
Expand All @@ -76,44 +95,13 @@ public function getCode(
| (($unpacked[3] & 0xff) )
);

$intValue %= 10 ** $this->length;
$intValue %= 10 ** $length;

return \str_pad(
(string) $intValue,
$this->length,
$length,
'0',
\STR_PAD_LEFT
);
}

/**
* @return int
*/
public function getLength(): int
{
return $this->length;
}

/**
* Get the binary T value
*
* @param int $unixTimestamp
* @param bool $rawOutput
* @return string
*/
protected function getTValue(
int $counter,
bool $rawOutput = false
): string {
$hex = \str_pad(
\dechex($counter),
16,
'0',
STR_PAD_LEFT
);
if ($rawOutput) {
return Hex::decode($hex);
}
return $hex;
}
}
Loading