PgpEngine.java

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