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