1package eu.siacs.conversations.crypto;
2
3import android.app.PendingIntent;
4import android.content.Intent;
5import android.util.Log;
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.Config;
21import eu.siacs.conversations.R;
22import eu.siacs.conversations.entities.Account;
23import eu.siacs.conversations.entities.Contact;
24import eu.siacs.conversations.entities.Conversation;
25import eu.siacs.conversations.entities.DownloadableFile;
26import eu.siacs.conversations.entities.Message;
27import eu.siacs.conversations.http.HttpConnectionManager;
28import eu.siacs.conversations.persistance.FileBackend;
29import eu.siacs.conversations.services.XmppConnectionService;
30import eu.siacs.conversations.ui.UiCallback;
31
32public class PgpEngine {
33 private OpenPgpApi api;
34 private XmppConnectionService mXmppConnectionService;
35
36 public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
37 this.api = api;
38 this.mXmppConnectionService = service;
39 }
40
41 public void decrypt(final Message message, final UiCallback<Message> callback) {
42 Intent params = new Intent();
43 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
44 final String uuid = message.getUuid();
45 if (message.getType() == Message.TYPE_TEXT) {
46 InputStream is = new ByteArrayInputStream(message.getBody().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, OpenPgpApi.RESULT_CODE_ERROR)) {
54 case OpenPgpApi.RESULT_CODE_SUCCESS:
55 try {
56 os.flush();
57 if (message.getEncryption() == Message.ENCRYPTION_PGP
58 && message.getUuid().equals(uuid)) {
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 mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile);
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, final UiCallback<Message> callback) {
132 Intent params = new Intent();
133 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
134 final Conversation conversation = message.getConversation();
135 if (conversation.getMode() == Conversation.MODE_SINGLE) {
136 long[] keys = {
137 conversation.getContact().getPgpKeyId(),
138 conversation.getAccount().getPgpId()
139 };
140 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
141 } else {
142 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.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.getConversation().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 final InputStream is = new FileInputStream(inputFile);
200 final 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.getConversation().getAccount(), OpenPgpApi.ACTION_ENCRYPT, result);
206 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
207 OpenPgpApi.RESULT_CODE_ERROR)) {
208 case OpenPgpApi.RESULT_CODE_SUCCESS:
209 try {
210 os.flush();
211 } catch (IOException ignored) {
212 //ignored
213 }
214 FileBackend.close(os);
215 callback.success(message);
216 break;
217 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
218 callback.userInputRequried(
219 (PendingIntent) result
220 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
221 message);
222 break;
223 case OpenPgpApi.RESULT_CODE_ERROR:
224 callback.error(R.string.openpgp_error, message);
225 break;
226 }
227 }
228 });
229 } catch (final IOException e) {
230 callback.error(R.string.openpgp_error, message);
231 }
232 }
233 }
234
235 public long fetchKeyId(Account account, String status, String signature) {
236 if ((signature == null) || (api == null)) {
237 return 0;
238 }
239 if (status == null) {
240 status = "";
241 }
242 final StringBuilder pgpSig = new StringBuilder();
243 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
244 pgpSig.append('\n');
245 pgpSig.append('\n');
246 pgpSig.append(status);
247 pgpSig.append('\n');
248 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
249 pgpSig.append('\n');
250 pgpSig.append('\n');
251 pgpSig.append(signature.replace("\n", "").trim());
252 pgpSig.append('\n');
253 pgpSig.append("-----END PGP SIGNATURE-----");
254 Intent params = new Intent();
255 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
256 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
257 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
258 ByteArrayOutputStream os = new ByteArrayOutputStream();
259 Intent result = api.executeApi(params, is, os);
260 notifyPgpDecryptionService(account, OpenPgpApi.ACTION_DECRYPT_VERIFY, result);
261 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
262 OpenPgpApi.RESULT_CODE_ERROR)) {
263 case OpenPgpApi.RESULT_CODE_SUCCESS:
264 OpenPgpSignatureResult sigResult = result
265 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
266 if (sigResult != null) {
267 return sigResult.getKeyId();
268 } else {
269 return 0;
270 }
271 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
272 return 0;
273 case OpenPgpApi.RESULT_CODE_ERROR:
274 return 0;
275 }
276 return 0;
277 }
278
279 public void chooseKey(final Account account, final UiCallback<Account> callback) {
280 Intent p = new Intent();
281 p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
282 api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
283
284 @Override
285 public void onReturn(Intent result) {
286 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
287 case OpenPgpApi.RESULT_CODE_SUCCESS:
288 callback.success(account);
289 return;
290 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
291 callback.userInputRequried((PendingIntent) result
292 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
293 account);
294 return;
295 case OpenPgpApi.RESULT_CODE_ERROR:
296 callback.error(R.string.openpgp_error, account);
297 }
298 }
299 });
300 }
301
302 public void generateSignature(final Account account, String status,
303 final UiCallback<Account> callback) {
304 if (account.getPgpId() == 0) {
305 return;
306 }
307 Intent params = new Intent();
308 params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
309 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
310 params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
311 InputStream is = new ByteArrayInputStream(status.getBytes());
312 final OutputStream os = new ByteArrayOutputStream();
313 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": signing status message \""+status+"\"");
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}