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