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