From 55384afdacd2f69fff6b1593a9961e46788b7dd5 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 15 Feb 2018 05:29:14 -0600 Subject: [PATCH] RSA: make it so you can't sign w/ public key, decrypt w/ private --- phpseclib/Crypt/RSA.php | 56 ++++++++++++- phpseclib/Exception/NoKeyLoadedException.php | 26 +++++++ .../UnsupportedOperationException.php | 26 +++++++ phpseclib/File/X509.php | 9 ++- tests/Unit/Crypt/RSA/ModeTest.php | 78 +++++++++++++++++++ tests/Unit/File/X509/SPKACTest.php | 5 +- 6 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 phpseclib/Exception/NoKeyLoadedException.php create mode 100644 phpseclib/Exception/UnsupportedOperationException.php diff --git a/phpseclib/Crypt/RSA.php b/phpseclib/Crypt/RSA.php index 0ae17f6d..115735ac 100644 --- a/phpseclib/Crypt/RSA.php +++ b/phpseclib/Crypt/RSA.php @@ -52,6 +52,8 @@ use phpseclib\Common\Functions\Strings; use phpseclib\File\ASN1\Maps\DigestInfo; use phpseclib\Crypt\Common\AsymmetricKey; use phpseclib\Exception\UnsupportedAlgorithmException; +use phpseclib\Exception\UnsupportedOperationException; +use phpseclib\Exception\NoKeyLoadedException; /** * Pure-PHP PKCS#1 compliant implementation of RSA. @@ -201,7 +203,6 @@ class RSA extends AsymmetricKey */ private $sLen; - /** * Comment * @@ -243,6 +244,14 @@ class RSA extends AsymmetricKey */ private static $defaultExponent = 65537; + /** + * Is the loaded key a public key? + * + * @var bool + * @access private + */ + private $isPublic = false; + /** * Smallest Prime * @@ -450,6 +459,7 @@ class RSA extends AsymmetricKey $publickey->k = $bits >> 3; $publickey->exponent = $e; $publickey->publicExponent = $e; + $publickey->isPublic = true; return compact('privatekey', 'publickey'); } @@ -475,6 +485,7 @@ class RSA extends AsymmetricKey $this->sLen = $key->sLen; $this->mgfHLen = $key->mgfHLen; $this->password = $key->password; + $this->isPublic = $key->isPublic; if (is_object($key->hash)) { $this->hash = new Hash($key->hash->getHash()); @@ -524,6 +535,7 @@ class RSA extends AsymmetricKey return false; } + $this->isPublic = false; $this->modulus = $components['modulus']; $this->k = $this->modulus->getLengthInBytes(); $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent']; @@ -669,6 +681,7 @@ class RSA extends AsymmetricKey } if ($key === false && !empty($this->modulus)) { + $this->isPublic = true; $this->publicExponent = $this->exponent; return true; } @@ -681,6 +694,7 @@ class RSA extends AsymmetricKey if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) { $this->modulus = $components['modulus']; $this->exponent = $this->publicExponent = $components['publicExponent']; + $this->isPublic = true; return true; } @@ -689,6 +703,18 @@ class RSA extends AsymmetricKey return true; } + /** + * Does the key self-identify as being a public key or not? + * + * @see self::isPublicKey() + * @access public + * @return bool + */ + public function isPublicKey() + { + return $this->isPublic(); + } + /** * Defines the private key * @@ -1729,6 +1755,14 @@ class RSA extends AsymmetricKey */ public function encrypt($plaintext, $padding = self::PADDING_OAEP) { + if (empty($this->modulus) || empty($this->exponent)) { + throw new NoKeyLoadedException('No key has been loaded'); + } + + if (!$this->isPublic) { + throw new UnsupportedOperationException('phpseclib does not allow the use of private keys to encrypt data'); + } + switch ($padding) { case self::PADDING_NONE: return $this->raw_encrypt($plaintext); @@ -1752,6 +1786,14 @@ class RSA extends AsymmetricKey */ public function decrypt($ciphertext, $padding = self::PADDING_OAEP) { + if (empty($this->modulus) || empty($this->exponent)) { + throw new NoKeyLoadedException('No key has been loaded'); + } + + if ($this->isPublic) { + throw new UnsupportedOperationException('phpseclib does not allow the use of public keys to decrypt data'); + } + switch ($padding) { case self::PADDING_NONE: return $this->raw_encrypt($ciphertext); @@ -1775,7 +1817,11 @@ class RSA extends AsymmetricKey public function sign($message, $padding = self::PADDING_PSS) { if (empty($this->modulus) || empty($this->exponent)) { - return false; + throw new NoKeyLoadedException('No key has been loaded'); + } + + if ($this->isPublic) { + throw new UnsupportedOperationException('phpseclib does not allow the use of public keys to sign data'); } switch ($padding) { @@ -1801,7 +1847,11 @@ class RSA extends AsymmetricKey public function verify($message, $signature, $padding = self::PADDING_PSS) { if (empty($this->modulus) || empty($this->exponent)) { - return false; + throw new NoKeyLoadedException('No key has been loaded'); + } + + if (!$this->isPublic) { + throw new UnsupportedOperationException('phpseclib does not allow the use of private keys to verify data'); } switch ($padding) { diff --git a/phpseclib/Exception/NoKeyLoadedException.php b/phpseclib/Exception/NoKeyLoadedException.php new file mode 100644 index 00000000..e7fab5ce --- /dev/null +++ b/phpseclib/Exception/NoKeyLoadedException.php @@ -0,0 +1,26 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Exception; + +/** + * NoKeyLoadedException + * + * @package NoKeyLoadedException + * @author Jim Wigginton + */ +class NoKeyLoadedException extends \RuntimeException +{ +} diff --git a/phpseclib/Exception/UnsupportedOperationException.php b/phpseclib/Exception/UnsupportedOperationException.php new file mode 100644 index 00000000..9ca819fc --- /dev/null +++ b/phpseclib/Exception/UnsupportedOperationException.php @@ -0,0 +1,26 @@ + + * @copyright 2015 Jim Wigginton + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://phpseclib.sourceforge.net + */ + +namespace phpseclib\Exception; + +/** + * UnsupportedOperationException + * + * @package UnsupportedOperationException + * @author Jim Wigginton + */ +class UnsupportedOperationException extends \RuntimeException +{ +} diff --git a/phpseclib/File/X509.php b/phpseclib/File/X509.php index 0a263965..9ba87ae2 100644 --- a/phpseclib/File/X509.php +++ b/phpseclib/File/X509.php @@ -518,7 +518,7 @@ class X509 switch ($algorithm) { case 'rsaEncryption': $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] - = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))); + = "\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])); /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier." -- https://tools.ietf.org/html/rfc3279#section-2.3.1 @@ -1235,6 +1235,8 @@ class X509 case 'rsaEncryption': $rsa = new RSA(); $rsa->load($publicKey); +//zzzzz + $rsa->setPublicKey(); switch ($signatureAlgorithm) { case 'md2WithRSAEncryption': @@ -2036,7 +2038,7 @@ class X509 switch ($algorithm) { case 'rsaEncryption': $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] - = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); + = "\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])); $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null; $csr['signatureAlgorithm']['parameters'] = null; $csr['certificationRequestInfo']['signature']['parameters'] = null; @@ -2122,7 +2124,6 @@ class X509 case 'rsaEncryption': $this->publicKey = new RSA(); $this->publicKey->load($key); - $this->publicKey->setPublicKey(); break; default: $this->publicKey = null; @@ -2157,7 +2158,7 @@ class X509 switch ($algorithm) { case 'rsaEncryption': $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] - = Base64::encode("\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))); + = "\0" . Base64::decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])); } } diff --git a/tests/Unit/Crypt/RSA/ModeTest.php b/tests/Unit/Crypt/RSA/ModeTest.php index df015e51..43753bc4 100644 --- a/tests/Unit/Crypt/RSA/ModeTest.php +++ b/tests/Unit/Crypt/RSA/ModeTest.php @@ -132,4 +132,82 @@ U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ $rsa->load($rsa->getPublicKey()); $this->assertTrue($rsa->verify($plaintext, $sig)); } + + /** + * @expectedException \phpseclib\Exception\UnsupportedOperationException + */ + public function testPrivateEncrypt() + { + $rsa = new RSA(); + $privatekey = '-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp +wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 +1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh +3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2 +pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX +GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il +AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF +L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k +X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl +U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ +37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= +-----END RSA PRIVATE KEY-----'; + $rsa->load($privatekey); + + $rsa->encrypt('hello, world!'); + } + + /** + * @expectedException \phpseclib\Exception\UnsupportedOperationException + */ + public function testPublicSign() + { + $rsa = new RSA(); + $rsa->load('-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAMuqkz8ij+ESAaNvgocVGmapjlrIldmhRo4h2NX4e6IXiCLTSxASQtY4 +iqRnmyxqQSfaan2okTfQ6sP95bl8Qz8lgneW3ClC6RXG/wpJgsx7TXQ2kodlcKBF +m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE= +-----END RSA PUBLIC KEY-----'); + + $rsa->sign('hello, world!'); + } + + /** + * @expectedException \phpseclib\Exception\UnsupportedOperationException + */ + public function testPublicDecrypt() + { + $rsa = new RSA(); + $rsa->load('-----BEGIN RSA PUBLIC KEY----- +MIGJAoGBAMuqkz8ij+ESAaNvgocVGmapjlrIldmhRo4h2NX4e6IXiCLTSxASQtY4 +iqRnmyxqQSfaan2okTfQ6sP95bl8Qz8lgneW3ClC6RXG/wpJgsx7TXQ2kodlcKBF +m4k72G75QXhZ+I40ZG7cjBf1/9egakR0a0X0MpeOrKCzMBLv9+mpAgMBAAE= +-----END RSA PUBLIC KEY-----'); + + $rsa->decrypt('zzz'); + } + + /** + * @expectedException \phpseclib\Exception\UnsupportedOperationException + */ + public function testPrivateVerify() + { + $rsa = new RSA(); + $privatekey = '-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp +wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 +1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh +3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2 +pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX +GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il +AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF +L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k +X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl +U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ +37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= +-----END RSA PRIVATE KEY-----'; + $rsa->load($privatekey); + + $rsa->verify('hello, world!', 'dummysignature'); + } } diff --git a/tests/Unit/File/X509/SPKACTest.php b/tests/Unit/File/X509/SPKACTest.php index 41ff2a0d..4f01181a 100644 --- a/tests/Unit/File/X509/SPKACTest.php +++ b/tests/Unit/File/X509/SPKACTest.php @@ -46,8 +46,7 @@ class Unit_File_X509_SPKACTest extends PhpseclibTestCase public function testSaveSPKAC() { - $privKey = new RSA(); - extract($privKey->createKey()); + extract(RSA::createKey()); $x509 = new X509(); $x509->setPrivateKey($privatekey); @@ -59,7 +58,7 @@ class Unit_File_X509_SPKACTest extends PhpseclibTestCase $this->assertInternalType('string', $x509->saveSPKAC($spkac)); $x509 = new X509(); - $x509->setPrivateKey($privKey); + $x509->setPrivateKey($privatekey); $spkac = $x509->signSPKAC(); $this->assertInternalType('array', $spkac);