- added Crypt_RSA

- added RSA public key authentication to Net_SSH2


git-svn-id: http://phpseclib.svn.sourceforge.net/svnroot/phpseclib/trunk@62 21d32557-59b3-4da0-833f-c5933fad653e
This commit is contained in:
Jim Wigginton 2009-12-03 08:19:00 +00:00
parent ec75e4fc32
commit a882a3a41f
5 changed files with 2189 additions and 30 deletions

1929
phpseclib/Crypt/RSA.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -69,7 +69,7 @@
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVI Jim Wigginton
* @license http://www.gnu.org/licenses/lgpl.txt
* @version $Id: BigInteger.php,v 1.16 2009-11-23 19:06:07 terrafrost Exp $
* @version $Id: BigInteger.php,v 1.17 2009-12-03 08:18:53 terrafrost Exp $
* @link http://pear.php.net/package/Math_BigInteger
*/
@ -291,7 +291,7 @@ class Math_BigInteger {
break;
// converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb)
case MATH_BIGINTEGER_MODE_INTERNAL:
default:
while (strlen($x)) {
$this->value[] = $this->_bytes2int($this->_base256_rshift($x, 26));
}
@ -332,7 +332,7 @@ class Math_BigInteger {
$this->value = $this->is_negative ? '-' . $temp->value : $temp->value;
$this->is_negative = false;
break;
case MATH_BIGINTEGER_MODE_INTERNAL:
default:
$x = ( strlen($x) & 1 ) ? '0' . $x : $x;
$temp = new Math_BigInteger(pack('H*', $x), 256);
$this->value = $temp->value;
@ -356,7 +356,7 @@ class Math_BigInteger {
// results then doing it on '-1' does (modInverse does $x[0])
$this->value = (string) $x;
break;
case MATH_BIGINTEGER_MODE_INTERNAL:
default:
$temp = new Math_BigInteger();
// array(10000000) is 10**7 in base-2**26. 10**7 is the closest to 2**26 we can get without passing it.
@ -1054,7 +1054,6 @@ class Math_BigInteger {
// the above for loop is what the previous comment was talking about. the
// following for loop is the "one with nested for loops"
for ($i = 1; $i < $x_length; $i++) {
$carry = 0;
@ -1272,6 +1271,12 @@ class Math_BigInteger {
return array($this->_normalize($quotient), $this->_normalize($remainder));
}
if (count($y->value) == 1) {
$temp = $this->_divide_digit($y->value[0]);
$temp[0]->is_negative = $this->is_negative != $y->is_negative;
return array($this->_normalize($temp[0]), $this->_normalize($temp[1]));
}
static $zero;
if (!isset($zero)) {
$zero = new Math_BigInteger();
@ -1399,6 +1404,32 @@ class Math_BigInteger {
return array($this->_normalize($quotient), $this->_normalize($x));
}
/**
* Divides a BigInteger by a regular integer
*
* abc / x = a00 / x + b0 / x + c / x
*
* @param Math_BigInteger $divisor
* @return Array
* @access public
*/
function _divide_digit($divisor)
{
$carry = 0;
$result = new Math_BigInteger();
for ($i = count($this->value) - 1; $i >= 0; $i--) {
$temp = 0x4000000 * $carry + $this->value[$i];
$result->value[$i] = floor($temp / $divisor);
$carry = fmod($temp, $divisor);
}
$remainder = new Math_BigInteger();
$remainder->value = array($carry);
return array($result, $remainder);
}
/**
* Performs modular exponentiation.
*
@ -1790,7 +1821,7 @@ class Math_BigInteger {
$result = $result->subtract($n);
}
return $result->_normalize();
return $result;
}
/**
@ -1810,7 +1841,7 @@ class Math_BigInteger {
$temp->value = array_merge($this->_array_repeat(0, $k), $this->value);
list(, $temp) = $temp->divide($n);
return $temp->_normalize();
return $temp;
}
/**
@ -2538,14 +2569,15 @@ class Math_BigInteger {
$size = strlen($max) - 1;
$random = '';
$bytes = $size & 3;
$bytes = $size & 1;
for ($i = 0; $i < $bytes; $i++) {
$random.= chr($generator(0, 255));
}
$blocks = $size >> 2;
$blocks = $size >> 1;
for ($i = 0; $i < $blocks; $i++) {
$random.= pack('N', $generator(-2147483648, 0x7FFFFFFF));
// mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems
$random.= pack('n', $generator(0, 0xFFFF));
}
$temp = new Math_BigInteger($random, 256);
@ -2600,6 +2632,7 @@ class Math_BigInteger {
}
$x = $this->random($min, $max);
$x->value = gmp_nextprime($x->value);
if ($x->compare($max) <= 0) {
@ -2628,7 +2661,6 @@ class Math_BigInteger {
}
$x = $this->random($min, $max);
if ($x->equals($two)) {
return $x;
}
@ -2643,19 +2675,19 @@ class Math_BigInteger {
$x = $x->add($one);
}
break;
case MATH_BIGINTEGER_MODE_INTERNAL:
default:
$x->value[0] |= 1;
}
// if we've seen this number twice before, assume there are no prime numbers within the given range
if (in_array($x, $repeat1)) {
if (in_array($x, $repeat2)) {
if (in_array($x->value, $repeat1)) {
if (in_array($x->value, $repeat2)) {
return false;
} else {
$repeat2[] = $x;
$repeat2[] = $x->value;
}
} else {
$repeat1[] = $x;
$repeat1[] = $x->value;
}
} while (!$x->isPrime());
@ -2709,7 +2741,7 @@ class Math_BigInteger {
return false;
}
break;
case MATH_BIGINTEGER_MODE_INTERNAL:
default:
if ($this->value == array(2)) {
return true;
}
@ -2843,7 +2875,7 @@ class Math_BigInteger {
function _rshift($shift)
{
if ($shift == 0) {
$this->_normalize();
return;
}
$num_digits = floor($shift / 26);

View File

@ -65,7 +65,7 @@
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @license http://www.gnu.org/licenses/lgpl.txt
* @version $Id: SSH1.php,v 1.13 2009-11-23 19:06:07 terrafrost Exp $
* @version $Id: SSH1.php,v 1.14 2009-12-03 08:18:53 terrafrost Exp $
* @link http://phpseclib.sourceforge.net
*/
@ -1016,6 +1016,17 @@ class Net_SSH1 {
*/
function _rsa_crypt($m, $key)
{
/*
if (!class_exists('Crypt_RSA')) {
require_once('Crypt/RSA.php');
}
$rsa = new Crypt_RSA();
$rsa->loadKey($key, CRYPT_RSA_PUBLIC_FORMAT_RAW);
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
return $rsa->encrypt($m);
*/
// To quote from protocol-1.5.txt:
// The most significant byte (which is only partial as the value must be
// less than the public modulus, which is never a power of two) is zero.

View File

@ -6,7 +6,7 @@
*
* PHP versions 4 and 5
*
* Here's a short example of how to use this library:
* Here's are some examples of how to use this library:
* <code>
* <?php
* include('Net/SSH2.php');
@ -21,6 +21,25 @@
* ?>
* </code>
*
* <code>
* <?php
* include('Crypt/RSA.php');
* include('Net/SSH2.php');
*
* $key = new Crypt_RSA();
* //$key->setPassword('whatever');
* $key->loadKey(file_get_contents('privatekey'));
*
* $ssh = new Net_SSH2('www.domain.tld');
* if (!$ssh->login('username', $key)) {
* exit('Login Failed');
* }
*
* echo $ssh->exec('pwd');
* echo $ssh->exec('ls -la');
* ?>
* </code>
*
* LICENSE: This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@ -41,7 +60,7 @@
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @license http://www.gnu.org/licenses/lgpl.txt
* @version $Id: SSH2.php,v 1.27 2009-11-26 06:41:34 terrafrost Exp $
* @version $Id: SSH2.php,v 1.28 2009-12-03 08:18:53 terrafrost Exp $
* @link http://phpseclib.sourceforge.net
*/
@ -497,7 +516,6 @@ class Net_SSH2 {
51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ',
80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
@ -546,7 +564,9 @@ class Net_SSH2 {
$this->disconnect_reasons,
$this->channel_open_failure_reasons,
$this->terminal_modes,
$this->channel_extended_data_type_codes
$this->channel_extended_data_type_codes,
array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'),
array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK')
);
$this->fsock = @fsockopen($host, $port, $errno, $errstr, $timeout);
@ -625,12 +645,10 @@ class Net_SSH2 {
);
static $encryption_algorithms = array(
'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
'aes128-cbc', // RECOMMENDED AES with a 128-bit key
'aes192-cbc', // OPTIONAL AES with a 192-bit key
'aes256-cbc', // OPTIONAL AES in CBC mode, with a 256-bit key
'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
'3des-cbc', // REQUIRED three-key 3DES in CBC mode
'none' // OPTIONAL no encryption; NOT RECOMMENDED
);
@ -956,6 +974,23 @@ class Net_SSH2 {
$n = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$nLength = $temp['length'];
/*
$temp = unpack('Nlength', $this->_string_shift($signature, 4));
$signature = $this->_string_shift($signature, $temp['length']);
if (!class_exists('Crypt_RSA')) {
require_once('Crypt/RSA.php');
}
$rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->loadKey(array('e' => $e, 'n' => $n), CRYPT_RSA_PUBLIC_FORMAT_RAW);
if (!$rsa->verify($source, $signature)) {
user_error('Bad server signature', E_USER_NOTICE);
return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
}
*/
$temp = unpack('Nlength', $this->_string_shift($signature, 4));
$s = new Math_BigInteger($this->_string_shift($signature, $temp['length']), 256);
@ -1179,7 +1214,7 @@ class Net_SSH2 {
* @return Boolean
* @access public
* @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
by sending dummy SSH_MSG_IGNORE messages.
* by sending dummy SSH_MSG_IGNORE messages.
*/
function login($username, $password = '')
{
@ -1208,7 +1243,11 @@ class Net_SSH2 {
return false;
}
// publickey authentication is required, per the SSH-2 specs, however, we don't support it.
// although PHP5's get_class() preserves the case, PHP4's does not
if (strtolower(get_class($password)) == 'crypt_rsa') {
return $this->_privatekey_login($username, $password);
}
$utf8_password = utf8_encode($password);
$packet = pack('CNa*Na*Na*CNa*',
NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection',
@ -1238,13 +1277,100 @@ class Net_SSH2 {
switch ($type) {
case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
if (defined('NET_SSH2_LOGGING')) {
$this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ';
}
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$this->debug_info.= "\r\n\r\nSSH_MSG_USERAUTH_PASSWD_CHANGEREQ:\r\n" . utf8_decode($this->_string_shift($response, $length));
return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
case NET_SSH2_MSG_USERAUTH_FAILURE:
// either the login is bad or the server employees multi-factor authentication
return false;
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= NET_SSH2_MASK_LOGIN;
return true;
}
return false;
}
/**
* Login with an RSA private key
*
* @param String $username
* @param Crypt_RSA $password
* @return Boolean
* @access private
* @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
* by sending dummy SSH_MSG_IGNORE messages.
*/
function _privatekey_login($username, $privatekey)
{
// see http://tools.ietf.org/html/rfc4253#page-15
$publickey = $privatekey->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_RAW);
$publickey = array(
'e' => $publickey['e']->toBytes(true),
'n' => $publickey['n']->toBytes(true)
);
$publickey = pack('Na*Na*Na*',
strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey['e']), $publickey['e'], strlen($publickey['n']), $publickey['n']
);
$part1 = pack('CNa*Na*Na*',
NET_SSH2_MSG_USERAUTH_REQUEST, strlen($username), $username, strlen('ssh-connection'), 'ssh-connection',
strlen('publickey'), 'publickey'
);
$part2 = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publickey), $publickey);
$packet = $part1 . chr(0) . $part2;
if (!$this->_send_binary_packet($packet)) {
return false;
}
$response = $this->_get_binary_packet();
if ($response === false) {
user_error('Connection closed by server', E_USER_NOTICE);
return false;
}
extract(unpack('Ctype', $this->_string_shift($response, 1)));
switch ($type) {
case NET_SSH2_MSG_USERAUTH_FAILURE:
extract(unpack('Nlength', $this->_string_shift($response, 4)));
$this->debug_info.= "\r\n\r\nSSH_MSG_USERAUTH_FAILURE:\r\n" . $this->_string_shift($response, $length);
return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
case NET_SSH2_MSG_USERAUTH_PK_OK:
// we'll just take it on faith that the public key blob and the public key algorithm name are as
// they should be
if (defined('NET_SSH2_LOGGING')) {
$this->message_number_log[count($this->message_number_log) - 1] = 'NET_SSH2_MSG_USERAUTH_PK_OK';
}
}
$packet = $part1 . chr(1) . $part2;
$privatekey->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet));
$signature = pack('Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($signature), $signature);
$packet.= pack('Na*', strlen($signature), $signature);
if (!$this->_send_binary_packet($packet)) {
return false;
}
$response = $this->_get_binary_packet();
if ($response === false) {
user_error('Connection closed by server', E_USER_NOTICE);
return false;
}
extract(unpack('Ctype', $this->_string_shift($response, 1)));
switch ($type) {
case NET_SSH2_MSG_USERAUTH_FAILURE:
// either the login is bad or the server employees multi-factor authentication
return false;
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= NET_SSH2_MASK_LOGIN;
return true;
@ -1458,7 +1584,8 @@ class Net_SSH2 {
$this->get_seq_no++;
if (defined('NET_SSH2_LOGGING')) {
$this->message_number_log[] = '<- ' . $this->message_numbers[ord($payload[0])] .
$temp = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN';
$this->message_number_log[] = '<- ' . $temp .
' (' . round($stop - $start, 4) . 's)';
$this->message_log[] = $payload;
}
@ -1670,7 +1797,8 @@ class Net_SSH2 {
$stop = strtok(microtime(), ' ') + strtok('');
if (defined('NET_SSH2_LOGGING')) {
$this->message_number_log[] = '-> ' . $this->message_numbers[ord($data[0])] .
$temp = isset($this->message_numbers[ord($data[0])]) ? $this->message_numbers[ord($data[0])] : 'UNKNOWN';
$this->message_number_log[] = '-> ' . $temp .
' (' . round($stop - $start, 4) . 's)';
$this->message_log[] = $data;
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Replace str_split()
*
* @category PHP
* @package PHP_Compat
* @license LGPL - http://www.gnu.org/licenses/lgpl.html
* @copyright 2004-2007 Aidan Lister <aidan@php.net>, Arpad Ray <arpad@php.net>
* @link http://php.net/function.str_split
* @author Aidan Lister <aidan@php.net>
* @version $Revision: 1.1 $
* @since PHP 5
* @require PHP 4.0.0 (user_error)
*/
function php_compat_str_split($string, $split_length = 1)
{
if (!is_scalar($split_length)) {
user_error('str_split() expects parameter 2 to be long, ' .
gettype($split_length) . ' given', E_USER_WARNING);
return false;
}
$split_length = (int) $split_length;
if ($split_length < 1) {
user_error('str_split() The length of each segment must be greater than zero', E_USER_WARNING);
return false;
}
// Select split method
if ($split_length < 65536) {
// Faster, but only works for less than 2^16
preg_match_all('/.{1,' . $split_length . '}/s', $string, $matches);
return $matches[0];
} else {
// Required due to preg limitations
$arr = array();
$idx = 0;
$pos = 0;
$len = strlen($string);
while ($len > 0) {
$blk = ($len < $split_length) ? $len : $split_length;
$arr[$idx++] = substr($string, $pos, $blk);
$pos += $blk;
$len -= $blk;
}
return $arr;
}
}
// Define
if (!function_exists('str_split')) {
function str_split($string, $split_length = 1)
{
return php_compat_str_split($string, $split_length);
}
}