Completed
Push — master ( 55b2fb...8df7d8 )
by Antonio Carlos
01:51
created

Base32::checkIsBigEnough()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 10
ccs 3
cts 3
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace PragmaRX\Google2FA\Support;
4
5
use ParagonIE\ConstantTime\Base32 as ParagonieBase32;
6
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
7
use PragmaRX\Google2FA\Exceptions\InvalidCharactersException;
8
use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException;
9
10
trait Base32
11
{
12
    /**
13
     * Enforce Google Authenticator compatibility.
14
     */
15
    protected $enforceGoogleAuthenticatorCompatibility = true;
16
17
    /**
18
     * Calculate char count bits.
19
     *
20
     * @param $b32
21
     * @return float|int
22
     */
23 17
    protected function charCountBits($b32)
24
    {
25 17
        return (strlen($b32) * 8);
26
    }
27
28
    /**
29
     * Generate a digit secret key in base32 format.
30
     *
31
     * @param int    $length
32
     * @param string $prefix
33
     *
34
     * @throws IncompatibleWithGoogleAuthenticatorException
35
     * @throws InvalidCharactersException
36
     *
37
     * @return string
38
     */
39 6
    public function generateBase32RandomKey($length = 16, $prefix = '')
40
    {
41 6
        $secret = $prefix ? $this->toBase32($prefix) : '';
42
43 6
        $secret = $this->strPadBase32($secret, $length);
44
45 6
        $this->validateSecret($secret);
46
47 4
        return $secret;
48
    }
49
50
    /**
51
     * Decodes a base32 string into a binary string.
52
     *
53
     * @param string $b32
54
     *
55
     * @throws InvalidCharactersException
56
     * @throws IncompatibleWithGoogleAuthenticatorException
57
     *
58
     * @return int
59
     */
60 13
    public function base32Decode($b32)
61
    {
62 13
        $b32 = strtoupper($b32);
63
64 13
        $this->validateSecret($b32);
65
66 8
        return ParagonieBase32::decodeUpper($b32);
67
    }
68
69
    /**
70
     * Check if the string length is power of two.
71
     *
72
     * @param $b32
73
     * @return bool
74
     */
75 14
    protected function isCharCountNotAPowerOfTwo($b32): bool
76
    {
77 14
        return (strlen($b32) & (strlen($b32) - 1)) !== 0;
78
    }
79
80
    /**
81
     * Pad string with random base 32 chars.
82
     *
83
     * @param $string
84
     * @param $length
85
     *
86
     * @throws \Exception
87
     *
88
     * @return string
89
     */
90 6
    private function strPadBase32($string, $length)
91
    {
92 6
        for ($i = 0; $i < $length; $i++) {
93 6
            $string .= substr(
94 6
                Constants::VALID_FOR_B32_SCRAMBLED,
95 6
                $this->getRandomNumber(),
96 6
                1
97
            );
98
        }
99
100 6
        return $string;
101
    }
102
103
    /**
104
     * Encode a string to Base32.
105
     *
106
     * @param $string
107
     *
108
     * @return mixed
109
     */
110 3
    public function toBase32($string)
111
    {
112 3
        $encoded = ParagonieBase32::encodeUpper($string);
113
114 3
        return str_replace('=', '', $encoded);
115
    }
116
117
    /**
118
     * Get a random number.
119
     *
120
     * @param $from
121
     * @param $to
122
     *
123
     * @throws \Exception
124
     *
125
     * @return int
126
     */
127 6
    protected function getRandomNumber($from = 0, $to = 31)
128
    {
129 6
        return random_int($from, $to);
130
    }
131
132
    /**
133
     * Validate the secret.
134
     *
135
     * @param $b32
136
     *
137
     * @throws IncompatibleWithGoogleAuthenticatorException
138
     * @throws InvalidCharactersException
139
     */
140 19
    protected function validateSecret($b32)
141
    {
142 19
        $this->checkForValidCharacters($b32);
143
144 19
        $this->checkGoogleAuthenticatorCompatibility($b32);
145
146 17
        $this->checkIsBigEnough($b32);
147 12
    }
148
149
    /**
150
     * Check if the secret key is compatible with Google Authenticator.
151
     *
152
     * @param $b32
153
     *
154
     * @throws IncompatibleWithGoogleAuthenticatorException
155
     */
156 19
    protected function checkGoogleAuthenticatorCompatibility($b32)
157
    {
158
        if (
159 19
            $this->enforceGoogleAuthenticatorCompatibility &&
160 19
            $this->isCharCountNotAPowerOfTwo($b32) // Google Authenticator requires it to be a power of 2 base32 length string
161
        ) {
162 2
            throw new IncompatibleWithGoogleAuthenticatorException();
163
        }
164 17
    }
165
166
    /**
167
     * Check if all secret key characters are valid.
168
     *
169
     * @param $b32
170
     *
171
     * @throws InvalidCharactersException
172
     */
173 19
    protected function checkForValidCharacters($b32)
174
    {
175
        if (
176 19
            preg_replace('/[^' . Constants::VALID_FOR_B32 . ']/', '', $b32) !==
177 19
            $b32
178
        ) {
179 1
            throw new InvalidCharactersException();
180
        }
181 19
    }
182
183
    /**
184
     * Check if secret key length is big enough
185
     *
186
     * @param $b32
187
     *
188
     * @throws InvalidCharactersException
189
     */
190 17
    protected function checkIsBigEnough($b32)
191
    {
192
        // Minimum = 128 bits
193
        // Recommended = 160 bits
194
        // Compatible with Google Authenticator = 256 bits
195
196
        if (
197 17
            $this->charCountBits($b32) < 128
198
        ) {
199 6
            throw new SecretKeyTooShortException();
200
        }
201 12
    }
202
}
203