PgpEngine.java

  1package eu.siacs.conversations.crypto;
  2
  3import android.app.PendingIntent;
  4import android.content.Intent;
  5import android.net.Uri;
  6
  7import org.openintents.openpgp.OpenPgpSignatureResult;
  8import org.openintents.openpgp.util.OpenPgpApi;
  9import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
 10
 11import java.io.ByteArrayInputStream;
 12import java.io.ByteArrayOutputStream;
 13import java.io.FileInputStream;
 14import java.io.FileOutputStream;
 15import java.io.IOException;
 16import java.io.InputStream;
 17import java.io.OutputStream;
 18import java.net.URL;
 19
 20import eu.siacs.conversations.R;
 21import eu.siacs.conversations.entities.Account;
 22import eu.siacs.conversations.entities.Contact;
 23import eu.siacs.conversations.entities.Conversation;
 24import eu.siacs.conversations.entities.DownloadableFile;
 25import eu.siacs.conversations.entities.Message;
 26import eu.siacs.conversations.http.HttpConnectionManager;
 27import eu.siacs.conversations.persistance.FileBackend;
 28import eu.siacs.conversations.services.XmppConnectionService;
 29import eu.siacs.conversations.ui.UiCallback;
 30
 31public class PgpEngine {
 32	private OpenPgpApi api;
 33	private XmppConnectionService mXmppConnectionService;
 34
 35	public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
 36		this.api = api;
 37		this.mXmppConnectionService = service;
 38	}
 39
 40	public void decrypt(final Message message,
 41			final UiCallback<Message> callback) {
 42		Intent params = new Intent();
 43		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
 44		if (message.getType() == Message.TYPE_TEXT) {
 45			InputStream is = new ByteArrayInputStream(message.getBody()
 46					.getBytes());
 47			final OutputStream os = new ByteArrayOutputStream();
 48			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
 49
 50				@Override
 51				public void onReturn(Intent result) {
 52					notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, 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								final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
 62								if (message.trusted()
 63										&& message.treatAsDownloadable() != Message.Decision.NEVER
 64										&& manager.getAutoAcceptFileSize() > 0) {
 65									manager.createNewDownloadConnection(message);
 66								}
 67								mXmppConnectionService.updateMessage(message);
 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						callback.error(R.string.openpgp_error, message);
 83                    }
 84				}
 85			});
 86		} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
 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.getParentFile().mkdirs();
 93				outputFile.createNewFile();
 94				InputStream is = new FileInputStream(inputFile);
 95				OutputStream os = new FileOutputStream(outputFile);
 96				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
 97
 98					@Override
 99					public void onReturn(Intent result) {
100						notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
101						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
102								OpenPgpApi.RESULT_CODE_ERROR)) {
103						case OpenPgpApi.RESULT_CODE_SUCCESS:
104							URL url = message.getFileParams().url;
105							mXmppConnectionService.getFileBackend().updateFileParams(message,url);
106							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
107							PgpEngine.this.mXmppConnectionService
108									.updateMessage(message);
109							inputFile.delete();
110							Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
111							intent.setData(Uri.fromFile(outputFile));
112							mXmppConnectionService.sendBroadcast(intent);
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						}
124					}
125				});
126			} catch (final IOException e) {
127				callback.error(R.string.error_decrypting_file, message);
128			}
129
130		}
131	}
132
133	public void encrypt(final Message message, final UiCallback<Message> callback) {
134		Intent params = new Intent();
135		params.setAction(OpenPgpApi.ACTION_ENCRYPT);
136		final Conversation conversation = message.getConversation();
137		if (conversation.getMode() == Conversation.MODE_SINGLE) {
138			long[] keys = {
139					conversation.getContact().getPgpKeyId(),
140					conversation.getAccount().getPgpId()
141			};
142			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
143		} else {
144			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
145		}
146
147		if (!message.needsUploading()) {
148			params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
149			String body;
150			if (message.hasFileOnRemoteHost()) {
151				body = message.getFileParams().url.toString();
152			} else {
153				body = message.getBody();
154			}
155			InputStream is = new ByteArrayInputStream(body.getBytes());
156			final OutputStream os = new ByteArrayOutputStream();
157			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
158
159				@Override
160				public void onReturn(Intent result) {
161					notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
162					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
163							OpenPgpApi.RESULT_CODE_ERROR)) {
164					case OpenPgpApi.RESULT_CODE_SUCCESS:
165						try {
166							os.flush();
167							StringBuilder encryptedMessageBody = new StringBuilder();
168							String[] lines = os.toString().split("\n");
169							for (int i = 2; i < lines.length - 1; ++i) {
170								if (!lines[i].contains("Version")) {
171									encryptedMessageBody.append(lines[i].trim());
172								}
173							}
174							message.setEncryptedBody(encryptedMessageBody
175									.toString());
176							callback.success(message);
177						} catch (IOException e) {
178							callback.error(R.string.openpgp_error, message);
179						}
180
181						break;
182					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
183						callback.userInputRequried((PendingIntent) result
184								.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
185								message);
186						break;
187					case OpenPgpApi.RESULT_CODE_ERROR:
188						callback.error(R.string.openpgp_error, message);
189						break;
190					}
191				}
192			});
193		} else {
194			try {
195				DownloadableFile inputFile = this.mXmppConnectionService
196						.getFileBackend().getFile(message, true);
197				DownloadableFile outputFile = this.mXmppConnectionService
198						.getFileBackend().getFile(message, false);
199				outputFile.getParentFile().mkdirs();
200				outputFile.createNewFile();
201				final InputStream is = new FileInputStream(inputFile);
202				final OutputStream os = new FileOutputStream(outputFile);
203				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
204
205					@Override
206					public void onReturn(Intent result) {
207						notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
208						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
209								OpenPgpApi.RESULT_CODE_ERROR)) {
210						case OpenPgpApi.RESULT_CODE_SUCCESS:
211							try {
212								os.flush();
213							} catch (IOException ignored) {
214								//ignored
215							}
216							FileBackend.close(os);
217							callback.success(message);
218							break;
219						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
220							callback.userInputRequried(
221									(PendingIntent) result
222											.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
223									message);
224							break;
225						case OpenPgpApi.RESULT_CODE_ERROR:
226							callback.error(R.string.openpgp_error, message);
227							break;
228						}
229					}
230				});
231			} catch (final IOException e) {
232				callback.error(R.string.openpgp_error, message);
233			}
234		}
235	}
236
237	public long fetchKeyId(Account account, String status, String signature) {
238		if ((signature == null) || (api == null)) {
239			return 0;
240		}
241		if (status == null) {
242			status = "";
243		}
244		final StringBuilder pgpSig = new StringBuilder();
245		pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
246		pgpSig.append('\n');
247		pgpSig.append('\n');
248		pgpSig.append(status);
249		pgpSig.append('\n');
250		pgpSig.append("-----BEGIN PGP SIGNATURE-----");
251		pgpSig.append('\n');
252		pgpSig.append('\n');
253		pgpSig.append(signature.replace("\n", "").trim());
254		pgpSig.append('\n');
255		pgpSig.append("-----END PGP SIGNATURE-----");
256		Intent params = new Intent();
257		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
258		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
259		InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
260		ByteArrayOutputStream os = new ByteArrayOutputStream();
261		Intent result = api.executeApi(params, is, os);
262		notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
263		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
264				OpenPgpApi.RESULT_CODE_ERROR)) {
265		case OpenPgpApi.RESULT_CODE_SUCCESS:
266			OpenPgpSignatureResult sigResult = result
267					.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
268			if (sigResult != null) {
269				return sigResult.getKeyId();
270			} else {
271				return 0;
272			}
273		case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
274			return 0;
275		case OpenPgpApi.RESULT_CODE_ERROR:
276			return 0;
277		}
278		return 0;
279	}
280
281	public void chooseKey(final Account account, final UiCallback<Account> callback) {
282		Intent p = new Intent();
283		p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
284		api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
285
286			@Override
287			public void onReturn(Intent result) {
288				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
289					case OpenPgpApi.RESULT_CODE_SUCCESS:
290						callback.success(account);
291						return;
292					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
293						callback.userInputRequried((PendingIntent) result
294										.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
295								account);
296						return;
297					case OpenPgpApi.RESULT_CODE_ERROR:
298						callback.error(R.string.openpgp_error, account);
299				}
300			}
301		});
302	}
303
304	public void generateSignature(final Account account, String status,
305			final UiCallback<Account> callback) {
306		if (account.getPgpId() == -1) {
307			return;
308		}
309		Intent params = new Intent();
310		params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
311		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
312		params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
313		InputStream is = new ByteArrayInputStream(status.getBytes());
314		final OutputStream os = new ByteArrayOutputStream();
315		api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
316
317			@Override
318			public void onReturn(Intent result) {
319				notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result);
320				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
321				case OpenPgpApi.RESULT_CODE_SUCCESS:
322					StringBuilder signatureBuilder = new StringBuilder();
323					try {
324						os.flush();
325						String[] lines = os.toString().split("\n");
326						boolean sig = false;
327						for (String line : lines) {
328							if (sig) {
329								if (line.contains("END PGP SIGNATURE")) {
330									sig = false;
331								} else {
332									if (!line.contains("Version")) {
333										signatureBuilder.append(line.trim());
334									}
335								}
336							}
337							if (line.contains("BEGIN PGP SIGNATURE")) {
338								sig = true;
339							}
340						}
341					} catch (IOException e) {
342						callback.error(R.string.openpgp_error, account);
343						return;
344					}
345					account.setPgpSignature(signatureBuilder.toString());
346					callback.success(account);
347					return;
348				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
349					callback.userInputRequried((PendingIntent) result
350							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
351							account);
352					return;
353				case OpenPgpApi.RESULT_CODE_ERROR:
354					callback.error(R.string.openpgp_error, account);
355                }
356			}
357		});
358	}
359
360	public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
361		Intent params = new Intent();
362		params.setAction(OpenPgpApi.ACTION_GET_KEY);
363		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
364		api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
365
366			@Override
367			public void onReturn(Intent result) {
368				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
369				case OpenPgpApi.RESULT_CODE_SUCCESS:
370					callback.success(contact);
371					return;
372				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
373					callback.userInputRequried((PendingIntent) result
374							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
375							contact);
376					return;
377				case OpenPgpApi.RESULT_CODE_ERROR:
378					callback.error(R.string.openpgp_error, contact);
379                }
380			}
381		});
382	}
383
384	public PendingIntent getIntentForKey(Contact contact) {
385		Intent params = new Intent();
386		params.setAction(OpenPgpApi.ACTION_GET_KEY);
387		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
388		Intent result = api.executeApi(params, null, null);
389		return (PendingIntent) result
390				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
391	}
392
393	public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
394		Intent params = new Intent();
395		params.setAction(OpenPgpApi.ACTION_GET_KEY);
396		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
397		Intent result = api.executeApi(params, null, null);
398		return (PendingIntent) result
399				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
400	}
401
402	private void notifyPgpDecryptionService(Account account, String action, final Intent result) {
403		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
404			case OpenPgpApi.RESULT_CODE_SUCCESS:
405				if (OpenPgpApi.ACTION_SIGN.equals(action)) {
406					account.getPgpDecryptionService().onKeychainUnlocked();
407				}
408				break;
409			case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
410				account.getPgpDecryptionService().onKeychainLocked();
411				break;
412		}
413	}
414}