/*
* This file is part of "The Java Telnet Application".
*
* (c) Matthias L. Jugel, Marcus Mei�ner 1996-2002. All Rights Reserved. The
* file was changed by Radek Polak to work as midlet in MIDP 1.0
*
* This file has been modified by Karl von Randow for MidpSSH.
*
* Please visit http://javatelnet.org/ for updates and contact.
*
* --LICENSE NOTICE-- This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 675 Mass
* Ave, Cambridge, MA 02139, USA. --LICENSE NOTICE--
*/
package org.bbssh.ssh.packets;
import java.io.IOException;
import net.rim.device.api.crypto.CryptoTokenException;
import net.rim.device.api.crypto.HMAC;
import org.bbssh.ssh.SshIO;
import org.bbssh.ssh.v2.SshCrypto2;
import org.bbssh.util.Logger;
import org.bbssh.util.Tools;
public class SshPacket2 extends SshPacket {
private static final int PHASE_PACKET_LENGTH = 0;
private static final int PHASE_BLOCK = 1;
private byte[] packetLengthArray = new byte[32]; // allows block sizes up to 32. If any larger are used, this size
// must increase.
private int packetLength; // 32-bit sign int
private int padlen; // packet length 1 byte unsigned
private int position;
private int phase = PHASE_PACKET_LENGTH;
private SshCrypto2 crypto;
public SshPacket2() {
}
public SshPacket2(SshCrypto2 _crypto) {
/* receiving packet */
crypto = _crypto;
}
public SshPacket2(byte newType) {
packetType = newType;
}
/**
* Return the mp-int at the position offset in the data First 4 bytes are the number of bytes in the integer, msb
* first (for example, the value 0x00012345 would have 17 bits). The value zero has zero bits. It is permissible
* that the number of bits be larger than the real number of bits. The number of bits is followed by (bits + 7) / 8
* bytes of binary data, msb first, giving the value of the integer.
*/
public byte[] getMpInt() {
return getBytes(getInt32());
}
public void putMpInt(byte[] foo) {
int i = foo.length;
if ((foo[0] & 0x80) != 0) {
i++;
putInt32(i);
putByte((byte) 0);
} else {
putInt32(i);
}
putBytes(foo);
}
public byte[] getPayLoad(SshCrypto2 xcrypt, long seqnr) throws IOException {
byte[] data = getData();
int blocksize = xcrypt == null ? 8 : xcrypt.getSendBlockSize();
// crypted data is:
// packet length [ payloadlen + padlen + type + data ]
packetLength = 4 + 1 + 1;
if (data != null) {
packetLength += data.length;
}
// pad it up to full blocksize.
// If not crypto, zeroes, otherwise random.
// (zeros because we do not want to tell the attacker the state of our
// random generator)
int padlen = blocksize - (packetLength % blocksize);
if (padlen < 4) {
padlen += blocksize;
}
byte[] padding = new byte[padlen];
if (xcrypt == null) {
for (int i = 0; i < padlen; i++) {
padding[i] = 0;
}
} else {
for (int i = 0; i < padlen; i++) {
padding[i] = SshIO.getNotZeroRandomByte();
}
}
// [ packetlength, padlength, padding, packet type, data ]
byte[] block = new byte[packetLength + padlen];
int xlen = padlen + packetLength - 4;
block[3] = (byte) (xlen & 0xff);
block[2] = (byte) ((xlen >> 8) & 0xff);
block[1] = (byte) ((xlen >> 16) & 0xff);
block[0] = (byte) ((xlen >> 24) & 0xff);
block[4] = (byte) padlen;
block[5] = getType();
System.arraycopy(data, 0, block, 6, data.length);
System.arraycopy(padding, 0, block, 6 + data.length, padlen);
byte[] mac = null;
if (xcrypt != null) {
HMAC c2smac = xcrypt.getSndHmac();
if (c2smac != null) {
try {
Tools.updateMACForInt((int) seqnr, c2smac);
c2smac.update(block, 0, block.length);
mac = c2smac.getMAC();
} catch (CryptoTokenException ex) {
Logger.fatal("Failed to obtain mac: " + ex.getMessage());
}
}
}
if (xcrypt != null) {
block = xcrypt.encrypt(block);
}
byte[] sendblock = new byte[block.length + (mac != null ? mac.length : 0)];
System.arraycopy(block, 0, sendblock, 0, block.length);
if (mac != null) {
System.arraycopy(mac, 0, sendblock, block.length, mac.length);
}
return sendblock;
}
;
private byte encryptedBlock[];
public int addPayload(byte buff[], int offset, int length) {
int hmaclen;
int end = offset + length;
int blockSize;
if (crypto == null) {
blockSize = 8;
hmaclen = 0;
} else {
hmaclen = crypto.getRcvHmac().getLength();
blockSize = crypto.getReceiveBlockSize();
}
// Length of data decrypted after the packet length + padding length, up to block size.
int decryptedLen = blockSize - 5;
while (offset < end) {
// @todo - this logic must be in a PacketFactory, which generates specific Packet instances
// after doing initial decryption etc.
switch (phase) {
// Packet length: 32 bit unsigned integer
// gives the length of the packet, not including the length field and padding.
// @todo - check packet length against the maximum we said we'd accept (if we're after channel open -
// what's the default prior to channel open? )
case PHASE_PACKET_LENGTH:
packetLengthArray[position++] = buff[offset++];
// In order to decrypt, we must fill up our decrypt block size - even though we only want the first
// four bytes to get the packet length.
if (position == blockSize) {
byte[] tempLengthArray;
if (crypto == null) {
tempLengthArray = this.packetLengthArray;
} else {
tempLengthArray = crypto.decrypt(this.packetLengthArray, blockSize);
};
// @todo - make a conversion-helper for these byte->ints , as they're showing up all over
packetLength = (tempLengthArray[3] & 0xff) + ((tempLengthArray[2] & 0xff) << 8)
+ ((tempLengthArray[1] & 0xff) << 16) + ((tempLengthArray[0] & 0xff) << 24);
padlen = tempLengthArray[4];
position = decryptedLen;
packetLength += hmaclen;
encryptedBlock = new byte[packetLength - 1];
System.arraycopy(tempLengthArray, 5, encryptedBlock, 0, decryptedLen);
phase++;
}
break;
case PHASE_BLOCK:
if (encryptedBlock.length > position) {
if (offset < end) {
int amount = end - offset;
if (amount > encryptedBlock.length - position) {
amount = encryptedBlock.length - position;
}
System.arraycopy(buff, offset, encryptedBlock, position, amount);
offset += amount;
position += amount;
}
}
if (position == encryptedBlock.length) { // the block fully loaded, now we're going to decrypt it
packetLength -= hmaclen; // now we're going to load the hmac.
// (incl type) + padding
byte[] decryptedBlock = new byte[encryptedBlock.length - hmaclen - decryptedLen];
byte[] data;
System.arraycopy(encryptedBlock, decryptedLen, decryptedBlock, 0, decryptedBlock.length);
if (crypto != null) {
decryptedBlock = crypto.decrypt(decryptedBlock, decryptedBlock.length);
}
// Add back in first bytes
byte[] dd = new byte[decryptedBlock.length + decryptedLen];
System.arraycopy(encryptedBlock, 0, dd, 0, decryptedLen);
System.arraycopy(decryptedBlock, 0, dd, decryptedLen, decryptedBlock.length);
decryptedBlock = dd;
packetType = decryptedBlock[0];
// data
if (packetLength > padlen + 1 + 1) { // 1 for padding
// length, 1 for type
data = new byte[packetLength - 2 - padlen];
System.arraycopy(decryptedBlock, 1, data, 0, data.length);
putData(data);
} else {
putData(null);
}
/* MAC! */
return offset;
}
break;
}
}
return offset;
}
public int getLength() {
if (byteArray == null)
return 0;
return byteArray.length;
}
}