Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
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
 */
13
 
14
namespace OTPHP;
15
 
16
use Assert\Assertion;
17
use ParagonIE\ConstantTime\Base32;
18
 
19
abstract class OTP implements OTPInterface
20
{
21
    use ParameterTrait;
22
 
23
    /**
24
     * OTP constructor.
25
     *
26
     * @param string|null $secret
27
     * @param string      $digest
28
     * @param int         $digits
29
     */
30
    protected function __construct($secret, string $digest, int $digits)
31
    {
32
        $this->setSecret($secret);
33
        $this->setDigest($digest);
34
        $this->setDigits($digits);
35
    }
36
 
37
    /**
38
     * {@inheritdoc}
39
     */
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
41
    {
42
        $provisioning_uri = urlencode($this->getProvisioningUri());
43
 
44
        return str_replace($placeholder, $provisioning_uri, $uri);
45
    }
46
 
47
    /**
48
     * @param int $input
49
     *
50
     * @return string The OTP at the specified input
51
     */
52
    protected function generateOTP(int $input): string
53
    {
54
        $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret());
55
        $hmac = [];
56
        foreach (str_split($hash, 2) as $hex) {
57
            $hmac[] = hexdec($hex);
58
        }
59
        $offset = $hmac[count($hmac) - 1] & 0xF;
60
        $code = ($hmac[$offset + 0] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
61
        $otp = $code % pow(10, $this->getDigits());
62
 
63
        return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);
64
    }
65
 
66
    /**
67
     * {@inheritdoc}
68
     */
69
    public function at(int $timestamp): string
70
    {
71
        return $this->generateOTP($timestamp);
72
    }
73
 
74
    /**
75
     * @param array $options
76
     */
77
    protected function filterOptions(array &$options)
78
    {
79
        foreach (['algorithm' => 'sha1', 'period' => 30, 'digits' => 6] as $key => $default) {
80
            if (isset($options[$key]) && $default === $options[$key]) {
81
                unset($options[$key]);
82
            }
83
        }
84
 
85
        ksort($options);
86
    }
87
 
88
    /**
89
     * @param string $type
90
     * @param array  $options
91
     *
92
     * @return string
93
     */
94
    protected function generateURI(string $type, array $options): string
95
    {
96
        $label = $this->getLabel();
97
        Assertion::string($label, 'The label is not set.');
98
        Assertion::false($this->hasColon($label), 'Label must not contain a colon.');
99
        $options = array_merge($options, $this->getParameters());
100
        $this->filterOptions($options);
101
        $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options, '', '&'));
102
 
103
        return sprintf('otpauth://%s/%s?%s', $type, rawurlencode((null !== $this->getIssuer() ? $this->getIssuer().':' : '').$label), $params);
104
    }
105
 
106
    /**
107
     * @return string
108
     */
109
    private function getDecodedSecret(): string
110
    {
111
        try {
112
            $secret = Base32::decodeUpper($this->getSecret());
113
        } catch (\Exception $e) {
114
            throw new \RuntimeException('Unable to decode the secret. Is it correctly base32 encoded?');
115
        }
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 = [];
128
        while (0 !== $int) {
129
            $result[] = chr($int & 0xFF);
130
            $int >>= 8;
131
        }
132
 
133
        return str_pad(implode(array_reverse($result)), 8, "\000", STR_PAD_LEFT);
134
    }
135
 
136
    /**
137
     * @param string $safe
138
     * @param string $user
139
     *
140
     * @return bool
141
     */
142
    protected function compareOTP(string $safe, string $user): bool
143
    {
144
        return hash_equals($safe, $user);
145
    }
146
}