@Override public void init(String remoteHost, int remotePort, InputStream in, OutputStream out) throws TransportException { connInfo = new ConnInfo(remoteHost, remotePort, in, out); try { log.info("Client identity string: {}", clientID); connInfo.out.write((clientID + "\r\n").getBytes(IOUtils.UTF8)); connInfo.out.flush(); // Read server's ID final Buffer.PlainBuffer buf = new Buffer.PlainBuffer(); while ((serverID = readIdentification(buf)).isEmpty()) { int b = connInfo.in.read(); if (b == -1) throw new TransportException("Server closed connection during identification exchange"); buf.putByte((byte) b); } log.info("Server identity string: {}", serverID); } catch (IOException e) { throw new TransportException(e); } reader.start(); }
@Override public boolean next(Message msg, SSHPacket packet) throws GeneralSecurityException, TransportException { if (msg != Message.KEXDH_31) throw new TransportException( DisconnectReason.KEY_EXCHANGE_FAILED, "Unexpected packet: " + msg); log.debug("Received SSH_MSG_KEXDH_REPLY"); final byte[] K_S; final BigInteger f; final byte[] sig; // signature sent by server try { K_S = packet.readBytes(); f = packet.readMPInt(); sig = packet.readBytes(); hostKey = new Buffer.PlainBuffer(K_S).readPublicKey(); } catch (Buffer.BufferException be) { throw new TransportException(be); } dh.computeK(f); final Buffer.PlainBuffer buf = new Buffer.PlainBuffer() .putString(V_C) .putString(V_S) .putString(I_C) .putString(I_S) .putString(K_S) .putMPInt(dh.getE()) .putMPInt(f) .putMPInt(dh.getK()); sha1.update(buf.array(), buf.rpos(), buf.available()); H = sha1.digest(); Signature signature = Factory.Named.Util.create( trans.getConfig().getSignatureFactories(), KeyType.fromKey(hostKey).toString()); signature.init(hostKey, null); signature.update(H, 0, H.length); if (!signature.verify(sig)) throw new TransportException( DisconnectReason.KEY_EXCHANGE_FAILED, "KeyExchange signature verification failed"); return true; }
/** * Reads the identification string from the SSH server. This is the very first string that is sent * upon connection by the server. It takes the form of, e.g. "SSH-2.0-OpenSSH_ver". * * <p>Several concerns are taken care of here, e.g. verifying protocol version, correct line * endings as specified in RFC and such. * * <p>This is not efficient but is only done once. * * @param buffer The buffer to read from. * @return empty string if full ident string has not yet been received * @throws IOException */ private String readIdentification(Buffer.PlainBuffer buffer) throws IOException { String ident; byte[] data = new byte[256]; for (; ; ) { int savedBufPos = buffer.rpos(); int pos = 0; boolean needLF = false; for (; ; ) { if (buffer.available() == 0) { // Need more data, so undo reading and return null buffer.rpos(savedBufPos); return ""; } byte b = buffer.readByte(); if (b == '\r') { needLF = true; continue; } if (b == '\n') break; if (needLF) throw new TransportException("Incorrect identification: bad line ending"); if (pos >= data.length) throw new TransportException("Incorrect identification: line too long"); data[pos++] = b; } ident = new String(data, 0, pos); if (ident.startsWith("SSH-")) break; if (buffer.rpos() > 16 * 1024) throw new TransportException("Incorrect identification: too many header lines"); } if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-")) throw new TransportException( DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED, "Server does not support SSHv2, identified as: " + ident); return ident; }