xref: /plugin/davcal/vendor/sabre/http/lib/Auth/AWS.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
1*a1a3b679SAndreas Boehler<?php
2*a1a3b679SAndreas Boehler
3*a1a3b679SAndreas Boehlernamespace Sabre\HTTP\Auth;
4*a1a3b679SAndreas Boehler
5*a1a3b679SAndreas Boehleruse Sabre\HTTP\Util;
6*a1a3b679SAndreas Boehler
7*a1a3b679SAndreas Boehler/**
8*a1a3b679SAndreas Boehler * HTTP AWS Authentication handler
9*a1a3b679SAndreas Boehler *
10*a1a3b679SAndreas Boehler * Use this class to leverage amazon's AWS authentication header
11*a1a3b679SAndreas Boehler *
12*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/).
13*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/)
14*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License
15*a1a3b679SAndreas Boehler */
16*a1a3b679SAndreas Boehlerclass AWS extends AbstractAuth {
17*a1a3b679SAndreas Boehler
18*a1a3b679SAndreas Boehler    /**
19*a1a3b679SAndreas Boehler     * The signature supplied by the HTTP client
20*a1a3b679SAndreas Boehler     *
21*a1a3b679SAndreas Boehler     * @var string
22*a1a3b679SAndreas Boehler     */
23*a1a3b679SAndreas Boehler    private $signature = null;
24*a1a3b679SAndreas Boehler
25*a1a3b679SAndreas Boehler    /**
26*a1a3b679SAndreas Boehler     * The accesskey supplied by the HTTP client
27*a1a3b679SAndreas Boehler     *
28*a1a3b679SAndreas Boehler     * @var string
29*a1a3b679SAndreas Boehler     */
30*a1a3b679SAndreas Boehler    private $accessKey = null;
31*a1a3b679SAndreas Boehler
32*a1a3b679SAndreas Boehler    /**
33*a1a3b679SAndreas Boehler     * An error code, if any
34*a1a3b679SAndreas Boehler     *
35*a1a3b679SAndreas Boehler     * This value will be filled with one of the ERR_* constants
36*a1a3b679SAndreas Boehler     *
37*a1a3b679SAndreas Boehler     * @var int
38*a1a3b679SAndreas Boehler     */
39*a1a3b679SAndreas Boehler    public $errorCode = 0;
40*a1a3b679SAndreas Boehler
41*a1a3b679SAndreas Boehler    const ERR_NOAWSHEADER = 1;
42*a1a3b679SAndreas Boehler    const ERR_MD5CHECKSUMWRONG = 2;
43*a1a3b679SAndreas Boehler    const ERR_INVALIDDATEFORMAT = 3;
44*a1a3b679SAndreas Boehler    const ERR_REQUESTTIMESKEWED = 4;
45*a1a3b679SAndreas Boehler    const ERR_INVALIDSIGNATURE = 5;
46*a1a3b679SAndreas Boehler
47*a1a3b679SAndreas Boehler    /**
48*a1a3b679SAndreas Boehler     * Gathers all information from the headers
49*a1a3b679SAndreas Boehler     *
50*a1a3b679SAndreas Boehler     * This method needs to be called prior to anything else.
51*a1a3b679SAndreas Boehler     *
52*a1a3b679SAndreas Boehler     * @return bool
53*a1a3b679SAndreas Boehler     */
54*a1a3b679SAndreas Boehler    function init() {
55*a1a3b679SAndreas Boehler
56*a1a3b679SAndreas Boehler        $authHeader = $this->request->getHeader('Authorization');
57*a1a3b679SAndreas Boehler        $authHeader = explode(' ', $authHeader);
58*a1a3b679SAndreas Boehler
59*a1a3b679SAndreas Boehler        if ($authHeader[0] != 'AWS' || !isset($authHeader[1])) {
60*a1a3b679SAndreas Boehler            $this->errorCode = self::ERR_NOAWSHEADER;
61*a1a3b679SAndreas Boehler             return false;
62*a1a3b679SAndreas Boehler        }
63*a1a3b679SAndreas Boehler
64*a1a3b679SAndreas Boehler        list($this->accessKey, $this->signature) = explode(':', $authHeader[1]);
65*a1a3b679SAndreas Boehler
66*a1a3b679SAndreas Boehler        return true;
67*a1a3b679SAndreas Boehler
68*a1a3b679SAndreas Boehler    }
69*a1a3b679SAndreas Boehler
70*a1a3b679SAndreas Boehler    /**
71*a1a3b679SAndreas Boehler     * Returns the username for the request
72*a1a3b679SAndreas Boehler     *
73*a1a3b679SAndreas Boehler     * @return string
74*a1a3b679SAndreas Boehler     */
75*a1a3b679SAndreas Boehler    function getAccessKey() {
76*a1a3b679SAndreas Boehler
77*a1a3b679SAndreas Boehler        return $this->accessKey;
78*a1a3b679SAndreas Boehler
79*a1a3b679SAndreas Boehler    }
80*a1a3b679SAndreas Boehler
81*a1a3b679SAndreas Boehler    /**
82*a1a3b679SAndreas Boehler     * Validates the signature based on the secretKey
83*a1a3b679SAndreas Boehler     *
84*a1a3b679SAndreas Boehler     * @param string $secretKey
85*a1a3b679SAndreas Boehler     * @return bool
86*a1a3b679SAndreas Boehler     */
87*a1a3b679SAndreas Boehler    function validate($secretKey) {
88*a1a3b679SAndreas Boehler
89*a1a3b679SAndreas Boehler        $contentMD5 = $this->request->getHeader('Content-MD5');
90*a1a3b679SAndreas Boehler
91*a1a3b679SAndreas Boehler        if ($contentMD5) {
92*a1a3b679SAndreas Boehler            // We need to validate the integrity of the request
93*a1a3b679SAndreas Boehler            $body = $this->request->getBody(true);
94*a1a3b679SAndreas Boehler            $this->request->setBody($body, true);
95*a1a3b679SAndreas Boehler
96*a1a3b679SAndreas Boehler            if ($contentMD5 != base64_encode(md5($body, true))) {
97*a1a3b679SAndreas Boehler                // content-md5 header did not match md5 signature of body
98*a1a3b679SAndreas Boehler                $this->errorCode = self::ERR_MD5CHECKSUMWRONG;
99*a1a3b679SAndreas Boehler                return false;
100*a1a3b679SAndreas Boehler            }
101*a1a3b679SAndreas Boehler
102*a1a3b679SAndreas Boehler        }
103*a1a3b679SAndreas Boehler
104*a1a3b679SAndreas Boehler        if (!$requestDate = $this->request->getHeader('x-amz-date'))
105*a1a3b679SAndreas Boehler            $requestDate = $this->request->getHeader('Date');
106*a1a3b679SAndreas Boehler
107*a1a3b679SAndreas Boehler        if (!$this->validateRFC2616Date($requestDate))
108*a1a3b679SAndreas Boehler            return false;
109*a1a3b679SAndreas Boehler
110*a1a3b679SAndreas Boehler        $amzHeaders = $this->getAmzHeaders();
111*a1a3b679SAndreas Boehler
112*a1a3b679SAndreas Boehler        $signature = base64_encode(
113*a1a3b679SAndreas Boehler            $this->hmacsha1($secretKey,
114*a1a3b679SAndreas Boehler                $this->request->getMethod() . "\n" .
115*a1a3b679SAndreas Boehler                $contentMD5 . "\n" .
116*a1a3b679SAndreas Boehler                $this->request->getHeader('Content-type') . "\n" .
117*a1a3b679SAndreas Boehler                $requestDate . "\n" .
118*a1a3b679SAndreas Boehler                $amzHeaders .
119*a1a3b679SAndreas Boehler                $this->request->getUrl()
120*a1a3b679SAndreas Boehler            )
121*a1a3b679SAndreas Boehler        );
122*a1a3b679SAndreas Boehler
123*a1a3b679SAndreas Boehler        if ($this->signature != $signature) {
124*a1a3b679SAndreas Boehler
125*a1a3b679SAndreas Boehler            $this->errorCode = self::ERR_INVALIDSIGNATURE;
126*a1a3b679SAndreas Boehler            return false;
127*a1a3b679SAndreas Boehler
128*a1a3b679SAndreas Boehler        }
129*a1a3b679SAndreas Boehler
130*a1a3b679SAndreas Boehler        return true;
131*a1a3b679SAndreas Boehler
132*a1a3b679SAndreas Boehler    }
133*a1a3b679SAndreas Boehler
134*a1a3b679SAndreas Boehler
135*a1a3b679SAndreas Boehler    /**
136*a1a3b679SAndreas Boehler     * Returns an HTTP 401 header, forcing login
137*a1a3b679SAndreas Boehler     *
138*a1a3b679SAndreas Boehler     * This should be called when username and password are incorrect, or not supplied at all
139*a1a3b679SAndreas Boehler     *
140*a1a3b679SAndreas Boehler     * @return void
141*a1a3b679SAndreas Boehler     */
142*a1a3b679SAndreas Boehler    function requireLogin() {
143*a1a3b679SAndreas Boehler
144*a1a3b679SAndreas Boehler        $this->response->addHeader('WWW-Authenticate', 'AWS');
145*a1a3b679SAndreas Boehler        $this->response->setStatus(401);
146*a1a3b679SAndreas Boehler
147*a1a3b679SAndreas Boehler    }
148*a1a3b679SAndreas Boehler
149*a1a3b679SAndreas Boehler    /**
150*a1a3b679SAndreas Boehler     * Makes sure the supplied value is a valid RFC2616 date.
151*a1a3b679SAndreas Boehler     *
152*a1a3b679SAndreas Boehler     * If we would just use strtotime to get a valid timestamp, we have no way of checking if a
153*a1a3b679SAndreas Boehler     * user just supplied the word 'now' for the date header.
154*a1a3b679SAndreas Boehler     *
155*a1a3b679SAndreas Boehler     * This function also makes sure the Date header is within 15 minutes of the operating
156*a1a3b679SAndreas Boehler     * system date, to prevent replay attacks.
157*a1a3b679SAndreas Boehler     *
158*a1a3b679SAndreas Boehler     * @param string $dateHeader
159*a1a3b679SAndreas Boehler     * @return bool
160*a1a3b679SAndreas Boehler     */
161*a1a3b679SAndreas Boehler    protected function validateRFC2616Date($dateHeader) {
162*a1a3b679SAndreas Boehler
163*a1a3b679SAndreas Boehler        $date = Util::parseHTTPDate($dateHeader);
164*a1a3b679SAndreas Boehler
165*a1a3b679SAndreas Boehler        // Unknown format
166*a1a3b679SAndreas Boehler        if (!$date) {
167*a1a3b679SAndreas Boehler            $this->errorCode = self::ERR_INVALIDDATEFORMAT;
168*a1a3b679SAndreas Boehler            return false;
169*a1a3b679SAndreas Boehler        }
170*a1a3b679SAndreas Boehler
171*a1a3b679SAndreas Boehler        $min = new \DateTime('-15 minutes');
172*a1a3b679SAndreas Boehler        $max = new \DateTime('+15 minutes');
173*a1a3b679SAndreas Boehler
174*a1a3b679SAndreas Boehler        // We allow 15 minutes around the current date/time
175*a1a3b679SAndreas Boehler        if ($date > $max || $date < $min) {
176*a1a3b679SAndreas Boehler            $this->errorCode = self::ERR_REQUESTTIMESKEWED;
177*a1a3b679SAndreas Boehler            return false;
178*a1a3b679SAndreas Boehler        }
179*a1a3b679SAndreas Boehler
180*a1a3b679SAndreas Boehler        return $date;
181*a1a3b679SAndreas Boehler
182*a1a3b679SAndreas Boehler    }
183*a1a3b679SAndreas Boehler
184*a1a3b679SAndreas Boehler    /**
185*a1a3b679SAndreas Boehler     * Returns a list of AMZ headers
186*a1a3b679SAndreas Boehler     *
187*a1a3b679SAndreas Boehler     * @return string
188*a1a3b679SAndreas Boehler     */
189*a1a3b679SAndreas Boehler    protected function getAmzHeaders() {
190*a1a3b679SAndreas Boehler
191*a1a3b679SAndreas Boehler        $amzHeaders = [];
192*a1a3b679SAndreas Boehler        $headers = $this->request->getHeaders();
193*a1a3b679SAndreas Boehler        foreach ($headers as $headerName => $headerValue) {
194*a1a3b679SAndreas Boehler            if (strpos(strtolower($headerName), 'x-amz-') === 0) {
195*a1a3b679SAndreas Boehler                $amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0]) . "\n";
196*a1a3b679SAndreas Boehler            }
197*a1a3b679SAndreas Boehler        }
198*a1a3b679SAndreas Boehler        ksort($amzHeaders);
199*a1a3b679SAndreas Boehler
200*a1a3b679SAndreas Boehler        $headerStr = '';
201*a1a3b679SAndreas Boehler        foreach ($amzHeaders as $h => $v) {
202*a1a3b679SAndreas Boehler            $headerStr .= $h . ':' . $v;
203*a1a3b679SAndreas Boehler        }
204*a1a3b679SAndreas Boehler
205*a1a3b679SAndreas Boehler        return $headerStr;
206*a1a3b679SAndreas Boehler
207*a1a3b679SAndreas Boehler    }
208*a1a3b679SAndreas Boehler
209*a1a3b679SAndreas Boehler    /**
210*a1a3b679SAndreas Boehler     * Generates an HMAC-SHA1 signature
211*a1a3b679SAndreas Boehler     *
212*a1a3b679SAndreas Boehler     * @param string $key
213*a1a3b679SAndreas Boehler     * @param string $message
214*a1a3b679SAndreas Boehler     * @return string
215*a1a3b679SAndreas Boehler     */
216*a1a3b679SAndreas Boehler    private function hmacsha1($key, $message) {
217*a1a3b679SAndreas Boehler
218*a1a3b679SAndreas Boehler        if (function_exists('hash_hmac')) {
219*a1a3b679SAndreas Boehler            return hash_hmac('sha1', $message, $key, true);
220*a1a3b679SAndreas Boehler        }
221*a1a3b679SAndreas Boehler
222*a1a3b679SAndreas Boehler        $blocksize = 64;
223*a1a3b679SAndreas Boehler        if (strlen($key) > $blocksize) {
224*a1a3b679SAndreas Boehler            $key = pack('H*', sha1($key));
225*a1a3b679SAndreas Boehler        }
226*a1a3b679SAndreas Boehler        $key = str_pad($key, $blocksize, chr(0x00));
227*a1a3b679SAndreas Boehler        $ipad = str_repeat(chr(0x36), $blocksize);
228*a1a3b679SAndreas Boehler        $opad = str_repeat(chr(0x5c), $blocksize);
229*a1a3b679SAndreas Boehler        $hmac = pack('H*', sha1(($key ^ $opad) . pack('H*', sha1(($key ^ $ipad) . $message))));
230*a1a3b679SAndreas Boehler        return $hmac;
231*a1a3b679SAndreas Boehler
232*a1a3b679SAndreas Boehler    }
233*a1a3b679SAndreas Boehler
234*a1a3b679SAndreas Boehler}
235