1<?php
2
3namespace SimpleSAML\Auth;
4
5/**
6 * A class that generates and verifies time-limited tokens.
7 */
8class TimeLimitedToken
9{
10
11    /**
12     * @var string
13     */
14    protected $secretSalt;
15
16    /**
17     * @var int
18     */
19    protected $lifetime;
20
21    /**
22     * @var int
23     */
24    protected $skew;
25
26    /**
27     * @var string
28     */
29    protected $algo;
30
31
32    /**
33     * Create a new time-limited token.
34     *
35     * Please note that the default algorithm will change in SSP 1.15.0 to SHA-256 instead of SHA-1.
36     *
37     * @param int $lifetime Token lifetime in seconds. Defaults to 900 (15 min).
38     * @param string $secretSalt A random and unique salt per installation. Defaults to the salt in the configuration.
39     * @param int $skew The allowed time skew (in seconds) to correct clock deviations. Defaults to 1 second.
40     * @param string $algo The hash algorithm to use to generate the tokens. Defaults to SHA-1.
41     *
42     * @throws \InvalidArgumentException if the given parameters are invalid.
43     */
44    public function __construct($lifetime = 900, $secretSalt = null, $skew = 1, $algo = 'sha1')
45    {
46        if ($secretSalt === null) {
47            $secretSalt = \SimpleSAML\Utils\Config::getSecretSalt();
48        }
49
50        if (!in_array($algo, hash_algos(), true)) {
51            throw new \InvalidArgumentException('Invalid hash algorithm "'.$algo.'"');
52        }
53
54        $this->secretSalt = $secretSalt;
55        $this->lifetime = $lifetime;
56        $this->skew = $skew;
57        $this->algo = $algo;
58    }
59
60
61    /**
62     * Add some given data to the current token. This data will be needed later too for token validation.
63     *
64     * This mechanism can be used to provide context for a token, such as a user identifier of the only subject
65     * authorised to use it. Note also that multiple data can be added to the token. This means that upon validation,
66     * not only the same data must be added, but also in the same order.
67     *
68     * @param string $data The data to incorporate into the current token.
69     */
70    public function addVerificationData($data)
71    {
72        $this->secretSalt .= '|'.$data;
73    }
74
75
76    /**
77     * Calculates a token value for a given offset.
78     *
79     * @param int $offset The offset to use.
80     * @param int|null $time The time stamp to which the offset is relative to. Defaults to the current time.
81     *
82     * @return string The token for the given time and offset.
83     */
84    private function calculateTokenValue($offset, $time = null)
85    {
86        if ($time === null) {
87            $time = time();
88        }
89        // a secret salt that should be randomly generated for each installation
90        return hash(
91            $this->algo,
92            $offset.':'.floor(($time - $offset) / ($this->lifetime + $this->skew)).':'.$this->secretSalt
93        );
94    }
95
96
97    /**
98     * Generates a token that contains an offset and a token value, using the current offset.
99     *
100     * @return string A time-limited token with the offset respect to the beginning of its time slot prepended.
101     */
102    public function generate()
103    {
104        $time = time();
105        $current_offset = ($time - $this->skew) % ($this->lifetime + $this->skew);
106        return dechex($current_offset).'-'.$this->calculateTokenValue($current_offset, $time);
107    }
108
109
110    /**
111     * @see generate
112     * @deprecated This method will be removed in SSP 2.0. Use generate() instead.
113     */
114    public function generate_token()
115    {
116        return $this->generate();
117    }
118
119
120    /**
121     * Validates a token by calculating the token value for the provided offset and comparing it.
122     *
123     * @param string $token The token to validate.
124     *
125     * @return boolean True if the given token is currently valid, false otherwise.
126     */
127    public function validate($token)
128    {
129        $splittoken = explode('-', $token);
130        if (count($splittoken) !== 2) {
131            return false;
132        }
133        $offset = intval(hexdec($splittoken[0]));
134        $value = $splittoken[1];
135        return ($this->calculateTokenValue($offset) === $value);
136    }
137
138
139    /**
140     * @see validate
141     * @deprecated This method will be removed in SSP 2.0. Use validate() instead.
142     */
143    public function validate_token($token)
144    {
145        return $this->validate($token);
146    }
147}
148