/lib/XmlSecurity/Key.php
PHP | 522 lines | 413 code | 66 blank | 43 comment | 97 complexity | 4ba0b5a77ec012501fa345c030174f6f MD5 | raw file
- <?php
- namespace XmlSecurity;
- class Key
- {
- const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
- const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
- const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
- const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
- const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
- const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
- const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
- const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
- const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
- const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
- const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
- private $cryptParams = array();
- public $type = 0;
- public $key = null;
- public $passphrase = "";
- public $iv = null;
- public $name = null;
- public $keyChain = null;
- public $isEncrypted = false;
- public $encryptedCtx = null;
- public $guid = null;
- /**
- * This variable contains the certificate as a string if this key represents an X509-certificate.
- * If this key doesn't represent a certificate, this will be NULL.
- */
- private $x509Certificate = null;
- /* This variable contains the certificate thunbprint if we have loaded an X509-certificate. */
- private $X509Thumbprint = null;
- public function __construct($type, $params = null)
- {
- srand();
- switch ($type) {
- case (Key::TRIPLEDES_CBC):
- $this->cryptParams['library'] = 'mcrypt';
- $this->cryptParams['cipher'] = MCRYPT_TRIPLEDES;
- $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
- $this->cryptParams['keysize'] = 24;
- break;
- case (Key::AES128_CBC):
- $this->cryptParams['library'] = 'mcrypt';
- $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
- $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
- $this->cryptParams['keysize'] = 16;
- break;
- case (Key::AES192_CBC):
- $this->cryptParams['library'] = 'mcrypt';
- $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
- $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
- $this->cryptParams['keysize'] = 24;
- break;
- case (Key::AES256_CBC):
- $this->cryptParams['library'] = 'mcrypt';
- $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128;
- $this->cryptParams['mode'] = MCRYPT_MODE_CBC;
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
- $this->cryptParams['keysize'] = 32;
- break;
- case (Key::RSA_1_5):
- $this->cryptParams['library'] = 'openssl';
- $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
- if (is_array($params) && !empty($params['type'])) {
- if ($params['type'] == 'public' || $params['type'] == 'private') {
- $this->cryptParams['type'] = $params['type'];
- break;
- }
- }
- throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
- return;
- case (Key::RSA_OAEP_MGF1P):
- $this->cryptParams['library'] = 'openssl';
- $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
- $this->cryptParams['hash'] = null;
- if (is_array($params) && !empty($params['type'])) {
- if ($params['type'] == 'public' || $params['type'] == 'private') {
- $this->cryptParams['type'] = $params['type'];
- break;
- }
- }
- throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
- return;
- case (Key::RSA_SHA1):
- $this->cryptParams['library'] = 'openssl';
- $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
- $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
- if (is_array($params) && !empty($params['type'])) {
- if ($params['type'] == 'public' || $params['type'] == 'private') {
- $this->cryptParams['type'] = $params['type'];
- break;
- }
- }
- throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
- break;
- case (Key::RSA_SHA256):
- $this->cryptParams['library'] = 'openssl';
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
- $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
- $this->cryptParams['digest'] = 'SHA256';
- if (is_array($params) && !empty($params['type'])) {
- if ($params['type'] == 'public' || $params['type'] == 'private') {
- $this->cryptParams['type'] = $params['type'];
- break;
- }
- }
- throw new \Exception('Certificate "type" (private/public) must be passed via parameters');
- break;
- case (Key::RSA_SHA384):
- $this->cryptParams['library'] = 'openssl';
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
- $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
- $this->cryptParams['digest'] = 'SHA384';
- if (is_array($params) && !empty($params['type'])) {
- if ($params['type'] == 'public' || $params['type'] == 'private') {
- $this->cryptParams['type'] = $params['type'];
- break;
- }
- }
- case (Key::RSA_SHA512):
- $this->cryptParams['library'] = 'openssl';
- $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
- $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
- $this->cryptParams['digest'] = 'SHA512';
- if (is_array($params) && !empty($params['type'])) {
- if ($params['type'] == 'public' || $params['type'] == 'private') {
- $this->cryptParams['type'] = $params['type'];
- break;
- }
- }
- default:
- throw new \Exception('Invalid Key Type');
- return;
- }
- $this->type = $type;
- }
- /**
- * Retrieve the key size for the symmetric encryption algorithm..
- *
- * If the key size is unknown, or this isn't a symmetric encryption algorithm,
- * NULL is returned.
- *
- * @return int|NULL The number of bytes in the key.
- */
- public function getSymmetricKeySize()
- {
- if (!isset($this->cryptParams['keysize'])) {
- return null;
- }
- return $this->cryptParams['keysize'];
- }
- public function generateSessionKey()
- {
- if (!isset($this->cryptParams['keysize'])) {
- throw new \Exception('Unknown key size for type "' . $this->type . '".');
- }
- $keysize = $this->cryptParams['keysize'];
- if (function_exists('openssl_random_pseudo_bytes')) {
- /* We have PHP >= 5.3 - use openssl to generate session key. */
- $key = openssl_random_pseudo_bytes($keysize);
- } else {
- /* Generating random key using iv generation routines */
- $key = mcrypt_create_iv($keysize, MCRYPT_RAND);
- }
- if ($this->type === Key::TRIPLEDES_CBC) {
- /* Make sure that the generated key has the proper parity bits set.
- * Mcrypt doesn't care about the parity bits, but others may care.
- */
- for ($i = 0; $i < strlen($key); $i++) {
- $byte = ord($key[$i]) & 0xfe;
- $parity = 1;
- for ($j = 1; $j < 8; $j++) {
- $parity ^= ($byte >> $j) & 1;
- }
- $byte |= $parity;
- $key[$i] = chr($byte);
- }
- }
- $this->key = $key;
- return $key;
- }
- public static function getRawThumbprint($cert)
- {
- $arCert = explode("\n", $cert);
- $data = '';
- $inData = false;
- foreach ($arCert AS $curData) {
- if (!$inData) {
- if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
- $inData = true;
- }
- } else {
- if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
- $inData = false;
- break;
- }
- $data .= trim($curData);
- }
- }
- if (!empty($data)) {
- return strtolower(sha1(base64_decode($data)));
- }
- return null;
- }
- public function loadKey($key, $isFile = false, $isCert = false)
- {
- if ($isFile) {
- $this->key = file_get_contents($key);
- } else {
- $this->key = $key;
- }
- if ($isCert) {
- $this->key = openssl_x509_read($this->key);
- openssl_x509_export($this->key, $str_cert);
- $this->x509Certificate = $str_cert;
- $this->key = $str_cert;
- } else {
- $this->x509Certificate = null;
- }
- if ($this->cryptParams['library'] == 'openssl') {
- if ($this->cryptParams['type'] == 'public') {
- if ($isCert) {
- /* Load the thumbprint if this is an X509 certificate. */
- $this->X509Thumbprint = self::getRawThumbprint($this->key);
- }
- $this->key = openssl_get_publickey($this->key);
- } else {
- $this->key = openssl_get_privatekey($this->key, $this->passphrase);
- }
- } else {
- if ($this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
- /* Check key length */
- switch ($this->type) {
- case (Key::AES256_CBC):
- if (strlen($this->key) < 25) {
- throw new \Exception('Key must contain at least 25 characters for this cipher');
- }
- break;
- case (Key::AES192_CBC):
- if (strlen($this->key) < 17) {
- throw new \Exception('Key must contain at least 17 characters for this cipher');
- }
- break;
- }
- }
- }
- }
- private function encryptMcrypt($data)
- {
- $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
- $this->iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
- mcrypt_generic_init($td, $this->key, $this->iv);
- if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
- $bs = mcrypt_enc_get_block_size($td);
- for ($datalen0 = $datalen = strlen($data); (($datalen % $bs) != ($bs - 1)); $datalen++)
- $data .= chr(rand(1, 127));
- $data .= chr($datalen - $datalen0 + 1);
- }
- $encrypted_data = $this->iv . mcrypt_generic($td, $data);
- mcrypt_generic_deinit($td);
- mcrypt_module_close($td);
- return $encrypted_data;
- }
- private function decryptMcrypt($data)
- {
- $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], '');
- $iv_length = mcrypt_enc_get_iv_size($td);
- $this->iv = substr($data, 0, $iv_length);
- $data = substr($data, $iv_length);
- mcrypt_generic_init($td, $this->key, $this->iv);
- $decrypted_data = mdecrypt_generic($td, $data);
- mcrypt_generic_deinit($td);
- mcrypt_module_close($td);
- if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) {
- $dataLen = strlen($decrypted_data);
- $paddingLength = substr($decrypted_data, $dataLen - 1, 1);
- $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength));
- }
- return $decrypted_data;
- }
- private function encryptOpenSSL($data)
- {
- if ($this->cryptParams['type'] == 'public') {
- if (!openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
- throw new \Exception('Failure encrypting Data');
- }
- } elseif (!openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) {
- throw new \Exception('Failure encrypting Data');
- }
- return $encrypted_data;
- }
- private function decryptOpenSSL($data)
- {
- if ($this->cryptParams['type'] == 'public') {
- if (!openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
- throw new \Exception('Failure decrypting Data');
- }
- } elseif (!openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
- throw new \Exception('Failure decrypting Data');
- }
- return $decrypted;
- }
- private function signOpenSSL($data)
- {
- $algo = OPENSSL_ALGO_SHA1;
- if (!empty($this->cryptParams['digest'])) {
- $algo = $this->cryptParams['digest'];
- }
- if (!openssl_sign($data, $signature, $this->key, $algo)) {
- throw new \Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
- }
- return $signature;
- }
- private function verifyOpenSSL($data, $signature)
- {
- $algo = OPENSSL_ALGO_SHA1;
- if (!empty($this->cryptParams['digest'])) {
- $algo = $this->cryptParams['digest'];
- }
- return openssl_verify($data, $signature, $this->key, $algo);
- }
- public function encryptData($data)
- {
- switch ($this->cryptParams['library']) {
- case 'mcrypt':
- return $this->encryptMcrypt($data);
- break;
- case 'openssl':
- return $this->encryptOpenSSL($data);
- break;
- }
- }
- public function decryptData($data)
- {
- switch ($this->cryptParams['library']) {
- case 'mcrypt':
- return $this->decryptMcrypt($data);
- break;
- case 'openssl':
- return $this->decryptOpenSSL($data);
- break;
- }
- }
- public function signData($data)
- {
- switch ($this->cryptParams['library']) {
- case 'openssl':
- return $this->signOpenSSL($data);
- break;
- }
- }
- public function verifySignature($data, $signature)
- {
- switch ($this->cryptParams['library']) {
- case 'openssl':
- return $this->verifyOpenSSL($data, $signature);
- break;
- }
- }
- public function getAlgorith()
- {
- return $this->cryptParams['method'];
- }
- static function makeAsnSegment($type, $string)
- {
- switch ($type) {
- case 0x02:
- if (ord($string) > 0x7f)
- $string = chr(0) . $string;
- break;
- case 0x03:
- $string = chr(0) . $string;
- break;
- }
- $length = strlen($string);
- if ($length < 128) {
- $output = sprintf("%c%c%s", $type, $length, $string);
- } else if ($length < 0x0100) {
- $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
- } else if ($length < 0x010000) {
- $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
- } else {
- $output = null;
- }
- return ($output);
- }
- /* Modulus and Exponent must already be base64 decoded */
- static function convertRSA($modulus, $exponent)
- {
- /* make an ASN publicKeyInfo */
- $exponentEncoding = Key::makeAsnSegment(0x02, $exponent);
- $modulusEncoding = Key::makeAsnSegment(0x02, $modulus);
- $sequenceEncoding = Key:: makeAsnSegment(0x30, $modulusEncoding . $exponentEncoding);
- $bitstringEncoding = Key::makeAsnSegment(0x03, $sequenceEncoding);
- $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
- $publicKeyInfo = Key::makeAsnSegment(0x30, $rsaAlgorithmIdentifier . $bitstringEncoding);
- /* encode the publicKeyInfo in base64 and add PEM brackets */
- $publicKeyInfoBase64 = base64_encode($publicKeyInfo);
- $encoding = "-----BEGIN PUBLIC KEY-----\n";
- $offset = 0;
- while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
- $encoding = $encoding . $segment . "\n";
- $offset += 64;
- }
- return $encoding . "-----END PUBLIC KEY-----\n";
- }
- public function serializeKey($parent)
- {
- }
- /**
- * Retrieve the X509 certificate this key represents.
- *
- * Will return the X509 certificate in PEM-format if this key represents
- * an X509 certificate.
- *
- * @return The X509 certificate or NULL if this key doesn't represent an X509-certificate.
- */
- public function getX509Certificate()
- {
- return $this->x509Certificate;
- }
- /* Get the thumbprint of this X509 certificate.
- *
- * Returns:
- * The thumbprint as a lowercase 40-character hexadecimal number, or NULL
- * if this isn't a X509 certificate.
- */
- public function getX509Thumbprint()
- {
- return $this->X509Thumbprint;
- }
- /**
- * Create key from an EncryptedKey-element.
- *
- * @param DOMElement $element The EncryptedKey-element.
- * @return Key The new key.
- */
- public static function fromEncryptedKeyElement(DOMElement $element)
- {
- $objenc = new Encoder();
- $objenc->setNode($element);
- if (!$objKey = $objenc->locateKey()) {
- throw new \Exception("Unable to locate algorithm for this Encrypted Key");
- }
- $objKey->isEncrypted = true;
- $objKey->encryptedCtx = $objenc;
- Encoder::staticLocateKeyInfo($objKey, $element);
- return $objKey;
- }
- }