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