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