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 mXmppConnectionService.getFileBackend().addImageFileToMedia(outputFile);
110 callback.success(message);
111 return;
112 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
113 callback.userInputRequried(
114 (PendingIntent) result
115 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
116 message);
117 return;
118 case OpenPgpApi.RESULT_CODE_ERROR:
119 callback.error(R.string.openpgp_error, message);
120 }
121 }
122 });
123 } catch (final IOException e) {
124 callback.error(R.string.error_decrypting_file, message);
125 }
126
127 }
128 }
129
130 public void encrypt(final Message message, final UiCallback<Message> callback) {
131 Intent params = new Intent();
132 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
133 final Conversation conversation = message.getConversation();
134 if (conversation.getMode() == Conversation.MODE_SINGLE) {
135 long[] keys = {
136 conversation.getContact().getPgpKeyId(),
137 conversation.getAccount().getPgpId()
138 };
139 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
140 } else {
141 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
142 }
143
144 if (!message.needsUploading()) {
145 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
146 String body;
147 if (message.hasFileOnRemoteHost()) {
148 body = message.getFileParams().url.toString();
149 } else {
150 body = message.getBody();
151 }
152 InputStream is = new ByteArrayInputStream(body.getBytes());
153 final OutputStream os = new ByteArrayOutputStream();
154 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
155
156 @Override
157 public void onReturn(Intent result) {
158 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
159 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
160 OpenPgpApi.RESULT_CODE_ERROR)) {
161 case OpenPgpApi.RESULT_CODE_SUCCESS:
162 try {
163 os.flush();
164 StringBuilder encryptedMessageBody = new StringBuilder();
165 String[] lines = os.toString().split("\n");
166 for (int i = 2; i < lines.length - 1; ++i) {
167 if (!lines[i].contains("Version")) {
168 encryptedMessageBody.append(lines[i].trim());
169 }
170 }
171 message.setEncryptedBody(encryptedMessageBody
172 .toString());
173 callback.success(message);
174 } catch (IOException e) {
175 callback.error(R.string.openpgp_error, message);
176 }
177
178 break;
179 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
180 callback.userInputRequried((PendingIntent) result
181 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
182 message);
183 break;
184 case OpenPgpApi.RESULT_CODE_ERROR:
185 callback.error(R.string.openpgp_error, message);
186 break;
187 }
188 }
189 });
190 } else {
191 try {
192 DownloadableFile inputFile = this.mXmppConnectionService
193 .getFileBackend().getFile(message, true);
194 DownloadableFile outputFile = this.mXmppConnectionService
195 .getFileBackend().getFile(message, false);
196 outputFile.getParentFile().mkdirs();
197 outputFile.createNewFile();
198 final InputStream is = new FileInputStream(inputFile);
199 final OutputStream os = new FileOutputStream(outputFile);
200 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
201
202 @Override
203 public void onReturn(Intent result) {
204 notifyPgpDecryptionService(message.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
205 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
206 OpenPgpApi.RESULT_CODE_ERROR)) {
207 case OpenPgpApi.RESULT_CODE_SUCCESS:
208 try {
209 os.flush();
210 } catch (IOException ignored) {
211 //ignored
212 }
213 FileBackend.close(os);
214 callback.success(message);
215 break;
216 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
217 callback.userInputRequried(
218 (PendingIntent) result
219 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
220 message);
221 break;
222 case OpenPgpApi.RESULT_CODE_ERROR:
223 callback.error(R.string.openpgp_error, message);
224 break;
225 }
226 }
227 });
228 } catch (final IOException e) {
229 callback.error(R.string.openpgp_error, message);
230 }
231 }
232 }
233
234 public long fetchKeyId(Account account, String status, String signature) {
235 if ((signature == null) || (api == null)) {
236 return 0;
237 }
238 if (status == null) {
239 status = "";
240 }
241 final StringBuilder pgpSig = new StringBuilder();
242 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
243 pgpSig.append('\n');
244 pgpSig.append('\n');
245 pgpSig.append(status);
246 pgpSig.append('\n');
247 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
248 pgpSig.append('\n');
249 pgpSig.append('\n');
250 pgpSig.append(signature.replace("\n", "").trim());
251 pgpSig.append('\n');
252 pgpSig.append("-----END PGP SIGNATURE-----");
253 Intent params = new Intent();
254 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
255 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
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 chooseKey(final Account account, final UiCallback<Account> callback) {
279 Intent p = new Intent();
280 p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
281 api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
282
283 @Override
284 public void onReturn(Intent result) {
285 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
286 case OpenPgpApi.RESULT_CODE_SUCCESS:
287 callback.success(account);
288 return;
289 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
290 callback.userInputRequried((PendingIntent) result
291 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
292 account);
293 return;
294 case OpenPgpApi.RESULT_CODE_ERROR:
295 callback.error(R.string.openpgp_error, account);
296 }
297 }
298 });
299 }
300
301 public void generateSignature(final Account account, String status,
302 final UiCallback<Account> callback) {
303 if (account.getPgpId() == -1) {
304 return;
305 }
306 Intent params = new Intent();
307 params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
308 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
309 params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
310 InputStream is = new ByteArrayInputStream(status.getBytes());
311 final OutputStream os = new ByteArrayOutputStream();
312 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
313
314 @Override
315 public void onReturn(Intent result) {
316 notifyPgpDecryptionService(account, OpenPgpApi.ACTION_SIGN, result);
317 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
318 case OpenPgpApi.RESULT_CODE_SUCCESS:
319 StringBuilder signatureBuilder = new StringBuilder();
320 try {
321 os.flush();
322 String[] lines = os.toString().split("\n");
323 boolean sig = false;
324 for (String line : lines) {
325 if (sig) {
326 if (line.contains("END PGP SIGNATURE")) {
327 sig = false;
328 } else {
329 if (!line.contains("Version")) {
330 signatureBuilder.append(line.trim());
331 }
332 }
333 }
334 if (line.contains("BEGIN PGP SIGNATURE")) {
335 sig = true;
336 }
337 }
338 } catch (IOException e) {
339 callback.error(R.string.openpgp_error, account);
340 return;
341 }
342 account.setPgpSignature(signatureBuilder.toString());
343 callback.success(account);
344 return;
345 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
346 callback.userInputRequried((PendingIntent) result
347 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
348 account);
349 return;
350 case OpenPgpApi.RESULT_CODE_ERROR:
351 callback.error(R.string.openpgp_error, account);
352 }
353 }
354 });
355 }
356
357 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
358 Intent params = new Intent();
359 params.setAction(OpenPgpApi.ACTION_GET_KEY);
360 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
361 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
362
363 @Override
364 public void onReturn(Intent result) {
365 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
366 case OpenPgpApi.RESULT_CODE_SUCCESS:
367 callback.success(contact);
368 return;
369 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
370 callback.userInputRequried((PendingIntent) result
371 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
372 contact);
373 return;
374 case OpenPgpApi.RESULT_CODE_ERROR:
375 callback.error(R.string.openpgp_error, contact);
376 }
377 }
378 });
379 }
380
381 public PendingIntent getIntentForKey(Contact contact) {
382 Intent params = new Intent();
383 params.setAction(OpenPgpApi.ACTION_GET_KEY);
384 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
385 Intent result = api.executeApi(params, null, null);
386 return (PendingIntent) result
387 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
388 }
389
390 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
391 Intent params = new Intent();
392 params.setAction(OpenPgpApi.ACTION_GET_KEY);
393 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
394 Intent result = api.executeApi(params, null, null);
395 return (PendingIntent) result
396 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
397 }
398
399 private void notifyPgpDecryptionService(Account account, String action, final Intent result) {
400 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
401 case OpenPgpApi.RESULT_CODE_SUCCESS:
402 if (OpenPgpApi.ACTION_SIGN.equals(action)) {
403 account.getPgpDecryptionService().onKeychainUnlocked();
404 }
405 break;
406 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
407 account.getPgpDecryptionService().onKeychainLocked();
408 break;
409 }
410 }
411}