Passed
Push — master ( 9e488e...35fb8a )
by Pieter van der
04:52
created

OCRA::generateOCRA()   F

Complexity

Conditions 39
Paths > 20000

Size

Total Lines 180
Code Lines 94

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 80
CRAP Score 44.9879

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 94
c 4
b 0
f 0
dl 0
loc 180
ccs 80
cts 95
cp 0.8421
rs 0
cc 39
nc 262144
nop 7
crap 44.9879

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.
35
     * @throws Exception
36
     */
37 107
    private static function _hmac(string $crypto, string $keyBytes, string $text) : string
38
    {
39 107
         $hash = hash_hmac($crypto, $text, $keyBytes);
40 107
         if (false === $hash) {
41
             throw new Exception("calculating hash_hmac failed");
42
         }
43 107
         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 127
    private static function _hexStr2Bytes(string $hex, int $maxBytes, string $parameterName) : string
59
    {
60 127
        $len = strlen($hex);
61 127
        if ( ($len !== 0) && (! ctype_xdigit($hex)) ) {
62 7
            throw new InvalidArgumentException("Parameter '$parameterName' contains non hex digits");
63
        }
64 124
        if ( $len % 2 !== 0 ) {
65 1
            throw new InvalidArgumentException("Parameter '$parameterName' contains odd number of hex digits");
66
        }
67 123
        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 120
        $res=hex2bin($hex);
73 120
        if (false === $res) {
74
            throw new InvalidArgumentException("Parameter '$parameterName' could not be decoded");
75
        }
76 120
        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 118
    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 118
        $codeDigits = 0;
0 ignored issues
show
Unused Code introduced by
The assignment to $codeDigits is dead and can be removed.
Loading history...
110 118
        $crypto = "";
111 118
        $result = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
112 118
        $ocraSuiteLength = strlen($ocraSuite);
113 118
        $counterLength = 0;
114 118
        $questionLength = 0;
115 118
        $passwordLength = 0;
116
117 118
        $sessionInformationLength = 0;
118 118
        $timeStampLength = 0;
119
120
        // How many digits should we return
121 118
        $components = explode(":", $ocraSuite);
122 118
        $cryptoFunction = $components[1];
123 118
        $dataInput = strtolower($components[2]); // lower here so we can do case insensitive comparisons
124
        
125 118
        if(stripos($cryptoFunction, "sha1")!==false)
126 82
            $crypto = "sha1";
127 118
        if(stripos($cryptoFunction, "sha256")!==false)
128 17
            $crypto = "sha256";
129 118
        if(stripos($cryptoFunction, "sha512")!==false)
130 19
            $crypto = "sha512";
131
        
132 118
        $codeDigits = substr($cryptoFunction, strrpos($cryptoFunction, "-")+1);
133
                
134
        // The size of the byte array message to be encrypted
135
        // Counter
136 118
        if($dataInput[0] == "c" ) {
137
            // Fix the length of the HEX string
138 24
            while(strlen($counter) < 16)
139 23
                $counter = "0" . $counter;
140 24
            $counterLength=8;
141
        }
142
        // Question
143 118
        if($dataInput[0] == "q" ||
144 118
                stripos($dataInput, "-q")!==false) {
145 118
            while(strlen($question) < 256)
146 117
                $question = $question . "0";
147 118
            $questionLength=128;
148
        }
149
150
        // Password
151 118
        if(stripos($dataInput, "psha1")!==false) {
152 17
            while(strlen($password) < 40)
153 1
                $password = "0" . $password;
154 17
            $passwordLength=20;
155
        }
156
    
157 118
        if(stripos($dataInput, "psha256")!==false) {
158
            while(strlen($password) < 64)
159
                $password = "0" . $password;
160
            $passwordLength=32;
161
        }
162
        
163 118
        if(stripos($dataInput, "psha512")!==false) {
164
            while(strlen($password) < 128)
165
                $password = "0" . $password;
166
            $passwordLength=64;
167
        }
168
        
169
        // sessionInformation
170 118
        if(stripos($dataInput, "s064") !==false) {
171 32
            while(strlen($sessionInformation) < 128)
172 32
                $sessionInformation = "0" . $sessionInformation;
173
174 32
            $sessionInformationLength=64;
175 86
        } else if(stripos($dataInput, "s128") !==false) {
176
            while(strlen($sessionInformation) < 256)
177
                $sessionInformation = "0" . $sessionInformation;
178
        
179
            $sessionInformationLength=128;
180 86
        } else if(stripos($dataInput, "s256") !==false) {
181
            while(strlen($sessionInformation) < 512)
182
                $sessionInformation = "0" . $sessionInformation;
183
        
184
            $sessionInformationLength=256;
185 86
        } else if(stripos($dataInput, "s512") !==false) {
186
            while(strlen($sessionInformation) < 128)
187
                $sessionInformation = "0" . $sessionInformation;
188
        
189
            $sessionInformationLength=64;
190 86
        } else if (stripos($dataInput, "-s") !== false ) {
191
            // deviation from spec. Officially 's' without a length indicator is not in the reference implementation.
192
            // RFC is ambigious. However we have supported this in Tiqr since day 1, so we continue to support it.
193
194
            // See the format of the datainput below. "[]" denotes optional.
195
            // Because Q is mandatory, s will always be preceded by the separator "-". Matching "-s" is required
196
            // to prevent matching the "s" in the password input e.g. "psha1".
197
            // [C] | QFxx | [PH | Snnn | TG] : Challenge-Response computation
198
            // [C] | QFxx | [PH | TG] : Plain Signature computation
199 40
            while(strlen($sessionInformation) < 128)
200 39
                $sessionInformation = "0" . $sessionInformation;
201
            
202 40
            $sessionInformationLength=64;
203
        }
204
        
205
        
206
             
207
        // TimeStamp
208 118
        if($dataInput[0] == "t" ||
209 118
                stripos($dataInput, "-t") !== false) {
210 7
            while(strlen($timeStamp) < 16)
211 6
                $timeStamp = "0" . $timeStamp;
212 7
            $timeStampLength=8;
213
        }
214
215
        // Put the bytes of "ocraSuite" parameters into the message
216
        
217 118
        $msg = array_fill(0,$ocraSuiteLength+$counterLength+$questionLength+$passwordLength+$sessionInformationLength+$timeStampLength+1, 0);
218
                
219 118
        for($i=0;$i<strlen($ocraSuite);$i++) {
220 118
            $msg[$i] = $ocraSuite[$i];
221
        }
222
        
223
        // Delimiter
224 118
        $msg[strlen($ocraSuite)] = "\0";
225
226
        // Put the bytes of "Counter" to the message
227
        // Input is HEX encoded
228 118
        if($counterLength > 0 ) {
229 24
            $bArray = self::_hexStr2Bytes($counter, $counterLength, 'counter');
230 22
            for ($i=0;$i<strlen($bArray);$i++) {
231 22
                $msg [$i + $ocraSuiteLength + 1] = $bArray[$i];
232
            }
233
        }
234
235
236
        // Put the bytes of "question" to the message
237
        // Input is text encoded
238 116
        if($questionLength > 0 ) {
239 116
            $bArray = self::_hexStr2Bytes($question, $questionLength, 'question');
240 114
            for ($i=0;$i<strlen($bArray);$i++) {
241 114
                $msg [$i + $ocraSuiteLength + 1 + $counterLength] = $bArray[$i];
242
            }
243
        }
244
245
        // Put the bytes of "password" to the message
246
        // Input is HEX encoded
247 114
        if($passwordLength > 0){
248 17
            $bArray = self::_hexStr2Bytes($password, $passwordLength, 'password');
249 15
            for ($i=0;$i<strlen($bArray);$i++) {
250 15
                $msg [$i + $ocraSuiteLength + 1 + $counterLength + $questionLength] = $bArray[$i];
251
            }
252
        }
253
254
        // Put the bytes of "sessionInformation" to the message
255
        // Input is HEX encoded
256 112
        if($sessionInformationLength > 0 ){
257 70
            $bArray = self::_hexStr2Bytes($sessionInformation, $sessionInformationLength, 'sessionInformation');
258 68
            for ($i=0;$i<strlen($bArray);$i++) {
259 68
                $msg [$i + $ocraSuiteLength + 1 + $counterLength + $questionLength + $passwordLength] = $bArray[$i];
260
            }
261
        }
262
263
        // Put the bytes of "time" to the message
264
        // Input is HEX encoded value of minutes
265 110
        if($timeStampLength > 0){
266 7
            $bArray = self::_hexStr2Bytes($timeStamp, $timeStampLength, 'timeStamp');
267 5
            for ($i=0;$i<strlen($bArray);$i++) {
268 5
                $msg [$i + $ocraSuiteLength + 1 + $counterLength + $questionLength + $passwordLength + $sessionInformationLength] = $bArray[$i];
269
            }
270
        }
271
        
272 108
        $byteKey = self::_hexStr2Bytes($key, strlen($key)/2, 'key');
273
              
274 107
        $msg = implode("", $msg);
275
276 107
        $hash = self::_hmac($crypto, $byteKey, $msg);
277
        
278 107
        $result = self::_oath_truncate($hash, $codeDigits);
0 ignored issues
show
Bug introduced by
$codeDigits of type string is incompatible with the type integer expected by parameter $length of OCRA::_oath_truncate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

278
        $result = self::_oath_truncate($hash, /** @scrutinizer ignore-type */ $codeDigits);
Loading history...
279
             
280 107
        return $result;
281
    }
282
283
    /**
284
     * Implementation of the Truncate function from RFC4226
285
     * Truncate a hex encoded (hash) result to a string of digital digits of $length
286
     *
287
     * @param string $hash hex encoded (hash) value to truncate. Minimum length is 20 bytes (i.e. a string of 40 hex digits)
288
     * @param int $length number of decimal digits to truncate to
289
     * @return string of $length digits
290
     */    
291 108
    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...
292
    {
293
        // Convert to dec
294 108
        foreach(str_split($hash,2) as $hex)
295
        {
296 108
            $hmac_result[]=hexdec($hex);
297
        }
298
    
299
        // Find offset
300 108
        $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 294. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
301
    
302
        $v = strval(
303 108
            (($hmac_result[$offset+0] & 0x7f) << 24 ) |
304 108
            (($hmac_result[$offset+1] & 0xff) << 16 ) |
305 108
            (($hmac_result[$offset+2] & 0xff) << 8 ) |
306 108
            ($hmac_result[$offset+3] & 0xff)
307
        );
308
309
        // Prefix truncated string with 0's to ensure it always has the required length
310 108
        $v=str_pad($v, $length, "0", STR_PAD_LEFT);
311
312 108
        $v = substr($v, strlen($v) - $length);
313 108
        return $v;
314
    }
315
    
316
}
317