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 message.ready = true;
165 callback.success(message);
166 break;
167 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
168 callback.userInputRequried((PendingIntent) result
169 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
170 message);
171 break;
172 case OpenPgpApi.RESULT_CODE_ERROR:
173 callback.error(R.string.openpgp_error, message);
174 break;
175 }
176 }
177 });
178 } else if (message.getType() == Message.TYPE_IMAGE) {
179 try {
180 JingleFile inputFile = this.mXmppConnectionService
181 .getFileBackend().getJingleFile(message, true);
182 JingleFile outputFile = this.mXmppConnectionService
183 .getFileBackend().getJingleFile(message, false);
184 outputFile.createNewFile();
185 InputStream is = new FileInputStream(inputFile);
186 OutputStream os = new FileOutputStream(outputFile);
187 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
188
189 @Override
190 public void onReturn(Intent result) {
191 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
192 OpenPgpApi.RESULT_CODE_ERROR)) {
193 case OpenPgpApi.RESULT_CODE_SUCCESS:
194 message.ready = true;
195 callback.success(message);
196 break;
197 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
198 callback.userInputRequried(
199 (PendingIntent) result
200 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
201 message);
202 break;
203 case OpenPgpApi.RESULT_CODE_ERROR:
204 callback.error(R.string.openpgp_error, message);
205 break;
206 }
207 }
208 });
209 } catch (FileNotFoundException e) {
210 Log.d("xmppService", "file not found: " + e.getMessage());
211 } catch (IOException e) {
212 Log.d("xmppService", "io exception during file encrypt");
213 }
214 }
215 }
216
217 public long fetchKeyId(Account account, String status, String signature) {
218 if ((signature == null) || (api == null)) {
219 return 0;
220 }
221 if (status == null) {
222 status = "";
223 }
224 StringBuilder pgpSig = new StringBuilder();
225 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
226 pgpSig.append('\n');
227 pgpSig.append('\n');
228 pgpSig.append(status);
229 pgpSig.append('\n');
230 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
231 pgpSig.append('\n');
232 pgpSig.append('\n');
233 pgpSig.append(signature.replace("\n", "").trim());
234 pgpSig.append('\n');
235 pgpSig.append("-----END PGP SIGNATURE-----");
236 Intent params = new Intent();
237 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
238 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
239 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
240 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
241 ByteArrayOutputStream os = new ByteArrayOutputStream();
242 Intent result = api.executeApi(params, is, os);
243 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
244 OpenPgpApi.RESULT_CODE_ERROR)) {
245 case OpenPgpApi.RESULT_CODE_SUCCESS:
246 OpenPgpSignatureResult sigResult = result
247 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
248 if (sigResult != null) {
249 return sigResult.getKeyId();
250 } else {
251 return 0;
252 }
253 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
254 return 0;
255 case OpenPgpApi.RESULT_CODE_ERROR:
256 Log.d("xmppService",
257 "openpgp error: "
258 + ((OpenPgpError) result
259 .getParcelableExtra(OpenPgpApi.RESULT_ERROR))
260 .getMessage());
261 return 0;
262 }
263 return 0;
264 }
265
266 public void generateSignature(final Account account, String status,
267 final UiCallback<Account> callback) {
268 Intent params = new Intent();
269 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
270 params.setAction(OpenPgpApi.ACTION_SIGN);
271 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
272 InputStream is = new ByteArrayInputStream(status.getBytes());
273 final OutputStream os = new ByteArrayOutputStream();
274 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
275
276 @Override
277 public void onReturn(Intent result) {
278 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
279 case OpenPgpApi.RESULT_CODE_SUCCESS:
280 StringBuilder signatureBuilder = new StringBuilder();
281 String[] lines = os.toString().split("\n");
282 for (int i = 7; i < lines.length - 1; ++i) {
283 signatureBuilder.append(lines[i].trim());
284 }
285 account.setKey("pgp_signature", signatureBuilder.toString());
286 callback.success(account);
287 return;
288 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
289 callback.userInputRequried((PendingIntent) result
290 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
291 account);
292 return;
293 case OpenPgpApi.RESULT_CODE_ERROR:
294 callback.error(R.string.openpgp_error, account);
295 return;
296 }
297 }
298 });
299 }
300
301 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
302 Intent params = new Intent();
303 params.setAction(OpenPgpApi.ACTION_GET_KEY);
304 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
305 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
306 .getJid());
307 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
308
309 @Override
310 public void onReturn(Intent result) {
311 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
312 case OpenPgpApi.RESULT_CODE_SUCCESS:
313 callback.success(contact);
314 return;
315 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
316 callback.userInputRequried((PendingIntent) result
317 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
318 contact);
319 return;
320 case OpenPgpApi.RESULT_CODE_ERROR:
321 callback.error(R.string.openpgp_error, contact);
322 return;
323 }
324 }
325 });
326 }
327
328 public PendingIntent getIntentForKey(Contact contact) {
329 Intent params = new Intent();
330 params.setAction(OpenPgpApi.ACTION_GET_KEY);
331 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
332 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
333 .getJid());
334 Intent result = api.executeApi(params, null, null);
335 return (PendingIntent) result
336 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
337 }
338
339 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
340 Intent params = new Intent();
341 params.setAction(OpenPgpApi.ACTION_GET_KEY);
342 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
343 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid());
344 Intent result = api.executeApi(params, null, null);
345 return (PendingIntent) result
346 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
347 }
348}