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