<?php

/**
 * PKCS#1 Formatted RSA Key Handler
 *
 * PHP version 5
 *
 * Used by File/X509.php
 *
 * Processes keys with the following headers:
 *
 * -----BEGIN RSA PRIVATE KEY-----
 * -----BEGIN RSA PUBLIC KEY-----
 *
 * Analogous to ssh-keygen's pem format (as specified by -m)
 *
 * @category  Crypt
 * @package   RSA
 * @author    Jim Wigginton <terrafrost@php.net>
 * @copyright 2015 Jim Wigginton
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
 * @link      http://phpseclib.sourceforge.net
 */

namespace phpseclib\Crypt\RSA;

use ParagonIE\ConstantTime\Base64;
use ParagonIE\ConstantTime\Hex;
use phpseclib\Crypt\DES;
use phpseclib\Crypt\Random;
use phpseclib\Math\BigInteger;
use phpseclib\Common\Functions\ASN1;

/**
 * PKCS#1 Formatted RSA Key Handler
 *
 * @package RSA
 * @author  Jim Wigginton <terrafrost@php.net>
 * @access  public
 */
class PKCS1 extends PKCS
{
    /**
     * Default encryption algorithm
     *
     * @var string
     * @access private
     */
    static $defaultEncryptionAlgorithm = 'DES-EDE3-CBC';

    /**
     * Sets the default encryption algorithm
     *
     * @access public
     * @param string $algo
     */
    static function setEncryptionAlgorithm($algo)
    {
        self::$defaultEncryptionAlgorithm = $algo;
    }

    /**
     * Convert a private key to the appropriate format.
     *
     * @access public
     * @param \phpseclib\Math\BigInteger $n
     * @param \phpseclib\Math\BigInteger $e
     * @param \phpseclib\Math\BigInteger $d
     * @param array $primes
     * @param array $exponents
     * @param array $coefficients
     * @param string $password optional
     * @return string
     */
    static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
    {
        $num_primes = count($primes);
        $raw = array(
            'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi
            'modulus' => $n->toBytes(true),
            'publicExponent' => $e->toBytes(true),
            'privateExponent' => $d->toBytes(true),
            'prime1' => $primes[1]->toBytes(true),
            'prime2' => $primes[2]->toBytes(true),
            'exponent1' => $exponents[1]->toBytes(true),
            'exponent2' => $exponents[2]->toBytes(true),
            'coefficient' => $coefficients[2]->toBytes(true)
        );

        $components = array();
        foreach ($raw as $name => $value) {
            $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($value)), $value);
        }

        $RSAPrivateKey = implode('', $components);

        if ($num_primes > 2) {
            $OtherPrimeInfos = '';
            for ($i = 3; $i <= $num_primes; $i++) {
                // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
                //
                // OtherPrimeInfo ::= SEQUENCE {
                //     prime             INTEGER,  -- ri
                //     exponent          INTEGER,  -- di
                //     coefficient       INTEGER   -- ti
                // }
                $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
                $OtherPrimeInfo .= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
                $OtherPrimeInfo .= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
                $OtherPrimeInfos .= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
            }
            $RSAPrivateKey .= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
        }

        $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);

        if (!empty($password) || is_string($password)) {
            $cipher = self::getEncryptionObject(self::$defaultEncryptionAlgorithm);
            $iv = Random::string($cipher->getBlockLength() >> 3);
            $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
            $cipher->setIV($iv);
            $iv = strtoupper(Hex::encode($iv));
            $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n".
                        "Proc-Type: 4,ENCRYPTED\r\n".
                        "DEK-Info: ".self::$defaultEncryptionAlgorithm.",$iv\r\n".
                        "\r\n".
                        chunk_split(Base64::encode($cipher->encrypt($RSAPrivateKey)), 64).
                        '-----END RSA PRIVATE KEY-----';
        } else {
            $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n".
                        chunk_split(Base64::encode($RSAPrivateKey), 64).
                        '-----END RSA PRIVATE KEY-----';
        }

        return $RSAPrivateKey;
    }

    /**
     * Convert a public key to the appropriate format
     *
     * @access public
     * @param \phpseclib\Math\BigInteger $n
     * @param \phpseclib\Math\BigInteger $e
     * @return string
     */
    static function savePublicKey(BigInteger $n, BigInteger $e)
    {
        $modulus = $n->toBytes(true);
        $publicExponent = $e->toBytes(true);

        // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>:
        // RSAPublicKey ::= SEQUENCE {
        //     modulus           INTEGER,  -- n
        //     publicExponent    INTEGER   -- e
        // }
        $components = array(
            'modulus' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($modulus)), $modulus),
            'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($publicExponent)), $publicExponent)
        );

        $RSAPublicKey = pack(
            'Ca*a*a*',
            self::ASN1_SEQUENCE,
            ASN1::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
            $components['modulus'],
            $components['publicExponent']
        );

        $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n".
                        chunk_split(Base64::encode($RSAPublicKey), 64).
                        '-----END RSA PUBLIC KEY-----';

        return $RSAPublicKey;
    }
}
