Completed
Push — master ( 03f6fb...55b2fb )
by Antonio Carlos
01:58
created

Base32::charCountBits()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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