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