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