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