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