package org.thoughtcrime.SMP.crypto.SMP;
/*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.PublicKey;
/**
* @author George Politis
*/
public class SerializationUtils {
/*
// Mysterious X IO.
public static org.thoughtcrime.SMP.crypto.SMP.messages.SignatureX toMysteriousX(byte[] b) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(b);
SMPInputStream ois = new SMPInputStream(in);
org.thoughtcrime.SMP.crypto.SMP.messages.SignatureX x = ois.readMysteriousX();
ois.close();
return x;
}
public static byte[] toByteArray(org.thoughtcrime.SMP.crypto.SMP.messages.SignatureX x) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SMPOutputStream oos = new SMPOutputStream(out);
oos.writeMysteriousX(x);
byte[] b = out.toByteArray();
oos.close();
return b;
}
// Mysterious M IO.
public static byte[] toByteArray(org.thoughtcrime.SMP.crypto.SMP.messages.SignatureM m) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SMPOutputStream oos = new SMPOutputStream(out);
oos.writeMysteriousX(m);
byte[] b = out.toByteArray();
oos.close();
return b;
}
// Mysterious T IO.
public static byte[] toByteArray(org.thoughtcrime.SMP.crypto.SMP.messages.MysteriousT t) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SMPOutputStream oos = new SMPOutputStream(out);
oos.writeMysteriousT(t);
byte[] b = out.toByteArray();
oos.close();
return b;
}
*/
// Basic IO.
public static byte[] writeData(byte[] b) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SMPOutputStream sos = new SMPOutputStream(out);
sos.writeData(b);
byte[] otrb = out.toByteArray();
sos.close();
return otrb;
}
// BigInteger IO.
public static byte[] writeMpi(BigInteger bigInt) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SMPOutputStream sos = new SMPOutputStream(out);
sos.writeBigInt(bigInt);
byte[] b = out.toByteArray();
sos.close();
return b;
}
public static BigInteger readMpi(byte[] b) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(b);
SMPInputStream sis = new SMPInputStream(in);
BigInteger bigint = sis.readBigInt();
sis.close();
return bigint;
}
// Public Key IO.
public static byte[] writePublicKey(PublicKey pubKey) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SMPOutputStream sos = new SMPOutputStream(out);
sos.writePublicKey(pubKey);
byte[] b = out.toByteArray();
sos.close();
return b;
}
/*
// Message IO.
public static String toString(AbstractMessage m) throws IOException {
StringWriter writer = new StringWriter();
if (m.messageType != org.thoughtcrime.SMP.crypto.SMP.messages.AbstractMessage.MESSAGE_PLAINTEXT)
writer.write(SerializationConstants.HEAD);
switch (m.messageType) {
case AbstractMessage.MESSAGE_ERROR:
org.thoughtcrime.SMP.crypto.SMP.messages.ErrorMessage error = (org.thoughtcrime.SMP.crypto.SMP.messages.ErrorMessage) m;
writer.write(SerializationConstants.HEAD_ERROR);
writer.write(SerializationConstants.ERROR_PREFIX);
writer.write(error.error);
break;
case AbstractMessage.MESSAGE_PLAINTEXT:
org.thoughtcrime.SMP.crypto.SMP.messages.PlainTextMessage plaintxt = (org.thoughtcrime.SMP.crypto.SMP.messages.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 == OTRv.ONE)
writer.write(" \t \t \t ");
if (version == OTRv.TWO)
writer.write(" \t\t \t ");
if (version == OTRv.THREE)
writer.write(" \t\t \t\t");
}
}
break;
case AbstractMessage.MESSAGE_QUERY:
org.thoughtcrime.SMP.crypto.SMP.messages.QueryMessage query = (org.thoughtcrime.SMP.crypto.SMP.messages.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 org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DHKEY:
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_REVEALSIG:
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_SIGNATURE:
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DH_COMMIT:
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DATA:
ByteArrayOutputStream o = new ByteArrayOutputStream();
SMPOutputStream s = new SMPOutputStream(o);
switch (m.messageType) {
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DHKEY:
org.thoughtcrime.SMP.crypto.SMP.messages.DHKeyMessage dhkey = (org.thoughtcrime.SMP.crypto.SMP.messages.DHKeyMessage) m;
s.writeShort(dhkey.protocolVersion);
s.writeByte(dhkey.messageType);
if (dhkey.protocolVersion == OTRv.THREE) {
s.writeInt(dhkey.senderInstanceTag);
s.writeInt(dhkey.receiverInstanceTag);
}
s.writeDHPublicKey(dhkey.dhPublicKey);
break;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_REVEALSIG:
org.thoughtcrime.SMP.crypto.SMP.messages.RevealSignatureMessage revealsig = (org.thoughtcrime.SMP.crypto.SMP.messages.RevealSignatureMessage) m;
s.writeShort(revealsig.protocolVersion);
s.writeByte(revealsig.messageType);
if (revealsig.protocolVersion == OTRv.THREE) {
s.writeInt(revealsig.senderInstanceTag);
s.writeInt(revealsig.receiverInstanceTag);
}
s.writeData(revealsig.revealedKey);
s.writeData(revealsig.xEncrypted);
s.writeMac(revealsig.xEncryptedMAC);
break;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_SIGNATURE:
org.thoughtcrime.SMP.crypto.SMP.messages.SignatureMessage sig = (org.thoughtcrime.SMP.crypto.SMP.messages.SignatureMessage) m;
s.writeShort(sig.protocolVersion);
s.writeByte(sig.messageType);
if (sig.protocolVersion == OTRv.THREE) {
s.writeInt(sig.senderInstanceTag);
s.writeInt(sig.receiverInstanceTag);
}
s.writeData(sig.xEncrypted);
s.writeMac(sig.xEncryptedMAC);
break;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DH_COMMIT:
org.thoughtcrime.SMP.crypto.SMP.messages.DHCommitMessage dhcommit = (org.thoughtcrime.SMP.crypto.SMP.messages.DHCommitMessage) m;
s.writeShort(dhcommit.protocolVersion);
s.writeByte(dhcommit.messageType);
if (dhcommit.protocolVersion == OTRv.THREE) {
s.writeInt(dhcommit.senderInstanceTag);
s.writeInt(dhcommit.receiverInstanceTag);
}
s.writeData(dhcommit.dhPublicKeyEncrypted);
s.writeData(dhcommit.dhPublicKeyHash);
break;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DATA:
org.thoughtcrime.SMP.crypto.SMP.messages.DataMessage data = (org.thoughtcrime.SMP.crypto.SMP.messages.DataMessage) m;
s.writeShort(data.protocolVersion);
s.writeByte(data.messageType);
if (data.protocolVersion == OTRv.THREE) {
s.writeInt(data.senderInstanceTag);
s.writeInt(data.receiverInstanceTag);
}
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 )?( \\t\\t \\t\\t)?");
public static AbstractMessage toMessage(String s) throws IOException {
if (s == null || s.length() == 0)
return null;
int idxHead = s.indexOf(SerializationConstants.HEAD);
if (idxHead > -1) {
// Message **contains** the string "?OTR". Check to see if it is an error message, a query message or a data
// message.
char contentType = s.charAt(idxHead + SerializationConstants.HEAD.length());
String content = s
.substring(idxHead + SerializationConstants.HEAD.length() + 1);
if (contentType == SerializationConstants.HEAD_ERROR
&& content.startsWith(SerializationConstants.ERROR_PREFIX)) {
// Error tag found.
content = content.substring(idxHead + SerializationConstants.ERROR_PREFIX
.length());
return new org.thoughtcrime.SMP.crypto.SMP.messages.ErrorMessage(org.thoughtcrime.SMP.crypto.SMP.messages.AbstractMessage.MESSAGE_ERROR, content);
} else if (contentType == SerializationConstants.HEAD_QUERY_V
|| contentType == SerializationConstants.HEAD_QUERY_Q) {
// Query tag found.
List<Integer> versions = new Vector<Integer>();
String versionString = null;
if (SerializationConstants.HEAD_QUERY_Q == contentType) {
versions.add(OTRv.ONE);
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)));
}
org.thoughtcrime.SMP.crypto.SMP.messages.QueryMessage query = new org.thoughtcrime.SMP.crypto.SMP.messages.QueryMessage(versions);
return query;
} else if (idxHead == 0 && contentType == SerializationConstants.HEAD_ENCODED) {
// Data message found.
// BC 1.48 added a check to throw an exception if a non-base64 character is encountered.
// An OTR message consists of ?OTR:AbcDefFe. (note the terminating point).
// Otr4j doesn't strip this point before passing the content to the base64 decoder.
// So in order to decode the content string we have to get rid of the '.' first.
ByteArrayInputStream bin = new ByteArrayInputStream(Base64
.decode(content.substring(0, content.length() - 1).getBytes()));
SMPInputStream otr = new SMPInputStream(bin);
// We have an encoded message.
int protocolVersion = otr.readShort();
int messageType = otr.readByte();
int senderInstanceTag = 0;
int recipientInstanceTag = 0;
if (protocolVersion == OTRv.THREE) {
senderInstanceTag = otr.readInt();
recipientInstanceTag = otr.readInt();
}
switch (messageType) {
case org.thoughtcrime.SMP.crypto.SMP.messages.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();
org.thoughtcrime.SMP.crypto.SMP.messages.DataMessage dataMessage =
new org.thoughtcrime.SMP.crypto.SMP.messages.DataMessage(protocolVersion, flags, senderKeyID,
recipientKeyID, nextDH, ctr, encryptedMessage, mac,
oldMacKeys);
dataMessage.senderInstanceTag = senderInstanceTag;
dataMessage.receiverInstanceTag = recipientInstanceTag;
otr.close();
return dataMessage;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DH_COMMIT:
byte[] dhPublicKeyEncrypted = otr.readData();
byte[] dhPublicKeyHash = otr.readData();
org.thoughtcrime.SMP.crypto.SMP.messages.DHCommitMessage dhCommitMessage =
new org.thoughtcrime.SMP.crypto.SMP.messages.DHCommitMessage(protocolVersion,
dhPublicKeyHash, dhPublicKeyEncrypted);
dhCommitMessage.senderInstanceTag = senderInstanceTag;
dhCommitMessage.receiverInstanceTag = recipientInstanceTag;
otr.close();
return dhCommitMessage;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_DHKEY:
DHPublicKey dhPublicKey = otr.readDHPublicKey();
org.thoughtcrime.SMP.crypto.SMP.messages.DHKeyMessage dhKeyMessage = new org.thoughtcrime.SMP.crypto.SMP.messages.DHKeyMessage(protocolVersion, dhPublicKey);
dhKeyMessage.senderInstanceTag = senderInstanceTag;
dhKeyMessage.receiverInstanceTag = recipientInstanceTag;
otr.close();
return dhKeyMessage;
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_REVEALSIG: {
byte[] revealedKey = otr.readData();
byte[] xEncrypted = otr.readData();
byte[] xEncryptedMac = otr.readMac();
org.thoughtcrime.SMP.crypto.SMP.messages.RevealSignatureMessage revealSignatureMessage =
new org.thoughtcrime.SMP.crypto.SMP.messages.RevealSignatureMessage(protocolVersion,
xEncrypted, xEncryptedMac, revealedKey);
revealSignatureMessage.senderInstanceTag = senderInstanceTag;
revealSignatureMessage.receiverInstanceTag = recipientInstanceTag;
otr.close();
return revealSignatureMessage;
}
case org.thoughtcrime.SMP.crypto.SMP.messages.AbstractEncodedMessage.MESSAGE_SIGNATURE: {
byte[] xEncryted = otr.readData();
byte[] xEncryptedMac = otr.readMac();
org.thoughtcrime.SMP.crypto.SMP.messages.SignatureMessage signatureMessage =
new org.thoughtcrime.SMP.crypto.SMP.messages.SignatureMessage(protocolVersion, xEncryted,
xEncryptedMac);
signatureMessage.senderInstanceTag = senderInstanceTag;
signatureMessage.receiverInstanceTag = recipientInstanceTag;
otr.close();
return signatureMessage;
}
default:
// NOTE by gp: aren't we being a little too harsh here? Passing the message as a plaintext
// message to the host application shouldn't hurt anybody.
otr.close();
throw new IOException("Illegal message type.");
}
}
}
// Try to detect whitespace tag.
final Matcher matcher = patternWhitespace.matcher(s);
boolean v1 = false;
boolean v2 = false;
boolean v3 = false;
while (matcher.find()) {
if (!v1 && matcher.start(2) > -1)
v1 = true;
if (!v2 && matcher.start(3) > -1)
v2 = true;
if (!v3 && matcher.start(3) > -1)
v3 = true;
if (v1 && v2 && v3)
break;
}
String cleanText = matcher.replaceAll("");
List<Integer> versions = null;
if (v1 || v2 || v3) {
versions = new ArrayList<Integer>();
if (v1)
versions.add(OTRv.ONE);
if (v2)
versions.add(OTRv.TWO);
if (v3)
versions.add(OTRv.THREE);
}
return new org.thoughtcrime.SMP.crypto.SMP.messages.PlainTextMessage(versions, cleanText);
}
*/
private static final char HEX_ENCODER[] = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static String byteArrayToHexString(byte in[]) {
int i = 0;
if (in == null || in.length <= 0)
return null;
StringBuffer out = new StringBuffer(in.length * 2);
while (i < in.length) {
out.append(HEX_ENCODER[(in[i] >>> 4) & 0x0F]);
out.append(HEX_ENCODER[in[i] & 0x0F]);
i++;
}
return out.toString();
}
/*
private static final String HEX_DECODER = "0123456789ABCDEF";
public static byte[] hexStringToByteArray(String value) {
value = value.toUpperCase();
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int index = 0; index < value.length(); index += 2) {
int high = HEX_DECODER.indexOf(value.charAt(index));
int low = HEX_DECODER.indexOf(value.charAt(index + 1));
out.write((high << 4) + low);
}
return out.toByteArray();
}
/**
* Check whether the provided content is OTR encoded.
*
* @param content
* the content to investigate
* @return returns true if content is OTR encoded, or false otherwise
public static boolean otrEncoded(String content) {
return content.startsWith(SerializationConstants.HEAD
+ SerializationConstants.HEAD_ENCODED);
}
*/
}