Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
totpalgorithm.php
1<?php
2
4
9
10Loc::loadMessages(__FILE__);
11
13{
14 const SYNC_WINDOW = 180;
15 protected static $type = 'totp';
16 protected $interval = 30;
17 protected int $startTimestamp = 0;
18 // ToDo: option here! May be just merge with HOTP window?
19 protected $window = 2;
20 protected $requireTwoCode = false;
21
22 public function __construct(array $initParams = [])
23 {
24 if (isset($initParams['startTimestamp']) && (int)$initParams['startTimestamp'] > 0)
25 {
26 $this->startTimestamp = (int)$initParams['startTimestamp'];
27 }
28
29 $interval = (int)Option::get('security', 'totp_interval');
30 if ($interval && $interval > 0)
31 {
32 $this->interval = $interval;
33 }
34 }
35
39 public function verify($input, $params = null, $time = null)
40 {
41 $input = (string)$input;
42
43 if ($params === null)
44 {
45 $params = '0:0';
46 }
47
48 if (!preg_match('#^\d+$#D', $input))
49 {
50 throw new ArgumentOutOfRangeException('input', 'string with numbers');
51 }
52
53 [$userOffset, $lastTimeCode] = explode(':', $params);
54 $userOffset = (int)$userOffset;
55 $lastTimeCode = (int)$lastTimeCode;
56
57 if ($time === null)
58 {
59 $timeCode = $this->timecode(time());
60 }
61 else
62 {
63 $timeCode = $this->timecode((int)$time);
64 }
65
66 $checkOffsets = [];
67 // First of all we must check input for provided offset
68 $checkOffsets[] = $userOffset;
69 if ($userOffset)
70 {
71 // If we failed on previous step and have user offset - try current time, may be user syncing time on device
72 $checkOffsets[] = 0;
73 }
74
75 if ($this->window)
76 {
77 // Otherwise, try deal with clock drifting
78 $checkOffsets = array_merge(
79 $checkOffsets,
80 range($userOffset - $this->window, $userOffset + $this->window)
81 );
82 }
83
84 $isSuccess = false;
85 $resultOffset = 0;
86 $resultTimeCode = 0;
87
88 foreach ($checkOffsets as $offset)
89 {
90 $code = $timeCode + $offset;
91 // Disallow authorization in the past. Must prevent replay attacks.
92 if ($lastTimeCode && $code <= $lastTimeCode)
93 {
94 continue;
95 }
96
97 if ($this->isStringsEqual($input, $this->generateOTP($code)))
98 {
99 $isSuccess = true;
100 $resultOffset = $offset;
101 $resultTimeCode = $code;
102 break;
103 }
104 }
105
106 if ($isSuccess === true)
107 {
108 return [true, sprintf('%d:%d', $resultOffset, $resultTimeCode)];
109 }
110
111 return [false, null];
112 }
113
117 public function generateUri($label, array $opts = [])
118 {
119 $opts += ['period' => $this->getInterval()];
120 return parent::generateUri($label, $opts);
121 }
122
129 public function timecode($timestamp)
130 {
131 // https://datatracker.ietf.org/doc/html/rfc6238
132 // T = (Current Unix time - T0) / X
133 return (int)((((int)$timestamp - $this->startTimestamp) * 1000) / ($this->getInterval() * 1000));
134 }
135
140 public function setInterval($interval)
141 {
142 $this->interval = (int)$interval;
143 return $this;
144 }
145
151 protected function getInterval()
152 {
153 return $this->interval;
154 }
155
160 public function setWindow($window)
161 {
162 $this->window = (int)$window;
163 return $this;
164 }
165
169 public function getSyncParameters($inputA, $inputB)
170 {
171 $offset = 0;
172 $this->window = 0;
173
174 // Before detect clock drift we must check current time :-)
175 [$isSuccess,] = $this->verify($inputA, $offset);
176
177 if (!$isSuccess)
178 {
179 // Otherwise try to calculate resynchronization
180 $offset = -self::SYNC_WINDOW;
181 for ($i = $offset; $i < self::SYNC_WINDOW; $i++)
182 {
183 [$isSuccess,] = $this->verify($inputA, $offset);
184 if ($isSuccess)
185 {
186 break;
187 }
188 $offset++;
189 }
190 }
191
192 if ($offset === self::SYNC_WINDOW)
193 {
194 throw new OtpException('Cannot synchronize this secret key with the provided password values.');
195 }
196
197 return sprintf('%d:%d', $offset, 0);
198 }
199}
static loadMessages($file)
Definition loc.php:64
verify($input, $params=null, $time=null)