Beispiel #1
0
 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;
 }
Beispiel #2
0
 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;
 }
Beispiel #3
0
 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;
 }
Beispiel #4
0
 /**
  * Load the keys in the given file as if they belonged
  * to the specified user.
  *
  *  \param string $user
  *      User the keys belong to.
  *
  *  \param string $file
  *      File containing the keys to load.
  *      It should follow the format of OpenSSH's
  *      authorized_keys file.
  *
  *  \retval File
  *      Returns this loader.
  */
 public function load($user, $file)
 {
     if (!is_string($user)) {
         throw new \InvalidArgumentException();
     }
     if (!is_string($file)) {
         throw new \InvalidArgumentException();
     }
     $algos = \fpoirotte\Pssht\Algorithms::factory();
     $types = array('ssh-dss', 'ssh-rsa');
     foreach (file($file) as $line) {
         $fields = explode(' ', preg_replace('/\\s+/', ' ', trim($line)));
         $max = count($fields);
         for ($i = 0; $i < $max; $i++) {
             if (in_array($fields[$i], $types, true)) {
                 $cls = $algos->getClass('PublicKey', $fields[$i]);
                 $this->store->add($user, $cls::loadPublic($fields[$i + 1]));
                 break;
             }
         }
     }
     return $this;
 }
Beispiel #5
0
 /**
  * Construct a new SSH transport layer.
  *
  *  \param array $serverKeys
  *      Keys presented by the server as an associated array where:
  *      -   keys indicate the key's algorithm (eg. "ssh-dss")
  *      -   values are an associative array with the following keys:
  *          -   "file": a PEM-encoded private key or path to a PEM-encoded
  *                      private key, in "file:///path/to/key.pem" format
  *          -   "passphrase": (optional) passphrase for the key
  *
  *  \param fpoirotte::Pssht::Handlers::SERVICE::REQUEST $authMethods
  *      Allowed authentication methods.
  *
  *  \param fpoirotte::Pssht::Wire::Encoder $encoder
  *      (optional) Encoder to use when sending SSH messages.
  *      If omitted, a new encoder is automatically created.
  *
  *  \param fpoirotte::Pssht::Wire::Decoder $decoder
  *      (optional) Decoder to use when sending SSH messages.
  *      If omitted, a new decoder is automatically created.
  *
  *  \note
  *      Once this class' constructor has been called,
  *      you are advised to call the setAddress() method
  *      to register the client's IP address.
  *      This is required for some authentication methods
  *      to work properly.
  */
 public function __construct(array $serverKeys, \fpoirotte\Pssht\Handlers\SERVICE\REQUEST $authMethods, \fpoirotte\Pssht\Wire\Encoder $encoder = null, \fpoirotte\Pssht\Wire\Decoder $decoder = null, $rekeyingBytes = 1073741824, $rekeyingTime = 3600)
 {
     if ($encoder === null) {
         $encoder = new \fpoirotte\Pssht\Wire\Encoder();
     }
     if ($decoder === null) {
         $decoder = new \fpoirotte\Pssht\Wire\Decoder();
     }
     if (!is_int($rekeyingBytes) || $rekeyingBytes <= 1024) {
         throw new \InvalidArgumentException();
     }
     if (!is_int($rekeyingTime) || $rekeyingTime <= 60) {
         throw new \InvalidArgumentException();
     }
     $algos = \fpoirotte\Pssht\Algorithms::factory();
     $keys = array();
     foreach ($serverKeys as $keyType => $params) {
         $cls = $algos->getClass('PublicKey', $keyType);
         if ($cls === null) {
             throw new \InvalidArgumentException();
         }
         $passphrase = '';
         if (isset($params['passphrase'])) {
             $passphrase = $params['passphrase'];
         }
         $keys[$keyType] = $cls::loadPrivate($params['file'], $passphrase);
     }
     $this->address = null;
     $this->appFactory = null;
     $this->banner = null;
     $this->context = array('rekeyingBytes' => 0, 'rekeyingTime' => time() + $rekeyingTime);
     $this->rekeyingBytes = $rekeyingBytes;
     $this->rekeyingTime = $rekeyingTime;
     $this->inSeqNo = 0;
     $this->outSeqNo = 0;
     $this->encoder = $encoder;
     $this->decoder = $decoder;
     $this->compressor = new \fpoirotte\Pssht\Compression\None(\fpoirotte\Pssht\CompressionInterface::MODE_COMPRESS);
     $this->uncompressor = new \fpoirotte\Pssht\Compression\None(\fpoirotte\Pssht\CompressionInterface::MODE_UNCOMPRESS);
     $this->encryptor = new \fpoirotte\Pssht\Encryption\None(null, null);
     $this->decryptor = new \fpoirotte\Pssht\Encryption\None(null, null);
     $this->inMAC = new \fpoirotte\Pssht\MAC\None(null);
     $this->outMAC = new \fpoirotte\Pssht\MAC\None(null);
     $this->handlers = array(\fpoirotte\Pssht\Messages\DISCONNECT::getMessageId() => new \fpoirotte\Pssht\Handlers\DISCONNECT(), \fpoirotte\Pssht\Messages\IGNORE::getMessageId() => new \fpoirotte\Pssht\Handlers\IGNORE(), \fpoirotte\Pssht\Messages\DEBUG::getMessageId() => new \fpoirotte\Pssht\Handlers\DEBUG(), \fpoirotte\Pssht\Messages\SERVICE\REQUEST::getMessageId() => $authMethods, \fpoirotte\Pssht\Messages\KEXINIT::getMessageId() => new \fpoirotte\Pssht\Handlers\KEXINIT(), \fpoirotte\Pssht\Messages\NEWKEYS::getMessageId() => new \fpoirotte\Pssht\Handlers\NEWKEYS(), 256 => new \fpoirotte\Pssht\Handlers\InitialState());
     $ident = "SSH-2.0-pssht_1.0.x_dev";
     $this->context['identity']['server'] = $ident;
     $this->context['serverKeys'] = $keys;
     $this->encoder->encodeBytes($ident . "\r\n");
 }
Beispiel #6
0
 public function handle($msgType, \fpoirotte\Pssht\Wire\Decoder $decoder, \fpoirotte\Pssht\Transport $transport, array &$context)
 {
     $algos = \fpoirotte\Pssht\Algorithms::factory();
     $kex = \fpoirotte\Pssht\Messages\KEXINIT::unserialize($decoder);
     $context['kex']['client'] = $kex;
     if (!isset($context['rekeying'])) {
         $context['rekeying'] = 'client';
     }
     // KEX method
     $context['kexAlgo'] = null;
     foreach ($kex->getKEXAlgos() as $algo) {
         if ($algos->getClass('KEX', $algo) !== null) {
             $kexCls = $context['kexAlgo'] = $algos->getClass('KEX', $algo);
             break;
         }
     }
     // No suitable KEX algorithm found.
     if (!isset($context['kexAlgo'])) {
         throw new \RuntimeException();
     }
     $kexCls::addHandlers($transport);
     // C2S encryption
     $context['C2S']['Encryption'] = null;
     foreach ($kex->getC2SEncryptionAlgos() as $algo) {
         if ($algos->getClass('Encryption', $algo) !== null) {
             $context['C2S']['Encryption'] = $algos->getClass('Encryption', $algo);
             break;
         }
     }
     // No suitable C2S encryption cipher found.
     if (!isset($context['C2S']['Encryption'])) {
         throw new \RuntimeException();
     }
     // C2S compression
     $context['C2S']['Compression'] = null;
     foreach ($kex->getC2SCompressionAlgos() as $algo) {
         if ($algos->getClass('Compression', $algo) !== null) {
             $context['C2S']['Compression'] = $algos->getClass('Compression', $algo);
             break;
         }
     }
     // No suitable C2S compression found.
     if (!isset($context['C2S']['Compression'])) {
         throw new \RuntimeException();
     }
     // C2S MAC
     $context['C2S']['MAC'] = null;
     $reflector = new \ReflectionClass($context['C2S']['Encryption']);
     // Skip MAC algorithm selection for AEAD.
     if ($reflector->implementsInterface('\\fpoirotte\\Pssht\\AEADInterface')) {
         $context['C2S']['MAC'] = '\\fpoirotte\\Pssht\\MAC\\None';
     } else {
         foreach ($kex->getC2SMACAlgos() as $algo) {
             if ($algos->getClass('MAC', $algo) !== null) {
                 $context['C2S']['MAC'] = $algos->getClass('MAC', $algo);
                 break;
             }
         }
     }
     // No suitable C2S MAC found.
     if (!isset($context['C2S']['MAC'])) {
         throw new \RuntimeException();
     }
     // S2C encryption
     $context['S2C']['Encryption'] = null;
     foreach ($kex->getS2CEncryptionAlgos() as $algo) {
         if ($algos->getClass('Encryption', $algo) !== null) {
             $context['S2C']['Encryption'] = $algos->getClass('Encryption', $algo);
             break;
         }
     }
     // No suitable S2C encryption cipher found.
     if (!isset($context['S2C']['Encryption'])) {
         throw new \RuntimeException();
     }
     // S2C compression
     $context['S2C']['Compression'] = null;
     foreach ($kex->getS2CCompressionAlgos() as $algo) {
         if ($algos->getClass('Compression', $algo) !== null) {
             $context['S2C']['Compression'] = $algos->getClass('Compression', $algo);
             break;
         }
     }
     // No suitable S2C compression found.
     if (!isset($context['S2C']['Compression'])) {
         throw new \RuntimeException();
     }
     // S2C MAC
     $context['S2C']['MAC'] = null;
     $reflector = new \ReflectionClass($context['S2C']['Encryption']);
     // Skip MAC algorithm selection for AEAD.
     if ($reflector->implementsInterface('\\fpoirotte\\Pssht\\AEADInterface')) {
         $context['S2C']['MAC'] = '\\fpoirotte\\Pssht\\MAC\\None';
     } else {
         foreach ($kex->getS2CMACAlgos() as $algo) {
             if ($algos->getClass('MAC', $algo) !== null) {
                 $context['S2C']['MAC'] = $algos->getClass('MAC', $algo);
                 break;
             }
         }
     }
     // No suitable S2C MAC found.
     if (!isset($context['S2C']['MAC'])) {
         throw new \RuntimeException();
     }
     if ($context['rekeying'] === 'client') {
         $kexinit = new \fpoirotte\Pssht\Handlers\InitialState();
         return $kexinit->handleKEXINIT($transport, $context);
     }
     return true;
 }
Beispiel #7
0
 /**
  * Construct a new SSH_MSG_KEXDH_REPLY message.
  *
  *  \param fpoirotte::Pssht::ECC::Curve $curve
  *      Elliptic curve in use.
  *
  *  \param fpoirotte::Pssht::Messages::KEX::ECDH::INIT::RFC5656 $kexDHInit
  *      Client's contribution to the Diffie-Hellman Key Exchange.
  *
  *  \param fpoirotte::Pssht::PublicKeyInterface $key
  *      Server's public key.
  *
  *  \param fpoirotte::Pssht::EncryptionInterface $encryptionAlgo
  *      Encryption algorithm in use.
  *
  *  \param fpoirotte::Pssht::KEXInterface $kexAlgo
  *      Key exchange algorithm to use.
  *
  *  \param fpoirotte::Pssht::Messages::KEXINIT $serverKEX
  *      Algorithms supported by the server.
  *
  *  \param fpoirotte::Pssht::Messages::KEXINIT $clientKEX
  *      Algorithms supported by the client.
  *
  *  \param string $serverIdent
  *      Server's identification string
  *
  *  \param string $clientIdent
  *      Client's identification string
  */
 public function __construct(\fpoirotte\Pssht\ECC\Curve $curve, \fpoirotte\Pssht\Messages\KEX\ECDH\INIT\RFC5656 $kexDHInit, \fpoirotte\Pssht\PublicKeyInterface $key, \fpoirotte\Pssht\EncryptionInterface $encryptionAlgo, \fpoirotte\Pssht\KEXInterface $kexAlgo, \fpoirotte\Pssht\Messages\KEXINIT $serverKEX, \fpoirotte\Pssht\Messages\KEXINIT $clientKEX, $serverIdent, $clientIdent)
 {
     if (!is_string($serverIdent)) {
         throw new \InvalidArgumentException();
     }
     if (!is_string($clientIdent)) {
         throw new \InvalidArgumentException();
     }
     $len = strlen(gmp_strval($curve->getOrder(), 2));
     $len = ceil($len / 8);
     $randBytes = openssl_random_pseudo_bytes($len);
     $d_S = gmp_mod(gmp_init(bin2hex($randBytes), 16), $curve->getModulus());
     $this->Q_S = $curve->getGenerator()->multiply($curve, $d_S);
     $Q_C = $kexDHInit->getQ();
     /// @FIXME this is not optimal...
     $algorithms = \fpoirotte\Pssht\Algorithms::factory();
     $cls = $algorithms->getClass('PublicKey', 'ecdsa-sha2-' . $curve->getName());
     $clientPK = new $cls($Q_C);
     if (!$clientPK->isValid()) {
         throw new \InvalidArgumentException();
     }
     // EC Co-factor DH (sec1-v2, section 3.3.2).
     $P = $Q_C->multiply($curve, gmp_mul($curve->getCofactor(), $d_S));
     if ($P->isIdentity($curve)) {
         throw new \InvalidArgumentException();
     }
     $this->K = $P->x;
     $this->curve = $curve;
     $this->K_S = $key;
     $this->kexDHInit = $kexDHInit;
     $this->kexAlgo = $kexAlgo;
     $this->serverKEX = $serverKEX;
     $this->clientKEX = $clientKEX;
     $this->serverIdent = $serverIdent;
     $this->clientIdent = $clientIdent;
     $msgId = chr(\fpoirotte\Pssht\Messages\KEXINIT::getMessageId());
     // $sub is used to create the structure for the hashing function.
     $sub = new \fpoirotte\Pssht\Wire\Encoder(new \fpoirotte\Pssht\Buffer());
     $this->K_S->serialize($sub);
     $K_S = $sub->getBuffer()->get(0);
     $sub->encodeString($this->clientIdent);
     $sub->encodeString($this->serverIdent);
     // $sub2 is used to compute the value
     // of various fields inside the structure.
     $sub2 = new \fpoirotte\Pssht\Wire\Encoder(new \fpoirotte\Pssht\Buffer());
     $sub2->encodeBytes($msgId);
     // Add message identifier.
     $this->clientKEX->serialize($sub2);
     $sub->encodeString($sub2->getBuffer()->get(0));
     $sub2->encodeBytes($msgId);
     // Add message identifier.
     $this->serverKEX->serialize($sub2);
     $sub->encodeString($sub2->getBuffer()->get(0));
     $sub->encodeString($K_S);
     $sub->encodeString($Q_C->serialize($curve));
     $sub->encodeString($this->Q_S->serialize($curve));
     $sub->encodeMpint($this->K);
     $logging = \Plop\Plop::getInstance();
     $origData = $sub->getBuffer()->get(0);
     $data = wordwrap(bin2hex($origData), 4, ' ', true);
     $data = wordwrap($data, 32 + 7, PHP_EOL, true);
     $logging->debug("Signature payload:\r\n%s", array($data));
     $this->H = $this->kexAlgo->hash($origData);
 }