1package eu.siacs.conversations.crypto;
2
3import android.app.PendingIntent;
4import android.content.Intent;
5import android.net.Uri;
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.R;
21import eu.siacs.conversations.entities.Account;
22import eu.siacs.conversations.entities.Contact;
23import eu.siacs.conversations.entities.Conversation;
24import eu.siacs.conversations.entities.DownloadableFile;
25import eu.siacs.conversations.entities.Message;
26import eu.siacs.conversations.http.HttpConnectionManager;
27import eu.siacs.conversations.services.XmppConnectionService;
28import eu.siacs.conversations.ui.UiCallback;
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 Intent params = new Intent();
42 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
43 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
44 .getConversation().getAccount().getJid().toBareJid().toString());
45 if (message.getType() == Message.TYPE_TEXT) {
46 InputStream is = new ByteArrayInputStream(message.getBody()
47 .getBytes());
48 final OutputStream os = new ByteArrayOutputStream();
49 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
50
51 @Override
52 public void onReturn(Intent result) {
53 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, 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 final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
63 if (message.trusted()
64 && message.treatAsDownloadable() != Message.Decision.NEVER
65 && manager.getAutoAcceptFileSize() > 0) {
66 manager.createNewDownloadConnection(message);
67 }
68 mXmppConnectionService.updateMessage(message);
69 callback.success(message);
70 }
71 } catch (IOException e) {
72 callback.error(R.string.openpgp_error, message);
73 return;
74 }
75
76 return;
77 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
78 callback.userInputRequried((PendingIntent) result
79 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
80 message);
81 return;
82 case OpenPgpApi.RESULT_CODE_ERROR:
83 callback.error(R.string.openpgp_error, message);
84 }
85 }
86 });
87 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
88 try {
89 final DownloadableFile inputFile = this.mXmppConnectionService
90 .getFileBackend().getFile(message, false);
91 final DownloadableFile outputFile = this.mXmppConnectionService
92 .getFileBackend().getFile(message, true);
93 outputFile.getParentFile().mkdirs();
94 outputFile.createNewFile();
95 InputStream is = new FileInputStream(inputFile);
96 OutputStream os = new FileOutputStream(outputFile);
97 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
98
99 @Override
100 public void onReturn(Intent result) {
101 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
102 OpenPgpApi.RESULT_CODE_ERROR)) {
103 case OpenPgpApi.RESULT_CODE_SUCCESS:
104 URL url = message.getFileParams().url;
105 mXmppConnectionService.getFileBackend().updateFileParams(message,url);
106 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
107 PgpEngine.this.mXmppConnectionService
108 .updateMessage(message);
109 inputFile.delete();
110 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
111 intent.setData(Uri.fromFile(outputFile));
112 mXmppConnectionService.sendBroadcast(intent);
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 }
124 }
125 });
126 } catch (final IOException e) {
127 callback.error(R.string.error_decrypting_file, message);
128 }
129
130 }
131 }
132
133 public void encrypt(final Message message,
134 final UiCallback<Message> callback) {
135
136 Intent params = new Intent();
137 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
138 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
139 long[] keys = { message.getConversation().getContact()
140 .getPgpKeyId() };
141 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
142 } else {
143 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
144 .getMucOptions().getPgpKeyIds());
145 }
146 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
147 .getConversation().getAccount().getJid().toBareJid().toString());
148
149 if (!message.needsUploading()) {
150 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
151 String body;
152 if (message.hasFileOnRemoteHost()) {
153 body = message.getFileParams().url.toString();
154 } else {
155 body = message.getBody();
156 }
157 InputStream is = new ByteArrayInputStream(body.getBytes());
158 final OutputStream os = new ByteArrayOutputStream();
159 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
160
161 @Override
162 public void onReturn(Intent result) {
163 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, 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 {
196 try {
197 DownloadableFile inputFile = this.mXmppConnectionService
198 .getFileBackend().getFile(message, true);
199 DownloadableFile outputFile = this.mXmppConnectionService
200 .getFileBackend().getFile(message, false);
201 outputFile.getParentFile().mkdirs();
202 outputFile.createNewFile();
203 InputStream is = new FileInputStream(inputFile);
204 OutputStream os = new FileOutputStream(outputFile);
205 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
206
207 @Override
208 public void onReturn(Intent result) {
209 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
210 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
211 OpenPgpApi.RESULT_CODE_ERROR)) {
212 case OpenPgpApi.RESULT_CODE_SUCCESS:
213 callback.success(message);
214 break;
215 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
216 callback.userInputRequried(
217 (PendingIntent) result
218 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
219 message);
220 break;
221 case OpenPgpApi.RESULT_CODE_ERROR:
222 callback.error(R.string.openpgp_error, message);
223 break;
224 }
225 }
226 });
227 } catch (final IOException e) {
228 callback.error(R.string.openpgp_error, message);
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 final 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().toBareJid().toString());
256 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
257 ByteArrayOutputStream os = new ByteArrayOutputStream();
258 Intent result = api.executeApi(params, is, os);
259 notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
260 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
261 OpenPgpApi.RESULT_CODE_ERROR)) {
262 case OpenPgpApi.RESULT_CODE_SUCCESS:
263 OpenPgpSignatureResult sigResult = result
264 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
265 if (sigResult != null) {
266 return sigResult.getKeyId();
267 } else {
268 return 0;
269 }
270 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
271 return 0;
272 case OpenPgpApi.RESULT_CODE_ERROR:
273 return 0;
274 }
275 return 0;
276 }
277
278 public void generateSignature(final Account account, String status,
279 final UiCallback<Account> callback) {
280 Intent params = new Intent();
281 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
282 params.setAction(OpenPgpApi.ACTION_SIGN);
283 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
284 InputStream is = new ByteArrayInputStream(status.getBytes());
285 final OutputStream os = new ByteArrayOutputStream();
286 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
287
288 @Override
289 public void onReturn(Intent result) {
290 notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result);
291 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
292 case OpenPgpApi.RESULT_CODE_SUCCESS:
293 StringBuilder signatureBuilder = new StringBuilder();
294 try {
295 os.flush();
296 String[] lines = os.toString().split("\n");
297 boolean sig = false;
298 for (String line : lines) {
299 if (sig) {
300 if (line.contains("END PGP SIGNATURE")) {
301 sig = false;
302 } else {
303 if (!line.contains("Version")) {
304 signatureBuilder.append(line.trim());
305 }
306 }
307 }
308 if (line.contains("BEGIN PGP SIGNATURE")) {
309 sig = true;
310 }
311 }
312 } catch (IOException e) {
313 callback.error(R.string.openpgp_error, account);
314 return;
315 }
316 account.setKey("pgp_signature", signatureBuilder.toString());
317 callback.success(account);
318 return;
319 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
320 callback.userInputRequried((PendingIntent) result
321 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
322 account);
323 return;
324 case OpenPgpApi.RESULT_CODE_ERROR:
325 callback.error(R.string.openpgp_error, account);
326 }
327 }
328 });
329 }
330
331 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
332 Intent params = new Intent();
333 params.setAction(OpenPgpApi.ACTION_GET_KEY);
334 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
335 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
336 .getJid().toBareJid().toString());
337 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
338
339 @Override
340 public void onReturn(Intent result) {
341 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
342 case OpenPgpApi.RESULT_CODE_SUCCESS:
343 callback.success(contact);
344 return;
345 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
346 callback.userInputRequried((PendingIntent) result
347 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
348 contact);
349 return;
350 case OpenPgpApi.RESULT_CODE_ERROR:
351 callback.error(R.string.openpgp_error, contact);
352 }
353 }
354 });
355 }
356
357 public PendingIntent getIntentForKey(Contact contact) {
358 Intent params = new Intent();
359 params.setAction(OpenPgpApi.ACTION_GET_KEY);
360 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
361 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
362 .getJid().toBareJid().toString());
363 Intent result = api.executeApi(params, null, null);
364 return (PendingIntent) result
365 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
366 }
367
368 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
369 Intent params = new Intent();
370 params.setAction(OpenPgpApi.ACTION_GET_KEY);
371 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
372 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
373 Intent result = api.executeApi(params, null, null);
374 return (PendingIntent) result
375 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
376 }
377
378 private void notifyPgpDecryptionService(Account account, String action, final Intent result) {
379 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
380 case OpenPgpApi.RESULT_CODE_SUCCESS:
381 if (OpenPgpApi.ACTION_SIGN.equals(action)) {
382 account.getPgpDecryptionService().onKeychainUnlocked();
383 }
384 break;
385 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
386 account.getPgpDecryptionService().onKeychainLocked();
387 break;
388 }
389 }
390}