PgpEngine.java

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