4 * HTTP AWS Authentication handler
6 * Use this class to leverage amazon's AWS authentication header
10 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
11 * @author Evert Pot (http://www.rooftopsolutions.nl/)
12 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
14 class Sabre_HTTP_AWSAuth extends Sabre_HTTP_AbstractAuth {
17 * The signature supplied by the HTTP client
21 private $signature = null;
24 * The accesskey supplied by the HTTP client
28 private $accessKey = null;
31 * An error code, if any
33 * This value will be filled with one of the ERR_* constants
37 public $errorCode = 0;
39 const ERR_NOAWSHEADER = 1;
40 const ERR_MD5CHECKSUMWRONG = 2;
41 const ERR_INVALIDDATEFORMAT = 3;
42 const ERR_REQUESTTIMESKEWED = 4;
43 const ERR_INVALIDSIGNATURE = 5;
46 * Gathers all information from the headers
48 * This method needs to be called prior to anything else.
52 public function init() {
54 $authHeader = $this->httpRequest->getHeader('Authorization');
55 $authHeader = explode(' ',$authHeader);
57 if ($authHeader[0]!='AWS' || !isset($authHeader[1])) {
58 $this->errorCode = self::ERR_NOAWSHEADER;
62 list($this->accessKey,$this->signature) = explode(':',$authHeader[1]);
69 * Returns the username for the request
73 public function getAccessKey() {
75 return $this->accessKey;
80 * Validates the signature based on the secretKey
82 * @param string $secretKey
85 public function validate($secretKey) {
87 $contentMD5 = $this->httpRequest->getHeader('Content-MD5');
90 // We need to validate the integrity of the request
91 $body = $this->httpRequest->getBody(true);
92 $this->httpRequest->setBody($body,true);
94 if ($contentMD5!=base64_encode(md5($body,true))) {
95 // content-md5 header did not match md5 signature of body
96 $this->errorCode = self::ERR_MD5CHECKSUMWRONG;
102 if (!$requestDate = $this->httpRequest->getHeader('x-amz-date'))
103 $requestDate = $this->httpRequest->getHeader('Date');
105 if (!$this->validateRFC2616Date($requestDate))
108 $amzHeaders = $this->getAmzHeaders();
110 $signature = base64_encode(
111 $this->hmacsha1($secretKey,
112 $this->httpRequest->getMethod() . "\n" .
114 $this->httpRequest->getHeader('Content-type') . "\n" .
115 $requestDate . "\n" .
117 $this->httpRequest->getURI()
121 if ($this->signature != $signature) {
123 $this->errorCode = self::ERR_INVALIDSIGNATURE;
134 * Returns an HTTP 401 header, forcing login
136 * This should be called when username and password are incorrect, or not supplied at all
140 public function requireLogin() {
142 $this->httpResponse->setHeader('WWW-Authenticate','AWS');
143 $this->httpResponse->sendStatus(401);
148 * Makes sure the supplied value is a valid RFC2616 date.
150 * If we would just use strtotime to get a valid timestamp, we have no way of checking if a
151 * user just supplied the word 'now' for the date header.
153 * This function also makes sure the Date header is within 15 minutes of the operating
154 * system date, to prevent replay attacks.
156 * @param string $dateHeader
159 protected function validateRFC2616Date($dateHeader) {
161 $date = Sabre_HTTP_Util::parseHTTPDate($dateHeader);
165 $this->errorCode = self::ERR_INVALIDDATEFORMAT;
169 $min = new DateTime('-15 minutes');
170 $max = new DateTime('+15 minutes');
172 // We allow 15 minutes around the current date/time
173 if ($date > $max || $date < $min) {
174 $this->errorCode = self::ERR_REQUESTTIMESKEWED;
183 * Returns a list of AMZ headers
187 protected function getAmzHeaders() {
189 $amzHeaders = array();
190 $headers = $this->httpRequest->getHeaders();
191 foreach($headers as $headerName => $headerValue) {
192 if (strpos(strtolower($headerName),'x-amz-')===0) {
193 $amzHeaders[strtolower($headerName)] = str_replace(array("\r\n"),array(' '),$headerValue) . "\n";
199 foreach($amzHeaders as $h=>$v) {
200 $headerStr.=$h.':'.$v;
208 * Generates an HMAC-SHA1 signature
211 * @param string $message
214 private function hmacsha1($key, $message) {
217 if (strlen($key)>$blocksize)
218 $key=pack('H*', sha1($key));
219 $key=str_pad($key,$blocksize,chr(0x00));
220 $ipad=str_repeat(chr(0x36),$blocksize);
221 $opad=str_repeat(chr(0x5c),$blocksize);
222 $hmac = pack('H*',sha1(($key^$opad).pack('H*',sha1(($key^$ipad).$message))));