14 use \fpoirotte\Pssht\Compression\CompressionInterface;
115 \
fpoirotte\Pssht\Handlers\SERVICE\REQUEST $authMethods,
122 $encoder = new \fpoirotte\Pssht\Wire\Encoder();
126 $decoder = new \fpoirotte\Pssht\Wire\Decoder();
130 throw new \InvalidArgumentException();
134 throw new \InvalidArgumentException();
139 foreach ($serverKeys as $keyType => $params) {
140 $cls = $algos->getClass(
'Key', $keyType);
142 throw new \InvalidArgumentException();
146 if (isset($params[
'passphrase'])) {
147 $passphrase = $params[
'passphrase'];
150 $keys[$keyType] = \fpoirotte\Pssht\KeyLoader\OpenSSH::loadPrivate(
151 file_get_contents($params[
'file']),
156 $this->connected =
false;
157 $this->address = null;
158 $this->appFactory = null;
159 $this->banner = null;
160 $this->context = array(
161 'rekeyingBytes' => 0,
177 $this->encryptor = new \fpoirotte\Pssht\Encryption\None(null, null);
178 $this->decryptor = new \fpoirotte\Pssht\Encryption\None(null, null);
180 $this->inMAC = new \fpoirotte\Pssht\MAC\None(null);
181 $this->outMAC = new \fpoirotte\Pssht\MAC\None(null);
183 $this->handlers = array(
184 \
fpoirotte\Pssht\Messages\DISCONNECT::getMessageId() =>
185 new \
fpoirotte\Pssht\Handlers\DISCONNECT(),
187 \
fpoirotte\Pssht\Messages\IGNORE::getMessageId() =>
190 \
fpoirotte\Pssht\Messages\DEBUG::getMessageId() =>
193 \
fpoirotte\Pssht\Messages\SERVICE\REQUEST::getMessageId() =>
196 \
fpoirotte\Pssht\Messages\KEXINIT::getMessageId() =>
199 \
fpoirotte\Pssht\Messages\NEWKEYS::getMessageId() =>
202 256 =>
new \
fpoirotte\Pssht\Handlers\InitialState(),
205 $ident =
'SSH-2.0-pssht_' .
206 str_replace(array(
' ',
'-'),
'_', PSSHT_VERSION);
207 $this->context[
'identity'][
'server'] = $ident;
208 $this->context[
'serverKeys'] = $keys;
209 $this->encoder->encodeBytes($ident .
"\r\n");
232 throw new \InvalidArgumentException();
235 if ($this->address !== null) {
236 throw new \RuntimeException();
240 $this->connected =
true;
270 if (!is_int($written)) {
271 throw new \InvalidArgumentException(
'Not an integer');
274 $this->context[
'rekeyingBytes'] += $written;
276 if (isset($this->context[
'rekeying'])) {
282 $logging = \Plop\Plop::getInstance();
284 'bytes' => $this->context[
'rekeyingBytes'],
286 $time - $this->context[
'rekeyingTime'] +
290 '%(bytes)d bytes sent in %(duration)d seconds',
295 if ($this->context[
'rekeyingBytes'] >= $this->rekeyingBytes ||
296 $time >= $this->context[
'rekeyingTime']) {
297 $logging->debug(
'Initiating rekeying');
298 $this->context[
'rekeying'] =
'server';
299 $this->context[
'rekeyingBytes'] = 0;
301 $kexinit = new \fpoirotte\Pssht\Handlers\InitialState();
302 $kexinit->handleKEXINIT($this, $this->context);
306 public function isConnected()
356 throw new \InvalidArgumentException();
386 throw new \InvalidArgumentException();
467 $this->inMAC = $inputMAC;
493 $this->outMAC = $outputMAC;
505 return $this->applicationFactory;
519 $this->applicationFactory = $factory;
548 if (!is_string($message)) {
549 throw new \InvalidArgumentException();
552 $this->banner = $message;
571 if (!is_int($type) || $type < 0 || $type > 255) {
572 throw new \InvalidArgumentException();
575 if (isset($this->handlers[$type])) {
576 return $this->handlers[$type];
599 if (!is_int($type) || $type < 0 || $type > 255) {
600 throw new \InvalidArgumentException();
603 $this->handlers[$type] = $handler;
621 if (!is_int($type) || $type < 0 || $type > 255) {
622 throw new \InvalidArgumentException();
625 if (isset($this->handlers[$type]) && $this->handlers[$type] === $handler) {
626 unset($this->handlers[$type]);
642 $logging = \Plop\Plop::getInstance();
645 $encoder = new \fpoirotte\Pssht\Wire\Encoder();
646 $encoder->encodeBytes(chr($message::getMessageId()));
648 $payload =
$encoder->getBuffer()->get(0);
649 $logging->debug(
'Sending payload: %s', array(\escape($payload)));
652 $payload = $this->compressor->update($payload);
653 $size = strlen($payload);
654 $blockSize = max(8, $this->encryptor->getBlockSize());
660 if ($this->outMAC instanceof \
fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
661 $padSize = $blockSize - ((1 + $size) % $blockSize);
663 $padSize = $blockSize - ((1 + $size) % $blockSize);
665 $padSize = $blockSize - ((1 + 4 + $size) % $blockSize);
668 $padSize = ($padSize + $blockSize) % 256;
670 $padding = openssl_random_pseudo_bytes($padSize);
675 $encoder->encodeUint32(1 + $size + $padSize);
676 if ($this->outMAC instanceof \
fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
678 $encSize =
$encoder->getBuffer()->get(0);
679 $this->encoder->encodeBytes($encSize);
681 $encoder->encodeBytes(chr($padSize));
684 $packet =
$encoder->getBuffer()->get(0);
685 $encrypted = $this->encryptor->encrypt($this->outSeqNo, $packet);
688 if ($this->outMAC instanceof \
fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
689 $mac = $this->outMAC->compute($this->outSeqNo, $encSize . $encrypted);
691 $mac = $this->outMAC->compute($this->outSeqNo, $packet);
695 $this->encoder->encodeBytes($encrypted);
696 $this->encoder->encodeBytes($mac);
697 $this->outSeqNo = ++$this->outSeqNo & 0xFFFFFFFF;
700 'Sending %(type)s packet ' .
701 '(size: %(size)d, payload: %(payload)d, ' .
702 'block: %(block)d, padding: %(padding)d)',
704 'type' => get_class($message),
705 'size' => strlen($encrypted),
707 'block' => $blockSize,
708 'padding' => $padSize,
712 if ($message instanceof \
fpoirotte\Pssht\Messages\DISCONNECT) {
713 $this->connected =
false;
735 $logging = \Plop\Plop::getInstance();
738 if (!isset($this->context[
'identity'][
'client'])) {
739 return $this->handlers[256]->handle(
747 $blockSize = max($this->decryptor->getBlockSize(), 8);
751 if ($this->inMAC instanceof \
fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
752 $encPayload = $this->decoder->getBuffer()->get(4);
753 if ($encPayload === null) {
756 $unencrypted = $encPayload;
758 $encPayload = $this->decoder->getBuffer()->get(4);
759 if ($encPayload === null) {
762 $unencrypted = $this->decryptor->decrypt($this->inSeqNo, $encPayload);
763 $this->decoder->getBuffer()->unget($encPayload);
766 $encPayload = $this->decoder->getBuffer()->get($blockSize);
767 if ($encPayload === null) {
770 $unencrypted = $this->decryptor->decrypt($this->inSeqNo, $encPayload);
772 $buffer = new \fpoirotte\Pssht\Buffer($unencrypted);
773 $decoder = new \fpoirotte\Pssht\Wire\Decoder($buffer);
774 $packetLength =
$decoder->decodeUint32();
777 if ($this->inMAC instanceof \
fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
779 $toRead = $packetLength;
784 $toRead = 4 + $packetLength + $this->decryptor->getSize();
797 throw new \RuntimeException();
802 $encPayload2 = $this->decoder->getBuffer()->get($toRead);
803 if ($encPayload2 === null) {
804 $this->decoder->getBuffer()->unget($encPayload);
807 $unencrypted2 = $this->decryptor->decrypt($this->inSeqNo, $encPayload2);
808 if ($unencrypted2 === null) {
811 $buffer->push($unencrypted2);
814 $paddingLength = ord(
$decoder->decodeBytes());
815 $payload =
$decoder->decodeBytes($packetLength - $paddingLength - 1);
816 $padding =
$decoder->decodeBytes($paddingLength);
819 $macSize = $this->inMAC->getSize();
822 $actualMAC = $this->decoder->getBuffer()->get($macSize);
823 if ($actualMAC === null) {
824 $this->decoder->getBuffer()->unget($encPayload2)->unget($encPayload);
828 if ($this->inMAC instanceof \
fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
830 $macData = $encPayload . $encPayload2;
832 $macData = $unencrypted . $unencrypted2;
835 $expectedMAC = $this->inMAC->compute(
837 ((
string) substr($macData, 0, $packetLength + 4))
840 if ($expectedMAC !== $actualMAC) {
841 throw new \RuntimeException();
845 if (!isset($packetLength, $paddingLength, $payload, $padding, $actualMAC)) {
846 $this->decoder->getBuffer()->unget($actualMAC)->unget($encPayload2)->unget($encPayload);
847 $logging->error(
'Something went wrong during decoding');
851 $payload = $this->uncompressor->update($payload);
853 $msgType = ord(
$decoder->decodeBytes(1));
854 $logging->debug(
'Received payload: %s', array(\escape($payload)));
857 if (isset($this->handlers[$msgType])) {
858 $handler = $this->handlers[$msgType];
860 'Calling %(handler)s with message type #%(msgType)d',
862 'handler' => get_class($handler) .
'::handle',
863 'msgType' => $msgType,
867 $res = $handler->handle($msgType,
$decoder, $this, $this->context);
868 }
catch (\
fpoirotte\Pssht\Messages\DISCONNECT $e) {
869 if ($e->getCode() !== 0) {
875 $logging->warn(
'Unimplemented message type (%d)', array($msgType));
876 $response = new \fpoirotte\Pssht\Messages\UNIMPLEMENTED($this->inSeqNo);
880 $this->inSeqNo = ++$this->inSeqNo & 0xFFFFFFFF;
setDecryptor(\fpoirotte\Pssht\Encryption\EncryptionInterface $decryptor)
const MODE_COMPRESS
Use the algorithm for compression.
setOutputMAC(\fpoirotte\Pssht\MAC\MACInterface $outputMAC)
$context
Context for this SSH connection.
setHandler($type,\fpoirotte\Pssht\Handlers\HandlerInterface $handler)
updateWriteStats($written)
$handlers
Registered handlers for this SSH connection.
$rekeyingTime
Maximum duration before rekeying.
$compressor
Output compression.
$outSeqNo
Output sequence number.
setApplicationFactory($factory)
$rekeyingBytes
Maximum number of bytes exchanged before rekeying.
$inSeqNo
Input sequence number.
setEncryptor(\fpoirotte\Pssht\Encryption\EncryptionInterface $encryptor)
setInputMAC(\fpoirotte\Pssht\MAC\MACInterface $inputMAC)
setCompressor(CompressionInterface $compressor)
setUncompressor(CompressionInterface $uncompressor)
const MODE_UNCOMPRESS
Use the algorithm for decompression.
$address
Address (ip:port) of the client.
$uncompressor
Input compression.
$connected
Whether this client is still connected or not.
$appFactory
Factory for the application.
__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)
writeMessage(\fpoirotte\Pssht\Messages\MessageInterface $message)
unsetHandler($type,\fpoirotte\Pssht\Handlers\HandlerInterface $handler)