PgpEngine.java

  1package eu.siacs.conversations.crypto;
  2
  3import android.app.PendingIntent;
  4import android.content.Intent;
  5import android.util.Log;
  6
  7import org.openintents.openpgp.OpenPgpSignatureResult;
  8import org.openintents.openpgp.util.OpenPgpApi;
  9import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
 10
 11import java.io.ByteArrayInputStream;
 12import java.io.ByteArrayOutputStream;
 13import java.io.FileInputStream;
 14import java.io.FileOutputStream;
 15import java.io.IOException;
 16import java.io.InputStream;
 17import java.io.OutputStream;
 18import java.net.URL;
 19
 20import eu.siacs.conversations.Config;
 21import eu.siacs.conversations.R;
 22import eu.siacs.conversations.entities.Account;
 23import eu.siacs.conversations.entities.Contact;
 24import eu.siacs.conversations.entities.Conversation;
 25import eu.siacs.conversations.entities.DownloadableFile;
 26import eu.siacs.conversations.entities.Message;
 27import eu.siacs.conversations.http.HttpConnectionManager;
 28import eu.siacs.conversations.persistance.FileBackend;
 29import eu.siacs.conversations.services.XmppConnectionService;
 30import eu.siacs.conversations.ui.UiCallback;
 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 encrypt(final Message message, final UiCallback<Message> callback) {
 42		Intent params = new Intent();
 43		params.setAction(OpenPgpApi.ACTION_ENCRYPT);
 44		final Conversation conversation = message.getConversation();
 45		if (conversation.getMode() == Conversation.MODE_SINGLE) {
 46			long[] keys = {
 47					conversation.getContact().getPgpKeyId(),
 48					conversation.getAccount().getPgpId()
 49			};
 50			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
 51		} else {
 52			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
 53		}
 54
 55		if (!message.needsUploading()) {
 56			params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
 57			String body;
 58			if (message.hasFileOnRemoteHost()) {
 59				body = message.getFileParams().url.toString();
 60			} else {
 61				body = message.getBody();
 62			}
 63			InputStream is = new ByteArrayInputStream(body.getBytes());
 64			final OutputStream os = new ByteArrayOutputStream();
 65			api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
 66
 67				@Override
 68				public void onReturn(Intent result) {
 69					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
 70							OpenPgpApi.RESULT_CODE_ERROR)) {
 71					case OpenPgpApi.RESULT_CODE_SUCCESS:
 72						try {
 73							os.flush();
 74							StringBuilder encryptedMessageBody = new StringBuilder();
 75							String[] lines = os.toString().split("\n");
 76							for (int i = 2; i < lines.length - 1; ++i) {
 77								if (!lines[i].contains("Version")) {
 78									encryptedMessageBody.append(lines[i].trim());
 79								}
 80							}
 81							message.setEncryptedBody(encryptedMessageBody
 82									.toString());
 83							callback.success(message);
 84						} catch (IOException e) {
 85							callback.error(R.string.openpgp_error, message);
 86						}
 87
 88						break;
 89					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
 90						callback.userInputRequried((PendingIntent) result
 91								.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
 92								message);
 93						break;
 94					case OpenPgpApi.RESULT_CODE_ERROR:
 95						callback.error(R.string.openpgp_error, message);
 96						break;
 97					}
 98				}
 99			});
100		} else {
101			try {
102				DownloadableFile inputFile = this.mXmppConnectionService
103						.getFileBackend().getFile(message, true);
104				DownloadableFile outputFile = this.mXmppConnectionService
105						.getFileBackend().getFile(message, false);
106				outputFile.getParentFile().mkdirs();
107				outputFile.createNewFile();
108				final InputStream is = new FileInputStream(inputFile);
109				final OutputStream os = new FileOutputStream(outputFile);
110				api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
111
112					@Override
113					public void onReturn(Intent result) {
114						switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
115								OpenPgpApi.RESULT_CODE_ERROR)) {
116						case OpenPgpApi.RESULT_CODE_SUCCESS:
117							try {
118								os.flush();
119							} catch (IOException ignored) {
120								//ignored
121							}
122							FileBackend.close(os);
123							callback.success(message);
124							break;
125						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
126							callback.userInputRequried(
127									(PendingIntent) result
128											.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
129									message);
130							break;
131						case OpenPgpApi.RESULT_CODE_ERROR:
132							callback.error(R.string.openpgp_error, message);
133							break;
134						}
135					}
136				});
137			} catch (final IOException e) {
138				callback.error(R.string.openpgp_error, message);
139			}
140		}
141	}
142
143	public long fetchKeyId(Account account, String status, String signature) {
144		if ((signature == null) || (api == null)) {
145			return 0;
146		}
147		if (status == null) {
148			status = "";
149		}
150		final StringBuilder pgpSig = new StringBuilder();
151		pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
152		pgpSig.append('\n');
153		pgpSig.append('\n');
154		pgpSig.append(status);
155		pgpSig.append('\n');
156		pgpSig.append("-----BEGIN PGP SIGNATURE-----");
157		pgpSig.append('\n');
158		pgpSig.append('\n');
159		pgpSig.append(signature.replace("\n", "").trim());
160		pgpSig.append('\n');
161		pgpSig.append("-----END PGP SIGNATURE-----");
162		Intent params = new Intent();
163		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
164		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
165		InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
166		ByteArrayOutputStream os = new ByteArrayOutputStream();
167		Intent result = api.executeApi(params, is, os);
168		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
169				OpenPgpApi.RESULT_CODE_ERROR)) {
170		case OpenPgpApi.RESULT_CODE_SUCCESS:
171			OpenPgpSignatureResult sigResult = result
172					.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
173			if (sigResult != null) {
174				return sigResult.getKeyId();
175			} else {
176				return 0;
177			}
178		case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
179			return 0;
180		case OpenPgpApi.RESULT_CODE_ERROR:
181			return 0;
182		}
183		return 0;
184	}
185
186	public void chooseKey(final Account account, final UiCallback<Account> callback) {
187		Intent p = new Intent();
188		p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
189		api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
190
191			@Override
192			public void onReturn(Intent result) {
193				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
194					case OpenPgpApi.RESULT_CODE_SUCCESS:
195						callback.success(account);
196						return;
197					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
198						callback.userInputRequried((PendingIntent) result
199										.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
200								account);
201						return;
202					case OpenPgpApi.RESULT_CODE_ERROR:
203						callback.error(R.string.openpgp_error, account);
204				}
205			}
206		});
207	}
208
209	public void generateSignature(final Account account, String status,
210			final UiCallback<Account> callback) {
211		if (account.getPgpId() == 0) {
212			return;
213		}
214		Intent params = new Intent();
215		params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
216		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
217		params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
218		InputStream is = new ByteArrayInputStream(status.getBytes());
219		final OutputStream os = new ByteArrayOutputStream();
220		Log.d(Config.LOGTAG,account.getJid().toBareJid()+": signing status message \""+status+"\"");
221		api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
222
223			@Override
224			public void onReturn(Intent result) {
225				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
226				case OpenPgpApi.RESULT_CODE_SUCCESS:
227					StringBuilder signatureBuilder = new StringBuilder();
228					try {
229						os.flush();
230						String[] lines = os.toString().split("\n");
231						boolean sig = false;
232						for (String line : lines) {
233							if (sig) {
234								if (line.contains("END PGP SIGNATURE")) {
235									sig = false;
236								} else {
237									if (!line.contains("Version")) {
238										signatureBuilder.append(line.trim());
239									}
240								}
241							}
242							if (line.contains("BEGIN PGP SIGNATURE")) {
243								sig = true;
244							}
245						}
246					} catch (IOException e) {
247						callback.error(R.string.openpgp_error, account);
248						return;
249					}
250					account.setPgpSignature(signatureBuilder.toString());
251					callback.success(account);
252					return;
253				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
254					callback.userInputRequried((PendingIntent) result
255							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
256							account);
257					return;
258				case OpenPgpApi.RESULT_CODE_ERROR:
259					callback.error(R.string.unable_to_connect_to_keychain, account);
260                }
261			}
262		});
263	}
264
265	public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
266		Intent params = new Intent();
267		params.setAction(OpenPgpApi.ACTION_GET_KEY);
268		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
269		api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
270
271			@Override
272			public void onReturn(Intent result) {
273				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
274				case OpenPgpApi.RESULT_CODE_SUCCESS:
275					callback.success(contact);
276					return;
277				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
278					callback.userInputRequried((PendingIntent) result
279							.getParcelableExtra(OpenPgpApi.RESULT_INTENT),
280							contact);
281					return;
282				case OpenPgpApi.RESULT_CODE_ERROR:
283					callback.error(R.string.openpgp_error, contact);
284                }
285			}
286		});
287	}
288
289	public PendingIntent getIntentForKey(Contact contact) {
290		Intent params = new Intent();
291		params.setAction(OpenPgpApi.ACTION_GET_KEY);
292		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
293		Intent result = api.executeApi(params, null, null);
294		return (PendingIntent) result
295				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
296	}
297
298	public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
299		Intent params = new Intent();
300		params.setAction(OpenPgpApi.ACTION_GET_KEY);
301		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
302		Intent result = api.executeApi(params, null, null);
303		return (PendingIntent) result
304				.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
305	}
306}