/*
* Copyright (c) 2002-2003, The Joust Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* - Neither the name of the Joust Project nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* File created by keith @ Apr 27, 2003
*
*/
package edu.tufts.vue.collab.im;
import net.kano.joscar.BinaryTools;
import net.kano.joscar.ByteBlock;
import net.kano.joscar.rv.RecvRvEvent;
import net.kano.joscar.rv.RvSession;
import net.kano.joscar.rv.RvSessionListener;
import net.kano.joscar.rv.RvSnacResponseEvent;
import net.kano.joscar.rvcmd.trillcrypt.TrillianCryptAcceptRvCmd;
import net.kano.joscar.rvcmd.trillcrypt.TrillianCryptBeginRvCmd;
import net.kano.joscar.rvcmd.trillcrypt.TrillianCryptCloseRvCmd;
import net.kano.joscar.rvcmd.trillcrypt.TrillianCryptMsgRvCmd;
import net.kano.joscar.rvcmd.trillcrypt.TrillianCryptReqRvCmd;
import net.kano.joscar.snaccmd.icbm.RvCommand;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
public class TrillianEncSession implements RvSessionListener {
private static final BigInteger FIVE = new BigInteger("5");
private Cipher encoder;
private Cipher decoder;
private BigInteger modulus;
private BigInteger myPrivate;
private BigInteger myPublic;
private BigInteger otherPublic;
private BigInteger sessionKey;
private final RvSession rvSession;
public Random random = new SecureRandom();
public TrillianEncSession(RvSession session) {
this.rvSession = session;
}
public void init() {
modulus = new BigInteger(128, random);
myPrivate = new BigInteger(128, random).mod(modulus);
myPublic = FIVE.modPow(myPrivate, modulus);
rvSession.sendRv(new TrillianCryptReqRvCmd(modulus, myPublic));
}
public RvSession getRvSession() { return rvSession; }
public BigInteger getModulus() { return modulus; }
public BigInteger getMyPrivate() { return myPrivate; }
public BigInteger getMyPublic() { return myPublic; }
public BigInteger getOtherPublic() { return otherPublic; }
public BigInteger getSessionKey() { return sessionKey; }
public void handleRv(RecvRvEvent event) {
RvCommand rvc = event.getRvCommand();
System.out.println("encsession handling event!");
if (rvc instanceof TrillianCryptReqRvCmd) {
System.out.println("got request for secureim from "
+ rvSession.getScreenname());
rvSession.addListener(this);
TrillianCryptReqRvCmd cmd = (TrillianCryptReqRvCmd) rvc;
modulus = cmd.getModulus();
otherPublic = cmd.getPublicValue();
myPrivate = new BigInteger(128, random).mod(modulus);
myPublic = FIVE.modPow(myPrivate, modulus);
initCiphers();
rvSession.sendRv(new TrillianCryptAcceptRvCmd(myPublic));
} else if (rvc instanceof TrillianCryptAcceptRvCmd) {
otherPublic = ((TrillianCryptAcceptRvCmd) rvc).getPublicValue();
initCiphers();
rvSession.sendRv(new TrillianCryptBeginRvCmd());
} else if (rvc instanceof TrillianCryptBeginRvCmd) {
System.out.println("encrypted session with "
+ rvSession.getScreenname() + " begun!");
} else if (rvc instanceof TrillianCryptMsgRvCmd) {
TrillianCryptMsgRvCmd cmd = (TrillianCryptMsgRvCmd) rvc;
byte[] encrypted = cmd.getEncryptedMsg().toByteArray();
byte[] decoded;
try {
decoded = decoder.doFinal(encrypted);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
return;
} catch (BadPaddingException e) {
e.printStackTrace();
return;
}
ByteBlock fullDecoded = ByteBlock.wrap(decoded);
// the first eight bytes are garbage and the last byte is null
ByteBlock textBlock = fullDecoded.subBlock(8,
fullDecoded.getLength() - 8 - 1);
String msg = BinaryTools.getAsciiString(textBlock);
System.out.println("message: " + msg);
} else if (rvc instanceof TrillianCryptCloseRvCmd) {
System.out.println("encryption session with "
+ rvSession.getScreenname() + " closed!");
}
}
private void initCiphers() {
sessionKey = otherPublic.modPow(myPrivate, modulus);
byte[] fbytes = sessionKey.toByteArray();
if (fbytes.length == 17) {
byte[] old = fbytes;
fbytes = new byte[fbytes.length - 1];
System.arraycopy(old, 1, fbytes, 0, fbytes.length);
}
SecretKeySpec spec = new SecretKeySpec(fbytes, "Blowfish");
byte[] ivb = new byte[8];
random.nextBytes(ivb);
IvParameterSpec ips = new IvParameterSpec(ivb);
try {
encoder = Cipher.getInstance("Blowfish/CFB64/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
try {
encoder.init(Cipher.ENCRYPT_MODE, spec, ips);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
try {
decoder = Cipher.getInstance("Blowfish/CFB64/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
try {
decoder.init(Cipher.DECRYPT_MODE, spec, ips);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
public void handleSnacResponse(RvSnacResponseEvent event) {
System.out.println("got response: " + event.getSnacCommand());
}
public void sendMsg(String msg) {
byte[] data = BinaryTools.getAsciiBytes(msg);
byte[] encoded = new byte[encoder.getOutputSize(8 + data.length)];
int totalLen;
try {
int offset = encoder.update(new byte[8], 0, 8, encoded, 0);
int len = encoder.doFinal(data, 0, data.length, encoded, offset);
totalLen = offset + len;
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
return;
} catch (BadPaddingException e) {
e.printStackTrace();
return;
} catch (ShortBufferException e) {
e.printStackTrace();
return;
}
ByteBlock encodedBlock = ByteBlock.wrap(encoded, 0, totalLen);
getRvSession().sendRv(new TrillianCryptMsgRvCmd(encodedBlock));
}
}