PageRenderTime 37ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/services/Signing/Services_Signing_Base.php

http://github.com/spotweb/spotweb
PHP | 248 lines | 115 code | 44 blank | 89 comment | 21 complexity | 4892a248fdaffb46042c43781ed7b675 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-3.0
  1. <?php
  2. use phpseclib\Crypt\RSA;
  3. abstract class Services_Signing_Base
  4. {
  5. /*
  6. * We never want to create this directly
  7. */
  8. private function __construct()
  9. {
  10. }
  11. // ctor
  12. /*
  13. * Create a factory method
  14. */
  15. public static function factory()
  16. {
  17. /*
  18. * Trigger the autoloader to load Crypt_RSA as
  19. * we need it for
  20. */
  21. if (class_exists('RSA')) {
  22. } // if
  23. /*
  24. * Automatically select OpenSSL if
  25. * possible
  26. */
  27. if (!defined('CRYPT_RSA_MODE')) {
  28. if (extension_loaded('openssl')) {
  29. define('CRYPT_RSA_MODE', RSA::MODE_OPENSSL);
  30. } else {
  31. define('CRYPT_RSA_MODE', RSA::MODE_INTERNAL);
  32. } // else
  33. } // if not defined
  34. if (CRYPT_RSA_MODE == RSA::MODE_OPENSSL) {
  35. return new Services_Signing_Openssl();
  36. } else {
  37. return new Services_Signing_Php();
  38. } // else
  39. }
  40. // factory
  41. /*
  42. * Actually validates the RSA signature
  43. */
  44. abstract protected function checkRsaSignature($toCheck, $signature, $rsaKey, $useCache);
  45. /*
  46. * Creates a public and private key
  47. */
  48. abstract public function createPrivateKey($sslCnfPath);
  49. /*
  50. * RSA signs a message and returns all possible values needed
  51. * to validate:
  52. *
  53. * - base64 encoded signature (signature)
  54. * - Public key (publickey)
  55. * - message which is signed (message)
  56. */
  57. public function signMessage($privatekey, $message)
  58. {
  59. /**
  60. * Test code:.
  61. *
  62. * $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
  63. * extract($rsa->createKey());
  64. * $spotSigning = new SpotSigning();
  65. * $x = $spotSigning->signMessage($privatekey, 'testmessage');
  66. * var_dump($x);
  67. * var_dump($spotSigning->checkRsaSignature('testmessage', $x['signature'], $x['publickey'], false));
  68. */
  69. if (empty($privatekey)) {
  70. throw new InvalidPrivateKeyException();
  71. } // if
  72. $rsa = new RSA();
  73. $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
  74. $rsa->loadKey($privatekey);
  75. // extract de public key
  76. $signature = $rsa->sign($message);
  77. $publickey = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_RAW);
  78. return ['signature' => base64_encode($signature),
  79. 'publickey' => ['modulo' => base64_encode($publickey['n']->toBytes()), 'exponent' => base64_encode($publickey['e']->toBytes())],
  80. 'message' => $message, ];
  81. }
  82. // signMessage
  83. /*
  84. * Returns a public key
  85. */
  86. public function getPublicKey($privateKey)
  87. {
  88. $rsa = new RSA();
  89. $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
  90. $rsa->loadKey($privateKey);
  91. /*
  92. * When we load a public key where a private key should
  93. * be loaded, this makes sure we can use it after all
  94. */
  95. if ($rsa->publicExponent == false) {
  96. $rsa->publicExponent = $rsa->exponent;
  97. } // if
  98. // extract the public key
  99. $publicKey = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_RAW);
  100. return ['modulo' => base64_encode($publicKey['n']->toBytes()), 'exponent' => base64_encode($publicKey['e']->toBytes())];
  101. }
  102. // getPublicKey
  103. /*
  104. * Convets a usuable public key for us, to a public key
  105. * usable for the SpotNet native client (.NET format)
  106. */
  107. public function pubkeyToXml($pubkey)
  108. {
  109. return '<RSAKeyValue><Modulus>'.$pubkey['modulo'].'</Modulus><Exponent>'.$pubkey['exponent'].'</Exponent></RSAKeyValue>';
  110. }
  111. // pubkeyToXml
  112. /*
  113. * Helper function to verify a spot header
  114. */
  115. public function verifySpotHeader($spot, $signature, $rsaKeys)
  116. {
  117. // This is the string to verify
  118. $toCheck = $spot['title'].substr($spot['header'], 0, strlen($spot['header']) - strlen($spot['headersign']) - 1).$spot['poster'];
  119. // Check the RSA signature on the spot
  120. return $this->checkRsaSignature($toCheck, $signature, $rsaKeys[$spot['keyid']], true);
  121. }
  122. // verifySpotHeader()
  123. /*
  124. * Helper function which verifies a fullspot
  125. */
  126. public function verifyFullSpot($spot)
  127. {
  128. if ((empty($spot['user-signature'])) || (empty($spot['user-key']))) {
  129. return false;
  130. } // if
  131. $verified = $this->checkRsaSignature('<'.$spot['messageid'].'>', $spot['user-signature'], $spot['user-key'], false);
  132. if ((!$verified) && (!empty($spot['xml-signature']))) {
  133. $verified = $this->checkRsaSignature($spot['xml-signature'], $spot['user-signature'], $spot['user-key'], false);
  134. } // if
  135. return $verified;
  136. }
  137. // verifyFullSpot()
  138. /*
  139. * Helper function to verify a comment header
  140. */
  141. public function verifyComment($comment)
  142. {
  143. $verified = false;
  144. if ((!empty($comment['user-signature'])) && (!empty($comment['user-key']))) {
  145. $verified = $this->checkRsaSignature('<'.$comment['messageid'].'>', $comment['user-signature'], $comment['user-key'], false);
  146. if (!$verified) {
  147. $verified = $this->checkRsaSignature(
  148. '<'.$comment['messageid'].'>'.
  149. implode("\r\n", $comment['body'])."\r\n".
  150. $comment['fromhdr'],
  151. $comment['user-signature'],
  152. $comment['user-key'],
  153. false
  154. );
  155. } // if
  156. } // if
  157. /*
  158. * When a spot is valid with regards to an RSA signature, we can also check the users'
  159. * hash, which also should validate. This hash is a so-called hashcash and is only
  160. * meant to require CPU power on the posting clinet preventing floods.
  161. *
  162. * Currently, some buggy clients post invalid hash cashes but valid spots so we cannot
  163. * use this yet.
  164. */
  165. if ($verified) {
  166. // $userSignedHash = sha1('<' . $comment['messageid'] . '>', false);
  167. // $verified = (substr($userSignedHash, 0, 4) == '0000');
  168. } // if
  169. return $verified;
  170. }
  171. // verifyComment()
  172. /*
  173. * Calculates an SHA1 hash of a message until the first bytes match 0000. Please use
  174. * the JavaScript variant for this
  175. */
  176. public function makeExpensiveHash($prefix, $suffix)
  177. {
  178. $runCount = 0;
  179. $hash = $prefix.$suffix;
  180. while (substr($hash, 0, 4) !== '0000') {
  181. if ($runCount > 400000) {
  182. throw new Exception('Unable to calculate SHA1 hash: '.$runCount);
  183. } // if
  184. $runCount++;
  185. $uniquePart = $this->makeRandomStr(15);
  186. $hash = sha1($prefix.$uniquePart.$suffix, false);
  187. } // while
  188. return $prefix.$uniquePart.$suffix;
  189. }
  190. // makeExpensiveHash
  191. /*
  192. * Creates a random string of $len length with A-z0-9
  193. */
  194. public function makeRandomStr($len)
  195. {
  196. $possibleChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
  197. $unique = '';
  198. for ($i = 0; $i < $len; $i++) {
  199. $unique .= $possibleChars[mt_rand(0, strlen($possibleChars) - 1)];
  200. } // for
  201. return $unique;
  202. }
  203. // makeRandomStr
  204. } // Services_Signing_Base