/** * Install constructor. * * @param \Twig_Environment $twig * @param array $data */ public function __construct(\Twig_Environment $twig, array $data = []) { if (!Halite::isLibsodiumSetupCorrectly()) { echo \file_get_contents(\dirname(__DIR__) . '/error_pages/old-libsodium.html'); exit(255); } $this->twig = $twig; $this->data = $data; $this->data['airship_version'] = \AIRSHIP_VERSION; $this->csrf = new CSRF(); // We do this to prevent someone from coming along and reading your // half-finished configuration settings (e.g. database passwords): if (empty($this->data['step'])) { $this->data['step'] = 1; } if (empty($this->data['token'])) { $this->data['token'] = Base64::encode(\random_bytes(33)); \setcookie('installer', $this->data['token'], \time() + 8640000, '/'); \Airship\redirect('/'); } elseif (empty($_COOKIE['installer'])) { echo 'No installer authorization token found.', "\n"; exit(255); } elseif (!\hash_equals($this->data['token'], $_COOKIE['installer'])) { // This effectively locks unauthorized users out of the system while installing echo 'Invalid installer authorization token.', "\n"; exit(255); } $dirs = ['comments', 'csp_hash', 'csp_static', 'hash', 'markdown', 'static', 'twig']; foreach ($dirs as $d) { if (!\is_dir(\dirname(__DIR__) . '/tmp/cache/' . $d)) { \mkdir(\dirname(__DIR__) . '/tmp/cache/' . $d, 0775, true); } } }
public function testFailure() { try { KeyPair::generateKeyPair(1024); $this->fail('Accepts too small of a key size!'); return; } catch (\Exception $ex) { $keyPair = KeyPair::generateKeyPair(2048); } $secretKey = $keyPair->getPrivateKey(); $publicKey = $keyPair->getPublicKey(); $plain = 'Short Message'; $encrypt = EasyRSA::encrypt($plain, $publicKey); $dissect = explode('$', $encrypt); // Flip a bit in the key, randomly! $dissect[1] = base64_decode($dissect[1]); $l = mt_rand(0, strlen($dissect[1]) - 1); $dissect[1][$l] = \chr(\ord($dissect[1][$l]) ^ 1 << mt_rand(0, 7)); $dissect[1] = base64_encode($dissect[1]); try { EasyRSA::decrypt(implode('$', $dissect), $secretKey); $this->fail('Checksum collision or logic error.'); return; } catch (\Exception $ex) { $this->assertInstanceOf('\\ParagonIE\\EasyRSA\\Exception\\InvalidChecksumException', $ex); } $dissect[3] = substr(hash('sha256', implode('$', array_slice($dissect, 0, 3))), 0, 16); try { EasyRSA::decrypt(implode('$', $dissect), $secretKey); $this->fail('This should not have passed.'); } catch (\Exception $ex) { $this->assertInstanceOf('\\ParagonIE\\EasyRSA\\Exception\\InvalidCiphertextException', $ex); } /////////////////////////////////////////////////////////////////////// $dissect = explode('$', $encrypt); // Flip a bit in the message, randomly! $dissect[2] = base64_decode($dissect[2]); $l = mt_rand(0, strlen($dissect[2]) - 1); $dissect[2][$l] = \chr(\ord($dissect[2][$l]) ^ 1 << mt_rand(0, 7)); $dissect[2] = Base64::encode($dissect[2]); try { $dummy = EasyRSA::decrypt(implode('$', $dissect), $secretKey); $this->fail('Checksum collision or logic error.'); unset($dummy); return; } catch (\Exception $ex) { $this->assertInstanceOf('\\ParagonIE\\EasyRSA\\Exception\\InvalidChecksumException', $ex); } $dissect[3] = substr(hash('sha256', implode('$', array_slice($dissect, 0, 3))), 0, 16); try { EasyRSA::decrypt(implode('$', $dissect), $secretKey); $this->fail('This should not have passed.'); } catch (\Exception $ex) { $this->assertInstanceOf('\\Defuse\\Crypto\\Exception\\WrongKeyOrModifiedCiphertextException', $ex); } }
public function testVectorBase64() { $this->assertSame(Base64::encode(''), ''); $this->assertSame(Base64::encode('f'), 'Zg=='); $this->assertSame(Base64::encode('fo'), 'Zm8='); $this->assertSame(Base64::encode('foo'), 'Zm9v'); $this->assertSame(Base64::encode('foob'), 'Zm9vYg=='); $this->assertSame(Base64::encode('fooba'), 'Zm9vYmE='); $this->assertSame(Base64::encode('foobar'), 'Zm9vYmFy'); }
/** * 1. VerifyHMAC-then-Decrypt the ciphertext to get the hash * 2. Verify that the password matches the hash * * @param string $password * @param string $ciphertext * @param Key $aesKey * @return bool * @throws \Exception * @throws \InvalidArgumentException */ public static function decryptAndVerify(string $password, string $ciphertext, Key $aesKey) : bool { if (!\is_string($password)) { throw new \InvalidArgumentException('Password must be a string.'); } if (!\is_string($ciphertext)) { throw new \InvalidArgumentException('Ciphertext must be a string.'); } $hash = Crypto::decrypt($ciphertext, $aesKey); return \password_verify(Base64::encode(\hash('sha384', $password, true)), $hash); }
/** * Encrypt a message with defuse/php-encryption, using an ephemeral key, * then encrypt the key with RSA. * * @param string $plaintext * @param PublicKey $rsaPublicKey * * @return string */ public static function encrypt($plaintext, PublicKey $rsaPublicKey) { // Random encryption key $ephemeral = Key::createNewRandomKey(); // Encrypt the actual message $symmetric = Base64::encode(Crypto::encrypt($plaintext, $ephemeral, true)); // Use RSA to encrypt the encryption key $storeKey = \base64_encode(self::rsaEncrypt($ephemeral->saveToAsciiSafeString(), $rsaPublicKey)); $packaged = \implode(self::SEPARATOR, array(self::VERSION_TAG, $storeKey, $symmetric)); $checksum = \substr(\hash('sha256', $packaged), 0, 16); // Return the ciphertext return $packaged . self::SEPARATOR . $checksum; }
/** * @covers Base64::encode() * @covers Base64::decode() */ public function testRandom() { for ($i = 1; $i < 32; ++$i) { for ($j = 0; $j < 50; ++$j) { $random = \random_bytes($i); $enc = Base64::encode($random); $this->assertSame($random, Base64::decode($enc)); $this->assertSame(\base64_encode($random), $enc); $unpadded = \rtrim($enc, '='); $this->assertSame($random, Base64::decode($unpadded)); } } $str = 'MIIFzzCCBLegAwIBAgIDAfdlMA0GCSqGSIb3DQEBBQUAMHMxCzAJBgNVBAYTAlBM' . 'MSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMSQwIgYDVQQ' . 'DDBtDT1BFIFNaQUZJUiAtIEt3YWxpZmlrb3dhbnkxFDASBgNVBAUTC05yIHdwaXN1Oi' . 'A2MB4XDTExMTEwOTA2MDAwMFoXDTEzMTEwOTA2MDAwMFowgdkxCzAJBgNVBAYTAlBMM' . 'RwwGgYDVQQKDBNVcnrEhWQgTWlhc3RhIEdkeW5pMRswGQYDVQQFExJQRVNFTDogNjEw' . 'NjA2MDMxMTgxGTAXBgNVBAMMEEplcnp5IFByemV3b3Jza2kxTzBNBgNVBBAwRgwiQWw' . 'uIE1hcnN6YcWCa2EgUGnFgnN1ZHNraWVnbyA1Mi81NAwNODEtMzgyIEdkeW5pYQwGUG' . '9sc2thDAlwb21vcnNraWUxDjAMBgNVBCoMBUplcnp5MRMwEQYDVQQEDApQcnpld29yc' . '2tpMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMm5vjGqHPthJCMqKpqssSISRo' . 's0PYDTcEQzyyurfX67EJWKtZj6HNwuDMEGJ02iBNZfjUl7r8dIi28bSKhNlsfycXZKY' . 'RcIjp0+r5RqtR2auo9GQ6veKb61DEAGIqaR+uLLcJVTHCu0w9oXLGbRlGth5eNoj03C' . 'xXVAH2IfhbNwIDAQABo4IChzCCAoMwDAYDVR0TAQH/BAIwADCCAUgGA1UdIAEB/wSCA' . 'TwwggE4MIIBNAYJKoRoAYb3IwEBMIIBJTCB3QYIKwYBBQUHAgIwgdAMgc1EZWtsYXJh' . 'Y2phIHRhIGplc3Qgb8Wbd2lhZGN6ZW5pZW0gd3lkYXdjeSwgxbxlIHRlbiBjZXJ0eWZ' . 'pa2F0IHpvc3RhxYIgd3lkYW55IGpha28gY2VydHlmaWthdCBrd2FsaWZpa293YW55IH' . 'pnb2RuaWUgeiB3eW1hZ2FuaWFtaSB1c3Rhd3kgbyBwb2RwaXNpZSBlbGVrdHJvbmlje' . 'm55bSBvcmF6IHRvd2FyenlzesSFY3ltaSBqZWogcm96cG9yesSFZHplbmlhbWkuMEMG' . 'CCsGAQUFBwIBFjdodHRwOi8vd3d3Lmtpci5jb20ucGwvY2VydHlmaWthY2phX2tsdWN' . '6eS9wb2xpdHlrYS5odG1sMAkGA1UdCQQCMAAwIQYDVR0RBBowGIEWai5wcnpld29yc2' . 'tpQGdkeW5pYS5wbDAOBgNVHQ8BAf8EBAMCBkAwgZ4GA1UdIwSBljCBk4AU3TGldJXip' . 'N4oGS3ZYmnBDMFs8gKhd6R1MHMxCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dh' . 'IEl6YmEgUm96bGljemVuaW93YSBTLkEuMSQwIgYDVQQDDBtDT1BFIFNaQUZJUiAtIEt' . '3YWxpZmlrb3dhbnkxFDASBgNVBAUTC05yIHdwaXN1OiA2ggJb9jBIBgNVHR8EQTA/MD' . '2gO6A5hjdodHRwOi8vd3d3Lmtpci5jb20ucGwvY2VydHlmaWthY2phX2tsdWN6eS9DU' . 'kxfT1pLMzIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQBYPIqnAreyeql7/opJjcar/qWZ' . 'y9ruhB2q0lZFsJOhwgMnbQXzp/4vv93YJqcHGAXdHP6EO8FQX47mjo2ZKQmi+cIHJHL' . 'ONdX/3Im+M17V0iNAh7Z1lOSfTRT+iiwe/F8phcEaD5q2RmvYusR7zXZq/cLL0If0hX' . 'oPZ/EHQxjN8pxzxiUx6bJAgturnIMEfRNesxwghdr1dkUjOhGLf3kHVzgM6j3VAM7oF' . 'mMUb5y5s96Bzl10DodWitjOEH0vvnIcsppSxH1C1dCAi0o9f/1y2XuLNhBNHMAyTqpY' . 'PX8Yvav1c+Z50OMaSXHAnTa20zv8UtiHbaAhwlifCelUMj93S'; try { Base64::decode($str, true); $this->fail('Strict padding not enforced'); } catch (\Exception $ex) { $this->assertSame(Base64::decode($str), \base64_decode($str)); } }
/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { $n = strrev($n->toBytes()); $e = str_pad(strrev($e->toBytes()), 4, ""); $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX); $key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e); $key .= $n; return Base64::encode($key); }
/** * Wrap a public key appropriately * * @access public * @param string $key * @return string */ static function wrapPublicKey($key, $algorithm) { $asn1 = new ASN1(); $key = ['publicKeyAlgorithm' => ['algorithm' => $algorithm, 'parameters' => null], 'publicKey' => Base64::encode("" . $key)]; $key = $asn1->encodeDER($key, PublicKeyInfo); return "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split(Base64::encode($key), 64) . "-----END PUBLIC KEY-----"; }
/** * ASN.1 Map * * Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format. * * "Special" mappings may be applied on a per tag-name basis via $special. * * @param array $decoded * @param array $mapping * @param array $special * @return array * @access public */ function asn1map($decoded, $mapping, $special = array()) { if (isset($mapping['explicit']) && is_array($decoded['content'])) { $decoded = $decoded['content'][0]; } switch (true) { case $mapping['type'] == self::TYPE_ANY: $intype = $decoded['type']; if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || ord($this->encoded[$decoded['start']]) & 0x20) { return new Element(substr($this->encoded, $decoded['start'], $decoded['length'])); } $inmap = $this->ANYmap[$intype]; if (is_string($inmap)) { return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special)); } break; case $mapping['type'] == self::TYPE_CHOICE: foreach ($mapping['children'] as $key => $option) { switch (true) { case isset($option['constant']) && $option['constant'] == $decoded['constant']: case !isset($option['constant']) && $option['type'] == $decoded['type']: $value = $this->asn1map($decoded, $option, $special); break; case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE: $v = $this->asn1map($decoded, $option, $special); if (isset($v)) { $value = $v; } } if (isset($value)) { if (isset($special[$key])) { $value = call_user_func($special[$key], $value); } return array($key => $value); } } return null; case isset($mapping['implicit']): case isset($mapping['explicit']): case $decoded['type'] == $mapping['type']: break; default: // if $decoded['type'] and $mapping['type'] are both strings, but different types of strings, // let it through switch (true) { case $decoded['type'] < 18: // self::TYPE_NUMERIC_STRING == 18 // self::TYPE_NUMERIC_STRING == 18 case $decoded['type'] > 30: // self::TYPE_BMP_STRING == 30 // self::TYPE_BMP_STRING == 30 case $mapping['type'] < 18: case $mapping['type'] > 30: return null; } } if (isset($mapping['implicit'])) { $decoded['type'] = $mapping['type']; } switch ($decoded['type']) { case self::TYPE_SEQUENCE: $map = array(); // ignore the min and max if (isset($mapping['min']) && isset($mapping['max'])) { $child = $mapping['children']; foreach ($decoded['content'] as $content) { if (($map[] = $this->asn1map($content, $child, $special)) === null) { return null; } } return $map; } $n = count($decoded['content']); $i = 0; foreach ($mapping['children'] as $key => $child) { $maymatch = $i < $n; // Match only existing input. if ($maymatch) { $temp = $decoded['content'][$i]; if ($child['type'] != self::TYPE_CHOICE) { // Get the mapping and input class & constant. $childClass = $tempClass = self::CLASS_UNIVERSAL; $constant = null; if (isset($temp['constant'])) { $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC; } if (isset($child['class'])) { $childClass = $child['class']; $constant = $child['cast']; } elseif (isset($child['constant'])) { $childClass = self::CLASS_CONTEXT_SPECIFIC; $constant = $child['constant']; } if (isset($constant) && isset($temp['constant'])) { // Can only match if constants and class match. $maymatch = $constant == $temp['constant'] && $childClass == $tempClass; } else { // Can only match if no constant expected and type matches or is generic. $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false; } } } if ($maymatch) { // Attempt submapping. $candidate = $this->asn1map($temp, $child, $special); $maymatch = $candidate !== null; } if ($maymatch) { // Got the match: use it. if (isset($special[$key])) { $candidate = call_user_func($special[$key], $candidate); } $map[$key] = $candidate; $i++; } elseif (isset($child['default'])) { $map[$key] = $child['default']; // Use default. } elseif (!isset($child['optional'])) { return null; // Syntax error. } } // Fail mapping if all input items have not been consumed. return $i < $n ? null : $map; // the main diff between sets and sequences is the encapsulation of the foreach in another for loop // the main diff between sets and sequences is the encapsulation of the foreach in another for loop case self::TYPE_SET: $map = array(); // ignore the min and max if (isset($mapping['min']) && isset($mapping['max'])) { $child = $mapping['children']; foreach ($decoded['content'] as $content) { if (($map[] = $this->asn1map($content, $child, $special)) === null) { return null; } } return $map; } for ($i = 0; $i < count($decoded['content']); $i++) { $temp = $decoded['content'][$i]; $tempClass = self::CLASS_UNIVERSAL; if (isset($temp['constant'])) { $tempClass = isset($temp['class']) ? $temp['class'] : self::CLASS_CONTEXT_SPECIFIC; } foreach ($mapping['children'] as $key => $child) { if (isset($map[$key])) { continue; } $maymatch = true; if ($child['type'] != self::TYPE_CHOICE) { $childClass = self::CLASS_UNIVERSAL; $constant = null; if (isset($child['class'])) { $childClass = $child['class']; $constant = $child['cast']; } elseif (isset($child['constant'])) { $childClass = self::CLASS_CONTEXT_SPECIFIC; $constant = $child['constant']; } if (isset($constant) && isset($temp['constant'])) { // Can only match if constants and class match. $maymatch = $constant == $temp['constant'] && $childClass == $tempClass; } else { // Can only match if no constant expected and type matches or is generic. $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false; } } if ($maymatch) { // Attempt submapping. $candidate = $this->asn1map($temp, $child, $special); $maymatch = $candidate !== null; } if (!$maymatch) { break; } // Got the match: use it. if (isset($special[$key])) { $candidate = call_user_func($special[$key], $candidate); } $map[$key] = $candidate; break; } } foreach ($mapping['children'] as $key => $child) { if (!isset($map[$key])) { if (isset($child['default'])) { $map[$key] = $child['default']; } elseif (!isset($child['optional'])) { return null; } } } return $map; case self::TYPE_OBJECT_IDENTIFIER: return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content']; case self::TYPE_UTC_TIME: case self::TYPE_GENERALIZED_TIME: if (isset($mapping['implicit'])) { $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']); } return @date($this->format, $decoded['content']); case self::TYPE_BIT_STRING: if (isset($mapping['mapping'])) { $offset = ord($decoded['content'][0]); $size = (strlen($decoded['content']) - 1) * 8 - $offset; /* From X.680-0207.pdf#page=46 (21.7): "When a "NamedBitList" is used in defining a bitstring type ASN.1 encoding rules are free to add (or remove) arbitrarily any trailing 0 bits to (or from) values that are being encoded or decoded. Application designers should therefore ensure that different semantics are not associated with such values which differ only in the number of trailing 0 bits." */ $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false); for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) { $current = ord($decoded['content'][$i]); for ($j = $offset; $j < 8; $j++) { $bits[] = (bool) ($current & 1 << $j); } $offset = 0; } $values = array(); $map = array_reverse($mapping['mapping']); foreach ($map as $i => $value) { if ($bits[$i]) { $values[] = $value; } } return $values; } case self::TYPE_OCTET_STRING: return Base64::encode($decoded['content']); case self::TYPE_NULL: return ''; case self::TYPE_BOOLEAN: return $decoded['content']; case self::TYPE_NUMERIC_STRING: case self::TYPE_PRINTABLE_STRING: case self::TYPE_TELETEX_STRING: case self::TYPE_VIDEOTEX_STRING: case self::TYPE_IA5_STRING: case self::TYPE_GRAPHIC_STRING: case self::TYPE_VISIBLE_STRING: case self::TYPE_GENERAL_STRING: case self::TYPE_UNIVERSAL_STRING: case self::TYPE_UTF8_STRING: case self::TYPE_BMP_STRING: return $decoded['content']; case self::TYPE_INTEGER: case self::TYPE_ENUMERATED: $temp = $decoded['content']; if (isset($mapping['implicit'])) { $temp = new BigInteger($decoded['content'], -256); } if (isset($mapping['mapping'])) { $temp = (int) $temp->toString(); return isset($mapping['mapping'][$temp]) ? $mapping['mapping'][$temp] : false; } return $temp; } }
/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { return "<RSAKeyValue>\r\n" . ' <Modulus>' . Base64::encode($n->toBytes()) . "</Modulus>\r\n" . ' <Exponent>' . Base64::encode($e->toBytes()) . "</Exponent>\r\n" . '</RSAKeyValue>'; }
/** * Add a file or directory to the * * @param string $filename * @param string $dir * @return int */ protected function addautoRun(string $filename, string $dir) : int { if (!empty($dir)) { if ($dir[\strlen($dir) - 1] !== DIRECTORY_SEPARATOR) { $dir .= DIRECTORY_SEPARATOR; } } if (!\file_exists($filename)) { echo $this->c['red'], 'File not found: ', $this->c[''], $filename, "\n"; return 0; } try { $path = $this->getRealPath(\realpath($filename)); } catch (\Error $e) { echo $this->c['red'], $e->getMessage(), $this->c[''], "\n"; return 0; } if (\array_key_exists($path, $this->session['autoRun'])) { echo $this->c['yellow'], 'autoRun script already registered: ', $this->c[''], $path, "\n"; return 0; } // Recursive adding if (\is_dir($path)) { echo $this->c['red'], 'You cannot add a directory to an autoRun script: ', $this->c[''], $path, "\n"; return 0; } $this->session['autoRun'][$path] = ['type' => $this->getType($path), 'data' => Base64::encode(\file_get_contents($path))]; echo $this->c['green'], 'autoRun script registered: ', $this->c[''], $path, "\n"; return 1; }
/** * Sets the subject key identifier * * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions. * * @param string $value * @access public */ function setKeyIdentifier($value) { if (empty($value)) { unset($this->currentKeyIdentifier); } else { $this->currentKeyIdentifier = Base64::encode($value); } }
/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { $n = $n->toBytes(true); $e = $e->toBytes(true); $key = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($e), $e, strlen($n), $n); $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" . 'Comment: "' . str_replace(array('\\', '"'), array('\\\\', '\\"'), self::$comment) . "\"\r\n" . chunk_split(Base64::encode($key), 64) . '---- END SSH2 PUBLIC KEY ----'; return $key; }
/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { $publicExponent = $e->toBytes(true); $modulus = $n->toBytes(true); // from <http://tools.ietf.org/html/rfc4253#page-15>: // string "ssh-rsa" // mpint e // mpint n $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); $RSAPublicKey = 'ssh-rsa ' . Base64::encode($RSAPublicKey) . ' ' . self::$comment; return $RSAPublicKey; }
/** * Add a new nonce to the existing CSP * * @param string $directive * @param string $nonce (if empty, it will be generated) * @return null|string */ public function nonce(string $directive = 'script-src', string $nonce = '') : string { $ruleKeys = \array_keys($this->policies); if (!\in_array($directive, $ruleKeys)) { return ''; } if (empty($nonce)) { $nonce = Base64::encode(\random_bytes(18)); } $this->policies[$directive]['nonces'][] = $nonce; return $nonce; }
/** * Request Identities * * See "2.5.2 Requesting a list of protocol 2 keys" * Returns an array containing zero or more \phpseclib\System\SSH\Agent\Identity objects * * @return array * @throws \RuntimeException on receipt of unexpected packets * @access public */ function requestIdentities() { if (!$this->fsock) { return array(); } $packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES); if (strlen($packet) != fputs($this->fsock, $packet)) { throw new \RuntimeException('Connection closed while requesting identities'); } $length = current(unpack('N', fread($this->fsock, 4))); $type = ord(fread($this->fsock, 1)); if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) { throw new \RuntimeException('Unable to request identities'); } $identities = array(); $keyCount = current(unpack('N', fread($this->fsock, 4))); for ($i = 0; $i < $keyCount; $i++) { $length = current(unpack('N', fread($this->fsock, 4))); $key_blob = fread($this->fsock, $length); $key_str = 'ssh-rsa ' . Base64::encode($key_blob); $length = current(unpack('N', fread($this->fsock, 4))); if ($length) { $key_str .= ' ' . fread($this->fsock, $length); } $length = current(unpack('N', substr($key_blob, 0, 4))); $key_type = substr($key_blob, 4, $length); switch ($key_type) { case 'ssh-rsa': $key = new RSA(); $key->load($key_str); break; case 'ssh-dss': // not currently supported break; } // resources are passed by reference by default if (isset($key)) { $identity = new Identity($this->fsock); $identity->setPublicKey($key); $identity->setPublicKeyBlob($key_blob); $identities[] = $identity; unset($key); } } return $identities; }
/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { $modulus = $n->toBytes(true); $publicExponent = $e->toBytes(true); // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>: // RSAPublicKey ::= SEQUENCE { // modulus INTEGER, -- n // publicExponent INTEGER -- e // } $components = array('modulus' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($modulus)), $modulus), 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($publicExponent)), $publicExponent)); $RSAPublicKey = pack('Ca*a*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent']); $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" . chunk_split(Base64::encode($RSAPublicKey), 64) . '-----END RSA PUBLIC KEY-----'; return $RSAPublicKey; }
/** * Coerce a string into base64 format. * * @param string $hash * @param string $algo * @return string * @throws \Exception */ protected function coerceBase64(string $hash, string $algo = 'sha256') : string { switch ($algo) { case 'sha256': $limits = ['raw' => 32, 'hex' => 64, 'pad_min' => 40, 'pad_max' => 44]; break; default: throw new \Exception('Browsers currently only support sha256 public key pins.'); } $len = Binary::safeStrlen($hash); if ($len === $limits['hex']) { $hash = Base64::encode(Hex::decode($hash)); } elseif ($len === $limits['raw']) { $hash = Base64::encode($hash); } elseif ($len > $limits['pad_min'] && $len < $limits['pad_max']) { // Padding was stripped! $hash .= \str_repeat('=', $len % 4); // Base64UrlSsafe encoded. if (\strpos($hash, '_') !== false || \strpos($hash, '-') !== false) { $hash = Base64UrlSafe::decode($hash); } else { $hash = Base64::decode($hash); } $hash = Base64::encode($hash); } return $hash; }
/** * Convert a public key to the appropriate format * * @access public * @param \phpseclib\Math\BigInteger $n * @param \phpseclib\Math\BigInteger $e * @return string */ static function savePublicKey(BigInteger $n, BigInteger $e) { $modulus = $n->toBytes(true); $publicExponent = $e->toBytes(true); // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>: // RSAPublicKey ::= SEQUENCE { // modulus INTEGER, -- n // publicExponent INTEGER -- e // } $components = array('modulus' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($modulus)), $modulus), 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($publicExponent)), $publicExponent)); $RSAPublicKey = pack('Ca*a*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent']); // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption. $rsaOID = "0\r\t*†H†÷\r"; // hex version of MA0GCSqGSIb3DQEBAQUA $RSAPublicKey = chr(0) . $RSAPublicKey; $RSAPublicKey = chr(3) . ASN1::encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey; $RSAPublicKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey); $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split(Base64::encode($RSAPublicKey), 64) . '-----END PUBLIC KEY-----'; return $RSAPublicKey; }
/** * Returns the public key's fingerprint * * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is * no public key currently loaded, false is returned. * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716) * * @access public * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned * for invalid values. * @return mixed */ public function getPublicKeyFingerprint($algorithm = 'md5') { if (empty($this->modulus) || empty($this->publicExponent)) { return false; } $modulus = $this->modulus->toBytes(true); $publicExponent = $this->publicExponent->toBytes(true); $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus); switch ($algorithm) { case 'sha256': $hash = new Hash('sha256'); $base = Base64::encode($hash->hash($RSAPublicKey)); return substr($base, 0, strlen($base) - 1); case 'md5': return substr(chunk_split(md5($RSAPublicKey), 2, ':'), 0, -1); default: return false; } }
/** * Wrap a public key appropriately * * @access public * @param string $key * @param string $type * @return string */ static function wrapPublicKey($key, $type) { return "-----BEGIN {$type} PUBLIC KEY-----\r\n" . chunk_split(Base64::encode($key), 64) . "-----END {$type} PUBLIC KEY-----"; }
/** * Returns the server public host key. * * Caching this the first time you connect to a server and checking the result on subsequent connections * is recommended. Returns false if the server signature is not signed correctly with the public host key. * * @return mixed * @throws \RuntimeException on badly formatted keys * @throws \phpseclib\Exception\NoSupportedAlgorithmsException when the key isn't in a supported format * @access public */ function getServerPublicHostKey() { if (!($this->bitmap & self::MASK_CONSTRUCTOR)) { if (!$this->_connect()) { return false; } } $signature = $this->signature; $server_public_host_key = $this->server_public_host_key; if (strlen($server_public_host_key) < 4) { return false; } extract(unpack('Nlength', Strings::shift($server_public_host_key, 4))); Strings::shift($server_public_host_key, $length); if ($this->signature_validated) { return $this->bitmap ? $this->signature_format . ' ' . Base64::encode($this->server_public_host_key) : false; } $this->signature_validated = true; switch ($this->signature_format) { case 'ssh-dss': $zero = new BigInteger(); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $p = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $q = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $g = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $y = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); /* The value for 'dss_signature_blob' is encoded as a string containing r, followed by s (which are 160-bit integers, without lengths or padding, unsigned, and in network byte order). */ $temp = unpack('Nlength', Strings::shift($signature, 4)); if ($temp['length'] != 40) { $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } $r = new BigInteger(Strings::shift($signature, 20), 256); $s = new BigInteger(Strings::shift($signature, 20), 256); switch (true) { case $r->equals($zero): case $r->compare($q) >= 0: case $s->equals($zero): case $s->compare($q) >= 0: $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } $w = $s->modInverse($q); $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16)); list(, $u1) = $u1->divide($q); $u2 = $w->multiply($r); list(, $u2) = $u2->divide($q); $g = $g->modPow($u1, $p); $y = $y->modPow($u2, $p); $v = $g->multiply($y); list(, $v) = $v->divide($p); list(, $v) = $v->divide($q); if (!$v->equals($r)) { //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; case 'ssh-rsa': if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $e = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256); if (strlen($server_public_host_key) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($server_public_host_key, 4)); $rawN = Strings::shift($server_public_host_key, $temp['length']); $n = new BigInteger($rawN, -256); $nLength = strlen(ltrim($rawN, "")); /* if (strlen($signature) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($signature, 4)); $signature = Strings::shift($signature, $temp['length']); $rsa = new RSA(); $rsa->load(array('e' => $e, 'n' => $n), 'raw'); $rsa->setHash('sha1'); if (!$rsa->verify($this->exchange_hash, $signature, RSA::PADDING_PKCS1)) { //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } */ if (strlen($signature) < 4) { return false; } $temp = unpack('Nlength', Strings::shift($signature, 4)); $s = new BigInteger(Strings::shift($signature, $temp['length']), 256); // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the // following URL: // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source. if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) { $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED); throw new \RuntimeException('Invalid signature'); } $s = $s->modPow($e, $n); $s = $s->toBytes(); $h = pack('N4H*', 0x302130, 0x906052b, 0xe03021a, 0x5000414, sha1($this->exchange_hash)); $h = chr(0x1) . str_repeat(chr(0xff), $nLength - 2 - strlen($h)) . $h; if ($s != $h) { //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); } break; default: $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); throw new NoSupportedAlgorithmsException('Unsupported signature format'); } return $this->signature_format . ' ' . Base64::encode($this->server_public_host_key); }
/** * Performs modular exponentiation. * * Here's an example: * <code> * <?php * $a = new \phpseclib\Math\BigInteger('10'); * $b = new \phpseclib\Math\BigInteger('20'); * $c = new \phpseclib\Math\BigInteger('30'); * * $c = $a->modPow($b, $c); * * echo $c->toString(); // outputs 10 * ?> * </code> * * @param \phpseclib\Math\BigInteger $e * @param \phpseclib\Math\BigInteger $n * @return \phpseclib\Math\BigInteger * @access public * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and * and although the approach involving repeated squaring does vastly better, it, too, is impractical * for our purposes. The reason being that division - by far the most complicated and time-consuming * of the basic operations (eg. +,-,*,/) - occurs multiple times within it. * * Modular reductions resolve this issue. Although an individual modular reduction takes more time * then an individual division, when performed in succession (with the same modulo), they're a lot faster. * * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction, * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because * the product of two odd numbers is odd), but what about when RSA isn't used? * * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however, * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and * the other, a power of two - and recombine them, later. This is the method that this modPow function uses. * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates. */ function modPow(BigInteger $e, BigInteger $n) { $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs(); if ($e->compare(new static()) < 0) { $e = $e->abs(); $temp = $this->modInverse($n); if ($temp === false) { return false; } return $this->_normalize($temp->modPow($e, $n)); } if (MATH_BIGINTEGER_MODE == self::MODE_GMP) { $temp = new static(); $temp->value = gmp_powm($this->value, $e->value, $n->value); return $this->_normalize($temp); } if ($this->compare(new static()) < 0 || $this->compare($n) > 0) { list(, $temp) = $this->divide($n); return $temp->modPow($e, $n); } if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) { $components = array('modulus' => $n->toBytes(true), 'publicExponent' => $e->toBytes(true)); $components = array('modulus' => pack('Ca*a*', 2, self::_encodeASN1Length(strlen($components['modulus'])), $components['modulus']), 'publicExponent' => pack('Ca*a*', 2, self::_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent'])); $RSAPublicKey = pack('Ca*a*a*', 48, self::_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent']); $rsaOID = "0\r\t*†H†÷\r"; // hex version of MA0GCSqGSIb3DQEBAQUA $RSAPublicKey = chr(0) . $RSAPublicKey; $RSAPublicKey = chr(3) . self::_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey; $encapsulated = pack('Ca*a*', 48, self::_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey); $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" . chunk_split(Base64::encode($encapsulated)) . '-----END PUBLIC KEY-----'; $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "", STR_PAD_LEFT); if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) { return new static($result, 256); } } if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) { $temp = new static(); $temp->value = bcpowmod($this->value, $e->value, $n->value, 0); return $this->_normalize($temp); } if (empty($e->value)) { $temp = new static(); $temp->value = array(1); return $this->_normalize($temp); } if ($e->value == array(1)) { list(, $temp) = $this->divide($n); return $this->_normalize($temp); } if ($e->value == array(2)) { $temp = new static(); $temp->value = self::_square($this->value); list(, $temp) = $temp->divide($n); return $this->_normalize($temp); } return $this->_normalize($this->_slidingWindow($e, $n, self::BARRETT)); // the following code, although not callable, can be run independently of the above code // although the above code performed better in my benchmarks the following could might // perform better under different circumstances. in lieu of deleting it it's just been // made uncallable // is the modulo odd? if ($n->value[0] & 1) { return $this->_normalize($this->_slidingWindow($e, $n, self::MONTGOMERY)); } // if it's not, it's even // find the lowest set bit (eg. the max pow of 2 that divides $n) for ($i = 0; $i < count($n->value); ++$i) { if ($n->value[$i]) { $temp = decbin($n->value[$i]); $j = strlen($temp) - strrpos($temp, '1') - 1; $j += 26 * $i; break; } } // at this point, 2^$j * $n/(2^$j) == $n $mod1 = clone $n; $mod1->_rshift($j); $mod2 = new static(); $mod2->value = array(1); $mod2->_lshift($j); $part1 = $mod1->value != array(1) ? $this->_slidingWindow($e, $mod1, self::MONTGOMERY) : new static(); $part2 = $this->_slidingWindow($e, $mod2, self::POWEROF2); $y1 = $mod2->modInverse($mod1); $y2 = $mod1->modInverse($mod2); $result = $part1->multiply($mod2); $result = $result->multiply($y1); $temp = $part2->multiply($mod1); $temp = $temp->multiply($y2); $result = $result->add($temp); list(, $result) = $result->divide($n); return $this->_normalize($result); }