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