<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2019 Lifestyle Webconsulting GmbH
 * @link       https://www.life-style.de
 */

namespace SimpleSAML\Module\lifestyle\Webservice\Cyphering;

use SimpleSAML\Module\lifestyle\Webservice\Api\Configuration\Cyphering;

/**
 * Class Decoder
 *
 * @copyright  2019 Lifestyle Webconsulting GmbH
 * @link       https://www.life-style.de
 * @package SimpleSAML\Module\lifestyle\Webservice\Cyphering
 */
class Decoder
{
    /**
     * @var Cyphering
     */
    private $configuration;

    /**
     * \SimpleSAML\Module\lifestyle\Webservice\Cyphering\Decoder constructor.
     * @param Cyphering $configuration
     */
    public function __construct(Cyphering $configuration)
    {
        $this->configuration = $configuration;
    }

    /**
     * @param string $encryptedString
     * @param string $encryptKey
     * @return string|false
     */
    public function decode($encryptedString, $encryptKey)
    {
        return Manager::useOpenSsl() ?
            $this->openSslDecode($encryptedString, $encryptKey) :
            $this->mcryptDecode($encryptedString, $encryptKey);
    }

    /**
     * @param string $encryptedString
     * @param string $encryptKey
     * @return string|false
     */
    private function mcryptDecode($encryptedString, $encryptKey)
    {
        //Combine static and flexible keys
        $mixedKey = $this->configuration->keyMixer($encryptKey);
        $key = substr(hash('sha256', $mixedKey), 0, 32);

        // Split data
        $decrypt = explode('#|#', $encryptedString . '#|#');
        $decoded = base64_decode($decrypt[0]);
        $iv = base64_decode($decrypt[1]);

        $mcryptCipherName = $this->configuration->getMcryptCipherName();
        $mcryptModeName = $this->configuration->getMcryptModeName();

        if (strlen($iv) !== mcrypt_get_iv_size(constant($mcryptCipherName), constant($mcryptModeName))) {
            return false;
        }

        // Pack key
        $key = pack('H*', $key);
        $decrypted = trim(mcrypt_decrypt(constant($mcryptCipherName), $key, $decoded, constant($mcryptModeName), $iv));
        $mac = substr($decrypted, -64);
        $decrypted = substr($decrypted, 0, -64);
        $calcmac = hash_hmac('sha256', $decrypted, substr(bin2hex($key), -32));
        if ($calcmac !== $mac) {
            return false;
        }

        return unserialize($decrypted);
    }

    /**
     * @param string $data
     * @param string $key
     * @return bool|string
     */
    private function openSslDecode($data, $key)
    {
        $openSslCipherName = $this->configuration->getOpenSslCipherName();

        //combine static and flexible keys
        $mixedKey = $this->configuration->keyMixer($key);
        $key = substr(hash('sha256', $mixedKey), 0, 32);

        // split data
        $decrypt = explode('#|#', $data . '#|##|#');
        $ciphertext = base64_decode($decrypt[0]);
        $openSslIV = base64_decode($decrypt[1]);
        $openSslTag = base64_decode($decrypt[2]);

        // Key should be in hex format
        $key = pack('H*', $key);

        $decrypted = openssl_decrypt($ciphertext, $openSslCipherName, $key, 0, $openSslIV, $openSslTag);
        if (false === $decrypted) {
            return false;
        }

        return unserialize($decrypted);
    }
}
