RSA: make it so PSS keys can be saved

This commit is contained in:
terrafrost 2019-06-01 19:35:17 -05:00
parent 5b89ff4177
commit 8e03f5bfb2
6 changed files with 154 additions and 14 deletions

View File

@ -53,6 +53,7 @@ use phpseclib\Crypt\RSA\PublicKey;
use phpseclib\Math\BigInteger;
use phpseclib\Exceptions\UnsupportedAlgorithmException;
use phpseclib\Exceptions\InconsistentSetupException;
use phpseclib\Crypt\RSA\Keys\PSS;
/**
* Pure-PHP PKCS#1 compliant implementation of RSA.
@ -405,14 +406,26 @@ abstract class RSA extends AsymmetricKey
if ($components['isPublicKey']) {
$key->exponent = $key->publicExponent;
return $key;
} else {
$key->privateExponent = $components['privateExponent'];
$key->exponent = $key->privateExponent;
$key->primes = $components['primes'];
$key->exponents = $components['exponents'];
$key->coefficients = $components['coefficients'];
}
$key->privateExponent = $components['privateExponent'];
$key->exponent = $key->privateExponent;
$key->primes = $components['primes'];
$key->exponents = $components['exponents'];
$key->coefficients = $components['coefficients'];
if ($components['format'] == PSS::class) {
$key = $key->withPadding(self::SIGNATURE_PSS);
if (isset($components['hash'])) {
$key = $key->withHash($components['hash']);
}
if (isset($components['MGFHash'])) {
$key = $key->withMGFHash($components['MGFHash']);
}
if (isset($components['saltLength'])) {
$key = $key->withSaltLength($components['saltLength']);
}
}
return $key;
}

View File

@ -9,11 +9,11 @@
*
* Processes keys with the following headers:
*
* -----BEGIN ENCRYPTED PRIVATE KEY-----
* -----BEGIN PRIVATE KEY-----
* -----BEGIN PUBLIC KEY-----
*
* Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
* is specific to private keys it's basically creating a DER-encoded wrapper
* for keys. This just extends that same concept to public keys (much like ssh-keygen)
* Analogous to "openssl genpkey -algorithm rsa-pss".
*
* @category Crypt
* @package RSA
@ -28,6 +28,7 @@ namespace phpseclib\Crypt\RSA\Keys;
use phpseclib\Math\BigInteger;
use phpseclib\Crypt\Common\Keys\PKCS8 as Progenitor;
use phpseclib\File\ASN1;
use phpseclib\File\ASN1\Maps;
/**
* PKCS#8 Formatted RSA-PSS Key Handler
@ -122,13 +123,13 @@ abstract class PSS extends Progenitor
if ($decoded === false) {
throw new \UnexpectedValueException('Unable to decode parameters');
}
$params = ASN1::asn1map($decoded[0], ASN1\Maps\RSASSA_PSS_params::MAP);
$params = ASN1::asn1map($decoded[0], Maps\RSASSA_PSS_params::MAP);
if (isset($params['maskGenAlgorithm']['parameters'])) {
$decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']);
if ($decoded === false) {
throw new \UnexpectedValueException('Unable to decode parameters');
}
$params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\HashAlgorithm::MAP);
$params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\HashAlgorithm::MAP);
} else {
$params['maskGenAlgorithm'] = [
'algorithm' => 'id-mgf1',
@ -136,6 +137,10 @@ abstract class PSS extends Progenitor
];
}
if (!isset($params['hashAlgorithm']['algorithm'])) {
$params['hashAlgorithm']['algorithm'] = 'id-sha1';
}
$result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']);
$result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']);
$result['saltLength'] = (int) $params['saltLength']->toString();
@ -146,4 +151,90 @@ abstract class PSS extends Progenitor
return $result;
}
/**
* 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, $primes, $exponents, $coefficients, $password = '', $options = [])
{
self::initialize_static_variables();
$key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
$key = ASN1::extractBER($key);
$params = self::savePSSParams($options);
return self::wrapPrivateKey($key, [], $params, $password, $options);
}
/**
* Convert a public key to the appropriate format
*
* @access public
* @param \phpseclib\Math\BigInteger $n
* @param \phpseclib\Math\BigInteger $e
* @param array $options optional
* @return string
*/
public static function savePublicKey(BigInteger $n, BigInteger $e, $options = [])
{
$key = PKCS1::savePublicKey($n, $e);
$key = ASN1::extractBER($key);
$params = self::savePSSParams($options);
return self::wrapPublicKey($key, $params);
}
/**
* Encodes PSS parameters
*
* @access public
* @param array $options
* @return string
*/
private static function savePSSParams($options)
{
/*
The trailerField field is an integer. It provides
compatibility with IEEE Std 1363a-2004 [P1363A]. The value
MUST be 1, which represents the trailer field with hexadecimal
value 0xBC. Other trailer fields, including the trailer field
composed of HashID concatenated with 0xCC that is specified in
IEEE Std 1363a, are not supported. Implementations that
perform signature generation MUST omit the trailerField field,
indicating that the default trailer field value was used.
Implementations that perform signature validation MUST
recognize both a present trailerField field with value 1 and an
absent trailerField field.
source: https://tools.ietf.org/html/rfc4055#page-9
*/
$params = [
'trailerField' => new BigInteger(1)
];
if (isset($options['hash'])) {
$params['hashAlgorithm']['algorithm'] = 'id-' . $options['hash'];
}
if (isset($options['MGFHash'])) {
$temp = ['algorithm' => 'id-' . $options['MGFHash']];
$temp = ASN1::encodeDER($temp, Maps\HashAlgorithm::MAP);
$params['maskGenAlgorithm'] = [
'algorithm' => 'id-mgf1',
'parameters' => new ASN1\Element($temp)
];
}
if (isset($options['saltLength'])) {
$params['saltLength'] = new BigInteger($options['saltLength']);
}
return new ASN1\Element(ASN1::encodeDER($params, Maps\RSASSA_PSS_params::MAP));
}
}

View File

@ -22,6 +22,7 @@ use phpseclib\Exceptions\NoKeyLoadedException;
use phpseclib\Exception\UnsupportedFormatException;
use phpseclib\Crypt\Random;
use phpseclib\Crypt\Common;
use phpseclib\Crypt\RSA\Keys\PSS;
/**
* Raw RSA Key Handler
@ -524,9 +525,21 @@ class PrivateKey extends RSA implements Common\PrivateKey
$type,
empty($this->primes) ? 'savePublicKey' : 'savePrivateKey'
);
if ($type == PSS::class) {
if ($this->signaturePadding == self::SIGNATURE_PSS) {
$options+= [
'hash' => $this->hash->getHash(),
'MGFHash' => $this->mgfHash->getHash(),
'saltLength' => $this->sLen
];
} else {
throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
}
}
if (empty($this->primes)) {
return $type::savePublicKey($this->modulus, $this->exponent);
return $type::savePublicKey($this->modulus, $this->exponent, $options);
}
return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);

View File

@ -22,6 +22,7 @@ use phpseclib\Exceptions\NoKeyLoadedException;
use phpseclib\Crypt\Random;
use phpseclib\Crypt\Common;
use phpseclib\File\ASN1\Maps\DigestInfo;
use phpseclib\Crypt\RSA\Keys\PSS;
/**
* Raw RSA Key Handler
@ -472,6 +473,18 @@ class PublicKey extends RSA implements Common\PublicKey
{
$type = self::validatePlugin('Keys', $type, 'savePublicKey');
if ($type == PSS::class) {
if ($this->signaturePadding == self::SIGNATURE_PSS) {
$options+= [
'hash' => $this->hash->getHash(),
'MGFHash' => $this->mgfHash->getHash(),
'saltLength' => $this->sLen
];
} else {
throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
}
}
return $type::savePublicKey($this->modulus, $this->publicExponent, $options);
}

View File

@ -35,13 +35,13 @@ abstract class RSASSA_PSS_params
'constant' => 0,
'optional' => true,
'explicit' => true,
'default' => 'sha1Identifier'
//'default' => 'sha1Identifier'
] + HashAlgorithm::MAP,
'maskGenAlgorithm' => [
'constant' => 1,
'optional' => true,
'explicit' => true,
'default' => 'mgf1SHA1Identifier'
//'default' => 'mgf1SHA1Identifier'
] + MaskGenAlgorithm::MAP,
'saltLength' => [
'type' => ASN1::TYPE_INTEGER,

View File

@ -13,6 +13,7 @@ use phpseclib\Crypt\RSA\Keys\PKCS1;
use phpseclib\Crypt\RSA\Keys\PKCS8;
use phpseclib\Crypt\RSA\Keys\PuTTY;
use phpseclib\Crypt\RSA\Keys\OpenSSH;
use phpseclib\Crypt\RSA\Keys\PSS;
use phpseclib\Math\BigInteger;
class Unit_Crypt_RSA_LoadKeyTest extends PhpseclibTestCase
@ -914,5 +915,14 @@ IBgv3a3Lyb+IQtT75LE1yjE=
$rsa = PublicKeyLoader::load($key);
$this->assertInstanceOf(PrivateKey::class, $rsa);
$this->assertInstanceOf(PublicKey::class, $rsa->getPublicKey());
$r = PSS::load($key);
$key = $rsa->toString('PSS');
$r2 = PSS::load($key);
$this->assertSame($r['hash'], $r2['hash']);
$this->assertSame($r['MGFHash'], $r2['MGFHash']);
$this->assertSame($r['saltLength'], $r2['saltLength']);
}
}