Passed
Branch master (c6dd11)
by Tim
15:21 queued 07:27
created

Auth_Yubico   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 99
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 31
c 1
b 0
f 0
dl 0
loc 99
rs 10
wmc 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
B verify() 0 45 8
A __construct() 0 4 1
A getLastResponse() 0 3 1
1
<?php
2
3
/**
4
 * Class for verifying Yubico One-Time-Passcodes
5
 *
6
 * LICENSE:
7
 *
8
 * Copyright (c) 2007, 2008  Simon Josefsson.  All rights reserved.
9
 *
10
 * Redistribution and use in source and binary forms, with or without
11
 * modification, are permitted provided that the following conditions
12
 * are met:
13
 *
14
 * o Redistributions of source code must retain the above copyright
15
 *   notice, this list of conditions and the following disclaimer.
16
 * o Redistributions in binary form must reproduce the above copyright
17
 *   notice, this list of conditions and the following disclaimer in the
18
 *   documentation and/or other materials provided with the distribution.
19
 * o The names of the authors may not be used to endorse or promote
20
 *   products derived from this software without specific prior written
21
 *   permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
 *
35
 * @category    Auth
36
 * @package     Auth_Yubico
37
 * @copyright   2008 Simon Josefsson
38
 * @license     http://opensource.org/licenses/bsd-license.php New BSD License
39
 * @version     CVS: $Id: Yubico.php,v 1.7 2007-10-22 12:56:14 jas Exp $
40
 * @link        http://yubico.com/
41
 */
42
43
/**
44
 * Class for verifying Yubico One-Time-Passcodes
45
 *
46
 * Simple example:
47
 * <code>
48
 * require_once 'Auth/Yubico.php';
49
 * $yubi = &new Auth_Yubico('42');
50
 * $auth = $yubi->verify("ccbbddeertkrctjkkcglfndnlihhnvekchkcctif");
51
 * if (PEAR::isError($auth)) {
52
 *    print "<p>Authentication failed: " . $auth->getMessage();
53
 *    print "<p>Debug output from server: " . $yubi->getLastResponse();
54
 * } else {
55
 *    print "<p>You are authenticated!";
56
 * }
57
 * </code>
58
 */
59
class Auth_Yubico
60
{
61
    /**
62
     * Yubico client ID
63
     * @var string
64
     */
65
    private $id;
66
67
    /**
68
     * Yubico client key
69
     * @var string
70
     */
71
    private $key;
72
73
    /**
74
     * Response from server
75
     * @var string
76
     */
77
    private $response;
78
79
    /**
80
     * Constructor
81
     *
82
     * Sets up the object
83
     * @param string $id    The client identity
84
     * @param string $key   The client MAC key (optional)
85
     * @access public
86
     */
87
    public function __construct(string $id, string $key = '')
88
    {
89
        $this->id = $id;
90
        $this->key = base64_decode($key);
91
    }
92
93
    /**
94
     * Return the last data received from the server, if any.
95
     *
96
     * @return string Output from server.
97
     * @access public
98
     */
99
    public function getLastResponse(): string
100
    {
101
        return $this->response;
102
    }
103
104
    // TODO? Add functions to get parsed parts of server response?
105
106
    /**
107
     * Verify Yubico OTP
108
     *
109
     * @param string $token     Yubico OTP
110
     * @return mixed            PEAR error on error, true otherwise
111
     * @access public
112
     */
113
    public function verify(string $token)
114
    {
115
        $parameters = "id=" . $this->id . "&otp=" . $token;
116
        // Generate signature
117
        if ($this->key !== "") {
118
            $signature = base64_encode(hash_hmac('sha1', $parameters, $this->key, true));
119
            $parameters .= '&h=' . $signature;
120
        }
121
        // Support https
122
        $url = "https://api.yubico.com/wsapi/verify?" . $parameters;
123
124
        /** @var string $responseMsg */
125
        $responseMsg = \SimpleSAML\Utils\HTTP::fetch($url);
126
127
        $out = [];
128
        if (preg_match("/status=([a-zA-Z0-9_]+)/", $responseMsg, $out) !== 1) {
129
            throw new Exception('Could not parse response');
130
        }
131
132
        $status = $out[1];
133
134
        // Verify signature
135
        if ($this->key <> "") {
136
            $rows = explode("\r\n", $responseMsg);
137
            $response = [];
138
            foreach ($rows as $val) {
139
                // = is also used in BASE64 encoding so we only replace the first = by # which is not used in BASE64
140
                $val = preg_replace('/=/', '#', $val, 1);
141
                $row = explode("#", $val);
142
                $response[$row[0]] = (isset($row[1])) ? $row[1] : "";
143
            }
144
145
            $check = 'status=' . $response['status'] . '&t=' . $response['t'];
146
            $checksignature = base64_encode(hash_hmac('sha1', $check, $this->key, true));
147
148
            if ($response['h'] != $checksignature) {
149
                throw new Exception('Checked Signature failed');
150
            }
151
        }
152
153
        if ($status != 'OK') {
154
            throw new Exception('Status was not OK: ' . $status);
155
        }
156
157
        return true;
158
    }
159
}
160