use detached signatures

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/PgpEngine.java      | 500 +-
src/main/java/eu/siacs/conversations/parser/PresenceParser.java |  10 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java       |  10 
src/main/java/eu/siacs/conversations/utils/AsciiArmor.java      |  29 
4 files changed, 288 insertions(+), 261 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/PgpEngine.java 🔗

@@ -6,10 +6,17 @@ import android.util.Log;
 
 import androidx.annotation.StringRes;
 
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
+
 import org.openintents.openpgp.OpenPgpError;
 import org.openintents.openpgp.OpenPgpSignatureResult;
 import org.openintents.openpgp.util.OpenPgpApi;
 import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
+import org.openintents.openpgp.util.OpenPgpUtils;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -18,6 +25,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -29,268 +37,258 @@ import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.persistance.FileBackend;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.ui.UiCallback;
+import eu.siacs.conversations.utils.AsciiArmor;
 
 public class PgpEngine {
-	private final OpenPgpApi api;
-	private final XmppConnectionService mXmppConnectionService;
+    private final OpenPgpApi api;
+    private final XmppConnectionService mXmppConnectionService;
 
-	public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
-		this.api = api;
-		this.mXmppConnectionService = service;
-	}
+    public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
+        this.api = api;
+        this.mXmppConnectionService = service;
+    }
 
-	private static void logError(Account account, OpenPgpError error) {
-		if (error != null) {
-			error.describeContents();
-			Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId()+" class="+error.getClass().getName());
-		} else {
-			Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error with no message");
-		}
-	}
+    private static void logError(Account account, OpenPgpError error) {
+        if (error != null) {
+            error.describeContents();
+            Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId() + " class=" + error.getClass().getName());
+        } else {
+            Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error with no message");
+        }
+    }
 
-	public void encrypt(final Message message, final UiCallback<Message> callback) {
-		Intent params = new Intent();
-		params.setAction(OpenPgpApi.ACTION_ENCRYPT);
-		final Conversation conversation = (Conversation) message.getConversation();
-		if (conversation.getMode() == Conversation.MODE_SINGLE) {
-			long[] keys = {
-					conversation.getContact().getPgpKeyId(),
-					conversation.getAccount().getPgpId()
-			};
-			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
-		} else {
-			params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
-		}
+    public void encrypt(final Message message, final UiCallback<Message> callback) {
+        Intent params = new Intent();
+        params.setAction(OpenPgpApi.ACTION_ENCRYPT);
+        final Conversation conversation = (Conversation) message.getConversation();
+        if (conversation.getMode() == Conversation.MODE_SINGLE) {
+            long[] keys = {
+                    conversation.getContact().getPgpKeyId(),
+                    conversation.getAccount().getPgpId()
+            };
+            params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
+        } else {
+            params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
+        }
 
-		if (!message.needsUploading()) {
-			params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
-			String body;
-			if (message.hasFileOnRemoteHost()) {
-				body = message.getFileParams().url.toString();
-			} else {
-				body = message.getBody();
-			}
-			InputStream is = new ByteArrayInputStream(body.getBytes());
-			final OutputStream os = new ByteArrayOutputStream();
-			api.executeApiAsync(params, is, os, result -> {
-				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
-					case OpenPgpApi.RESULT_CODE_SUCCESS:
-						try {
-							os.flush();
-							StringBuilder encryptedMessageBody = new StringBuilder();
-							String[] lines = os.toString().split("\n");
-							for (int i = 2; i < lines.length - 1; ++i) {
-								if (!lines[i].contains("Version")) {
-									encryptedMessageBody.append(lines[i].trim());
-								}
-							}
-							message.setEncryptedBody(encryptedMessageBody.toString());
-							message.setEncryption(Message.ENCRYPTION_DECRYPTED);
-							mXmppConnectionService.sendMessage(message);
-							callback.success(message);
-						} catch (IOException e) {
-							callback.error(R.string.openpgp_error, message);
-						}
+        if (!message.needsUploading()) {
+            params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+            String body;
+            if (message.hasFileOnRemoteHost()) {
+                body = message.getFileParams().url.toString();
+            } else {
+                body = message.getBody();
+            }
+            InputStream is = new ByteArrayInputStream(body.getBytes());
+            final OutputStream os = new ByteArrayOutputStream();
+            api.executeApiAsync(params, is, os, result -> {
+                switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+                    case OpenPgpApi.RESULT_CODE_SUCCESS:
+                        try {
+                            os.flush();
+                            final ArrayList<String> encryptedMessageBody = new ArrayList<>();
+                            final String[] lines = os.toString().split("\n");
+                            for (int i = 2; i < lines.length - 1; ++i) {
+                                if (!lines[i].contains("Version")) {
+                                    encryptedMessageBody.add(lines[i].trim());
+                                }
+                            }
+                            message.setEncryptedBody(Joiner.on('\n').join(encryptedMessageBody));
+                            message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+                            mXmppConnectionService.sendMessage(message);
+                            callback.success(message);
+                        } catch (IOException e) {
+                            callback.error(R.string.openpgp_error, message);
+                        }
 
-						break;
-					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-						callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
-						break;
-					case OpenPgpApi.RESULT_CODE_ERROR:
-						OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
-						String errorMessage = error != null ? error.getMessage() : null;
-						@StringRes final int res;
-						if (errorMessage != null && errorMessage.startsWith("Bad key for encryption")) {
-							res = R.string.bad_key_for_encryption;
-						} else {
-							res = R.string.openpgp_error;
-						}
-						logError(conversation.getAccount(), error);
-						callback.error(res, message);
-						break;
-				}
-			});
-		} else {
-			try {
-				DownloadableFile inputFile = this.mXmppConnectionService
-						.getFileBackend().getFile(message, true);
-				DownloadableFile outputFile = this.mXmppConnectionService
-						.getFileBackend().getFile(message, false);
-				outputFile.getParentFile().mkdirs();
-				outputFile.createNewFile();
-				final InputStream is = new FileInputStream(inputFile);
-				final OutputStream os = new FileOutputStream(outputFile);
-				api.executeApiAsync(params, is, os, result -> {
-					switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
-						case OpenPgpApi.RESULT_CODE_SUCCESS:
-							try {
-								os.flush();
-							} catch (IOException ignored) {
-								//ignored
-							}
-							FileBackend.close(os);
-							mXmppConnectionService.sendMessage(message);
-							callback.success(message);
-							break;
-						case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-							callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
-							break;
-						case OpenPgpApi.RESULT_CODE_ERROR:
-							logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
-							callback.error(R.string.openpgp_error, message);
-							break;
-					}
-				});
-			} catch (final IOException e) {
-				callback.error(R.string.openpgp_error, message);
-			}
-		}
-	}
+                        break;
+                    case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                        callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
+                        break;
+                    case OpenPgpApi.RESULT_CODE_ERROR:
+                        OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
+                        String errorMessage = error != null ? error.getMessage() : null;
+                        @StringRes final int res;
+                        if (errorMessage != null && errorMessage.startsWith("Bad key for encryption")) {
+                            res = R.string.bad_key_for_encryption;
+                        } else {
+                            res = R.string.openpgp_error;
+                        }
+                        logError(conversation.getAccount(), error);
+                        callback.error(res, message);
+                        break;
+                }
+            });
+        } else {
+            try {
+                DownloadableFile inputFile = this.mXmppConnectionService
+                        .getFileBackend().getFile(message, true);
+                DownloadableFile outputFile = this.mXmppConnectionService
+                        .getFileBackend().getFile(message, false);
+                outputFile.getParentFile().mkdirs();
+                outputFile.createNewFile();
+                final InputStream is = new FileInputStream(inputFile);
+                final OutputStream os = new FileOutputStream(outputFile);
+                api.executeApiAsync(params, is, os, result -> {
+                    switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
+                        case OpenPgpApi.RESULT_CODE_SUCCESS:
+                            try {
+                                os.flush();
+                            } catch (IOException ignored) {
+                                //ignored
+                            }
+                            FileBackend.close(os);
+                            mXmppConnectionService.sendMessage(message);
+                            callback.success(message);
+                            break;
+                        case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                            callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
+                            break;
+                        case OpenPgpApi.RESULT_CODE_ERROR:
+                            logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+                            callback.error(R.string.openpgp_error, message);
+                            break;
+                    }
+                });
+            } catch (final IOException e) {
+                callback.error(R.string.openpgp_error, message);
+            }
+        }
+    }
 
-	public long fetchKeyId(Account account, String status, String signature) {
-		if ((signature == null) || (api == null)) {
-			return 0;
-		}
-		if (status == null) {
-			status = "";
-		}
-		final StringBuilder pgpSig = new StringBuilder();
-		pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
-		pgpSig.append('\n');
-		pgpSig.append('\n');
-		pgpSig.append(status);
-		pgpSig.append('\n');
-		pgpSig.append("-----BEGIN PGP SIGNATURE-----");
-		pgpSig.append('\n');
-		pgpSig.append('\n');
-		pgpSig.append(signature.replace("\n", "").trim());
-		pgpSig.append('\n');
-		pgpSig.append("-----END PGP SIGNATURE-----");
-		Intent params = new Intent();
-		params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
-		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
-		InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes());
-		ByteArrayOutputStream os = new ByteArrayOutputStream();
-		Intent result = api.executeApi(params, is, os);
-		switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
-				OpenPgpApi.RESULT_CODE_ERROR)) {
-			case OpenPgpApi.RESULT_CODE_SUCCESS:
-				OpenPgpSignatureResult sigResult = result
-						.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
-				if (sigResult != null) {
-					return sigResult.getKeyId();
-				} else {
-					return 0;
-				}
-			case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-				return 0;
-			case OpenPgpApi.RESULT_CODE_ERROR:
-				logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
-				return 0;
-		}
-		return 0;
-	}
+    public long fetchKeyId(final Account account, final String status, final String signature) {
+        if (signature == null || api == null) {
+            return 0;
+        }
+        final Intent params = new Intent();
+        params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
+        try {
+            params.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, AsciiArmor.decode(signature));
+        } catch (final IllegalArgumentException e) {
+            Log.d(Config.LOGTAG, "unable to parse signature", e);
+            return 0;
+        }
+        final InputStream is = new ByteArrayInputStream(Strings.nullToEmpty(status).getBytes());
+        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+        final Intent result = api.executeApi(params, is, os);
+        switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
+                OpenPgpApi.RESULT_CODE_ERROR)) {
+            case OpenPgpApi.RESULT_CODE_SUCCESS:
+                final OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
+                //TODO unsure that sigResult.getResult() is either 1, 2 or 3
+                if (sigResult != null) {
+                    return sigResult.getKeyId();
+                } else {
+                    return 0;
+                }
+            case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                return 0;
+            case OpenPgpApi.RESULT_CODE_ERROR:
+                logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+                return 0;
+        }
+        return 0;
+    }
 
-	public void chooseKey(final Account account, final UiCallback<Account> callback) {
-		Intent p = new Intent();
-		p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
-		api.executeApiAsync(p, null, null, result -> {
-			switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
-				case OpenPgpApi.RESULT_CODE_SUCCESS:
-					callback.success(account);
-					return;
-				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-					callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
-					return;
-				case OpenPgpApi.RESULT_CODE_ERROR:
-					logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
-					callback.error(R.string.openpgp_error, account);
-			}
-		});
-	}
+    public void chooseKey(final Account account, final UiCallback<Account> callback) {
+        Intent p = new Intent();
+        p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
+        api.executeApiAsync(p, null, null, result -> {
+            switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+                case OpenPgpApi.RESULT_CODE_SUCCESS:
+                    callback.success(account);
+                    return;
+                case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                    callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
+                    return;
+                case OpenPgpApi.RESULT_CODE_ERROR:
+                    logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+                    callback.error(R.string.openpgp_error, account);
+            }
+        });
+    }
 
-	public void generateSignature(Intent intent, final Account account, String status, final UiCallback<String> callback) {
-		if (account.getPgpId() == 0) {
-			return;
-		}
-		Intent params = intent == null ? new Intent() : intent;
-		params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
-		params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
-		params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
-		InputStream is = new ByteArrayInputStream(status.getBytes());
-		final OutputStream os = new ByteArrayOutputStream();
-		Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": signing status message \"" + status + "\"");
-		api.executeApiAsync(params, is, os, result -> {
-			switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
-				case OpenPgpApi.RESULT_CODE_SUCCESS:
-					StringBuilder signatureBuilder = new StringBuilder();
-					try {
-						os.flush();
-						String[] lines = os.toString().split("\n");
-						boolean sig = false;
-						for (String line : lines) {
-							if (sig) {
-								if (line.contains("END PGP SIGNATURE")) {
-									sig = false;
-								} else {
-									if (!line.contains("Version")) {
-										signatureBuilder.append(line.trim());
-									}
-								}
-							}
-							if (line.contains("BEGIN PGP SIGNATURE")) {
-								sig = true;
-							}
-						}
-					} catch (IOException e) {
-						callback.error(R.string.openpgp_error, null);
-						return;
-					}
-					callback.success(signatureBuilder.toString());
-					return;
-				case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-					callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
-					return;
-				case OpenPgpApi.RESULT_CODE_ERROR:
-					OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
-					if (error != null && "signing subkey not found!".equals(error.getMessage())) {
-						callback.error(0, null);
-					} else {
-						logError(account, error);
-						callback.error(R.string.unable_to_connect_to_keychain, null);
-					}
-			}
-		});
-	}
+    public void generateSignature(Intent intent, final Account account, String status, final UiCallback<String> callback) {
+        if (account.getPgpId() == 0) {
+            return;
+        }
+        Intent params = intent == null ? new Intent() : intent;
+        params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
+        params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
+        params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
+        InputStream is = new ByteArrayInputStream(status.getBytes());
+        final OutputStream os = new ByteArrayOutputStream();
+        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": signing status message \"" + status + "\"");
+        api.executeApiAsync(params, is, os, result -> {
+            switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+                case OpenPgpApi.RESULT_CODE_SUCCESS:
+                    final ArrayList<String> signature = new ArrayList<>();
+                    try {
+                        os.flush();
+                        boolean sig = false;
+                        for (final String line : Splitter.on('\n').split(os.toString())) {
+                            if (sig) {
+                                if (line.contains("END PGP SIGNATURE")) {
+                                    sig = false;
+                                } else {
+                                    if (!line.contains("Version")) {
+                                        signature.add(line.trim());
+                                    }
+                                }
+                            }
+                            if (line.contains("BEGIN PGP SIGNATURE")) {
+                                sig = true;
+                            }
+                        }
+                    } catch (IOException e) {
+                        callback.error(R.string.openpgp_error, null);
+                        return;
+                    }
+                    callback.success(Joiner.on('\n').join(signature));
+                    return;
+                case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                    callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
+                    return;
+                case OpenPgpApi.RESULT_CODE_ERROR:
+                    OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
+                    if (error != null && "signing subkey not found!".equals(error.getMessage())) {
+                        callback.error(0, null);
+                    } else {
+                        logError(account, error);
+                        callback.error(R.string.unable_to_connect_to_keychain, null);
+                    }
+            }
+        });
+    }
 
-	public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
-		Intent params = new Intent();
-		params.setAction(OpenPgpApi.ACTION_GET_KEY);
-		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
-		api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
+    public void hasKey(final Contact contact, final UiCallback<Contact> callback) {
+        Intent params = new Intent();
+        params.setAction(OpenPgpApi.ACTION_GET_KEY);
+        params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
+        api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
 
-			@Override
-			public void onReturn(Intent result) {
-				switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
-					case OpenPgpApi.RESULT_CODE_SUCCESS:
-						callback.success(contact);
-						return;
-					case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
-						callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
-						return;
-					case OpenPgpApi.RESULT_CODE_ERROR:
-						logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
-						callback.error(R.string.openpgp_error, contact);
-				}
-			}
-		});
-	}
+            @Override
+            public void onReturn(Intent result) {
+                switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
+                    case OpenPgpApi.RESULT_CODE_SUCCESS:
+                        callback.success(contact);
+                        return;
+                    case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
+                        callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
+                        return;
+                    case OpenPgpApi.RESULT_CODE_ERROR:
+                        logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
+                        callback.error(R.string.openpgp_error, contact);
+                }
+            }
+        });
+    }
 
-	public PendingIntent getIntentForKey(long pgpKeyId) {
-		Intent params = new Intent();
-		params.setAction(OpenPgpApi.ACTION_GET_KEY);
-		params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
-		Intent result = api.executeApi(params, null, null);
-		return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
-	}
+    public PendingIntent getIntentForKey(long pgpKeyId) {
+        Intent params = new Intent();
+        params.setAction(OpenPgpApi.ACTION_GET_KEY);
+        params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
+        Intent result = api.executeApi(params, null, null);
+        return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
+    }
 }

src/main/java/eu/siacs/conversations/parser/PresenceParser.java 🔗

@@ -2,6 +2,8 @@ package eu.siacs.conversations.parser;
 
 import android.util.Log;
 
+import org.openintents.openpgp.util.OpenPgpUtils;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -17,6 +19,7 @@ import eu.siacs.conversations.entities.Presence;
 import eu.siacs.conversations.generator.IqGenerator;
 import eu.siacs.conversations.generator.PresenceGenerator;
 import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.XmppUri;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
@@ -313,9 +316,10 @@ public class PresenceParser extends AbstractParser implements
 			PgpEngine pgp = mXmppConnectionService.getPgpEngine();
 			Element x = packet.findChild("x", "jabber:x:signed");
 			if (pgp != null && x != null) {
-				Element status = packet.findChild("status");
-				String msg = status != null ? status.getContent() : "";
-				if (contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent()))) {
+				final String status = packet.findChildContent("status");
+				final long keyId = pgp.fetchKeyId(account, status, x.getContent());
+				if (keyId != 0 && contact.setPgpKeyId(keyId)) {
+					Log.d(Config.LOGTAG,account.getJid().asBareJid()+": found OpenPGP key id for "+contact.getJid()+" "+OpenPgpUtils.convertKeyIdToHex(keyId));
 					mXmppConnectionService.syncRoster(account);
 				}
 			}

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -52,6 +52,8 @@ import androidx.appcompat.app.AlertDialog.Builder;
 import androidx.appcompat.app.AppCompatDelegate;
 import androidx.databinding.DataBindingUtil;
 
+import com.google.common.base.Strings;
+
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -573,13 +575,7 @@ public abstract class XmppActivity extends ActionBarActivity {
         if (account.getPgpId() == 0) {
             choosePgpSignId(account);
         } else {
-            String status = null;
-            if (manuallyChangePresence()) {
-                status = account.getPresenceStatusMessage();
-            }
-            if (status == null) {
-                status = "";
-            }
+            final String status = Strings.nullToEmpty(account.getPresenceStatusMessage());
             xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback<String>() {
 
                 @Override

src/main/java/eu/siacs/conversations/utils/AsciiArmor.java 🔗

@@ -0,0 +1,29 @@
+package eu.siacs.conversations.utils;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.io.BaseEncoding;
+
+import java.util.List;
+
+public class AsciiArmor {
+
+    public static byte[] decode(final String input) {
+        final List<String> lines = Splitter.on('\n').splitToList(Strings.nullToEmpty(input).trim());
+        if (lines.size() == 1) {
+            final String line = lines.get(0);
+            final String cleaned = line.substring(0, line.lastIndexOf("="));
+            return BaseEncoding.base64().decode(cleaned);
+        }
+        final String withoutChecksum;
+        if (Iterables.getLast(lines).charAt(0) == '=') {
+            withoutChecksum = Joiner.on("").join(lines.subList(0, lines.size() - 1));
+        } else {
+            withoutChecksum = Joiner.on("").join(lines);
+        }
+        return BaseEncoding.base64().decode(withoutChecksum);
+    }
+
+}