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