Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 1 Rev 1441
Línea 1... Línea 1...
1
<?php
1
<?php
Línea 2... Línea 2...
2
 
2
 
Línea 3... Línea -...
3
declare(strict_types=1);
-
 
4
 
-
 
5
/*
-
 
6
 * The MIT License (MIT)
-
 
7
 *
-
 
8
 * Copyright (c) 2014-2018 Spomky-Labs
-
 
9
 *
-
 
10
 * This software may be modified and distributed under the terms
-
 
11
 * of the MIT license.  See the LICENSE file for details.
-
 
12
 */
3
declare(strict_types=1);
Línea 13... Línea 4...
13
 
4
 
-
 
5
namespace OTPHP;
14
namespace OTPHP;
6
 
-
 
7
use Exception;
-
 
8
use InvalidArgumentException;
-
 
9
use ParagonIE\ConstantTime\Base32;
-
 
10
use RuntimeException;
-
 
11
use function assert;
-
 
12
use function chr;
Línea 15... Línea 13...
15
 
13
use function count;
16
use Assert\Assertion;
14
use function is_string;
17
use ParagonIE\ConstantTime\Base32;
15
use const STR_PAD_LEFT;
Línea -... Línea 16...
-
 
16
 
-
 
17
abstract class OTP implements OTPInterface
18
 
18
{
19
abstract class OTP implements OTPInterface
-
 
20
{
-
 
21
    use ParameterTrait;
19
    use ParameterTrait;
22
 
-
 
23
    /**
-
 
24
     * OTP constructor.
20
 
25
     *
21
    private const DEFAULT_SECRET_SIZE = 64;
26
     * @param string|null $secret
22
 
27
     * @param string      $digest
23
    /**
28
     * @param int         $digits
-
 
29
     */
-
 
30
    protected function __construct($secret, string $digest, int $digits)
24
     * @param non-empty-string $secret
Línea 31... Línea -...
31
    {
-
 
32
        $this->setSecret($secret);
-
 
33
        $this->setDigest($digest);
-
 
34
        $this->setDigits($digits);
25
     */
35
    }
26
    protected function __construct(string $secret)
36
 
27
    {
Línea 37... Línea 28...
37
    /**
28
        $this->setSecret($secret);
38
     * {@inheritdoc}
29
    }
Línea 39... Línea 30...
39
     */
30
 
40
    public function getQrCodeUri(string $uri = 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl={PROVISIONING_URI}', string $placeholder = '{PROVISIONING_URI}'): string
31
    public function getQrCodeUri(string $uri, string $placeholder): string
41
    {
-
 
42
        $provisioning_uri = urlencode($this->getProvisioningUri());
-
 
43
 
32
    {
44
        return str_replace($placeholder, $provisioning_uri, $uri);
33
        $provisioning_uri = urlencode($this->getProvisioningUri());
45
    }
34
 
46
 
-
 
47
    /**
-
 
48
     * @param int $input
-
 
49
     *
35
        return str_replace($placeholder, $provisioning_uri, $uri);
50
     * @return string The OTP at the specified input
36
    }
51
     */
-
 
52
    protected function generateOTP(int $input): string
-
 
53
    {
-
 
Línea -... Línea 37...
-
 
37
 
-
 
38
    /**
-
 
39
     * @param 0|positive-int $input
-
 
40
     */
-
 
41
    public function at(int $input): string
54
        $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret());
42
    {
55
        $hmac = [];
43
        return $this->generateOTP($input);
Línea 56... Línea 44...
56
        foreach (str_split($hash, 2) as $hex) {
44
    }
-
 
45
 
-
 
46
    /**
-
 
47
     * @return non-empty-string
-
 
48
     */
57
            $hmac[] = hexdec($hex);
49
    final protected static function generateSecret(): string
58
        }
50
    {
59
        $offset = $hmac[count($hmac) - 1] & 0xF;
51
        return Base32::encodeUpper(random_bytes(self::DEFAULT_SECRET_SIZE));
60
        $code = ($hmac[$offset + 0] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
52
    }
-
 
53
 
-
 
54
    /**
-
 
55
     * The OTP at the specified input.
-
 
56
     *
-
 
57
     * @param 0|positive-int $input
-
 
58
     *
-
 
59
     * @return non-empty-string
61
        $otp = $code % pow(10, $this->getDigits());
60
     */
-
 
61
    protected function generateOTP(int $input): string
-
 
62
    {
62
 
63
        $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true);
Línea 63... Línea 64...
63
        return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);
64
        $unpacked = unpack('C*', $hash);
64
    }
65
        $unpacked !== false || throw new InvalidArgumentException('Invalid data.');
65
 
66
        $hmac = array_values($unpacked);
66
    /**
67
 
67
     * {@inheritdoc}
68
        $offset = ($hmac[count($hmac) - 1] & 0xF);
-
 
69
        $code = ($hmac[$offset] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
-
 
70
        $otp = $code % (10 ** $this->getDigits());
-
 
71
 
-
 
72
        return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);
68
     */
73
    }
69
    public function at(int $timestamp): string
74
 
70
    {
75
    /**
71
        return $this->generateOTP($timestamp);
76
     * @param array<non-empty-string, mixed> $options
72
    }
77
     */
Línea 73... Línea 78...
73
 
78
    protected function filterOptions(array &$options): void
74
    /**
79
    {
Línea 75... Línea 80...
75
     * @param array $options
80
        foreach ([
76
     */
81
            'algorithm' => 'sha1',
77
    protected function filterOptions(array &$options)
82
            'period' => 30,
78
    {
83
            'digits' => 6,
79
        foreach (['algorithm' => 'sha1', 'period' => 30, 'digits' => 6] as $key => $default) {
84
        ] as $key => $default) {
80
            if (isset($options[$key]) && $default === $options[$key]) {
85
            if (isset($options[$key]) && $default === $options[$key]) {
81
                unset($options[$key]);
86
                unset($options[$key]);
82
            }
87
            }
83
        }
88
        }
84
 
89
 
85
        ksort($options);
90
        ksort($options);
86
    }
91
    }
87
 
92
 
88
    /**
93
    /**
Línea -... Línea 94...
-
 
94
     * @param non-empty-string $type
-
 
95
     * @param array<non-empty-string, mixed> $options
-
 
96
     *
89
     * @param string $type
97
     * @return non-empty-string
-
 
98
     */
-
 
99
    protected function generateURI(string $type, array $options): string
90
     * @param array  $options
100
    {
Línea 91... Línea 101...
91
     *
101
        $label = $this->getLabel();
-
 
102
        is_string($label) || throw new InvalidArgumentException('The label is not set.');
-
 
103
        $this->hasColon($label) === false || throw new InvalidArgumentException('Label must not contain a colon.');
-
 
104
        $options = [...$options, ...$this->getParameters()];
-
 
105
        $this->filterOptions($options);
-
 
106
        $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options, '', '&'));
-
 
107
 
-
 
108
        return sprintf(
-
 
109
            'otpauth://%s/%s?%s',
-
 
110
            $type,
92
     * @return string
111
            rawurlencode(($this->getIssuer() !== null ? $this->getIssuer() . ':' : '') . $label),
93
     */
112
            $params
94
    protected function generateURI(string $type, array $options): string
113
        );
95
    {
114
    }
96
        $label = $this->getLabel();
115
 
97
        Assertion::string($label, 'The label is not set.');
116
    /**
98
        Assertion::false($this->hasColon($label), 'Label must not contain a colon.');
117
     * @param non-empty-string $safe
99
        $options = array_merge($options, $this->getParameters());
118
     * @param non-empty-string $user
100
        $this->filterOptions($options);
119
     */
-
 
120
    protected function compareOTP(string $safe, string $user): bool
Línea 101... Línea 121...
101
        $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options, '', '&'));
121
    {
102
 
122
        return hash_equals($safe, $user);
Línea 103... Línea -...
103
        return sprintf('otpauth://%s/%s?%s', $type, rawurlencode((null !== $this->getIssuer() ? $this->getIssuer().':' : '').$label), $params);
-
 
104
    }
-
 
105
 
-
 
106
    /**
-
 
107
     * @return string
-
 
108
     */
123
    }
109
    private function getDecodedSecret(): string
124
 
110
    {
125
    /**
111
        try {
126
     * @return non-empty-string
112
            $secret = Base32::decodeUpper($this->getSecret());
127
     */
113
        } catch (\Exception $e) {
128
    private function getDecodedSecret(): string
114
            throw new \RuntimeException('Unable to decode the secret. Is it correctly base32 encoded?');
129
    {
Línea 115... Línea 130...
115
        }
130
        try {
116
 
-
 
117
        return $secret;
-
 
118
    }
-
 
119
 
-
 
120
    /**
-
 
121
     * @param int $int
-
 
122
     *
-
 
123
     * @return string
-
 
124
     */
-
 
125
    private function intToByteString(int $int): string
-
 
126
    {
-
 
127
        $result = [];
131
            $decoded = Base32::decodeUpper($this->getSecret());
128
        while (0 !== $int) {
132
        } catch (Exception) {