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.treatAsDownloadable() != Message.Decision.NEVER
63 && manager.getAutoAcceptFileSize() > 0) {
64 manager.createNewDownloadConnection(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.getFileParams().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.needsUploading()) {
147 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
148 String body;
149 if (message.hasFileOnRemoteHost()) {
150 body = message.getFileParams().url.toString();
151 } else {
152 body = message.getBody();
153 }
154 InputStream is = new ByteArrayInputStream(body.getBytes());
155 final OutputStream os = new ByteArrayOutputStream();
156 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
157
158 @Override
159 public void onReturn(Intent result) {
160 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
161 OpenPgpApi.RESULT_CODE_ERROR)) {
162 case OpenPgpApi.RESULT_CODE_SUCCESS:
163 try {
164 os.flush();
165 StringBuilder encryptedMessageBody = new StringBuilder();
166 String[] lines = os.toString().split("\n");
167 for (int i = 2; i < lines.length - 1; ++i) {
168 if (!lines[i].contains("Version")) {
169 encryptedMessageBody.append(lines[i].trim());
170 }
171 }
172 message.setEncryptedBody(encryptedMessageBody
173 .toString());
174 callback.success(message);
175 } catch (IOException e) {
176 callback.error(R.string.openpgp_error, message);
177 }
178
179 break;
180 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
181 callback.userInputRequried((PendingIntent) result
182 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
183 message);
184 break;
185 case OpenPgpApi.RESULT_CODE_ERROR:
186 callback.error(R.string.openpgp_error, message);
187 break;
188 }
189 }
190 });
191 } else {
192 try {
193 DownloadableFile inputFile = this.mXmppConnectionService
194 .getFileBackend().getFile(message, true);
195 DownloadableFile outputFile = this.mXmppConnectionService
196 .getFileBackend().getFile(message, false);
197 outputFile.getParentFile().mkdirs();
198 outputFile.createNewFile();
199 InputStream is = new FileInputStream(inputFile);
200 OutputStream os = new FileOutputStream(outputFile);
201 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
202
203 @Override
204 public void onReturn(Intent result) {
205 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
206 OpenPgpApi.RESULT_CODE_ERROR)) {
207 case OpenPgpApi.RESULT_CODE_SUCCESS:
208 callback.success(message);
209 break;
210 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
211 callback.userInputRequried(
212 (PendingIntent) result
213 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
214 message);
215 break;
216 case OpenPgpApi.RESULT_CODE_ERROR:
217 callback.error(R.string.openpgp_error, message);
218 break;
219 }
220 }
221 });
222 } catch (final IOException e) {
223 callback.error(R.string.openpgp_error, message);
224 }
225 }
226 }
227
228 public long fetchKeyId(Account account, String status, String signature) {
229 if ((signature == null) || (api == null)) {
230 return 0;
231 }
232 if (status == null) {
233 status = "";
234 }
235 final StringBuilder pgpSig = new StringBuilder();
236 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
237 pgpSig.append('\n');
238 pgpSig.append('\n');
239 pgpSig.append(status);
240 pgpSig.append('\n');
241 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
242 pgpSig.append('\n');
243 pgpSig.append('\n');
244 pgpSig.append(signature.replace("\n", "").trim());
245 pgpSig.append('\n');
246 pgpSig.append("-----END PGP SIGNATURE-----");
247 Intent params = new Intent();
248 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
249 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
250 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
251 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
252 ByteArrayOutputStream os = new ByteArrayOutputStream();
253 Intent result = api.executeApi(params, is, os);
254 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
255 OpenPgpApi.RESULT_CODE_ERROR)) {
256 case OpenPgpApi.RESULT_CODE_SUCCESS:
257 OpenPgpSignatureResult sigResult = result
258 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
259 if (sigResult != null) {
260 return sigResult.getKeyId();
261 } else {
262 return 0;
263 }
264 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
265 return 0;
266 case OpenPgpApi.RESULT_CODE_ERROR:
267 return 0;
268 }
269 return 0;
270 }
271
272 public void generateSignature(final Account account, String status,
273 final UiCallback<Account> callback) {
274 Intent params = new Intent();
275 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
276 params.setAction(OpenPgpApi.ACTION_SIGN);
277 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
278 InputStream is = new ByteArrayInputStream(status.getBytes());
279 final OutputStream os = new ByteArrayOutputStream();
280 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
281
282 @Override
283 public void onReturn(Intent result) {
284 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
285 case OpenPgpApi.RESULT_CODE_SUCCESS:
286 StringBuilder signatureBuilder = new StringBuilder();
287 try {
288 os.flush();
289 String[] lines = os.toString().split("\n");
290 boolean sig = false;
291 for (String line : lines) {
292 if (sig) {
293 if (line.contains("END PGP SIGNATURE")) {
294 sig = false;
295 } else {
296 if (!line.contains("Version")) {
297 signatureBuilder.append(line.trim());
298 }
299 }
300 }
301 if (line.contains("BEGIN PGP SIGNATURE")) {
302 sig = true;
303 }
304 }
305 } catch (IOException e) {
306 callback.error(R.string.openpgp_error, account);
307 return;
308 }
309 account.setKey("pgp_signature", signatureBuilder.toString());
310 callback.success(account);
311 return;
312 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
313 callback.userInputRequried((PendingIntent) result
314 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
315 account);
316 return;
317 case OpenPgpApi.RESULT_CODE_ERROR:
318 callback.error(R.string.openpgp_error, account);
319 }
320 }
321 });
322 }
323
324 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
325 Intent params = new Intent();
326 params.setAction(OpenPgpApi.ACTION_GET_KEY);
327 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
328 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
329 .getJid().toBareJid().toString());
330 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
331
332 @Override
333 public void onReturn(Intent result) {
334 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
335 case OpenPgpApi.RESULT_CODE_SUCCESS:
336 callback.success(contact);
337 return;
338 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
339 callback.userInputRequried((PendingIntent) result
340 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
341 contact);
342 return;
343 case OpenPgpApi.RESULT_CODE_ERROR:
344 callback.error(R.string.openpgp_error, contact);
345 }
346 }
347 });
348 }
349
350 public PendingIntent getIntentForKey(Contact contact) {
351 Intent params = new Intent();
352 params.setAction(OpenPgpApi.ACTION_GET_KEY);
353 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
354 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, contact.getAccount()
355 .getJid().toBareJid().toString());
356 Intent result = api.executeApi(params, null, null);
357 return (PendingIntent) result
358 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
359 }
360
361 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
362 Intent params = new Intent();
363 params.setAction(OpenPgpApi.ACTION_GET_KEY);
364 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
365 params.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, account.getJid().toBareJid().toString());
366 Intent result = api.executeApi(params, null, null);
367 return (PendingIntent) result
368 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
369 }
370}