<?php

// Uses phpseclib to create a key
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Exception\UnsupportedFormatException;
use phpseclib3\Exception\NoKeyLoadedException;
use phpseclib3\Math\BigInteger;
use phpseclib3\Crypt\Hash;
use phpseclib3\Crypt\RSA;
use phpseclib3\Crypt\DSA;
use phpseclib3\Crypt\ECDSA;

class Auth_Model extends Model {
	
	function __construct() {
		parent::__construct();
		$this->pvk_bits = FALSE;
	}
	
	private function _init() {
		$settings = $this->SettingGet('config.module_info', NULL);
		$priv_key = isset($settings['key']) ? $settings['key'] : '';
		if ($priv_key == '') {
			$this->pvk_bits = 0;
		} else {
			$this->pvk = openssl_get_privatekey($priv_key);
			$det = openssl_pkey_get_details($this->pvk);
			$this->pvk_bits = $det['bits'];
		}
		
		$pub_key = isset($settings['key']) ? $settings['publickey'] : '';
		if ($pub_key == '') {
			$this->pbk_bits = 0;
		} else {
			$this->pbk = openssl_pkey_get_public($pub_key);
			$det = openssl_pkey_get_details($this->pbk);
			$this->pbk_bits = $det['bits'];
		}
	}
	
	public function encryptData($data) {
		if ($this->pvk_bits === FALSE) $this->_init();
		$blocksize = intval(($this->pbk_bits - 88) / 8);
		$output = '';
		if ($blocksize <= 0) {
			die("Unable to encryptData() - no key present");
		}
		foreach(str_split($data, $blocksize) as $chunk) {
			$encrypted = '';
			$j = @openssl_public_encrypt($chunk, $encrypted, $this->pbk, OPENSSL_PKCS1_PADDING );
			if ($j === FALSE) return FALSE;		// failed to encrypt - bad public public
			$output .= $encrypted;
		}
		return $output;
	}

	public function decryptData($data_to_be_decrypted) {
		if ($this->pvk_bits === FALSE) $this->_init();
		$output = '';
		$blocksize = intval($this->pvk_bits / 8);
		
		while ($data_to_be_decrypted != '') {
			$segment = substr($data_to_be_decrypted, 0, $blocksize);
			$data_to_be_decrypted = substr($data_to_be_decrypted, $blocksize);
			$decrypted_data = '';
			openssl_private_decrypt($segment, $decrypted_data, $this->pvk);
			if ($decrypted_data == '') return FALSE;		// corrupted
			$output .= $decrypted_data;
		}
		return $output;
	}

	public function encryptDataPrivate($data) {
		if ($this->pvk_bits === FALSE) $this->_init();
		$blocksize = intval(($this->pvk_bits - 88) / 8);
		$output = '';
		foreach(str_split($data, $blocksize) as $chunk) {
			$encrypted = '';
			$j = @openssl_private_encrypt($chunk, $encrypted, $this->pvk, OPENSSL_PKCS1_PADDING );
			if ($j === FALSE) return FALSE;		// failed to encrypt - bad public public
			$output .= $encrypted;
		}
		return $output;
	}

	private function _ConvertPuttyKey($key) {
        $rsa = PublicKeyLoader::Load($key);
		return $rsa->getPrivateKey(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
	}
	
	private function _generatePublicKey($key) {
        $rsa = PublicKeyLoader::Load($key);
        $public = $rsa->getPublicKey();
        return $public->toString('PKCS8');
	}
	
	public function CreateKey($keylength = 512) {
		$result = [];
		$rsa =  phpseclib3\Crypt\RSA::createKey($keylength);
        $privateKey = $rsa->toString('PKCS8');
		$result['field'] = array('config_key' => array('value' => $privateKey));
		return $result;
	}
	
	public function As64($data) {
		$data = base64_encode($data);
		return str_replace(array('+', '/'), array('-', '_'), $data);
	}
	public function From64($data) {
		$data = str_replace(array('-', '_'), array('+', '/'), $data);
		return $this->decryptData(base64_decode($data));
	}
	
	public function Authorization_Create($subject, $key, $ttl = 300, $single_use = FALSE) {
		// subject 8
		// expiry 4
		// random 2
		// singleuse 1
		// key 49
		$expiry = time() + $ttl;
		$rand = mt_rand(1, 65535);
		$single_use = $single_use ? 1 : 0;
		$subject = substr($subject, 0, 8);
		$key = substr($key, 0, 49);
		$payload = pack('A8VvCA49', $subject, $expiry, $rand, $single_use, $key);
		return $this->as64($this->encryptData($payload));
	}

	public function Authorization_Create_Private($subject, $key, $ttl = 300, $single_use = FALSE) {
		// subject 8
		// expiry 4
		// random 2
		// singleuse 1
		// key 49
		$expiry = time() + $ttl;
		$rand = mt_rand(1, 65535);
		$single_use = $single_use ? 1 : 0;
		$subject = substr($subject, 0, 8);
		$key = substr($key, 0, 49);
		$payload = pack('A8VvCA49', $subject, $expiry, $rand, $single_use, $key);
		return $this->as64($this->encryptDataPrivate($payload));
	}

	public function Authorization_Info($authorization_text) {
		$data = $this->From64($authorization_text);
		$node = unpack('A8subject/Vexpiry/vrandom/Csingle_use/A49key', $data);
		$node['_expiry'] = gmdate('Y-m-d H:i:s', $node['expiry']) . 'Z';
		return $node;
	}
	
	public function Authorization_Verify($subject, $key, $authorization_text, $noexpire = FALSE) {
		$subject = substr($subject, 0, 8);
		if ($key !== FALSE) $key = substr($key, 0, 49);
		$data = $this->From64($authorization_text);
		if ($data == FALSE) return FALSE;
		$node = unpack('A8subject/Vexpiry/vrandom/Csingle_use/A49key', $data);
		if (($key != $node['key'] && $key !== FALSE) || $subject != $node['subject']) return FALSE;
		$now = time();
		if (($node['expiry'] < $now || $node['expiry'] > $now + 86400) && !$noexpire) return FALSE;		// fake expiry way into the future is also bad
		
		if ($node['single_use']) {		// update database
			// use Message to update database
			// use subject, expiry, random and key
		}
		if ($key === FALSE) return $node['key'];
		return TRUE;
	}

	// convert private key, create public key
	public function onSaveSettings(&$settings) {
		
        header('Content-type: text/plain');
		$private_key = $settings['key'];
	// if key is ppk then convert (cannot have passphrase)
		if (strpos($private_key, 'PuTTY-User-Key-File-2') !== FALSE) {
			$private_key = $settings['key'] = $this->_ConvertPuttyKey($private_key);
			if ($private_key == 'false' || empty($private_key)) $private_key = '';
		}
		if ($private_key == '') {
			$settings['publickey'] = '';
		} else {
			$settings['publickey'] = $this->_generatePublicKey($private_key);
		}
		$settings['secret'] = md5($settings['publickey'] . ':salted:' . $settings['key']);
		
		// To do: re-encrypt all user details (use old key to decrypt), create new hashes?
	}

}