|
1 /* |
|
2 * otr4j, the open source java otr library. |
|
3 * |
|
4 * Distributable under LGPL license. |
|
5 * See terms of license at gnu.org. |
|
6 */ |
|
7 package net.java.otr4j.io; |
|
8 |
|
9 import java.io.ByteArrayInputStream; |
|
10 import java.io.ByteArrayOutputStream; |
|
11 import java.io.IOException; |
|
12 import java.io.StringReader; |
|
13 import java.io.StringWriter; |
|
14 import java.math.BigInteger; |
|
15 import java.security.PublicKey; |
|
16 import java.util.List; |
|
17 import java.util.Vector; |
|
18 import java.util.regex.Matcher; |
|
19 import java.util.regex.Pattern; |
|
20 |
|
21 import javax.crypto.interfaces.DHPublicKey; |
|
22 |
|
23 import org.bouncycastle.util.encoders.Base64; |
|
24 |
|
25 import net.java.otr4j.io.messages.AbstractEncodedMessage; |
|
26 import net.java.otr4j.io.messages.AbstractMessage; |
|
27 import net.java.otr4j.io.messages.DHCommitMessage; |
|
28 import net.java.otr4j.io.messages.DHKeyMessage; |
|
29 import net.java.otr4j.io.messages.DataMessage; |
|
30 import net.java.otr4j.io.messages.ErrorMessage; |
|
31 import net.java.otr4j.io.messages.MysteriousT; |
|
32 import net.java.otr4j.io.messages.PlainTextMessage; |
|
33 import net.java.otr4j.io.messages.QueryMessage; |
|
34 import net.java.otr4j.io.messages.RevealSignatureMessage; |
|
35 import net.java.otr4j.io.messages.SignatureM; |
|
36 import net.java.otr4j.io.messages.SignatureMessage; |
|
37 import net.java.otr4j.io.messages.SignatureX; |
|
38 |
|
39 /** |
|
40 * |
|
41 * @author George Politis |
|
42 */ |
|
43 public class SerializationUtils { |
|
44 // Mysterious X IO. |
|
45 public static SignatureX toMysteriousX(byte[] b) throws IOException { |
|
46 ByteArrayInputStream in = new ByteArrayInputStream(b); |
|
47 OtrInputStream ois = new OtrInputStream(in); |
|
48 SignatureX x = ois.readMysteriousX(); |
|
49 ois.close(); |
|
50 return x; |
|
51 } |
|
52 |
|
53 public static byte[] toByteArray(SignatureX x) throws IOException { |
|
54 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
55 OtrOutputStream oos = new OtrOutputStream(out); |
|
56 oos.writeMysteriousX(x); |
|
57 byte[] b = out.toByteArray(); |
|
58 oos.close(); |
|
59 return b; |
|
60 } |
|
61 |
|
62 // Mysterious M IO. |
|
63 public static byte[] toByteArray(SignatureM m) throws IOException { |
|
64 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
65 OtrOutputStream oos = new OtrOutputStream(out); |
|
66 oos.writeMysteriousX(m); |
|
67 byte[] b = out.toByteArray(); |
|
68 oos.close(); |
|
69 return b; |
|
70 } |
|
71 |
|
72 // Mysterious T IO. |
|
73 public static byte[] toByteArray(MysteriousT t) throws IOException { |
|
74 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
75 OtrOutputStream oos = new OtrOutputStream(out); |
|
76 oos.writeMysteriousT(t); |
|
77 byte[] b = out.toByteArray(); |
|
78 out.close(); |
|
79 return b; |
|
80 } |
|
81 |
|
82 // Basic IO. |
|
83 public static byte[] writeData(byte[] b) throws IOException { |
|
84 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
85 OtrOutputStream oos = new OtrOutputStream(out); |
|
86 oos.writeData(b); |
|
87 byte[] otrb = out.toByteArray(); |
|
88 out.close(); |
|
89 return otrb; |
|
90 } |
|
91 |
|
92 // BigInteger IO. |
|
93 public static byte[] writeMpi(BigInteger bigInt) throws IOException { |
|
94 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
95 OtrOutputStream oos = new OtrOutputStream(out); |
|
96 oos.writeBigInt(bigInt); |
|
97 byte[] b = out.toByteArray(); |
|
98 oos.close(); |
|
99 return b; |
|
100 } |
|
101 |
|
102 public static BigInteger readMpi(byte[] b) throws IOException { |
|
103 ByteArrayInputStream in = new ByteArrayInputStream(b); |
|
104 OtrInputStream ois = new OtrInputStream(in); |
|
105 BigInteger bigint = ois.readBigInt(); |
|
106 ois.close(); |
|
107 return bigint; |
|
108 } |
|
109 |
|
110 // Public Key IO. |
|
111 public static byte[] writePublicKey(PublicKey pubKey) throws IOException { |
|
112 ByteArrayOutputStream out = new ByteArrayOutputStream(); |
|
113 OtrOutputStream oos = new OtrOutputStream(out); |
|
114 oos.writePublicKey(pubKey); |
|
115 byte[] b = out.toByteArray(); |
|
116 oos.close(); |
|
117 return b; |
|
118 } |
|
119 |
|
120 // Message IO. |
|
121 public static String toString(AbstractMessage m) throws IOException { |
|
122 StringWriter writer = new StringWriter(); |
|
123 writer.write(SerializationConstants.HEAD); |
|
124 |
|
125 switch (m.messageType) { |
|
126 case AbstractMessage.MESSAGE_ERROR: |
|
127 ErrorMessage error = (ErrorMessage) m; |
|
128 writer.write(SerializationConstants.HEAD_ERROR); |
|
129 writer.write(error.error); |
|
130 break; |
|
131 case AbstractMessage.MESSAGE_PLAINTEXT: |
|
132 PlainTextMessage plaintxt = (PlainTextMessage) m; |
|
133 writer.write(plaintxt.cleanText); |
|
134 if (plaintxt.versions != null && plaintxt.versions.size() > 0) { |
|
135 writer.write(" \\t \\t\\t\\t\\t \\t \\t \\t "); |
|
136 for (int version : plaintxt.versions) { |
|
137 if (version == 1) |
|
138 writer.write(" \\t\\t \\t "); |
|
139 |
|
140 if (version == 2) |
|
141 writer.write(" \\t \\t \\t "); |
|
142 } |
|
143 } |
|
144 break; |
|
145 case AbstractMessage.MESSAGE_QUERY: |
|
146 QueryMessage query = (QueryMessage) m; |
|
147 if (query.versions.size() == 1 && query.versions.get(0) == 1) { |
|
148 writer.write(SerializationConstants.HEAD_QUERY_Q); |
|
149 } else { |
|
150 writer.write(SerializationConstants.HEAD_QUERY_V); |
|
151 for (int version : query.versions) |
|
152 writer.write(String.valueOf(version)); |
|
153 |
|
154 writer.write(SerializationConstants.HEAD_QUERY_Q); |
|
155 } |
|
156 break; |
|
157 case AbstractEncodedMessage.MESSAGE_DHKEY: |
|
158 case AbstractEncodedMessage.MESSAGE_REVEALSIG: |
|
159 case AbstractEncodedMessage.MESSAGE_SIGNATURE: |
|
160 case AbstractEncodedMessage.MESSAGE_DH_COMMIT: |
|
161 case AbstractEncodedMessage.MESSAGE_DATA: |
|
162 ByteArrayOutputStream o = new ByteArrayOutputStream(); |
|
163 OtrOutputStream s = new OtrOutputStream(o); |
|
164 |
|
165 switch (m.messageType) { |
|
166 case AbstractEncodedMessage.MESSAGE_DHKEY: |
|
167 DHKeyMessage dhkey = (DHKeyMessage) m; |
|
168 s.writeShort(dhkey.protocolVersion); |
|
169 s.writeByte(dhkey.messageType); |
|
170 s.writeDHPublicKey(dhkey.dhPublicKey); |
|
171 break; |
|
172 case AbstractEncodedMessage.MESSAGE_REVEALSIG: |
|
173 RevealSignatureMessage revealsig = (RevealSignatureMessage) m; |
|
174 s.writeShort(revealsig.protocolVersion); |
|
175 s.writeByte(revealsig.messageType); |
|
176 s.writeData(revealsig.revealedKey); |
|
177 s.writeData(revealsig.xEncrypted); |
|
178 s.writeMac(revealsig.xEncryptedMAC); |
|
179 break; |
|
180 case AbstractEncodedMessage.MESSAGE_SIGNATURE: |
|
181 SignatureMessage sig = (SignatureMessage) m; |
|
182 s.writeShort(sig.protocolVersion); |
|
183 s.writeByte(sig.messageType); |
|
184 s.writeData(sig.xEncrypted); |
|
185 s.writeMac(sig.xEncryptedMAC); |
|
186 break; |
|
187 case AbstractEncodedMessage.MESSAGE_DH_COMMIT: |
|
188 DHCommitMessage dhcommit = (DHCommitMessage) m; |
|
189 s.writeShort(dhcommit.protocolVersion); |
|
190 s.writeByte(dhcommit.messageType); |
|
191 s.writeData(dhcommit.dhPublicKeyEncrypted); |
|
192 s.writeData(dhcommit.dhPublicKeyHash); |
|
193 break; |
|
194 case AbstractEncodedMessage.MESSAGE_DATA: |
|
195 DataMessage data = (DataMessage) m; |
|
196 s.writeShort(data.protocolVersion); |
|
197 s.writeByte(data.messageType); |
|
198 s.writeByte(data.flags); |
|
199 s.writeInt(data.senderKeyID); |
|
200 s.writeInt(data.recipientKeyID); |
|
201 s.writeDHPublicKey(data.nextDH); |
|
202 s.writeCtr(data.ctr); |
|
203 s.writeData(data.encryptedMessage); |
|
204 s.writeMac(data.mac); |
|
205 s.writeData(data.oldMACKeys); |
|
206 break; |
|
207 } |
|
208 |
|
209 writer.write(SerializationConstants.HEAD_ENCODED); |
|
210 writer.write(new String(Base64.encode(o.toByteArray()))); |
|
211 writer.write("."); |
|
212 break; |
|
213 default: |
|
214 throw new IOException("Illegal message type."); |
|
215 } |
|
216 |
|
217 return writer.toString(); |
|
218 } |
|
219 |
|
220 static final Pattern patternWhitespace = Pattern |
|
221 .compile("( \\t \\t\\t\\t\\t \\t \\t \\t )( \\t\\t \\t )?( \\t \\t \\t )?"); |
|
222 |
|
223 public static AbstractMessage toMessage(String s) throws IOException { |
|
224 if (s == null || s.length() <= 1) |
|
225 return null; |
|
226 |
|
227 if (s.indexOf(SerializationConstants.HEAD) != 0 |
|
228 || s.length() <= SerializationConstants.HEAD.length()) { |
|
229 // Try to detect whitespace tag. |
|
230 final Matcher matcher = patternWhitespace.matcher(s); |
|
231 |
|
232 boolean v1 = false; |
|
233 boolean v2 = false; |
|
234 while (matcher.find()) { |
|
235 if (!v1 && matcher.start(2) > -1) |
|
236 v1 = true; |
|
237 |
|
238 if (!v2 && matcher.start(3) > -1) |
|
239 v2 = true; |
|
240 |
|
241 if (v1 && v2) |
|
242 break; |
|
243 } |
|
244 |
|
245 String cleanText = matcher.replaceAll(""); |
|
246 List<Integer> versions; |
|
247 if (v1 && v2) { |
|
248 versions = new Vector<Integer>(2); |
|
249 versions.add(0, 1); |
|
250 versions.add(0, 2); |
|
251 } else if (v1) { |
|
252 versions = new Vector<Integer>(1); |
|
253 versions.add(0, 1); |
|
254 } else if (v2) { |
|
255 versions = new Vector<Integer>(1); |
|
256 versions.add(2); |
|
257 } else |
|
258 versions = null; |
|
259 |
|
260 return new PlainTextMessage(versions, cleanText); |
|
261 } else { |
|
262 char contentType = s.charAt(SerializationConstants.HEAD.length()); |
|
263 String content = s |
|
264 .substring(SerializationConstants.HEAD.length() + 1); |
|
265 switch (contentType) { |
|
266 case SerializationConstants.HEAD_ENCODED: |
|
267 ByteArrayInputStream bin = new ByteArrayInputStream(Base64 |
|
268 .decode(content.getBytes())); |
|
269 OtrInputStream otr = new OtrInputStream(bin); |
|
270 // We have an encoded message. |
|
271 int protocolVersion = otr.readShort(); |
|
272 int messageType = otr.readByte(); |
|
273 switch (messageType) { |
|
274 case AbstractEncodedMessage.MESSAGE_DATA: |
|
275 int flags = otr.readByte(); |
|
276 int senderKeyID = otr.readInt(); |
|
277 int recipientKeyID = otr.readInt(); |
|
278 DHPublicKey nextDH = otr.readDHPublicKey(); |
|
279 byte[] ctr = otr.readCtr(); |
|
280 byte[] encryptedMessage = otr.readData(); |
|
281 byte[] mac = otr.readMac(); |
|
282 byte[] oldMacKeys = otr.readMac(); |
|
283 return new DataMessage(protocolVersion, flags, senderKeyID, |
|
284 recipientKeyID, nextDH, ctr, encryptedMessage, mac, |
|
285 oldMacKeys); |
|
286 case AbstractEncodedMessage.MESSAGE_DH_COMMIT: |
|
287 byte[] dhPublicKeyEncrypted = otr.readData(); |
|
288 byte[] dhPublicKeyHash = otr.readData(); |
|
289 return new DHCommitMessage(protocolVersion, |
|
290 dhPublicKeyHash, dhPublicKeyEncrypted); |
|
291 case AbstractEncodedMessage.MESSAGE_DHKEY: |
|
292 DHPublicKey dhPublicKey = otr.readDHPublicKey(); |
|
293 return new DHKeyMessage(protocolVersion, dhPublicKey); |
|
294 case AbstractEncodedMessage.MESSAGE_REVEALSIG: { |
|
295 byte[] revealedKey = otr.readData(); |
|
296 byte[] xEncrypted = otr.readData(); |
|
297 byte[] xEncryptedMac = otr.readMac(); |
|
298 return new RevealSignatureMessage(protocolVersion, |
|
299 xEncrypted, xEncryptedMac, revealedKey); |
|
300 } |
|
301 case AbstractEncodedMessage.MESSAGE_SIGNATURE: { |
|
302 byte[] xEncryted = otr.readData(); |
|
303 byte[] xEncryptedMac = otr.readMac(); |
|
304 return new SignatureMessage(protocolVersion, xEncryted, |
|
305 xEncryptedMac); |
|
306 } |
|
307 default: |
|
308 throw new IOException("Illegal message type."); |
|
309 } |
|
310 case SerializationConstants.HEAD_ERROR: |
|
311 return new ErrorMessage(AbstractMessage.MESSAGE_ERROR, content); |
|
312 case SerializationConstants.HEAD_QUERY_V: |
|
313 case SerializationConstants.HEAD_QUERY_Q: |
|
314 List<Integer> versions = new Vector<Integer>(); |
|
315 String versionString = null; |
|
316 if (SerializationConstants.HEAD_QUERY_Q == contentType) { |
|
317 versions.add(1); |
|
318 if (content.charAt(0) == 'v') { |
|
319 versionString = content.substring(1, content |
|
320 .indexOf('?')); |
|
321 } |
|
322 } else if (SerializationConstants.HEAD_QUERY_V == contentType) { |
|
323 versionString = content.substring(0, content.indexOf('?')); |
|
324 } |
|
325 |
|
326 if (versionString != null) { |
|
327 StringReader sr = new StringReader(versionString); |
|
328 int c; |
|
329 while ((c = sr.read()) != -1) |
|
330 if (!versions.contains(c)) |
|
331 versions.add(Integer.parseInt(String |
|
332 .valueOf((char) c))); |
|
333 } |
|
334 QueryMessage query = new QueryMessage(versions); |
|
335 return query; |
|
336 default: |
|
337 throw new IOException("Uknown message type."); |
|
338 } |
|
339 } |
|
340 } |
|
341 } |