/* * otr4j, the open source java otr library. * * Distributable under LGPL license. See terms of license at gnu.org. */ package net.java.otr4j.io; import android.util.Base64; import info.guardianproject.otr.OtrConstants; 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.nio.charset.Charset; 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 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 { /** * Charset for base64-encoded content. */ public static Charset ASCII = Charset.forName("US-ASCII"); /** * Charset for message content according to OTR spec. */ public static Charset UTF8 = Charset.forName("UTF-8"); // 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(); oos.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(); oos.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); } writer.write(OtrConstants.CommonRequest); 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(Base64.encodeToString(o.toByteArray(),Base64.NO_WRAP)); 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<Integer> versions; if (v1 && v2) { versions = new Vector<Integer>(2); versions.add(0, 1); versions.add(0, 2); } else if (v1) { versions = new Vector<Integer>(1); versions.add(0, 1); } else if (v2) { versions = new Vector<Integer>(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,Base64.NO_WRAP)); 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_MESSAGE: return new ErrorMessage(AbstractMessage.MESSAGE_ERROR, content); case SerializationConstants.HEAD_QUERY_V: case SerializationConstants.HEAD_QUERY_Q: List<Integer> versions = new Vector<Integer>(); 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("Unknown message type."); } } } }