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