mirror of
https://github.com/phpseclib/phpseclib.git
synced 2024-12-27 11:52:48 +00:00
add expanded support for OpenSSH private keys
This commit is contained in:
parent
88b6337a3f
commit
327f555b7c
@ -19,6 +19,7 @@ namespace phpseclib\Crypt\Common\Keys;
|
|||||||
|
|
||||||
use ParagonIE\ConstantTime\Base64;
|
use ParagonIE\ConstantTime\Base64;
|
||||||
use phpseclib\Common\Functions\Strings;
|
use phpseclib\Common\Functions\Strings;
|
||||||
|
use phpseclib\Crypt\Random;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenSSH Formatted RSA Key Handler
|
* OpenSSH Formatted RSA Key Handler
|
||||||
@ -66,50 +67,99 @@ abstract class OpenSSH
|
|||||||
* @param string $type
|
* @param string $type
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function load($key, $type)
|
public static function load($key, $password = '')
|
||||||
{
|
{
|
||||||
if (!is_string($key)) {
|
if (!is_string($key)) {
|
||||||
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
|
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// key format is described here:
|
||||||
|
// https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
|
||||||
|
|
||||||
|
if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) {
|
||||||
|
$key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
|
||||||
|
$key = Base64::decode($key);
|
||||||
|
$magic = Strings::shift($key, 15);
|
||||||
|
if ($magic != "openssh-key-v1\0") {
|
||||||
|
throw new \RuntimeException('Expected openssh-key-v1');
|
||||||
|
}
|
||||||
|
list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key);
|
||||||
|
if ($numKeys != 1) {
|
||||||
|
// if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys
|
||||||
|
// would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass
|
||||||
|
// that to the appropriate key loading parser $numKey times or something
|
||||||
|
throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
|
||||||
|
}
|
||||||
|
if (strlen($kdfoptions) || $kdfname != 'none' || $ciphername != 'none') {
|
||||||
|
/*
|
||||||
|
OpenSSH private keys use a customized version of bcrypt. specifically, instead of encrypting
|
||||||
|
OrpheanBeholderScryDoubt 64 times OpenSSH's bcrypt variant encrypts
|
||||||
|
OxychromaticBlowfishSwatDynamite 64 times. so we can't use crypt().
|
||||||
|
|
||||||
|
bcrypt is basically Blowfish with an altered key expansion. whereas Blowfish just runs the
|
||||||
|
key through the key expansion bcrypt interleaves the key expansion with the salt and
|
||||||
|
password. this renders openssl / mcrypt unusuable. this forces us to use a pure-PHP implementation
|
||||||
|
of bcrypt. the problem with that is that pure-PHP is too slow to be practically useful.
|
||||||
|
|
||||||
|
in addition to encrypting a different string 64 times the OpenSSH implementation also performs bcrypt
|
||||||
|
from scratch $rounds times. calling crypt() 64x with bcrypt takes 0.7s. PHP is going to be naturally
|
||||||
|
slower. pure-PHP is 215x slower than OpenSSL for AES and pure-PHP is 43x slower for bcrypt.
|
||||||
|
43 * 0.7 = 30s. no one wants to wait 30s to load a private key.
|
||||||
|
|
||||||
|
another way to think about this.. according to wikipedia's article on Blowfish,
|
||||||
|
"Each new key requires pre-processing equivalent to encrypting about 4 kilobytes of text".
|
||||||
|
key expansion is done (9+64*2)*160 times. multiply that by 4 and it turns out that Blowfish,
|
||||||
|
OpenSSH style, is the equivalent of encrypting ~80mb of text.
|
||||||
|
|
||||||
|
more supporting evidence: sodium_compat does not implement Argon2 (another password hashing
|
||||||
|
algorithm) because "It's not feasible to polyfill scrypt or Argon2 into PHP and get reasonable
|
||||||
|
performance. Users would feel motivated to select parameters that downgrade security to avoid
|
||||||
|
denial of service (DoS) attacks. The only winning move is not to play"
|
||||||
|
-- https://github.com/paragonie/sodium_compat/blob/master/README.md
|
||||||
|
*/
|
||||||
|
throw new \RuntimeException('Encrypted OpenSSH private keys are not supported');
|
||||||
|
//list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key);
|
||||||
|
list($type) = Strings::unpackSSH2('s', $publicKey);
|
||||||
|
list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey);
|
||||||
|
// any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
|
||||||
|
if ($checkint1 != $checkint2) {
|
||||||
|
throw new \RuntimeException('The two checkints do not match');
|
||||||
|
}
|
||||||
|
self::checkType($type);
|
||||||
|
|
||||||
|
return compact('type', 'publicKey', 'paddedKey');
|
||||||
|
}
|
||||||
|
|
||||||
$parts = explode(' ', $key, 3);
|
$parts = explode(' ', $key, 3);
|
||||||
|
|
||||||
if (!isset($parts[1])) {
|
if (!isset($parts[1])) {
|
||||||
$key = Base64::decode($parts[0]);
|
$key = base64_decode($parts[0]);
|
||||||
$comment = isset($parts[1]) ? $parts[1] : false;
|
$comment = isset($parts[1]) ? $parts[1] : false;
|
||||||
} else {
|
} else {
|
||||||
if ($parts[0] != $type) {
|
$asciiType = $parts[0];
|
||||||
throw new \UnexpectedValueException('Expected a ' . $type . ' key - got a ' . $parts[0] . ' key');
|
self::checkType($parts[0]);
|
||||||
}
|
$key = base64_decode($parts[1]);
|
||||||
$key = Base64::decode($parts[1]);
|
|
||||||
$comment = isset($parts[2]) ? $parts[2] : false;
|
$comment = isset($parts[2]) ? $parts[2] : false;
|
||||||
}
|
}
|
||||||
if ($key === false) {
|
if ($key === false) {
|
||||||
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
|
throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Strings::shift($key, strlen($type) + 4) != "\0\0\0" . chr(strlen($type)) . $type) {
|
list($type) = Strings::unpackSSH2('s', $key);
|
||||||
throw new \UnexpectedValueException('Key appears to be malformed');
|
self::checkType($type);
|
||||||
|
if (isset($asciiType) && $asciiType != $type) {
|
||||||
|
throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type);
|
||||||
}
|
}
|
||||||
if (strlen($key) <= 4) {
|
if (strlen($key) <= 4) {
|
||||||
throw new \UnexpectedValueException('Key appears to be malformed');
|
throw new \UnexpectedValueException('Key appears to be malformed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $key;
|
$publicKey = $key;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return compact('type', 'publicKey', 'comment');
|
||||||
* Returns the comment for the key
|
|
||||||
*
|
|
||||||
* @access public
|
|
||||||
* @param string $key
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public static function getComment($key)
|
|
||||||
{
|
|
||||||
$parts = explode(' ', $key, 3);
|
|
||||||
|
|
||||||
return isset($parts[2]) ? $parts[2] : false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,4 +175,53 @@ abstract class OpenSSH
|
|||||||
{
|
{
|
||||||
self::$binary = $enabled;
|
self::$binary = $enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if the type is valid
|
||||||
|
*
|
||||||
|
* @access private
|
||||||
|
* @param string $candidate
|
||||||
|
*/
|
||||||
|
private static function checkType($candidate)
|
||||||
|
{
|
||||||
|
if (!in_array($candidate, static::$types)) {
|
||||||
|
throw new \RuntimeException('The key type is not equal to: ' . implode(',', static::$types));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a private key appropriately
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param string $publicKey
|
||||||
|
* @param string $privateKey
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function wrapPrivateKey($publicKey, $privateKey, $options)
|
||||||
|
{
|
||||||
|
list(, $checkint) = unpack('N', Random::string(4));
|
||||||
|
|
||||||
|
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
||||||
|
$paddedKey = Strings::packSSH2('NN', $checkint, $checkint) .
|
||||||
|
$privateKey .
|
||||||
|
Strings::packSSH2('s', $comment);
|
||||||
|
|
||||||
|
/*
|
||||||
|
from http://tools.ietf.org/html/rfc4253#section-6 :
|
||||||
|
|
||||||
|
Note that the length of the concatenation of 'packet_length',
|
||||||
|
'padding_length', 'payload', and 'random padding' MUST be a multiple
|
||||||
|
of the cipher block size or 8, whichever is larger.
|
||||||
|
*/
|
||||||
|
$paddingLength = (7 * strlen($paddedKey)) % 8;
|
||||||
|
for ($i = 1; $i <= $paddingLength; $i++) {
|
||||||
|
$paddedKey.= chr($i);
|
||||||
|
}
|
||||||
|
$key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
|
||||||
|
$key = "openssh-key-v1\0$key";
|
||||||
|
|
||||||
|
return "-----BEGIN OPENSSH PRIVATE KEY-----\r\n" .
|
||||||
|
chunk_split(Base64::encode($key), 70) .
|
||||||
|
"-----END OPENSSH PRIVATE KEY-----";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,13 @@ use phpseclib\Crypt\Common\Keys\OpenSSH as Progenitor;
|
|||||||
*/
|
*/
|
||||||
abstract class OpenSSH extends Progenitor
|
abstract class OpenSSH extends Progenitor
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Supported Key Types
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $types = ['ssh-dss'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Break a public or private key down into its constituent components
|
* Break a public or private key down into its constituent components
|
||||||
*
|
*
|
||||||
@ -41,15 +48,22 @@ abstract class OpenSSH extends Progenitor
|
|||||||
*/
|
*/
|
||||||
public static function load($key, $password = '')
|
public static function load($key, $password = '')
|
||||||
{
|
{
|
||||||
$key = parent::load($key, 'ssh-dss');
|
$parsed = parent::load($key, 'ssh-dss');
|
||||||
|
|
||||||
$result = Strings::unpackSSH2('iiii', $key);
|
if (isset($parsed['paddedKey'])) {
|
||||||
if ($result === false) {
|
list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
|
||||||
throw new \UnexpectedValueException('Key appears to be malformed');
|
if ($type != $parsed['type']) {
|
||||||
|
throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
|
||||||
}
|
}
|
||||||
list($p, $q, $g, $y) = $result;
|
|
||||||
|
|
||||||
$comment = parent::getComment($key);
|
list($p, $q, $g, $y, $x, $comment) = Strings::unpackSSH2('i5s', $parsed['paddedKey']);
|
||||||
|
|
||||||
|
return compact('p', 'q', 'g', 'y', 'x', 'comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $parsed['publicKey']);
|
||||||
|
|
||||||
|
$comment = $parsed['comment'];
|
||||||
|
|
||||||
return compact('p', 'q', 'g', 'y', 'comment');
|
return compact('p', 'q', 'g', 'y', 'comment');
|
||||||
}
|
}
|
||||||
@ -84,8 +98,29 @@ abstract class OpenSSH extends Progenitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
||||||
$DSAPublicKey = 'ssh-dss ' . Base64::encode($DSAPublicKey) . ' ' . $comment;
|
$DSAPublicKey = 'ssh-dss ' . base64_encode($DSAPublicKey) . ' ' . $comment;
|
||||||
|
|
||||||
return $DSAPublicKey;
|
return $DSAPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a private key to the appropriate format.
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param \phpseclib\Math\BigInteger $p
|
||||||
|
* @param \phpseclib\Math\BigInteger $q
|
||||||
|
* @param \phpseclib\Math\BigInteger $g
|
||||||
|
* @param \phpseclib\Math\BigInteger $x
|
||||||
|
* @param \phpseclib\Math\BigInteger $y
|
||||||
|
* @param string $password optional
|
||||||
|
* @param array $options optional
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
|
||||||
|
{
|
||||||
|
$publicKey = self::savePublicKey($p, $q, $g, $y, ['binary' => true]);
|
||||||
|
$privateKey = Strings::packSSH2('si5', 'ssh-dss', $p, $q, $g, $y, $x);
|
||||||
|
|
||||||
|
return self::wrapPrivateKey($publicKey, $privateKey, $options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ use phpseclib\Crypt\Common\Keys\PuTTY as Progenitor;
|
|||||||
/**
|
/**
|
||||||
* PuTTY Formatted DSA Key Handler
|
* PuTTY Formatted DSA Key Handler
|
||||||
*
|
*
|
||||||
* @package RSA
|
* @package DSA
|
||||||
* @author Jim Wigginton <terrafrost@php.net>
|
* @author Jim Wigginton <terrafrost@php.net>
|
||||||
* @access public
|
* @access public
|
||||||
*/
|
*/
|
||||||
@ -66,17 +66,8 @@ abstract class PuTTY extends Progenitor
|
|||||||
extract($components);
|
extract($components);
|
||||||
unset($components['public'], $components['private']);
|
unset($components['public'], $components['private']);
|
||||||
|
|
||||||
$result = Strings::unpackSSH2('iiii', $public);
|
list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $public);
|
||||||
if ($result === false) {
|
list($x) = Strings::unpackSSH2('i', $private);
|
||||||
throw new \UnexpectedValueException('Key appears to be malformed');
|
|
||||||
}
|
|
||||||
list($p, $q, $g, $y) = $result;
|
|
||||||
|
|
||||||
$result = Strings::unpackSSH2('i', $private);
|
|
||||||
if ($result === false) {
|
|
||||||
throw new \UnexpectedValueException('Key appears to be malformed');
|
|
||||||
}
|
|
||||||
list($x) = $result;
|
|
||||||
|
|
||||||
return compact('p', 'q', 'g', 'y', 'x', 'comment');
|
return compact('p', 'q', 'g', 'y', 'x', 'comment');
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ use phpseclib\Crypt\ECDSA\BaseCurves\Base as BaseCurve;
|
|||||||
use phpseclib\Exception\UnsupportedCurveException;
|
use phpseclib\Exception\UnsupportedCurveException;
|
||||||
use phpseclib\Crypt\ECDSA\Curves\Ed25519;
|
use phpseclib\Crypt\ECDSA\Curves\Ed25519;
|
||||||
use phpseclib\Math\Common\FiniteField\Integer;
|
use phpseclib\Math\Common\FiniteField\Integer;
|
||||||
use phpseclib\Crypt\Random;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpenSSH Formatted ECDSA Key Handler
|
* OpenSSH Formatted ECDSA Key Handler
|
||||||
@ -43,7 +42,7 @@ abstract class OpenSSH extends Progenitor
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $types = [
|
protected static $types = [
|
||||||
'ecdsa-sha2-nistp256',
|
'ecdsa-sha2-nistp256',
|
||||||
'ecdsa-sha2-nistp384',
|
'ecdsa-sha2-nistp384',
|
||||||
'ecdsa-sha2-nistp521',
|
'ecdsa-sha2-nistp521',
|
||||||
@ -60,113 +59,39 @@ abstract class OpenSSH extends Progenitor
|
|||||||
*/
|
*/
|
||||||
public static function load($key, $password = '')
|
public static function load($key, $password = '')
|
||||||
{
|
{
|
||||||
/*
|
$parsed = parent::load($key, $password);
|
||||||
key format is described here:
|
|
||||||
https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
|
|
||||||
|
|
||||||
this is only supported for ECDSA because of Ed25519. ssh-keygen doesn't generate a
|
if (isset($parsed['paddedKey'])) {
|
||||||
PKCS1/8 formatted private key for Ed25519 - it generates an OpenSSH formatted
|
$paddedKey = $parsed['paddedKey'];
|
||||||
private key. probably because, at the time of this writing, there's not an actual
|
list($type) = Strings::unpackSSH2('s', $paddedKey);
|
||||||
IETF RFC describing an Ed25519 format
|
if ($type != $parsed['type']) {
|
||||||
*/
|
throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
|
||||||
if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) {
|
|
||||||
$key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
|
|
||||||
$key = Base64::decode($key);
|
|
||||||
$magic = Strings::shift($key, 15);
|
|
||||||
if ($magic != "openssh-key-v1\0") {
|
|
||||||
throw new \RuntimeException('Expected openssh-key-v1');
|
|
||||||
}
|
}
|
||||||
list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key);
|
if ($type == 'ssh-ed25519' ) {
|
||||||
if ($numKeys != 1) {
|
list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey);
|
||||||
throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
|
$key = libsodium::load($key);
|
||||||
|
$key['comment'] = $comment;
|
||||||
|
return $key;
|
||||||
}
|
}
|
||||||
if (strlen($kdfoptions) || $kdfname != 'none' || $ciphername != 'none') {
|
list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey);
|
||||||
/*
|
$curve = self::loadCurveByParam(['namedCurve' => $curveName]);
|
||||||
OpenSSH private keys use a customized version of bcrypt. specifically, instead of encrypting
|
|
||||||
OrpheanBeholderScryDoubt 64 times OpenSSH's bcrypt variant encrypts
|
|
||||||
OxychromaticBlowfishSwatDynamite 64 times. so we can't use crypt().
|
|
||||||
|
|
||||||
bcrypt is basically Blowfish with an altered key expansion. whereas Blowfish just runs the
|
|
||||||
key through the key expansion bcrypt interleaves the key expansion with the salt and
|
|
||||||
password. this renders openssl / mcrypt unusuable. this forces us to use a pure-PHP implementation
|
|
||||||
of bcrypt. the problem with that is that pure-PHP is too slow to be practically useful.
|
|
||||||
|
|
||||||
in addition to encrypting a different string 64 times the OpenSSH also performs bcrypt from
|
|
||||||
scratch $rounds times. calling crypt() 64x with bcrypt takes 0.7s. PHP is going to be naturally
|
|
||||||
slower. pure-PHP is 215x slower than OpenSSL for AES and pure-PHP is 43x slower for bcrypt.
|
|
||||||
43 * 0.7 = 30s. no one wants to wait 30s to load a private key.
|
|
||||||
|
|
||||||
another way to think about this.. according to wikipedia's article on Blowfish,
|
|
||||||
"Each new key requires pre-processing equivalent to encrypting about 4 kilobytes of text".
|
|
||||||
key expansion is done (9+64*2)*160 times. multiply that by 4 and it turns out that Blowfish,
|
|
||||||
OpenSSH style, is the equivalent of encrypting ~80mb of text.
|
|
||||||
|
|
||||||
more supporting evidence: sodium_compat does not implement Argon2 (another password hashing
|
|
||||||
algorithm) because "It's not feasible to polyfill scrypt or Argon2 into PHP and get reasonable
|
|
||||||
performance. Users would feel motivated to select parameters that downgrade security to avoid
|
|
||||||
denial of service (DoS) attacks. The only winning move is not to play"
|
|
||||||
-- https://github.com/paragonie/sodium_compat/blob/master/README.md
|
|
||||||
*/
|
|
||||||
throw new \RuntimeException('Encrypted OpenSSH private keys are not supported');
|
|
||||||
//list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key);
|
|
||||||
list($type, $publicKey) = Strings::unpackSSH2('ss', $publicKey);
|
|
||||||
if ($type != 'ssh-ed25519') {
|
|
||||||
throw new UnsupportedCurveException('ssh-ed25519 is the only supported curve for OpenSSH public keys');
|
|
||||||
}
|
|
||||||
list($checkint1, $checkint2, $type, $publicKey2, $privateKey, $comment) = Strings::unpackSSH2('NNssss', $paddedKey);
|
|
||||||
// any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
|
|
||||||
if ($checkint1 != $checkint2) {
|
|
||||||
throw new \RuntimeException('The two checkints do not match');
|
|
||||||
}
|
|
||||||
if ($type != 'ssh-ed25519') {
|
|
||||||
throw new UnsupportedCurveException('ssh-ed25519 is the only supported curve for OpenSSH private keys');
|
|
||||||
}
|
|
||||||
if ($publicKey != $publicKey2 || $publicKey2 != substr($privateKey, 32)) {
|
|
||||||
throw new \RuntimeException('The public keys do not match up');
|
|
||||||
}
|
|
||||||
$privateKey = substr($privateKey, 0, 32);
|
|
||||||
$curve = new Ed25519();
|
|
||||||
return [
|
return [
|
||||||
'curve' => $curve,
|
'curve' => $curve,
|
||||||
'dA' => $curve->extractSecret($privateKey),
|
'dA' => $curve->convertInteger($privateKey),
|
||||||
'QA' => self::extractPoint($publicKey, $curve),
|
'QA' => self::extractPoint("\0$publicKey", $curve),
|
||||||
'comment' => $comment
|
'comment' => $comment
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$parts = explode(' ', $key, 3);
|
if ($parsed['type'] == 'ssh-ed25519') {
|
||||||
|
if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") {
|
||||||
if (!isset($parts[1])) {
|
|
||||||
$key = Base64::decode($parts[0]);
|
|
||||||
$comment = isset($parts[1]) ? $parts[1] : false;
|
|
||||||
} else {
|
|
||||||
$asciiType = $parts[0];
|
|
||||||
if (!in_array($asciiType, self::$types)) {
|
|
||||||
throw new \RuntimeException('Keys of type ' . $asciiType . ' are not supported');
|
|
||||||
}
|
|
||||||
$key = Base64::decode($parts[1]);
|
|
||||||
$comment = isset($parts[2]) ? $parts[2] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
list($binaryType) = Strings::unpackSSH2('s', $key);
|
|
||||||
if (isset($asciiType) && $asciiType != $binaryType) {
|
|
||||||
throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $binaryType);
|
|
||||||
} elseif (!isset($asciiType) && !in_array($binaryType, self::$types)) {
|
|
||||||
throw new \RuntimeException('Keys of type ' . $binaryType . ' are not supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($binaryType == 'ssh-ed25519') {
|
|
||||||
if (Strings::shift($key, 4) != "\0\0\0\x20") {
|
|
||||||
throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
|
throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
|
||||||
}
|
}
|
||||||
|
|
||||||
$curve = new Ed25519();
|
$curve = new Ed25519();
|
||||||
$qa = self::extractPoint($key, $curve);
|
$qa = self::extractPoint($parsed['publicKey'], $curve);
|
||||||
} else {
|
} else {
|
||||||
list($curveName, $publicKey) = Strings::unpackSSH2('ss', $key);
|
list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']);
|
||||||
$curveName = '\phpseclib\Crypt\ECDSA\Curves\\' . $curveName;
|
$curveName = '\phpseclib\Crypt\ECDSA\Curves\\' . $curveName;
|
||||||
$curve = new $curveName();
|
$curve = new $curveName();
|
||||||
|
|
||||||
@ -176,34 +101,17 @@ abstract class OpenSSH extends Progenitor
|
|||||||
return [
|
return [
|
||||||
'curve' => $curve,
|
'curve' => $curve,
|
||||||
'QA' => $qa,
|
'QA' => $qa,
|
||||||
'comment' => $comment
|
'comment' => $parsed['comment']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert an ECDSA public key to the appropriate format
|
* Returns the alias that corresponds to a curve
|
||||||
*
|
*
|
||||||
* @access public
|
|
||||||
* @param \phpseclib\Crypt\ECDSA\BaseCurves\Base $curve
|
|
||||||
* @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey
|
|
||||||
* @param array $options optional
|
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
|
private static function getAlias(BaseCurve $curve)
|
||||||
{
|
{
|
||||||
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
|
||||||
|
|
||||||
if ($curve instanceof Ed25519) {
|
|
||||||
$key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey));
|
|
||||||
|
|
||||||
if (self::$binary) {
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
$key = 'ssh-ed25519 ' . Base64::encode($key) . ' ' . $comment;
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::initialize_static_variables();
|
self::initialize_static_variables();
|
||||||
|
|
||||||
$reflect = new \ReflectionClass($curve);
|
$reflect = new \ReflectionClass($curve);
|
||||||
@ -226,6 +134,35 @@ abstract class OpenSSH extends Progenitor
|
|||||||
throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports');
|
throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an ECDSA public key to the appropriate format
|
||||||
|
*
|
||||||
|
* @access public
|
||||||
|
* @param \phpseclib\Crypt\ECDSA\BaseCurves\Base $curve
|
||||||
|
* @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey
|
||||||
|
* @param array $options optional
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
|
||||||
|
{
|
||||||
|
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
||||||
|
|
||||||
|
if ($curve instanceof Ed25519) {
|
||||||
|
$key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey));
|
||||||
|
|
||||||
|
if (self::$binary) {
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment;
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alias = self::getAlias($curve);
|
||||||
|
|
||||||
$points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
|
$points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
|
||||||
$key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points);
|
$key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points);
|
||||||
|
|
||||||
@ -233,7 +170,7 @@ abstract class OpenSSH extends Progenitor
|
|||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
$key = 'ecdsa-sha2-' . $alias . ' ' . Base64::encode($key) . ' ' . $comment;
|
$key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment;
|
||||||
|
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
@ -246,10 +183,12 @@ abstract class OpenSSH extends Progenitor
|
|||||||
* @param \phpseclib\Crypt\ECDSA\Curves\Ed25519 $curve
|
* @param \phpseclib\Crypt\ECDSA\Curves\Ed25519 $curve
|
||||||
* @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey
|
* @param \phpseclib\Math\Common\FiniteField\Integer[] $publicKey
|
||||||
* @param string $password optional
|
* @param string $password optional
|
||||||
|
* @param array $options optional
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function savePrivateKey(Integer $privateKey, Ed25519 $curve, array $publicKey, $password = '')
|
public static function savePrivateKey(Integer $privateKey, BaseCurve $curve, array $publicKey, $password = '', array $options = [])
|
||||||
{
|
{
|
||||||
|
if ($curve instanceof Ed25519) {
|
||||||
if (!isset($privateKey->secret)) {
|
if (!isset($privateKey->secret)) {
|
||||||
throw new \RuntimeException('Private Key does not have a secret set');
|
throw new \RuntimeException('Private Key does not have a secret set');
|
||||||
}
|
}
|
||||||
@ -257,27 +196,21 @@ abstract class OpenSSH extends Progenitor
|
|||||||
throw new \RuntimeException('Private Key secret is not of the correct length');
|
throw new \RuntimeException('Private Key secret is not of the correct length');
|
||||||
}
|
}
|
||||||
|
|
||||||
list(, $checkint) = unpack('N', Random::string(4));
|
|
||||||
$pubKey = $curve->encodePoint($publicKey);
|
$pubKey = $curve->encodePoint($publicKey);
|
||||||
|
|
||||||
$publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey);
|
$publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey);
|
||||||
$paddedKey = Strings::packSSH2('NNssss', $checkint, $checkint, 'ssh-ed25519', $pubKey, $privateKey->secret . $pubKey, self::$comment);
|
$privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $privateKey->secret . $pubKey);
|
||||||
/*
|
|
||||||
from http://tools.ietf.org/html/rfc4253#section-6 :
|
|
||||||
|
|
||||||
Note that the length of the concatenation of 'packet_length',
|
return self::wrapPrivateKey($publicKey, $privateKey, $options);
|
||||||
'padding_length', 'payload', and 'random padding' MUST be a multiple
|
|
||||||
of the cipher block size or 8, whichever is larger.
|
|
||||||
*/
|
|
||||||
$paddingLength = (7 * strlen($paddedKey)) % 8;
|
|
||||||
for ($i = 1; $i <= $paddingLength; $i++) {
|
|
||||||
$paddedKey.= chr($i);
|
|
||||||
}
|
}
|
||||||
$key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
|
|
||||||
$key = "openssh-key-v1\0$key";
|
|
||||||
|
|
||||||
return "-----BEGIN OPENSSH PRIVATE KEY-----\r\n" .
|
$alias = self::getAlias($curve);
|
||||||
chunk_split(Base64::encode($key), 70) .
|
|
||||||
"-----END OPENSSH PRIVATE KEY-----";
|
$points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
|
||||||
|
$publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]);
|
||||||
|
|
||||||
|
$privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey);
|
||||||
|
|
||||||
|
return self::wrapPrivateKey($publicKey, $privateKey, $options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,13 @@ use phpseclib\Crypt\Common\Keys\OpenSSH as Progenitor;
|
|||||||
*/
|
*/
|
||||||
abstract class OpenSSH extends Progenitor
|
abstract class OpenSSH extends Progenitor
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Supported Key Types
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $types = ['ssh-rsa'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Break a public or private key down into its constituent components
|
* Break a public or private key down into its constituent components
|
||||||
*
|
*
|
||||||
@ -41,19 +48,48 @@ abstract class OpenSSH extends Progenitor
|
|||||||
*/
|
*/
|
||||||
public static function load($key, $password = '')
|
public static function load($key, $password = '')
|
||||||
{
|
{
|
||||||
$key = parent::load($key, 'ssh-rsa');
|
static $one;
|
||||||
|
if (!isset($one)) {
|
||||||
$result = Strings::unpackSSH2('ii', $key);
|
$one = new BigInteger(1);
|
||||||
if ($result === false) {
|
|
||||||
throw new \UnexpectedValueException('Key appears to be malformed');
|
|
||||||
}
|
}
|
||||||
list($publicExponent, $modulus) = $result;
|
|
||||||
|
$parsed = parent::load($key, $password);
|
||||||
|
|
||||||
|
if (isset($parsed['paddedKey'])) {
|
||||||
|
list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
|
||||||
|
if ($type != $parsed['type']) {
|
||||||
|
throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
|
||||||
|
}
|
||||||
|
|
||||||
|
$primes = $coefficients = [];
|
||||||
|
|
||||||
|
list(
|
||||||
|
$modulus,
|
||||||
|
$publicExponent,
|
||||||
|
$privateExponent,
|
||||||
|
$coefficients[2],
|
||||||
|
$primes[1],
|
||||||
|
$primes[2],
|
||||||
|
$comment,
|
||||||
|
) = Strings::unpackSSH2('i6s', $parsed['paddedKey']);
|
||||||
|
|
||||||
|
$temp = $primes[1]->subtract($one);
|
||||||
|
$exponents = [1 => $publicExponent->modInverse($temp)];
|
||||||
|
$temp = $primes[2]->subtract($one);
|
||||||
|
$exponents[] = $publicExponent->modInverse($temp);
|
||||||
|
|
||||||
|
$isPublicKey = false;
|
||||||
|
|
||||||
|
return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
|
||||||
|
}
|
||||||
|
|
||||||
|
list($publicExponent, $modulus) = Strings::unpackSSH2('ii', $parsed['publicKey']);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'isPublicKey' => true,
|
'isPublicKey' => true,
|
||||||
'modulus' => $modulus,
|
'modulus' => $modulus,
|
||||||
'publicExponent' => $publicExponent,
|
'publicExponent' => $publicExponent,
|
||||||
'comment' => parent::getComment($key)
|
'comment' => $parsed['comment']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,8 +111,30 @@ abstract class OpenSSH extends Progenitor
|
|||||||
}
|
}
|
||||||
|
|
||||||
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
$comment = isset($options['comment']) ? $options['comment'] : self::$comment;
|
||||||
$RSAPublicKey = 'ssh-rsa ' . Base64::encode($RSAPublicKey) . ' ' . $comment;
|
$RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $comment;
|
||||||
|
|
||||||
return $RSAPublicKey;
|
return $RSAPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @param array $options optional
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
|
||||||
|
{
|
||||||
|
$publicKey = self::savePublicKey($n, $e, ['binary' => true]);
|
||||||
|
$privateKey = Strings::packSSH2('si6', 'ssh-rsa', $n, $e, $d, $coefficients[2], $primes[1], $primes[2]);
|
||||||
|
|
||||||
|
return self::wrapPrivateKey($publicKey, $privateKey, $options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,4 +218,41 @@ ZpmyOpXM/0opRMIRdmqVW4ardBFNokmlqngwcbaptfRnk9W2cQtx0lmKy6X/vnis
|
|||||||
strtolower(preg_replace('#\s#', '', $key))
|
strtolower(preg_replace('#\s#', '', $key))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testOpenSSHPrivate()
|
||||||
|
{
|
||||||
|
$key = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABswAAAAdzc2gtZH
|
||||||
|
NzAAAAgQDpE1/71V6uuaeEqbaAzoEsA1kdJBZh9In3/VlCXwvlJ6zz8KSzbQxrC45sO7y9
|
||||||
|
fMwD5QyWEphVeIXO/NSfcZhK/SD/D+N1Zx52Ku2KEFTb3dAhfNGe9yhsrAVI5WyE4lS2qe
|
||||||
|
e5fLNnh138hYAdN7ENRoUAQ3I6Hk9HAIn+ltHMmQAAABUA95iPdxHL3ikkmZd1X5WhQFTI
|
||||||
|
+9sAAACBAMcn1PdWdUmE8D4KP6g0rq4KAElZc904mYX+bHQNMXONm4BrsScn3/iOf370Ea
|
||||||
|
iUgkomo+CSP2H8S3pLBNbiQW7AzS9TGT782FlG/bXf8kSMFb7IzAuFmQMeouLZo40AwHEv
|
||||||
|
7PpdzrXs6GRQ0vwJlNoqoUAUi9MMhexDzpGMbNjqAAAAgQCU1JuJZDzpk+cBgEdRTRGx6m
|
||||||
|
JZkP9vHP7ctUhgKZcAPSyd8keN8gQCpvmZuK1ADtd/+pXBxbQBAPb1+p8wAgqDU4m8+LFf
|
||||||
|
2igKtb8mf8qp/ghxV08/Tzf5WfcDWPxOesdlN48qLbSmUgsO7gq/1vodebMSHcduV4JTq8
|
||||||
|
ix5Ey87QAAAeiOLNHLjizRywAAAAdzc2gtZHNzAAAAgQDpE1/71V6uuaeEqbaAzoEsA1kd
|
||||||
|
JBZh9In3/VlCXwvlJ6zz8KSzbQxrC45sO7y9fMwD5QyWEphVeIXO/NSfcZhK/SD/D+N1Zx
|
||||||
|
52Ku2KEFTb3dAhfNGe9yhsrAVI5WyE4lS2qee5fLNnh138hYAdN7ENRoUAQ3I6Hk9HAIn+
|
||||||
|
ltHMmQAAABUA95iPdxHL3ikkmZd1X5WhQFTI+9sAAACBAMcn1PdWdUmE8D4KP6g0rq4KAE
|
||||||
|
lZc904mYX+bHQNMXONm4BrsScn3/iOf370EaiUgkomo+CSP2H8S3pLBNbiQW7AzS9TGT78
|
||||||
|
2FlG/bXf8kSMFb7IzAuFmQMeouLZo40AwHEv7PpdzrXs6GRQ0vwJlNoqoUAUi9MMhexDzp
|
||||||
|
GMbNjqAAAAgQCU1JuJZDzpk+cBgEdRTRGx6mJZkP9vHP7ctUhgKZcAPSyd8keN8gQCpvmZ
|
||||||
|
uK1ADtd/+pXBxbQBAPb1+p8wAgqDU4m8+LFf2igKtb8mf8qp/ghxV08/Tzf5WfcDWPxOes
|
||||||
|
dlN48qLbSmUgsO7gq/1vodebMSHcduV4JTq8ix5Ey87QAAABQhHEzWiduF4V0DestSnJ3q
|
||||||
|
9GNNTQAAAAxyb290QHZhZ3JhbnQBAgMEBQ==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----';
|
||||||
|
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$key2 = PublicKeyLoader::load($key->toString('OpenSSH'));
|
||||||
|
$this->assertInstanceOf(PrivateKey::class, $key2);
|
||||||
|
|
||||||
|
$sig = $key->sign('zzz');
|
||||||
|
|
||||||
|
$key = 'ssh-dss AAAAB3NzaC1kc3MAAACBAOkTX/vVXq65p4SptoDOgSwDWR0kFmH0iff9WUJfC+UnrPPwpLNtDGsLjmw7vL18zAPlDJYSmFV4hc781J9xmEr9IP8P43VnHnYq7YoQVNvd0CF80Z73KGysBUjlbITiVLap57l8s2eHXfyFgB03sQ1GhQBDcjoeT0cAif6W0cyZAAAAFQD3mI93EcveKSSZl3VflaFAVMj72wAAAIEAxyfU91Z1SYTwPgo/qDSurgoASVlz3TiZhf5sdA0xc42bgGuxJyff+I5/fvQRqJSCSiaj4JI/YfxLeksE1uJBbsDNL1MZPvzYWUb9td/yRIwVvsjMC4WZAx6i4tmjjQDAcS/s+l3OtezoZFDS/AmU2iqhQBSL0wyF7EPOkYxs2OoAAACBAJTUm4lkPOmT5wGAR1FNEbHqYlmQ/28c/ty1SGAplwA9LJ3yR43yBAKm+Zm4rUAO13/6lcHFtAEA9vX6nzACCoNTibz4sV/aKAq1vyZ/yqn+CHFXTz9PN/lZ9wNY/E56x2U3jyottKZSCw7uCr/W+h15sxIdx25XglOryLHkTLzt root@vagrant';
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$this->assertTrue($key->verify('zzz', $sig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ use phpseclib\Crypt\ECDSA\Keys\PuTTY;
|
|||||||
use phpseclib\Crypt\ECDSA\Keys\OpenSSH;
|
use phpseclib\Crypt\ECDSA\Keys\OpenSSH;
|
||||||
use phpseclib\Crypt\ECDSA\Keys\XML;
|
use phpseclib\Crypt\ECDSA\Keys\XML;
|
||||||
use phpseclib\Crypt\PublicKeyLoader;
|
use phpseclib\Crypt\PublicKeyLoader;
|
||||||
|
use phpseclib\Crypt\ECDSA\PrivateKey;
|
||||||
|
|
||||||
class Unit_Crypt_ECDSA_LoadKeyTest extends PhpseclibTestCase
|
class Unit_Crypt_ECDSA_LoadKeyTest extends PhpseclibTestCase
|
||||||
{
|
{
|
||||||
@ -441,4 +442,48 @@ pomV7r6gmoMYteGVABfgAAAAD3ZhZ3JhbnRAdmFncmFudAECAwQFBg==
|
|||||||
$actual = str_replace("\r\n", "\n", $actual);
|
$actual = str_replace("\r\n", "\n", $actual);
|
||||||
return parent::assertSame($expected, $actual, $message);
|
return parent::assertSame($expected, $actual, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testOpenSSHPrivateECDSA()
|
||||||
|
{
|
||||||
|
$key = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
|
||||||
|
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTk2tbDiyQPzljR+LLIsMzJiwqkfHkG
|
||||||
|
StUt3kO00FKMoYv3RJfP6mqdE3E3pPcT5cBg4yB+KzYsYDxwuBc03oQcAAAAqCTU2l0k1N
|
||||||
|
pdAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOTa1sOLJA/OWNH4
|
||||||
|
ssiwzMmLCqR8eQZK1S3eQ7TQUoyhi/dEl8/qap0TcTek9xPlwGDjIH4rNixgPHC4FzTehB
|
||||||
|
wAAAAgZ8mK8+EsQ46susQn4mwMNmpvTaKX9Q9KDvOrzotP2qgAAAAMcm9vdEB2YWdyYW50
|
||||||
|
AQIDBA==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----';
|
||||||
|
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$key2 = PublicKeyLoader::load($key->toString('OpenSSH'));
|
||||||
|
$this->assertInstanceOf(PrivateKey::class, $key2);
|
||||||
|
|
||||||
|
$sig = $key->sign('zzz');
|
||||||
|
|
||||||
|
$key = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOTa1sOLJA/OWNH4ssiwzMmLCqR8eQZK1S3eQ7TQUoyhi/dEl8/qap0TcTek9xPlwGDjIH4rNixgPHC4FzTehBw= root@vagrant';
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$this->assertTrue($key->verify('zzz', $sig));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpenSSHPrivateEd25519()
|
||||||
|
{
|
||||||
|
$key = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||||
|
QyNTUxOQAAACChhCZwqkIh43AfURPOgbyYeZRCKvd4jFcyAK4xmiqxQwAAAJDqGgwS6hoM
|
||||||
|
EgAAAAtzc2gtZWQyNTUxOQAAACChhCZwqkIh43AfURPOgbyYeZRCKvd4jFcyAK4xmiqxQw
|
||||||
|
AAAEDzL/Yl1Vr/5MxhIIEkVKXBMEIumVG8gUjT9i2PTGSehqGEJnCqQiHjcB9RE86BvJh5
|
||||||
|
lEIq93iMVzIArjGaKrFDAAAADHJvb3RAdmFncmFudAE=
|
||||||
|
-----END OPENSSH PRIVATE KEY-----';
|
||||||
|
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
$sig = $key->sign('zzz');
|
||||||
|
|
||||||
|
$key = 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKGEJnCqQiHjcB9RE86BvJh5lEIq93iMVzIArjGaKrFD root@vagrant';
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$this->assertTrue($key->verify('zzz', $sig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -925,4 +925,58 @@ IBgv3a3Lyb+IQtT75LE1yjE=
|
|||||||
$this->assertSame($r['MGFHash'], $r2['MGFHash']);
|
$this->assertSame($r['MGFHash'], $r2['MGFHash']);
|
||||||
$this->assertSame($r['saltLength'], $r2['saltLength']);
|
$this->assertSame($r['saltLength'], $r2['saltLength']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testOpenSSHPrivate()
|
||||||
|
{
|
||||||
|
$key = '-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----';
|
||||||
|
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$key2 = PublicKeyLoader::load($key->toString('OpenSSH'));
|
||||||
|
$this->assertInstanceOf(PrivateKey::class, $key2);
|
||||||
|
|
||||||
|
$sig = $key->sign('zzz');
|
||||||
|
|
||||||
|
$key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDS8/TfgDLaowESNlVxYcLOSEPS1SAeBTIu5S2s8oRlTTrBD9ZyGSJBdsPR2ADGVmhD9lHQ3dbhjSpkIqC4msZ+2yPLJyIpyOY9BdW+V4sUfQUO5BOO8koyIThDSkJYdpsP2SWUtZU5gn+LAFqcGlSfj90BBoYWMtCNEsgBFMjo1kYDGYhoFv5wjyxz6rMHRZ9swYzT7wA7nJ5pmdEm10YCH0AcM4dP5Xqu5t1hWyhgEgd2UCfNy/4RJSFBhgk7Jb+rah3ovwgXn5ZU9CLPyc17fOeNFyT5P8pJjpfx9L9XJ+ZAOOSZIxSTePe4HrWo3NR1E2ZMqBNOsx8ljlsz6MNxShwfk6leFg40nh2k/VAOoWB7j5loKfkzPz0GOGQlR44xaVoHwqq/PsHOKUKYP6q/hOiyxOpxkgayB0QgbsdK32VWzPSZ523/r35iaS6GKRJ9nREAA74lRnmBdMbAWbQEWklY0vEl46yADpZCZRAZEretjXCDYsw0ri7RXKj8u8s= root@vagrant';
|
||||||
|
$key = PublicKeyLoader::load($key);
|
||||||
|
|
||||||
|
$this->assertTrue($key->verify('zzz', $sig));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user