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