From 1417463eba50c55a0a5b8f69f8f596c3d01e5b73 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 1 Jul 2012 12:07:42 -0500 Subject: [PATCH] - make Crypt_RSA use openssl for key generation (if openssl is available) and make it so File_X509 can create CSRs --- phpseclib/Crypt/RSA.php | 48 +++++++++++------- phpseclib/File/X509.php | 107 +++++++++++++++++++++++++++++++++++++--- phpseclib/openssl.cnf | 6 +++ 3 files changed, 137 insertions(+), 24 deletions(-) create mode 100644 phpseclib/openssl.cnf diff --git a/phpseclib/Crypt/RSA.php b/phpseclib/Crypt/RSA.php index 07b8575d..84d511ae 100644 --- a/phpseclib/Crypt/RSA.php +++ b/phpseclib/Crypt/RSA.php @@ -439,9 +439,9 @@ class Crypt_RSA { { if ( !defined('CRYPT_RSA_MODE') ) { switch (true) { - //case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>='): - // define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); - // break; + case extension_loaded('openssl') && version_compare(PHP_VERSION, '4.2.0', '>='): + define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_OPENSSL); + break; default: define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL); } @@ -477,9 +477,28 @@ class Crypt_RSA { */ function createKey($bits = 1024, $timeout = false, $partial = array()) { - if ( CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL ) { - $rsa = openssl_pkey_new(array('private_key_bits' => $bits)); - openssl_pkey_export($rsa, $privatekey); + if (!defined('CRYPT_RSA_EXPONENT')) { + // http://en.wikipedia.org/wiki/65537_%28number%29 + define('CRYPT_RSA_EXPONENT', '65537'); + } + // per , this number ought not result in primes smaller + // than 256 bits. as a consequence if the key you're trying to create is 1024 bits and you've set CRYPT_RSA_SMALLEST_PRIME + // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if + // CRYPT_RSA_MODE is set to CRYPT_RSA_MODE_INTERNAL. if CRYPT_RSA_MODE is set to CRYPT_RSA_MODE_OPENSSL then + // CRYPT_RSA_SMALLEST_PRIME is ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key + // generation when there's a chance neither gmp nor OpenSSL are installed) + if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { + define('CRYPT_RSA_SMALLEST_PRIME', 4096); + } + + // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum + if ( CRYPT_RSA_MODE == CRYPT_RSA_MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) { + $rsa = openssl_pkey_new(array( + 'private_key_bits' => $bits, + 'config' => dirname(__FILE__) . '/../openssl.cnf' + )); + + openssl_pkey_export($rsa, $privatekey, NULL, array('config' => dirname(__FILE__) . '/../openssl.cnf')); $publickey = openssl_pkey_get_details($rsa); $publickey = $publickey['key']; @@ -488,6 +507,9 @@ class Crypt_RSA { $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, CRYPT_RSA_PUBLIC_FORMAT_PKCS1))); } + // clear the buffer of error strings stemming from a minimalistic openssl.cnf + while (openssl_error_string() !== false); + return array( 'privatekey' => $privatekey, 'publickey' => $publickey, @@ -497,22 +519,12 @@ class Crypt_RSA { static $e; if (!isset($e)) { - if (!defined('CRYPT_RSA_EXPONENT')) { - // http://en.wikipedia.org/wiki/65537_%28number%29 - define('CRYPT_RSA_EXPONENT', '65537'); - } - // per , this number ought not result in primes smaller - // than 256 bits. - if (!defined('CRYPT_RSA_SMALLEST_PRIME')) { - define('CRYPT_RSA_SMALLEST_PRIME', 4096); - } - $e = new Math_BigInteger(CRYPT_RSA_EXPONENT); } extract($this->_generateMinMax($bits)); $absoluteMin = $min; - $temp = $bits >> 1; + $temp = $bits >> 1; // divide by two to see how many bits P and Q would be if ($temp > CRYPT_RSA_SMALLEST_PRIME) { $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME); $temp = CRYPT_RSA_SMALLEST_PRIME; @@ -2555,4 +2567,4 @@ class Crypt_RSA { return $this->_rsassa_pss_verify($message, $signature); } } -} +} \ No newline at end of file diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 6ce5c263..ff7f744c 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -1269,7 +1269,7 @@ class File_X509 { /** * Save X.509 certificate * - * @param optional Array $cert + * @param Array $cert * @access public * @return String */ @@ -2059,6 +2059,7 @@ class File_X509 { $orig = $csr = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $csr) ? base64_decode($csr) : false; if ($csr === false) { + $this->currentCert = false; return false; } @@ -2066,6 +2067,7 @@ class File_X509 { $decoded = $asn1->decodeBER($csr); $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest); if (!isset($csr) || $csr === false) { + $this->currentCert = false; return false; } @@ -2095,6 +2097,40 @@ class File_X509 { return $csr; } + /** + * Save CSR request + * + * @param Array $csr + * @access public + * @return String + */ + function saveCSR($csr) + { + if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) { + return false; + } + + switch ($csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']) { + case 'rsaEncryption': + $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] = + base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); + } + + $asn1 = new File_ASN1(); + + $asn1->loadOIDs($this->oids); + + $filters = array(); + $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] = + array('type' => FILE_ASN1_TYPE_UTF8_STRING); + + $asn1->loadFilters($filters); + + $csr = $asn1->encodeDER($csr, $this->CertificationRequest); + + return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr)) . '-----END CERTIFICATE REQUEST-----'; + } + /** * Sign an X.509 certificate * @@ -2102,8 +2138,8 @@ class File_X509 { * $subject can be either an existing X.509 cert (if you want to resign it), * a CSR or something with the DN and public key explicitly set. * - * @param Crypt_X509 $issuer - * @param Crypt_X509 $subject + * @param File_X509 $issuer + * @param File_X509 $subject * @param String $signatureAlgorithm optional * @access public * @return Mixed @@ -2122,10 +2158,10 @@ class File_X509 { $signatureSubject = $this->signatureSubject; if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { + $this->currentCert = $subject->currentCert; $this->currentCert['tbsCertificate']['signature']['algorithm'] = $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; - $this->currentCert = $subject->currentCert; if (!empty($this->startDate)) { $this->currentCert['tbsCertificate']['validity']['notBefore']['generalTime'] = $this->startDate; unset($this->currentCert['tbsCertificate']['validity']['notBefore']['utcTime']); @@ -2258,11 +2294,70 @@ class File_X509 { return $result; } + /** + * Sign a CSR + * + * @access public + * @return Mixed + */ + function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') + { + if (!is_object($this->privateKey) || empty($this->dn)) { + return false; + } + + $origPublicKey = $this->publicKey; + $class = get_class($this->privateKey); + $this->publicKey = new $class(); + $this->publicKey->loadKey($this->privateKey->getPublicKey()); + $this->publicKey->setPublicKey(); + if (!($publicKey = $this->_formatSubjectPublicKey())) { + return false; + } + $this->publicKey = $origPublicKey; + + $currentCert = $this->currentCert; + $signatureSubject = $this->signatureSubject; + + if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { + $this->currentCert['signatureAlgorithm']['algorithm'] = + $signatureAlgorithm; + if (!empty($this->dn)) { + $this->currentCert['certificationRequestInfo']['subject'] = $this->dn; + } + $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey; + } else { + $this->currentCert = array( + 'certificationRequestInfo' => + array( + 'version' => 'v1', + 'subject' => $this->dn, + 'subjectPKInfo' => $publicKey + ), + 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), + 'signature' => false // this is going to be overwritten later + ); + } + + // resync $this->signatureSubject + // save $certificationRequestInfo in case there are any File_ASN1_Element objects in it + $certificationRequestInfo = $this->currentCert['certificationRequestInfo']; + $this->loadCSR($this->saveCSR($this->currentCert)); + + $result = $this->_sign($this->privateKey, $signatureAlgorithm); + $result['certificationRequestInfo'] = $certificationRequestInfo; + + $this->currentCert = $currentCert; + $this->signatureSubject = $signatureSubject; + + return $result; + } + /** * X.509 certificate signing helper function. * * @param Object $key - * @param Crypt_X509 $subject + * @param File_X509 $subject * @param String $signatureAlgorithm * @access public * @return Mixed @@ -2484,4 +2579,4 @@ class File_X509 { $this->removeDNProp('id-at-commonName'); $this->setDNProp('id-at-commonName', $this->domains[0]); } -} +} \ No newline at end of file diff --git a/phpseclib/openssl.cnf b/phpseclib/openssl.cnf new file mode 100644 index 00000000..6baa5661 --- /dev/null +++ b/phpseclib/openssl.cnf @@ -0,0 +1,6 @@ +# minimalist openssl.cnf file for use with phpseclib + +HOME = . +RANDFILE = $ENV::HOME/.rnd + +[ v3_ca ] \ No newline at end of file