PgpEngine.java

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