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.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 decrypt(final Message message, final UiCallback<Message> callback) {
41 Intent params = new Intent();
42 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
43 final String uuid = message.getUuid();
44 if (message.getType() == Message.TYPE_TEXT) {
45 InputStream is = new ByteArrayInputStream(message.getBody().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 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
52 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
53 case OpenPgpApi.RESULT_CODE_SUCCESS:
54 try {
55 os.flush();
56 if (message.getEncryption() == Message.ENCRYPTION_PGP
57 && message.getUuid().equals(uuid)) {
58 message.setBody(os.toString());
59 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
60 final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
61 if (message.trusted()
62 && message.treatAsDownloadable() != Message.Decision.NEVER
63 && manager.getAutoAcceptFileSize() > 0) {
64 manager.createNewDownloadConnection(message);
65 }
66 mXmppConnectionService.updateMessage(message);
67 callback.success(message);
68 }
69 } catch (IOException e) {
70 callback.error(R.string.openpgp_error, message);
71 return;
72 }
73
74 return;
75 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
76 callback.userInputRequried((PendingIntent) result
77 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
78 message);
79 return;
80 case OpenPgpApi.RESULT_CODE_ERROR:
81 callback.error(R.string.openpgp_error, message);
82 }
83 }
84 });
85 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
86 try {
87 final DownloadableFile inputFile = this.mXmppConnectionService
88 .getFileBackend().getFile(message, false);
89 final DownloadableFile outputFile = this.mXmppConnectionService
90 .getFileBackend().getFile(message, true);
91 outputFile.getParentFile().mkdirs();
92 outputFile.createNewFile();
93 InputStream is = new FileInputStream(inputFile);
94 OutputStream os = new FileOutputStream(outputFile);
95 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
96
97 @Override
98 public void onReturn(Intent result) {
99 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
100 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
101 OpenPgpApi.RESULT_CODE_ERROR)) {
102 case OpenPgpApi.RESULT_CODE_SUCCESS:
103 URL url = message.getFileParams().url;
104 mXmppConnectionService.getFileBackend().updateFileParams(message,url);
105 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
106 PgpEngine.this.mXmppConnectionService
107 .updateMessage(message);
108 inputFile.delete();
109 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
110 intent.setData(Uri.fromFile(outputFile));
111 mXmppConnectionService.sendBroadcast(intent);
112 callback.success(message);
113 return;
114 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
115 callback.userInputRequried(
116 (PendingIntent) result
117 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
118 message);
119 return;
120 case OpenPgpApi.RESULT_CODE_ERROR:
121 callback.error(R.string.openpgp_error, message);
122 }
123 }
124 });
125 } catch (final IOException e) {
126 callback.error(R.string.error_decrypting_file, message);
127 }
128
129 }
130 }
131
132 public void encrypt(final Message message, final UiCallback<Message> callback) {
133 Intent params = new Intent();
134 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
135 final Conversation conversation = message.getConversation();
136 if (conversation.getMode() == Conversation.MODE_SINGLE) {
137 long[] keys = {
138 conversation.getContact().getPgpKeyId(),
139 conversation.getAccount().getPgpId()
140 };
141 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
142 } else {
143 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
144 }
145
146 if (!message.needsUploading()) {
147 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
148 String body;
149 if (message.hasFileOnRemoteHost()) {
150 body = message.getFileParams().url.toString();
151 } else {
152 body = message.getBody();
153 }
154 InputStream is = new ByteArrayInputStream(body.getBytes());
155 final OutputStream os = new ByteArrayOutputStream();
156 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
157
158 @Override
159 public void onReturn(Intent result) {
160 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
161 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
162 OpenPgpApi.RESULT_CODE_ERROR)) {
163 case OpenPgpApi.RESULT_CODE_SUCCESS:
164 try {
165 os.flush();
166 StringBuilder encryptedMessageBody = new StringBuilder();
167 String[] lines = os.toString().split("\n");
168 for (int i = 2; i < lines.length - 1; ++i) {
169 if (!lines[i].contains("Version")) {
170 encryptedMessageBody.append(lines[i].trim());
171 }
172 }
173 message.setEncryptedBody(encryptedMessageBody
174 .toString());
175 callback.success(message);
176 } catch (IOException e) {
177 callback.error(R.string.openpgp_error, message);
178 }
179
180 break;
181 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
182 callback.userInputRequried((PendingIntent) result
183 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
184 message);
185 break;
186 case OpenPgpApi.RESULT_CODE_ERROR:
187 callback.error(R.string.openpgp_error, message);
188 break;
189 }
190 }
191 });
192 } else {
193 try {
194 DownloadableFile inputFile = this.mXmppConnectionService
195 .getFileBackend().getFile(message, true);
196 DownloadableFile outputFile = this.mXmppConnectionService
197 .getFileBackend().getFile(message, false);
198 outputFile.getParentFile().mkdirs();
199 outputFile.createNewFile();
200 final InputStream is = new FileInputStream(inputFile);
201 final OutputStream os = new FileOutputStream(outputFile);
202 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
203
204 @Override
205 public void onReturn(Intent result) {
206 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
207 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
208 OpenPgpApi.RESULT_CODE_ERROR)) {
209 case OpenPgpApi.RESULT_CODE_SUCCESS:
210 try {
211 os.flush();
212 } catch (IOException ignored) {
213 //ignored
214 }
215 FileBackend.close(os);
216 callback.success(message);
217 break;
218 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
219 callback.userInputRequried(
220 (PendingIntent) result
221 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
222 message);
223 break;
224 case OpenPgpApi.RESULT_CODE_ERROR:
225 callback.error(R.string.openpgp_error, message);
226 break;
227 }
228 }
229 });
230 } catch (final IOException e) {
231 callback.error(R.string.openpgp_error, message);
232 }
233 }
234 }
235
236 public long fetchKeyId(Account account, String status, String signature) {
237 if ((signature == null) || (api == null)) {
238 return 0;
239 }
240 if (status == null) {
241 status = "";
242 }
243 final StringBuilder pgpSig = new StringBuilder();
244 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
245 pgpSig.append('\n');
246 pgpSig.append('\n');
247 pgpSig.append(status);
248 pgpSig.append('\n');
249 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
250 pgpSig.append('\n');
251 pgpSig.append('\n');
252 pgpSig.append(signature.replace("\n", "").trim());
253 pgpSig.append('\n');
254 pgpSig.append("-----END PGP SIGNATURE-----");
255 Intent params = new Intent();
256 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
257 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
258 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
259 ByteArrayOutputStream os = new ByteArrayOutputStream();
260 Intent result = api.executeApi(params, is, os);
261 notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
262 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
263 OpenPgpApi.RESULT_CODE_ERROR)) {
264 case OpenPgpApi.RESULT_CODE_SUCCESS:
265 OpenPgpSignatureResult sigResult = result
266 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
267 if (sigResult != null) {
268 return sigResult.getKeyId();
269 } else {
270 return 0;
271 }
272 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
273 return 0;
274 case OpenPgpApi.RESULT_CODE_ERROR:
275 return 0;
276 }
277 return 0;
278 }
279
280 public void chooseKey(final Account account, final UiCallback<Account> callback) {
281 Intent p = new Intent();
282 p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
283 api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
284
285 @Override
286 public void onReturn(Intent result) {
287 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
288 case OpenPgpApi.RESULT_CODE_SUCCESS:
289 callback.success(account);
290 return;
291 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
292 callback.userInputRequried((PendingIntent) result
293 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
294 account);
295 return;
296 case OpenPgpApi.RESULT_CODE_ERROR:
297 callback.error(R.string.openpgp_error, account);
298 }
299 }
300 });
301 }
302
303 public void generateSignature(final Account account, String status,
304 final UiCallback<Account> callback) {
305 if (account.getPgpId() == -1) {
306 return;
307 }
308 Intent params = new Intent();
309 params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
310 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
311 params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
312 InputStream is = new ByteArrayInputStream(status.getBytes());
313 final OutputStream os = new ByteArrayOutputStream();
314 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
315
316 @Override
317 public void onReturn(Intent result) {
318 notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result);
319 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
320 case OpenPgpApi.RESULT_CODE_SUCCESS:
321 StringBuilder signatureBuilder = new StringBuilder();
322 try {
323 os.flush();
324 String[] lines = os.toString().split("\n");
325 boolean sig = false;
326 for (String line : lines) {
327 if (sig) {
328 if (line.contains("END PGP SIGNATURE")) {
329 sig = false;
330 } else {
331 if (!line.contains("Version")) {
332 signatureBuilder.append(line.trim());
333 }
334 }
335 }
336 if (line.contains("BEGIN PGP SIGNATURE")) {
337 sig = true;
338 }
339 }
340 } catch (IOException e) {
341 callback.error(R.string.openpgp_error, account);
342 return;
343 }
344 account.setPgpSignature(signatureBuilder.toString());
345 callback.success(account);
346 return;
347 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
348 callback.userInputRequried((PendingIntent) result
349 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
350 account);
351 return;
352 case OpenPgpApi.RESULT_CODE_ERROR:
353 callback.error(R.string.openpgp_error, account);
354 }
355 }
356 });
357 }
358
359 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
360 Intent params = new Intent();
361 params.setAction(OpenPgpApi.ACTION_GET_KEY);
362 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
363 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
364
365 @Override
366 public void onReturn(Intent result) {
367 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
368 case OpenPgpApi.RESULT_CODE_SUCCESS:
369 callback.success(contact);
370 return;
371 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
372 callback.userInputRequried((PendingIntent) result
373 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
374 contact);
375 return;
376 case OpenPgpApi.RESULT_CODE_ERROR:
377 callback.error(R.string.openpgp_error, contact);
378 }
379 }
380 });
381 }
382
383 public PendingIntent getIntentForKey(Contact contact) {
384 Intent params = new Intent();
385 params.setAction(OpenPgpApi.ACTION_GET_KEY);
386 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
387 Intent result = api.executeApi(params, null, null);
388 return (PendingIntent) result
389 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
390 }
391
392 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
393 Intent params = new Intent();
394 params.setAction(OpenPgpApi.ACTION_GET_KEY);
395 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
396 Intent result = api.executeApi(params, null, null);
397 return (PendingIntent) result
398 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
399 }
400
401 private void notifyPgpDecryptionService(Account account, String action, final Intent result) {
402 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
403 case OpenPgpApi.RESULT_CODE_SUCCESS:
404 if (OpenPgpApi.ACTION_SIGN.equals(action)) {
405 account.getPgpDecryptionService().onKeychainUnlocked();
406 }
407 break;
408 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
409 account.getPgpDecryptionService().onKeychainLocked();
410 break;
411 }
412 }
413}