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