Subversion Repositories oidplus

Rev

Blame | Last modification | View Log | RSS feed

  1. <?php
  2. namespace RobRichards\XMLSecLibs;
  3.  
  4. use DOMElement;
  5. use Exception;
  6.  
  7. /**
  8.  * xmlseclibs.php
  9.  *
  10.  * Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
  11.  * All rights reserved.
  12.  *
  13.  * Redistribution and use in source and binary forms, with or without
  14.  * modification, are permitted provided that the following conditions
  15.  * are met:
  16.  *
  17.  *   * Redistributions of source code must retain the above copyright
  18.  *     notice, this list of conditions and the following disclaimer.
  19.  *
  20.  *   * Redistributions in binary form must reproduce the above copyright
  21.  *     notice, this list of conditions and the following disclaimer in
  22.  *     the documentation and/or other materials provided with the
  23.  *     distribution.
  24.  *
  25.  *   * Neither the name of Robert Richards nor the names of his
  26.  *     contributors may be used to endorse or promote products derived
  27.  *     from this software without specific prior written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  30.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  31.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  32.  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  33.  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  34.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  35.  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  36.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  37.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  38.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  39.  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  40.  * POSSIBILITY OF SUCH DAMAGE.
  41.  *
  42.  * @author    Robert Richards <rrichards@cdatazone.org>
  43.  * @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
  44.  * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
  45.  */
  46.  
  47. class XMLSecurityKey
  48. {
  49.     const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
  50.     const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
  51.     const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
  52.     const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
  53.     const AES128_GCM = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
  54.     const AES192_GCM = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
  55.     const AES256_GCM = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
  56.     const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
  57.     const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
  58.     const RSA_OAEP = 'http://www.w3.org/2009/xmlenc11#rsa-oaep';
  59.     const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
  60.     const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
  61.     const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
  62.     const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
  63.     const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
  64.     const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
  65.     const AUTHTAG_LENGTH = 16;
  66.  
  67.     /** @var array */
  68.     private $cryptParams = array();
  69.  
  70.     /** @var int|string */
  71.     public $type = 0;
  72.  
  73.     /** @var mixed|null */
  74.     public $key = null;
  75.  
  76.     /** @var string  */
  77.     public $passphrase = "";
  78.  
  79.     /** @var string|null */
  80.     public $iv = null;
  81.  
  82.     /** @var string|null */
  83.     public $name = null;
  84.  
  85.     /** @var mixed|null */
  86.     public $keyChain = null;
  87.  
  88.     /** @var bool */
  89.     public $isEncrypted = false;
  90.  
  91.     /** @var XMLSecEnc|null */
  92.     public $encryptedCtx = null;
  93.  
  94.     /** @var mixed|null */
  95.     public $guid = null;
  96.  
  97.     /**
  98.      * This variable contains the certificate as a string if this key represents an X509-certificate.
  99.      * If this key doesn't represent a certificate, this will be null.
  100.      * @var string|null
  101.      */
  102.     private $x509Certificate = null;
  103.  
  104.     /**
  105.      * This variable contains the certificate thumbprint if we have loaded an X509-certificate.
  106.      * @var string|null
  107.      */
  108.     private $X509Thumbprint = null;
  109.  
  110.     /**
  111.      * @param string $type
  112.      * @param null|array $params
  113.      * @throws Exception
  114.      */
  115.     public function __construct($type, $params=null)
  116.     {
  117.         switch ($type) {
  118.             case (self::TRIPLEDES_CBC):
  119.                 $this->cryptParams['library'] = 'openssl';
  120.                 $this->cryptParams['cipher'] = 'des-ede3-cbc';
  121.                 $this->cryptParams['type'] = 'symmetric';
  122.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
  123.                 $this->cryptParams['keysize'] = 24;
  124.                 $this->cryptParams['blocksize'] = 8;
  125.                 break;
  126.             case (self::AES128_CBC):
  127.                 $this->cryptParams['library'] = 'openssl';
  128.                 $this->cryptParams['cipher'] = 'aes-128-cbc';
  129.                 $this->cryptParams['type'] = 'symmetric';
  130.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
  131.                 $this->cryptParams['keysize'] = 16;
  132.                 $this->cryptParams['blocksize'] = 16;
  133.                 break;
  134.             case (self::AES192_CBC):
  135.                 $this->cryptParams['library'] = 'openssl';
  136.                 $this->cryptParams['cipher'] = 'aes-192-cbc';
  137.                 $this->cryptParams['type'] = 'symmetric';
  138.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
  139.                 $this->cryptParams['keysize'] = 24;
  140.                 $this->cryptParams['blocksize'] = 16;
  141.                 break;
  142.             case (self::AES256_CBC):
  143.                 $this->cryptParams['library'] = 'openssl';
  144.                 $this->cryptParams['cipher'] = 'aes-256-cbc';
  145.                 $this->cryptParams['type'] = 'symmetric';
  146.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
  147.                 $this->cryptParams['keysize'] = 32;
  148.                 $this->cryptParams['blocksize'] = 16;
  149.                 break;
  150.             case (self::AES128_GCM):
  151.                 $this->cryptParams['library'] = 'openssl';
  152.                 $this->cryptParams['cipher'] = 'aes-128-gcm';
  153.                 $this->cryptParams['type'] = 'symmetric';
  154.                 $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
  155.                 $this->cryptParams['keysize'] = 16;
  156.                 $this->cryptParams['blocksize'] = 16;
  157.                 break;
  158.             case (self::AES192_GCM):
  159.                 $this->cryptParams['library'] = 'openssl';
  160.                 $this->cryptParams['cipher'] = 'aes-192-gcm';
  161.                 $this->cryptParams['type'] = 'symmetric';
  162.                 $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
  163.                 $this->cryptParams['keysize'] = 24;
  164.                 $this->cryptParams['blocksize'] = 16;
  165.                 break;
  166.             case (self::AES256_GCM):
  167.                 $this->cryptParams['library'] = 'openssl';
  168.                 $this->cryptParams['cipher'] = 'aes-256-gcm';
  169.                 $this->cryptParams['type'] = 'symmetric';
  170.                 $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
  171.                 $this->cryptParams['keysize'] = 32;
  172.                 $this->cryptParams['blocksize'] = 16;
  173.                 break;
  174.             case (self::RSA_1_5):
  175.                 $this->cryptParams['library'] = 'openssl';
  176.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  177.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
  178.                 if (is_array($params) && ! empty($params['type'])) {
  179.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  180.                         $this->cryptParams['type'] = $params['type'];
  181.                         break;
  182.                     }
  183.                 }
  184.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  185.             case (self::RSA_OAEP_MGF1P):
  186.                 $this->cryptParams['library'] = 'openssl';
  187.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
  188.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
  189.                 $this->cryptParams['hash'] = null;
  190.                 if (is_array($params) && ! empty($params['type'])) {
  191.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  192.                         $this->cryptParams['type'] = $params['type'];
  193.                         break;
  194.                     }
  195.                 }
  196.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  197.             case (self::RSA_OAEP):
  198.                 $this->cryptParams['library'] = 'openssl';
  199.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
  200.                 $this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#rsa-oaep';
  201.                 $this->cryptParams['hash'] = 'http://www.w3.org/2009/xmlenc11#mgf1sha1';
  202.                 if (is_array($params) && ! empty($params['type'])) {
  203.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  204.                         $this->cryptParams['type'] = $params['type'];
  205.                         break;
  206.                     }
  207.                 }
  208.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  209.             case (self::RSA_SHA1):
  210.                 $this->cryptParams['library'] = 'openssl';
  211.                 $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
  212.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  213.                 if (is_array($params) && ! empty($params['type'])) {
  214.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  215.                         $this->cryptParams['type'] = $params['type'];
  216.                         break;
  217.                     }
  218.                 }
  219.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  220.             case (self::RSA_SHA256):
  221.                 $this->cryptParams['library'] = 'openssl';
  222.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
  223.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  224.                 $this->cryptParams['digest'] = 'SHA256';
  225.                 if (is_array($params) && ! empty($params['type'])) {
  226.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  227.                         $this->cryptParams['type'] = $params['type'];
  228.                         break;
  229.                     }
  230.                 }
  231.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  232.             case (self::RSA_SHA384):
  233.                 $this->cryptParams['library'] = 'openssl';
  234.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
  235.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  236.                 $this->cryptParams['digest'] = 'SHA384';
  237.                 if (is_array($params) && ! empty($params['type'])) {
  238.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  239.                         $this->cryptParams['type'] = $params['type'];
  240.                         break;
  241.                     }
  242.                 }
  243.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  244.             case (self::RSA_SHA512):
  245.                 $this->cryptParams['library'] = 'openssl';
  246.                 $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
  247.                 $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  248.                 $this->cryptParams['digest'] = 'SHA512';
  249.                 if (is_array($params) && ! empty($params['type'])) {
  250.                     if ($params['type'] == 'public' || $params['type'] == 'private') {
  251.                         $this->cryptParams['type'] = $params['type'];
  252.                         break;
  253.                     }
  254.                 }
  255.                 throw new Exception('Certificate "type" (private/public) must be passed via parameters');
  256.             case (self::HMAC_SHA1):
  257.                 $this->cryptParams['library'] = $type;
  258.                 $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
  259.                 break;
  260.             default:
  261.                 throw new Exception('Invalid Key Type');
  262.         }
  263.         $this->type = $type;
  264.     }
  265.  
  266.     /**
  267.      * Retrieve the key size for the symmetric encryption algorithm..
  268.      *
  269.      * If the key size is unknown, or this isn't a symmetric encryption algorithm,
  270.      * null is returned.
  271.      *
  272.      * @return int|null  The number of bytes in the key.
  273.      */
  274.     public function getSymmetricKeySize()
  275.     {
  276.         if (! isset($this->cryptParams['keysize'])) {
  277.             return null;
  278.         }
  279.         return $this->cryptParams['keysize'];
  280.     }
  281.  
  282.     /**
  283.      * Generates a session key using the openssl-extension.
  284.      * In case of using DES3-CBC the key is checked for a proper parity bits set.
  285.      * @return string
  286.      * @throws Exception
  287.      */
  288.     public function generateSessionKey()
  289.     {
  290.         if (!isset($this->cryptParams['keysize'])) {
  291.             throw new Exception('Unknown key size for type "' . $this->type . '".');
  292.         }
  293.         $keysize = $this->cryptParams['keysize'];
  294.        
  295.         $key = openssl_random_pseudo_bytes($keysize);
  296.        
  297.         if ($this->type === self::TRIPLEDES_CBC) {
  298.             /* Make sure that the generated key has the proper parity bits set.
  299.              * Mcrypt doesn't care about the parity bits, but others may care.
  300.             */
  301.             for ($i = 0; $i < strlen($key); $i++) {
  302.                 $byte = ord($key[$i]) & 0xfe;
  303.                 $parity = 1;
  304.                 for ($j = 1; $j < 8; $j++) {
  305.                     $parity ^= ($byte >> $j) & 1;
  306.                 }
  307.                 $byte |= $parity;
  308.                 $key[$i] = chr($byte);
  309.             }
  310.         }
  311.        
  312.         $this->key = $key;
  313.         return $key;
  314.     }
  315.  
  316.     /**
  317.      * Get the raw thumbprint of a certificate
  318.      *
  319.      * @param string $cert
  320.      * @return null|string
  321.      */
  322.     public static function getRawThumbprint($cert)
  323.     {
  324.  
  325.         $arCert = explode("\n", $cert);
  326.         $data = '';
  327.         $inData = false;
  328.  
  329.         foreach ($arCert AS $curData) {
  330.             if (! $inData) {
  331.                 if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
  332.                     $inData = true;
  333.                 }
  334.             } else {
  335.                 if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
  336.                     break;
  337.                 }
  338.                 $data .= trim($curData);
  339.             }
  340.         }
  341.  
  342.         if (! empty($data)) {
  343.             return strtolower(sha1(base64_decode($data)));
  344.         }
  345.  
  346.         return null;
  347.     }
  348.  
  349.     /**
  350.      * Loads the given key, or - with isFile set true - the key from the keyfile.
  351.      *
  352.      * @param string $key
  353.      * @param bool $isFile
  354.      * @param bool $isCert
  355.      * @throws Exception
  356.      */
  357.     public function loadKey($key, $isFile=false, $isCert = false)
  358.     {
  359.         if ($isFile) {
  360.             $this->key = file_get_contents($key);
  361.         } else {
  362.             $this->key = $key;
  363.         }
  364.         if ($isCert) {
  365.             $this->key = openssl_x509_read($this->key);
  366.             openssl_x509_export($this->key, $str_cert);
  367.             $this->x509Certificate = $str_cert;
  368.             $this->key = $str_cert;
  369.         } else {
  370.             $this->x509Certificate = null;
  371.         }
  372.         if ($this->cryptParams['library'] == 'openssl') {
  373.             switch ($this->cryptParams['type']) {
  374.                 case 'public':
  375.                         if ($isCert) {
  376.                             /* Load the thumbprint if this is an X509 certificate. */
  377.                             $this->X509Thumbprint = self::getRawThumbprint($this->key);
  378.                         }
  379.                         $this->key = openssl_get_publickey($this->key);
  380.                         if (! $this->key) {
  381.                             throw new Exception('Unable to extract public key');
  382.                         }
  383.                         break;
  384.  
  385.                     case 'private':
  386.                     $this->key = openssl_get_privatekey($this->key, $this->passphrase);
  387.                     break;
  388.  
  389.                 case'symmetric':
  390.                     if (strlen($this->key) < $this->cryptParams['keysize']) {
  391.                         throw new Exception('Key must contain at least '.$this->cryptParams['keysize'].' characters for this cipher, contains '.strlen($this->key));
  392.                     }
  393.                     break;
  394.  
  395.                 default:
  396.                     throw new Exception('Unknown type');
  397.             }
  398.         }
  399.     }
  400.  
  401.     /**
  402.      * ISO 10126 Padding
  403.      *
  404.      * @param string $data
  405.      * @param integer $blockSize
  406.      * @throws Exception
  407.      * @return string
  408.      */
  409.     private function padISO10126($data, $blockSize)
  410.     {
  411.         if ($blockSize > 256) {
  412.             throw new Exception('Block size higher than 256 not allowed');
  413.         }
  414.         $padChr = $blockSize - (strlen($data) % $blockSize);
  415.         $pattern = chr($padChr);
  416.         return $data . str_repeat($pattern, $padChr);
  417.     }
  418.  
  419.     /**
  420.      * Remove ISO 10126 Padding
  421.      *
  422.      * @param string $data
  423.      * @return string
  424.      */
  425.     private function unpadISO10126($data)
  426.     {
  427.         $padChr = substr($data, -1);
  428.         $padLen = ord($padChr);
  429.         return substr($data, 0, -$padLen);
  430.     }
  431.  
  432.     /**
  433.      * Encrypts the given data (string) using the openssl-extension
  434.      *
  435.      * @param string $data
  436.      * @return string
  437.      */
  438.     private function encryptSymmetric($data)
  439.     {
  440.         $this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher']));
  441.         $authTag = null;
  442.         if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
  443.             if (version_compare(PHP_VERSION, '7.1.0') < 0) {
  444.                 throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
  445.             }
  446.             $authTag = openssl_random_pseudo_bytes(self::AUTHTAG_LENGTH);
  447.             $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
  448.         } else {
  449.             $data = $this->padISO10126($data, $this->cryptParams['blocksize']);
  450.             $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
  451.         }
  452.        
  453.         if (false === $encrypted) {
  454.             throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string());
  455.         }
  456.         return $this->iv . $encrypted . $authTag;
  457.     }
  458.  
  459.     /**
  460.      * Decrypts the given data (string) using the openssl-extension
  461.      *
  462.      * @param string $data
  463.      * @return string
  464.      */
  465.     private function decryptSymmetric($data)
  466.     {
  467.         $iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']);
  468.         $this->iv = substr($data, 0, $iv_length);
  469.         $data = substr($data, $iv_length);
  470.         $authTag = null;
  471.         if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
  472.             if (version_compare(PHP_VERSION, '7.1.0') < 0) {
  473.                 throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
  474.             }
  475.             // obtain and remove the authentication tag
  476.             $offset = 0 - self::AUTHTAG_LENGTH;
  477.             $authTag = substr($data, $offset);
  478.             $data = substr($data, 0, $offset);
  479.             $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
  480.         } else {
  481.             $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
  482.         }
  483.        
  484.         if (false === $decrypted) {
  485.             throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string());
  486.         }
  487.         return null !== $authTag ? $decrypted : $this->unpadISO10126($decrypted);
  488.     }
  489.  
  490.     /**
  491.      * Encrypts the given public data (string) using the openssl-extension
  492.      *
  493.      * @param string $data
  494.      * @return string
  495.      * @throws Exception
  496.      */
  497.     private function encryptPublic($data)
  498.     {
  499.         if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
  500.             throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string());
  501.         }
  502.         return $encrypted;
  503.     }
  504.  
  505.     /**
  506.      * Decrypts the given public data (string) using the openssl-extension
  507.      *
  508.      * @param string $data
  509.      * @return string
  510.      * @throws Exception
  511.      */
  512.     private function decryptPublic($data)
  513.     {
  514.         if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
  515.             throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string());
  516.         }
  517.         return $decrypted;
  518.     }
  519.  
  520.     /**
  521.      * Encrypts the given private data (string) using the openssl-extension
  522.      *
  523.      * @param string $data
  524.      * @return string
  525.      * @throws Exception
  526.      */
  527.     private function encryptPrivate($data)
  528.     {
  529.         if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
  530.             throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string());
  531.         }
  532.         return $encrypted;
  533.     }
  534.  
  535.     /**
  536.      * Decrypts the given private data (string) using the openssl-extension
  537.      *
  538.      * @param string $data
  539.      * @return string
  540.      * @throws Exception
  541.      */
  542.     private function decryptPrivate($data)
  543.     {
  544.         if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
  545.             throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string());
  546.         }
  547.         return $decrypted;
  548.     }
  549.  
  550.     /**
  551.      * Signs the given data (string) using the openssl-extension
  552.      *
  553.      * @param string $data
  554.      * @return string
  555.      * @throws Exception
  556.      */
  557.     private function signOpenSSL($data)
  558.     {
  559.         $algo = OPENSSL_ALGO_SHA1;
  560.         if (! empty($this->cryptParams['digest'])) {
  561.             $algo = $this->cryptParams['digest'];
  562.         }
  563.         if (! openssl_sign($data, $signature, $this->key, $algo)) {
  564.             throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
  565.         }
  566.         return $signature;
  567.     }
  568.  
  569.     /**
  570.      * Verifies the given data (string) belonging to the given signature using the openssl-extension
  571.      *
  572.      * Returns:
  573.      *  1 on succesful signature verification,
  574.      *  0 when signature verification failed,
  575.      *  -1 if an error occurred during processing.
  576.      *
  577.      * NOTE: be very careful when checking the return value, because in PHP,
  578.      * -1 will be cast to True when in boolean context. So always check the
  579.      * return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
  580.      *
  581.      * @param string $data
  582.      * @param string $signature
  583.      * @return int
  584.      */
  585.     private function verifyOpenSSL($data, $signature)
  586.     {
  587.         $algo = OPENSSL_ALGO_SHA1;
  588.         if (! empty($this->cryptParams['digest'])) {
  589.             $algo = $this->cryptParams['digest'];
  590.         }
  591.         return openssl_verify($data, $signature, $this->key, $algo);
  592.     }
  593.  
  594.     /**
  595.      * Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
  596.      *
  597.      * @param string $data
  598.      * @return mixed|string
  599.      */
  600.     public function encryptData($data)
  601.     {
  602.         if ($this->cryptParams['library'] === 'openssl') {
  603.             switch ($this->cryptParams['type']) {
  604.                 case 'symmetric':
  605.                     return $this->encryptSymmetric($data);
  606.                 case 'public':
  607.                     return $this->encryptPublic($data);
  608.                 case 'private':
  609.                     return $this->encryptPrivate($data);
  610.             }
  611.         }
  612.     }
  613.  
  614.     /**
  615.      * Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
  616.      *
  617.      * @param string $data
  618.      * @return mixed|string
  619.      */
  620.     public function decryptData($data)
  621.     {
  622.         if ($this->cryptParams['library'] === 'openssl') {
  623.             switch ($this->cryptParams['type']) {
  624.                 case 'symmetric':
  625.                     return $this->decryptSymmetric($data);
  626.                 case 'public':
  627.                     return $this->decryptPublic($data);
  628.                 case 'private':
  629.                     return $this->decryptPrivate($data);
  630.             }
  631.         }
  632.     }
  633.  
  634.     /**
  635.      * Signs the data (string) using the extension assigned to the type in the constructor.
  636.      *
  637.      * @param string $data
  638.      * @return mixed|string
  639.      */
  640.     public function signData($data)
  641.     {
  642.         switch ($this->cryptParams['library']) {
  643.             case 'openssl':
  644.                 return $this->signOpenSSL($data);
  645.             case (self::HMAC_SHA1):
  646.                 return hash_hmac("sha1", $data, $this->key, true);
  647.         }
  648.     }
  649.  
  650.     /**
  651.      * Verifies the data (string) against the given signature using the extension assigned to the type in the constructor.
  652.      *
  653.      * Returns in case of openSSL:
  654.      *  1 on succesful signature verification,
  655.      *  0 when signature verification failed,
  656.      *  -1 if an error occurred during processing.
  657.      *
  658.      * NOTE: be very careful when checking the return value, because in PHP,
  659.      * -1 will be cast to True when in boolean context. So always check the
  660.      * return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
  661.      *
  662.      * @param string $data
  663.      * @param string $signature
  664.      * @return bool|int
  665.      */
  666.     public function verifySignature($data, $signature)
  667.     {
  668.         switch ($this->cryptParams['library']) {
  669.             case 'openssl':
  670.                 return $this->verifyOpenSSL($data, $signature);
  671.             case (self::HMAC_SHA1):
  672.                 $expectedSignature = hash_hmac("sha1", $data, $this->key, true);
  673.                 return strcmp($signature, $expectedSignature) == 0;
  674.         }
  675.     }
  676.  
  677.     /**
  678.      * @deprecated
  679.      * @see getAlgorithm()
  680.      * @return mixed
  681.      */
  682.     public function getAlgorith()
  683.     {
  684.         return $this->getAlgorithm();
  685.     }
  686.  
  687.     /**
  688.      * @return mixed
  689.      */
  690.     public function getAlgorithm()
  691.     {
  692.         return $this->cryptParams['method'];
  693.     }
  694.  
  695.     /**
  696.      *
  697.      * @param int $type
  698.      * @param string $string
  699.      * @return null|string
  700.      */
  701.     public static function makeAsnSegment($type, $string)
  702.     {
  703.         switch ($type) {
  704.             case 0x02:
  705.                 if (ord($string) > 0x7f)
  706.                     $string = chr(0).$string;
  707.                 break;
  708.             case 0x03:
  709.                 $string = chr(0).$string;
  710.                 break;
  711.         }
  712.  
  713.         $length = strlen($string);
  714.  
  715.         if ($length < 128) {
  716.             $output = sprintf("%c%c%s", $type, $length, $string);
  717.         } else if ($length < 0x0100) {
  718.             $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
  719.         } else if ($length < 0x010000) {
  720.             $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
  721.         } else {
  722.             $output = null;
  723.         }
  724.         return $output;
  725.     }
  726.  
  727.     /**
  728.      *
  729.      * Hint: Modulus and Exponent must already be base64 decoded
  730.      * @param string $modulus
  731.      * @param string $exponent
  732.      * @return string
  733.      */
  734.     public static function convertRSA($modulus, $exponent)
  735.     {
  736.         /* make an ASN publicKeyInfo */
  737.         $exponentEncoding = self::makeAsnSegment(0x02, $exponent);
  738.         $modulusEncoding = self::makeAsnSegment(0x02, $modulus);
  739.         $sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
  740.         $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
  741.         $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
  742.         $publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
  743.  
  744.         /* encode the publicKeyInfo in base64 and add PEM brackets */
  745.         $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
  746.         $encoding = "-----BEGIN PUBLIC KEY-----\n";
  747.         $offset = 0;
  748.         while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
  749.             $encoding = $encoding.$segment."\n";
  750.             $offset += 64;
  751.         }
  752.         return $encoding."-----END PUBLIC KEY-----\n";
  753.     }
  754.  
  755.     /**
  756.      * @param mixed $parent
  757.      */
  758.     public function serializeKey($parent)
  759.     {
  760.  
  761.     }
  762.  
  763.     /**
  764.      * Retrieve the X509 certificate this key represents.
  765.      *
  766.      * Will return the X509 certificate in PEM-format if this key represents
  767.      * an X509 certificate.
  768.      *
  769.      * @return string The X509 certificate or null if this key doesn't represent an X509-certificate.
  770.      */
  771.     public function getX509Certificate()
  772.     {
  773.         return $this->x509Certificate;
  774.     }
  775.  
  776.     /**
  777.      * Get the thumbprint of this X509 certificate.
  778.      *
  779.      * Returns:
  780.      *  The thumbprint as a lowercase 40-character hexadecimal number, or null
  781.      *  if this isn't a X509 certificate.
  782.      *
  783.      *  @return string Lowercase 40-character hexadecimal number of thumbprint
  784.      */
  785.     public function getX509Thumbprint()
  786.     {
  787.         return $this->X509Thumbprint;
  788.     }
  789.  
  790.  
  791.     /**
  792.      * Create key from an EncryptedKey-element.
  793.      *
  794.      * @param DOMElement $element The EncryptedKey-element.
  795.      * @throws Exception
  796.      *
  797.      * @return XMLSecurityKey The new key.
  798.      */
  799.     public static function fromEncryptedKeyElement(DOMElement $element)
  800.     {
  801.  
  802.         $objenc = new XMLSecEnc();
  803.         $objenc->setNode($element);
  804.         if (! $objKey = $objenc->locateKey()) {
  805.             throw new Exception("Unable to locate algorithm for this Encrypted Key");
  806.         }
  807.         $objKey->isEncrypted = true;
  808.         $objKey->encryptedCtx = $objenc;
  809.         XMLSecEnc::staticLocateKeyInfo($objKey, $element);
  810.         return $objKey;
  811.     }
  812.  
  813. }
  814.