1package eu.siacs.conversations.crypto;
2
3import java.math.BigInteger;
4import java.security.KeyFactory;
5import java.security.KeyPair;
6import java.security.KeyPairGenerator;
7import java.security.NoSuchAlgorithmException;
8import java.security.PrivateKey;
9import java.security.PublicKey;
10import java.security.spec.DSAPrivateKeySpec;
11import java.security.spec.DSAPublicKeySpec;
12import java.security.spec.InvalidKeySpecException;
13
14import org.json.JSONException;
15import org.json.JSONObject;
16
17import android.util.Log;
18
19import eu.siacs.conversations.Config;
20import eu.siacs.conversations.entities.Account;
21import eu.siacs.conversations.entities.Conversation;
22import eu.siacs.conversations.services.XmppConnectionService;
23import eu.siacs.conversations.xmpp.jid.InvalidJidException;
24import eu.siacs.conversations.xmpp.jid.Jid;
25import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
26
27import net.java.otr4j.OtrEngineHost;
28import net.java.otr4j.OtrException;
29import net.java.otr4j.OtrPolicy;
30import net.java.otr4j.OtrPolicyImpl;
31import net.java.otr4j.crypto.OtrCryptoEngineImpl;
32import net.java.otr4j.crypto.OtrCryptoException;
33import net.java.otr4j.session.InstanceTag;
34import net.java.otr4j.session.SessionID;
35
36public class OtrEngine implements OtrEngineHost {
37
38 private Account account;
39 private OtrPolicy otrPolicy;
40 private KeyPair keyPair;
41 private XmppConnectionService mXmppConnectionService;
42
43 public OtrEngine(XmppConnectionService service, Account account) {
44 this.account = account;
45 this.otrPolicy = new OtrPolicyImpl();
46 this.otrPolicy.setAllowV1(false);
47 this.otrPolicy.setAllowV2(true);
48 this.otrPolicy.setAllowV3(true);
49 this.keyPair = loadKey(account.getKeys());
50 this.mXmppConnectionService = service;
51 }
52
53 private KeyPair loadKey(JSONObject keys) {
54 if (keys == null) {
55 return null;
56 }
57 try {
58 BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
59 BigInteger y = new BigInteger(keys.getString("otr_y"), 16);
60 BigInteger p = new BigInteger(keys.getString("otr_p"), 16);
61 BigInteger q = new BigInteger(keys.getString("otr_q"), 16);
62 BigInteger g = new BigInteger(keys.getString("otr_g"), 16);
63 KeyFactory keyFactory = KeyFactory.getInstance("DSA");
64 DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
65 DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
66 PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
67 PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
68 return new KeyPair(publicKey, privateKey);
69 } catch (JSONException e) {
70 return null;
71 } catch (NoSuchAlgorithmException e) {
72 return null;
73 } catch (InvalidKeySpecException e) {
74 return null;
75 }
76 }
77
78 private void saveKey() {
79 PublicKey publicKey = keyPair.getPublic();
80 PrivateKey privateKey = keyPair.getPrivate();
81 KeyFactory keyFactory;
82 try {
83 keyFactory = KeyFactory.getInstance("DSA");
84 DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(
85 privateKey, DSAPrivateKeySpec.class);
86 DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey,
87 DSAPublicKeySpec.class);
88 this.account.setKey("otr_x", privateKeySpec.getX().toString(16));
89 this.account.setKey("otr_g", privateKeySpec.getG().toString(16));
90 this.account.setKey("otr_p", privateKeySpec.getP().toString(16));
91 this.account.setKey("otr_q", privateKeySpec.getQ().toString(16));
92 this.account.setKey("otr_y", publicKeySpec.getY().toString(16));
93 } catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
94 e.printStackTrace();
95 }
96
97 }
98
99 @Override
100 public void askForSecret(SessionID id, InstanceTag instanceTag, String question) {
101 try {
102 final Jid jid = Jid.fromSessionID(id);
103 Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
104 if (conversation!=null) {
105 conversation.smp().hint = question;
106 conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED;
107 mXmppConnectionService.updateConversationUi();
108 }
109 } catch (InvalidJidException e) {
110 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": smp in invalid session "+id.toString());
111 }
112 }
113
114 @Override
115 public void finishedSessionMessage(SessionID arg0, String arg1)
116 throws OtrException {
117
118 }
119
120 @Override
121 public String getFallbackMessage(SessionID arg0) {
122 return "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that";
123 }
124
125 @Override
126 public byte[] getLocalFingerprintRaw(SessionID arg0) {
127 try {
128 return new OtrCryptoEngineImpl().getFingerprintRaw(getPublicKey());
129 } catch (OtrCryptoException e) {
130 return null;
131 }
132 }
133
134 public PublicKey getPublicKey() {
135 if (this.keyPair == null) {
136 return null;
137 }
138 return this.keyPair.getPublic();
139 }
140
141 @Override
142 public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
143 if (this.keyPair == null) {
144 KeyPairGenerator kg;
145 try {
146 kg = KeyPairGenerator.getInstance("DSA");
147 this.keyPair = kg.genKeyPair();
148 this.saveKey();
149 mXmppConnectionService.databaseBackend.updateAccount(account);
150 } catch (NoSuchAlgorithmException e) {
151 Log.d(Config.LOGTAG,
152 "error generating key pair " + e.getMessage());
153 }
154 }
155 return this.keyPair;
156 }
157
158 @Override
159 public String getReplyForUnreadableMessage(SessionID arg0) {
160 // TODO Auto-generated method stub
161 return null;
162 }
163
164 @Override
165 public OtrPolicy getSessionPolicy(SessionID arg0) {
166 return otrPolicy;
167 }
168
169 @Override
170 public void injectMessage(SessionID session, String body)
171 throws OtrException {
172 MessagePacket packet = new MessagePacket();
173 packet.setFrom(account.getJid());
174 if (session.getUserID().isEmpty()) {
175 packet.setAttribute("to", session.getAccountID());
176 } else {
177 packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID());
178 }
179 packet.setBody(body);
180 packet.addChild("private", "urn:xmpp:carbons:2");
181 packet.addChild("no-copy", "urn:xmpp:hints");
182 packet.setType(MessagePacket.TYPE_CHAT);
183 account.getXmppConnection().sendMessagePacket(packet);
184 }
185
186 @Override
187 public void messageFromAnotherInstanceReceived(SessionID id) {
188 Log.d(Config.LOGTAG,
189 "unreadable message received from " + id.getAccountID());
190 }
191
192 @Override
193 public void multipleInstancesDetected(SessionID arg0) {
194 // TODO Auto-generated method stub
195
196 }
197
198 @Override
199 public void requireEncryptedMessage(SessionID arg0, String arg1)
200 throws OtrException {
201 // TODO Auto-generated method stub
202
203 }
204
205 @Override
206 public void showError(SessionID arg0, String arg1) throws OtrException {
207 Log.d(Config.LOGTAG,"show error");
208 }
209
210 @Override
211 public void smpAborted(SessionID id) throws OtrException {
212 setSmpStatus(id, Conversation.Smp.STATUS_NONE);
213 }
214
215 private void setSmpStatus(SessionID id, int status) {
216 try {
217 final Jid jid = Jid.fromSessionID(id);
218 Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
219 if (conversation!=null) {
220 conversation.smp().status = status;
221 mXmppConnectionService.updateConversationUi();
222 }
223 } catch (final InvalidJidException ignored) {
224
225 }
226 }
227
228 @Override
229 public void smpError(SessionID id, int arg1, boolean arg2)
230 throws OtrException {
231 setSmpStatus(id, Conversation.Smp.STATUS_NONE);
232 }
233
234 @Override
235 public void unencryptedMessageReceived(SessionID arg0, String arg1)
236 throws OtrException {
237 throw new OtrException(new Exception("unencrypted message received"));
238 }
239
240 @Override
241 public void unreadableMessageReceived(SessionID arg0) throws OtrException {
242 Log.d(Config.LOGTAG,"unreadable message received");
243 throw new OtrException(new Exception("unreadable message received"));
244 }
245
246 @Override
247 public void unverify(SessionID id, String arg1) {
248 setSmpStatus(id, Conversation.Smp.STATUS_FAILED);
249 }
250
251 @Override
252 public void verify(SessionID id, String arg1, boolean arg2) {
253 try {
254 final Jid jid = Jid.fromSessionID(id);
255 Conversation conversation = this.mXmppConnectionService.find(this.account,jid);
256 if (conversation!=null) {
257 conversation.smp().hint = null;
258 conversation.smp().status = Conversation.Smp.STATUS_VERIFIED;
259 conversation.verifyOtrFingerprint();
260 mXmppConnectionService.updateConversationUi();
261 mXmppConnectionService.syncRosterToDisk(conversation.getAccount());
262 }
263 } catch (final InvalidJidException ignored) {
264 }
265 }
266
267}