web/Zend/InfoCard.php
changeset 0 4eba9c11703f
equal deleted inserted replaced
-1:000000000000 0:4eba9c11703f
       
     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  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  * @version    $Id: InfoCard.php 20096 2010-01-06 02:05:09Z bkarwin $
       
    20  */
       
    21 
       
    22 /**
       
    23  * Zend_InfoCard_Xml_EncryptedData
       
    24  */
       
    25 require_once 'Zend/InfoCard/Xml/EncryptedData.php';
       
    26 
       
    27 /**
       
    28  * Zend_InfoCard_Xml_Assertion
       
    29  */
       
    30 require_once 'Zend/InfoCard/Xml/Assertion.php';
       
    31 
       
    32 /**
       
    33  * Zend_InfoCard_Cipher
       
    34  */
       
    35 require_once 'Zend/InfoCard/Cipher.php';
       
    36 
       
    37 /**
       
    38  * Zend_InfoCard_Xml_Security
       
    39  */
       
    40 require_once 'Zend/InfoCard/Xml/Security.php';
       
    41 
       
    42 /**
       
    43  * Zend_InfoCard_Adapter_Interface
       
    44  */
       
    45 require_once 'Zend/InfoCard/Adapter/Interface.php';
       
    46 
       
    47 /**
       
    48  * Zend_InfoCard_Claims
       
    49  */
       
    50 require_once 'Zend/InfoCard/Claims.php';
       
    51 
       
    52 /**
       
    53  * @category   Zend
       
    54  * @package    Zend_InfoCard
       
    55  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    56  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    57  */
       
    58 class Zend_InfoCard
       
    59 {
       
    60     /**
       
    61      * URI for XML Digital Signature SHA1 Digests
       
    62      */
       
    63     const DIGEST_SHA1        = 'http://www.w3.org/2000/09/xmldsig#sha1';
       
    64 
       
    65     /**
       
    66      * An array of certificate pair files and optional passwords for them to search
       
    67      * when trying to determine which certificate was used to encrypt the transient key
       
    68      *
       
    69      * @var Array
       
    70      */
       
    71     protected $_keyPairs;
       
    72 
       
    73     /**
       
    74      * The instance to use to decrypt public-key encrypted data
       
    75      *
       
    76      * @var Zend_InfoCard_Cipher_Pki_Interface
       
    77      */
       
    78     protected $_pkiCipherObj;
       
    79 
       
    80     /**
       
    81      * The instance to use to decrypt symmetric encrypted data
       
    82      *
       
    83      * @var Zend_InfoCard_Cipher_Symmetric_Interface
       
    84      */
       
    85     protected $_symCipherObj;
       
    86 
       
    87     /**
       
    88      * The InfoCard Adapter to use for callbacks into the application using the component
       
    89      * such as when storing assertions, etc.
       
    90      *
       
    91      * @var Zend_InfoCard_Adapter_Interface
       
    92      */
       
    93     protected $_adapter;
       
    94 
       
    95 
       
    96     /**
       
    97      * InfoCard Constructor
       
    98      *
       
    99      * @throws Zend_InfoCard_Exception
       
   100      */
       
   101     public function __construct()
       
   102     {
       
   103         $this->_keyPairs = array();
       
   104 
       
   105         if(!extension_loaded('mcrypt')) {
       
   106             require_once 'Zend/InfoCard/Exception.php';
       
   107             throw new Zend_InfoCard_Exception("Use of the Zend_InfoCard component requires the mcrypt extension to be enabled in PHP");
       
   108         }
       
   109 
       
   110         if(!extension_loaded('openssl')) {
       
   111             require_once 'Zend/InfoCard/Exception.php';
       
   112             throw new Zend_InfoCard_Exception("Use of the Zend_InfoCard component requires the openssl extension to be enabled in PHP");
       
   113         }
       
   114     }
       
   115 
       
   116     /**
       
   117      * Sets the adapter uesd for callbacks into the application using the component, used
       
   118      * when doing things such as storing / retrieving assertions, etc.
       
   119      *
       
   120      * @param Zend_InfoCard_Adapter_Interface $a The Adapter instance
       
   121      * @return Zend_InfoCard The instnace
       
   122      */
       
   123     public function setAdapter(Zend_InfoCard_Adapter_Interface $a)
       
   124     {
       
   125         $this->_adapter = $a;
       
   126         return $this;
       
   127     }
       
   128 
       
   129     /**
       
   130      * Retrieves the adapter used for callbacks into the application using the component.
       
   131      * If no adapter was set then an instance of Zend_InfoCard_Adapter_Default is used
       
   132      *
       
   133      * @return Zend_InfoCard_Adapter_Interface The Adapter instance
       
   134      */
       
   135     public function getAdapter()
       
   136     {
       
   137         if($this->_adapter === null) {
       
   138             require_once 'Zend/InfoCard/Adapter/Default.php';
       
   139             $this->setAdapter(new Zend_InfoCard_Adapter_Default());
       
   140         }
       
   141 
       
   142         return $this->_adapter;
       
   143     }
       
   144 
       
   145     /**
       
   146      * Gets the Public Key Cipher object used in this instance
       
   147      *
       
   148      * @return Zend_InfoCard_Cipher_Pki_Interface
       
   149      */
       
   150     public function getPkiCipherObject()
       
   151     {
       
   152         return $this->_pkiCipherObj;
       
   153     }
       
   154 
       
   155     /**
       
   156      * Sets the Public Key Cipher Object used in this instance
       
   157      *
       
   158      * @param Zend_InfoCard_Cipher_Pki_Interface $cipherObj
       
   159      * @return Zend_InfoCard
       
   160      */
       
   161     public function setPkiCipherObject(Zend_InfoCard_Cipher_Pki_Interface $cipherObj)
       
   162     {
       
   163         $this->_pkiCipherObj = $cipherObj;
       
   164         return $this;
       
   165     }
       
   166 
       
   167     /**
       
   168      * Get the Symmetric Cipher Object used in this instance
       
   169      *
       
   170      * @return Zend_InfoCard_Cipher_Symmetric_Interface
       
   171      */
       
   172     public function getSymCipherObject()
       
   173     {
       
   174         return $this->_symCipherObj;
       
   175     }
       
   176 
       
   177     /**
       
   178      * Sets the Symmetric Cipher Object used in this instance
       
   179      *
       
   180      * @param Zend_InfoCard_Cipher_Symmetric_Interface $cipherObj
       
   181      * @return Zend_InfoCard
       
   182      */
       
   183     public function setSymCipherObject($cipherObj)
       
   184     {
       
   185         $this->_symCipherObj = $cipherObj;
       
   186         return $this;
       
   187     }
       
   188 
       
   189     /**
       
   190      * Remove a Certificate Pair by Key ID from the search list
       
   191      *
       
   192      * @throws Zend_InfoCard_Exception
       
   193      * @param string $key_id The Certificate Key ID returned from adding the certificate pair
       
   194      * @return Zend_InfoCard
       
   195      */
       
   196     public function removeCertificatePair($key_id)
       
   197     {
       
   198 
       
   199         if(!key_exists($key_id, $this->_keyPairs)) {
       
   200             require_once 'Zend/InfoCard/Exception.php';
       
   201             throw new Zend_InfoCard_Exception("Attempted to remove unknown key id: $key_id");
       
   202         }
       
   203 
       
   204         unset($this->_keyPairs[$key_id]);
       
   205         return $this;
       
   206     }
       
   207 
       
   208     /**
       
   209      * Add a Certificate Pair to the list of certificates searched by the component
       
   210      *
       
   211      * @throws Zend_InfoCard_Exception
       
   212      * @param string $private_key_file The path to the private key file for the pair
       
   213      * @param string $public_key_file The path to the certificate / public key for the pair
       
   214      * @param string $type (optional) The URI for the type of key pair this is (default RSA with OAEP padding)
       
   215      * @param string $password (optional) The password for the private key file if necessary
       
   216      * @return string A key ID representing this key pair in the component
       
   217      */
       
   218     public function addCertificatePair($private_key_file, $public_key_file, $type = Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P, $password = null)
       
   219     {
       
   220         if(!file_exists($private_key_file) ||
       
   221            !file_exists($public_key_file)) {
       
   222             require_once 'Zend/InfoCard/Exception.php';
       
   223             throw new Zend_InfoCard_Exception("Could not locate the public and private certificate pair files: $private_key_file, $public_key_file");
       
   224         }
       
   225 
       
   226         if(!is_readable($private_key_file) ||
       
   227            !is_readable($public_key_file)) {
       
   228             require_once 'Zend/InfoCard/Exception.php';
       
   229             throw new Zend_InfoCard_Exception("Could not read the public and private certificate pair files (check permissions): $private_key_file, $public_key_file");
       
   230         }
       
   231 
       
   232         $key_id = md5($private_key_file.$public_key_file);
       
   233 
       
   234         if(key_exists($key_id, $this->_keyPairs)) {
       
   235             require_once 'Zend/InfoCard/Exception.php';
       
   236             throw new Zend_InfoCard_Exception("Attempted to add previously existing certificate pair: $private_key_file, $public_key_file");
       
   237         }
       
   238 
       
   239         switch($type) {
       
   240             case Zend_InfoCard_Cipher::ENC_RSA:
       
   241             case Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P:
       
   242                 $this->_keyPairs[$key_id] = array('private' => $private_key_file,
       
   243                                 'public'      => $public_key_file,
       
   244                                 'type_uri'    => $type);
       
   245 
       
   246                 if($password !== null) {
       
   247                     $this->_keyPairs[$key_id]['password'] = $password;
       
   248                 } else {
       
   249                     $this->_keyPairs[$key_id]['password'] = null;
       
   250                 }
       
   251 
       
   252                 return $key_id;
       
   253                 break;
       
   254             default:
       
   255                 require_once 'Zend/InfoCard/Exception.php';
       
   256                 throw new Zend_InfoCard_Exception("Invalid Certificate Pair Type specified: $type");
       
   257         }
       
   258     }
       
   259 
       
   260     /**
       
   261      * Return a Certificate Pair from a key ID
       
   262      *
       
   263      * @throws Zend_InfoCard_Exception
       
   264      * @param string $key_id The Key ID of the certificate pair in the component
       
   265      * @return array An array containing the path to the private/public key files,
       
   266      *               the type URI and the password if provided
       
   267      */
       
   268     public function getCertificatePair($key_id)
       
   269     {
       
   270         if(key_exists($key_id, $this->_keyPairs)) {
       
   271             return $this->_keyPairs[$key_id];
       
   272         }
       
   273 
       
   274         require_once 'Zend/InfoCard/Exception.php';
       
   275         throw new Zend_InfoCard_Exception("Invalid Certificate Pair ID provided: $key_id");
       
   276     }
       
   277 
       
   278     /**
       
   279      * Retrieve the digest of a given public key / certificate using the provided digest
       
   280      * method
       
   281      *
       
   282      * @throws Zend_InfoCard_Exception
       
   283      * @param string $key_id The certificate key id in the component
       
   284      * @param string $digestMethod The URI of the digest method to use (default SHA1)
       
   285      * @return string The digest value in binary format
       
   286      */
       
   287     protected function _getPublicKeyDigest($key_id, $digestMethod = self::DIGEST_SHA1)
       
   288     {
       
   289         $certificatePair = $this->getCertificatePair($key_id);
       
   290 
       
   291         $temp = file($certificatePair['public']);
       
   292         unset($temp[count($temp)-1]);
       
   293         unset($temp[0]);
       
   294         $certificateData = base64_decode(implode("\n", $temp));
       
   295 
       
   296         switch($digestMethod) {
       
   297             case self::DIGEST_SHA1:
       
   298                 $digest_retval = sha1($certificateData, true);
       
   299                 break;
       
   300             default:
       
   301                 require_once 'Zend/InfoCard/Exception.php';
       
   302                 throw new Zend_InfoCard_Exception("Invalid Digest Type Provided: $digestMethod");
       
   303         }
       
   304 
       
   305         return $digest_retval;
       
   306     }
       
   307 
       
   308     /**
       
   309      * Find a certificate pair based on a digest of its public key / certificate file
       
   310      *
       
   311      * @param string $digest The digest value of the public key wanted in binary form
       
   312      * @param string $digestMethod The URI of the digest method used to calculate the digest
       
   313      * @return mixed The Key ID of the matching certificate pair or false if not found
       
   314      */
       
   315     protected function _findCertifiatePairByDigest($digest, $digestMethod = self::DIGEST_SHA1)
       
   316     {
       
   317 
       
   318         foreach($this->_keyPairs as $key_id => $certificate_data) {
       
   319 
       
   320             $cert_digest = $this->_getPublicKeyDigest($key_id, $digestMethod);
       
   321 
       
   322             if($cert_digest == $digest) {
       
   323                 return $key_id;
       
   324             }
       
   325         }
       
   326 
       
   327         return false;
       
   328     }
       
   329 
       
   330     /**
       
   331      * Extracts the Signed Token from an EncryptedData block
       
   332      *
       
   333      * @throws Zend_InfoCard_Exception
       
   334      * @param string $strXmlToken The EncryptedData XML block
       
   335      * @return string The XML of the Signed Token inside of the EncryptedData block
       
   336      */
       
   337     protected function _extractSignedToken($strXmlToken)
       
   338     {
       
   339         $encryptedData = Zend_InfoCard_Xml_EncryptedData::getInstance($strXmlToken);
       
   340 
       
   341         // Determine the Encryption Method used to encrypt the token
       
   342 
       
   343         switch($encryptedData->getEncryptionMethod()) {
       
   344             case Zend_InfoCard_Cipher::ENC_AES128CBC:
       
   345             case Zend_InfoCard_Cipher::ENC_AES256CBC:
       
   346                 break;
       
   347             default:
       
   348                 require_once 'Zend/InfoCard/Exception.php';
       
   349                 throw new Zend_InfoCard_Exception("Unknown Encryption Method used in the secure token");
       
   350         }
       
   351 
       
   352         // Figure out the Key we are using to decrypt the token
       
   353 
       
   354         $keyinfo = $encryptedData->getKeyInfo();
       
   355 
       
   356         if(!($keyinfo instanceof Zend_InfoCard_Xml_KeyInfo_XmlDSig)) {
       
   357             require_once 'Zend/InfoCard/Exception.php';
       
   358             throw new Zend_InfoCard_Exception("Expected a XML digital signature KeyInfo, but was not found");
       
   359         }
       
   360 
       
   361 
       
   362         $encryptedKey = $keyinfo->getEncryptedKey();
       
   363 
       
   364         switch($encryptedKey->getEncryptionMethod()) {
       
   365             case Zend_InfoCard_Cipher::ENC_RSA:
       
   366             case Zend_InfoCard_Cipher::ENC_RSA_OAEP_MGF1P:
       
   367                 break;
       
   368             default:
       
   369                 require_once 'Zend/InfoCard/Exception.php';
       
   370                 throw new Zend_InfoCard_Exception("Unknown Key Encryption Method used in secure token");
       
   371         }
       
   372 
       
   373         $securityTokenRef = $encryptedKey->getKeyInfo()->getSecurityTokenReference();
       
   374 
       
   375         $key_id = $this->_findCertifiatePairByDigest($securityTokenRef->getKeyReference());
       
   376 
       
   377         if(!$key_id) {
       
   378             require_once 'Zend/InfoCard/Exception.php';
       
   379             throw new Zend_InfoCard_Exception("Unable to find key pair used to encrypt symmetric InfoCard Key");
       
   380         }
       
   381 
       
   382         $certificate_pair = $this->getCertificatePair($key_id);
       
   383 
       
   384         // Santity Check
       
   385 
       
   386         if($certificate_pair['type_uri'] != $encryptedKey->getEncryptionMethod()) {
       
   387             require_once 'Zend/InfoCard/Exception.php';
       
   388             throw new Zend_InfoCard_Exception("Certificate Pair which matches digest is not of same algorithm type as document, check addCertificate()");
       
   389         }
       
   390 
       
   391         $PKcipher = Zend_InfoCard_Cipher::getInstanceByURI($encryptedKey->getEncryptionMethod());
       
   392 
       
   393         $base64DecodeSupportsStrictParam = version_compare(PHP_VERSION, '5.2.0', '>=');
       
   394 
       
   395         if ($base64DecodeSupportsStrictParam) {
       
   396             $keyCipherValueBase64Decoded = base64_decode($encryptedKey->getCipherValue(), true);
       
   397         } else {
       
   398             $keyCipherValueBase64Decoded = base64_decode($encryptedKey->getCipherValue());
       
   399         }
       
   400 
       
   401         $symmetricKey = $PKcipher->decrypt(
       
   402             $keyCipherValueBase64Decoded,
       
   403             file_get_contents($certificate_pair['private']),
       
   404             $certificate_pair['password']
       
   405             );
       
   406 
       
   407         $symCipher = Zend_InfoCard_Cipher::getInstanceByURI($encryptedData->getEncryptionMethod());
       
   408 
       
   409         if ($base64DecodeSupportsStrictParam) {
       
   410             $dataCipherValueBase64Decoded = base64_decode($encryptedData->getCipherValue(), true);
       
   411         } else {
       
   412             $dataCipherValueBase64Decoded = base64_decode($encryptedData->getCipherValue());
       
   413         }
       
   414 
       
   415         $signedToken = $symCipher->decrypt($dataCipherValueBase64Decoded, $symmetricKey);
       
   416 
       
   417         return $signedToken;
       
   418     }
       
   419 
       
   420     /**
       
   421      * Process an input Infomation Card EncryptedData block sent from the client,
       
   422      * validate it, and return the claims contained within it on success or an error message on error
       
   423      *
       
   424      * @param string $strXmlToken The XML token sent to the server from the client
       
   425      * @return Zend_Infocard_Claims The Claims object containing the claims, or any errors which occurred
       
   426      */
       
   427     public function process($strXmlToken)
       
   428     {
       
   429 
       
   430         $retval = new Zend_InfoCard_Claims();
       
   431 
       
   432         require_once 'Zend/InfoCard/Exception.php';
       
   433         try {
       
   434             $signedAssertionsXml = $this->_extractSignedToken($strXmlToken);
       
   435         } catch(Zend_InfoCard_Exception $e) {
       
   436             $retval->setError('Failed to extract assertion document');
       
   437             $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE);
       
   438             return $retval;
       
   439         }
       
   440 
       
   441         try {
       
   442             $assertions = Zend_InfoCard_Xml_Assertion::getInstance($signedAssertionsXml);
       
   443         } catch(Zend_InfoCard_Exception $e) {
       
   444             $retval->setError('Failure processing assertion document');
       
   445             $retval->setCode(Zend_InfoCard_Claims::RESULT_PROCESSING_FAILURE);
       
   446             return $retval;
       
   447         }
       
   448 
       
   449         if(!($assertions instanceof Zend_InfoCard_Xml_Assertion_Interface)) {
       
   450             throw new Zend_InfoCard_Exception("Invalid Assertion Object returned");
       
   451         }
       
   452 
       
   453         if(!($reference_id = Zend_InfoCard_Xml_Security::validateXMLSignature($assertions->asXML()))) {
       
   454             $retval->setError("Failure Validating the Signature of the assertion document");
       
   455             $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
       
   456             return $retval;
       
   457         }
       
   458 
       
   459         // The reference id should be locally scoped as far as I know
       
   460         if($reference_id[0] == '#') {
       
   461             $reference_id = substr($reference_id, 1);
       
   462         } else {
       
   463             $retval->setError("Reference of document signature does not reference the local document");
       
   464             $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
       
   465             return $retval;
       
   466         }
       
   467 
       
   468         // Make sure the signature is in reference to the same document as the assertions
       
   469         if($reference_id != $assertions->getAssertionID()) {
       
   470             $retval->setError("Reference of document signature does not reference the local document");
       
   471             $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
       
   472         }
       
   473 
       
   474         // Validate we haven't seen this before and the conditions are acceptable
       
   475         $conditions = $this->getAdapter()->retrieveAssertion($assertions->getAssertionURI(), $assertions->getAssertionID());
       
   476 
       
   477         if($conditions === false) {
       
   478             $conditions = $assertions->getConditions();
       
   479         }
       
   480 
       
   481 
       
   482         if(is_array($condition_error = $assertions->validateConditions($conditions))) {
       
   483             $retval->setError("Conditions of assertion document are not met: {$condition_error[1]} ({$condition_error[0]})");
       
   484             $retval->setCode(Zend_InfoCard_Claims::RESULT_VALIDATION_FAILURE);
       
   485         }
       
   486 
       
   487         $attributes = $assertions->getAttributes();
       
   488 
       
   489         $retval->setClaims($attributes);
       
   490 
       
   491         if($retval->getCode() == 0) {
       
   492             $retval->setCode(Zend_InfoCard_Claims::RESULT_SUCCESS);
       
   493         }
       
   494 
       
   495         return $retval;
       
   496     }
       
   497 }