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