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.bodyContainsDownloadable()
 63										&& manager.getAutoAcceptFileSize() > 0) {
 64									manager.createNewConnection(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.getImageParams().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.getType() == Message.TYPE_TEXT) {
147			params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
148
149			InputStream is = new ByteArrayInputStream(message.getBody()
150					.getBytes());
151			final OutputStream os = new ByteArrayOutputStream();
152			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
153
154				@Override
155				public void onReturn(Intent result) {
156					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
157							OpenPgpApi.RESULT_CODE_ERROR)) {
158					case OpenPgpApi.RESULT_CODE_SUCCESS:
159						try {
160							os.flush();
161							StringBuilder encryptedMessageBody = new StringBuilder();
162							String[] lines = os.toString().split("\n");
163							for (int i = 2; i < lines.length - 1; ++i) {
164								if (!lines[i].contains("Version")) {
165									encryptedMessageBody.append(lines[i].trim());
166								}
167							}
168							message.setEncryptedBody(encryptedMessageBody
169									.toString());
170							callback.success(message);
171						} catch (IOException e) {
172							callback.error(R.string.openpgp_error, message);
173						}
174
175						break;
176					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
177						callback.userInputRequried((PendingIntent) result
178								.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
179								message);
180						break;
181					case OpenPgpApi.RESULT_CODE_ERROR:
182						callback.error(R.string.openpgp_error, message);
183						break;
184					}
185				}
186			});
187		} else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
188			try {
189				DownloadableFile inputFile = this.mXmppConnectionService
190						.getFileBackend().getFile(message, true);
191				DownloadableFile outputFile = this.mXmppConnectionService
192						.getFileBackend().getFile(message, false);
193				outputFile.getParentFile().mkdirs();
194				outputFile.createNewFile();
195				InputStream is = new FileInputStream(inputFile);
196				OutputStream os = new FileOutputStream(outputFile);
197				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
198
199					@Override
200					public void onReturn(Intent result) {
201						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
202								OpenPgpApi.RESULT_CODE_ERROR)) {
203						case OpenPgpApi.RESULT_CODE_SUCCESS:
204							callback.success(message);
205							break;
206						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
207							callback.userInputRequried(
208									(PendingIntent) result
209											.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
210									message);
211							break;
212						case OpenPgpApi.RESULT_CODE_ERROR:
213							callback.error(R.string.openpgp_error, message);
214							break;
215						}
216					}
217				});
218			} catch (final IOException e) {
219				callback.error(R.string.openpgp_error, message);
220			}
221		}
222	}
223
224	public long fetchKeyId(Account account, String status, String signature) {
225		if ((signature == null) || (api == null)) {
226			return 0;
227		}
228		if (status == null) {
229			status = "";
230		}
231		final StringBuilder pgpSig = new StringBuilder();
232		pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
233		pgpSig.append('\n');
234		pgpSig.append('\n');
235		pgpSig.append(status);
236		pgpSig.append('\n');
237		pgpSig.append("-----BEGIN PGP SIGNATURE-----");
238		pgpSig.append('\n');
239		pgpSig.append('\n');
240		pgpSig.append(signature.replace("\n", "").trim());
241		pgpSig.append('\n');
242		pgpSig.append("-----END PGP SIGNATURE-----");
243		Intent params = new Intent();
244		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
245		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
246		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
247		InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
248		ByteArrayOutputStream os = new ByteArrayOutputStream();
249		Intent result = api.executeApi(params, is, os);
250		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
251				OpenPgpApi.RESULT_CODE_ERROR)) {
252		case OpenPgpApi.RESULT_CODE_SUCCESS:
253			OpenPgpSignatureResult sigResult = result
254					.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
255			if (sigResult != null) {
256				return sigResult.getKeyId();
257			} else {
258				return 0;
259			}
260		case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
261			return 0;
262		case OpenPgpApi.RESULT_CODE_ERROR:
263			return 0;
264		}
265		return 0;
266	}
267
268	public void generateSignature(final Account account, String status,
269			final UiCallback<Account> callback) {
270		Intent params = new Intent();
271		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
272		params.setAction(OpenPgpApi.ACTION_SIGN);
273		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
274		InputStream is = new ByteArrayInputStream(status.getBytes());
275		final OutputStream os = new ByteArrayOutputStream();
276		api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
277
278			@Override
279			public void onReturn(Intent result) {
280				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
281				case OpenPgpApi.RESULT_CODE_SUCCESS:
282					StringBuilder signatureBuilder = new StringBuilder();
283					try {
284						os.flush();
285						String[] lines = os.toString().split("\n");
286						boolean sig = false;
287						for (String line : lines) {
288							if (sig) {
289								if (line.contains("END PGP SIGNATURE")) {
290									sig = false;
291								} else {
292									if (!line.contains("Version")) {
293										signatureBuilder.append(line.trim());
294									}
295								}
296							}
297							if (line.contains("BEGIN PGP SIGNATURE")) {
298								sig = true;
299							}
300						}
301					} catch (IOException e) {
302						callback.error(R.string.openpgp_error, account);
303						return;
304					}
305					account.setKey("pgp_signature", signatureBuilder.toString());
306					callback.success(account);
307					return;
308				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
309					callback.userInputRequried((PendingIntent) result
310							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
311							account);
312					return;
313				case OpenPgpApi.RESULT_CODE_ERROR:
314					callback.error(R.string.openpgp_error, account);
315                }
316			}
317		});
318	}
319
320	public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
321		Intent params = new Intent();
322		params.setAction(OpenPgpApi.ACTION_GET_KEY);
323		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
324		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
325				.getJid().toBareJid().toString());
326		api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
327
328			@Override
329			public void onReturn(Intent result) {
330				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
331				case OpenPgpApi.RESULT_CODE_SUCCESS:
332					callback.success(contact);
333					return;
334				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
335					callback.userInputRequried((PendingIntent) result
336							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
337							contact);
338					return;
339				case OpenPgpApi.RESULT_CODE_ERROR:
340					callback.error(R.string.openpgp_error, contact);
341                }
342			}
343		});
344	}
345
346	public PendingIntent getIntentForKey(Contact contact) {
347		Intent params = new Intent();
348		params.setAction(OpenPgpApi.ACTION_GET_KEY);
349		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
350		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
351				.getJid().toBareJid().toString());
352		Intent result = api.executeApi(params, null, null);
353		return (PendingIntent) result
354				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
355	}
356
357	public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
358		Intent params = new Intent();
359		params.setAction(OpenPgpApi.ACTION_GET_KEY);
360		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
361		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
362		Intent result = api.executeApi(params, null, null);
363		return (PendingIntent) result
364				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
365	}
366}