1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Filter\Encrypt; 11 12use Traversable; 13use Zend\Filter\Compress; 14use Zend\Filter\Decompress; 15use Zend\Filter\Exception; 16use Zend\Stdlib\ArrayUtils; 17 18/** 19 * Encryption adapter for openssl 20 */ 21class Openssl implements EncryptionAlgorithmInterface 22{ 23 /** 24 * Definitions for encryption 25 * array( 26 * 'public' => public keys 27 * 'private' => private keys 28 * 'envelope' => resulting envelope keys 29 * ) 30 */ 31 protected $keys = array( 32 'public' => array(), 33 'private' => array(), 34 'envelope' => array(), 35 ); 36 37 /** 38 * Internal passphrase 39 * 40 * @var string 41 */ 42 protected $passphrase; 43 44 /** 45 * Internal compression 46 * 47 * @var array 48 */ 49 protected $compression; 50 51 /** 52 * Internal create package 53 * 54 * @var bool 55 */ 56 protected $package = false; 57 58 /** 59 * Class constructor 60 * Available options 61 * 'public' => public key 62 * 'private' => private key 63 * 'envelope' => envelope key 64 * 'passphrase' => passphrase 65 * 'compression' => compress value with this compression adapter 66 * 'package' => pack envelope keys into encrypted string, simplifies decryption 67 * 68 * @param string|array|Traversable $options Options for this adapter 69 * @throws Exception\ExtensionNotLoadedException 70 */ 71 public function __construct($options = array()) 72 { 73 if (!extension_loaded('openssl')) { 74 throw new Exception\ExtensionNotLoadedException('This filter needs the openssl extension'); 75 } 76 77 if ($options instanceof Traversable) { 78 $options = ArrayUtils::iteratorToArray($options); 79 } 80 81 if (!is_array($options)) { 82 $options = array('public' => $options); 83 } 84 85 if (array_key_exists('passphrase', $options)) { 86 $this->setPassphrase($options['passphrase']); 87 unset($options['passphrase']); 88 } 89 90 if (array_key_exists('compression', $options)) { 91 $this->setCompression($options['compression']); 92 unset($options['compress']); 93 } 94 95 if (array_key_exists('package', $options)) { 96 $this->setPackage($options['package']); 97 unset($options['package']); 98 } 99 100 $this->_setKeys($options); 101 } 102 103 /** 104 * Sets the encryption keys 105 * 106 * @param string|array $keys Key with type association 107 * @return self 108 * @throws Exception\InvalidArgumentException 109 */ 110 protected function _setKeys($keys) 111 { 112 if (!is_array($keys)) { 113 throw new Exception\InvalidArgumentException('Invalid options argument provided to filter'); 114 } 115 116 foreach ($keys as $type => $key) { 117 if (is_file($key) and is_readable($key)) { 118 $file = fopen($key, 'r'); 119 $cert = fread($file, 8192); 120 fclose($file); 121 } else { 122 $cert = $key; 123 $key = count($this->keys[$type]); 124 } 125 126 switch ($type) { 127 case 'public': 128 $test = openssl_pkey_get_public($cert); 129 if ($test === false) { 130 throw new Exception\InvalidArgumentException("Public key '{$cert}' not valid"); 131 } 132 133 openssl_free_key($test); 134 $this->keys['public'][$key] = $cert; 135 break; 136 case 'private': 137 $test = openssl_pkey_get_private($cert, $this->passphrase); 138 if ($test === false) { 139 throw new Exception\InvalidArgumentException("Private key '{$cert}' not valid"); 140 } 141 142 openssl_free_key($test); 143 $this->keys['private'][$key] = $cert; 144 break; 145 case 'envelope': 146 $this->keys['envelope'][$key] = $cert; 147 break; 148 default: 149 break; 150 } 151 } 152 153 return $this; 154 } 155 156 /** 157 * Returns all public keys 158 * 159 * @return array 160 */ 161 public function getPublicKey() 162 { 163 $key = $this->keys['public']; 164 return $key; 165 } 166 167 /** 168 * Sets public keys 169 * 170 * @param string|array $key Public keys 171 * @return self 172 */ 173 public function setPublicKey($key) 174 { 175 if (is_array($key)) { 176 foreach ($key as $type => $option) { 177 if ($type !== 'public') { 178 $key['public'] = $option; 179 unset($key[$type]); 180 } 181 } 182 } else { 183 $key = array('public' => $key); 184 } 185 186 return $this->_setKeys($key); 187 } 188 189 /** 190 * Returns all private keys 191 * 192 * @return array 193 */ 194 public function getPrivateKey() 195 { 196 $key = $this->keys['private']; 197 return $key; 198 } 199 200 /** 201 * Sets private keys 202 * 203 * @param string $key Private key 204 * @param string $passphrase 205 * @return self 206 */ 207 public function setPrivateKey($key, $passphrase = null) 208 { 209 if (is_array($key)) { 210 foreach ($key as $type => $option) { 211 if ($type !== 'private') { 212 $key['private'] = $option; 213 unset($key[$type]); 214 } 215 } 216 } else { 217 $key = array('private' => $key); 218 } 219 220 if ($passphrase !== null) { 221 $this->setPassphrase($passphrase); 222 } 223 224 return $this->_setKeys($key); 225 } 226 227 /** 228 * Returns all envelope keys 229 * 230 * @return array 231 */ 232 public function getEnvelopeKey() 233 { 234 $key = $this->keys['envelope']; 235 return $key; 236 } 237 238 /** 239 * Sets envelope keys 240 * 241 * @param string|array $key Envelope keys 242 * @return self 243 */ 244 public function setEnvelopeKey($key) 245 { 246 if (is_array($key)) { 247 foreach ($key as $type => $option) { 248 if ($type !== 'envelope') { 249 $key['envelope'] = $option; 250 unset($key[$type]); 251 } 252 } 253 } else { 254 $key = array('envelope' => $key); 255 } 256 257 return $this->_setKeys($key); 258 } 259 260 /** 261 * Returns the passphrase 262 * 263 * @return string 264 */ 265 public function getPassphrase() 266 { 267 return $this->passphrase; 268 } 269 270 /** 271 * Sets a new passphrase 272 * 273 * @param string $passphrase 274 * @return self 275 */ 276 public function setPassphrase($passphrase) 277 { 278 $this->passphrase = $passphrase; 279 return $this; 280 } 281 282 /** 283 * Returns the compression 284 * 285 * @return array 286 */ 287 public function getCompression() 288 { 289 return $this->compression; 290 } 291 292 /** 293 * Sets an internal compression for values to encrypt 294 * 295 * @param string|array $compression 296 * @return self 297 */ 298 public function setCompression($compression) 299 { 300 if (is_string($this->compression)) { 301 $compression = array('adapter' => $compression); 302 } 303 304 $this->compression = $compression; 305 return $this; 306 } 307 308 /** 309 * Returns if header should be packaged 310 * 311 * @return bool 312 */ 313 public function getPackage() 314 { 315 return $this->package; 316 } 317 318 /** 319 * Sets if the envelope keys should be included in the encrypted value 320 * 321 * @param bool $package 322 * @return self 323 */ 324 public function setPackage($package) 325 { 326 $this->package = (bool) $package; 327 return $this; 328 } 329 330 /** 331 * Encrypts $value with the defined settings 332 * Note that you also need the "encrypted" keys to be able to decrypt 333 * 334 * @param string $value Content to encrypt 335 * @return string The encrypted content 336 * @throws Exception\RuntimeException 337 */ 338 public function encrypt($value) 339 { 340 $encrypted = array(); 341 $encryptedkeys = array(); 342 343 if (count($this->keys['public']) == 0) { 344 throw new Exception\RuntimeException('Openssl can not encrypt without public keys'); 345 } 346 347 $keys = array(); 348 $fingerprints = array(); 349 $count = -1; 350 foreach ($this->keys['public'] as $key => $cert) { 351 $keys[$key] = openssl_pkey_get_public($cert); 352 if ($this->package) { 353 $details = openssl_pkey_get_details($keys[$key]); 354 if ($details === false) { 355 $details = array('key' => 'ZendFramework'); 356 } 357 358 ++$count; 359 $fingerprints[$count] = md5($details['key']); 360 } 361 } 362 363 // compress prior to encryption 364 if (!empty($this->compression)) { 365 $compress = new Compress($this->compression); 366 $value = $compress($value); 367 } 368 369 $crypt = openssl_seal($value, $encrypted, $encryptedkeys, $keys); 370 foreach ($keys as $key) { 371 openssl_free_key($key); 372 } 373 374 if ($crypt === false) { 375 throw new Exception\RuntimeException('Openssl was not able to encrypt your content with the given options'); 376 } 377 378 $this->keys['envelope'] = $encryptedkeys; 379 380 // Pack data and envelope keys into single string 381 if ($this->package) { 382 $header = pack('n', count($this->keys['envelope'])); 383 foreach ($this->keys['envelope'] as $key => $envKey) { 384 $header .= pack('H32n', $fingerprints[$key], strlen($envKey)) . $envKey; 385 } 386 387 $encrypted = $header . $encrypted; 388 } 389 390 return $encrypted; 391 } 392 393 /** 394 * Defined by Zend\Filter\FilterInterface 395 * 396 * Decrypts $value with the defined settings 397 * 398 * @param string $value Content to decrypt 399 * @return string The decrypted content 400 * @throws Exception\RuntimeException 401 */ 402 public function decrypt($value) 403 { 404 $decrypted = ""; 405 $envelope = current($this->getEnvelopeKey()); 406 407 if (count($this->keys['private']) !== 1) { 408 throw new Exception\RuntimeException('Please give a private key for decryption with Openssl'); 409 } 410 411 if (!$this->package && empty($envelope)) { 412 throw new Exception\RuntimeException('Please give an envelope key for decryption with Openssl'); 413 } 414 415 foreach ($this->keys['private'] as $cert) { 416 $keys = openssl_pkey_get_private($cert, $this->getPassphrase()); 417 } 418 419 if ($this->package) { 420 $details = openssl_pkey_get_details($keys); 421 if ($details !== false) { 422 $fingerprint = md5($details['key']); 423 } else { 424 $fingerprint = md5("ZendFramework"); 425 } 426 427 $count = unpack('ncount', $value); 428 $count = $count['count']; 429 $length = 2; 430 for ($i = $count; $i > 0; --$i) { 431 $header = unpack('H32print/nsize', substr($value, $length, 18)); 432 $length += 18; 433 if ($header['print'] == $fingerprint) { 434 $envelope = substr($value, $length, $header['size']); 435 } 436 437 $length += $header['size']; 438 } 439 440 // remainder of string is the value to decrypt 441 $value = substr($value, $length); 442 } 443 444 $crypt = openssl_open($value, $decrypted, $envelope, $keys); 445 openssl_free_key($keys); 446 447 if ($crypt === false) { 448 throw new Exception\RuntimeException('Openssl was not able to decrypt you content with the given options'); 449 } 450 451 // decompress after decryption 452 if (!empty($this->compression)) { 453 $decompress = new Decompress($this->compression); 454 $decrypted = $decompress($decrypted); 455 } 456 457 return $decrypted; 458 } 459 460 /** 461 * Returns the adapter name 462 * 463 * @return string 464 */ 465 public function toString() 466 { 467 return 'Openssl'; 468 } 469} 470