1package eu.siacs.conversations.crypto;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.FileInputStream;
6import java.io.FileNotFoundException;
7import java.io.FileOutputStream;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.OutputStream;
11
12import org.openintents.openpgp.OpenPgpError;
13import org.openintents.openpgp.OpenPgpSignatureResult;
14import org.openintents.openpgp.util.OpenPgpApi;
15import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
16
17import eu.siacs.conversations.R;
18import eu.siacs.conversations.entities.Account;
19import eu.siacs.conversations.entities.Contact;
20import eu.siacs.conversations.entities.Conversation;
21import eu.siacs.conversations.entities.Message;
22import eu.siacs.conversations.services.XmppConnectionService;
23import eu.siacs.conversations.ui.UiCallback;
24import eu.siacs.conversations.xmpp.jingle.JingleFile;
25import android.app.PendingIntent;
26import android.content.Intent;
27import android.graphics.BitmapFactory;
28import android.util.Log;
29
30public class PgpEngine {
31 private OpenPgpApi api;
32 private XmppConnectionService mXmppConnectionService;
33
34 public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
35 this.api = api;
36 this.mXmppConnectionService = service;
37 }
38
39 public void decrypt(final Message message,
40 final UiCallback<Message> callback) {
41 Log.d("xmppService", "decrypting message " + message.getUuid());
42 Intent params = new Intent();
43 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
44 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
45 .getConversation().getAccount().getJid());
46 if (message.getType() == Message.TYPE_TEXT) {
47 InputStream is = new ByteArrayInputStream(message.getBody()
48 .getBytes());
49 final OutputStream os = new ByteArrayOutputStream();
50 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
51
52 @Override
53 public void onReturn(Intent result) {
54 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
55 OpenPgpApi.RESULT_CODE_ERROR)) {
56 case OpenPgpApi.RESULT_CODE_SUCCESS:
57 try {
58 os.flush();
59 if (message.getEncryption() == Message.ENCRYPTION_PGP) {
60 message.setBody(os.toString());
61 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
62 callback.success(message);
63 }
64 } catch (IOException e) {
65 callback.error(R.string.openpgp_error, message);
66 return;
67 }
68
69 return;
70 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
71 callback.userInputRequried((PendingIntent) result
72 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
73 message);
74 return;
75 case OpenPgpApi.RESULT_CODE_ERROR:
76 OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
77 Log.d("xmppService",error.getMessage());
78 callback.error(R.string.openpgp_error, message);
79 return;
80 default:
81 return;
82 }
83 }
84 });
85 } else if (message.getType() == Message.TYPE_IMAGE) {
86 try {
87 final JingleFile inputFile = this.mXmppConnectionService
88 .getFileBackend().getJingleFile(message, false);
89 final JingleFile outputFile = this.mXmppConnectionService
90 .getFileBackend().getJingleFile(message, true);
91 outputFile.createNewFile();
92 InputStream is = new FileInputStream(inputFile);
93 OutputStream os = new FileOutputStream(outputFile);
94 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
95
96 @Override
97 public void onReturn(Intent result) {
98 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
99 OpenPgpApi.RESULT_CODE_ERROR)) {
100 case OpenPgpApi.RESULT_CODE_SUCCESS:
101 BitmapFactory.Options options = new BitmapFactory.Options();
102 options.inJustDecodeBounds = true;
103 BitmapFactory.decodeFile(
104 outputFile.getAbsolutePath(), options);
105 int imageHeight = options.outHeight;
106 int imageWidth = options.outWidth;
107 message.setBody("" + outputFile.getSize() + ","
108 + imageWidth + "," + imageHeight);
109 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
110 PgpEngine.this.mXmppConnectionService
111 .updateMessage(message);
112 PgpEngine.this.mXmppConnectionService.updateConversationUi();
113 callback.success(message);
114 return;
115 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
116 callback.userInputRequried(
117 (PendingIntent) result
118 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
119 message);
120 return;
121 case OpenPgpApi.RESULT_CODE_ERROR:
122 callback.error(R.string.openpgp_error, message);
123 return;
124 default:
125 return;
126 }
127 }
128 });
129 } catch (FileNotFoundException e) {
130 callback.error(R.string.error_decrypting_file, message);
131 } catch (IOException e) {
132 callback.error(R.string.error_decrypting_file, message);
133 }
134
135 }
136 }
137
138 public void encrypt(final Message message,
139 final UiCallback<Message> callback) {
140
141 Intent params = new Intent();
142 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
143 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
144 long[] keys = { message.getConversation().getContact()
145 .getPgpKeyId() };
146 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
147 } else {
148 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
149 .getMucOptions().getPgpKeyIds());
150 }
151 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
152 .getConversation().getAccount().getJid());
153
154 if (message.getType() == Message.TYPE_TEXT) {
155 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
156
157 InputStream is = new ByteArrayInputStream(message.getBody()
158 .getBytes());
159 final OutputStream os = new ByteArrayOutputStream();
160 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
161
162 @Override
163 public void onReturn(Intent result) {
164 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
165 OpenPgpApi.RESULT_CODE_ERROR)) {
166 case OpenPgpApi.RESULT_CODE_SUCCESS:
167 try {
168 os.flush();
169 StringBuilder encryptedMessageBody = new StringBuilder();
170 String[] lines = os.toString().split("\n");
171 for (int i = 2; i < lines.length - 1; ++i) {
172 if (!lines[i].contains("Version")) {
173 encryptedMessageBody.append(lines[i].trim());
174 }
175 }
176 message.setEncryptedBody(encryptedMessageBody
177 .toString());
178 callback.success(message);
179 } catch (IOException e) {
180 callback.error(R.string.openpgp_error, message);
181 }
182
183 break;
184 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
185 callback.userInputRequried((PendingIntent) result
186 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
187 message);
188 break;
189 case OpenPgpApi.RESULT_CODE_ERROR:
190 callback.error(R.string.openpgp_error, message);
191 break;
192 }
193 }
194 });
195 } else if (message.getType() == Message.TYPE_IMAGE) {
196 try {
197 JingleFile inputFile = this.mXmppConnectionService
198 .getFileBackend().getJingleFile(message, true);
199 JingleFile outputFile = this.mXmppConnectionService
200 .getFileBackend().getJingleFile(message, false);
201 outputFile.createNewFile();
202 InputStream is = new FileInputStream(inputFile);
203 OutputStream os = new FileOutputStream(outputFile);
204 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
205
206 @Override
207 public void onReturn(Intent result) {
208 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
209 OpenPgpApi.RESULT_CODE_ERROR)) {
210 case OpenPgpApi.RESULT_CODE_SUCCESS:
211 callback.success(message);
212 break;
213 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
214 callback.userInputRequried(
215 (PendingIntent) result
216 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
217 message);
218 break;
219 case OpenPgpApi.RESULT_CODE_ERROR:
220 callback.error(R.string.openpgp_error, message);
221 break;
222 }
223 }
224 });
225 } catch (FileNotFoundException e) {
226 Log.d("xmppService", "file not found: " + e.getMessage());
227 } catch (IOException e) {
228 Log.d("xmppService", "io exception during file encrypt");
229 }
230 }
231 }
232
233 public long fetchKeyId(Account account, String status, String signature) {
234 if ((signature == null) || (api == null)) {
235 return 0;
236 }
237 if (status == null) {
238 status = "";
239 }
240 StringBuilder pgpSig = new StringBuilder();
241 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
242 pgpSig.append('\n');
243 pgpSig.append('\n');
244 pgpSig.append(status);
245 pgpSig.append('\n');
246 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
247 pgpSig.append('\n');
248 pgpSig.append('\n');
249 pgpSig.append(signature.replace("\n", "").trim());
250 pgpSig.append('\n');
251 pgpSig.append("-----END PGP SIGNATURE-----");
252 Intent params = new Intent();
253 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
254 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
255 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
256 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
257 ByteArrayOutputStream os = new ByteArrayOutputStream();
258 Intent result = api.executeApi(params, is, os);
259 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
260 OpenPgpApi.RESULT_CODE_ERROR)) {
261 case OpenPgpApi.RESULT_CODE_SUCCESS:
262 OpenPgpSignatureResult sigResult = result
263 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
264 if (sigResult != null) {
265 return sigResult.getKeyId();
266 } else {
267 return 0;
268 }
269 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
270 return 0;
271 case OpenPgpApi.RESULT_CODE_ERROR:
272 Log.d("xmppService",
273 "openpgp error: "
274 + ((OpenPgpError) result
275 .getParcelableExtra(OpenPgpApi.RESULT_ERROR))
276 .getMessage());
277 return 0;
278 }
279 return 0;
280 }
281
282 public void generateSignature(final Account account, String status,
283 final UiCallback<Account> callback) {
284 Intent params = new Intent();
285 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
286 params.setAction(OpenPgpApi.ACTION_SIGN);
287 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
288 InputStream is = new ByteArrayInputStream(status.getBytes());
289 final OutputStream os = new ByteArrayOutputStream();
290 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
291
292 @Override
293 public void onReturn(Intent result) {
294 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
295 case OpenPgpApi.RESULT_CODE_SUCCESS:
296 StringBuilder signatureBuilder = new StringBuilder();
297 try {
298 os.flush();
299 String[] lines = os.toString().split("\n");
300 boolean sig = false;
301 for(String line : lines) {
302 if (sig) {
303 if (line.contains("END PGP SIGNATURE")) {
304 sig = false;
305 } else {
306 if (!line.contains("Version")) {
307 signatureBuilder.append(line.trim());
308 }
309 }
310 }
311 if (line.contains("BEGIN PGP SIGNATURE")) {
312 sig = true;
313 }
314 }
315 } catch (IOException e) {
316 callback.error(R.string.openpgp_error, account);
317 return;
318 }
319 account.setKey("pgp_signature", signatureBuilder.toString());
320 callback.success(account);
321 return;
322 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
323 callback.userInputRequried((PendingIntent) result
324 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
325 account);
326 return;
327 case OpenPgpApi.RESULT_CODE_ERROR:
328 callback.error(R.string.openpgp_error, account);
329 return;
330 }
331 }
332 });
333 }
334
335 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
336 Intent params = new Intent();
337 params.setAction(OpenPgpApi.ACTION_GET_KEY);
338 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
339 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
340 .getJid());
341 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
342
343 @Override
344 public void onReturn(Intent result) {
345 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
346 case OpenPgpApi.RESULT_CODE_SUCCESS:
347 callback.success(contact);
348 return;
349 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
350 callback.userInputRequried((PendingIntent) result
351 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
352 contact);
353 return;
354 case OpenPgpApi.RESULT_CODE_ERROR:
355 callback.error(R.string.openpgp_error, contact);
356 return;
357 }
358 }
359 });
360 }
361
362 public PendingIntent getIntentForKey(Contact contact) {
363 Intent params = new Intent();
364 params.setAction(OpenPgpApi.ACTION_GET_KEY);
365 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
366 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
367 .getJid());
368 Intent result = api.executeApi(params, null, null);
369 return (PendingIntent) result
370 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
371 }
372
373 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
374 Intent params = new Intent();
375 params.setAction(OpenPgpApi.ACTION_GET_KEY);
376 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
377 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
378 Intent result = api.executeApi(params, null, null);
379 return (PendingIntent) result
380 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
381 }
382}