/** * Construct a new SSH_MSG_KEXDH_REPLY message. * * \param fpoirotte::Pssht::Messages::KEXDH::INIT $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\Messages\KEXDH\INIT $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(); } $keyLength = min(20, max($encryptionAlgo->getKeySize(), 16)); $randBytes = openssl_random_pseudo_bytes(2 * $keyLength); $y = gmp_init(bin2hex($randBytes), 16); $prime = gmp_init($kexAlgo::getPrime(), 16); $this->f = gmp_powm($kexAlgo::getGenerator(), $y, $prime); $this->K = gmp_powm($kexDHInit->getE(), $y, $prime); $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->encodeMpint($this->kexDHInit->getE()); $sub->encodeMpint($this->f); $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); }
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; }
/** * 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"); }
/** * 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); }