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 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
44 .getConversation().getAccount().getJid().toBareJid().toString());
45 if (message.getType() == Message.TYPE_TEXT) {
46 InputStream is = new ByteArrayInputStream(message.getBody()
47 .getBytes());
48 final OutputStream os = new ByteArrayOutputStream();
49 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
50
51 @Override
52 public void onReturn(Intent 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 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 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
145 .getConversation().getAccount().getJid().toBareJid().toString());
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 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
162 OpenPgpApi.RESULT_CODE_ERROR)) {
163 case OpenPgpApi.RESULT_CODE_SUCCESS:
164 try {
165 os.flush();
166 StringBuilder encryptedMessageBody = new StringBuilder();
167 String[] lines = os.toString().split("\n");
168 for (int i = 2; i < lines.length - 1; ++i) {
169 if (!lines[i].contains("Version")) {
170 encryptedMessageBody.append(lines[i].trim());
171 }
172 }
173 message.setEncryptedBody(encryptedMessageBody
174 .toString());
175 callback.success(message);
176 } catch (IOException e) {
177 callback.error(R.string.openpgp_error, message);
178 }
179
180 break;
181 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
182 callback.userInputRequried((PendingIntent) result
183 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
184 message);
185 break;
186 case OpenPgpApi.RESULT_CODE_ERROR:
187 callback.error(R.string.openpgp_error, message);
188 break;
189 }
190 }
191 });
192 } else {
193 try {
194 DownloadableFile inputFile = this.mXmppConnectionService
195 .getFileBackend().getFile(message, true);
196 DownloadableFile outputFile = this.mXmppConnectionService
197 .getFileBackend().getFile(message, false);
198 outputFile.getParentFile().mkdirs();
199 outputFile.createNewFile();
200 InputStream is = new FileInputStream(inputFile);
201 OutputStream os = new FileOutputStream(outputFile);
202 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
203
204 @Override
205 public void onReturn(Intent 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 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
252 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
253 ByteArrayOutputStream os = new ByteArrayOutputStream();
254 Intent result = api.executeApi(params, is, os);
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 generateSignature(final Account account, String status,
274 final UiCallback<Account> callback) {
275 Intent params = new Intent();
276 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
277 params.setAction(OpenPgpApi.ACTION_SIGN);
278 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
279 InputStream is = new ByteArrayInputStream(status.getBytes());
280 final OutputStream os = new ByteArrayOutputStream();
281 api.executeApiAsync(params, is, os, 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 StringBuilder signatureBuilder = new StringBuilder();
288 try {
289 os.flush();
290 String[] lines = os.toString().split("\n");
291 boolean sig = false;
292 for (String line : lines) {
293 if (sig) {
294 if (line.contains("END PGP SIGNATURE")) {
295 sig = false;
296 } else {
297 if (!line.contains("Version")) {
298 signatureBuilder.append(line.trim());
299 }
300 }
301 }
302 if (line.contains("BEGIN PGP SIGNATURE")) {
303 sig = true;
304 }
305 }
306 } catch (IOException e) {
307 callback.error(R.string.openpgp_error, account);
308 return;
309 }
310 account.setKey("pgp_signature", signatureBuilder.toString());
311 callback.success(account);
312 return;
313 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
314 callback.userInputRequried((PendingIntent) result
315 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
316 account);
317 return;
318 case OpenPgpApi.RESULT_CODE_ERROR:
319 callback.error(R.string.openpgp_error, account);
320 }
321 }
322 });
323 }
324
325 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
326 Intent params = new Intent();
327 params.setAction(OpenPgpApi.ACTION_GET_KEY);
328 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
329 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
330 .getJid().toBareJid().toString());
331 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
332
333 @Override
334 public void onReturn(Intent result) {
335 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
336 case OpenPgpApi.RESULT_CODE_SUCCESS:
337 callback.success(contact);
338 return;
339 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
340 callback.userInputRequried((PendingIntent) result
341 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
342 contact);
343 return;
344 case OpenPgpApi.RESULT_CODE_ERROR:
345 callback.error(R.string.openpgp_error, contact);
346 }
347 }
348 });
349 }
350
351 public PendingIntent getIntentForKey(Contact contact) {
352 Intent params = new Intent();
353 params.setAction(OpenPgpApi.ACTION_GET_KEY);
354 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
355 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
356 .getJid().toBareJid().toString());
357 Intent result = api.executeApi(params, null, null);
358 return (PendingIntent) result
359 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
360 }
361
362 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
363 Intent params = new Intent();
364 params.setAction(OpenPgpApi.ACTION_GET_KEY);
365 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
366 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
367 Intent result = api.executeApi(params, null, null);
368 return (PendingIntent) result
369 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
370 }
371}