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