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 private static void logError(Account account, OpenPgpError error) {
41 if (error != null) {
42 Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId());
43 } else {
44 Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error with no message");
45 }
46 }
47
48 public void encrypt(final Message message, final UiCallback<Message> callback) {
49 Intent params = new Intent();
50 params.setAction(OpenPgpApi.ACTION_ENCRYPT);
51 final Conversation conversation = (Conversation) message.getConversation();
52 if (conversation.getMode() == Conversation.MODE_SINGLE) {
53 long[] keys = {
54 conversation.getContact().getPgpKeyId(),
55 conversation.getAccount().getPgpId()
56 };
57 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
58 } else {
59 params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
60 }
61
62 if (!message.needsUploading()) {
63 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
64 String body;
65 if (message.hasFileOnRemoteHost()) {
66 body = message.getFileParams().url.toString();
67 } else {
68 body = message.getBody();
69 }
70 InputStream is = new ByteArrayInputStream(body.getBytes());
71 final OutputStream os = new ByteArrayOutputStream();
72 api.executeApiAsync(params, is, os, result -> {
73 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
74 case OpenPgpApi.RESULT_CODE_SUCCESS:
75 try {
76 os.flush();
77 StringBuilder encryptedMessageBody = new StringBuilder();
78 String[] lines = os.toString().split("\n");
79 for (int i = 2; i < lines.length - 1; ++i) {
80 if (!lines[i].contains("Version")) {
81 encryptedMessageBody.append(lines[i].trim());
82 }
83 }
84 message.setEncryptedBody(encryptedMessageBody.toString());
85 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
86 mXmppConnectionService.sendMessage(message);
87 callback.success(message);
88 } catch (IOException e) {
89 callback.error(R.string.openpgp_error, message);
90 }
91
92 break;
93 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
94 callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
95 break;
96 case OpenPgpApi.RESULT_CODE_ERROR:
97 logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
98 callback.error(R.string.openpgp_error, message);
99 break;
100 }
101 });
102 } else {
103 try {
104 DownloadableFile inputFile = this.mXmppConnectionService
105 .getFileBackend().getFile(message, true);
106 DownloadableFile outputFile = this.mXmppConnectionService
107 .getFileBackend().getFile(message, false);
108 outputFile.getParentFile().mkdirs();
109 outputFile.createNewFile();
110 final InputStream is = new FileInputStream(inputFile);
111 final OutputStream os = new FileOutputStream(outputFile);
112 api.executeApiAsync(params, is, os, result -> {
113 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
114 case OpenPgpApi.RESULT_CODE_SUCCESS:
115 try {
116 os.flush();
117 } catch (IOException ignored) {
118 //ignored
119 }
120 FileBackend.close(os);
121 mXmppConnectionService.sendMessage(message);
122 callback.success(message);
123 break;
124 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
125 callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
126 break;
127 case OpenPgpApi.RESULT_CODE_ERROR:
128 logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
129 callback.error(R.string.openpgp_error, message);
130 break;
131 }
132 });
133 } catch (final IOException e) {
134 callback.error(R.string.openpgp_error, message);
135 }
136 }
137 }
138
139 public long fetchKeyId(Account account, String status, String signature) {
140 if ((signature == null) || (api == null)) {
141 return 0;
142 }
143 if (status == null) {
144 status = "";
145 }
146 final StringBuilder pgpSig = new StringBuilder();
147 pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
148 pgpSig.append('\n');
149 pgpSig.append('\n');
150 pgpSig.append(status);
151 pgpSig.append('\n');
152 pgpSig.append("-----BEGIN PGP SIGNATURE-----");
153 pgpSig.append('\n');
154 pgpSig.append('\n');
155 pgpSig.append(signature.replace("\n", "").trim());
156 pgpSig.append('\n');
157 pgpSig.append("-----END PGP SIGNATURE-----");
158 Intent params = new Intent();
159 params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
160 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
161 InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
162 ByteArrayOutputStream os = new ByteArrayOutputStream();
163 Intent result = api.executeApi(params, is, os);
164 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
165 OpenPgpApi.RESULT_CODE_ERROR)) {
166 case OpenPgpApi.RESULT_CODE_SUCCESS:
167 OpenPgpSignatureResult sigResult = result
168 .getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
169 if (sigResult != null) {
170 return sigResult.getKeyId();
171 } else {
172 return 0;
173 }
174 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
175 return 0;
176 case OpenPgpApi.RESULT_CODE_ERROR:
177 logError(account, (OpenPgpError) result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
178 return 0;
179 }
180 return 0;
181 }
182
183 public void chooseKey(final Account account, final UiCallback<Account> callback) {
184 Intent p = new Intent();
185 p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
186 api.executeApiAsync(p, null, null, result -> {
187 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
188 case OpenPgpApi.RESULT_CODE_SUCCESS:
189 callback.success(account);
190 return;
191 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
192 callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
193 return;
194 case OpenPgpApi.RESULT_CODE_ERROR:
195 logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
196 callback.error(R.string.openpgp_error, account);
197 }
198 });
199 }
200
201 public void generateSignature(Intent intent, final Account account, String status, final UiCallback<String> callback) {
202 if (account.getPgpId() == 0) {
203 return;
204 }
205 Intent params = intent == null ? new Intent() : intent;
206 params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
207 params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
208 params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
209 InputStream is = new ByteArrayInputStream(status.getBytes());
210 final OutputStream os = new ByteArrayOutputStream();
211 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": signing status message \"" + status + "\"");
212 api.executeApiAsync(params, is, os, result -> {
213 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
214 case OpenPgpApi.RESULT_CODE_SUCCESS:
215 StringBuilder signatureBuilder = new StringBuilder();
216 try {
217 os.flush();
218 String[] lines = os.toString().split("\n");
219 boolean sig = false;
220 for (String line : lines) {
221 if (sig) {
222 if (line.contains("END PGP SIGNATURE")) {
223 sig = false;
224 } else {
225 if (!line.contains("Version")) {
226 signatureBuilder.append(line.trim());
227 }
228 }
229 }
230 if (line.contains("BEGIN PGP SIGNATURE")) {
231 sig = true;
232 }
233 }
234 } catch (IOException e) {
235 callback.error(R.string.openpgp_error, null);
236 return;
237 }
238 callback.success(signatureBuilder.toString());
239 return;
240 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
241 callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
242 return;
243 case OpenPgpApi.RESULT_CODE_ERROR:
244 OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
245 if (error != null && "signing subkey not found!".equals(error.getMessage())) {
246 callback.error(0, null);
247 } else {
248 logError(account, error);
249 callback.error(R.string.unable_to_connect_to_keychain, null);
250 }
251 }
252 });
253 }
254
255 public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
256 Intent params = new Intent();
257 params.setAction(OpenPgpApi.ACTION_GET_KEY);
258 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
259 api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
260
261 @Override
262 public void onReturn(Intent result) {
263 switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
264 case OpenPgpApi.RESULT_CODE_SUCCESS:
265 callback.success(contact);
266 return;
267 case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
268 callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
269 return;
270 case OpenPgpApi.RESULT_CODE_ERROR:
271 logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
272 callback.error(R.string.openpgp_error, contact);
273 }
274 }
275 });
276 }
277
278 public PendingIntent getIntentForKey(long pgpKeyId) {
279 Intent params = new Intent();
280 params.setAction(OpenPgpApi.ACTION_GET_KEY);
281 params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
282 Intent result = api.executeApi(params, null, null);
283 return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
284 }
285}