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