示例#1
0
 public static function loadPrivate($pem, $passphrase = '')
 {
     if (!is_string($pem)) {
         throw new \InvalidArgumentException();
     }
     if (!is_string($passphrase)) {
         throw new \InvalidArgumentException();
     }
     /// @FIXME support passphrase-protected EdDSA private keys.
     if ($passphrase !== '') {
         throw new \RuntimeException();
     }
     if (strncmp($pem, 'file://', 7) === 0) {
         $key = file_get_contents(substr($pem, 7));
     } else {
         $key = $pem;
     }
     $header = '-----BEGIN OPENSSH PRIVATE KEY-----';
     $footer = '-----END OPENSSH PRIVATE KEY-----';
     if (strncmp($key, $header, strlen($header)) !== 0) {
         throw new \InvalidArgumentException();
     } elseif (substr($key, -strlen($footer)) !== $footer) {
         throw new \InvalidArgumentException();
     }
     $key = base64_decode(substr($key, strlen($header), -strlen($footer)));
     if (strncmp($key, static::AUTH_MAGIC, strlen(static::AUTH_MAGIC))) {
         throw new \InvalidArgumentException();
     }
     $decoder = new \fpoirotte\Pssht\Wire\Decoder();
     $decoder->getBuffer()->push(substr($key, strlen(static::AUTH_MAGIC)));
     $ciphername = $decoder->decodeString();
     /// @FIXME support encrypted private keys
     if ($ciphername !== 'none') {
         throw new \InvalidArgumentException();
     }
     $kdfname = $decoder->decodeString();
     $kdfoptions = $decoder->decodeString();
     $numKeys = $decoder->decodeUint32();
     $publicKey = array();
     // Block malicious inputs
     if ($numKeys <= 0 || $numKeys >= 0x80000000) {
         throw new \InvalidArgumentException();
     }
     for ($i = 0; $i < $numKeys; $i++) {
         $tmp = new \fpoirotte\Pssht\Wire\Decoder();
         $tmp->getBuffer()->push($decoder->decodeString());
         // Reject unknown key identifiers
         if ($tmp->decodeString() !== static::getName()) {
             continue;
         }
         $publicKey[$i] = $tmp->decodeString();
     }
     $tmp = new \fpoirotte\Pssht\Wire\Decoder();
     $tmp->getBuffer()->push($decoder->decodeString());
     // Both "checkint" fields must have the same value.
     if ($tmp->decodeUint32() !== $tmp->decodeUint32()) {
         throw new \InvalidArgumentException();
     }
     // Reject unknown identifiers.
     if ($tmp->decodeString() !== static::getName()) {
         throw new \InvalidArgumentException();
     }
     // Discard public key blob (duplicate).
     $tmp->decodeString();
     $secretKey = array();
     for ($i = 0; $i < $numKeys; $i++) {
         $secretKey[$i] = $tmp->decodeString();
         // Discard comment field.
         $tmp->decodeString();
     }
     // Should we also ensure that a correct padding
     // has been applied?
     $pk = reset($publicKey);
     if (!isset($secretKey[key($publicKey)])) {
         throw new \InvalidArgumentException();
     }
     $sk = $secretKey[key($publicKey)];
     return new static($pk, $sk);
 }
示例#2
0
 /**
  * Try to read and handle a single SSH message.
  *
  *  \retval bool
  *      \b true if a message was successfully read and handled,
  *      \b false otherwise.
  *
  *  \note
  *      Depending on the circumstances, messages may be successfully
  *      read but left unhandled (eg. because the message was incomplete).
  *      In such cases, the message will be reinjected and \b false
  *      returned, making it possible for a future call to this method
  *      to handle the (full) message again.
  */
 public function readMessage()
 {
     $logging = \Plop\Plop::getInstance();
     // Initial state: expect the client's identification string.
     if (!isset($this->context['identity']['client'])) {
         return $this->handlers[256]->handle(null, $this->decoder, $this, $this->context);
     }
     $blockSize = max($this->decryptor->getBlockSize(), 8);
     // See http://api.libssh.org/rfc/PROTOCOL
     // for more information on EtM (Encrypt-then-MAC).
     if ($this->inMAC instanceof \fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
         $encPayload = $this->decoder->getBuffer()->get(4);
         if ($encPayload === null) {
             return false;
         }
         $unencrypted = $encPayload;
     } elseif ($this->decryptor instanceof \fpoirotte\Pssht\AEADInterface) {
         $encPayload = $this->decoder->getBuffer()->get(4);
         if ($encPayload === null) {
             return false;
         }
         $unencrypted = $this->decryptor->decrypt($this->inSeqNo, $encPayload);
         $this->decoder->getBuffer()->unget($encPayload);
         $encPayload = '';
     } else {
         $encPayload = $this->decoder->getBuffer()->get($blockSize);
         if ($encPayload === null) {
             return false;
         }
         $unencrypted = $this->decryptor->decrypt($this->inSeqNo, $encPayload);
     }
     $buffer = new \fpoirotte\Pssht\Buffer($unencrypted);
     $decoder = new \fpoirotte\Pssht\Wire\Decoder($buffer);
     $packetLength = $decoder->decodeUint32();
     // Read the rest of the message.
     if ($this->inMAC instanceof \fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
         // Only the main payload remains.
         $toRead = $packetLength;
     } elseif ($this->decryptor instanceof \fpoirotte\Pssht\AEADInterface) {
         // packet length (authenticated data)
         // + encrypted payload
         // + authentication tag (AT)
         $toRead = 4 + $packetLength + $this->decryptor->getSize();
     } else {
         $toRead = 4 - $blockSize + $packetLength;
     }
     if ($toRead < 0) {
         throw new \RuntimeException();
     }
     $unencrypted2 = '';
     if ($toRead !== 0) {
         $encPayload2 = $this->decoder->getBuffer()->get($toRead);
         if ($encPayload2 === null) {
             $this->decoder->getBuffer()->unget($encPayload);
             return false;
         }
         $unencrypted2 = $this->decryptor->decrypt($this->inSeqNo, $encPayload2);
         if ($unencrypted2 === null) {
             return false;
         }
         $buffer->push($unencrypted2);
     }
     $paddingLength = ord($decoder->decodeBytes());
     $payload = $decoder->decodeBytes($packetLength - $paddingLength - 1);
     $padding = $decoder->decodeBytes($paddingLength);
     // If a MAC is in use.
     $macSize = $this->inMAC->getSize();
     $actualMAC = '';
     if ($macSize > 0) {
         $actualMAC = $this->decoder->getBuffer()->get($macSize);
         if ($actualMAC === null) {
             $this->decoder->getBuffer()->unget($encPayload2)->unget($encPayload);
             return false;
         }
         if ($this->inMAC instanceof \fpoirotte\Pssht\MAC\OpensshCom\EtM\EtMInterface) {
             // $encPayload actually contains packet length (in plaintext).
             $macData = $encPayload . $encPayload2;
         } else {
             $macData = $unencrypted . $unencrypted2;
         }
         $expectedMAC = $this->inMAC->compute($this->inSeqNo, (string) substr($macData, 0, $packetLength + 4));
         if ($expectedMAC !== $actualMAC) {
             throw new \RuntimeException();
         }
     }
     if (!isset($packetLength, $paddingLength, $payload, $padding, $actualMAC)) {
         $this->decoder->getBuffer()->unget($actualMAC)->unget($encPayload2)->unget($encPayload);
         $logging->error('Something went wrong during decoding');
         return false;
     }
     $payload = $this->uncompressor->update($payload);
     $decoder = new \fpoirotte\Pssht\Wire\Decoder(new \fpoirotte\Pssht\Buffer($payload));
     $msgType = ord($decoder->decodeBytes(1));
     $logging->debug('Received payload: %s', array(\escape($payload)));
     $res = true;
     if (isset($this->handlers[$msgType])) {
         $handler = $this->handlers[$msgType];
         $logging->debug('Calling %(handler)s with message type #%(msgType)d', array('handler' => get_class($handler) . '::handle', 'msgType' => $msgType));
         try {
             $res = $handler->handle($msgType, $decoder, $this, $this->context);
         } catch (\fpoirotte\Pssht\Messages\DISCONNECT $e) {
             if ($e->getCode() !== 0) {
                 $this->writeMessage($e);
             }
             throw $e;
         }
     } else {
         $logging->warn('Unimplemented message type (%d)', array($msgType));
         $response = new \fpoirotte\Pssht\Messages\UNIMPLEMENTED($this->inSeqNo);
         $this->writeMessage($response);
     }
     $this->inSeqNo = ++$this->inSeqNo & 0xffffffff;
     return $res;
 }