814 lines
30 KiB
PHP
814 lines
30 KiB
PHP
|
<?php
|
||
|
namespace RobRichards\XMLSecLibs;
|
||
|
|
||
|
use DOMElement;
|
||
|
use Exception;
|
||
|
|
||
|
/**
|
||
|
* xmlseclibs.php
|
||
|
*
|
||
|
* Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions
|
||
|
* are met:
|
||
|
*
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
*
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in
|
||
|
* the documentation and/or other materials provided with the
|
||
|
* distribution.
|
||
|
*
|
||
|
* * Neither the name of Robert Richards nor the names of his
|
||
|
* contributors may be used to endorse or promote products derived
|
||
|
* from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* @author Robert Richards <rrichards@cdatazone.org>
|
||
|
* @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
|
||
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||
|
*/
|
||
|
|
||
|
class XMLSecurityKey
|
||
|
{
|
||
|
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 AES128_GCM = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
|
||
|
const AES192_GCM = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
|
||
|
const AES256_GCM = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
|
||
|
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 RSA_OAEP = 'http://www.w3.org/2009/xmlenc11#rsa-oaep';
|
||
|
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';
|
||
|
const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
|
||
|
const AUTHTAG_LENGTH = 16;
|
||
|
|
||
|
/** @var array */
|
||
|
private $cryptParams = array();
|
||
|
|
||
|
/** @var int|string */
|
||
|
public $type = 0;
|
||
|
|
||
|
/** @var mixed|null */
|
||
|
public $key = null;
|
||
|
|
||
|
/** @var string */
|
||
|
public $passphrase = "";
|
||
|
|
||
|
/** @var string|null */
|
||
|
public $iv = null;
|
||
|
|
||
|
/** @var string|null */
|
||
|
public $name = null;
|
||
|
|
||
|
/** @var mixed|null */
|
||
|
public $keyChain = null;
|
||
|
|
||
|
/** @var bool */
|
||
|
public $isEncrypted = false;
|
||
|
|
||
|
/** @var XMLSecEnc|null */
|
||
|
public $encryptedCtx = null;
|
||
|
|
||
|
/** @var mixed|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.
|
||
|
* @var string|null
|
||
|
*/
|
||
|
private $x509Certificate = null;
|
||
|
|
||
|
/**
|
||
|
* This variable contains the certificate thumbprint if we have loaded an X509-certificate.
|
||
|
* @var string|null
|
||
|
*/
|
||
|
private $X509Thumbprint = null;
|
||
|
|
||
|
/**
|
||
|
* @param string $type
|
||
|
* @param null|array $params
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
public function __construct($type, $params=null)
|
||
|
{
|
||
|
switch ($type) {
|
||
|
case (self::TRIPLEDES_CBC):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'des-ede3-cbc';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
|
||
|
$this->cryptParams['keysize'] = 24;
|
||
|
$this->cryptParams['blocksize'] = 8;
|
||
|
break;
|
||
|
case (self::AES128_CBC):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'aes-128-cbc';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
|
||
|
$this->cryptParams['keysize'] = 16;
|
||
|
$this->cryptParams['blocksize'] = 16;
|
||
|
break;
|
||
|
case (self::AES192_CBC):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'aes-192-cbc';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
|
||
|
$this->cryptParams['keysize'] = 24;
|
||
|
$this->cryptParams['blocksize'] = 16;
|
||
|
break;
|
||
|
case (self::AES256_CBC):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'aes-256-cbc';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
|
||
|
$this->cryptParams['keysize'] = 32;
|
||
|
$this->cryptParams['blocksize'] = 16;
|
||
|
break;
|
||
|
case (self::AES128_GCM):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'aes-128-gcm';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
|
||
|
$this->cryptParams['keysize'] = 16;
|
||
|
$this->cryptParams['blocksize'] = 16;
|
||
|
break;
|
||
|
case (self::AES192_GCM):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'aes-192-gcm';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
|
||
|
$this->cryptParams['keysize'] = 24;
|
||
|
$this->cryptParams['blocksize'] = 16;
|
||
|
break;
|
||
|
case (self::AES256_GCM):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['cipher'] = 'aes-256-gcm';
|
||
|
$this->cryptParams['type'] = 'symmetric';
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
|
||
|
$this->cryptParams['keysize'] = 32;
|
||
|
$this->cryptParams['blocksize'] = 16;
|
||
|
break;
|
||
|
case (self::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');
|
||
|
case (self::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');
|
||
|
case (self::RSA_OAEP):
|
||
|
$this->cryptParams['library'] = 'openssl';
|
||
|
$this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#rsa-oaep';
|
||
|
$this->cryptParams['hash'] = 'http://www.w3.org/2009/xmlenc11#mgf1sha1';
|
||
|
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');
|
||
|
case (self::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');
|
||
|
case (self::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');
|
||
|
case (self::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;
|
||
|
}
|
||
|
}
|
||
|
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||
|
case (self::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;
|
||
|
}
|
||
|
}
|
||
|
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||
|
case (self::HMAC_SHA1):
|
||
|
$this->cryptParams['library'] = $type;
|
||
|
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
|
||
|
break;
|
||
|
default:
|
||
|
throw new Exception('Invalid Key Type');
|
||
|
}
|
||
|
$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'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates a session key using the openssl-extension.
|
||
|
* In case of using DES3-CBC the key is checked for a proper parity bits set.
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
public function generateSessionKey()
|
||
|
{
|
||
|
if (!isset($this->cryptParams['keysize'])) {
|
||
|
throw new Exception('Unknown key size for type "' . $this->type . '".');
|
||
|
}
|
||
|
$keysize = $this->cryptParams['keysize'];
|
||
|
|
||
|
$key = openssl_random_pseudo_bytes($keysize);
|
||
|
|
||
|
if ($this->type === self::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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the raw thumbprint of a certificate
|
||
|
*
|
||
|
* @param string $cert
|
||
|
* @return null|string
|
||
|
*/
|
||
|
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) {
|
||
|
break;
|
||
|
}
|
||
|
$data .= trim($curData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (! empty($data)) {
|
||
|
return strtolower(sha1(base64_decode($data)));
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Loads the given key, or - with isFile set true - the key from the keyfile.
|
||
|
*
|
||
|
* @param string $key
|
||
|
* @param bool $isFile
|
||
|
* @param bool $isCert
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
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') {
|
||
|
switch ($this->cryptParams['type']) {
|
||
|
case '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);
|
||
|
if (! $this->key) {
|
||
|
throw new Exception('Unable to extract public key');
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'private':
|
||
|
$this->key = openssl_get_privatekey($this->key, $this->passphrase);
|
||
|
break;
|
||
|
|
||
|
case'symmetric':
|
||
|
if (strlen($this->key) < $this->cryptParams['keysize']) {
|
||
|
throw new Exception('Key must contain at least '.$this->cryptParams['keysize'].' characters for this cipher, contains '.strlen($this->key));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
throw new Exception('Unknown type');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ISO 10126 Padding
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @param integer $blockSize
|
||
|
* @throws Exception
|
||
|
* @return string
|
||
|
*/
|
||
|
private function padISO10126($data, $blockSize)
|
||
|
{
|
||
|
if ($blockSize > 256) {
|
||
|
throw new Exception('Block size higher than 256 not allowed');
|
||
|
}
|
||
|
$padChr = $blockSize - (strlen($data) % $blockSize);
|
||
|
$pattern = chr($padChr);
|
||
|
return $data . str_repeat($pattern, $padChr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove ISO 10126 Padding
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
*/
|
||
|
private function unpadISO10126($data)
|
||
|
{
|
||
|
$padChr = substr($data, -1);
|
||
|
$padLen = ord($padChr);
|
||
|
return substr($data, 0, -$padLen);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Encrypts the given data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
*/
|
||
|
private function encryptSymmetric($data)
|
||
|
{
|
||
|
$this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher']));
|
||
|
$authTag = null;
|
||
|
if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
|
||
|
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
|
||
|
throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
|
||
|
}
|
||
|
$authTag = openssl_random_pseudo_bytes(self::AUTHTAG_LENGTH);
|
||
|
$encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
|
||
|
} else {
|
||
|
$data = $this->padISO10126($data, $this->cryptParams['blocksize']);
|
||
|
$encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
|
||
|
}
|
||
|
|
||
|
if (false === $encrypted) {
|
||
|
throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string());
|
||
|
}
|
||
|
return $this->iv . $encrypted . $authTag;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Decrypts the given data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
*/
|
||
|
private function decryptSymmetric($data)
|
||
|
{
|
||
|
$iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']);
|
||
|
$this->iv = substr($data, 0, $iv_length);
|
||
|
$data = substr($data, $iv_length);
|
||
|
$authTag = null;
|
||
|
if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
|
||
|
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
|
||
|
throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
|
||
|
}
|
||
|
// obtain and remove the authentication tag
|
||
|
$offset = 0 - self::AUTHTAG_LENGTH;
|
||
|
$authTag = substr($data, $offset);
|
||
|
$data = substr($data, 0, $offset);
|
||
|
$decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
|
||
|
} else {
|
||
|
$decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
|
||
|
}
|
||
|
|
||
|
if (false === $decrypted) {
|
||
|
throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string());
|
||
|
}
|
||
|
return null !== $authTag ? $decrypted : $this->unpadISO10126($decrypted);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Encrypts the given public data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private function encryptPublic($data)
|
||
|
{
|
||
|
if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
|
||
|
throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string());
|
||
|
}
|
||
|
return $encrypted;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Decrypts the given public data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private function decryptPublic($data)
|
||
|
{
|
||
|
if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
|
||
|
throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string());
|
||
|
}
|
||
|
return $decrypted;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Encrypts the given private data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private function encryptPrivate($data)
|
||
|
{
|
||
|
if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
|
||
|
throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string());
|
||
|
}
|
||
|
return $encrypted;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Decrypts the given private data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
private function decryptPrivate($data)
|
||
|
{
|
||
|
if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
|
||
|
throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string());
|
||
|
}
|
||
|
return $decrypted;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Signs the given data (string) using the openssl-extension
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return string
|
||
|
* @throws Exception
|
||
|
*/
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verifies the given data (string) belonging to the given signature using the openssl-extension
|
||
|
*
|
||
|
* Returns:
|
||
|
* 1 on succesful signature verification,
|
||
|
* 0 when signature verification failed,
|
||
|
* -1 if an error occurred during processing.
|
||
|
*
|
||
|
* NOTE: be very careful when checking the return value, because in PHP,
|
||
|
* -1 will be cast to True when in boolean context. So always check the
|
||
|
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @param string $signature
|
||
|
* @return int
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return mixed|string
|
||
|
*/
|
||
|
public function encryptData($data)
|
||
|
{
|
||
|
if ($this->cryptParams['library'] === 'openssl') {
|
||
|
switch ($this->cryptParams['type']) {
|
||
|
case 'symmetric':
|
||
|
return $this->encryptSymmetric($data);
|
||
|
case 'public':
|
||
|
return $this->encryptPublic($data);
|
||
|
case 'private':
|
||
|
return $this->encryptPrivate($data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return mixed|string
|
||
|
*/
|
||
|
public function decryptData($data)
|
||
|
{
|
||
|
if ($this->cryptParams['library'] === 'openssl') {
|
||
|
switch ($this->cryptParams['type']) {
|
||
|
case 'symmetric':
|
||
|
return $this->decryptSymmetric($data);
|
||
|
case 'public':
|
||
|
return $this->decryptPublic($data);
|
||
|
case 'private':
|
||
|
return $this->decryptPrivate($data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Signs the data (string) using the extension assigned to the type in the constructor.
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @return mixed|string
|
||
|
*/
|
||
|
public function signData($data)
|
||
|
{
|
||
|
switch ($this->cryptParams['library']) {
|
||
|
case 'openssl':
|
||
|
return $this->signOpenSSL($data);
|
||
|
case (self::HMAC_SHA1):
|
||
|
return hash_hmac("sha1", $data, $this->key, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Verifies the data (string) against the given signature using the extension assigned to the type in the constructor.
|
||
|
*
|
||
|
* Returns in case of openSSL:
|
||
|
* 1 on succesful signature verification,
|
||
|
* 0 when signature verification failed,
|
||
|
* -1 if an error occurred during processing.
|
||
|
*
|
||
|
* NOTE: be very careful when checking the return value, because in PHP,
|
||
|
* -1 will be cast to True when in boolean context. So always check the
|
||
|
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
|
||
|
*
|
||
|
* @param string $data
|
||
|
* @param string $signature
|
||
|
* @return bool|int
|
||
|
*/
|
||
|
public function verifySignature($data, $signature)
|
||
|
{
|
||
|
switch ($this->cryptParams['library']) {
|
||
|
case 'openssl':
|
||
|
return $this->verifyOpenSSL($data, $signature);
|
||
|
case (self::HMAC_SHA1):
|
||
|
$expectedSignature = hash_hmac("sha1", $data, $this->key, true);
|
||
|
return strcmp($signature, $expectedSignature) == 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated
|
||
|
* @see getAlgorithm()
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function getAlgorith()
|
||
|
{
|
||
|
return $this->getAlgorithm();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return mixed
|
||
|
*/
|
||
|
public function getAlgorithm()
|
||
|
{
|
||
|
return $this->cryptParams['method'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @param int $type
|
||
|
* @param string $string
|
||
|
* @return null|string
|
||
|
*/
|
||
|
public 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* Hint: Modulus and Exponent must already be base64 decoded
|
||
|
* @param string $modulus
|
||
|
* @param string $exponent
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function convertRSA($modulus, $exponent)
|
||
|
{
|
||
|
/* make an ASN publicKeyInfo */
|
||
|
$exponentEncoding = self::makeAsnSegment(0x02, $exponent);
|
||
|
$modulusEncoding = self::makeAsnSegment(0x02, $modulus);
|
||
|
$sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
|
||
|
$bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
|
||
|
$rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
|
||
|
$publicKeyInfo = self::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";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param mixed $parent
|
||
|
*/
|
||
|
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 string 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.
|
||
|
*
|
||
|
* @return string Lowercase 40-character hexadecimal number of thumbprint
|
||
|
*/
|
||
|
public function getX509Thumbprint()
|
||
|
{
|
||
|
return $this->X509Thumbprint;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Create key from an EncryptedKey-element.
|
||
|
*
|
||
|
* @param DOMElement $element The EncryptedKey-element.
|
||
|
* @throws Exception
|
||
|
*
|
||
|
* @return XMLSecurityKey The new key.
|
||
|
*/
|
||
|
public static function fromEncryptedKeyElement(DOMElement $element)
|
||
|
{
|
||
|
|
||
|
$objenc = new XMLSecEnc();
|
||
|
$objenc->setNode($element);
|
||
|
if (! $objKey = $objenc->locateKey()) {
|
||
|
throw new Exception("Unable to locate algorithm for this Encrypted Key");
|
||
|
}
|
||
|
$objKey->isEncrypted = true;
|
||
|
$objKey->encryptedCtx = $objenc;
|
||
|
XMLSecEnc::staticLocateKeyInfo($objKey, $element);
|
||
|
return $objKey;
|
||
|
}
|
||
|
|
||
|
}
|