PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/XmlSecurity/Key.php

https://bitbucket.org/shareworks/xml-security
PHP | 522 lines | 413 code | 66 blank | 43 comment | 97 complexity | 4ba0b5a77ec012501fa345c030174f6f MD5 | raw file
  1. <?php
  2. namespace XmlSecurity;
  3. class Key
  4. {
  5. const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
  6. const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
  7. const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
  8. const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
  9. const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
  10. const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
  11. const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
  12. const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
  13. const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
  14. const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
  15. const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
  16. private $cryptParams = array();
  17. public $type = 0;
  18. public $key = null;
  19. public $passphrase = "";
  20. public $iv = null;
  21. public $name = null;
  22. public $keyChain = null;
  23. public $isEncrypted = false;
  24. public $encryptedCtx = null;
  25. public $guid = null;
  26. /**
  27. * This variable contains the certificate as a string if this key represents an X509-certificate.
  28. * If this key doesn't represent a certificate, this will be NULL.
  29. */
  30. private $x509Certificate = null;
  31. /* This variable contains the certificate thunbprint if we have loaded an X509-certificate. */
  32. private $X509Thumbprint = null;
  33. public function __construct($type, $params = null)
  34. {
  35. srand();
  36. switch ($type) {
  37. case (Key::TRIPLEDES_CBC):
  38. $this->cryptParams['library'] = 'mcrypt';
  39. $this->cryptParams['cipher'] = MCRYPT_TRIPLEDES;
  40. $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
  41. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
  42. $this->cryptParams['keysize'] = 24;
  43. break;
  44. case (Key::AES128_CBC):
  45. $this->cryptParams['library'] = 'mcrypt';
  46. $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
  47. $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
  48. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
  49. $this->cryptParams['keysize'] = 16;
  50. break;
  51. case (Key::AES192_CBC):
  52. $this->cryptParams['library'] = 'mcrypt';
  53. $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
  54. $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
  55. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
  56. $this->cryptParams['keysize'] = 24;
  57. break;
  58. case (Key::AES256_CBC):
  59. $this->cryptParams['library'] = 'mcrypt';
  60. $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
  61. $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
  62. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
  63. $this->cryptParams['keysize'] = 32;
  64. break;
  65. case (Key::RSA_1_5):
  66. $this->cryptParams['library'] = 'openssl';
  67. $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  68. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
  69. if (is_array($params) && !empty($params['type'])) {
  70. if ($params['type'] == 'public' || $params['type'] == 'private') {
  71. $this->cryptParams['type'] = $params['type'];
  72. break;
  73. }
  74. }
  75. throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
  76. return;
  77. case (Key::RSA_OAEP_MGF1P):
  78. $this->cryptParams['library'] = 'openssl';
  79. $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
  80. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
  81. $this->cryptParams['hash'] = null;
  82. if (is_array($params) && !empty($params['type'])) {
  83. if ($params['type'] == 'public' || $params['type'] == 'private') {
  84. $this->cryptParams['type'] = $params['type'];
  85. break;
  86. }
  87. }
  88. throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
  89. return;
  90. case (Key::RSA_SHA1):
  91. $this->cryptParams['library'] = 'openssl';
  92. $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
  93. $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  94. if (is_array($params) && !empty($params['type'])) {
  95. if ($params['type'] == 'public' || $params['type'] == 'private') {
  96. $this->cryptParams['type'] = $params['type'];
  97. break;
  98. }
  99. }
  100. throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
  101. break;
  102. case (Key::RSA_SHA256):
  103. $this->cryptParams['library'] = 'openssl';
  104. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
  105. $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  106. $this->cryptParams['digest'] = 'SHA256';
  107. if (is_array($params) && !empty($params['type'])) {
  108. if ($params['type'] == 'public' || $params['type'] == 'private') {
  109. $this->cryptParams['type'] = $params['type'];
  110. break;
  111. }
  112. }
  113. throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
  114. break;
  115. case (Key::RSA_SHA384):
  116. $this->cryptParams['library'] = 'openssl';
  117. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
  118. $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  119. $this->cryptParams['digest'] = 'SHA384';
  120. if (is_array($params) && !empty($params['type'])) {
  121. if ($params['type'] == 'public' || $params['type'] == 'private') {
  122. $this->cryptParams['type'] = $params['type'];
  123. break;
  124. }
  125. }
  126. case (Key::RSA_SHA512):
  127. $this->cryptParams['library'] = 'openssl';
  128. $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
  129. $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  130. $this->cryptParams['digest'] = 'SHA512';
  131. if (is_array($params) && !empty($params['type'])) {
  132. if ($params['type'] == 'public' || $params['type'] == 'private') {
  133. $this->cryptParams['type'] = $params['type'];
  134. break;
  135. }
  136. }
  137. default:
  138. throw new \Exception('Invalid Key Type');
  139. return;
  140. }
  141. $this->type = $type;
  142. }
  143. /**
  144. * Retrieve the key size for the symmetric encryption algorithm..
  145. *
  146. * If the key size is unknown, or this isn't a symmetric encryption algorithm,
  147. * NULL is returned.
  148. *
  149. * @return int|NULL The number of bytes in the key.
  150. */
  151. public function getSymmetricKeySize()
  152. {
  153. if (!isset($this->cryptParams['keysize'])) {
  154. return null;
  155. }
  156. return $this->cryptParams['keysize'];
  157. }
  158. public function generateSessionKey()
  159. {
  160. if (!isset($this->cryptParams['keysize'])) {
  161. throw new \Exception('Unknown key size for type "' . $this->type . '".');
  162. }
  163. $keysize = $this->cryptParams['keysize'];
  164. if (function_exists('openssl_random_pseudo_bytes')) {
  165. /* We have PHP >= 5.3 - use openssl to generate session key. */
  166. $key = openssl_random_pseudo_bytes($keysize);
  167. } else {
  168. /* Generating random key using iv generation routines */
  169. $key = mcrypt_create_iv($keysize, MCRYPT_RAND);
  170. }
  171. if ($this->type === Key::TRIPLEDES_CBC) {
  172. /* Make sure that the generated key has the proper parity bits set.
  173. * Mcrypt doesn't care about the parity bits, but others may care.
  174. */
  175. for ($i = 0; $i < strlen($key); $i++) {
  176. $byte = ord($key[$i]) & 0xfe;
  177. $parity = 1;
  178. for ($j = 1; $j < 8; $j++) {
  179. $parity ^= ($byte >> $j) & 1;
  180. }
  181. $byte |= $parity;
  182. $key[$i] = chr($byte);
  183. }
  184. }
  185. $this->key = $key;
  186. return $key;
  187. }
  188. public static function getRawThumbprint($cert)
  189. {
  190. $arCert = explode("\n", $cert);
  191. $data = '';
  192. $inData = false;
  193. foreach ($arCert AS $curData) {
  194. if (!$inData) {
  195. if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
  196. $inData = true;
  197. }
  198. } else {
  199. if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
  200. $inData = false;
  201. break;
  202. }
  203. $data .= trim($curData);
  204. }
  205. }
  206. if (!empty($data)) {
  207. return strtolower(sha1(base64_decode($data)));
  208. }
  209. return null;
  210. }
  211. public function loadKey($key, $isFile = false, $isCert = false)
  212. {
  213. if ($isFile) {
  214. $this->key = file_get_contents($key);
  215. } else {
  216. $this->key = $key;
  217. }
  218. if ($isCert) {
  219. $this->key = openssl_x509_read($this->key);
  220. openssl_x509_export($this->key, $str_cert);
  221. $this->x509Certificate = $str_cert;
  222. $this->key = $str_cert;
  223. } else {
  224. $this->x509Certificate = null;
  225. }
  226. if ($this->cryptParams['library'] == 'openssl') {
  227. if ($this->cryptParams['type'] == 'public') {
  228. if ($isCert) {
  229. /* Load the thumbprint if this is an X509 certificate. */
  230. $this->X509Thumbprint = self::getRawThumbprint($this->key);
  231. }
  232. $this->key = openssl_get_publickey($this->key);
  233. } else {
  234. $this->key = openssl_get_privatekey($this->key, $this->passphrase);
  235. }
  236. } else {
  237. if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
  238. /* Check key length */
  239. switch ($this->type) {
  240. case (Key::AES256_CBC):
  241. if (strlen($this->key) < 25) {
  242. throw new \Exception('Key must contain at least 25 characters for this cipher');
  243. }
  244. break;
  245. case (Key::AES192_CBC):
  246. if (strlen($this->key) < 17) {
  247. throw new \Exception('Key must contain at least 17 characters for this cipher');
  248. }
  249. break;
  250. }
  251. }
  252. }
  253. }
  254. private function encryptMcrypt($data)
  255. {
  256. $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
  257. $this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
  258. mcrypt_generic_init($td, $this->key, $this->iv);
  259. if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
  260. $bs = mcrypt_enc_get_block_size($td);
  261. for ($datalen0 = $datalen = strlen($data); (($datalen % $bs) != ($bs - 1)); $datalen++)
  262. $data .= chr(rand(1, 127));
  263. $data .= chr($datalen - $datalen0 + 1);
  264. }
  265. $encrypted_data = $this->iv . mcrypt_generic($td, $data);
  266. mcrypt_generic_deinit($td);
  267. mcrypt_module_close($td);
  268. return $encrypted_data;
  269. }
  270. private function decryptMcrypt($data)
  271. {
  272. $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
  273. $iv_length = mcrypt_enc_get_iv_size($td);
  274. $this->iv = substr($data, 0, $iv_length);
  275. $data = substr($data, $iv_length);
  276. mcrypt_generic_init($td, $this->key, $this->iv);
  277. $decrypted_data = mdecrypt_generic($td, $data);
  278. mcrypt_generic_deinit($td);
  279. mcrypt_module_close($td);
  280. if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
  281. $dataLen = strlen($decrypted_data);
  282. $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
  283. $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
  284. }
  285. return $decrypted_data;
  286. }
  287. private function encryptOpenSSL($data)
  288. {
  289. if ($this->cryptParams['type'] == 'public') {
  290. if (!openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
  291. throw new \Exception('Failure encrypting Data');
  292. }
  293. } elseif (!openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
  294. throw new \Exception('Failure encrypting Data');
  295. }
  296. return $encrypted_data;
  297. }
  298. private function decryptOpenSSL($data)
  299. {
  300. if ($this->cryptParams['type'] == 'public') {
  301. if (!openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
  302. throw new \Exception('Failure decrypting Data');
  303. }
  304. } elseif (!openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
  305. throw new \Exception('Failure decrypting Data');
  306. }
  307. return $decrypted;
  308. }
  309. private function signOpenSSL($data)
  310. {
  311. $algo = OPENSSL_ALGO_SHA1;
  312. if (!empty($this->cryptParams['digest'])) {
  313. $algo = $this->cryptParams['digest'];
  314. }
  315. if (!openssl_sign($data, $signature, $this->key, $algo)) {
  316. throw new \Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
  317. }
  318. return $signature;
  319. }
  320. private function verifyOpenSSL($data, $signature)
  321. {
  322. $algo = OPENSSL_ALGO_SHA1;
  323. if (!empty($this->cryptParams['digest'])) {
  324. $algo = $this->cryptParams['digest'];
  325. }
  326. return openssl_verify($data, $signature, $this->key, $algo);
  327. }
  328. public function encryptData($data)
  329. {
  330. switch ($this->cryptParams['library']) {
  331. case 'mcrypt':
  332. return $this->encryptMcrypt($data);
  333. break;
  334. case 'openssl':
  335. return $this->encryptOpenSSL($data);
  336. break;
  337. }
  338. }
  339. public function decryptData($data)
  340. {
  341. switch ($this->cryptParams['library']) {
  342. case 'mcrypt':
  343. return $this->decryptMcrypt($data);
  344. break;
  345. case 'openssl':
  346. return $this->decryptOpenSSL($data);
  347. break;
  348. }
  349. }
  350. public function signData($data)
  351. {
  352. switch ($this->cryptParams['library']) {
  353. case 'openssl':
  354. return $this->signOpenSSL($data);
  355. break;
  356. }
  357. }
  358. public function verifySignature($data, $signature)
  359. {
  360. switch ($this->cryptParams['library']) {
  361. case 'openssl':
  362. return $this->verifyOpenSSL($data, $signature);
  363. break;
  364. }
  365. }
  366. public function getAlgorith()
  367. {
  368. return $this->cryptParams['method'];
  369. }
  370. static function makeAsnSegment($type, $string)
  371. {
  372. switch ($type) {
  373. case 0x02:
  374. if (ord($string) > 0x7f)
  375. $string = chr(0) . $string;
  376. break;
  377. case 0x03:
  378. $string = chr(0) . $string;
  379. break;
  380. }
  381. $length = strlen($string);
  382. if ($length < 128) {
  383. $output = sprintf("%c%c%s", $type, $length, $string);
  384. } else if ($length < 0x0100) {
  385. $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
  386. } else if ($length < 0x010000) {
  387. $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
  388. } else {
  389. $output = null;
  390. }
  391. return ($output);
  392. }
  393. /* Modulus and Exponent must already be base64 decoded */
  394. static function convertRSA($modulus, $exponent)
  395. {
  396. /* make an ASN publicKeyInfo */
  397. $exponentEncoding = Key::makeAsnSegment(0x02, $exponent);
  398. $modulusEncoding = Key::makeAsnSegment(0x02, $modulus);
  399. $sequenceEncoding = Key:: makeAsnSegment(0x30, $modulusEncoding . $exponentEncoding);
  400. $bitstringEncoding = Key::makeAsnSegment(0x03, $sequenceEncoding);
  401. $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
  402. $publicKeyInfo = Key::makeAsnSegment(0x30, $rsaAlgorithmIdentifier . $bitstringEncoding);
  403. /* encode the publicKeyInfo in base64 and add PEM brackets */
  404. $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
  405. $encoding = "-----BEGIN PUBLIC KEY-----\n";
  406. $offset = 0;
  407. while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
  408. $encoding = $encoding . $segment . "\n";
  409. $offset += 64;
  410. }
  411. return $encoding . "-----END PUBLIC KEY-----\n";
  412. }
  413. public function serializeKey($parent)
  414. {
  415. }
  416. /**
  417. * Retrieve the X509 certificate this key represents.
  418. *
  419. * Will return the X509 certificate in PEM-format if this key represents
  420. * an X509 certificate.
  421. *
  422. * @return The X509 certificate or NULL if this key doesn't represent an X509-certificate.
  423. */
  424. public function getX509Certificate()
  425. {
  426. return $this->x509Certificate;
  427. }
  428. /* Get the thumbprint of this X509 certificate.
  429. *
  430. * Returns:
  431. * The thumbprint as a lowercase 40-character hexadecimal number, or NULL
  432. * if this isn't a X509 certificate.
  433. */
  434. public function getX509Thumbprint()
  435. {
  436. return $this->X509Thumbprint;
  437. }
  438. /**
  439. * Create key from an EncryptedKey-element.
  440. *
  441. * @param DOMElement $element The EncryptedKey-element.
  442. * @return Key The new key.
  443. */
  444. public static function fromEncryptedKeyElement(DOMElement $element)
  445. {
  446. $objenc = new Encoder();
  447. $objenc->setNode($element);
  448. if (!$objKey = $objenc->locateKey()) {
  449. throw new \Exception("Unable to locate algorithm for this Encrypted Key");
  450. }
  451. $objKey->isEncrypted = true;
  452. $objKey->encryptedCtx = $objenc;
  453. Encoder::staticLocateKeyInfo($objKey, $element);
  454. return $objKey;
  455. }
  456. }