protected function createResponse(\fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context, $hostAlgo) { $kexAlgo = $context['kexAlgo']; $kexAlgo = new $kexAlgo(); $message = \fpoirotte\Pssht\Messages\KEX\ECDH\INIT\Curve25519::unserialize($decoder); return new \fpoirotte\Pssht\Messages\KEX\ECDH\REPLY\Curve25519($message, $context['serverKeys'][$hostAlgo], $transport->getEncryptor(), $kexAlgo, $context['kex']['server'], $context['kex']['client'], $context['identity']['server'], $context['identity']['client']); }
public function authenticate(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base $message, \fpoirotte\Pssht\Transport $transport, array &$context) { if (!$message instanceof \fpoirotte\Pssht\Messages\USERAUTH\REQUEST\PublicKey) { throw new \InvalidArgumentException(); } if ($message->getSignature() === null) { return self::AUTH_REJECT; } $logging = \Plop\Plop::getInstance(); $reverse = gethostbyaddr($transport->getAddress()); $algos = \fpoirotte\Pssht\Algorithms::factory(); $cls = $algos->getClass('PublicKey', $message->getAlgorithm()); if ($cls === null || !$this->store->exists($message->getUserName(), $message->getKey())) { $logging->info('Rejected public key connection from remote host "%(reverse)s" ' . 'to "%(luser)s" (unsupported key)', array('luser' => escape($message->getUserName()), 'reverse' => $reverse)); return self::AUTH_REJECT; } $key = $cls::loadPublic(base64_encode($message->getKey())); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeString($context['DH']->getExchangeHash()); $encoder->encodeBytes(chr(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base::getMessageId())); $encoder->encodeString($message->getUserName()); $encoder->encodeString($message->getServiceName()); $encoder->encodeString(static::getName()); $encoder->encodeBoolean(true); $encoder->encodeString($message->getAlgorithm()); $encoder->encodeString($message->getKey()); if ($key->check($encoder->getBuffer()->get(0), $message->getSignature())) { $logging->info('Accepted public key connection from remote host "%(reverse)s" ' . 'to "%(luser)s" (using "%(algorithm)s" algorithm)', array('luser' => escape($message->getUserName()), 'reverse' => $reverse, 'algorithm' => escape($message->getAlgorithm()))); return self::AUTH_ACCEPT; } $logging->info('Rejected public key connection from remote host "%(reverse)s" ' . 'to "%(luser)s" (invalid signature)', array('luser' => escape($message->getUserName()), 'reverse' => $reverse)); return self::AUTH_REJECT; }
/** * Construct a new SSH connection layer. * * \param fpoirotte::Pssht::Transport $transport * SSH transport layer. */ public function __construct(\fpoirotte\Pssht\Transport $transport) { $this->channels = array(); $transport->setHandler(\fpoirotte\Pssht\Messages\CHANNEL\OPEN::getMessageId(), new \fpoirotte\Pssht\Handlers\CHANNEL\OPEN($this))->setHandler(\fpoirotte\Pssht\Messages\CHANNEL\CLOSE::getMessageId(), new \fpoirotte\Pssht\Handlers\CHANNEL\CLOSE($this))->setHandler(\fpoirotte\Pssht\Messages\CHANNEL\REQUEST\Base::getMessageId(), new \fpoirotte\Pssht\Handlers\CHANNEL\REQUEST($this)); foreach (array_merge(range(91, 96), array(99, 100)) as $msgId) { $transport->setHandler($msgId, $this); } }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $message = \fpoirotte\Pssht\Messages\CHANNEL\DATA::unserialize($decoder); $channel = $message->getChannel(); $response = new \fpoirotte\Pssht\Messages\CHANNEL\DATA($channel, $message->getData()); $transport->writeMessage($response); return true; }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $message = \fpoirotte\Pssht\Messages\CHANNEL\CLOSE::unserialize($decoder); $channel = $message->getChannel(); $response = new \fpoirotte\Pssht\Messages\CHANNEL\CLOSE($this->connection->getChannel($channel)); $transport->writeMessage($response); $this->connection->freeChannel($channel); return true; }
protected function createResponse(\fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context, $hostAlgo) { $kexAlgo = $context['kexAlgo']; $kexAlgo = new $kexAlgo(); $curveName = str_replace('ecdh-sha2-', '', $kexAlgo::getName()); $cls = str_replace('nist', 'NIST', $curveName); $cls = "\\fpoirotte\\Pssht\\Messages\\KEX\\ECDH\\INIT\\{$cls}"; $message = $cls::unserialize($decoder); $curve = \fpoirotte\Pssht\ECC\Curve::getCurve($curveName); return new \fpoirotte\Pssht\Messages\KEX\ECDH\REPLY\RFC5656($curve, $message, $context['serverKeys'][$hostAlgo], $transport->getEncryptor(), $kexAlgo, $context['kex']['server'], $context['kex']['client'], $context['identity']['server'], $context['identity']['client']); }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $message = \fpoirotte\Pssht\Messages\CHANNEL\OPEN::unserialize($decoder); $recipientChannel = $message->getChannel(); if ($message->getType() === 'session') { $response = new \fpoirotte\Pssht\Messages\CHANNEL\OPEN\CONFIRMATION($recipientChannel, $this->connection->allocateChannel($message), 0x200000, 0x800000); } else { $response = new \fpoirotte\Pssht\Messages\CHANNEL\OPEN\FAILURE($recipientChannel, \fpoirotte\Pssht\Messages\CHANNEL\OPEN\FAILURE::SSH_OPEN_UNKNOWN_CHANNEL_TYPE, 'No such channel type'); } $transport->writeMessage($response); return true; }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $message = \fpoirotte\Pssht\Messages\SERVICE\REQUEST::unserialize($decoder); $service = $message->getServiceName(); if ($service === 'ssh-userauth') { $response = new \fpoirotte\Pssht\Messages\SERVICE\ACCEPT($service); $transport->setHandler(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base::getMessageId(), $this->userAuthRequestHandler); } else { $response = new DISCONNECT(DISCONNECT::SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, 'No such service'); } $transport->writeMessage($response); return true; }
public function authenticate(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base $message, \fpoirotte\Pssht\Transport $transport, array &$context) { if (!$message instanceof \fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Password) { throw new \InvalidArgumentException(); } $logging = \Plop\Plop::getInstance(); $reverse = gethostbyaddr($transport->getAddress()); if (isset($this->credentials[$message->getUserName()]) && $message->getPassword() === $this->credentials[$message->getUserName()]) { $logging->info('Accepted password connection from ' . 'remote host "%(reverse)s" to "%(luser)s"', array('reverse' => $reverse, 'luser' => escape($message->getUserName()))); return self::AUTH_ACCEPT; } $logging->info('Rejected password connection from ' . 'remote host "%(reverse)s" to "%(luser)s" ' . '(invalid credentials)', array('reverse' => $reverse, 'luser' => escape($message->getUserName()))); return self::AUTH_REJECT; }
protected function createResponse(\fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context, $hostAlgo) { $kexAlgo = $context['kexAlgo']; $kexAlgo = new $kexAlgo(); $message = \fpoirotte\Pssht\Messages\KEXDH\INIT::unserialize($decoder); /* // @TODO: we ought to check whether the given public key is valid. // // Unfortunately, the current API is broken as getQ() only exists // for ECDH. Also, even though the regular DH has a getE() method, // it returns raw GMP resources/objects which are unusable here. if (!$message->getQ()->isValid()) { throw new \InvalidArgumentException(); } */ return new \fpoirotte\Pssht\Messages\KEXDH\REPLY($message, $context['serverKeys'][$hostAlgo], $transport->getEncryptor(), $kexAlgo, $context['kex']['server'], $context['kex']['client'], $context['identity']['server'], $context['identity']['client']); }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $channel = $decoder->decodeUint32(); $type = $decoder->decodeString(); $wantsReply = $decoder->decodeBoolean(); $encoder->encodeUint32($channel); $encoder->encodeString($type); $encoder->encodeBoolean($wantsReply); $decoder->getBuffer()->unget($encoder->getBuffer()->get(0)); $remoteChannel = $this->connection->getChannel($channel); switch ($type) { case 'exec': case 'shell': case 'pty-req': // Normalize the name. // Eg. "pty-req" becomes "PtyReq". $cls = str_replace(' ', '', ucwords(str_replace('-', ' ', $type))); $cls = '\\fpoirotte\\Pssht\\Messages\\CHANNEL\\REQUEST\\' . $cls; $message = $cls::unserialize($decoder); break; default: if ($wantsReply) { $response = new \fpoirotte\Pssht\Messages\CHANNEL\FAILURE($remoteChannel); $transport->writeMessage($response); } return true; } if (!$wantsReply) { return true; } if (in_array($type, array('shell', 'exec'), true)) { $response = new \fpoirotte\Pssht\Messages\CHANNEL\SUCCESS($remoteChannel); } else { $response = new \fpoirotte\Pssht\Messages\CHANNEL\FAILURE($remoteChannel); } $transport->writeMessage($response); if (in_array($type, array('shell', 'exec'), true)) { $callable = $transport->getApplicationFactory(); if ($callable !== null) { call_user_func($callable, $transport, $this->connection, $message); } } return true; }
public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context) { unset($context['rekeying']); $response = new \fpoirotte\Pssht\Messages\NEWKEYS(); $transport->writeMessage($response); $logging = \Plop\Plop::getInstance(); // Reset the various keys. $kexAlgo = $context['kexAlgo']; $kexAlgo = new $kexAlgo(); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeMpint($context['DH']->getSharedSecret()); $sharedSecret = $encoder->getBuffer()->get(0); $exchangeHash = $context['DH']->getExchangeHash(); $sessionId = $context['sessionIdentifier']; $limiters = array('A' => array($context['C2S']['Encryption'], 'getIVSize'), 'B' => array($context['S2C']['Encryption'], 'getIVSize'), 'C' => array($context['C2S']['Encryption'], 'getKeySize'), 'D' => array($context['S2C']['Encryption'], 'getKeySize'), 'E' => array($context['C2S']['MAC'], 'getKeySize'), 'F' => array($context['C2S']['MAC'], 'getKeySize')); $shared = gmp_strval($context['DH']->getSharedSecret(), 16); $shared = str_pad($shared, strlen($shared) + 1 >> 1 << 1, '0', STR_PAD_LEFT); $logging->debug('Key exchange: %s', array($context['kexAlgo'])); $logging->debug('Shared secret: %s', array(wordwrap($shared, 16, ' ', true))); $logging->debug('Hash: %s', array(wordwrap(bin2hex($exchangeHash), 16, ' ', true))); foreach (array('A', 'B', 'C', 'D', 'E', 'F') as $keyIndex) { $key = $kexAlgo->hash($sharedSecret . $exchangeHash . $keyIndex . $sessionId); $limit = call_user_func($limiters[$keyIndex]); while (strlen($key) < $limit) { $key .= $kexAlgo->hash($sharedSecret . $exchangeHash . $key); } $key = (string) substr($key, 0, $limit); $context['keys'][$keyIndex] = $key; $logging->debug('Key %(keyName)s: %(keyValue)s', array('keyName' => $keyIndex, 'keyValue' => wordwrap(bin2hex($key), 16, ' ', true))); } // Encryption $cls = $context['C2S']['Encryption']; $transport->setDecryptor(new $cls($context['keys']['A'], $context['keys']['C'])); $logging->debug('C2S Encryption: %s', array($cls)); $cls = $context['S2C']['Encryption']; $transport->setEncryptor(new $cls($context['keys']['B'], $context['keys']['D'])); $logging->debug('S2C Encryption: %s', array($cls)); // MAC $cls = $context['C2S']['MAC']; $transport->setInputMAC(new $cls($context['keys']['E'])); $logging->debug('C2S MAC: %s', array($cls)); $cls = $context['S2C']['MAC']; $transport->setOutputMAC(new $cls($context['keys']['F'])); $logging->debug('S2C MAC: %s', array($cls)); // Compression $cls = $context['C2S']['Compression']; $transport->setUncompressor(new $cls(CompressionInterface::MODE_UNCOMPRESS)); $logging->debug('C2S Compression: %s', array($cls)); $cls = $context['S2C']['Compression']; $transport->setCompressor(new $cls(CompressionInterface::MODE_COMPRESS)); $logging->debug('S2C Compression: %s', array($cls)); return true; }
public function authenticate(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base $message, \fpoirotte\Pssht\Transport $transport, array &$context) { if (!$message instanceof \fpoirotte\Pssht\Messages\USERAUTH\REQUEST\HostBased) { throw new \InvalidArgumentException(); } $logging = \Plop\Plop::getInstance(); $reverse = gethostbyaddr($transport->getAddress()); $untrustedHost = rtrim($message->getHostname(), '.'); $algos = \fpoirotte\Pssht\Algorithms::factory(); $cls = $algos->getClass('PublicKey', $message->getAlgorithm()); if ($cls === null || !$this->store->exists($message->getUserName(), $message->getKey())) { $logging->info('Rejected host based connection from %(ruser)s@%(rhost)s ' . '(%(ruser)s@%(reverse)s) to "%(luser)s" ' . '(unsupported key)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse)); return self::AUTH_REMOVE; } $key = $cls::loadPublic(base64_encode($message->getKey())); $encoder = new \fpoirotte\Pssht\Wire\Encoder(); $encoder->encodeString($context['DH']->getExchangeHash()); $encoder->encodeBytes(chr(\fpoirotte\Pssht\Messages\USERAUTH\REQUEST\Base::getMessageId())); $encoder->encodeString($message->getUserName()); $encoder->encodeString($message->getServiceName()); $encoder->encodeString(static::getName()); $encoder->encodeString($message->getAlgorithm()); $encoder->encodeString($message->getKey()); $encoder->encodeString($message->getHostname()); $encoder->encodeString($message->getRemoteUser()); if (!$key->check($encoder->getBuffer()->get(0), $message->getSignature())) { $logging->warn('Rejected host based connection from %(ruser)s@%(rhost)s ' . '(%(ruser)s@%(reverse)s) to "%(luser)s" (invalid signature)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse)); return self::AUTH_REJECT; } if ($reverse !== $untrustedHost) { $logging->warning('Ignored reverse lookup mismatch for %(address)s (' . '"%(reverse)s" vs. "%(untrusted)s")', array('address' => $transport->getAddress(), 'reverse' => $reverse, 'untrusted' => escape($untrustedHost))); } if ($message->getUserName() !== $message->getRemoteUser()) { $logging->warning('Rejected host based connection from %(ruser)s@%(rhost)s ' . '(%(ruser)s@%(reverse)s): remote user does not match ' . 'local user (%(luser)s)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse)); return self::AUTH_REMOVE; } $logging->info('Accepted host based connection ' . 'from "%(ruser)s@%(rhost)s" (%(ruser)s@%(reverse)s) ' . 'to "%(luser)s" (using "%(algorithm)s" algorithm)', array('ruser' => escape($message->getRemoteUser()), 'luser' => escape($message->getUserName()), 'rhost' => escape($untrustedHost), 'reverse' => $reverse, 'algorithm' => escape($message->getAlgorithm()))); return self::AUTH_ACCEPT; }
public function handleKEXINIT(\fpoirotte\Pssht\Transport $transport, array &$context) { $algos = \fpoirotte\Pssht\Algorithms::factory(); // Cookie $random = new \fpoirotte\Pssht\Random\OpenSSL(); // KEX $kexAlgos = $algos->getAlgorithms('KEX'); if (!count($kexAlgos)) { throw new \RuntimeException(); } // Server key $serverHostKeyAlgos = array_intersect($algos->getAlgorithms('PublicKey'), array_keys($context['serverKeys'])); if (!count($serverHostKeyAlgos)) { throw new \RuntimeException(); } // Encryption $encAlgosC2S = array_diff($algos->getAlgorithms('Encryption'), array('none')); $encAlgosS2C = $encAlgosC2S; if (!count($encAlgosC2S)) { throw new \RuntimeException(); } // MAC $macAlgosC2S = array_diff($algos->getAlgorithms('MAC'), array('none')); $macAlgosS2C = $macAlgosC2S; if (!count($macAlgosC2S)) { throw new \RuntimeException(); } // Compression $compAlgosC2S = $algos->getAlgorithms('Compression'); $compAlgosS2C = $compAlgosC2S; if (!count($compAlgosC2S)) { throw new \RuntimeException(); } $kex = new \fpoirotte\Pssht\Messages\KEXINIT($random, $kexAlgos, $serverHostKeyAlgos, $encAlgosC2S, $encAlgosS2C, $macAlgosC2S, $macAlgosS2C, $compAlgosC2S, $compAlgosS2C); $context['kex']['server'] = $kex; $transport->writeMessage($kex); return true; }
public static function addHandlers(\fpoirotte\Pssht\Transport $transport) { $transport->setHandler(\fpoirotte\Pssht\Messages\KEX\ECDH\INIT\Curve25519::getMessageId(), new \fpoirotte\Pssht\Handlers\KEX\ECDH\INIT\Curve25519()); }
/** * Report an authentication failure. * * \param fpoirotte::Pssht::Transport $transport * Transport layer used to report the failure. * * \param array &$context * SSH session context (containing authentication methods * that may continue). * * \param bool $partial * (optional) Indicates whether the request ended with * a partial success (\b true) or not (\b false). * If omitted, \b false is implied. * * \retval true * This method always returns true. */ protected function failure(\fpoirotte\Pssht\Transport $transport, array &$context, $partial = false) { if (!is_bool($partial)) { throw new \InvalidArgumentException(); } $remaining = $context['authMethods']; unset($remaining['none']); $remaining = array_keys($remaining); $response = new \fpoirotte\Pssht\Messages\USERAUTH\FAILURE($remaining, $partial); $transport->writeMessage($response); return true; }