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