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