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