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.OpenPgpError;
 14import org.openintents.openpgp.OpenPgpSignatureResult;
 15import org.openintents.openpgp.util.OpenPgpApi;
 16import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
 17
 18import eu.siacs.conversations.Config;
 19import eu.siacs.conversations.R;
 20import eu.siacs.conversations.entities.Account;
 21import eu.siacs.conversations.entities.Contact;
 22import eu.siacs.conversations.entities.Conversation;
 23import eu.siacs.conversations.entities.DownloadableFile;
 24import eu.siacs.conversations.entities.Message;
 25import eu.siacs.conversations.services.XmppConnectionService;
 26import eu.siacs.conversations.ui.UiCallback;
 27import android.app.PendingIntent;
 28import android.content.Intent;
 29import android.graphics.BitmapFactory;
 30import android.util.Log;
 31
 32public class PgpEngine {
 33	private OpenPgpApi api;
 34	private XmppConnectionService mXmppConnectionService;
 35
 36	public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
 37		this.api = api;
 38		this.mXmppConnectionService = service;
 39	}
 40
 41	public void decrypt(final Message message,
 42			final UiCallback<Message> callback) {
 43		Log.d(Config.LOGTAG, "decrypting message " + message.getUuid());
 44		Intent params = new Intent();
 45		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
 46		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
 47				.getConversation().getAccount().getJid());
 48		if (message.getType() == Message.TYPE_TEXT) {
 49			InputStream is = new ByteArrayInputStream(message.getBody()
 50					.getBytes());
 51			final OutputStream os = new ByteArrayOutputStream();
 52			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
 53
 54				@Override
 55				public void onReturn(Intent result) {
 56					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 57							OpenPgpApi.RESULT_CODE_ERROR)) {
 58					case OpenPgpApi.RESULT_CODE_SUCCESS:
 59						try {
 60							os.flush();
 61							if (message.getEncryption() == Message.ENCRYPTION_PGP) {
 62								message.setBody(os.toString());
 63								message.setEncryption(Message.ENCRYPTION_DECRYPTED);
 64								if (message.trusted() && message.bodyContainsDownloadable()) {
 65									mXmppConnectionService.getHttpConnectionManager()
 66											.createNewConnection(message);
 67								}
 68								callback.success(message);
 69							}
 70						} catch (IOException e) {
 71							callback.error(R.string.openpgp_error, message);
 72							return;
 73						}
 74
 75						return;
 76					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
 77						callback.userInputRequried((PendingIntent) result
 78								.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
 79								message);
 80						return;
 81					case OpenPgpApi.RESULT_CODE_ERROR:
 82						OpenPgpError error = result
 83								.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
 84						Log.d(Config.LOGTAG,
 85								"openpgp error: " + error.getMessage());
 86						callback.error(R.string.openpgp_error, message);
 87						return;
 88					default:
 89						return;
 90					}
 91				}
 92			});
 93		} else if (message.getType() == Message.TYPE_IMAGE) {
 94			try {
 95				final DownloadableFile inputFile = this.mXmppConnectionService
 96						.getFileBackend().getFile(message, false);
 97				final DownloadableFile outputFile = this.mXmppConnectionService
 98						.getFileBackend().getFile(message, true);
 99				outputFile.createNewFile();
100				InputStream is = new FileInputStream(inputFile);
101				OutputStream os = new FileOutputStream(outputFile);
102				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
103
104					@Override
105					public void onReturn(Intent result) {
106						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
107								OpenPgpApi.RESULT_CODE_ERROR)) {
108						case OpenPgpApi.RESULT_CODE_SUCCESS:
109							URL url = message.getImageParams().url;
110							BitmapFactory.Options options = new BitmapFactory.Options();
111							options.inJustDecodeBounds = true;
112							BitmapFactory.decodeFile(
113									outputFile.getAbsolutePath(), options);
114							int imageHeight = options.outHeight;
115							int imageWidth = options.outWidth;
116							if (url == null) {
117								message.setBody(Long.toString(outputFile
118										.getSize())
119										+ '|'
120										+ imageWidth
121										+ '|'
122										+ imageHeight);
123							} else {
124								message.setBody(url.toString() + "|"
125										+ Long.toString(outputFile.getSize())
126										+ '|' + imageWidth + '|' + imageHeight);
127							}
128							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
129							PgpEngine.this.mXmppConnectionService
130									.updateMessage(message);
131							;
132							callback.success(message);
133							return;
134						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
135							callback.userInputRequried(
136									(PendingIntent) result
137											.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
138									message);
139							return;
140						case OpenPgpApi.RESULT_CODE_ERROR:
141							callback.error(R.string.openpgp_error, message);
142							return;
143						default:
144							return;
145						}
146					}
147				});
148			} catch (FileNotFoundException e) {
149				callback.error(R.string.error_decrypting_file, message);
150			} catch (IOException e) {
151				callback.error(R.string.error_decrypting_file, message);
152			}
153
154		}
155	}
156
157	public void encrypt(final Message message,
158			final UiCallback<Message> callback) {
159
160		Intent params = new Intent();
161		params.setAction(OpenPgpApi.ACTION_ENCRYPT);
162		if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
163			long[] keys = { message.getConversation().getContact()
164					.getPgpKeyId() };
165			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
166		} else {
167			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
168					.getMucOptions().getPgpKeyIds());
169		}
170		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
171				.getConversation().getAccount().getJid());
172
173		if (message.getType() == Message.TYPE_TEXT) {
174			params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
175
176			InputStream is = new ByteArrayInputStream(message.getBody()
177					.getBytes());
178			final OutputStream os = new ByteArrayOutputStream();
179			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
180
181				@Override
182				public void onReturn(Intent result) {
183					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
184							OpenPgpApi.RESULT_CODE_ERROR)) {
185					case OpenPgpApi.RESULT_CODE_SUCCESS:
186						try {
187							os.flush();
188							StringBuilder encryptedMessageBody = new StringBuilder();
189							String[] lines = os.toString().split("\n");
190							for (int i = 2; i < lines.length - 1; ++i) {
191								if (!lines[i].contains("Version")) {
192									encryptedMessageBody.append(lines[i].trim());
193								}
194							}
195							message.setEncryptedBody(encryptedMessageBody
196									.toString());
197							callback.success(message);
198						} catch (IOException e) {
199							callback.error(R.string.openpgp_error, message);
200						}
201
202						break;
203					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
204						callback.userInputRequried((PendingIntent) result
205								.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
206								message);
207						break;
208					case OpenPgpApi.RESULT_CODE_ERROR:
209						callback.error(R.string.openpgp_error, message);
210						break;
211					}
212				}
213			});
214		} else if (message.getType() == Message.TYPE_IMAGE) {
215			try {
216				DownloadableFile inputFile = this.mXmppConnectionService
217						.getFileBackend().getFile(message, true);
218				DownloadableFile outputFile = this.mXmppConnectionService
219						.getFileBackend().getFile(message, false);
220				outputFile.createNewFile();
221				InputStream is = new FileInputStream(inputFile);
222				OutputStream os = new FileOutputStream(outputFile);
223				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
224
225					@Override
226					public void onReturn(Intent result) {
227						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
228								OpenPgpApi.RESULT_CODE_ERROR)) {
229						case OpenPgpApi.RESULT_CODE_SUCCESS:
230							callback.success(message);
231							break;
232						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
233							callback.userInputRequried(
234									(PendingIntent) result
235											.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
236									message);
237							break;
238						case OpenPgpApi.RESULT_CODE_ERROR:
239							callback.error(R.string.openpgp_error, message);
240							break;
241						}
242					}
243				});
244			} catch (FileNotFoundException e) {
245				Log.d(Config.LOGTAG, "file not found: " + e.getMessage());
246			} catch (IOException e) {
247				Log.d(Config.LOGTAG, "io exception during file encrypt");
248			}
249		}
250	}
251
252	public long fetchKeyId(Account account, String status, String signature) {
253		if ((signature == null) || (api == null)) {
254			return 0;
255		}
256		if (status == null) {
257			status = "";
258		}
259		StringBuilder pgpSig = new StringBuilder();
260		pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
261		pgpSig.append('\n');
262		pgpSig.append('\n');
263		pgpSig.append(status);
264		pgpSig.append('\n');
265		pgpSig.append("-----BEGIN PGP SIGNATURE-----");
266		pgpSig.append('\n');
267		pgpSig.append('\n');
268		pgpSig.append(signature.replace("\n", "").trim());
269		pgpSig.append('\n');
270		pgpSig.append("-----END PGP SIGNATURE-----");
271		Intent params = new Intent();
272		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
273		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
274		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
275		InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
276		ByteArrayOutputStream os = new ByteArrayOutputStream();
277		Intent result = api.executeApi(params, is, os);
278		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
279				OpenPgpApi.RESULT_CODE_ERROR)) {
280		case OpenPgpApi.RESULT_CODE_SUCCESS:
281			OpenPgpSignatureResult sigResult = result
282					.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
283			if (sigResult != null) {
284				return sigResult.getKeyId();
285			} else {
286				return 0;
287			}
288		case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
289			return 0;
290		case OpenPgpApi.RESULT_CODE_ERROR:
291			Log.d(Config.LOGTAG,
292					"openpgp error: "
293							+ ((OpenPgpError) result
294									.getParcelableExtra(OpenPgpApi.RESULT_ERROR))
295									.getMessage());
296			return 0;
297		}
298		return 0;
299	}
300
301	public void generateSignature(final Account account, String status,
302			final UiCallback<Account> callback) {
303		Intent params = new Intent();
304		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
305		params.setAction(OpenPgpApi.ACTION_SIGN);
306		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
307		InputStream is = new ByteArrayInputStream(status.getBytes());
308		final OutputStream os = new ByteArrayOutputStream();
309		api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
310
311			@Override
312			public void onReturn(Intent result) {
313				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
314				case OpenPgpApi.RESULT_CODE_SUCCESS:
315					StringBuilder signatureBuilder = new StringBuilder();
316					try {
317						os.flush();
318						String[] lines = os.toString().split("\n");
319						boolean sig = false;
320						for (String line : lines) {
321							if (sig) {
322								if (line.contains("END PGP SIGNATURE")) {
323									sig = false;
324								} else {
325									if (!line.contains("Version")) {
326										signatureBuilder.append(line.trim());
327									}
328								}
329							}
330							if (line.contains("BEGIN PGP SIGNATURE")) {
331								sig = true;
332							}
333						}
334					} catch (IOException e) {
335						callback.error(R.string.openpgp_error, account);
336						return;
337					}
338					account.setKey("pgp_signature", signatureBuilder.toString());
339					callback.success(account);
340					return;
341				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
342					callback.userInputRequried((PendingIntent) result
343							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
344							account);
345					return;
346				case OpenPgpApi.RESULT_CODE_ERROR:
347					callback.error(R.string.openpgp_error, account);
348					return;
349				}
350			}
351		});
352	}
353
354	public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
355		Intent params = new Intent();
356		params.setAction(OpenPgpApi.ACTION_GET_KEY);
357		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
358		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
359				.getJid());
360		api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
361
362			@Override
363			public void onReturn(Intent result) {
364				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
365				case OpenPgpApi.RESULT_CODE_SUCCESS:
366					callback.success(contact);
367					return;
368				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
369					callback.userInputRequried((PendingIntent) result
370							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
371							contact);
372					return;
373				case OpenPgpApi.RESULT_CODE_ERROR:
374					callback.error(R.string.openpgp_error, contact);
375					return;
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		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
386				.getJid());
387		Intent result = api.executeApi(params, null, null);
388		return (PendingIntent) result
389				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
390	}
391
392	public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
393		Intent params = new Intent();
394		params.setAction(OpenPgpApi.ACTION_GET_KEY);
395		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
396		params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
397		Intent result = api.executeApi(params, null, null);
398		return (PendingIntent) result
399				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
400	}
401}