1package eu.siacs.conversations.crypto;
2
3import java.io.ByteArrayInputStream;
4import java.io.ByteArrayOutputStream;
5import java.io.FileInputStream;
6import java.io.FileNotFoundException;
7import java.io.FileOutputStream;
8import java.io.IOException;
9import java.io.InputStream;
10import java.io.OutputStream;
11import java.net.URL;
12
13import org.openintents.openpgp.OpenPgpSignatureResult;
14import org.openintents.openpgp.util.OpenPgpApi;
15import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
16
17import eu.siacs.conversations.R;
18import eu.siacs.conversations.entities.Account;
19import eu.siacs.conversations.entities.Contact;
20import eu.siacs.conversations.entities.Conversation;
21import eu.siacs.conversations.entities.DownloadableFile;
22import eu.siacs.conversations.entities.Message;
23import eu.siacs.conversations.services.XmppConnectionService;
24import eu.siacs.conversations.ui.UiCallback;
25import android.app.PendingIntent;
26import android.content.Intent;
27import android.graphics.BitmapFactory;
28import android.net.Uri;
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 if (message.trusted() && message.bodyContainsDownloadable()) {
62 mXmppConnectionService.getHttpConnectionManager()
63 .createNewConnection(message);
64 }
65 callback.success(message);
66 }
67 } catch (IOException e) {
68 callback.error(R.string.openpgp_error, message);
69 return;
70 }
71
72 return;
73 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
74 callback.userInputRequried((PendingIntent) result
75 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
76 message);
77 return;
78 case OpenPgpApi.RESULT_CODE_ERROR:
79 callback.error(R.string.openpgp_error, message);
80 }
81 }
82 });
83 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
84 try {
85 final DownloadableFile inputFile = this.mXmppConnectionService
86 .getFileBackend().getFile(message, false);
87 final DownloadableFile outputFile = this.mXmppConnectionService
88 .getFileBackend().getFile(message, true);
89 outputFile.getParentFile().mkdirs();
90 outputFile.createNewFile();
91 InputStream is = new FileInputStream(inputFile);
92 OutputStream os = new FileOutputStream(outputFile);
93 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
94
95 @Override
96 public void onReturn(Intent result) {
97 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
98 OpenPgpApi.RESULT_CODE_ERROR)) {
99 case OpenPgpApi.RESULT_CODE_SUCCESS:
100 URL url = message.getImageParams().url;
101 BitmapFactory.Options options = new BitmapFactory.Options();
102 options.inJustDecodeBounds = true;
103 BitmapFactory.decodeFile(
104 outputFile.getAbsolutePath(), options);
105 int imageHeight = options.outHeight;
106 int imageWidth = options.outWidth;
107 if (url == null) {
108 message.setBody(Long.toString(outputFile
109 .getSize())
110 + '|'
111 + imageWidth
112 + '|'
113 + imageHeight);
114 } else {
115 message.setBody(url.toString() + "|"
116 + Long.toString(outputFile.getSize())
117 + '|' + imageWidth + '|' + imageHeight);
118 }
119 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
120 PgpEngine.this.mXmppConnectionService
121 .updateMessage(message);
122 inputFile.delete();
123 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
124 intent.setData(Uri.fromFile(outputFile));
125 mXmppConnectionService.sendBroadcast(intent);
126 callback.success(message);
127 return;
128 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
129 callback.userInputRequried(
130 (PendingIntent) result
131 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
132 message);
133 return;
134 case OpenPgpApi.RESULT_CODE_ERROR:
135 callback.error(R.string.openpgp_error, message);
136 }
137 }
138 });
139 } catch (final IOException e) {
140 callback.error(R.string.error_decrypting_file, message);
141 }
142
143 }
144 }
145
146 public void encrypt(final Message message,
147 final UiCallback<Message> callback) {
148
149 Intent params = new Intent();
150 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
151 if (message.getConversation().getMode() == Conversation.MODE_SINGLE) {
152 long[] keys = { message.getConversation().getContact()
153 .getPgpKeyId() };
154 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
155 } else {
156 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, message.getConversation()
157 .getMucOptions().getPgpKeyIds());
158 }
159 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, message
160 .getConversation().getAccount().getJid().toBareJid().toString());
161
162 if (message.getType() == Message.TYPE_TEXT) {
163 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
164
165 InputStream is = new ByteArrayInputStream(message.getBody()
166 .getBytes());
167 final OutputStream os = new ByteArrayOutputStream();
168 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
169
170 @Override
171 public void onReturn(Intent result) {
172 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
173 OpenPgpApi.RESULT_CODE_ERROR)) {
174 case OpenPgpApi.RESULT_CODE_SUCCESS:
175 try {
176 os.flush();
177 StringBuilder encryptedMessageBody = new StringBuilder();
178 String[] lines = os.toString().split("\n");
179 for (int i = 2; i < lines.length - 1; ++i) {
180 if (!lines[i].contains("Version")) {
181 encryptedMessageBody.append(lines[i].trim());
182 }
183 }
184 message.setEncryptedBody(encryptedMessageBody
185 .toString());
186 callback.success(message);
187 } catch (IOException e) {
188 callback.error(R.string.openpgp_error, message);
189 }
190
191 break;
192 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
193 callback.userInputRequried((PendingIntent) result
194 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
195 message);
196 break;
197 case OpenPgpApi.RESULT_CODE_ERROR:
198 callback.error(R.string.openpgp_error, message);
199 break;
200 }
201 }
202 });
203 } else if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
204 try {
205 DownloadableFile inputFile = this.mXmppConnectionService
206 .getFileBackend().getFile(message, true);
207 DownloadableFile outputFile = this.mXmppConnectionService
208 .getFileBackend().getFile(message, false);
209 outputFile.getParentFile().mkdirs();
210 outputFile.createNewFile();
211 InputStream is = new FileInputStream(inputFile);
212 OutputStream os = new FileOutputStream(outputFile);
213 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
214
215 @Override
216 public void onReturn(Intent result) {
217 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
218 OpenPgpApi.RESULT_CODE_ERROR)) {
219 case OpenPgpApi.RESULT_CODE_SUCCESS:
220 callback.success(message);
221 break;
222 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
223 callback.userInputRequried(
224 (PendingIntent) result
225 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
226 message);
227 break;
228 case OpenPgpApi.RESULT_CODE_ERROR:
229 callback.error(R.string.openpgp_error, message);
230 break;
231 }
232 }
233 });
234 } catch (final IOException e) {
235 callback.error(R.string.openpgp_error, message);
236 }
237 }
238 }
239
240 public long fetchKeyId(Account account, String status, String signature) {
241 if ((signature == null) || (api == null)) {
242 return 0;
243 }
244 if (status == null) {
245 status = "";
246 }
247 final StringBuilder pgpSig = new StringBuilder();
248 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
249 pgpSig.append('\n');
250 pgpSig.append('\n');
251 pgpSig.append(status);
252 pgpSig.append('\n');
253 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
254 pgpSig.append('\n');
255 pgpSig.append('\n');
256 pgpSig.append(signature.replace("\n", "").trim());
257 pgpSig.append('\n');
258 pgpSig.append("-----END PGP SIGNATURE-----");
259 Intent params = new Intent();
260 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
261 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
262 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
263 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
264 ByteArrayOutputStream os = new ByteArrayOutputStream();
265 Intent result = api.executeApi(params, is, os);
266 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
267 OpenPgpApi.RESULT_CODE_ERROR)) {
268 case OpenPgpApi.RESULT_CODE_SUCCESS:
269 OpenPgpSignatureResult sigResult = result
270 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
271 if (sigResult != null) {
272 return sigResult.getKeyId();
273 } else {
274 return 0;
275 }
276 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
277 return 0;
278 case OpenPgpApi.RESULT_CODE_ERROR:
279 return 0;
280 }
281 return 0;
282 }
283
284 public void generateSignature(final Account account, String status,
285 final UiCallback<Account> callback) {
286 Intent params = new Intent();
287 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
288 params.setAction(OpenPgpApi.ACTION_SIGN);
289 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
290 InputStream is = new ByteArrayInputStream(status.getBytes());
291 final OutputStream os = new ByteArrayOutputStream();
292 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
293
294 @Override
295 public void onReturn(Intent result) {
296 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
297 case OpenPgpApi.RESULT_CODE_SUCCESS:
298 StringBuilder signatureBuilder = new StringBuilder();
299 try {
300 os.flush();
301 String[] lines = os.toString().split("\n");
302 boolean sig = false;
303 for (String line : lines) {
304 if (sig) {
305 if (line.contains("END PGP SIGNATURE")) {
306 sig = false;
307 } else {
308 if (!line.contains("Version")) {
309 signatureBuilder.append(line.trim());
310 }
311 }
312 }
313 if (line.contains("BEGIN PGP SIGNATURE")) {
314 sig = true;
315 }
316 }
317 } catch (IOException e) {
318 callback.error(R.string.openpgp_error, account);
319 return;
320 }
321 account.setKey("pgp_signature", signatureBuilder.toString());
322 callback.success(account);
323 return;
324 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
325 callback.userInputRequried((PendingIntent) result
326 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
327 account);
328 return;
329 case OpenPgpApi.RESULT_CODE_ERROR:
330 callback.error(R.string.openpgp_error, account);
331 }
332 }
333 });
334 }
335
336 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
337 Intent params = new Intent();
338 params.setAction(OpenPgpApi.ACTION_GET_KEY);
339 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
340 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
341 .getJid().toBareJid().toString());
342 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
343
344 @Override
345 public void onReturn(Intent result) {
346 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
347 case OpenPgpApi.RESULT_CODE_SUCCESS:
348 callback.success(contact);
349 return;
350 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
351 callback.userInputRequried((PendingIntent) result
352 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
353 contact);
354 return;
355 case OpenPgpApi.RESULT_CODE_ERROR:
356 callback.error(R.string.openpgp_error, contact);
357 }
358 }
359 });
360 }
361
362 public PendingIntent getIntentForKey(Contact contact) {
363 Intent params = new Intent();
364 params.setAction(OpenPgpApi.ACTION_GET_KEY);
365 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
366 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
367 .getJid().toBareJid().toString());
368 Intent result = api.executeApi(params, null, null);
369 return (PendingIntent) result
370 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
371 }
372
373 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
374 Intent params = new Intent();
375 params.setAction(OpenPgpApi.ACTION_GET_KEY);
376 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
377 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
378 Intent result = api.executeApi(params, null, null);
379 return (PendingIntent) result
380 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
381 }
382}