|
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.session; |
|
8 |
|
9 import java.io.IOException; |
|
10 import java.math.BigInteger; |
|
11 import java.nio.ByteBuffer; |
|
12 import java.security.KeyPair; |
|
13 import java.security.PublicKey; |
|
14 import java.util.Arrays; |
|
15 import java.util.Random; |
|
16 import java.util.Vector; |
|
17 import java.util.logging.Logger; |
|
18 |
|
19 import javax.crypto.interfaces.DHPublicKey; |
|
20 |
|
21 import net.java.otr4j.OtrException; |
|
22 import net.java.otr4j.crypto.OtrCryptoEngine; |
|
23 import net.java.otr4j.crypto.OtrCryptoEngineImpl; |
|
24 import net.java.otr4j.io.SerializationUtils; |
|
25 import net.java.otr4j.io.messages.DHCommitMessage; |
|
26 import net.java.otr4j.io.messages.DHKeyMessage; |
|
27 import net.java.otr4j.io.messages.AbstractEncodedMessage; |
|
28 import net.java.otr4j.io.messages.AbstractMessage; |
|
29 import net.java.otr4j.io.messages.SignatureM; |
|
30 import net.java.otr4j.io.messages.SignatureX; |
|
31 import net.java.otr4j.io.messages.QueryMessage; |
|
32 import net.java.otr4j.io.messages.RevealSignatureMessage; |
|
33 import net.java.otr4j.io.messages.SignatureMessage; |
|
34 |
|
35 /** |
|
36 * |
|
37 * @author George Politis |
|
38 */ |
|
39 class AuthContextImpl implements AuthContext { |
|
40 |
|
41 public AuthContextImpl(Session session) { |
|
42 this.setSession(session); |
|
43 this.reset(); |
|
44 } |
|
45 |
|
46 private Session session; |
|
47 |
|
48 private int authenticationState; |
|
49 private byte[] r; |
|
50 |
|
51 private DHPublicKey remoteDHPublicKey; |
|
52 private byte[] remoteDHPublicKeyEncrypted; |
|
53 private byte[] remoteDHPublicKeyHash; |
|
54 |
|
55 private KeyPair localDHKeyPair; |
|
56 private int localDHPrivateKeyID; |
|
57 private byte[] localDHPublicKeyBytes; |
|
58 private byte[] localDHPublicKeyHash; |
|
59 private byte[] localDHPublicKeyEncrypted; |
|
60 |
|
61 private BigInteger s; |
|
62 private byte[] c; |
|
63 private byte[] m1; |
|
64 private byte[] m2; |
|
65 private byte[] cp; |
|
66 private byte[] m1p; |
|
67 private byte[] m2p; |
|
68 |
|
69 private KeyPair localLongTermKeyPair; |
|
70 private Boolean isSecure = false; |
|
71 private int protocolVersion; |
|
72 |
|
73 private int getProtocolVersion() { |
|
74 return this.protocolVersion; |
|
75 } |
|
76 |
|
77 private void setProtocolVersion(int protoVersion) { |
|
78 this.protocolVersion = protoVersion; |
|
79 } |
|
80 |
|
81 private static Logger logger = Logger.getLogger(AuthContextImpl.class |
|
82 .getName()); |
|
83 |
|
84 class MessageFactory { |
|
85 |
|
86 private QueryMessage getQueryMessage() { |
|
87 Vector<Integer> versions = new Vector<Integer>(); |
|
88 versions.add(2); |
|
89 return new QueryMessage(versions); |
|
90 } |
|
91 |
|
92 private DHCommitMessage getDHCommitMessage() throws OtrException { |
|
93 return new DHCommitMessage(getProtocolVersion(), |
|
94 getLocalDHPublicKeyHash(), getLocalDHPublicKeyEncrypted()); |
|
95 } |
|
96 |
|
97 private DHKeyMessage getDHKeyMessage() throws OtrException { |
|
98 return new DHKeyMessage(getProtocolVersion(), |
|
99 (DHPublicKey) getLocalDHKeyPair().getPublic()); |
|
100 } |
|
101 |
|
102 private RevealSignatureMessage getRevealSignatureMessage() |
|
103 throws OtrException { |
|
104 try { |
|
105 SignatureM m = new SignatureM((DHPublicKey) getLocalDHKeyPair() |
|
106 .getPublic(), getRemoteDHPublicKey(), |
|
107 getLocalLongTermKeyPair().getPublic(), |
|
108 getLocalDHKeyPairID()); |
|
109 |
|
110 OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); |
|
111 byte[] mhash = otrCryptoEngine.sha256Hmac(SerializationUtils |
|
112 .toByteArray(m), getM1()); |
|
113 byte[] signature = otrCryptoEngine.sign(mhash, |
|
114 getLocalLongTermKeyPair().getPrivate()); |
|
115 |
|
116 SignatureX mysteriousX = new SignatureX( |
|
117 getLocalLongTermKeyPair().getPublic(), |
|
118 getLocalDHKeyPairID(), signature); |
|
119 byte[] xEncrypted = otrCryptoEngine.aesEncrypt(getC(), null, |
|
120 SerializationUtils.toByteArray(mysteriousX)); |
|
121 |
|
122 byte[] tmp = SerializationUtils.writeData(xEncrypted); |
|
123 |
|
124 byte[] xEncryptedHash = otrCryptoEngine.sha256Hmac160(tmp, |
|
125 getM2()); |
|
126 return new RevealSignatureMessage(getProtocolVersion(), |
|
127 xEncrypted, xEncryptedHash, getR()); |
|
128 } catch (IOException e) { |
|
129 throw new OtrException(e); |
|
130 } |
|
131 } |
|
132 |
|
133 private SignatureMessage getSignatureMessage() throws OtrException { |
|
134 SignatureM m = new SignatureM((DHPublicKey) getLocalDHKeyPair() |
|
135 .getPublic(), getRemoteDHPublicKey(), |
|
136 getLocalLongTermKeyPair().getPublic(), |
|
137 getLocalDHKeyPairID()); |
|
138 |
|
139 OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); |
|
140 byte[] mhash; |
|
141 try { |
|
142 mhash = otrCryptoEngine.sha256Hmac(SerializationUtils |
|
143 .toByteArray(m), getM1p()); |
|
144 } catch (IOException e) { |
|
145 throw new OtrException(e); |
|
146 } |
|
147 |
|
148 byte[] signature = otrCryptoEngine.sign(mhash, |
|
149 getLocalLongTermKeyPair().getPrivate()); |
|
150 |
|
151 SignatureX mysteriousX = new SignatureX(getLocalLongTermKeyPair() |
|
152 .getPublic(), getLocalDHKeyPairID(), signature); |
|
153 |
|
154 byte[] xEncrypted; |
|
155 try { |
|
156 xEncrypted = otrCryptoEngine.aesEncrypt(getCp(), null, |
|
157 SerializationUtils.toByteArray(mysteriousX)); |
|
158 byte[] tmp = SerializationUtils.writeData(xEncrypted); |
|
159 byte[] xEncryptedHash = otrCryptoEngine.sha256Hmac160(tmp, |
|
160 getM2p()); |
|
161 return new SignatureMessage(getProtocolVersion(), xEncrypted, |
|
162 xEncryptedHash); |
|
163 } catch (IOException e) { |
|
164 throw new OtrException(e); |
|
165 } |
|
166 } |
|
167 } |
|
168 |
|
169 private MessageFactory messageFactory = new MessageFactory(); |
|
170 |
|
171 public void reset() { |
|
172 logger.finest("Resetting authentication state."); |
|
173 authenticationState = AuthContext.NONE; |
|
174 r = null; |
|
175 |
|
176 remoteDHPublicKey = null; |
|
177 remoteDHPublicKeyEncrypted = null; |
|
178 remoteDHPublicKeyHash = null; |
|
179 |
|
180 localDHKeyPair = null; |
|
181 localDHPrivateKeyID = 1; |
|
182 localDHPublicKeyBytes = null; |
|
183 localDHPublicKeyHash = null; |
|
184 localDHPublicKeyEncrypted = null; |
|
185 |
|
186 s = null; |
|
187 c = m1 = m2 = cp = m1p = m2p = null; |
|
188 |
|
189 localLongTermKeyPair = null; |
|
190 protocolVersion = 0; |
|
191 setIsSecure(false); |
|
192 } |
|
193 |
|
194 private void setIsSecure(Boolean isSecure) { |
|
195 this.isSecure = isSecure; |
|
196 } |
|
197 |
|
198 public boolean getIsSecure() { |
|
199 return isSecure; |
|
200 } |
|
201 |
|
202 private void setAuthenticationState(int authenticationState) { |
|
203 this.authenticationState = authenticationState; |
|
204 } |
|
205 |
|
206 private int getAuthenticationState() { |
|
207 return authenticationState; |
|
208 } |
|
209 |
|
210 private byte[] getR() { |
|
211 if (r == null) { |
|
212 logger.finest("Picking random key r."); |
|
213 r = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; |
|
214 new Random().nextBytes(r); |
|
215 } |
|
216 return r; |
|
217 } |
|
218 |
|
219 private void setRemoteDHPublicKey(DHPublicKey dhPublicKey) { |
|
220 // Verifies that Alice's gy is a legal value (2 <= gy <= modulus-2) |
|
221 if (dhPublicKey.getY().compareTo(OtrCryptoEngine.MODULUS_MINUS_TWO) > 0) { |
|
222 throw new IllegalArgumentException( |
|
223 "Illegal D-H Public Key value, Ignoring message."); |
|
224 } else if (dhPublicKey.getY().compareTo(OtrCryptoEngine.BIGINTEGER_TWO) < 0) { |
|
225 throw new IllegalArgumentException( |
|
226 "Illegal D-H Public Key value, Ignoring message."); |
|
227 } |
|
228 logger.finest("Received D-H Public Key is a legal value."); |
|
229 |
|
230 this.remoteDHPublicKey = dhPublicKey; |
|
231 } |
|
232 |
|
233 public DHPublicKey getRemoteDHPublicKey() { |
|
234 return remoteDHPublicKey; |
|
235 } |
|
236 |
|
237 private void setRemoteDHPublicKeyEncrypted(byte[] remoteDHPublicKeyEncrypted) { |
|
238 logger.finest("Storing encrypted remote public key."); |
|
239 this.remoteDHPublicKeyEncrypted = remoteDHPublicKeyEncrypted; |
|
240 } |
|
241 |
|
242 private byte[] getRemoteDHPublicKeyEncrypted() { |
|
243 return remoteDHPublicKeyEncrypted; |
|
244 } |
|
245 |
|
246 private void setRemoteDHPublicKeyHash(byte[] remoteDHPublicKeyHash) { |
|
247 logger.finest("Storing encrypted remote public key hash."); |
|
248 this.remoteDHPublicKeyHash = remoteDHPublicKeyHash; |
|
249 } |
|
250 |
|
251 private byte[] getRemoteDHPublicKeyHash() { |
|
252 return remoteDHPublicKeyHash; |
|
253 } |
|
254 |
|
255 public KeyPair getLocalDHKeyPair() throws OtrException { |
|
256 if (localDHKeyPair == null) { |
|
257 localDHKeyPair = new OtrCryptoEngineImpl().generateDHKeyPair(); |
|
258 logger.finest("Generated local D-H key pair."); |
|
259 } |
|
260 return localDHKeyPair; |
|
261 } |
|
262 |
|
263 private int getLocalDHKeyPairID() { |
|
264 return localDHPrivateKeyID; |
|
265 } |
|
266 |
|
267 private byte[] getLocalDHPublicKeyHash() throws OtrException { |
|
268 if (localDHPublicKeyHash == null) { |
|
269 localDHPublicKeyHash = new OtrCryptoEngineImpl() |
|
270 .sha256Hash(getLocalDHPublicKeyBytes()); |
|
271 logger.finest("Hashed local D-H public key."); |
|
272 } |
|
273 return localDHPublicKeyHash; |
|
274 } |
|
275 |
|
276 private byte[] getLocalDHPublicKeyEncrypted() throws OtrException { |
|
277 if (localDHPublicKeyEncrypted == null) { |
|
278 localDHPublicKeyEncrypted = new OtrCryptoEngineImpl().aesEncrypt( |
|
279 getR(), null, getLocalDHPublicKeyBytes()); |
|
280 logger.finest("Encrypted our D-H public key."); |
|
281 } |
|
282 return localDHPublicKeyEncrypted; |
|
283 } |
|
284 |
|
285 public BigInteger getS() throws OtrException { |
|
286 if (s == null) { |
|
287 s = new OtrCryptoEngineImpl().generateSecret(this |
|
288 .getLocalDHKeyPair().getPrivate(), this |
|
289 .getRemoteDHPublicKey()); |
|
290 logger.finest("Generated shared secret."); |
|
291 } |
|
292 return s; |
|
293 } |
|
294 |
|
295 private byte[] getC() throws OtrException { |
|
296 if (c != null) |
|
297 return c; |
|
298 |
|
299 byte[] h2 = h2(C_START); |
|
300 ByteBuffer buff = ByteBuffer.wrap(h2); |
|
301 this.c = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; |
|
302 buff.get(this.c); |
|
303 logger.finest("Computed c."); |
|
304 return c; |
|
305 |
|
306 } |
|
307 |
|
308 private byte[] getM1() throws OtrException { |
|
309 if (m1 != null) |
|
310 return m1; |
|
311 |
|
312 byte[] h2 = h2(M1_START); |
|
313 ByteBuffer buff = ByteBuffer.wrap(h2); |
|
314 byte[] m1 = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; |
|
315 buff.get(m1); |
|
316 logger.finest("Computed m1."); |
|
317 this.m1 = m1; |
|
318 return m1; |
|
319 } |
|
320 |
|
321 private byte[] getM2() throws OtrException { |
|
322 if (m2 != null) |
|
323 return m2; |
|
324 |
|
325 byte[] h2 = h2(M2_START); |
|
326 ByteBuffer buff = ByteBuffer.wrap(h2); |
|
327 byte[] m2 = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; |
|
328 buff.get(m2); |
|
329 logger.finest("Computed m2."); |
|
330 this.m2 = m2; |
|
331 return m2; |
|
332 } |
|
333 |
|
334 private byte[] getCp() throws OtrException { |
|
335 if (cp != null) |
|
336 return cp; |
|
337 |
|
338 byte[] h2 = h2(C_START); |
|
339 ByteBuffer buff = ByteBuffer.wrap(h2); |
|
340 byte[] cp = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH]; |
|
341 buff.position(OtrCryptoEngine.AES_KEY_BYTE_LENGTH); |
|
342 buff.get(cp); |
|
343 logger.finest("Computed c'."); |
|
344 this.cp = cp; |
|
345 return cp; |
|
346 } |
|
347 |
|
348 private byte[] getM1p() throws OtrException { |
|
349 if (m1p != null) |
|
350 return m1p; |
|
351 |
|
352 byte[] h2 = h2(M1p_START); |
|
353 ByteBuffer buff = ByteBuffer.wrap(h2); |
|
354 byte[] m1p = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; |
|
355 buff.get(m1p); |
|
356 this.m1p = m1p; |
|
357 logger.finest("Computed m1'."); |
|
358 return m1p; |
|
359 } |
|
360 |
|
361 private byte[] getM2p() throws OtrException { |
|
362 if (m2p != null) |
|
363 return m2p; |
|
364 |
|
365 byte[] h2 = h2(M2p_START); |
|
366 ByteBuffer buff = ByteBuffer.wrap(h2); |
|
367 byte[] m2p = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH]; |
|
368 buff.get(m2p); |
|
369 this.m2p = m2p; |
|
370 logger.finest("Computed m2'."); |
|
371 return m2p; |
|
372 } |
|
373 |
|
374 public KeyPair getLocalLongTermKeyPair() { |
|
375 if (localLongTermKeyPair == null) { |
|
376 localLongTermKeyPair = getSession().getLocalKeyPair(); |
|
377 } |
|
378 return localLongTermKeyPair; |
|
379 } |
|
380 |
|
381 private byte[] h2(byte b) throws OtrException { |
|
382 byte[] secbytes; |
|
383 try { |
|
384 secbytes = SerializationUtils.writeMpi(getS()); |
|
385 } catch (IOException e) { |
|
386 throw new OtrException(e); |
|
387 } |
|
388 |
|
389 int len = secbytes.length + 1; |
|
390 ByteBuffer buff = ByteBuffer.allocate(len); |
|
391 buff.put(b); |
|
392 buff.put(secbytes); |
|
393 byte[] sdata = buff.array(); |
|
394 return new OtrCryptoEngineImpl().sha256Hash(sdata); |
|
395 } |
|
396 |
|
397 private byte[] getLocalDHPublicKeyBytes() throws OtrException { |
|
398 if (localDHPublicKeyBytes == null) { |
|
399 try { |
|
400 this.localDHPublicKeyBytes = SerializationUtils |
|
401 .writeMpi(((DHPublicKey) getLocalDHKeyPair() |
|
402 .getPublic()).getY()); |
|
403 |
|
404 } catch (IOException e) { |
|
405 throw new OtrException(e); |
|
406 } |
|
407 |
|
408 } |
|
409 return localDHPublicKeyBytes; |
|
410 } |
|
411 |
|
412 public void handleReceivingMessage(AbstractMessage m) throws OtrException { |
|
413 |
|
414 switch (m.messageType) { |
|
415 case AbstractEncodedMessage.MESSAGE_DH_COMMIT: |
|
416 handleDHCommitMessage((DHCommitMessage) m); |
|
417 break; |
|
418 case AbstractEncodedMessage.MESSAGE_DHKEY: |
|
419 handleDHKeyMessage((DHKeyMessage) m); |
|
420 break; |
|
421 case AbstractEncodedMessage.MESSAGE_REVEALSIG: |
|
422 handleRevealSignatureMessage((RevealSignatureMessage) m); |
|
423 break; |
|
424 case AbstractEncodedMessage.MESSAGE_SIGNATURE: |
|
425 handleSignatureMessage((SignatureMessage) m); |
|
426 break; |
|
427 default: |
|
428 throw new UnsupportedOperationException(); |
|
429 } |
|
430 } |
|
431 |
|
432 private void handleSignatureMessage(SignatureMessage m) throws OtrException { |
|
433 Session session = getSession(); |
|
434 SessionID sessionID = session.getSessionID(); |
|
435 logger.finest(sessionID.getAccountID() |
|
436 + " received a signature message from " + sessionID.getUserID() |
|
437 + " throught " + sessionID.getProtocolName() + "."); |
|
438 if (!session.getSessionPolicy().getAllowV2()) { |
|
439 logger.finest("Policy does not allow OTRv2, ignoring message."); |
|
440 return; |
|
441 } |
|
442 |
|
443 switch (this.getAuthenticationState()) { |
|
444 case AWAITING_SIG: |
|
445 // Verify MAC. |
|
446 if (!m.verify(this.getM2p())) { |
|
447 logger |
|
448 .finest("Signature MACs are not equal, ignoring message."); |
|
449 return; |
|
450 } |
|
451 |
|
452 // Decrypt X. |
|
453 byte[] remoteXDecrypted = m.decrypt(this.getCp()); |
|
454 SignatureX remoteX; |
|
455 try { |
|
456 remoteX = SerializationUtils.toMysteriousX(remoteXDecrypted); |
|
457 } catch (IOException e) { |
|
458 throw new OtrException(e); |
|
459 } |
|
460 // Compute signature. |
|
461 PublicKey remoteLongTermPublicKey = remoteX.longTermPublicKey; |
|
462 SignatureM remoteM = new SignatureM(this.getRemoteDHPublicKey(), |
|
463 (DHPublicKey) this.getLocalDHKeyPair().getPublic(), |
|
464 remoteLongTermPublicKey, remoteX.dhKeyID); |
|
465 OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); |
|
466 // Verify signature. |
|
467 byte[] signature; |
|
468 try { |
|
469 signature = otrCryptoEngine.sha256Hmac(SerializationUtils |
|
470 .toByteArray(remoteM), this.getM1p()); |
|
471 } catch (IOException e) { |
|
472 throw new OtrException(e); |
|
473 } |
|
474 if (!otrCryptoEngine.verify(signature, remoteLongTermPublicKey, |
|
475 remoteX.signature)) { |
|
476 logger.finest("Signature verification failed."); |
|
477 return; |
|
478 } |
|
479 |
|
480 this.setIsSecure(true); |
|
481 this.setRemoteLongTermPublicKey(remoteLongTermPublicKey); |
|
482 break; |
|
483 default: |
|
484 logger |
|
485 .finest("We were not expecting a signature, ignoring message."); |
|
486 return; |
|
487 } |
|
488 } |
|
489 |
|
490 private void handleRevealSignatureMessage(RevealSignatureMessage m) |
|
491 throws OtrException { |
|
492 Session session = getSession(); |
|
493 SessionID sessionID = session.getSessionID(); |
|
494 logger.finest(sessionID.getAccountID() |
|
495 + " received a reveal signature message from " |
|
496 + sessionID.getUserID() + " throught " |
|
497 + sessionID.getProtocolName() + "."); |
|
498 |
|
499 if (!session.getSessionPolicy().getAllowV2()) { |
|
500 logger.finest("Policy does not allow OTRv2, ignoring message."); |
|
501 return; |
|
502 } |
|
503 |
|
504 switch (this.getAuthenticationState()) { |
|
505 case AWAITING_REVEALSIG: |
|
506 // Use the received value of r to decrypt the value of gx |
|
507 // received |
|
508 // in the D-H Commit Message, and verify the hash therein. |
|
509 // Decrypt |
|
510 // the encrypted signature, and verify the signature and the |
|
511 // MACs. |
|
512 // If everything checks out: |
|
513 |
|
514 // * Reply with a Signature Message. |
|
515 // * Transition authstate to AUTHSTATE_NONE. |
|
516 // * Transition msgstate to MSGSTATE_ENCRYPTED. |
|
517 // * TODO If there is a recent stored message, encrypt it and |
|
518 // send |
|
519 // it as a Data Message. |
|
520 |
|
521 OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); |
|
522 // Uses r to decrypt the value of gx sent earlier |
|
523 byte[] remoteDHPublicKeyDecrypted = otrCryptoEngine.aesDecrypt( |
|
524 m.revealedKey, null, this.getRemoteDHPublicKeyEncrypted()); |
|
525 |
|
526 // Verifies that HASH(gx) matches the value sent earlier |
|
527 byte[] remoteDHPublicKeyHash = otrCryptoEngine |
|
528 .sha256Hash(remoteDHPublicKeyDecrypted); |
|
529 if (!Arrays.equals(remoteDHPublicKeyHash, this |
|
530 .getRemoteDHPublicKeyHash())) { |
|
531 logger.finest("Hashes don't match, ignoring message."); |
|
532 return; |
|
533 } |
|
534 |
|
535 // Verifies that Bob's gx is a legal value (2 <= gx <= |
|
536 // modulus-2) |
|
537 BigInteger remoteDHPublicKeyMpi; |
|
538 try { |
|
539 remoteDHPublicKeyMpi = SerializationUtils |
|
540 .readMpi(remoteDHPublicKeyDecrypted); |
|
541 } catch (IOException e) { |
|
542 throw new OtrException(e); |
|
543 } |
|
544 |
|
545 this.setRemoteDHPublicKey(otrCryptoEngine |
|
546 .getDHPublicKey(remoteDHPublicKeyMpi)); |
|
547 |
|
548 // Verify received Data. |
|
549 if (!m.verify(this.getM2())) { |
|
550 logger |
|
551 .finest("Signature MACs are not equal, ignoring message."); |
|
552 return; |
|
553 } |
|
554 |
|
555 // Decrypt X. |
|
556 byte[] remoteXDecrypted = m.decrypt(this.getC()); |
|
557 SignatureX remoteX; |
|
558 try { |
|
559 remoteX = SerializationUtils.toMysteriousX(remoteXDecrypted); |
|
560 } catch (IOException e) { |
|
561 throw new OtrException(e); |
|
562 } |
|
563 |
|
564 // Compute signature. |
|
565 PublicKey remoteLongTermPublicKey = remoteX.longTermPublicKey; |
|
566 SignatureM remoteM = new SignatureM(this.getRemoteDHPublicKey(), |
|
567 (DHPublicKey) this.getLocalDHKeyPair().getPublic(), |
|
568 remoteLongTermPublicKey, remoteX.dhKeyID); |
|
569 |
|
570 // Verify signature. |
|
571 byte[] signature; |
|
572 try { |
|
573 signature = otrCryptoEngine.sha256Hmac(SerializationUtils |
|
574 .toByteArray(remoteM), this.getM1()); |
|
575 } catch (IOException e) { |
|
576 throw new OtrException(e); |
|
577 } |
|
578 |
|
579 if (!otrCryptoEngine.verify(signature, remoteLongTermPublicKey, |
|
580 remoteX.signature)) { |
|
581 logger.finest("Signature verification failed."); |
|
582 return; |
|
583 } |
|
584 |
|
585 logger.finest("Signature verification succeeded."); |
|
586 |
|
587 this.setAuthenticationState(AuthContext.NONE); |
|
588 this.setIsSecure(true); |
|
589 this.setRemoteLongTermPublicKey(remoteLongTermPublicKey); |
|
590 getSession().injectMessage(messageFactory.getSignatureMessage()); |
|
591 break; |
|
592 default: |
|
593 logger.finest("Ignoring message."); |
|
594 break; |
|
595 } |
|
596 } |
|
597 |
|
598 private void handleDHKeyMessage(DHKeyMessage m) throws OtrException { |
|
599 Session session = getSession(); |
|
600 SessionID sessionID = session.getSessionID(); |
|
601 logger.finest(sessionID.getAccountID() |
|
602 + " received a D-H key message from " + sessionID.getUserID() |
|
603 + " throught " + sessionID.getProtocolName() + "."); |
|
604 |
|
605 if (!session.getSessionPolicy().getAllowV2()) { |
|
606 logger.finest("If ALLOW_V2 is not set, ignore this message."); |
|
607 return; |
|
608 } |
|
609 |
|
610 switch (this.getAuthenticationState()) { |
|
611 case AWAITING_DHKEY: |
|
612 // Reply with a Reveal Signature Message and transition |
|
613 // authstate to |
|
614 // AUTHSTATE_AWAITING_SIG |
|
615 this.setRemoteDHPublicKey(m.dhPublicKey); |
|
616 this.setAuthenticationState(AuthContext.AWAITING_SIG); |
|
617 getSession().injectMessage( |
|
618 messageFactory.getRevealSignatureMessage()); |
|
619 logger.finest("Sent Reveal Signature."); |
|
620 break; |
|
621 case AWAITING_SIG: |
|
622 |
|
623 if (m.dhPublicKey.getY().equals(this.getRemoteDHPublicKey().getY())) { |
|
624 // If this D-H Key message is the same the one you received |
|
625 // earlier (when you entered AUTHSTATE_AWAITING_SIG): |
|
626 // Retransmit |
|
627 // your Reveal Signature Message. |
|
628 getSession().injectMessage( |
|
629 messageFactory.getRevealSignatureMessage()); |
|
630 logger.finest("Resent Reveal Signature."); |
|
631 } else { |
|
632 // Otherwise: Ignore the message. |
|
633 logger.finest("Ignoring message."); |
|
634 } |
|
635 break; |
|
636 default: |
|
637 // Ignore the message |
|
638 break; |
|
639 } |
|
640 } |
|
641 |
|
642 private void handleDHCommitMessage(DHCommitMessage m) throws OtrException { |
|
643 Session session = getSession(); |
|
644 SessionID sessionID = session.getSessionID(); |
|
645 logger.finest(sessionID.getAccountID() |
|
646 + " received a D-H commit message from " |
|
647 + sessionID.getUserID() + " throught " |
|
648 + sessionID.getProtocolName() + "."); |
|
649 |
|
650 if (!session.getSessionPolicy().getAllowV2()) { |
|
651 logger.finest("ALLOW_V2 is not set, ignore this message."); |
|
652 return; |
|
653 } |
|
654 |
|
655 switch (this.getAuthenticationState()) { |
|
656 case NONE: |
|
657 // Reply with a D-H Key Message, and transition authstate to |
|
658 // AUTHSTATE_AWAITING_REVEALSIG. |
|
659 this.reset(); |
|
660 this.setProtocolVersion(2); |
|
661 this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); |
|
662 this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); |
|
663 this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG); |
|
664 getSession().injectMessage(messageFactory.getDHKeyMessage()); |
|
665 logger.finest("Sent D-H key."); |
|
666 break; |
|
667 |
|
668 case AWAITING_DHKEY: |
|
669 // This is the trickiest transition in the whole protocol. It |
|
670 // indicates that you have already sent a D-H Commit message to |
|
671 // your |
|
672 // correspondent, but that he either didn't receive it, or just |
|
673 // didn't receive it yet, and has sent you one as well. The |
|
674 // symmetry |
|
675 // will be broken by comparing the hashed gx you sent in your |
|
676 // D-H |
|
677 // Commit Message with the one you received, considered as |
|
678 // 32-byte |
|
679 // unsigned big-endian values. |
|
680 BigInteger ourHash = new BigInteger(1, this |
|
681 .getLocalDHPublicKeyHash()); |
|
682 BigInteger theirHash = new BigInteger(1, m.dhPublicKeyHash); |
|
683 |
|
684 if (theirHash.compareTo(ourHash) == -1) { |
|
685 // Ignore the incoming D-H Commit message, but resend your |
|
686 // D-H |
|
687 // Commit message. |
|
688 getSession().injectMessage(messageFactory.getDHCommitMessage()); |
|
689 logger |
|
690 .finest("Ignored the incoming D-H Commit message, but resent our D-H Commit message."); |
|
691 } else { |
|
692 // *Forget* your old gx value that you sent (encrypted) |
|
693 // earlier, |
|
694 // and pretend you're in AUTHSTATE_NONE; i.e. reply with a |
|
695 // D-H |
|
696 // Key Message, and transition authstate to |
|
697 // AUTHSTATE_AWAITING_REVEALSIG. |
|
698 this.reset(); |
|
699 this.setProtocolVersion(2); |
|
700 this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); |
|
701 this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); |
|
702 this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG); |
|
703 getSession().injectMessage(messageFactory.getDHKeyMessage()); |
|
704 logger |
|
705 .finest("Forgot our old gx value that we sent (encrypted) earlier, and pretended we're in AUTHSTATE_NONE -> Sent D-H key."); |
|
706 } |
|
707 break; |
|
708 |
|
709 case AWAITING_REVEALSIG: |
|
710 // Retransmit your D-H Key Message (the same one as you sent |
|
711 // when |
|
712 // you entered AUTHSTATE_AWAITING_REVEALSIG). Forget the old D-H |
|
713 // Commit message, and use this new one instead. |
|
714 this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); |
|
715 this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); |
|
716 getSession().injectMessage(messageFactory.getDHKeyMessage()); |
|
717 logger.finest("Sent D-H key."); |
|
718 break; |
|
719 case AWAITING_SIG: |
|
720 // Reply with a new D-H Key message, and transition authstate to |
|
721 // AUTHSTATE_AWAITING_REVEALSIG |
|
722 this.reset(); |
|
723 this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted); |
|
724 this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash); |
|
725 this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG); |
|
726 getSession().injectMessage(messageFactory.getDHKeyMessage()); |
|
727 logger.finest("Sent D-H key."); |
|
728 break; |
|
729 case V1_SETUP: |
|
730 throw new UnsupportedOperationException(); |
|
731 } |
|
732 } |
|
733 |
|
734 public void startV2Auth() throws OtrException { |
|
735 logger |
|
736 .finest("Starting Authenticated Key Exchange, sending query message"); |
|
737 getSession().injectMessage(messageFactory.getQueryMessage()); |
|
738 } |
|
739 |
|
740 public void respondV2Auth() throws OtrException { |
|
741 logger.finest("Responding to Query Message"); |
|
742 this.reset(); |
|
743 this.setProtocolVersion(2); |
|
744 this.setAuthenticationState(AuthContext.AWAITING_DHKEY); |
|
745 logger.finest("Sending D-H Commit."); |
|
746 getSession().injectMessage(messageFactory.getDHCommitMessage()); |
|
747 } |
|
748 |
|
749 private void setSession(Session session) { |
|
750 this.session = session; |
|
751 } |
|
752 |
|
753 private Session getSession() { |
|
754 return session; |
|
755 } |
|
756 |
|
757 private PublicKey remoteLongTermPublicKey; |
|
758 |
|
759 public PublicKey getRemoteLongTermPublicKey() { |
|
760 return remoteLongTermPublicKey; |
|
761 } |
|
762 |
|
763 private void setRemoteLongTermPublicKey(PublicKey pubKey) { |
|
764 this.remoteLongTermPublicKey = pubKey; |
|
765 } |
|
766 } |