diff -r fbd3585af53e -r 0ff0059f2ec3 src/net/java/otr4j/io/SerializationUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/SerializationUtils.java Sun Dec 05 18:43:51 2010 +0100 @@ -0,0 +1,341 @@ +/* + * otr4j, the open source java otr library. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.otr4j.io; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigInteger; +import java.security.PublicKey; +import java.util.List; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.interfaces.DHPublicKey; + +import org.bouncycastle.util.encoders.Base64; + +import net.java.otr4j.io.messages.AbstractEncodedMessage; +import net.java.otr4j.io.messages.AbstractMessage; +import net.java.otr4j.io.messages.DHCommitMessage; +import net.java.otr4j.io.messages.DHKeyMessage; +import net.java.otr4j.io.messages.DataMessage; +import net.java.otr4j.io.messages.ErrorMessage; +import net.java.otr4j.io.messages.MysteriousT; +import net.java.otr4j.io.messages.PlainTextMessage; +import net.java.otr4j.io.messages.QueryMessage; +import net.java.otr4j.io.messages.RevealSignatureMessage; +import net.java.otr4j.io.messages.SignatureM; +import net.java.otr4j.io.messages.SignatureMessage; +import net.java.otr4j.io.messages.SignatureX; + +/** + * + * @author George Politis + */ +public class SerializationUtils { + // Mysterious X IO. + public static SignatureX toMysteriousX(byte[] b) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(b); + OtrInputStream ois = new OtrInputStream(in); + SignatureX x = ois.readMysteriousX(); + ois.close(); + return x; + } + + public static byte[] toByteArray(SignatureX x) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeMysteriousX(x); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Mysterious M IO. + public static byte[] toByteArray(SignatureM m) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeMysteriousX(m); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Mysterious T IO. + public static byte[] toByteArray(MysteriousT t) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeMysteriousT(t); + byte[] b = out.toByteArray(); + out.close(); + return b; + } + + // Basic IO. + public static byte[] writeData(byte[] b) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeData(b); + byte[] otrb = out.toByteArray(); + out.close(); + return otrb; + } + + // BigInteger IO. + public static byte[] writeMpi(BigInteger bigInt) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writeBigInt(bigInt); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + public static BigInteger readMpi(byte[] b) throws IOException { + ByteArrayInputStream in = new ByteArrayInputStream(b); + OtrInputStream ois = new OtrInputStream(in); + BigInteger bigint = ois.readBigInt(); + ois.close(); + return bigint; + } + + // Public Key IO. + public static byte[] writePublicKey(PublicKey pubKey) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OtrOutputStream oos = new OtrOutputStream(out); + oos.writePublicKey(pubKey); + byte[] b = out.toByteArray(); + oos.close(); + return b; + } + + // Message IO. + public static String toString(AbstractMessage m) throws IOException { + StringWriter writer = new StringWriter(); + writer.write(SerializationConstants.HEAD); + + switch (m.messageType) { + case AbstractMessage.MESSAGE_ERROR: + ErrorMessage error = (ErrorMessage) m; + writer.write(SerializationConstants.HEAD_ERROR); + writer.write(error.error); + break; + case AbstractMessage.MESSAGE_PLAINTEXT: + PlainTextMessage plaintxt = (PlainTextMessage) m; + writer.write(plaintxt.cleanText); + if (plaintxt.versions != null && plaintxt.versions.size() > 0) { + writer.write(" \\t \\t\\t\\t\\t \\t \\t \\t "); + for (int version : plaintxt.versions) { + if (version == 1) + writer.write(" \\t\\t \\t "); + + if (version == 2) + writer.write(" \\t \\t \\t "); + } + } + break; + case AbstractMessage.MESSAGE_QUERY: + QueryMessage query = (QueryMessage) m; + if (query.versions.size() == 1 && query.versions.get(0) == 1) { + writer.write(SerializationConstants.HEAD_QUERY_Q); + } else { + writer.write(SerializationConstants.HEAD_QUERY_V); + for (int version : query.versions) + writer.write(String.valueOf(version)); + + writer.write(SerializationConstants.HEAD_QUERY_Q); + } + break; + case AbstractEncodedMessage.MESSAGE_DHKEY: + case AbstractEncodedMessage.MESSAGE_REVEALSIG: + case AbstractEncodedMessage.MESSAGE_SIGNATURE: + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + case AbstractEncodedMessage.MESSAGE_DATA: + ByteArrayOutputStream o = new ByteArrayOutputStream(); + OtrOutputStream s = new OtrOutputStream(o); + + switch (m.messageType) { + case AbstractEncodedMessage.MESSAGE_DHKEY: + DHKeyMessage dhkey = (DHKeyMessage) m; + s.writeShort(dhkey.protocolVersion); + s.writeByte(dhkey.messageType); + s.writeDHPublicKey(dhkey.dhPublicKey); + break; + case AbstractEncodedMessage.MESSAGE_REVEALSIG: + RevealSignatureMessage revealsig = (RevealSignatureMessage) m; + s.writeShort(revealsig.protocolVersion); + s.writeByte(revealsig.messageType); + s.writeData(revealsig.revealedKey); + s.writeData(revealsig.xEncrypted); + s.writeMac(revealsig.xEncryptedMAC); + break; + case AbstractEncodedMessage.MESSAGE_SIGNATURE: + SignatureMessage sig = (SignatureMessage) m; + s.writeShort(sig.protocolVersion); + s.writeByte(sig.messageType); + s.writeData(sig.xEncrypted); + s.writeMac(sig.xEncryptedMAC); + break; + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + DHCommitMessage dhcommit = (DHCommitMessage) m; + s.writeShort(dhcommit.protocolVersion); + s.writeByte(dhcommit.messageType); + s.writeData(dhcommit.dhPublicKeyEncrypted); + s.writeData(dhcommit.dhPublicKeyHash); + break; + case AbstractEncodedMessage.MESSAGE_DATA: + DataMessage data = (DataMessage) m; + s.writeShort(data.protocolVersion); + s.writeByte(data.messageType); + s.writeByte(data.flags); + s.writeInt(data.senderKeyID); + s.writeInt(data.recipientKeyID); + s.writeDHPublicKey(data.nextDH); + s.writeCtr(data.ctr); + s.writeData(data.encryptedMessage); + s.writeMac(data.mac); + s.writeData(data.oldMACKeys); + break; + } + + writer.write(SerializationConstants.HEAD_ENCODED); + writer.write(new String(Base64.encode(o.toByteArray()))); + writer.write("."); + break; + default: + throw new IOException("Illegal message type."); + } + + return writer.toString(); + } + + static final Pattern patternWhitespace = Pattern + .compile("( \\t \\t\\t\\t\\t \\t \\t \\t )( \\t\\t \\t )?( \\t \\t \\t )?"); + + public static AbstractMessage toMessage(String s) throws IOException { + if (s == null || s.length() <= 1) + return null; + + if (s.indexOf(SerializationConstants.HEAD) != 0 + || s.length() <= SerializationConstants.HEAD.length()) { + // Try to detect whitespace tag. + final Matcher matcher = patternWhitespace.matcher(s); + + boolean v1 = false; + boolean v2 = false; + while (matcher.find()) { + if (!v1 && matcher.start(2) > -1) + v1 = true; + + if (!v2 && matcher.start(3) > -1) + v2 = true; + + if (v1 && v2) + break; + } + + String cleanText = matcher.replaceAll(""); + List versions; + if (v1 && v2) { + versions = new Vector(2); + versions.add(0, 1); + versions.add(0, 2); + } else if (v1) { + versions = new Vector(1); + versions.add(0, 1); + } else if (v2) { + versions = new Vector(1); + versions.add(2); + } else + versions = null; + + return new PlainTextMessage(versions, cleanText); + } else { + char contentType = s.charAt(SerializationConstants.HEAD.length()); + String content = s + .substring(SerializationConstants.HEAD.length() + 1); + switch (contentType) { + case SerializationConstants.HEAD_ENCODED: + ByteArrayInputStream bin = new ByteArrayInputStream(Base64 + .decode(content.getBytes())); + OtrInputStream otr = new OtrInputStream(bin); + // We have an encoded message. + int protocolVersion = otr.readShort(); + int messageType = otr.readByte(); + switch (messageType) { + case AbstractEncodedMessage.MESSAGE_DATA: + int flags = otr.readByte(); + int senderKeyID = otr.readInt(); + int recipientKeyID = otr.readInt(); + DHPublicKey nextDH = otr.readDHPublicKey(); + byte[] ctr = otr.readCtr(); + byte[] encryptedMessage = otr.readData(); + byte[] mac = otr.readMac(); + byte[] oldMacKeys = otr.readMac(); + return new DataMessage(protocolVersion, flags, senderKeyID, + recipientKeyID, nextDH, ctr, encryptedMessage, mac, + oldMacKeys); + case AbstractEncodedMessage.MESSAGE_DH_COMMIT: + byte[] dhPublicKeyEncrypted = otr.readData(); + byte[] dhPublicKeyHash = otr.readData(); + return new DHCommitMessage(protocolVersion, + dhPublicKeyHash, dhPublicKeyEncrypted); + case AbstractEncodedMessage.MESSAGE_DHKEY: + DHPublicKey dhPublicKey = otr.readDHPublicKey(); + return new DHKeyMessage(protocolVersion, dhPublicKey); + case AbstractEncodedMessage.MESSAGE_REVEALSIG: { + byte[] revealedKey = otr.readData(); + byte[] xEncrypted = otr.readData(); + byte[] xEncryptedMac = otr.readMac(); + return new RevealSignatureMessage(protocolVersion, + xEncrypted, xEncryptedMac, revealedKey); + } + case AbstractEncodedMessage.MESSAGE_SIGNATURE: { + byte[] xEncryted = otr.readData(); + byte[] xEncryptedMac = otr.readMac(); + return new SignatureMessage(protocolVersion, xEncryted, + xEncryptedMac); + } + default: + throw new IOException("Illegal message type."); + } + case SerializationConstants.HEAD_ERROR: + return new ErrorMessage(AbstractMessage.MESSAGE_ERROR, content); + case SerializationConstants.HEAD_QUERY_V: + case SerializationConstants.HEAD_QUERY_Q: + List versions = new Vector(); + String versionString = null; + if (SerializationConstants.HEAD_QUERY_Q == contentType) { + versions.add(1); + if (content.charAt(0) == 'v') { + versionString = content.substring(1, content + .indexOf('?')); + } + } else if (SerializationConstants.HEAD_QUERY_V == contentType) { + versionString = content.substring(0, content.indexOf('?')); + } + + if (versionString != null) { + StringReader sr = new StringReader(versionString); + int c; + while ((c = sr.read()) != -1) + if (!versions.contains(c)) + versions.add(Integer.parseInt(String + .valueOf((char) c))); + } + QueryMessage query = new QueryMessage(versions); + return query; + default: + throw new IOException("Uknown message type."); + } + } + } +}