From e8f09fc842056593b7b4cad147eb6987a67538ee Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 30 May 2018 22:06:09 +0200 Subject: [PATCH] Another overhaul --- smack-integration-test/build.gradle | 2 + .../openpgp/OpenPgpIntegrationTest.java | 4 - .../ox/bouncycastle/BCOpenPgpProvider.java | 46 +++-- .../ox/bouncycastle/BCOpenPgpStore.java | 16 ++ .../bouncycastle/FileBasedBcOpenPgpStore.java | 44 +++-- smack-openpgp/build.gradle | 1 + .../smackx/ox/OXInstantMessagingManager.java | 70 +++++++- .../smackx/ox/OpenPgpEncryptedChat.java | 92 ++++++++++ .../smackx/ox/OpenPgpFingerprints.java | 102 +++++++++++ .../smackx/ox/OpenPgpManager.java | 161 ++++++++++++++++-- .../smackx/ox/OpenPgpMessage.java | 43 ++++- .../jivesoftware/smackx/ox/OpenPgpStore.java | 45 +++-- .../smackx/ox/PubSubDelegate.java | 5 +- .../java/org/jivesoftware/smackx/ox/Util.java | 16 ++ .../SecretKeyBackupSelectionCallback.java | 16 ++ ...eption.java => SmackOpenPgpException.java} | 12 +- .../OXEncryptedChatMessageListener.java | 31 ++++ .../OpenPgpMessageListener.java | 4 +- .../smackx/ox/listener}/package-info.java | 7 +- .../ox/provider/CryptElementProvider.java | 2 +- .../OpenPgpContentElementProvider.java | 13 +- .../ox/provider/SignElementProvider.java | 2 +- .../ox/provider/SigncryptElementProvider.java | 2 +- .../smackx/ox/OpenPgpElementTest.java | 6 +- 24 files changed, 644 insertions(+), 98 deletions(-) delete mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/OpenPgpIntegrationTest.java create mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpEncryptedChat.java create mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpFingerprints.java rename smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/{CorruptedOpenPgpKeyException.java => SmackOpenPgpException.java} (78%) create mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java rename smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/{ => listener}/OpenPgpMessageListener.java (95%) rename {smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp => smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener}/package-info.java (82%) diff --git a/smack-integration-test/build.gradle b/smack-integration-test/build.gradle index 285bb74ea..85c2b673c 100644 --- a/smack-integration-test/build.gradle +++ b/smack-integration-test/build.gradle @@ -12,6 +12,8 @@ dependencies { compile project(':smack-extensions') compile project(':smack-experimental') compile project(':smack-omemo') + compile project(':smack-openpgp') + compile project(':smack-openpgp-bouncycastle') compile project(':smack-debug') compile project(path: ":smack-omemo", configuration: "testRuntime") compile 'org.reflections:reflections:0.9.9-RC1' diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/OpenPgpIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/OpenPgpIntegrationTest.java deleted file mode 100644 index 217dedbe4..000000000 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/OpenPgpIntegrationTest.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.jivesoftware.smackx.openpgp; - -public class OpenPgpIntegrationTest { -} diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java index ba9a09988..5fde46300 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jivesoftware.smackx.ox.bouncycastle; import java.io.ByteArrayInputStream; @@ -24,10 +40,10 @@ import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.element.SignElement; import org.jivesoftware.smackx.ox.element.SigncryptElement; -import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeySize; @@ -38,19 +54,16 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.io.Streams; import org.jxmpp.jid.BareJid; -import sun.reflect.generics.reflectiveObjects.NotImplementedException; public class BCOpenPgpProvider implements OpenPgpProvider { private final BareJid user; - private OpenPgpV4Fingerprint primaryKeyPair; private BCOpenPgpStore store; public BCOpenPgpProvider(BareJid user) { this.user = user; - this.primaryKeyPair = null; } public void setStore(BCOpenPgpStore store) { @@ -59,7 +72,7 @@ public class BCOpenPgpProvider implements OpenPgpProvider { @Override public OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint() { - return primaryKeyPair; + return store.primaryOpenPgpKeyPairFingerprint(); } @Override @@ -141,35 +154,36 @@ public class BCOpenPgpProvider implements OpenPgpProvider { @Override public OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint) throws MissingOpenPgpKeyPairException { - throw new NotImplementedException(); + throw new AssertionError("Feature not implemented!"); } @Override public OpenPgpMessage verify(OpenPgpElement element, Set singingKeyFingerprints) throws MissingOpenPgpPublicKeyException { - throw new NotImplementedException(); + throw new AssertionError("Feature not implemented!"); } @Override public OpenPgpElement encrypt(CryptElement element, Set encryptionKeyFingerprints) throws MissingOpenPgpPublicKeyException { - throw new NotImplementedException(); + throw new AssertionError("Feature not implemented!"); } @Override - public OpenPgpMessage decrypt(OpenPgpElement element) throws MissingOpenPgpKeyPairException { - throw new NotImplementedException(); + public OpenPgpMessage decrypt(OpenPgpElement element) + throws MissingOpenPgpKeyPairException { + throw new AssertionError("Feature not implemented!"); } @Override public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint) - throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException { + throws MissingOpenPgpPublicKeyException, SmackOpenPgpException { return store.createPubkeyElement(fingerprint); } @Override public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) - throws CorruptedOpenPgpKeyException { + throws SmackOpenPgpException { store.storePublicKey(owner, fingerprint, element); } @@ -180,25 +194,25 @@ public class BCOpenPgpProvider implements OpenPgpProvider { @Override public OpenPgpV4Fingerprint createOpenPgpKeyPair() - throws NoSuchAlgorithmException, NoSuchProviderException, CorruptedOpenPgpKeyException { + throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException { return store.createOpenPgpKeyPair(); } @Override public SecretkeyElement createSecretkeyElement(Set fingerprints, String password) - throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException { + throws MissingOpenPgpKeyPairException, SmackOpenPgpException { return store.createSecretkeyElement(fingerprints, password); } @Override public Set availableOpenPgpPublicKeysFingerprints(BareJid contact) - throws CorruptedOpenPgpKeyException { + throws SmackOpenPgpException { return store.availableOpenPgpPublicKeysFingerprints(contact); } @Override public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) - throws CorruptedOpenPgpKeyException, InvalidBackupCodeException { + throws SmackOpenPgpException, InvalidBackupCodeException { store.restoreSecretKeyBackup(secretkeyElement, password, callback); } diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpStore.java index 32fa64314..fff699308 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpStore.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jivesoftware.smackx.ox.bouncycastle; import org.jivesoftware.smackx.ox.OpenPgpStore; diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java index eb8a1a3d1..7a909ba85 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jivesoftware.smackx.ox.bouncycastle; import java.io.BufferedOutputStream; @@ -31,10 +47,10 @@ import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; -import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPHashAlgorithms; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPSymmetricEncryptionAlgorithms; @@ -156,7 +172,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { @Override public Set availableOpenPgpPublicKeysFingerprints(BareJid contact) - throws CorruptedOpenPgpKeyException { + throws SmackOpenPgpException { Set availableKeys = new HashSet<>(); try { Iterator ringIterator = keyringConfig.getPublicKeyRings().getKeyRings("xmpp:" + contact.toString()); @@ -171,7 +187,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } } } catch (PGPException | IOException e) { - throw new CorruptedOpenPgpKeyException(e); + throw new SmackOpenPgpException(e); } return availableKeys; } @@ -208,7 +224,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { @Override public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint) - throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException { + throws MissingOpenPgpPublicKeyException, SmackOpenPgpException { try { PGPPublicKey publicKey = keyringConfig.getPublicKeyRings().getPublicKey(Util.keyIdFromFingerprint(fingerprint)); if (publicKey == null) { @@ -217,19 +233,19 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { byte[] base64 = Base64.encode(publicKey.getEncoded()); return new PubkeyElement(new PubkeyElement.PubkeyDataElement(base64), new Date()); } catch (PGPException | IOException e) { - throw new CorruptedOpenPgpKeyException(e); + throw new SmackOpenPgpException(e); } } @Override public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) - throws CorruptedOpenPgpKeyException { + throws SmackOpenPgpException { byte[] base64decoded = Base64.decode(element.getDataElement().getB64Data()); try { keyringConfig.addPublicKey(base64decoded); writePublicKeysToFile(keyringConfig, publicKeyringPath()); } catch (PGPException | IOException e) { - throw new CorruptedOpenPgpKeyException(e); + throw new SmackOpenPgpException(e); } catch (IllegalArgumentException e) { LOGGER.log(Level.WARNING, "Public Key with ID " + fingerprint.toString() + " of " + owner + " is already in memory. Skip."); @@ -238,7 +254,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { @Override public SecretkeyElement createSecretkeyElement(Set fingerprints, String password) - throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException { + throws MissingOpenPgpKeyPairException, SmackOpenPgpException { PGPDigestCalculator calculator; try { @@ -281,13 +297,13 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { return new SecretkeyElement(Base64.encode(buffer.toByteArray())); } catch (PGPException | IOException e) { - throw new CorruptedOpenPgpKeyException(e); + throw new SmackOpenPgpException(e); } } @Override public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) - throws CorruptedOpenPgpKeyException, InvalidBackupCodeException { + throws SmackOpenPgpException, InvalidBackupCodeException { byte[] base64Decoded = Base64.decode(secretkeyElement.getB64Data()); try { @@ -332,13 +348,13 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { writePrivateKeysToFile(keyringConfig, secretKeyringPath()); } } catch (PGPException | IOException e) { - throw new CorruptedOpenPgpKeyException(e); + throw new SmackOpenPgpException(e); } } @Override public OpenPgpV4Fingerprint createOpenPgpKeyPair() - throws NoSuchAlgorithmException, NoSuchProviderException, CorruptedOpenPgpKeyException { + throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException { try { PGPSecretKeyRing ourKey = BCOpenPgpProvider.generateKey(user).generateSecretKeyRing(); keyringConfig.addSecretKey(ourKey.getSecretKey().getEncoded()); @@ -346,7 +362,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { primaryKeyFingerprint = BCOpenPgpProvider.getFingerprint(ourKey.getPublicKey()); return primaryKeyFingerprint; } catch (PGPException | IOException e) { - throw new CorruptedOpenPgpKeyException(e); + throw new SmackOpenPgpException(e); } } @@ -359,7 +375,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } private File contactsPath() { - return new File(basePath, "users"); + return new File(basePath, user.toString() + "/users"); } private File contactsPath(BareJid contact) { diff --git a/smack-openpgp/build.gradle b/smack-openpgp/build.gradle index e20600d0f..36ff4a4e6 100644 --- a/smack-openpgp/build.gradle +++ b/smack-openpgp/build.gradle @@ -6,6 +6,7 @@ Smack API for XEP-0373: OpenPGP for XMPP.""" dependencies { compile project(':smack-core') compile project(':smack-extensions') + compile project(':smack-experimental') testCompile project(path: ":smack-core", configuration: "testRuntime") testCompile project(path: ":smack-core", configuration: "archives") } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java index b01a320fc..671da17aa 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java @@ -16,24 +16,42 @@ */ package org.jivesoftware.smackx.ox; -import java.util.List; import java.util.Map; import java.util.WeakHashMap; +import java.util.logging.Logger; import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.chat2.Chat; +import org.jivesoftware.smack.chat2.ChatManager; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; -public class OXInstantMessagingManager extends Manager { +/** + * Entry point of Smacks API for XEP-0374: OpenPGP for XMPP: Instant Messaging. + * + * @see + * XEP-0374: OpenPGP for XMPP: Instant Messaging + */ +public final class OXInstantMessagingManager extends Manager { + + public static final String NAMESPACE_0 = "urn:xmpp:openpgp:im:0"; + + private static final Logger LOGGER = Logger.getLogger(OXInstantMessagingManager.class.getName()); private static final Map INSTANCES = new WeakHashMap<>(); private final OpenPgpManager openPgpManager; + private final ChatManager chatManager; private OXInstantMessagingManager(XMPPConnection connection) { super(connection); this.openPgpManager = OpenPgpManager.getInstanceFor(connection); + this.chatManager = ChatManager.getInstanceFor(connection); } public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) { @@ -45,7 +63,51 @@ public class OXInstantMessagingManager extends Manager { return manager; } - public void send(List messageContent, BareJid recipient) { + /** + * Determine, whether a contact announces support for XEP-0374: OpenPGP for XMPP: Instant Messaging. + * + * @param jid {@link BareJid} of the contact in question. + * @return true if contact announces support, otherwise false. + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws InterruptedException + * @throws SmackException.NoResponseException + */ + public boolean contactSupportsOxInstantMessaging(BareJid jid) + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException { + return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid, NAMESPACE_0); + } + + /** + * Start an encrypted chat with {@code jid}. + * The chat is encrypted with OpenPGP for XMPP: Instant Messaging (XEP-0374). + * + * @see XEP-0374: OpenPGP for XMPP: Instant Messaging + * @param jid {@link BareJid} of the contact. + * @return {@link OpenPgpEncryptedChat} with the contact. + * @throws SmackOpenPgpException if something happens while gathering fingerprints. + * @throws InterruptedException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + * @throws SmackException.FeatureNotSupportedException if the contact does not announce support for XEP-0374. + */ + public OpenPgpEncryptedChat chatWith(EntityBareJid jid) + throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException, + SmackException.FeatureNotSupportedException { + if (!contactSupportsOxInstantMessaging(jid)) { + throw new SmackException.FeatureNotSupportedException(NAMESPACE_0, jid); + } + + OpenPgpFingerprints theirKeys = openPgpManager.determineContactsKeys(jid); + OpenPgpFingerprints ourKeys = openPgpManager.determineContactsKeys(connection().getUser().asBareJid()); + Chat chat = chatManager.chatWith(jid); + return new OpenPgpEncryptedChat(openPgpManager.getOpenPgpProvider(), chat, ourKeys, theirKeys); + } + + public void addOpenPgpEncryptedMessageListener() { } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpEncryptedChat.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpEncryptedChat.java new file mode 100644 index 000000000..130e8ea61 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpEncryptedChat.java @@ -0,0 +1,92 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.ox; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jivesoftware.smack.chat2.Chat; +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.eme.element.ExplicitMessageEncryptionElement; +import org.jivesoftware.smackx.hints.element.StoreHint; +import org.jivesoftware.smackx.ox.element.OpenPgpElement; +import org.jivesoftware.smackx.ox.element.SigncryptElement; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; + +import org.jxmpp.jid.Jid; + +public class OpenPgpEncryptedChat { + + private final Chat chat; + private final OpenPgpFingerprints contactsFingerprints; + private final OpenPgpFingerprints ourFingerprints; + private final OpenPgpProvider cryptoProvider; + private final OpenPgpV4Fingerprint singingKey; + + public OpenPgpEncryptedChat(OpenPgpProvider cryptoProvider, + Chat chat, + OpenPgpFingerprints ourFingerprints, + OpenPgpFingerprints contactsFingerprints) { + this.cryptoProvider = cryptoProvider; + this.chat = chat; + this.singingKey = cryptoProvider.primaryOpenPgpKeyPairFingerprint(); + this.ourFingerprints = ourFingerprints; + this.contactsFingerprints = contactsFingerprints; + } + + public void send(Message message, List payload) + throws MissingOpenPgpKeyPairException { + Set encryptionFingerprints = new HashSet<>(contactsFingerprints.getActiveKeys()); + encryptionFingerprints.addAll(ourFingerprints.getActiveKeys()); + + SigncryptElement preparedPayload = new SigncryptElement( + Collections.singleton(chat.getXmppAddressOfChatPartner()), + payload); + OpenPgpElement encryptedPayload; + + // Encrypt the payload + try { + encryptedPayload = cryptoProvider.signAndEncrypt( + preparedPayload, + singingKey, + encryptionFingerprints); + } catch (MissingOpenPgpPublicKeyException e) { + throw new AssertionError("Missing OpenPGP public key, even though this should not happen here.", e); + } + + // Add encrypted payload to message + message.addExtension(encryptedPayload); + + // Add additional information to the message + message.addExtension(new ExplicitMessageEncryptionElement( + ExplicitMessageEncryptionElement.ExplicitMessageEncryptionProtocol.openpgpV0)); + StoreHint.set(message); + message.setBody("This message is encrypted using XEP-0374: OpenPGP for XMPP: Instant Messaging."); + } + + public void send(Message message, CharSequence body) + throws MissingOpenPgpKeyPairException { + List payload = new ArrayList<>(); + payload.add(new Message.Body(null, body.toString())); + send(message, payload); + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpFingerprints.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpFingerprints.java new file mode 100644 index 000000000..9ab70a735 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpFingerprints.java @@ -0,0 +1,102 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.ox; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jxmpp.jid.BareJid; + +/** + * Metadata about a contacts OpenPGP fingerprints. + */ +public class OpenPgpFingerprints { + + private final Set announcedKeys; + private final Set availableKeys; + private final Map unfetchableKeys; + + /** + * Constructor. + * + * @param announcedKeys keys the contact currently announces via their metadata node. + * @param availableKeys keys which contain the contacts {@link BareJid} as user ID. + * @param unfetchableKeys keys that are announced, but cannot be fetched fro PubSub. + */ + public OpenPgpFingerprints(Set announcedKeys, + Set availableKeys, + Map unfetchableKeys) { + this.announcedKeys = Collections.unmodifiableSet(announcedKeys); + this.availableKeys = Collections.unmodifiableSet(availableKeys); + this.unfetchableKeys = Collections.unmodifiableMap(unfetchableKeys); + } + + /** + * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s, which the contact in question announced via their + * metadata node. + * + * @see + * XEP-0373 §4.2 about the Public Key Metadata Node + * + * @return announced keys. + */ + public Set getAnnouncedKeys() { + return announcedKeys; + } + + /** + * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s, which are available in the local key ring, which contain + * the users {@link BareJid} as user-id (eg. "xmpp:juliet@capulet.lit"). + *
+ * Note: As everybody can publish a key with an arbitrary user-id, the keys contained in the result set MAY be keys + * of an potential attacker. NEVER use these keys for encryption without collating them with the users announced + * keys. + * + * @return locally available keys with users user-id. + */ + public Set getAvailableKeys() { + return availableKeys; + } + + /** + * Return a {@link Map}, which maps {@link OpenPgpV4Fingerprint}s which the user announced, but that cannot be fetched + * via PubSub to {@link Throwable}s which were thrown when we tried to fetch them. + * + * @return unfetchable keys. + */ + public Map getUnfetchableKeys() { + return unfetchableKeys; + } + + /** + * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s, which are both announced, as well as available. + * It is recommended to use those keys for encryption. + * + * @return active keys. + */ + public Set getActiveKeys() { + Set active = new HashSet<>(); + for (OpenPgpV4Fingerprint fingerprint : getAvailableKeys()) { + if (getAnnouncedKeys().contains(fingerprint)) { + active.add(fingerprint); + } + } + return Collections.unmodifiableSet(active); + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java index 562efe0d0..653b8d8ee 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java @@ -18,9 +18,15 @@ package org.jivesoftware.smackx.ox; import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS; import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY; +import static org.jivesoftware.smackx.ox.PubSubDelegate.fetchPubkey; +import static org.jivesoftware.smackx.ox.PubSubDelegate.publishPublicKey; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.SecureRandom; +import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -34,20 +40,25 @@ import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.callback.AskForBackupCodeCallback; import org.jivesoftware.smackx.ox.callback.DisplayBackupCodeCallback; +import org.jivesoftware.smackx.ox.callback.SecretKeyBackupSelectionCallback; import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; +import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; -import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; import org.jivesoftware.smackx.pep.PEPListener; import org.jivesoftware.smackx.pep.PEPManager; import org.jivesoftware.smackx.pubsub.EventElement; import org.jivesoftware.smackx.pubsub.ItemsExtension; +import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubManager; +import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; public final class OpenPgpManager extends Manager { @@ -71,11 +82,6 @@ public final class OpenPgpManager extends Manager { */ private OpenPgpManager(XMPPConnection connection) { super(connection); - - // Subscribe to public key changes - PEPManager.getInstanceFor(connection()).addPEPListener(metadataListener); - ServiceDiscoveryManager.getInstanceFor(connection()) - .addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY); } /** @@ -103,13 +109,62 @@ public final class OpenPgpManager extends Manager { this.provider = provider; } + /** + * Return the registered {@link OpenPgpProvider}. + * + * @return provider. + */ + OpenPgpProvider getOpenPgpProvider() { + return provider; + } + + /** + * Generate a fresh OpenPGP key pair, given we don't have one already. + * Publish the public key to the Public Key Node and update the Public Key Metadata Node with our keys fingerprint. + * Lastly register a {@link PEPListener} which listens for updates to Public Key Metadata Nodes. + * + * @throws NoSuchAlgorithmException if we are missing an algorithm to generate a fresh key pair. + * @throws NoSuchProviderException if we are missing a suitable {@link java.security.Provider}. + * @throws SmackOpenPgpException if something bad happens during key generation/loading. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + */ + public void announceSupportAndPublish() throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException, + InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + throwIfNoProviderSet(); + + OpenPgpV4Fingerprint primaryFingerprint = provider.primaryOpenPgpKeyPairFingerprint(); + if (primaryFingerprint == null) { + primaryFingerprint = provider.createOpenPgpKeyPair(); + } + + // Create element + PubkeyElement pubkeyElement; + try { + pubkeyElement = provider.createPubkeyElement(primaryFingerprint); + } catch (MissingOpenPgpPublicKeyException e) { + throw new AssertionError("Cannot publish our public key, since it is missing (MUST NOT happen!)"); + } + + // publish it + publishPublicKey(connection(), pubkeyElement, primaryFingerprint); + + // Subscribe to public key changes + PEPManager.getInstanceFor(connection()).addPEPListener(metadataListener); + ServiceDiscoveryManager.getInstanceFor(connection()) + .addFeature(PEP_NODE_PUBLIC_KEYS_NOTIFY); + } + /** * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. * * @return fingerprint. - * @throws CorruptedOpenPgpKeyException if for some reason we cannot determine our fingerprint. */ - public OpenPgpV4Fingerprint getOurFingerprint() throws CorruptedOpenPgpKeyException { + public OpenPgpV4Fingerprint getOurFingerprint() { throwIfNoProviderSet(); return provider.primaryOpenPgpKeyPairFingerprint(); } @@ -140,35 +195,60 @@ public final class OpenPgpManager extends Manager { * * @see XEP-0373 §5 * - * @param callback callback, which will receive the backup password used to encrypt the secret key. - * @throws CorruptedOpenPgpKeyException if the secret key is corrupted or can for some reason not be serialized. + * @param displayCodeCallback callback, which will receive the backup password used to encrypt the secret key. + * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. + * @throws SmackOpenPgpException if the secret key is corrupted or can for some reason not be serialized. * @throws InterruptedException * @throws PubSubException.NotALeafNodeException * @throws XMPPException.XMPPErrorException * @throws SmackException.NotConnectedException * @throws SmackException.NoResponseException */ - public void backupSecretKeyToServer(DisplayBackupCodeCallback callback) - throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, + public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback, + SecretKeyBackupSelectionCallback selectKeyCallback) + throws SmackOpenPgpException, InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, MissingOpenPgpKeyPairException { throwIfNoProviderSet(); String backupCode = generateBackupPassword(); - SecretkeyElement secretKey = provider.createSecretkeyElement(null, backupCode); // TODO + Set availableKeyPairs = provider.availableOpenPgpKeyPairFingerprints(); + SecretkeyElement secretKey = provider.createSecretkeyElement( + selectKeyCallback.selectKeysToBackup(availableKeyPairs), backupCode); PubSubDelegate.depositSecretKey(connection(), secretKey); - callback.displayBackupCode(backupCode); + displayCodeCallback.displayBackupCode(backupCode); } + /** + * Delete the private {@link LeafNode} containing our secret key backup. + * + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws InterruptedException + * @throws SmackException.NoResponseException + */ public void deleteSecretKeyServerBackup() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { PubSubDelegate.deleteSecretKeyNode(connection()); } + /** + * Fetch a secret key backup from the server and try to restore a selected secret key from it. + * + * @param codeCallback callback for prompting the user to provide the secret backup code. + * @param selectionCallback callback allowing the user to select a secret key which will be restored. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + * @throws SmackOpenPgpException if something goes wrong while restoring the secret key. + * @throws InvalidBackupCodeException if the user-provided backup code is invalid. + */ public void restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback, SecretKeyRestoreSelectionCallback selectionCallback) throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, - SmackException.NotConnectedException, SmackException.NoResponseException, CorruptedOpenPgpKeyException, + SmackException.NotConnectedException, SmackException.NoResponseException, SmackOpenPgpException, InvalidBackupCodeException { throwIfNoProviderSet(); SecretkeyElement backup = PubSubDelegate.fetchSecretKey(connection()); @@ -176,6 +256,38 @@ public final class OpenPgpManager extends Manager { // TODO: catch InvalidBackupCodeException in order to prevent re-fetching the backup on next try. } + /** + * Determine which keys belong to a user and fetch any missing keys. + * + * @param jid {@link BareJid} of the user in question. + * @return {@link OpenPgpFingerprints} object containing the announced, available and unfetchable keys of the user. + * @throws SmackOpenPgpException + * @throws InterruptedException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + */ + public OpenPgpFingerprints determineContactsKeys(BareJid jid) + throws SmackOpenPgpException, InterruptedException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + Set announced = provider.announcedOpenPgpKeyFingerprints(jid); + Set available = provider.availableOpenPgpPublicKeysFingerprints(jid); + Map unfetched = new HashMap<>(); + for (OpenPgpV4Fingerprint f : announced) { + if (!available.contains(f)) { + try { + PubkeyElement pubkeyElement = PubSubDelegate.fetchPubkey(connection(), jid, f); + provider.storePublicKey(jid, f, pubkeyElement); + available.add(f); + } catch (PubSubException.NotAPubSubNodeException | PubSubException.NotALeafNodeException e) { + LOGGER.log(Level.WARNING, "Could not fetch public key " + f.toString() + " of user " + jid.toString(), e); + unfetched.put(f, e); + } + } + } + return new OpenPgpFingerprints(announced, available, unfetched); + } + /** * {@link PEPListener} that listens for changes to the OX public keys metadata node. * @@ -185,18 +297,31 @@ public final class OpenPgpManager extends Manager { @Override public void eventReceived(final EntityBareJid from, final EventElement event, Message message) { if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) { - LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + from); + final BareJid contact = from.asBareJid(); + LOGGER.log(Level.INFO, "Received OpenPGP metadata update from " + contact); Async.go(new Runnable() { @Override public void run() { ItemsExtension items = (ItemsExtension) event.getExtensions().get(0); PayloadItem payload = (PayloadItem) items.getItems().get(0); PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload(); + provider.storePublicKeysList(connection(), listElement, contact); + + Set missingKeys = listElement.getMetadata().keySet(); try { - provider.storePublicKeysList(connection(), listElement, from.asBareJid()); + provider.storePublicKeysList(connection(), listElement, contact); + missingKeys.removeAll(provider.availableOpenPgpPublicKeysFingerprints(contact)); + for (OpenPgpV4Fingerprint missing : missingKeys) { + try { + PubkeyElement pubkeyElement = fetchPubkey(connection(), contact, missing); + provider.storePublicKey(contact, missing, pubkeyElement); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error fetching missing OpenPGP key " + missing.toString(), e); + } + } } catch (Exception e) { - LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + from + ".", e); + LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + contact + ".", e); } } }, "ProcessOXMetadata"); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java index 6f312ebfc..028af6885 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessage.java @@ -18,18 +18,32 @@ package org.jivesoftware.smackx.ox; import java.io.IOException; +import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpContentElement; +import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.SignElement; import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jivesoftware.smackx.ox.provider.OpenPgpContentElementProvider; import org.xmlpull.v1.XmlPullParserException; +/** + * This class embodies a decrypted {@link OpenPgpElement}. + */ public class OpenPgpMessage { public enum State { + /** + * Represents a {@link SigncryptElement}. + */ signcrypt, + /** + * Represents a {@link SignElement}. + */ sign, + /** + * Represents a {@link CryptElement}. + */ crypt, ; } @@ -39,11 +53,26 @@ public class OpenPgpMessage { private OpenPgpContentElement openPgpContentElement; + /** + * Constructor. + * + * @param state state of the content element. + * @param content XML representation of the decrypted {@link OpenPgpContentElement}. + */ public OpenPgpMessage(State state, String content) { this.state = state; this.element = content; } + /** + * Return the decrypted {@link OpenPgpContentElement} of this message. + * To determine, whether the element is a {@link SignElement}, {@link CryptElement} or {@link SigncryptElement}, + * please consult {@link #getState()}. + * + * @return {@link OpenPgpContentElement} + * @throws XmlPullParserException if the parser encounters an error. + * @throws IOException if the parser encounters an error. + */ public OpenPgpContentElement getOpenPgpContentElement() throws XmlPullParserException, IOException { ensureOpenPgpContentElementSet(); @@ -59,15 +88,27 @@ public class OpenPgpMessage { return; } + // Determine the state of the content element. if (openPgpContentElement instanceof SigncryptElement) { state = State.signcrypt; } else if (openPgpContentElement instanceof SignElement) { state = State.sign; - } else { + } else if (openPgpContentElement instanceof CryptElement) { state = State.crypt; + } else { + throw new AssertionError("OpenPgpContentElement is neither a SignElement, " + + "CryptElement nor a SignCryptElement."); } } + /** + * Return the state of the message. This value determines, whether the message was a {@link SignElement}, + * {@link CryptElement} or {@link SigncryptElement}. + * + * @return state of the content element. + * @throws IOException if the parser encounters an error. + * @throws XmlPullParserException if the parser encounters and error. + */ public State getState() throws IOException, XmlPullParserException { ensureOpenPgpContentElementSet(); return state; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java index e0f86ca4a..d393cb34d 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jivesoftware.smackx.ox; import java.security.NoSuchAlgorithmException; @@ -10,10 +26,10 @@ import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.element.SigncryptElement; -import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; import org.jxmpp.jid.BareJid; @@ -60,9 +76,10 @@ public interface OpenPgpStore { * * @param contact contact. * @return list of contacts locally available public keys. + * @throws SmackOpenPgpException if something goes wrong */ Set availableOpenPgpPublicKeysFingerprints(BareJid contact) - throws CorruptedOpenPgpKeyException; + throws SmackOpenPgpException; /** * Store incoming update to the OpenPGP metadata node in persistent storage. @@ -81,20 +98,21 @@ public interface OpenPgpStore { * @throws NoSuchAlgorithmException if a Hash algorithm is not available * @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider) * is registered. - * @throws CorruptedOpenPgpKeyException if the generated key cannot be added to the keyring for some reason. + * @throws SmackOpenPgpException if the generated key cannot be added to the keyring for some reason. */ OpenPgpV4Fingerprint createOpenPgpKeyPair() - throws NoSuchAlgorithmException, NoSuchProviderException, CorruptedOpenPgpKeyException; + throws NoSuchAlgorithmException, NoSuchProviderException, SmackOpenPgpException; /** * Create a {@link PubkeyElement} which contains our exported OpenPGP public key. * The element can for example be published. * * @return {@link PubkeyElement} containing our public key. - * @throws CorruptedOpenPgpKeyException if our public key can for some reason not be serialized. + * @throws MissingOpenPgpPublicKeyException if we have no OpenPGP key pair. + * @throws SmackOpenPgpException if something goes wrong. */ PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint) - throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException; + throws SmackOpenPgpException, MissingOpenPgpPublicKeyException; /** * Process an incoming {@link PubkeyElement} of a contact or ourselves. @@ -103,23 +121,23 @@ public interface OpenPgpStore { * @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}. * @param fingerprint {@link OpenPgpV4Fingerprint} of the key. * @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}. - * @throws CorruptedOpenPgpKeyException if the key found in the {@link PubkeyElement} + * @throws SmackOpenPgpException if the key found in the {@link PubkeyElement} * can not be deserialized or imported. */ void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) - throws CorruptedOpenPgpKeyException; + throws SmackOpenPgpException; /** * Create an encrypted backup of our secret keys. * * @param fingerprints {@link Set} of IDs of the keys that will be included in the backup. * @param password password that is used to symmetrically encrypt the backup. - * @return {@link SigncryptElement}. + * @return {@link SigncryptElement} containing the selected encrypted secret keys. * @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key available. - * @throws CorruptedOpenPgpKeyException if for some reason the key pair cannot be serialized. + * @throws SmackOpenPgpException if for some reason the key pair cannot be serialized. */ SecretkeyElement createSecretkeyElement(Set fingerprints, String password) - throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException; + throws MissingOpenPgpKeyPairException, SmackOpenPgpException; /** * Decrypt a secret key backup and restore the key from it. @@ -127,9 +145,10 @@ public interface OpenPgpStore { * @param secretkeyElement {@link SecretkeyElement} containing the backup. * @param password password to decrypt the backup. * @param callback {@link SecretKeyRestoreSelectionCallback} to let the user decide which key to restore. - * @throws CorruptedOpenPgpKeyException if the selected key is corrupted and cannot be restored. + * @throws SmackOpenPgpException if the selected key is corrupted and cannot be restored or our key ring + * is corrupted. * @throws InvalidBackupCodeException if the user provided backup code is invalid. */ void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) - throws CorruptedOpenPgpKeyException, InvalidBackupCodeException; + throws SmackOpenPgpException, InvalidBackupCodeException; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java index 423b90c66..b08bef97d 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java @@ -28,7 +28,6 @@ import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; -import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.pubsub.AccessModel; import org.jivesoftware.smackx.pubsub.ConfigureForm; import org.jivesoftware.smackx.pubsub.Item; @@ -91,8 +90,6 @@ public class PubSubDelegate { * * @see XEP-0373 §4.1 * - * @throws CorruptedOpenPgpKeyException if our OpenPGP key is corrupted and for that reason cannot - * be serialized. * @throws InterruptedException * @throws PubSubException.NotALeafNodeException * @throws XMPPException.XMPPErrorException @@ -100,7 +97,7 @@ public class PubSubDelegate { * @throws SmackException.NoResponseException */ public static void publishPublicKey(XMPPConnection connection, PubkeyElement pubkeyElement, OpenPgpV4Fingerprint fingerprint) - throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, + throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java index 726ffcb11..951ec856b 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jivesoftware.smackx.ox; import java.nio.ByteBuffer; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java index 5955fd4aa..be5ad6efe 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java @@ -1,3 +1,19 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.jivesoftware.smackx.ox.callback; import java.util.Set; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/CorruptedOpenPgpKeyException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/SmackOpenPgpException.java similarity index 78% rename from smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/CorruptedOpenPgpKeyException.java rename to smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/SmackOpenPgpException.java index 113266af8..274f1910d 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/CorruptedOpenPgpKeyException.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/SmackOpenPgpException.java @@ -16,15 +16,15 @@ */ package org.jivesoftware.smackx.ox.exception; -public class CorruptedOpenPgpKeyException extends Exception { +public class SmackOpenPgpException extends Exception { private static final long serialVersionUID = 1L; - public CorruptedOpenPgpKeyException() { - super(); - } - - public CorruptedOpenPgpKeyException(Exception cause) { + public SmackOpenPgpException(Throwable cause) { super(cause); } + + public SmackOpenPgpException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java new file mode 100644 index 000000000..c0ac29447 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OXEncryptedChatMessageListener.java @@ -0,0 +1,31 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.ox.listener; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.ox.OpenPgpEncryptedChat; +import org.jivesoftware.smackx.ox.element.SigncryptElement; + +import org.jxmpp.jid.EntityBareJid; + +public interface OXEncryptedChatMessageListener { + + void newIncomingEncryptedMessage(EntityBareJid from, + Message originalMessage, + SigncryptElement decryptedPayload, + OpenPgpEncryptedChat chat); +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java similarity index 95% rename from smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java rename to smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java index c389f3b2f..5aabdf7fe 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpMessageListener.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/OpenPgpMessageListener.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smackx.ox; +package org.jivesoftware.smackx.ox.listener; import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.SignElement; @@ -22,7 +22,7 @@ import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jxmpp.jid.BareJid; -public interface OpenPgpMessageListener { +interface OpenPgpMessageListener { /** * This method gets called whenever we received and successfully decrypted/verified an encrypted, signed message. diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/package-info.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/package-info.java similarity index 82% rename from smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/package-info.java rename to smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/package-info.java index 49e29068b..da3a10c3a 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/package-info.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/listener/package-info.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015 Florian Schmaus + * Copyright 2017 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /** - * Smacks implementation of XEP-0199: XMPP Ping. + * Listeners for XEP-0373: OpenPGP for XMPP. */ -package org.jivesoftware.smackx.ping; +package org.jivesoftware.smackx.ox.listener; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java index dd14aa752..26b1fe8ab 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java @@ -25,7 +25,7 @@ import org.xmlpull.v1.XmlPullParser; */ public class CryptElementProvider extends OpenPgpContentElementProvider { - public static final CryptElementProvider TEST_INSTANCE = new CryptElementProvider(); + public static final CryptElementProvider INSTANCE = new CryptElementProvider(); @Override public CryptElement parse(XmlPullParser parser, int initialDepth) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java index d642dbd8f..7aadc3793 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java @@ -45,8 +45,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; /** - * Abstract {@link ExtensionElementProvider} implementation for the also abstract - * {@link OpenPgpContentElement}. + * Abstract {@link ExtensionElementProvider} implementation for the also abstract {@link OpenPgpContentElement}. + * * @param Specialized subclass of {@link OpenPgpContentElement}. */ public abstract class OpenPgpContentElementProvider extends ExtensionElementProvider { @@ -64,12 +64,13 @@ public abstract class OpenPgpContentElementProvider, or element, " + + "but got neither of them."); } } catch (Exception e) { throw new XmlPullParserException(e.getMessage()); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java index c0e4b8378..ea509d572 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java @@ -29,7 +29,7 @@ import org.xmlpull.v1.XmlPullParser; public class SignElementProvider extends OpenPgpContentElementProvider { private static final Logger LOGGER = Logger.getLogger(SigncryptElementProvider.class.getName()); - public static final SignElementProvider TEST_INSTANCE = new SignElementProvider(); + public static final SignElementProvider INSTANCE = new SignElementProvider(); @Override public SignElement parse(XmlPullParser parser, int initialDepth) throws Exception { diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java index 979ec8227..ba906754d 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java @@ -25,7 +25,7 @@ import org.xmlpull.v1.XmlPullParser; */ public class SigncryptElementProvider extends OpenPgpContentElementProvider { - public static final SigncryptElementProvider TEST_INSTANCE = new SigncryptElementProvider(); + public static final SigncryptElementProvider INSTANCE = new SigncryptElementProvider(); @Override public SigncryptElement parse(XmlPullParser parser, int initialDepth) throws Exception { diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java index 59cf3dea4..212a24110 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java @@ -104,7 +104,7 @@ public class OpenPgpElementTest extends SmackTestSuite { assertXMLEqual(expected, element.toXML(null).toString()); XmlPullParser parser = TestUtils.getParser(expected); - SignElement parsed = SignElementProvider.TEST_INSTANCE.parse(parser); + SignElement parsed = SignElementProvider.INSTANCE.parse(parser); assertEquals(element.getTimestamp(), parsed.getTimestamp()); assertEquals(element.getTo(), parsed.getTo()); @@ -134,7 +134,7 @@ public class OpenPgpElementTest extends SmackTestSuite { assertXMLEqual(expected, element.toXML(null).toString()); XmlPullParser parser = TestUtils.getParser(expected); - CryptElement parsed = CryptElementProvider.TEST_INSTANCE.parse(parser); + CryptElement parsed = CryptElementProvider.INSTANCE.parse(parser); assertEquals(element.getTimestamp(), parsed.getTimestamp()); assertEquals(element.getTo(), parsed.getTo()); @@ -164,7 +164,7 @@ public class OpenPgpElementTest extends SmackTestSuite { assertXMLEqual(expected, element.toXML(null).toString()); XmlPullParser parser = TestUtils.getParser(expected); - SigncryptElement parsed = SigncryptElementProvider.TEST_INSTANCE.parse(parser); + SigncryptElement parsed = SigncryptElementProvider.INSTANCE.parse(parser); assertEquals(element.getTimestamp(), parsed.getTimestamp()); assertEquals(element.getTo(), parsed.getTo());