PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/library/Zend/InfoCard/XML/Security.php

https://github.com/mfairchild365/zf2
PHP | 279 lines | 138 code | 53 blank | 88 comment | 15 complexity | 4c3ee150cd080b461ea517b9296ad642 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_InfoCard
  17. * @subpackage Zend_InfoCard_Xml_Security
  18. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. */
  21. /**
  22. * @namespace
  23. */
  24. namespace Zend\InfoCard\XML;
  25. /**
  26. * @uses \Zend\InfoCard\XML\Security\Exception
  27. * @uses \Zend\InfoCard\XML\Security\Transform\TransformChain
  28. * @category Zend
  29. * @package Zend_InfoCard
  30. * @subpackage Zend_InfoCard_Xml_Security
  31. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  32. * @license http://framework.zend.com/license/new-bsd New BSD License
  33. */
  34. class Security
  35. {
  36. /**
  37. * ASN.1 type INTEGER class
  38. */
  39. const ASN_TYPE_INTEGER = 0x02;
  40. /**
  41. * ASN.1 type BIT STRING class
  42. */
  43. const ASN_TYPE_BITSTRING = 0x03;
  44. /**
  45. * ASN.1 type SEQUENCE class
  46. */
  47. const ASN_TYPE_SEQUENCE = 0x30;
  48. /**
  49. * The URI for Canonical Method C14N Exclusive
  50. */
  51. const CANONICAL_METHOD_C14N_EXC = 'http://www.w3.org/2001/10/xml-exc-c14n#';
  52. /**
  53. * The URI for Signature Method SHA1
  54. */
  55. const SIGNATURE_METHOD_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
  56. /**
  57. * The URI for Digest Method SHA1
  58. */
  59. const DIGEST_METHOD_SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1';
  60. /**
  61. * The Identifier for RSA Keys
  62. */
  63. const RSA_KEY_IDENTIFIER = '300D06092A864886F70D0101010500';
  64. /**
  65. * Constructor (disabled)
  66. *
  67. * @return void
  68. */
  69. private function __construct()
  70. {
  71. }
  72. /**
  73. * Validates the signature of a provided XML block
  74. *
  75. * @param string $strXMLInput An XML block containing a Signature
  76. * @return bool True if the signature validated, false otherwise
  77. * @throws \Zend\InfoCard\XML\Security\Exception
  78. */
  79. static public function validateXMLSignature($strXMLInput)
  80. {
  81. if(!extension_loaded('openssl')) {
  82. throw new Security\Exception\ExtensionNotLoadedException("You must have the openssl extension installed to use this class");
  83. }
  84. $sxe = simplexml_load_string($strXMLInput);
  85. if(!isset($sxe->Signature)) {
  86. throw new Security\Exception\InvalidArgumentException("Could not identify XML Signature element");
  87. }
  88. if(!isset($sxe->Signature->SignedInfo)) {
  89. throw new Security\Exception\InvalidArgumentException("Signature is missing a SignedInfo block");
  90. }
  91. if(!isset($sxe->Signature->SignatureValue)) {
  92. throw new Security\Exception\InvalidArgumentException("Signature is missing a SignatureValue block");
  93. }
  94. if(!isset($sxe->Signature->KeyInfo)) {
  95. throw new Security\Exception\InvalidArgumentException("Signature is missing a KeyInfo block");
  96. }
  97. if(!isset($sxe->Signature->KeyInfo->KeyValue)) {
  98. throw new Security\Exception\InvalidArgumentException("Signature is missing a KeyValue block");
  99. }
  100. switch((string)$sxe->Signature->SignedInfo->CanonicalizationMethod['Algorithm']) {
  101. case self::CANONICAL_METHOD_C14N_EXC:
  102. $cMethod = (string)$sxe->Signature->SignedInfo->CanonicalizationMethod['Algorithm'];
  103. break;
  104. default:
  105. throw new Security\Exception\InvalidArgumentException("Unknown or unsupported CanonicalizationMethod Requested");
  106. break;
  107. }
  108. switch((string)$sxe->Signature->SignedInfo->SignatureMethod['Algorithm']) {
  109. case self::SIGNATURE_METHOD_SHA1:
  110. $sMethod = (string)$sxe->Signature->SignedInfo->SignatureMethod['Algorithm'];
  111. break;
  112. default:
  113. throw new Security\Exception\InvalidArgumentException("Unknown or unsupported SignatureMethod Requested");
  114. break;
  115. }
  116. switch((string)$sxe->Signature->SignedInfo->Reference->DigestMethod['Algorithm']) {
  117. case self::DIGEST_METHOD_SHA1:
  118. $dMethod = (string)$sxe->Signature->SignedInfo->Reference->DigestMethod['Algorithm'];
  119. break;
  120. default:
  121. throw new Security\Exception\InvalidArgumentException("Unknown or unsupported DigestMethod Requested");
  122. break;
  123. }
  124. $dValue = base64_decode((string)$sxe->Signature->SignedInfo->Reference->DigestValue, true);
  125. $signatureValue = base64_decode((string)$sxe->Signature->SignatureValue, true);
  126. $transformer = new Security\Transform\TransformChain();
  127. foreach($sxe->Signature->SignedInfo->Reference->Transforms->children() as $transform) {
  128. $transformer->addTransform((string)$transform['Algorithm']);
  129. }
  130. $transformed_xml = $transformer->applyTransforms($strXMLInput);
  131. $transformed_xml_binhash = pack("H*", sha1($transformed_xml));
  132. if($transformed_xml_binhash != $dValue) {
  133. throw new Security\Exception\RuntimeException("Locally Transformed XML does not match XML Document. Cannot Verify Signature");
  134. }
  135. $public_key = null;
  136. switch(true) {
  137. case isset($sxe->Signature->KeyInfo->KeyValue->X509Certificate):
  138. $certificate = (string)$sxe->Signature->KeyInfo->KeyValue->X509Certificate;
  139. $pem = "-----BEGIN CERTIFICATE-----\n" .
  140. wordwrap($certificate, 64, "\n", true) .
  141. "\n-----END CERTIFICATE-----";
  142. $public_key = openssl_pkey_get_public($pem);
  143. if(!$public_key) {
  144. throw new Security\Exception\RuntimeException("Unable to extract and prcoess X509 Certificate from KeyValue");
  145. }
  146. break;
  147. case isset($sxe->Signature->KeyInfo->KeyValue->RSAKeyValue):
  148. if(!isset($sxe->Signature->KeyInfo->KeyValue->RSAKeyValue->Modulus) ||
  149. !isset($sxe->Signature->KeyInfo->KeyValue->RSAKeyValue->Exponent)) {
  150. throw new Security\Exception\InvalidArgumentException("RSA Key Value not in Modulus/Exponent form");
  151. }
  152. $modulus = base64_decode((string)$sxe->Signature->KeyInfo->KeyValue->RSAKeyValue->Modulus);
  153. $exponent = base64_decode((string)$sxe->Signature->KeyInfo->KeyValue->RSAKeyValue->Exponent);
  154. $pem_public_key = self::_getPublicKeyFromModExp($modulus, $exponent);
  155. $public_key = openssl_pkey_get_public ($pem_public_key);
  156. break;
  157. default:
  158. throw new Security\Exception\RuntimeException("Unable to determine or unsupported representation of the KeyValue block");
  159. }
  160. $transformer = new Security\Transform\TransformChain();
  161. $transformer->addTransform((string)$sxe->Signature->SignedInfo->CanonicalizationMethod['Algorithm']);
  162. // The way we are doing our XML processing requires that we specifically add this
  163. // (even though it's in the <Signature> parent-block).. otherwise, our canonical form
  164. // fails signature verification
  165. $sxe->Signature->SignedInfo->addAttribute('xmlns', 'http://www.w3.org/2000/09/xmldsig#');
  166. $canonical_signedinfo = $transformer->applyTransforms($sxe->Signature->SignedInfo->asXML());
  167. if(@openssl_verify($canonical_signedinfo, $signatureValue, $public_key)) {
  168. return (string)$sxe->Signature->SignedInfo->Reference['URI'];
  169. }
  170. return false;
  171. }
  172. /**
  173. * Transform an RSA Key in Modulus/Exponent format into a PEM encoding and
  174. * return an openssl resource for it
  175. *
  176. * @param string $modulus The RSA Modulus in binary format
  177. * @param string $exponent The RSA exponent in binary format
  178. * @return string The PEM encoded version of the key
  179. */
  180. static protected function _getPublicKeyFromModExp($modulus, $exponent)
  181. {
  182. $modulusInteger = self::_encodeValue($modulus, self::ASN_TYPE_INTEGER);
  183. $exponentInteger = self::_encodeValue($exponent, self::ASN_TYPE_INTEGER);
  184. $modExpSequence = self::_encodeValue($modulusInteger . $exponentInteger, self::ASN_TYPE_SEQUENCE);
  185. $modExpBitString = self::_encodeValue($modExpSequence, self::ASN_TYPE_BITSTRING);
  186. $binRsaKeyIdentifier = pack( "H*", self::RSA_KEY_IDENTIFIER );
  187. $publicKeySequence = self::_encodeValue($binRsaKeyIdentifier . $modExpBitString, self::ASN_TYPE_SEQUENCE);
  188. $publicKeyInfoBase64 = base64_encode( $publicKeySequence );
  189. $publicKeyString = "-----BEGIN PUBLIC KEY-----\n";
  190. $publicKeyString .= wordwrap($publicKeyInfoBase64, 64, "\n", true);
  191. $publicKeyString .= "\n-----END PUBLIC KEY-----\n";
  192. return $publicKeyString;
  193. }
  194. /**
  195. * Encode a limited set of data types into ASN.1 encoding format
  196. * which is used in X.509 certificates
  197. *
  198. * @param string $data The data to encode
  199. * @param const $type The encoding format constant
  200. * @return string The encoded value
  201. * @throws \Zend\InfoCard\XML\Security\Exception
  202. */
  203. static protected function _encodeValue($data, $type)
  204. {
  205. // Null pad some data when we get it (integer values > 128 and bitstrings)
  206. if( (($type == self::ASN_TYPE_INTEGER) && (ord($data) > 0x7f)) ||
  207. ($type == self::ASN_TYPE_BITSTRING)) {
  208. $data = "\0$data";
  209. }
  210. $len = strlen($data);
  211. // encode the value based on length of the string
  212. // I'm fairly confident that this is by no means a complete implementation
  213. // but it is enough for our purposes
  214. switch(true) {
  215. case ($len < 128):
  216. return sprintf("%c%c%s", $type, $len, $data);
  217. case ($len < 0x0100):
  218. return sprintf("%c%c%c%s", $type, 0x81, $len, $data);
  219. case ($len < 0x010000):
  220. return sprintf("%c%c%c%c%s", $type, 0x82, $len / 0x0100, $len % 0x0100, $data);
  221. default:
  222. throw new Security\Exception\RuntimeException("Could not encode value");
  223. }
  224. throw new Security\Exception\RuntimeException("Invalid code path");
  225. }
  226. }