OCRA::generateOCRA()   F
last analyzed

Complexity

Conditions 44
Paths > 20000

Size

Total Lines 197
Code Lines 104

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 88
CRAP Score 49.9757

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 104
c 5
b 0
f 0
dl 0
loc 197
ccs 88
cts 103
cp 0.8544
rs 0
cc 44
nc 196615
nop 7
crap 49.9757

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php 
2
/**
3
 * This file is part of the ocra-implementations package.
4
 *
5
 * More information: https://github.com/SURFnet/ocra-implementations/
6
 *
7
 * @author Ivo Jansch <[email protected]>
8
 * 
9
 * @license See the LICENSE file in the source distribution
10
 */
11
12
/**
13
 * This a PHP port of the example implementation of the 
14
 * OATH OCRA algorithm.
15
 * Visit www.openauthentication.org for more information.
16
 *
17
 * @author Johan Rydell, PortWise (original Java)
18
 * @author Ivo Jansch, Egeniq (PHP port)
19
 */
20
class OCRA {
21
22
    private function __construct() {
23
        
24
    }
25
26
    /**
27
     * This method uses the hmac_hash function to provide the crypto
28
     * algorithm.
29
     * HMAC computes a Hashed Message Authentication Code with the
30
     * crypto hash algorithm as a parameter.
31
     *
32
     * @param String crypto     the crypto algorithm (sha1, sha256 or sha512)
33
     * @param String keyBytes   the bytes to use for the HMAC key
34
     * @param String text       the message or text to be authenticated.
0 ignored issues
show
Bug introduced by
The type text was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
35
     * @throws Exception
36
     */
37 116
    private static function _hmac(string $crypto, string $keyBytes, string $text) : string
38
    {
39 116
         $hash = hash_hmac($crypto, $text, $keyBytes);
40 116
         if (false === $hash) {
41
             throw new Exception("calculating hash_hmac failed");
42
         }
43 116
         return $hash;
44
    }
45
46
    /**
47
     * This method converts HEX string to Byte[]
48
     *
49
     * @param string $hex The hex string to decode
50
     * @param int $maxBytes The maximum length of the resulting decoded string
51
     * @param string $parameterName A descriptive name for the $hex parameter, used in exception error message
52
     * @return String a string with the decoded raw bytes of $hex
53
     * @throws InvalidArgumentException
54
     *
55
     * The length of the returned string will always be length($hex)/2 bytes
56
     * Note that $maxBytes is the max length of the returned string, not the max number of hex digits in $hex
57
     */
58 136
    private static function _hexStr2Bytes(string $hex, int $maxBytes, string $parameterName) : string
59
    {
60 136
        $len = strlen($hex);
61 136
        if ( ($len !== 0) && (! ctype_xdigit($hex)) ) {
62 7
            throw new InvalidArgumentException("Parameter '$parameterName' contains non hex digits");
63
        }
64 133
        if ( $len % 2 !== 0 ) {
65 1
            throw new InvalidArgumentException("Parameter '$parameterName' contains odd number of hex digits");
66
        }
67 132
        if ( $len > $maxBytes * 2) {
68 6
            throw new InvalidArgumentException("Parameter '$parameterName' too long");
69
        }
70
        // hex2bin logs PHP warnings when $hex contains invalid characters or has uneven length. Because we
71
        // check for these conditions above hex2bin() should always be silent
72 129
        $res=hex2bin($hex);
73 129
        if (false === $res) {
74
            throw new InvalidArgumentException("Parameter '$parameterName' could not be decoded");
75
        }
76 129
        return $res;
77
    }
78
79
80
    /**
81
     * Calculate the OCRA (RFC 6287) response for the given set of parameters
82
     * This implementation uses the same interface as the Java reference implementation from the RFC
83
     *
84
     * @param string $ocraSuite the OCRASuite
85
     * @param string $key the shared OCRA secret, HEX encoded
86
     * @param string $counter the counter (C), HEX encoded
87
     * @param string $question the challenge question (Q), HEX encoded
88
     * @param string $password the Hashed version of PIN/password (P), HEX encoded
89
     * @param string $sessionInformation the session information (S), HEX encoded
90
     * @param string $timeStamp the timestamp (T), HEX encoded
91
     *
92
     * @return string The Response, A numeric String in base 10 that includes number of digits specified in the OCRASuite
93
     * @throws InvalidArgumentException|Exception InvalidArgumentException is thrown when a HEX encoded parameter does not contain hex or when it exceeds its maximum length
94
     *
95
     * Note: The OCRA secret and the parameters C, Q, P, S and T must be provided as HEX encoded strings.
96
     *       How each parameter must be encoded is specified in the RFC
97
     *
98
     * In addition to the RFC reference implementation this implementation supports using "-S" in OCRASuite as an
99
     * alternative to "-S064"
100
     */
101 139
    static function generateOCRA(string $ocraSuite,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
102
                                 string $key,
103
                                 string $counter,
104
                                 string $question,
105
                                 string $password,
106
                                 string $sessionInformation,
107
                                 string $timeStamp) : string
108
    {
109 139
        $codeDigits = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $codeDigits is dead and can be removed.
Loading history...
110 139
        $crypto = "";
111 139
        $result = null;
112 139
        $ocraSuiteLength = strlen($ocraSuite);
113 139
        $counterLength = 0;
114 139
        $questionLength = 0;
115 139
        $passwordLength = 0;
116
117 139
        $sessionInformationLength = 0;
118 139
        $timeStampLength = 0;
119
120
        // Parse the cryptofunction
121
        // The cryptofucntion is defined as HOTP-<hash function>-<t>, where
122
        // <hash function> is one of sha1, sha256 or sha512
123
        // <t> is the truncation length: 0 (no truncation), 4-10
124 139
        $components = explode(":", $ocraSuite);
125 139
        $cryptoFunction = $components[1];
126 139
        $dataInput = strtolower($components[2]); // lower here so we can do case insensitive comparisons
127
128 139
        if(stripos($cryptoFunction, "hotp-sha1")!==false)
129 99
            $crypto = "sha1";
130 40
        elseif(stripos($cryptoFunction, "hotp-sha256")!==false)
131 17
            $crypto = "sha256";
132 23
        elseif(stripos($cryptoFunction, "hotp-sha512")!==false)
133 19
            $crypto = "sha512";
134
        else {
135 4
            throw new InvalidArgumentException('Unsupported OCRA CryptoFunction');
136
        }
137
138
        // The Cryptofucntion must ha a truncation of 0, 4-10
139 135
        $codeDigits_str = substr($cryptoFunction, strrpos($cryptoFunction, "-")+1);
140 135
        if (! ctype_digit($codeDigits_str)) {
141 3
            throw new InvalidArgumentException('Unsupported OCRA CryptoFunction');
142
        }
143 132
        $codeDigits = (integer)$codeDigits_str;
144 132
        if (($codeDigits != 0) && (($codeDigits < 4) || ($codeDigits > 10))) {
145 5
            throw new InvalidArgumentException('Unsupported OCRA CryptoFunction');
146
        }
147
                
148
        // The size of the byte array message to be encrypted
149
        // Counter
150 127
        if($dataInput[0] == "c" ) {
151
            // Fix the length of the HEX string
152 24
            while(strlen($counter) < 16)
153 23
                $counter = "0" . $counter;
154 24
            $counterLength=8;
155
        }
156
        // Question
157 127
        if($dataInput[0] == "q" ||
158 127
                stripos($dataInput, "-q")!==false) {
159 127
            while(strlen($question) < 256)
160 126
                $question = $question . "0";
161 127
            $questionLength=128;
162
        }
163
164
        // Password
165 127
        if(stripos($dataInput, "psha1")!==false) {
166 17
            while(strlen($password) < 40)
167 1
                $password = "0" . $password;
168 17
            $passwordLength=20;
169
        }
170
    
171 127
        if(stripos($dataInput, "psha256")!==false) {
172
            while(strlen($password) < 64)
173
                $password = "0" . $password;
174
            $passwordLength=32;
175
        }
176
        
177 127
        if(stripos($dataInput, "psha512")!==false) {
178
            while(strlen($password) < 128)
179
                $password = "0" . $password;
180
            $passwordLength=64;
181
        }
182
        
183
        // sessionInformation
184 127
        if(stripos($dataInput, "s064") !==false) {
185 32
            while(strlen($sessionInformation) < 128)
186 32
                $sessionInformation = "0" . $sessionInformation;
187
188 32
            $sessionInformationLength=64;
189 95
        } else if(stripos($dataInput, "s128") !==false) {
190
            while(strlen($sessionInformation) < 256)
191
                $sessionInformation = "0" . $sessionInformation;
192
        
193
            $sessionInformationLength=128;
194 95
        } else if(stripos($dataInput, "s256") !==false) {
195
            while(strlen($sessionInformation) < 512)
196
                $sessionInformation = "0" . $sessionInformation;
197
        
198
            $sessionInformationLength=256;
199 95
        } else if(stripos($dataInput, "s512") !==false) {
200
            while(strlen($sessionInformation) < 128)
201
                $sessionInformation = "0" . $sessionInformation;
202
        
203
            $sessionInformationLength=64;
204 95
        } else if (stripos($dataInput, "-s") !== false ) {
205
            // deviation from spec. Officially 's' without a length indicator is not in the reference implementation.
206
            // RFC is ambigious. However we have supported this in Tiqr since day 1, so we continue to support it.
207
208
            // See the format of the datainput below. "[]" denotes optional.
209
            // Because Q is mandatory, s will always be preceded by the separator "-". Matching "-s" is required
210
            // to prevent matching the "s" in the password input e.g. "psha1".
211
            // [C] | QFxx | [PH | Snnn | TG] : Challenge-Response computation
212
            // [C] | QFxx | [PH | TG] : Plain Signature computation
213 49
            while(strlen($sessionInformation) < 128)
214 48
                $sessionInformation = "0" . $sessionInformation;
215
            
216 49
            $sessionInformationLength=64;
217
        }
218
        
219
        
220
             
221
        // TimeStamp
222 127
        if($dataInput[0] == "t" ||
223 127
                stripos($dataInput, "-t") !== false) {
224 7
            while(strlen($timeStamp) < 16)
225 6
                $timeStamp = "0" . $timeStamp;
226 7
            $timeStampLength=8;
227
        }
228
229
        // Put the bytes of "ocraSuite" parameters into the message
230
        
231 127
        $msg = array_fill(0,$ocraSuiteLength+$counterLength+$questionLength+$passwordLength+$sessionInformationLength+$timeStampLength+1, 0);
232
                
233 127
        for($i=0;$i<strlen($ocraSuite);$i++) {
234 127
            $msg[$i] = $ocraSuite[$i];
235
        }
236
        
237
        // Delimiter
238 127
        $msg[strlen($ocraSuite)] = "\0";
239
240
        // Put the bytes of "Counter" to the message
241
        // Input is HEX encoded
242 127
        if($counterLength > 0 ) {
243 24
            $bArray = self::_hexStr2Bytes($counter, $counterLength, 'counter');
244 22
            for ($i=0;$i<strlen($bArray);$i++) {
245 22
                $msg [$i + $ocraSuiteLength + 1] = $bArray[$i];
246
            }
247
        }
248
249
250
        // Put the bytes of "question" to the message
251
        // Input is text encoded
252 125
        if($questionLength > 0 ) {
253 125
            $bArray = self::_hexStr2Bytes($question, $questionLength, 'question');
254 123
            for ($i=0;$i<strlen($bArray);$i++) {
255 123
                $msg [$i + $ocraSuiteLength + 1 + $counterLength] = $bArray[$i];
256
            }
257
        }
258
259
        // Put the bytes of "password" to the message
260
        // Input is HEX encoded
261 123
        if($passwordLength > 0){
262 17
            $bArray = self::_hexStr2Bytes($password, $passwordLength, 'password');
263 15
            for ($i=0;$i<strlen($bArray);$i++) {
264 15
                $msg [$i + $ocraSuiteLength + 1 + $counterLength + $questionLength] = $bArray[$i];
265
            }
266
        }
267
268
        // Put the bytes of "sessionInformation" to the message
269
        // Input is HEX encoded
270 121
        if($sessionInformationLength > 0 ){
271 79
            $bArray = self::_hexStr2Bytes($sessionInformation, $sessionInformationLength, 'sessionInformation');
272 77
            for ($i=0;$i<strlen($bArray);$i++) {
273 77
                $msg [$i + $ocraSuiteLength + 1 + $counterLength + $questionLength + $passwordLength] = $bArray[$i];
274
            }
275
        }
276
277
        // Put the bytes of "time" to the message
278
        // Input is HEX encoded value of minutes
279 119
        if($timeStampLength > 0){
280 7
            $bArray = self::_hexStr2Bytes($timeStamp, $timeStampLength, 'timeStamp');
281 5
            for ($i=0;$i<strlen($bArray);$i++) {
282 5
                $msg [$i + $ocraSuiteLength + 1 + $counterLength + $questionLength + $passwordLength + $sessionInformationLength] = $bArray[$i];
283
            }
284
        }
285
        
286 117
        $byteKey = self::_hexStr2Bytes($key, strlen($key)/2, 'key');
287
              
288 116
        $msg = implode("", $msg);
289
290 116
        $hash = self::_hmac($crypto, $byteKey, $msg);
291
292 116
        if ($codeDigits == 0)
293 1
            $result = $hash;
294
        else
295 115
            $result = self::_oath_truncate($hash, $codeDigits);
296
             
297 116
        return $result;
298
    }
299
300
    /**
301
     * Implementation of the Truncate function from RFC4226
302
     * Truncate a hex encoded (hash) result to a string of digital digits of $length
303
     *
304
     * @param string $hash hex encoded (hash) value to truncate. Minimum length is 20 bytes (i.e. a string of 40 hex digits)
305
     * @param int $length number of decimal digits to truncate to
306
     * @return string of $length digits
307
     */    
308 116
    static function _oath_truncate(string $hash, int $length = 6) : string
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
309
    {
310
        // Convert to dec
311 116
        foreach(str_split($hash,2) as $hex)
312
        {
313 116
            $hmac_result[]=hexdec($hex);
314
        }
315
    
316
        // Find offset
317 116
        $offset = $hmac_result[count($hmac_result) - 1] & 0xf;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $hmac_result seems to be defined by a foreach iteration on line 311. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
318
    
319 116
        $v = strval(
320 116
            (($hmac_result[$offset+0] & 0x7f) << 24 ) |
321 116
            (($hmac_result[$offset+1] & 0xff) << 16 ) |
322 116
            (($hmac_result[$offset+2] & 0xff) << 8 ) |
323 116
            ($hmac_result[$offset+3] & 0xff)
324 116
        );
325
326
        // Prefix truncated string with 0's to ensure it always has the required length
327 116
        $v=str_pad($v, $length, "0", STR_PAD_LEFT);
328
329 116
        $v = substr($v, strlen($v) - $length);
330 116
        return $v;
331
    }
332
    
333
}
334