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