1package eu.siacs.conversations.crypto;
2
3import android.app.PendingIntent;
4import android.content.Intent;
5import android.util.Log;
6
7import org.openintents.openpgp.OpenPgpSignatureResult;
8import org.openintents.openpgp.util.OpenPgpApi;
9import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
10
11import java.io.ByteArrayInputStream;
12import java.io.ByteArrayOutputStream;
13import java.io.FileInputStream;
14import java.io.FileOutputStream;
15import java.io.IOException;
16import java.io.InputStream;
17import java.io.OutputStream;
18import java.net.URL;
19
20import eu.siacs.conversations.Config;
21import eu.siacs.conversations.R;
22import eu.siacs.conversations.entities.Account;
23import eu.siacs.conversations.entities.Contact;
24import eu.siacs.conversations.entities.Conversation;
25import eu.siacs.conversations.entities.DownloadableFile;
26import eu.siacs.conversations.entities.Message;
27import eu.siacs.conversations.http.HttpConnectionManager;
28import eu.siacs.conversations.persistance.FileBackend;
29import eu.siacs.conversations.services.XmppConnectionService;
30import eu.siacs.conversations.ui.UiCallback;
31
32public class PgpEngine {
33 private OpenPgpApi api;
34 private XmppConnectionService mXmppConnectionService;
35
36 public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
37 this.api = api;
38 this.mXmppConnectionService = service;
39 }
40
41 public void encrypt(final Message message, final UiCallback<Message> callback) {
42 Intent params = new Intent();
43 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
44 final Conversation conversation = message.getConversation();
45 if (conversation.getMode() == Conversation.MODE_SINGLE) {
46 long[] keys = {
47 conversation.getContact().getPgpKeyId(),
48 conversation.getAccount().getPgpId()
49 };
50 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
51 } else {
52 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
53 }
54
55 if (!message.needsUploading()) {
56 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
57 String body;
58 if (message.hasFileOnRemoteHost()) {
59 body = message.getFileParams().url.toString();
60 } else {
61 body = message.getBody();
62 }
63 InputStream is = new ByteArrayInputStream(body.getBytes());
64 final OutputStream os = new ByteArrayOutputStream();
65 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
66
67 @Override
68 public void onReturn(Intent result) {
69 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
70 OpenPgpApi.RESULT_CODE_ERROR)) {
71 case OpenPgpApi.RESULT_CODE_SUCCESS:
72 try {
73 os.flush();
74 StringBuilder encryptedMessageBody = new StringBuilder();
75 String[] lines = os.toString().split("\n");
76 for (int i = 2; i < lines.length - 1; ++i) {
77 if (!lines[i].contains("Version")) {
78 encryptedMessageBody.append(lines[i].trim());
79 }
80 }
81 message.setEncryptedBody(encryptedMessageBody
82 .toString());
83 callback.success(message);
84 } catch (IOException e) {
85 callback.error(R.string.openpgp_error, message);
86 }
87
88 break;
89 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
90 callback.userInputRequried((PendingIntent) result
91 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
92 message);
93 break;
94 case OpenPgpApi.RESULT_CODE_ERROR:
95 callback.error(R.string.openpgp_error, message);
96 break;
97 }
98 }
99 });
100 } else {
101 try {
102 DownloadableFile inputFile = this.mXmppConnectionService
103 .getFileBackend().getFile(message, true);
104 DownloadableFile outputFile = this.mXmppConnectionService
105 .getFileBackend().getFile(message, false);
106 outputFile.getParentFile().mkdirs();
107 outputFile.createNewFile();
108 final InputStream is = new FileInputStream(inputFile);
109 final OutputStream os = new FileOutputStream(outputFile);
110 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
111
112 @Override
113 public void onReturn(Intent result) {
114 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
115 OpenPgpApi.RESULT_CODE_ERROR)) {
116 case OpenPgpApi.RESULT_CODE_SUCCESS:
117 try {
118 os.flush();
119 } catch (IOException ignored) {
120 //ignored
121 }
122 FileBackend.close(os);
123 callback.success(message);
124 break;
125 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
126 callback.userInputRequried(
127 (PendingIntent) result
128 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
129 message);
130 break;
131 case OpenPgpApi.RESULT_CODE_ERROR:
132 callback.error(R.string.openpgp_error, message);
133 break;
134 }
135 }
136 });
137 } catch (final IOException e) {
138 callback.error(R.string.openpgp_error, message);
139 }
140 }
141 }
142
143 public long fetchKeyId(Account account, String status, String signature) {
144 if ((signature == null) || (api == null)) {
145 return 0;
146 }
147 if (status == null) {
148 status = "";
149 }
150 final StringBuilder pgpSig = new StringBuilder();
151 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
152 pgpSig.append('\n');
153 pgpSig.append('\n');
154 pgpSig.append(status);
155 pgpSig.append('\n');
156 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
157 pgpSig.append('\n');
158 pgpSig.append('\n');
159 pgpSig.append(signature.replace("\n", "").trim());
160 pgpSig.append('\n');
161 pgpSig.append("-----END PGP SIGNATURE-----");
162 Intent params = new Intent();
163 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
164 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
165 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
166 ByteArrayOutputStream os = new ByteArrayOutputStream();
167 Intent result = api.executeApi(params, is, os);
168 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
169 OpenPgpApi.RESULT_CODE_ERROR)) {
170 case OpenPgpApi.RESULT_CODE_SUCCESS:
171 OpenPgpSignatureResult sigResult = result
172 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
173 if (sigResult != null) {
174 return sigResult.getKeyId();
175 } else {
176 return 0;
177 }
178 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
179 return 0;
180 case OpenPgpApi.RESULT_CODE_ERROR:
181 return 0;
182 }
183 return 0;
184 }
185
186 public void chooseKey(final Account account, final UiCallback<Account> callback) {
187 Intent p = new Intent();
188 p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
189 api.executeApiAsync(p, null, null, new IOpenPgpCallback() {
190
191 @Override
192 public void onReturn(Intent result) {
193 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
194 case OpenPgpApi.RESULT_CODE_SUCCESS:
195 callback.success(account);
196 return;
197 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
198 callback.userInputRequried((PendingIntent) result
199 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
200 account);
201 return;
202 case OpenPgpApi.RESULT_CODE_ERROR:
203 callback.error(R.string.openpgp_error, account);
204 }
205 }
206 });
207 }
208
209 public void generateSignature(final Account account, String status,
210 final UiCallback<Account> callback) {
211 if (account.getPgpId() == 0) {
212 return;
213 }
214 Intent params = new Intent();
215 params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
216 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
217 params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
218 InputStream is = new ByteArrayInputStream(status.getBytes());
219 final OutputStream os = new ByteArrayOutputStream();
220 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": signing status message \""+status+"\"");
221 api.executeApiAsync(params, is, os, new IOpenPgpCallback() {
222
223 @Override
224 public void onReturn(Intent result) {
225 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
226 case OpenPgpApi.RESULT_CODE_SUCCESS:
227 StringBuilder signatureBuilder = new StringBuilder();
228 try {
229 os.flush();
230 String[] lines = os.toString().split("\n");
231 boolean sig = false;
232 for (String line : lines) {
233 if (sig) {
234 if (line.contains("END PGP SIGNATURE")) {
235 sig = false;
236 } else {
237 if (!line.contains("Version")) {
238 signatureBuilder.append(line.trim());
239 }
240 }
241 }
242 if (line.contains("BEGIN PGP SIGNATURE")) {
243 sig = true;
244 }
245 }
246 } catch (IOException e) {
247 callback.error(R.string.openpgp_error, account);
248 return;
249 }
250 account.setPgpSignature(signatureBuilder.toString());
251 callback.success(account);
252 return;
253 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
254 callback.userInputRequried((PendingIntent) result
255 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
256 account);
257 return;
258 case OpenPgpApi.RESULT_CODE_ERROR:
259 callback.error(R.string.openpgp_error, account);
260 }
261 }
262 });
263 }
264
265 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
266 Intent params = new Intent();
267 params.setAction(OpenPgpApi.ACTION_GET_KEY);
268 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
269 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
270
271 @Override
272 public void onReturn(Intent result) {
273 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
274 case OpenPgpApi.RESULT_CODE_SUCCESS:
275 callback.success(contact);
276 return;
277 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
278 callback.userInputRequried((PendingIntent) result
279 .getParcelableExtra(OpenPgpApi.RESULT_INTENT),
280 contact);
281 return;
282 case OpenPgpApi.RESULT_CODE_ERROR:
283 callback.error(R.string.openpgp_error, contact);
284 }
285 }
286 });
287 }
288
289 public PendingIntent getIntentForKey(Contact contact) {
290 Intent params = new Intent();
291 params.setAction(OpenPgpApi.ACTION_GET_KEY);
292 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
293 Intent result = api.executeApi(params, null, null);
294 return (PendingIntent) result
295 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
296 }
297
298 public PendingIntent getIntentForKey(Account account, long pgpKeyId) {
299 Intent params = new Intent();
300 params.setAction(OpenPgpApi.ACTION_GET_KEY);
301 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
302 Intent result = api.executeApi(params, null, null);
303 return (PendingIntent) result
304 .getParcelableExtra(OpenPgpApi.RESULT_INTENT);
305 }
306}