Signature.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <?php
  2. namespace AmazonPay;
  3. class Signature
  4. {
  5. const MWS_VERSION = '2013-01-01';
  6. private $config = array();
  7. private $signature = null;
  8. private $mwsEndpointPath = null;
  9. private $mwsEndpointUrl = null;
  10. private $modePath = null;
  11. private $mwsServiceUrl = null;
  12. private $mwsServiceUrls = array('eu' => 'mws-eu.amazonservices.com',
  13. 'na' => 'mws.amazonservices.com',
  14. 'jp' => 'mws.amazonservices.jp');
  15. private $regionMappings = array('de' => 'eu',
  16. 'uk' => 'eu',
  17. 'us' => 'na',
  18. 'jp' => 'jp');
  19. public function __construct($config = array(),$parameters = array())
  20. {
  21. $config = array_change_key_case($config, CASE_LOWER);
  22. $this->config = $config;
  23. $this->signature = $this->calculateSignature($parameters);
  24. }
  25. public function getSignature()
  26. {
  27. return trim($this->signature);
  28. }
  29. /* Create an Array of required parameters, sort them
  30. * Calculate signature and invoke the POST them to the MWS Service URL
  31. *
  32. * @param AWSAccessKeyId [String]
  33. * @param Version [String]
  34. * @param SignatureMethod [String]
  35. * @param Timestamp [String]
  36. * @param Signature [String]
  37. */
  38. private function calculateSignature($parameters)
  39. {
  40. $this->createServiceUrl();
  41. $signature = $this->signParameters($parameters);
  42. return $signature;
  43. }
  44. /* Computes RFC 2104-compliant HMAC signature for request parameters
  45. * Implements AWS Signature, as per following spec:
  46. *
  47. * If Signature Version is 0, it signs concatenated Action and Timestamp
  48. *
  49. * If Signature Version is 1, it performs the following:
  50. *
  51. * Sorts all parameters (including SignatureVersion and excluding Signature,
  52. * the value of which is being created), ignoring case.
  53. *
  54. * Iterate over the sorted list and append the parameter name (in original case)
  55. * and then its value. It will not URL-encode the parameter values before
  56. * constructing this string. There are no separators.
  57. *
  58. * If Signature Version is 2, string to sign is based on following:
  59. *
  60. * 1. The HTTP Request Method followed by an ASCII newline (%0A)
  61. * 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline.
  62. * 3. The URL encoded HTTP absolute path component of the URI
  63. * (up to but not including the query string parameters);
  64. * if this is empty use a forward '/'. This parameter is followed by an ASCII newline.
  65. * 4. The concatenation of all query string components (names and values)
  66. * as UTF-8 characters which are URL encoded as per RFC 3986
  67. * (hex characters MUST be uppercase), sorted using lexicographic byte ordering.
  68. * Parameter names are separated from their values by the '=' character
  69. * (ASCII character 61), even if the value is empty.
  70. * Pairs of parameter and values are separated by the '&' character (ASCII code 38).
  71. *
  72. */
  73. private function signParameters(array $parameters)
  74. {
  75. $signatureVersion = $parameters['SignatureVersion'];
  76. $algorithm = "HmacSHA1";
  77. $stringToSign = null;
  78. if (2 === $signatureVersion) {
  79. $algorithm = "HmacSHA256";
  80. $parameters['SignatureMethod'] = $algorithm;
  81. $stringToSign = $this->calculateStringToSignV2($parameters);
  82. } else {
  83. throw new \Exception("Invalid Signature Version specified");
  84. }
  85. return $this->sign($stringToSign, $algorithm);
  86. }
  87. /* Calculate String to Sign for SignatureVersion 2
  88. * @param array $parameters request parameters
  89. * @return String to Sign
  90. */
  91. private function calculateStringToSignV2(array $parameters)
  92. {
  93. $data = 'POST';
  94. $data .= "\n";
  95. $data .= $this->mwsEndpointUrl;
  96. $data .= "\n";
  97. $data .= $this->mwsEndpointPath;
  98. $data .= "\n";
  99. $data .= $this->getParametersAsString($parameters);
  100. return $data;
  101. }
  102. /* Convert paremeters to Url encoded query string */
  103. private function getParametersAsString(array $parameters)
  104. {
  105. $queryParameters = array();
  106. foreach ($parameters as $key => $value) {
  107. $queryParameters[] = $key . '=' . $this->urlEncode($value);
  108. }
  109. return implode('&', $queryParameters);
  110. }
  111. private function urlEncode($value)
  112. {
  113. return str_replace('%7E', '~', rawurlencode($value));
  114. }
  115. /* Computes RFC 2104-compliant HMAC signature.*/
  116. private function sign($data, $algorithm)
  117. {
  118. if ($algorithm === 'HmacSHA1') {
  119. $hash = 'sha1';
  120. } else if ($algorithm === 'HmacSHA256') {
  121. $hash = 'sha256';
  122. } else {
  123. throw new \Exception("Non-supported signing method specified");
  124. }
  125. return base64_encode(hash_hmac($hash, $data, $this->config['secret_key'], true));
  126. }
  127. /* Formats date as ISO 8601 timestamp */
  128. private function getFormattedTimestamp()
  129. {
  130. return gmdate("Y-m-d\TH:i:s.\\0\\0\\0\\Z", time());
  131. }
  132. private function createServiceUrl()
  133. {
  134. $this->modePath = strtolower($this->config['sandbox']) ? 'OffAmazonPayments_Sandbox' : 'OffAmazonPayments';
  135. if (!empty($this->config['region'])) {
  136. $region = strtolower($this->config['region']);
  137. if (array_key_exists($region, $this->regionMappings)) {
  138. $this->mwsEndpointUrl = $this->mwsServiceUrls[$this->regionMappings[$region]];
  139. $this->mwsServiceUrl = 'https://' . $this->mwsEndpointUrl . '/' . $this->modePath . '/' . self::MWS_VERSION;
  140. $this->mwsEndpointPath = '/' . $this->modePath . '/' . self::MWS_VERSION;
  141. } else {
  142. throw new \Exception($region . ' is not a valid region');
  143. }
  144. } else {
  145. throw new \Exception("config['region'] is a required parameter and is not set");
  146. }
  147. }
  148. }