mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-07-14 14:41:58 +02:00
XEP-0373, XEP-0374: OpenPGP for XMPP: Instant Messaging
Fixes SMACK-826
This commit is contained in:
parent
f3262c9d58
commit
f0af00ee43
97 changed files with 8582 additions and 1 deletions
|
@ -79,6 +79,7 @@ allprojects {
|
|||
':smack-experimental',
|
||||
':smack-omemo',
|
||||
':smack-omemo-signal',
|
||||
':smack-openpgp',
|
||||
].collect{ project(it) }
|
||||
androidBootClasspathProjects = [
|
||||
':smack-android',
|
||||
|
|
|
@ -96,6 +96,8 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
|
|||
| Stable and Unique Stanza IDs | [XEP-0359](https://xmpp.org/extensions/xep-0359.html) | 0.5.0 | This specification describes unique and stable IDs for messages. |
|
||||
| HTTP File Upload | [XEP-0363](https://xmpp.org/extensions/xep-0363.html) | 0.3.1 | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
|
||||
| References | [XEP-0372](https://xmpp.org/extensions/xep-0363.html) | 0.2.0 | Add references like mentions or external data to stanzas. |
|
||||
| [OpenPGP for XMPP](ox.md) | [XEP-0373](https://xmpp.org/extensions/xep-0373.html) | 0.3.2 | Utilize OpenPGP to exchange encrypted and signed content. |
|
||||
| [OpenPGP for XMPP: Instant Messaging](ox-im.md) | [XEP-0374](https://xmpp.org/extensions/xep-0374.html) | 0.2.0 | OpenPGP encrypted Instant Messaging. |
|
||||
| [Spoiler Messages](spoiler.md) | [XEP-0382](https://xmpp.org/extensions/xep-0382.html) | 0.2.0 | Indicate that the body of a message should be treated as a spoiler. |
|
||||
| [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-0384](https://xmpp.org/extensions/xep-0384.html) | n/a | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). |
|
||||
| [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.4.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. |
|
||||
|
|
6
documentation/extensions/ox-im.md
Normal file
6
documentation/extensions/ox-im.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
OpenPGP for XMPP: Instant Messaging
|
||||
===================================
|
||||
|
||||
[Back](index.md)
|
||||
|
||||
See the javadoc of `OpenPgpManager` and `OXInstantMessagingManager` for details.
|
6
documentation/extensions/ox.md
Normal file
6
documentation/extensions/ox.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
OpenPGP for XMPP
|
||||
================
|
||||
|
||||
[Back](index.md)
|
||||
|
||||
See the javadoc of `OpenPgpManager` for details.
|
|
@ -26,4 +26,5 @@ include 'smack-core',
|
|||
'smack-omemo',
|
||||
'smack-omemo-signal',
|
||||
'smack-omemo-signal-integration-test',
|
||||
'smack-repl'
|
||||
'smack-repl',
|
||||
'smack-openpgp'
|
||||
|
|
|
@ -158,6 +158,9 @@ public final class FileUtils {
|
|||
}
|
||||
|
||||
public static void deleteDirectory(File root) {
|
||||
if (!root.exists()) {
|
||||
return;
|
||||
}
|
||||
File[] currList;
|
||||
Stack<File> stack = new Stack<>();
|
||||
stack.push(root);
|
||||
|
@ -176,4 +179,25 @@ public final class FileUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link File} pointing to a temporary directory. On unix like systems this might be {@code /tmp}
|
||||
* for example.
|
||||
* If {@code suffix} is not null, the returned file points to {@code <temp>/suffix}.
|
||||
*
|
||||
* @param suffix optional path suffix
|
||||
* @return temp directory
|
||||
*/
|
||||
public static File getTempDir(String suffix) {
|
||||
String temp = System.getProperty("java.io.tmpdir");
|
||||
if (temp == null) {
|
||||
temp = "tmp";
|
||||
}
|
||||
|
||||
if (suffix == null) {
|
||||
return new File(temp);
|
||||
} else {
|
||||
return new File(temp, suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,5 +21,6 @@
|
|||
<className>org.jivesoftware.smack.java7.Java7SmackInitializer</className>
|
||||
<className>org.jivesoftware.smack.im.SmackImInitializer</className>
|
||||
<className>org.jivesoftware.smackx.omemo.OmemoInitializer</className>
|
||||
<className>org.jivesoftware.smackx.ox.util.OpenPgpInitializer</className>
|
||||
</optionalStartupClasses>
|
||||
</smack>
|
||||
|
|
|
@ -12,6 +12,7 @@ dependencies {
|
|||
compile project(':smack-extensions')
|
||||
compile project(':smack-experimental')
|
||||
compile project(':smack-omemo')
|
||||
compile project(':smack-openpgp')
|
||||
compile project(':smack-debug')
|
||||
compile project(path: ":smack-omemo", configuration: "testRuntime")
|
||||
compile 'org.reflections:reflections:0.9.9-RC1'
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
*
|
||||
* 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 org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||
import org.jivesoftware.smackx.pep.PEPManager;
|
||||
|
||||
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
public abstract class AbstractOpenPgpIntegrationTest extends AbstractSmackIntegrationTest {
|
||||
|
||||
protected final XMPPConnection aliceConnection;
|
||||
protected final XMPPConnection bobConnection;
|
||||
protected final XMPPConnection chloeConnection;
|
||||
|
||||
protected final BareJid alice;
|
||||
protected final BareJid bob;
|
||||
protected final BareJid chloe;
|
||||
|
||||
protected AbstractOpenPgpIntegrationTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException,
|
||||
InterruptedException, SmackException.NoResponseException {
|
||||
super(environment);
|
||||
|
||||
throwIfPubSubNotSupported(conOne);
|
||||
throwIfPubSubNotSupported(conTwo);
|
||||
throwIfPubSubNotSupported(conThree);
|
||||
|
||||
this.aliceConnection = conOne;
|
||||
this.bobConnection = conTwo;
|
||||
this.chloeConnection = conThree;
|
||||
|
||||
this.alice = aliceConnection.getUser().asBareJid();
|
||||
this.bob = bobConnection.getUser().asBareJid();
|
||||
this.chloe = chloeConnection.getUser().asBareJid();
|
||||
|
||||
OpenPgpPubSubUtil.deletePubkeysListNode(aliceConnection);
|
||||
OpenPgpPubSubUtil.deletePubkeysListNode(bobConnection);
|
||||
OpenPgpPubSubUtil.deletePubkeysListNode(chloeConnection);
|
||||
}
|
||||
|
||||
private static void throwIfPubSubNotSupported(XMPPConnection connection)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException, TestNotPossibleException {
|
||||
if (!PEPManager.getInstanceFor(connection).isSupported()) {
|
||||
throw new TestNotPossibleException("Server " + connection.getXMPPServiceDomain().toString() +
|
||||
" does not support PEP.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
*
|
||||
* 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 static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.util.FileUtils;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.crypto.PainlessOpenPgpProvider;
|
||||
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.NoBackupFoundException;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.store.filebased.FileBasedOpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
|
||||
public class OXSecretKeyBackupIntegrationTest extends AbstractOpenPgpIntegrationTest {
|
||||
|
||||
private static final String sessionId = StringUtils.randomString(10);
|
||||
private static final File beforePath = FileUtils.getTempDir("ox_backup_" + sessionId);
|
||||
private static final File afterPath = FileUtils.getTempDir("ox_restore_" + sessionId);
|
||||
|
||||
private String backupCode = null;
|
||||
|
||||
private OpenPgpManager openPgpManager;
|
||||
|
||||
/**
|
||||
* This integration test tests the basic secret key backup and restore functionality as described
|
||||
* in XEP-0373 §5.
|
||||
*
|
||||
* In order to simulate two different devices, we are using two {@link FileBasedOpenPgpStore} implementations
|
||||
* which point to different directories.
|
||||
*
|
||||
* First, Alice generates a fresh OpenPGP key pair.
|
||||
*
|
||||
* She then creates a backup of the key in her private PEP node.
|
||||
*
|
||||
* Now the {@link OpenPgpStore} implementation is replaced by another instance to simulate a different device.
|
||||
*
|
||||
* Then the secret key backup is restored from PubSub and the imported secret key is compared to the one in
|
||||
* the original store.
|
||||
*
|
||||
* Afterwards the private PEP node is deleted from PubSub and the storage directories are emptied.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">
|
||||
* XEP-0373 §5: Synchronizing the Secret Key with a Private PEP Node</a>
|
||||
* @param environment
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws TestNotPossibleException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws InterruptedException
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public OXSecretKeyBackupIntegrationTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, TestNotPossibleException, SmackException.NotConnectedException,
|
||||
InterruptedException, SmackException.NoResponseException {
|
||||
super(environment);
|
||||
if (!OpenPgpManager.serverSupportsSecretKeyBackups(aliceConnection)) {
|
||||
throw new TestNotPossibleException("Server does not support the 'whitelist' PubSub access model.");
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@BeforeClass
|
||||
public static void cleanStore() {
|
||||
LOGGER.log(Level.INFO, "Delete store directories...");
|
||||
FileUtils.deleteDirectory(afterPath);
|
||||
FileUtils.deleteDirectory(beforePath);
|
||||
}
|
||||
|
||||
@After
|
||||
@Before
|
||||
public void cleanUp()
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
OpenPgpPubSubUtil.deleteSecretKeyNode(aliceConnection);
|
||||
|
||||
if (openPgpManager != null) {
|
||||
openPgpManager.stopMetadataListener();
|
||||
}
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void test() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||
NoSuchProviderException, IOException, InterruptedException, PubSubException.NotALeafNodeException,
|
||||
SmackException.NoResponseException, SmackException.NotConnectedException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotLoggedInException, SmackException.FeatureNotSupportedException,
|
||||
MissingUserIdOnKeyException, NoBackupFoundException, InvalidBackupCodeException, PGPException,
|
||||
MissingOpenPgpKeyException {
|
||||
|
||||
OpenPgpStore beforeStore = new FileBasedOpenPgpStore(beforePath);
|
||||
beforeStore.setKeyRingProtector(new UnprotectedKeysProtector());
|
||||
PainlessOpenPgpProvider beforeProvider = new PainlessOpenPgpProvider(aliceConnection, beforeStore);
|
||||
openPgpManager = OpenPgpManager.getInstanceFor(aliceConnection);
|
||||
openPgpManager.setOpenPgpProvider(beforeProvider);
|
||||
|
||||
OpenPgpSelf self = openPgpManager.getOpenPgpSelf();
|
||||
|
||||
assertNull(self.getSigningKeyFingerprint());
|
||||
|
||||
OpenPgpV4Fingerprint keyFingerprint = openPgpManager.generateAndImportKeyPair(alice);
|
||||
assertEquals(keyFingerprint, self.getSigningKeyFingerprint());
|
||||
|
||||
assertTrue(self.getSecretKeys().contains(keyFingerprint.getKeyId()));
|
||||
|
||||
PGPSecretKeyRing beforeSec = beforeStore.getSecretKeyRing(alice, keyFingerprint);
|
||||
assertNotNull(beforeSec);
|
||||
|
||||
PGPPublicKeyRing beforePub = beforeStore.getPublicKeyRing(alice, keyFingerprint);
|
||||
assertNotNull(beforePub);
|
||||
|
||||
openPgpManager.backupSecretKeyToServer(new DisplayBackupCodeCallback() {
|
||||
@Override
|
||||
public void displayBackupCode(String backupCode) {
|
||||
OXSecretKeyBackupIntegrationTest.this.backupCode = backupCode;
|
||||
}
|
||||
}, new SecretKeyBackupSelectionCallback() {
|
||||
@Override
|
||||
public Set<OpenPgpV4Fingerprint> selectKeysToBackup(Set<OpenPgpV4Fingerprint> availableSecretKeys) {
|
||||
return availableSecretKeys;
|
||||
}
|
||||
});
|
||||
|
||||
FileBasedOpenPgpStore afterStore = new FileBasedOpenPgpStore(afterPath);
|
||||
afterStore.setKeyRingProtector(new UnprotectedKeysProtector());
|
||||
PainlessOpenPgpProvider afterProvider = new PainlessOpenPgpProvider(aliceConnection, afterStore);
|
||||
openPgpManager.setOpenPgpProvider(afterProvider);
|
||||
|
||||
OpenPgpV4Fingerprint fingerprint = openPgpManager.restoreSecretKeyServerBackup(new AskForBackupCodeCallback() {
|
||||
@Override
|
||||
public String askForBackupCode() {
|
||||
return backupCode;
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(keyFingerprint, fingerprint);
|
||||
|
||||
assertTrue(self.getSecretKeys().contains(keyFingerprint.getKeyId()));
|
||||
|
||||
assertEquals(keyFingerprint, self.getSigningKeyFingerprint());
|
||||
|
||||
PGPSecretKeyRing afterSec = afterStore.getSecretKeyRing(alice, keyFingerprint);
|
||||
assertNotNull(afterSec);
|
||||
assertTrue(Arrays.equals(beforeSec.getEncoded(), afterSec.getEncoded()));
|
||||
|
||||
PGPPublicKeyRing afterPub = afterStore.getPublicKeyRing(alice, keyFingerprint);
|
||||
assertNotNull(afterPub);
|
||||
assertTrue(Arrays.equals(beforePub.getEncoded(), afterPub.getEncoded()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Integration Tests for Smacks support for XEP-0373: OpenPGP for XMPP.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html">
|
||||
* XEP-0373: OpenPGP for XMPP</a>
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
*
|
||||
* 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_im;
|
||||
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.FileUtils;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smackx.ox.AbstractOpenPgpIntegrationTest;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpManager;
|
||||
import org.jivesoftware.smackx.ox.crypto.PainlessOpenPgpProvider;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox.store.filebased.FileBasedOpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
|
||||
public class OXInstantMessagingIntegrationTest extends AbstractOpenPgpIntegrationTest {
|
||||
|
||||
private static final String sessionId = StringUtils.randomString(10);
|
||||
private static final File aliceStorePath = FileUtils.getTempDir("basic_ox_messaging_test_alice_" + sessionId);
|
||||
private static final File bobStorePath = FileUtils.getTempDir("basic_ox_messaging_test_bob_" + sessionId);
|
||||
|
||||
private OpenPgpV4Fingerprint aliceFingerprint = null;
|
||||
private OpenPgpV4Fingerprint bobFingerprint = null;
|
||||
|
||||
private OpenPgpManager aliceOpenPgp;
|
||||
private OpenPgpManager bobOpenPgp;
|
||||
|
||||
/**
|
||||
* This integration test tests basic OX message exchange.
|
||||
* In this scenario, Alice and Bob are strangers, as they do not have subscribed to one another.
|
||||
*
|
||||
* Alice (conOne) creates keys and publishes them to the server.
|
||||
* Bob (conTwo) creates keys and publishes them to the server.
|
||||
*
|
||||
* Alice then manually fetches Bobs metadata node and all announced keys.
|
||||
*
|
||||
* Alice trusts Bobs keys and vice versa (even though Bob does not have copies of Alice' keys yet).
|
||||
*
|
||||
* She proceeds to create an OX encrypted message, which is encrypted to Bob and herself and signed by her.
|
||||
*
|
||||
* She sends the message.
|
||||
*
|
||||
* Bob receives the message, which - due to missing keys - triggers him to update Alice' keys.
|
||||
*
|
||||
* After the update Bob proceeds to decrypt and verify the message.
|
||||
*
|
||||
* After the test, the keys are deleted from local storage and from PubSub.
|
||||
*
|
||||
* @param environment test environment
|
||||
*
|
||||
* @throws XMPPException.XMPPErrorException
|
||||
* @throws InterruptedException
|
||||
* @throws SmackException.NotConnectedException
|
||||
* @throws TestNotPossibleException if the test is not possible due to lacking server support for PEP.
|
||||
* @throws SmackException.NoResponseException
|
||||
*/
|
||||
public OXInstantMessagingIntegrationTest(SmackIntegrationTestEnvironment environment)
|
||||
throws XMPPException.XMPPErrorException, InterruptedException, SmackException.NotConnectedException,
|
||||
TestNotPossibleException, SmackException.NoResponseException {
|
||||
super(environment);
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
@AfterClass
|
||||
public static void deleteStore() {
|
||||
LOGGER.log(Level.INFO, "Deleting storage directories...");
|
||||
FileUtils.deleteDirectory(aliceStorePath);
|
||||
FileUtils.deleteDirectory(bobStorePath);
|
||||
}
|
||||
|
||||
@SmackIntegrationTest
|
||||
public void basicInstantMessagingTest()
|
||||
throws Exception {
|
||||
|
||||
LOGGER.log(Level.INFO, aliceStorePath.getAbsolutePath() + " " + bobStorePath.getAbsolutePath());
|
||||
|
||||
final SimpleResultSyncPoint bobReceivedMessage = new SimpleResultSyncPoint();
|
||||
final String body = "Writing integration tests is an annoying task, but it has to be done, so lets do it!!!";
|
||||
|
||||
FileBasedOpenPgpStore aliceStore = new FileBasedOpenPgpStore(aliceStorePath);
|
||||
aliceStore.setKeyRingProtector(new UnprotectedKeysProtector());
|
||||
FileBasedOpenPgpStore bobStore = new FileBasedOpenPgpStore(bobStorePath);
|
||||
bobStore.setKeyRingProtector(new UnprotectedKeysProtector());
|
||||
|
||||
PainlessOpenPgpProvider aliceProvider = new PainlessOpenPgpProvider(aliceConnection, aliceStore);
|
||||
PainlessOpenPgpProvider bobProvider = new PainlessOpenPgpProvider(bobConnection, bobStore);
|
||||
|
||||
aliceOpenPgp = OpenPgpManager.getInstanceFor(aliceConnection);
|
||||
bobOpenPgp = OpenPgpManager.getInstanceFor(bobConnection);
|
||||
|
||||
OXInstantMessagingManager aliceInstantMessaging = OXInstantMessagingManager.getInstanceFor(aliceConnection);
|
||||
OXInstantMessagingManager bobInstantMessaging = OXInstantMessagingManager.getInstanceFor(bobConnection);
|
||||
|
||||
bobInstantMessaging.addOxMessageListener(new OxMessageListener() {
|
||||
@Override
|
||||
public void newIncomingOxMessage(OpenPgpContact contact, Message originalMessage, SigncryptElement decryptedPayload, OpenPgpMetadata metadata) {
|
||||
if (((Message.Body) decryptedPayload.getExtension(Message.Body.NAMESPACE)).getMessage().equals(body)) {
|
||||
bobReceivedMessage.signal();
|
||||
} else {
|
||||
bobReceivedMessage.signalFailure();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
aliceOpenPgp.setOpenPgpProvider(aliceProvider);
|
||||
bobOpenPgp.setOpenPgpProvider(bobProvider);
|
||||
|
||||
aliceFingerprint = aliceOpenPgp.generateAndImportKeyPair(alice);
|
||||
bobFingerprint = bobOpenPgp.generateAndImportKeyPair(bob);
|
||||
|
||||
aliceOpenPgp.announceSupportAndPublish();
|
||||
bobOpenPgp.announceSupportAndPublish();
|
||||
|
||||
OpenPgpContact bobForAlice = aliceOpenPgp.getOpenPgpContact(bob.asEntityBareJidIfPossible());
|
||||
OpenPgpContact aliceForBob = bobOpenPgp.getOpenPgpContact(alice.asEntityBareJidIfPossible());
|
||||
|
||||
bobForAlice.updateKeys(aliceConnection);
|
||||
|
||||
assertFalse(bobForAlice.isTrusted(bobFingerprint));
|
||||
assertFalse(aliceForBob.isTrusted(aliceFingerprint));
|
||||
|
||||
bobForAlice.trust(bobFingerprint);
|
||||
aliceForBob.trust(aliceFingerprint);
|
||||
|
||||
assertTrue(bobForAlice.isTrusted(bobFingerprint));
|
||||
assertTrue(aliceForBob.isTrusted(aliceFingerprint));
|
||||
|
||||
aliceInstantMessaging.sendOxMessage(bobForAlice, body);
|
||||
|
||||
bobReceivedMessage.waitForResult(timeout);
|
||||
}
|
||||
|
||||
@After
|
||||
public void deleteKeyMetadata()
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
OpenPgpPubSubUtil.deletePubkeysListNode(aliceConnection);
|
||||
OpenPgpPubSubUtil.deletePubkeysListNode(bobConnection);
|
||||
|
||||
if (aliceFingerprint != null) {
|
||||
OpenPgpPubSubUtil.deletePublicKeyNode(aliceConnection, aliceFingerprint);
|
||||
}
|
||||
if (bobFingerprint != null) {
|
||||
OpenPgpPubSubUtil.deletePublicKeyNode(bobConnection, bobFingerprint);
|
||||
}
|
||||
|
||||
if (aliceOpenPgp != null) {
|
||||
aliceOpenPgp.stopMetadataListener();
|
||||
}
|
||||
|
||||
if (bobOpenPgp != null) {
|
||||
bobOpenPgp.stopMetadataListener();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Integration Tests for Smacks support for XEP-0374: OpenPGP for XMPP: Instant Messaging.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0374.html">
|
||||
* XEP-0374: OpenPGP for XMPP: Instant Messaging</a>
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox_im;
|
19
smack-openpgp/build.gradle
Normal file
19
smack-openpgp/build.gradle
Normal file
|
@ -0,0 +1,19 @@
|
|||
description = """\
|
||||
Smack API for XEP-0373: OpenPGP for XMPP."""
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// Note that the test dependencies (junit, …) are inferred from the
|
||||
// sourceSet.test of the core subproject
|
||||
dependencies {
|
||||
compile project(':smack-core')
|
||||
compile project(':smack-extensions')
|
||||
compile project(':smack-experimental')
|
||||
|
||||
compile 'org.pgpainless:pgpainless-core:0.0.1-alpha2'
|
||||
|
||||
testCompile project(path: ":smack-core", configuration: "testRuntime")
|
||||
testCompile project(path: ":smack-core", configuration: "archives")
|
||||
}
|
|
@ -0,0 +1,417 @@
|
|||
/**
|
||||
*
|
||||
* 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.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.selection_strategy.BareJidUserId;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
|
||||
/**
|
||||
* The OpenPgpContact is sort of a specialized view on the OpenPgpStore, which gives you access to the information
|
||||
* about the user. It also allows contact-specific actions like fetching the contacts keys from PubSub etc.
|
||||
*/
|
||||
public class OpenPgpContact {
|
||||
|
||||
private final Logger LOGGER;
|
||||
|
||||
protected final BareJid jid;
|
||||
protected final OpenPgpStore store;
|
||||
protected final Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a new OpenPgpContact.
|
||||
*
|
||||
* @param jid {@link BareJid} of the contact.
|
||||
* @param store {@link OpenPgpStore}.
|
||||
*/
|
||||
public OpenPgpContact(BareJid jid, OpenPgpStore store) {
|
||||
this.jid = jid;
|
||||
this.store = store;
|
||||
LOGGER = Logger.getLogger(OpenPgpContact.class.getName() + ":" + jid.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the jid of the contact.
|
||||
*
|
||||
* @return jid
|
||||
*/
|
||||
public BareJid getJid() {
|
||||
return jid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any available public keys of the user. The result might also contain outdated or invalid keys.
|
||||
*
|
||||
* @return any keys of the contact.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
public PGPPublicKeyRingCollection getAnyPublicKeys() throws IOException, PGPException {
|
||||
return store.getPublicKeysOf(jid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return any announced public keys. This is the set returned by {@link #getAnyPublicKeys()} with non-announced
|
||||
* keys and keys which lack a user-id with the contacts jid removed.
|
||||
*
|
||||
* @return announced keys of the contact
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
public PGPPublicKeyRingCollection getAnnouncedPublicKeys() throws IOException, PGPException {
|
||||
PGPPublicKeyRingCollection anyKeys = getAnyPublicKeys();
|
||||
Map<OpenPgpV4Fingerprint, Date> announced = store.getAnnouncedFingerprintsOf(jid);
|
||||
|
||||
BareJidUserId.PubRingSelectionStrategy userIdFilter = new BareJidUserId.PubRingSelectionStrategy();
|
||||
|
||||
PGPPublicKeyRingCollection announcedKeysCollection = null;
|
||||
for (OpenPgpV4Fingerprint announcedFingerprint : announced.keySet()) {
|
||||
PGPPublicKeyRing ring = anyKeys.getPublicKeyRing(announcedFingerprint.getKeyId());
|
||||
|
||||
if (ring == null) continue;
|
||||
|
||||
ring = BCUtil.removeUnassociatedKeysFromKeyRing(ring, ring.getPublicKey(announcedFingerprint.getKeyId()));
|
||||
|
||||
if (!userIdFilter.accept(getJid(), ring)) {
|
||||
LOGGER.log(Level.WARNING, "Ignore key " + Long.toHexString(ring.getPublicKey().getKeyID()) +
|
||||
" as it lacks the user-id \"xmpp" + getJid().toString() + "\"");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (announcedKeysCollection == null) {
|
||||
announcedKeysCollection = new PGPPublicKeyRingCollection(Collections.singleton(ring));
|
||||
} else {
|
||||
announcedKeysCollection = PGPPublicKeyRingCollection.addPublicKeyRing(announcedKeysCollection, ring);
|
||||
}
|
||||
}
|
||||
|
||||
return announcedKeysCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PGPPublicKeyRingCollection}, which contains all keys from {@code keys}, which are marked with the
|
||||
* {@link OpenPgpTrustStore.Trust} state of {@code trust}.
|
||||
*
|
||||
* @param keys {@link PGPPublicKeyRingCollection}
|
||||
* @param trust {@link OpenPgpTrustStore.Trust}
|
||||
*
|
||||
* @return all keys from {@code keys} with trust state {@code trust}.
|
||||
*
|
||||
* @throws IOException IO error
|
||||
*/
|
||||
protected PGPPublicKeyRingCollection getPublicKeysOfTrustState(PGPPublicKeyRingCollection keys,
|
||||
OpenPgpTrustStore.Trust trust)
|
||||
throws IOException {
|
||||
|
||||
if (keys == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<PGPPublicKeyRing> toRemove = new HashSet<>();
|
||||
Iterator<PGPPublicKeyRing> iterator = keys.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
PGPPublicKeyRing ring = iterator.next();
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(ring);
|
||||
if (store.getTrust(getJid(), fingerprint) != trust) {
|
||||
toRemove.add(ring);
|
||||
}
|
||||
}
|
||||
|
||||
for (PGPPublicKeyRing ring : toRemove) {
|
||||
keys = PGPPublicKeyRingCollection.removePublicKeyRing(keys, ring);
|
||||
}
|
||||
|
||||
if (!keys.iterator().hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PGPPublicKeyRingCollection} which contains all public keys of the contact, which are announced,
|
||||
* as well as marked as {@link OpenPgpStore.Trust#trusted}.
|
||||
*
|
||||
* @return announced, trusted keys.
|
||||
*
|
||||
* @throws IOException IO error
|
||||
* @throws PGPException PGP error
|
||||
*/
|
||||
public PGPPublicKeyRingCollection getTrustedAnnouncedKeys()
|
||||
throws IOException, PGPException {
|
||||
PGPPublicKeyRingCollection announced = getAnnouncedPublicKeys();
|
||||
PGPPublicKeyRingCollection trusted = getPublicKeysOfTrustState(announced, OpenPgpTrustStore.Trust.trusted);
|
||||
return trusted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys of the contact, which have the trust state
|
||||
* {@link OpenPgpStore.Trust#trusted}.
|
||||
*
|
||||
* @return trusted fingerprints
|
||||
*
|
||||
* @throws IOException IO error
|
||||
* @throws PGPException PGP error
|
||||
*/
|
||||
public Set<OpenPgpV4Fingerprint> getTrustedFingerprints()
|
||||
throws IOException, PGPException {
|
||||
return getFingerprintsOfKeysWithState(getAnyPublicKeys(), OpenPgpTrustStore.Trust.trusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys of the contact, which have the trust state
|
||||
* {@link OpenPgpStore.Trust#untrusted}.
|
||||
*
|
||||
* @return untrusted fingerprints
|
||||
*
|
||||
* @throws IOException IO error
|
||||
* @throws PGPException PGP error
|
||||
*/
|
||||
public Set<OpenPgpV4Fingerprint> getUntrustedFingerprints()
|
||||
throws IOException, PGPException {
|
||||
return getFingerprintsOfKeysWithState(getAnyPublicKeys(), OpenPgpTrustStore.Trust.untrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys of the contact, which have the trust state
|
||||
* {@link OpenPgpStore.Trust#undecided}.
|
||||
*
|
||||
* @return undecided fingerprints
|
||||
*
|
||||
* @throws IOException IO error
|
||||
* @throws PGPException PGP error
|
||||
*/
|
||||
public Set<OpenPgpV4Fingerprint> getUndecidedFingerprints()
|
||||
throws IOException, PGPException {
|
||||
return getFingerprintsOfKeysWithState(getAnyPublicKeys(), OpenPgpTrustStore.Trust.undecided);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys in {@code publicKeys}, which are marked with the
|
||||
* {@link OpenPgpTrustStore.Trust} of {@code trust}.
|
||||
*
|
||||
* @param publicKeys {@link PGPPublicKeyRingCollection} of keys which are iterated.
|
||||
* @param trust {@link OpenPgpTrustStore.Trust} state.
|
||||
* @return {@link Set} of fingerprints
|
||||
*
|
||||
* @throws IOException IO error
|
||||
*/
|
||||
public Set<OpenPgpV4Fingerprint> getFingerprintsOfKeysWithState(PGPPublicKeyRingCollection publicKeys,
|
||||
OpenPgpTrustStore.Trust trust)
|
||||
throws IOException {
|
||||
PGPPublicKeyRingCollection keys = getPublicKeysOfTrustState(publicKeys, trust);
|
||||
Set<OpenPgpV4Fingerprint> fingerprints = new HashSet<>();
|
||||
|
||||
if (keys == null) {
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
for (PGPPublicKeyRing ring : keys) {
|
||||
fingerprints.add(new OpenPgpV4Fingerprint(ring));
|
||||
}
|
||||
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the {@link OpenPgpTrustStore.Trust} state of the key identified by the {@code fingerprint}.
|
||||
*
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key
|
||||
* @return trust record
|
||||
*
|
||||
* @throws IOException IO error
|
||||
*/
|
||||
public OpenPgpTrustStore.Trust getTrust(OpenPgpV4Fingerprint fingerprint)
|
||||
throws IOException {
|
||||
return store.getTrust(getJid(), fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine, whether the key identified by the {@code fingerprint} is marked as
|
||||
* {@link OpenPgpTrustStore.Trust#trusted} or not.
|
||||
*
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key
|
||||
* @return true, if the key is marked as trusted, false otherwise
|
||||
*
|
||||
* @throws IOException IO error
|
||||
*/
|
||||
public boolean isTrusted(OpenPgpV4Fingerprint fingerprint)
|
||||
throws IOException {
|
||||
return getTrust(fingerprint) == OpenPgpTrustStore.Trust.trusted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a key as {@link OpenPgpStore.Trust#trusted}.
|
||||
*
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key to mark as trusted.
|
||||
*
|
||||
* @throws IOException IO error
|
||||
*/
|
||||
public void trust(OpenPgpV4Fingerprint fingerprint)
|
||||
throws IOException {
|
||||
store.setTrust(getJid(), fingerprint, OpenPgpTrustStore.Trust.trusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a key as {@link OpenPgpStore.Trust#untrusted}.
|
||||
*
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key to mark as untrusted.
|
||||
*
|
||||
* @throws IOException IO error
|
||||
*/
|
||||
public void distrust(OpenPgpV4Fingerprint fingerprint)
|
||||
throws IOException {
|
||||
store.setTrust(getJid(), fingerprint, OpenPgpTrustStore.Trust.untrusted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine, whether there are keys available, for which we did not yet decided whether to trust them or not.
|
||||
*
|
||||
* @return more than 0 keys with trust state {@link OpenPgpTrustStore.Trust#undecided}.
|
||||
*
|
||||
* @throws IOException I/O error reading the keys or trust records.
|
||||
* @throws PGPException PGP error reading the keys.
|
||||
*/
|
||||
public boolean hasUndecidedKeys()
|
||||
throws IOException, PGPException {
|
||||
return getUndecidedFingerprints().size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link Map} of any unfetchable keys fingerprints and the cause of them not being fetched.
|
||||
*
|
||||
* @return unfetchable keys
|
||||
*/
|
||||
public Map<OpenPgpV4Fingerprint, Throwable> getUnfetchableKeys() {
|
||||
return new HashMap<>(unfetchableKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the contacts keys by consulting the users PubSub nodes.
|
||||
* This method fetches the users metadata node and then tries to fetch any announced keys.
|
||||
*
|
||||
* @param connection our {@link XMPPConnection}.
|
||||
*
|
||||
* @throws InterruptedException In case the thread gets interrupted.
|
||||
* @throws SmackException.NotConnectedException in case the connection is not connected.
|
||||
* @throws SmackException.NoResponseException in case the server doesn't respond.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws PubSubException.NotALeafNodeException in case the metadata node is not a {@link LeafNode}.
|
||||
* @throws PubSubException.NotAPubSubNodeException in case the metadata node is not a PubSub node.
|
||||
* @throws IOException IO is brittle.
|
||||
*/
|
||||
public void updateKeys(XMPPConnection connection) throws InterruptedException, SmackException.NotConnectedException,
|
||||
SmackException.NoResponseException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException,
|
||||
PubSubException.NotAPubSubNodeException, IOException {
|
||||
PublicKeysListElement metadata = OpenPgpPubSubUtil.fetchPubkeysList(connection, getJid());
|
||||
if (metadata == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateKeys(connection, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the contacts keys using a prefetched {@link PublicKeysListElement}.
|
||||
*
|
||||
* @param connection our {@link XMPPConnection}.
|
||||
* @param metadata pre-fetched OX metadata node of the contact.
|
||||
*
|
||||
* @throws InterruptedException in case the thread gets interrupted.
|
||||
* @throws SmackException.NotConnectedException in case the connection is not connected.
|
||||
* @throws SmackException.NoResponseException in case the server doesn't respond.
|
||||
* @throws IOException IO is dangerous.
|
||||
*/
|
||||
public void updateKeys(XMPPConnection connection, PublicKeysListElement metadata)
|
||||
throws InterruptedException, SmackException.NotConnectedException, SmackException.NoResponseException,
|
||||
IOException {
|
||||
|
||||
Map<OpenPgpV4Fingerprint, Date> fingerprintsAndDates = new HashMap<>();
|
||||
for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) {
|
||||
fingerprintsAndDates.put(fingerprint, metadata.getMetadata().get(fingerprint).getDate());
|
||||
}
|
||||
|
||||
store.setAnnouncedFingerprintsOf(getJid(), fingerprintsAndDates);
|
||||
Map<OpenPgpV4Fingerprint, Date> fetchDates = store.getPublicKeyFetchDates(getJid());
|
||||
|
||||
for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) {
|
||||
Date fetchDate = fetchDates.get(fingerprint);
|
||||
if (fetchDate != null && fingerprintsAndDates.get(fingerprint) != null && fetchDate.after(fingerprintsAndDates.get(fingerprint))) {
|
||||
LOGGER.log(Level.FINE, "Skip key " + Long.toHexString(fingerprint.getKeyId()) + " as we already have the most recent version. " +
|
||||
"Last announced: " + fingerprintsAndDates.get(fingerprint).toString() + " Last fetched: " + fetchDate.toString());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
PubkeyElement key = OpenPgpPubSubUtil.fetchPubkey(connection, getJid(), fingerprint);
|
||||
unfetchableKeys.remove(fingerprint);
|
||||
fetchDates.put(fingerprint, new Date());
|
||||
if (key == null) {
|
||||
LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) +
|
||||
" can not be imported: Is null");
|
||||
unfetchableKeys.put(fingerprint, new NullPointerException("Public key is null."));
|
||||
continue;
|
||||
}
|
||||
PGPPublicKeyRing keyRing = new PGPPublicKeyRing(Base64.decode(key.getDataElement().getB64Data()), new BcKeyFingerprintCalculator());
|
||||
store.importPublicKey(getJid(), keyRing);
|
||||
} catch (PubSubException.NotAPubSubNodeException | PubSubException.NotALeafNodeException |
|
||||
XMPPException.XMPPErrorException e) {
|
||||
LOGGER.log(Level.WARNING, "Error fetching public key " + Long.toHexString(fingerprint.getKeyId()), e);
|
||||
unfetchableKeys.put(fingerprint, e);
|
||||
} catch (PGPException | IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) +
|
||||
" can not be imported.", e);
|
||||
unfetchableKeys.put(fingerprint, e);
|
||||
} catch (MissingUserIdOnKeyException e) {
|
||||
LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) +
|
||||
" is missing the user-id \"xmpp:" + getJid() + "\". Refuse to import it.", e);
|
||||
unfetchableKeys.put(fingerprint, e);
|
||||
}
|
||||
}
|
||||
store.setPublicKeyFetchDates(getJid(), fetchDates);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,709 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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 static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS;
|
||||
import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS_NOTIFY;
|
||||
import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.publishPublicKey;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.Manager;
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.chat2.Chat;
|
||||
import org.jivesoftware.smack.chat2.ChatManager;
|
||||
import org.jivesoftware.smack.chat2.IncomingChatMessageListener;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.util.Async;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback;
|
||||
import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback;
|
||||
import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider;
|
||||
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.PubkeyElement;
|
||||
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.InvalidBackupCodeException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.exception.NoBackupFoundException;
|
||||
import org.jivesoftware.smackx.ox.listener.CryptElementReceivedListener;
|
||||
import org.jivesoftware.smackx.ox.listener.SignElementReceivedListener;
|
||||
import org.jivesoftware.smackx.ox.listener.SigncryptElementReceivedListener;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
|
||||
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.PubSubFeature;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.jid.EntityBareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Entry point for Smacks API for OpenPGP for XMPP.
|
||||
*
|
||||
* <h2>Setup</h2>
|
||||
*
|
||||
* In order to use OpenPGP for XMPP in Smack, just follow the following procedure.<br>
|
||||
* <br>
|
||||
* First, acquire an instance of the {@link OpenPgpManager} for your {@link XMPPConnection} using
|
||||
* {@link #getInstanceFor(XMPPConnection)}.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* OpenPgpManager openPgpManager = OpenPgpManager.getInstanceFor(connection);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* You also need an {@link OpenPgpProvider}, as well as an {@link OpenPgpStore}.
|
||||
* The provider must be registered using {@link #setOpenPgpProvider(OpenPgpProvider)}.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* OpenPgpStore store = new FileBasedOpenPgpStore(storePath);
|
||||
* OpenPgpProvider provider = new PainlessOpenPgpProvider(connection, store);
|
||||
* openPgpManager.setOpenPgpProvider(provider);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* It is also advised to register a custom {@link SecretKeyRingProtector} using
|
||||
* {@link OpenPgpStore#setKeyRingProtector(SecretKeyRingProtector)} in order to be able to handle password protected
|
||||
* secret keys.<br>
|
||||
* <br>
|
||||
* Speaking of keys, you can now check, if you have any keys available in your {@link OpenPgpStore} by doing
|
||||
* {@link #hasSecretKeysAvailable()}.<br>
|
||||
* <br>
|
||||
* If you do, you can now announce support for OX and publish those keys using {@link #announceSupportAndPublish()}.<br>
|
||||
* <br>
|
||||
* Otherwise, you can either generate fresh keys using {@link #generateAndImportKeyPair(BareJid)},
|
||||
* or try to restore a secret key backup from your private PubSub node by doing
|
||||
* {@link #restoreSecretKeyServerBackup(AskForBackupCodeCallback)}.<br>
|
||||
* <br>
|
||||
* In any case you should still do an {@link #announceSupportAndPublish()} afterwards.
|
||||
* <br>
|
||||
* <br>
|
||||
* Contacts are represented by {@link OpenPgpContact}s in the context of OpenPGP for XMPP. You can get those by using
|
||||
* {@link #getOpenPgpContact(EntityBareJid)}. The main function of {@link OpenPgpContact}s is to bundle information
|
||||
* about the OpenPGP capabilities of a contact in one spot. The pendant to the {@link OpenPgpContact} is the
|
||||
* {@link OpenPgpSelf}, which encapsulates your own OpenPGP identity. Both classes can be used to acquire information
|
||||
* about the OpenPGP keys of a user.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html">
|
||||
* XEP-0373: OpenPGP for XMPP</a>
|
||||
*/
|
||||
public final class OpenPgpManager extends Manager {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OpenPgpManager.class.getName());
|
||||
|
||||
/**
|
||||
* Map of instances.
|
||||
*/
|
||||
private static final Map<XMPPConnection, OpenPgpManager> INSTANCES = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* {@link OpenPgpProvider} responsible for processing keys, encrypting and decrypting messages and so on.
|
||||
*/
|
||||
private OpenPgpProvider provider;
|
||||
|
||||
private final Set<SigncryptElementReceivedListener> signcryptElementReceivedListeners = new HashSet<>();
|
||||
private final Set<SignElementReceivedListener> signElementReceivedListeners = new HashSet<>();
|
||||
private final Set<CryptElementReceivedListener> cryptElementReceivedListeners = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Private constructor to avoid instantiation without putting the object into {@code INSTANCES}.
|
||||
*
|
||||
* @param connection xmpp connection.
|
||||
*/
|
||||
private OpenPgpManager(XMPPConnection connection) {
|
||||
super(connection);
|
||||
ChatManager.getInstanceFor(connection).addIncomingListener(incomingOpenPgpMessageListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the instance of the {@link OpenPgpManager} which belongs to the {@code connection}.
|
||||
*
|
||||
* @param connection xmpp connection.
|
||||
* @return instance of the manager.
|
||||
*/
|
||||
public static OpenPgpManager getInstanceFor(XMPPConnection connection) {
|
||||
OpenPgpManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new OpenPgpManager(connection);
|
||||
INSTANCES.put(connection, manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return our own {@link BareJid}.
|
||||
*
|
||||
* @return our bareJid
|
||||
*
|
||||
* @throws SmackException.NotLoggedInException in case our connection is not logged in, which means our BareJid is unknown.
|
||||
*/
|
||||
public BareJid getJidOrThrow() throws SmackException.NotLoggedInException {
|
||||
throwIfNotAuthenticated();
|
||||
return connection().getUser().asEntityBareJidOrThrow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link OpenPgpProvider} which will be used to process incoming OpenPGP elements,
|
||||
* as well as to execute cryptographic operations.
|
||||
*
|
||||
* @param provider OpenPgpProvider.
|
||||
*/
|
||||
public void setOpenPgpProvider(OpenPgpProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public OpenPgpProvider getOpenPgpProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our OpenPGP self.
|
||||
*
|
||||
* @return self
|
||||
* @throws SmackException.NotLoggedInException if we are not logged in
|
||||
*/
|
||||
public OpenPgpSelf getOpenPgpSelf() throws SmackException.NotLoggedInException {
|
||||
throwIfNoProviderSet();
|
||||
return new OpenPgpSelf(getJidOrThrow(), provider.getStore());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 InterruptedException if the thread gets interrupted.
|
||||
* @throws PubSubException.NotALeafNodeException if one of the PubSub nodes is not a {@link LeafNode}.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
* @throws IOException IO is dangerous.
|
||||
* @throws InvalidAlgorithmParameterException if illegal algorithm parameters are used for key generation.
|
||||
* @throws SmackException.NotLoggedInException if we are not logged in.
|
||||
* @throws PGPException if something goes wrong during key loading/generating
|
||||
*/
|
||||
public void announceSupportAndPublish()
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, InterruptedException,
|
||||
PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException, IOException,
|
||||
InvalidAlgorithmParameterException, SmackException.NotLoggedInException, PGPException {
|
||||
throwIfNoProviderSet();
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
OpenPgpV4Fingerprint primaryFingerprint = getOurFingerprint();
|
||||
|
||||
if (primaryFingerprint == null) {
|
||||
primaryFingerprint = generateAndImportKeyPair(getJidOrThrow());
|
||||
}
|
||||
|
||||
// Create <pubkey/> element
|
||||
PubkeyElement pubkeyElement;
|
||||
try {
|
||||
pubkeyElement = createPubkeyElement(getJidOrThrow(), primaryFingerprint, new Date());
|
||||
} catch (MissingOpenPgpKeyException 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a fresh OpenPGP key pair and import it.
|
||||
*
|
||||
* @param ourJid our {@link BareJid}.
|
||||
* @return {@link OpenPgpV4Fingerprint} of the generated key.
|
||||
* @throws NoSuchAlgorithmException if the JVM doesn't support one of the used algorithms.
|
||||
* @throws InvalidAlgorithmParameterException if the used algorithm parameters are invalid.
|
||||
* @throws NoSuchProviderException if we are missing a cryptographic provider.
|
||||
* @throws PGPException PGP is brittle.
|
||||
* @throws IOException IO is dangerous.
|
||||
*/
|
||||
public OpenPgpV4Fingerprint generateAndImportKeyPair(BareJid ourJid)
|
||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException,
|
||||
PGPException, IOException {
|
||||
|
||||
throwIfNoProviderSet();
|
||||
OpenPgpStore store = provider.getStore();
|
||||
PGPKeyRing keys = store.generateKeyRing(ourJid);
|
||||
try {
|
||||
store.importSecretKey(ourJid, keys.getSecretKeys());
|
||||
store.importPublicKey(ourJid, keys.getPublicKeys());
|
||||
} catch (MissingUserIdOnKeyException e) {
|
||||
// This should never throw, since we set our jid literally one line above this comment.
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keys.getSecretKeys());
|
||||
|
||||
store.setTrust(ourJid, fingerprint, OpenPgpTrustStore.Trust.trusted);
|
||||
|
||||
return fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair.
|
||||
*
|
||||
* @return fingerprint.
|
||||
* @throws SmackException.NotLoggedInException in case we are not logged in.
|
||||
* @throws IOException IO is dangerous.
|
||||
* @throws PGPException PGP is brittle.
|
||||
*/
|
||||
public OpenPgpV4Fingerprint getOurFingerprint()
|
||||
throws SmackException.NotLoggedInException, IOException, PGPException {
|
||||
return getOpenPgpSelf().getSigningKeyFingerprint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an OpenPGP capable contact.
|
||||
* This object can be used as an entry point to OpenPGP related API.
|
||||
*
|
||||
* @param jid {@link BareJid} of the contact.
|
||||
* @return {@link OpenPgpContact}.
|
||||
*/
|
||||
public OpenPgpContact getOpenPgpContact(EntityBareJid jid) {
|
||||
throwIfNoProviderSet();
|
||||
return provider.getStore().getOpenPgpContact(jid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if we have a secret key available, otherwise false.
|
||||
*
|
||||
* @return true if secret key available
|
||||
*
|
||||
* @throws SmackException.NotLoggedInException If we are not logged in (we need to know our jid in order to look up
|
||||
* our keys in the key store.
|
||||
* @throws PGPException in case the keys in the store are damaged somehow.
|
||||
* @throws IOException IO is dangerous.
|
||||
*/
|
||||
public boolean hasSecretKeysAvailable() throws SmackException.NotLoggedInException, PGPException, IOException {
|
||||
throwIfNoProviderSet();
|
||||
return getOpenPgpSelf().hasSecretKeyAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine, if we can sync secret keys using private PEP nodes as described in the XEP.
|
||||
* Requirements on the server side are support for PEP and support for the whitelist access model of PubSub.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a>
|
||||
*
|
||||
* @param connection XMPP connection
|
||||
* @return true, if the server supports secret key backups, otherwise false.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws InterruptedException if the thread is interrupted.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
*/
|
||||
public static boolean serverSupportsSecretKeyBackups(XMPPConnection connection)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
return ServiceDiscoveryManager.getInstanceFor(connection)
|
||||
.serverSupportsFeature(PubSubFeature.access_whitelist.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the metadata listener. This method is mainly used in tests.
|
||||
*/
|
||||
public void stopMetadataListener() {
|
||||
PEPManager.getInstanceFor(connection()).removePEPListener(metadataListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload the encrypted secret key to a private PEP node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a>
|
||||
*
|
||||
* @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 InterruptedException if the thread is interrupted.
|
||||
* @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
* @throws SmackException.NotLoggedInException if we are not logged in.
|
||||
* @throws IOException IO is dangerous.
|
||||
* @throws SmackException.FeatureNotSupportedException if the server doesn't support the PubSub whitelist access model.
|
||||
* @throws PGPException PGP is brittle
|
||||
* @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up.
|
||||
*/
|
||||
public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback,
|
||||
SecretKeyBackupSelectionCallback selectKeyCallback)
|
||||
throws InterruptedException, PubSubException.NotALeafNodeException,
|
||||
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException,
|
||||
SmackException.NotLoggedInException, IOException,
|
||||
SmackException.FeatureNotSupportedException, PGPException, MissingOpenPgpKeyException {
|
||||
throwIfNoProviderSet();
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
BareJid ownJid = connection().getUser().asBareJid();
|
||||
|
||||
String backupCode = SecretKeyBackupHelper.generateBackupPassword();
|
||||
|
||||
PGPSecretKeyRingCollection secretKeyRings = provider.getStore().getSecretKeysOf(ownJid);
|
||||
|
||||
Set<OpenPgpV4Fingerprint> availableKeyPairs = new HashSet<>();
|
||||
for (PGPSecretKeyRing ring : secretKeyRings) {
|
||||
availableKeyPairs.add(new OpenPgpV4Fingerprint(ring));
|
||||
}
|
||||
|
||||
Set<OpenPgpV4Fingerprint> selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs);
|
||||
|
||||
SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode);
|
||||
|
||||
OpenPgpPubSubUtil.depositSecretKey(connection(), secretKey);
|
||||
displayCodeCallback.displayBackupCode(backupCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the private {@link LeafNode} containing our secret key backup.
|
||||
*
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws InterruptedException if the thread gets interrupted.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
* @throws SmackException.NotLoggedInException if we are not logged in.
|
||||
*/
|
||||
public void deleteSecretKeyServerBackup()
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException, SmackException.NotLoggedInException {
|
||||
throwIfNotAuthenticated();
|
||||
OpenPgpPubSubUtil.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.
|
||||
* @return fingerprint of the restored secret key
|
||||
*
|
||||
* @throws InterruptedException if the thread gets interrupted.
|
||||
* @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
* @throws InvalidBackupCodeException if the user-provided backup code is invalid.
|
||||
* @throws SmackException.NotLoggedInException if we are not logged in
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws MissingUserIdOnKeyException if the key that is to be imported is missing a user-id with our jid
|
||||
* @throws NoBackupFoundException if no secret key backup has been found
|
||||
* @throws PGPException in case the restored secret key is damaged.
|
||||
*/
|
||||
public OpenPgpV4Fingerprint restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback)
|
||||
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
|
||||
SmackException.NotConnectedException, SmackException.NoResponseException,
|
||||
InvalidBackupCodeException, SmackException.NotLoggedInException, IOException, MissingUserIdOnKeyException,
|
||||
NoBackupFoundException, PGPException {
|
||||
throwIfNoProviderSet();
|
||||
throwIfNotAuthenticated();
|
||||
SecretkeyElement backup = OpenPgpPubSubUtil.fetchSecretKey(connection());
|
||||
if (backup == null) {
|
||||
throw new NoBackupFoundException();
|
||||
}
|
||||
|
||||
String backupCode = codeCallback.askForBackupCode();
|
||||
|
||||
PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(backup, backupCode);
|
||||
provider.getStore().importSecretKey(getJidOrThrow(), secretKeys);
|
||||
provider.getStore().importPublicKey(getJidOrThrow(), BCUtil.publicKeyRingFromSecretKeyRing(secretKeys));
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream(2048);
|
||||
for (PGPSecretKey sk : secretKeys) {
|
||||
PGPPublicKey pk = sk.getPublicKey();
|
||||
if (pk != null) pk.encode(buffer);
|
||||
}
|
||||
PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(buffer.toByteArray(), new BcKeyFingerprintCalculator());
|
||||
provider.getStore().importPublicKey(getJidOrThrow(), publicKeys);
|
||||
|
||||
return new OpenPgpV4Fingerprint(secretKeys);
|
||||
}
|
||||
|
||||
/*
|
||||
Private stuff.
|
||||
*/
|
||||
|
||||
/**
|
||||
* {@link PEPListener} that listens for changes to the OX public keys metadata node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a>
|
||||
*/
|
||||
private final PEPListener metadataListener = new PEPListener() {
|
||||
@Override
|
||||
public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) {
|
||||
if (PEP_NODE_PUBLIC_KEYS.equals(event.getEvent().getNode())) {
|
||||
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();
|
||||
|
||||
processPublicKeysListElement(from, listElement);
|
||||
}
|
||||
}, "ProcessOXMetadata");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void processPublicKeysListElement(BareJid contact, PublicKeysListElement listElement) {
|
||||
OpenPgpContact openPgpContact = getOpenPgpContact(contact.asEntityBareJidIfPossible());
|
||||
try {
|
||||
openPgpContact.updateKeys(connection(), listElement);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Could not update contacts keys", e);
|
||||
}
|
||||
}
|
||||
|
||||
public OpenPgpMessage decryptOpenPgpElement(OpenPgpElement element, OpenPgpContact contact) throws SmackException.NotLoggedInException, IOException, PGPException {
|
||||
return provider.decryptAndOrVerify(element, getOpenPgpSelf(), contact);
|
||||
}
|
||||
|
||||
private final IncomingChatMessageListener incomingOpenPgpMessageListener =
|
||||
new IncomingChatMessageListener() {
|
||||
@Override
|
||||
public void newIncomingMessage(final EntityBareJid from, final Message message, Chat chat) {
|
||||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
OpenPgpElement element = message.getExtension(OpenPgpElement.ELEMENT, OpenPgpElement.NAMESPACE);
|
||||
if (element == null) {
|
||||
// Message does not contain an OpenPgpElement -> discard
|
||||
return;
|
||||
}
|
||||
|
||||
OpenPgpContact contact = getOpenPgpContact(from);
|
||||
|
||||
OpenPgpMessage decrypted = null;
|
||||
OpenPgpContentElement contentElement = null;
|
||||
try {
|
||||
decrypted = decryptOpenPgpElement(element, contact);
|
||||
contentElement = decrypted.getOpenPgpContentElement();
|
||||
} catch (PGPException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not decrypt incoming OpenPGP encrypted message", e);
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Invalid XML content of incoming OpenPGP encrypted message", e);
|
||||
} catch (SmackException.NotLoggedInException e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot determine our JID, since we are not logged in.", e);
|
||||
}
|
||||
|
||||
if (contentElement instanceof SigncryptElement) {
|
||||
for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) {
|
||||
l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentElement instanceof SignElement) {
|
||||
for (SignElementReceivedListener l : signElementReceivedListeners) {
|
||||
l.signElementReceived(contact, message, (SignElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (contentElement instanceof CryptElement) {
|
||||
for (CryptElementReceivedListener l : cryptElementReceivedListeners) {
|
||||
l.cryptElementReceived(contact, message, (CryptElement) contentElement, decrypted.getMetadata());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
else {
|
||||
throw new AssertionError("Invalid element received: " + contentElement.getClass().getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a {@link PubkeyElement} which contains the OpenPGP public key of {@code owner} which belongs to
|
||||
* the {@link OpenPgpV4Fingerprint} {@code fingerprint}.
|
||||
*
|
||||
* @param owner owner of the public key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @param date date of creation of the element
|
||||
* @return {@link PubkeyElement} containing the key
|
||||
*
|
||||
* @throws MissingOpenPgpKeyException if the public key notated by the fingerprint cannot be found
|
||||
*/
|
||||
private PubkeyElement createPubkeyElement(BareJid owner,
|
||||
OpenPgpV4Fingerprint fingerprint,
|
||||
Date date)
|
||||
throws MissingOpenPgpKeyException, IOException, PGPException {
|
||||
PGPPublicKeyRing ring = provider.getStore().getPublicKeyRing(owner, fingerprint);
|
||||
if (ring != null) {
|
||||
byte[] keyBytes = ring.getEncoded(true);
|
||||
return createPubkeyElement(keyBytes, date);
|
||||
}
|
||||
throw new MissingOpenPgpKeyException(owner, fingerprint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link PubkeyElement} which contains the given {@code data} base64 encoded.
|
||||
*
|
||||
* @param bytes byte representation of an OpenPGP public key
|
||||
* @param date date of creation of the element
|
||||
* @return {@link PubkeyElement} containing the key
|
||||
*/
|
||||
private static PubkeyElement createPubkeyElement(byte[] bytes, Date date) {
|
||||
return new PubkeyElement(new PubkeyElement.PubkeyDataElement(Base64.encode(bytes)), date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link SigncryptElementReceivedListener} on the {@link OpenPgpManager}.
|
||||
* That listener will get informed whenever a {@link SigncryptElement} has been received and successfully decrypted.
|
||||
*
|
||||
* Note: This method is not intended for clients to listen for incoming {@link SigncryptElement}s.
|
||||
* Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as
|
||||
* OpenPGP for XMPP: Instant Messaging.
|
||||
*
|
||||
* @param listener listener that gets registered
|
||||
*/
|
||||
public void registerSigncryptReceivedListener(SigncryptElementReceivedListener listener) {
|
||||
signcryptElementReceivedListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a prior registered {@link SigncryptElementReceivedListener}. That listener will no longer get
|
||||
* informed about incoming decrypted {@link SigncryptElement}s.
|
||||
*
|
||||
* @param listener listener that gets unregistered
|
||||
*/
|
||||
void unregisterSigncryptElementReceivedListener(SigncryptElementReceivedListener listener) {
|
||||
signcryptElementReceivedListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link SignElementReceivedListener} on the {@link OpenPgpManager}.
|
||||
* That listener will get informed whenever a {@link SignElement} has been received and successfully verified.
|
||||
*
|
||||
* Note: This method is not intended for clients to listen for incoming {@link SignElement}s.
|
||||
* Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as
|
||||
* OpenPGP for XMPP: Instant Messaging.
|
||||
*
|
||||
* @param listener listener that gets registered
|
||||
*/
|
||||
void registerSignElementReceivedListener(SignElementReceivedListener listener) {
|
||||
signElementReceivedListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a prior registered {@link SignElementReceivedListener}. That listener will no longer get
|
||||
* informed about incoming decrypted {@link SignElement}s.
|
||||
*
|
||||
* @param listener listener that gets unregistered
|
||||
*/
|
||||
void unregisterSignElementReceivedListener(SignElementReceivedListener listener) {
|
||||
signElementReceivedListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link CryptElementReceivedListener} on the {@link OpenPgpManager}.
|
||||
* That listener will get informed whenever a {@link CryptElement} has been received and successfully decrypted.
|
||||
*
|
||||
* Note: This method is not intended for clients to listen for incoming {@link CryptElement}s.
|
||||
* Instead its purpose is to allow easy extension of XEP-0373 for custom OpenPGP profiles such as
|
||||
* OpenPGP for XMPP: Instant Messaging.
|
||||
*
|
||||
* @param listener listener that gets registered
|
||||
*/
|
||||
void registerCryptElementReceivedListener(CryptElementReceivedListener listener) {
|
||||
cryptElementReceivedListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a prior registered {@link CryptElementReceivedListener}. That listener will no longer get
|
||||
* informed about incoming decrypted {@link CryptElement}s.
|
||||
*
|
||||
* @param listener listener that gets unregistered
|
||||
*/
|
||||
void unregisterCryptElementReceivedListener(CryptElementReceivedListener listener) {
|
||||
cryptElementReceivedListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set.
|
||||
* The OpenPgpProvider is used to process information related to RFC-4880.
|
||||
*/
|
||||
private void throwIfNoProviderSet() {
|
||||
if (provider == null) {
|
||||
throw new IllegalStateException("No OpenPgpProvider set!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a {@link org.jivesoftware.smack.SmackException.NotLoggedInException} if the {@link XMPPConnection} of this
|
||||
* manager is not authenticated at this point.
|
||||
*
|
||||
* @throws SmackException.NotLoggedInException if we are not authenticated
|
||||
*/
|
||||
private void throwIfNotAuthenticated() throws SmackException.NotLoggedInException {
|
||||
if (!connection().isAuthenticated()) {
|
||||
throw new SmackException.NotLoggedInException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
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.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
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,
|
||||
;
|
||||
}
|
||||
|
||||
private final String element;
|
||||
private final State state;
|
||||
private final OpenPgpMetadata metadata;
|
||||
|
||||
private OpenPgpContentElement openPgpContentElement;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param content XML representation of the decrypted {@link OpenPgpContentElement}.
|
||||
* @param state {@link State} of the {@link OpenPgpContentElement}.
|
||||
* @param metadata Metadata about the encryption.
|
||||
*/
|
||||
public OpenPgpMessage(String content, State state, OpenPgpMetadata metadata) {
|
||||
this.metadata = Objects.requireNonNull(metadata);
|
||||
this.state = Objects.requireNonNull(state);
|
||||
this.element = Objects.requireNonNull(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param bytes bytes of the XML representation of the decrypted {@link OpenPgpContentElement}.
|
||||
* @param state {@link State} of the {@link OpenPgpContentElement}.
|
||||
* @param metadata metadata about the encryption.
|
||||
*/
|
||||
public OpenPgpMessage(byte[] bytes, State state, OpenPgpMetadata metadata) {
|
||||
this(new String(Objects.requireNonNull(bytes), Charset.forName("UTF-8")), state, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
return openPgpContentElement;
|
||||
}
|
||||
|
||||
private void ensureOpenPgpContentElementSet() throws XmlPullParserException, IOException {
|
||||
if (openPgpContentElement != null)
|
||||
return;
|
||||
|
||||
openPgpContentElement = OpenPgpContentElementProvider.parseOpenPgpContentElement(element);
|
||||
if (openPgpContentElement == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the state of the content element.
|
||||
if (openPgpContentElement instanceof SigncryptElement) {
|
||||
if (state != State.signcrypt) {
|
||||
throw new IllegalStateException("OpenPgpContentElement was signed and encrypted, but is not a SigncryptElement.");
|
||||
}
|
||||
} else if (openPgpContentElement instanceof SignElement) {
|
||||
if (state != State.sign) {
|
||||
throw new IllegalStateException("OpenPgpContentElement was signed and unencrypted, but is not a SignElement.");
|
||||
}
|
||||
} else if (openPgpContentElement instanceof CryptElement) {
|
||||
if (state != State.crypt) {
|
||||
throw new IllegalStateException("OpenPgpContentElement was unsigned and encrypted, but is not a CryptElement.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata about the encrypted message.
|
||||
*
|
||||
* @return metadata
|
||||
*/
|
||||
public OpenPgpMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
*
|
||||
* 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.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
|
||||
public class OpenPgpSelf extends OpenPgpContact {
|
||||
|
||||
OpenPgpSelf(BareJid jid, OpenPgpStore store) {
|
||||
super(jid, store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if we have a usable secret key available.
|
||||
* @return true if we have secret key, otherwise false.
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
public boolean hasSecretKeyAvailable() throws IOException, PGPException {
|
||||
return getSecretKeys() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PGPSecretKeyRingCollection} which contains all of our {@link PGPSecretKeyRing}s.
|
||||
* @return collection of our secret keys
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
public PGPSecretKeyRingCollection getSecretKeys() throws IOException, PGPException {
|
||||
return store.getSecretKeysOf(jid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link PGPSecretKeyRing} which we will use to sign our messages.
|
||||
* @return signing key
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
public PGPSecretKeyRing getSigningKeyRing() throws IOException, PGPException {
|
||||
PGPSecretKeyRingCollection secretKeyRings = getSecretKeys();
|
||||
if (secretKeyRings == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPSecretKeyRing signingKeyRing = null;
|
||||
for (PGPSecretKeyRing ring : secretKeyRings) {
|
||||
if (signingKeyRing == null) {
|
||||
signingKeyRing = ring;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ring.getPublicKey().getCreationTime().after(signingKeyRing.getPublicKey().getCreationTime())) {
|
||||
signingKeyRing = ring;
|
||||
}
|
||||
}
|
||||
|
||||
return signingKeyRing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OpenPgpV4Fingerprint} of our signing key.
|
||||
* @return fingerprint of signing key
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
public OpenPgpV4Fingerprint getSigningKeyFingerprint() throws IOException, PGPException {
|
||||
PGPSecretKeyRing signingKeyRing = getSigningKeyRing();
|
||||
return signingKeyRing != null ? new OpenPgpV4Fingerprint(signingKeyRing.getPublicKey()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PGPPublicKeyRingCollection} containing only the public keys belonging to our signing key ring.
|
||||
* TODO: Add support for public keys of other devices of the owner.
|
||||
*
|
||||
* @return public keys
|
||||
*
|
||||
* @throws IOException IO is dangerous.
|
||||
* @throws PGPException PGP is brittle.
|
||||
*/
|
||||
@Override
|
||||
public PGPPublicKeyRingCollection getAnnouncedPublicKeys() throws IOException, PGPException {
|
||||
PGPSecretKeyRing secretKeys = getSigningKeyRing();
|
||||
PGPPublicKeyRing publicKeys = getAnyPublicKeys().getPublicKeyRing(secretKeys.getPublicKey().getKeyID());
|
||||
publicKeys = BCUtil.removeUnassociatedKeysFromKeyRing(publicKeys, secretKeys.getPublicKey());
|
||||
return new PGPPublicKeyRingCollection(Collections.singleton(publicKeys));
|
||||
}
|
||||
}
|
|
@ -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.callback;
|
||||
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
public interface SecretKeyPassphraseCallback {
|
||||
|
||||
/**
|
||||
* This method gets called in case a passphrase is needed for a secret key.
|
||||
*
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @return passphrase for the key
|
||||
*/
|
||||
Passphrase onPassphraseNeeded(OpenPgpV4Fingerprint fingerprint);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
*
|
||||
* 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.backup;
|
||||
|
||||
public interface AskForBackupCodeCallback {
|
||||
|
||||
/**
|
||||
* This callback is used to ask the user to provide a backup code.
|
||||
* The backup code must follow the format described in XEP-0373 §5.3
|
||||
*
|
||||
* TODO: Update reflink
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#sect-idm139662753819792">
|
||||
* XEP-0373 §5.3 about the format of the backup code</a>
|
||||
*
|
||||
* @return backup code provided by the user.
|
||||
*/
|
||||
String askForBackupCode();
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
*
|
||||
* 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.backup;
|
||||
|
||||
public interface DisplayBackupCodeCallback {
|
||||
|
||||
/**
|
||||
* This method is used to provide a client access to the generated backup code.
|
||||
* The client can then go ahead and display the code to the user.
|
||||
* The backup code follows the format described in XEP-0373 §5.3
|
||||
*
|
||||
* TODO: Update reflink
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#sect-idm139662753819792">
|
||||
* XEP-0373 §5.3 about the format of the backup code</a>
|
||||
*
|
||||
* @param backupCode backup code
|
||||
*/
|
||||
void displayBackupCode(String backupCode);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
*
|
||||
* 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.backup;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
|
||||
/**
|
||||
* Callback to allow the user to decide, which locally available secret keys they want to include in a backup.
|
||||
*/
|
||||
public interface SecretKeyBackupSelectionCallback {
|
||||
|
||||
/**
|
||||
* Let the user decide, which secret keys they want to backup.
|
||||
*
|
||||
* @param availableSecretKeys {@link Set} of {@link OpenPgpV4Fingerprint}s of locally available
|
||||
* OpenPGP secret keys.
|
||||
* @return {@link Set} which contains the {@link OpenPgpV4Fingerprint}s the user decided to include
|
||||
* in the backup.
|
||||
*/
|
||||
Set<OpenPgpV4Fingerprint> selectKeysToBackup(Set<OpenPgpV4Fingerprint> availableSecretKeys);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
*
|
||||
* 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.backup;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
|
||||
/**
|
||||
* Callback to let the user decide which key from a backup they want to restore.
|
||||
*/
|
||||
public interface SecretKeyRestoreSelectionCallback {
|
||||
|
||||
/**
|
||||
* Let the user choose, which SecretKey they want to restore as the new primary OpenPGP signing key.
|
||||
* @param availableSecretKeys {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which are contained
|
||||
* in the backup.
|
||||
* @return {@link OpenPgpV4Fingerprint} of the key the user wants to restore as the new primary
|
||||
* signing key.
|
||||
*/
|
||||
OpenPgpV4Fingerprint selectSecretKeyToRestore(Set<OpenPgpV4Fingerprint> availableSecretKeys);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Callback classes Secret key backups.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.callback.backup;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Callback classes for XEP-0373: OpenPGP for XMPP.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.callback;
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
*
|
||||
* 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.crypto;
|
||||
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
|
||||
/**
|
||||
* Bundle together an {@link OpenPgpElement} and {@link OpenPgpMetadata}.
|
||||
*/
|
||||
public class OpenPgpElementAndMetadata {
|
||||
|
||||
private final OpenPgpElement element;
|
||||
private final OpenPgpMetadata metadata;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param element element
|
||||
* @param metadata metadata about the elements encryption
|
||||
*/
|
||||
public OpenPgpElementAndMetadata(OpenPgpElement element, OpenPgpMetadata metadata) {
|
||||
this.element = element;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OpenPgpElement}.
|
||||
*
|
||||
* @return element
|
||||
*/
|
||||
public OpenPgpElement getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link OpenPgpMetadata} about the {@link OpenPgpElement}s encryption/signatures.
|
||||
*
|
||||
* @return metadata
|
||||
*/
|
||||
public OpenPgpMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
*
|
||||
* 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.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpSelf;
|
||||
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.store.definition.OpenPgpStore;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
|
||||
public interface OpenPgpProvider {
|
||||
|
||||
/**
|
||||
* Return the {@link OpenPgpStore} instance of this provider.
|
||||
* This MUST NOT return null.
|
||||
*
|
||||
* @return store
|
||||
*/
|
||||
OpenPgpStore getStore();
|
||||
|
||||
/**
|
||||
* Sign a {@link SigncryptElement} using our signing key and encrypt it for all {@code recipients} and ourselves.
|
||||
*
|
||||
* @param element {@link SigncryptElement} which contains a payload which will be transmitted.
|
||||
* @param self our own OpenPGP identity.
|
||||
* @param recipients recipients identities.
|
||||
*
|
||||
* @return signed and encrypted {@link SigncryptElement} as a {@link OpenPgpElement}, along with
|
||||
* {@link OpenPgpMetadata} about the encryption/signatures.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
OpenPgpElementAndMetadata signAndEncrypt(SigncryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients)
|
||||
throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Sign a {@link SignElement} using our signing key.
|
||||
* @param element {@link SignElement} which contains a payload.
|
||||
* @param self our OpenPGP identity.
|
||||
*
|
||||
* @return signed {@link SignElement} as {@link OpenPgpElement}, along with {@link OpenPgpMetadata} about the
|
||||
* signatures.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
OpenPgpElementAndMetadata sign(SignElement element, OpenPgpSelf self)
|
||||
throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Encrypt a {@link CryptElement} for all {@code recipients} and ourselves.
|
||||
* @param element {@link CryptElement} which contains a payload which will be transmitted.
|
||||
* @param self our own OpenPGP identity.
|
||||
* @param recipients recipient identities.
|
||||
*
|
||||
* @return encrypted {@link CryptElement} as an {@link OpenPgpElement}, along with {@link OpenPgpMetadata} about
|
||||
* the encryption.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
OpenPgpElementAndMetadata encrypt(CryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients)
|
||||
throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Decrypt and/or verify signatures on an incoming {@link OpenPgpElement}.
|
||||
* If the message is encrypted, this method decrypts it. If it is (also) signed, the signature will be checked.
|
||||
* The resulting {@link OpenPgpMessage} contains the original {@link OpenPgpContentElement}, as well as information
|
||||
* about the encryption/signing.
|
||||
*
|
||||
* @param element signed and or encrypted {@link OpenPgpElement}.
|
||||
* @param self our OpenPGP identity.
|
||||
* @param sender OpenPGP identity of the sender.
|
||||
*
|
||||
* @return decrypted message as {@link OpenPgpMessage}.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
OpenPgpMessage decryptAndOrVerify(OpenPgpElement element, OpenPgpSelf self, OpenPgpContact sender)
|
||||
throws IOException, PGPException;
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
*
|
||||
* 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.crypto;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpMessage;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpSelf;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
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.store.definition.OpenPgpStore;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.util.io.Streams;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||
import org.pgpainless.decryption_verification.MissingPublicKeyCallback;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
|
||||
public class PainlessOpenPgpProvider implements OpenPgpProvider {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PainlessOpenPgpProvider.class.getName());
|
||||
|
||||
static {
|
||||
// Remove any BC providers and add a fresh one.
|
||||
// This is done, since older Android versions ship with a crippled BC provider.
|
||||
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
private final XMPPConnection connection;
|
||||
private final OpenPgpStore store;
|
||||
|
||||
public PainlessOpenPgpProvider(XMPPConnection connection, OpenPgpStore store) {
|
||||
this.connection = Objects.requireNonNull(connection);
|
||||
this.store = Objects.requireNonNull(store);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpStore getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpElementAndMetadata signAndEncrypt(SigncryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients)
|
||||
throws IOException, PGPException {
|
||||
InputStream plainText = element.toInputStream();
|
||||
ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
|
||||
|
||||
ArrayList<PGPPublicKeyRingCollection> recipientKeys = new ArrayList<>();
|
||||
for (OpenPgpContact contact : recipients) {
|
||||
PGPPublicKeyRingCollection keys = contact.getTrustedAnnouncedKeys();
|
||||
if (keys != null) {
|
||||
recipientKeys.add(keys);
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "There are no suitable keys for contact " + contact.getJid().toString());
|
||||
}
|
||||
}
|
||||
|
||||
EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText)
|
||||
.toRecipients(recipientKeys.toArray(new PGPPublicKeyRingCollection[] {}))
|
||||
.andToSelf(self.getTrustedAnnouncedKeys())
|
||||
.usingSecureAlgorithms()
|
||||
.signWith(getStore().getKeyRingProtector(), self.getSigningKeyRing())
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(plainText, cipherStream);
|
||||
plainText.close();
|
||||
cipherStream.flush();
|
||||
cipherStream.close();
|
||||
cipherText.close();
|
||||
|
||||
String base64 = Base64.encodeToString(cipherText.toByteArray());
|
||||
OpenPgpElement openPgpElement = new OpenPgpElement(base64);
|
||||
|
||||
return new OpenPgpElementAndMetadata(openPgpElement, cipherStream.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpElementAndMetadata sign(SignElement element, OpenPgpSelf self)
|
||||
throws IOException, PGPException {
|
||||
InputStream plainText = element.toInputStream();
|
||||
ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
|
||||
|
||||
EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText)
|
||||
.doNotEncrypt()
|
||||
.signWith(getStore().getKeyRingProtector(), self.getSigningKeyRing())
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(plainText, cipherStream);
|
||||
plainText.close();
|
||||
cipherStream.flush();
|
||||
cipherStream.close();
|
||||
cipherText.close();
|
||||
|
||||
String base64 = Base64.encodeToString(cipherText.toByteArray());
|
||||
OpenPgpElement openPgpElement = new OpenPgpElement(base64);
|
||||
|
||||
return new OpenPgpElementAndMetadata(openPgpElement, cipherStream.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpElementAndMetadata encrypt(CryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients)
|
||||
throws IOException, PGPException {
|
||||
InputStream plainText = element.toInputStream();
|
||||
ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
|
||||
|
||||
ArrayList<PGPPublicKeyRingCollection> recipientKeys = new ArrayList<>();
|
||||
for (OpenPgpContact contact : recipients) {
|
||||
PGPPublicKeyRingCollection keys = contact.getTrustedAnnouncedKeys();
|
||||
if (keys != null) {
|
||||
recipientKeys.add(keys);
|
||||
} else {
|
||||
LOGGER.log(Level.WARNING, "There are no suitable keys for contact " + contact.getJid().toString());
|
||||
}
|
||||
}
|
||||
|
||||
EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText)
|
||||
.toRecipients(recipientKeys.toArray(new PGPPublicKeyRingCollection[] {}))
|
||||
.andToSelf(self.getTrustedAnnouncedKeys())
|
||||
.usingSecureAlgorithms()
|
||||
.doNotSign()
|
||||
.noArmor();
|
||||
|
||||
Streams.pipeAll(plainText, cipherStream);
|
||||
plainText.close();
|
||||
cipherStream.flush();
|
||||
cipherStream.close();
|
||||
cipherText.close();
|
||||
|
||||
String base64 = Base64.encodeToString(cipherText.toByteArray());
|
||||
OpenPgpElement openPgpElement = new OpenPgpElement(base64);
|
||||
|
||||
return new OpenPgpElementAndMetadata(openPgpElement, cipherStream.getResult());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpMessage decryptAndOrVerify(OpenPgpElement element, final OpenPgpSelf self, final OpenPgpContact sender) throws IOException, PGPException {
|
||||
ByteArrayOutputStream plainText = new ByteArrayOutputStream();
|
||||
InputStream cipherText = element.toInputStream();
|
||||
|
||||
PGPPublicKeyRingCollection announcedPublicKeys = sender.getAnnouncedPublicKeys();
|
||||
if (announcedPublicKeys == null) {
|
||||
LOGGER.log(Level.INFO, "Received a message from " + sender.getJid() + " but we have no keys yet. Try fetching them.");
|
||||
try {
|
||||
sender.updateKeys(connection);
|
||||
announcedPublicKeys = sender.getAnnouncedPublicKeys();
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Fetching keys of " + sender.getJid() + " failed. Abort decryption and discard message.", e);
|
||||
throw new PGPException("Abort decryption due to lack of keys.", e);
|
||||
}
|
||||
}
|
||||
|
||||
MissingPublicKeyCallback missingPublicKeyCallback = new MissingPublicKeyCallback() {
|
||||
@Override
|
||||
public PGPPublicKey onMissingPublicKeyEncountered(Long keyId) {
|
||||
try {
|
||||
sender.updateKeys(connection);
|
||||
return sender.getAnyPublicKeys().getPublicKey(keyId);
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "Cannot fetch missing key " + keyId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DecryptionStream cipherStream = PGPainless.createDecryptor().onInputStream(cipherText)
|
||||
.decryptWith(getStore().getKeyRingProtector(), self.getSecretKeys())
|
||||
.verifyWith(announcedPublicKeys)
|
||||
.handleMissingPublicKeysWith(missingPublicKeyCallback)
|
||||
.build();
|
||||
|
||||
Streams.pipeAll(cipherStream, plainText);
|
||||
|
||||
cipherText.close();
|
||||
cipherStream.close();
|
||||
plainText.close();
|
||||
|
||||
OpenPgpMetadata info = cipherStream.getResult();
|
||||
|
||||
OpenPgpMessage.State state;
|
||||
if (info.isSigned()) {
|
||||
if (info.isEncrypted()) {
|
||||
state = OpenPgpMessage.State.signcrypt;
|
||||
} else {
|
||||
state = OpenPgpMessage.State.sign;
|
||||
}
|
||||
} else if (info.isEncrypted()) {
|
||||
state = OpenPgpMessage.State.crypt;
|
||||
} else {
|
||||
throw new PGPException("Received message appears to be neither encrypted, nor signed.");
|
||||
}
|
||||
|
||||
return new OpenPgpMessage(plainText.toByteArray(), state, info);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Crypto Providers for XEP-0373: OpenPGP for XMPP using Bouncycastle.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.crypto;
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.element;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* This class describes an OpenPGP content element which is encrypted, but not signed.
|
||||
*/
|
||||
public class CryptElement extends EncryptedOpenPgpContentElement {
|
||||
|
||||
public static final String ELEMENT = "crypt";
|
||||
|
||||
public CryptElement(Set<Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
|
||||
super(to, rpad, timestamp, payload);
|
||||
}
|
||||
|
||||
public CryptElement(Set<Jid> to, List<ExtensionElement> payload) {
|
||||
super(to, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
|
||||
addCommonXml(xml);
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.element;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* Abstract class that bundles functionality of encrypted OpenPGP content elements ({@link CryptElement},
|
||||
* {@link SigncryptElement}) together.
|
||||
*/
|
||||
public abstract class EncryptedOpenPgpContentElement extends OpenPgpContentElement {
|
||||
|
||||
public static final String ELEM_RPAD = "rpad";
|
||||
|
||||
private final String rpad;
|
||||
|
||||
protected EncryptedOpenPgpContentElement(Set<Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
|
||||
super(Objects.requireNonNullNorEmpty(
|
||||
to, "Encrypted OpenPGP content elements must have at least one 'to' attribute."),
|
||||
timestamp, payload);
|
||||
this.rpad = Objects.requireNonNull(rpad);
|
||||
}
|
||||
|
||||
protected EncryptedOpenPgpContentElement(Set<Jid> to, List<ExtensionElement> payload) {
|
||||
super(Objects.requireNonNullNorEmpty(
|
||||
to, "Encrypted OpenPGP content elements must have at least one 'to' attribute."),
|
||||
new Date(), payload);
|
||||
this.rpad = createRandomPadding();
|
||||
}
|
||||
|
||||
private static String createRandomPadding() {
|
||||
SecureRandom secRan = new SecureRandom();
|
||||
int len = secRan.nextInt(256);
|
||||
return StringUtils.randomString(len);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCommonXml(XmlStringBuilder xml) {
|
||||
super.addCommonXml(xml);
|
||||
|
||||
xml.openElement("rpad").escape(rpad).closeElement("rpad");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.element;
|
||||
|
||||
import static org.jivesoftware.smack.util.StringUtils.requireNotNullNorEmpty;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.util.MultiMap;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.PacketUtil;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
import org.jxmpp.util.XmppStringUtils;
|
||||
|
||||
/**
|
||||
* This class describes an OpenPGP content element. It defines the elements and fields that OpenPGP content elements
|
||||
* do have in common.
|
||||
*/
|
||||
public abstract class OpenPgpContentElement implements ExtensionElement {
|
||||
|
||||
public static final String ELEM_TO = "to";
|
||||
public static final String ATTR_JID = "jid";
|
||||
public static final String ELEM_TIME = "time";
|
||||
public static final String ATTR_STAMP = "stamp";
|
||||
public static final String ELEM_PAYLOAD = "payload";
|
||||
|
||||
private final Set<Jid> to;
|
||||
private final Date timestamp;
|
||||
private final MultiMap<String, ExtensionElement> payload;
|
||||
|
||||
private String timestampString;
|
||||
|
||||
protected OpenPgpContentElement(Set<Jid> to, Date timestamp, List<ExtensionElement> payload) {
|
||||
this.to = to;
|
||||
this.timestamp = Objects.requireNonNull(timestamp);
|
||||
this.payload = new MultiMap<>();
|
||||
for (ExtensionElement e : payload) {
|
||||
this.payload.put(XmppStringUtils.generateKey(e.getElementName(), e.getNamespace()), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of recipients.
|
||||
*
|
||||
* @return recipients.
|
||||
*/
|
||||
public final Set<Jid> getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp on which the encrypted element has been created.
|
||||
* This should be checked for sanity by the client.
|
||||
*
|
||||
* @return timestamp.
|
||||
*/
|
||||
public final Date getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the payload of the message.
|
||||
*
|
||||
* @return payload.
|
||||
*/
|
||||
public final List<ExtensionElement> getExtensions() {
|
||||
synchronized (payload) {
|
||||
return payload.values();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all extensions with the given element name <em>and</em> namespace.
|
||||
* <p>
|
||||
* Changes to the returned set will update the stanza extensions, if the returned set is not the empty set.
|
||||
* </p>
|
||||
*
|
||||
* @param elementName the element name, must not be null.
|
||||
* @param namespace the namespace of the element(s), must not be null.
|
||||
* @return a set of all matching extensions.
|
||||
*/
|
||||
public List<ExtensionElement> getExtensions(String elementName, String namespace) {
|
||||
requireNotNullNorEmpty(elementName, "elementName must not be null or empty");
|
||||
requireNotNullNorEmpty(namespace, "namespace must not be null or empty");
|
||||
String key = XmppStringUtils.generateKey(elementName, namespace);
|
||||
return payload.getAll(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first extension of this stanza that has the given namespace.
|
||||
* <p>
|
||||
* When possible, use {@link #getExtension(String,String)} instead.
|
||||
* </p>
|
||||
*
|
||||
* @param namespace the namespace of the extension that is desired.
|
||||
* @return the stanza extension with the given namespace.
|
||||
*/
|
||||
public ExtensionElement getExtension(String namespace) {
|
||||
return PacketUtil.extensionElementFrom(getExtensions(), null, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first extension that matches the specified element name and
|
||||
* namespace, or <tt>null</tt> if it doesn't exist. If the provided elementName is null,
|
||||
* only the namespace is matched. Extensions are
|
||||
* are arbitrary XML elements in standard XMPP stanzas.
|
||||
*
|
||||
* @param elementName the XML element name of the extension. (May be null)
|
||||
* @param namespace the XML element namespace of the extension.
|
||||
* @param <PE> type of the ExtensionElement.
|
||||
* @return the extension, or <tt>null</tt> if it doesn't exist.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <PE extends ExtensionElement> PE getExtension(String elementName, String namespace) {
|
||||
if (namespace == null) {
|
||||
return null;
|
||||
}
|
||||
String key = XmppStringUtils.generateKey(elementName, namespace);
|
||||
ExtensionElement packetExtension;
|
||||
synchronized (payload) {
|
||||
packetExtension = payload.getFirst(key);
|
||||
}
|
||||
if (packetExtension == null) {
|
||||
return null;
|
||||
}
|
||||
return (PE) packetExtension;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return OpenPgpElement.NAMESPACE;
|
||||
}
|
||||
|
||||
protected void ensureTimestampStringSet() {
|
||||
if (timestampString != null) return;
|
||||
|
||||
timestampString = XmppDateTime.formatXEP0082Date(timestamp);
|
||||
}
|
||||
|
||||
protected void addCommonXml(XmlStringBuilder xml) {
|
||||
for (Jid toJid : (to != null ? to : Collections.<Jid>emptySet())) {
|
||||
xml.halfOpenElement(ELEM_TO).attribute(ATTR_JID, toJid).closeEmptyElement();
|
||||
}
|
||||
|
||||
ensureTimestampStringSet();
|
||||
xml.halfOpenElement(ELEM_TIME).attribute(ATTR_STAMP, timestampString).closeEmptyElement();
|
||||
|
||||
xml.openElement(ELEM_PAYLOAD);
|
||||
for (ExtensionElement element : payload.values()) {
|
||||
xml.append(element.toXML(getNamespace()));
|
||||
}
|
||||
xml.closeElement(ELEM_PAYLOAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link ByteArrayInputStream} that reads the bytes of the XML representation of this element.
|
||||
*
|
||||
* @return InputStream over xml.
|
||||
*/
|
||||
public InputStream toInputStream() {
|
||||
byte[] encoded = toXML(null).toString().getBytes(Charset.forName("UTF-8"));
|
||||
return new ByteArrayInputStream(encoded);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.element;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
import org.jivesoftware.smackx.ox.util.Util;
|
||||
|
||||
/**
|
||||
* Class that represents an OpenPGP message.
|
||||
* The content of this elements text is an base64 encoded , OpenPGP encrypted/signed content element ({@link SignElement},
|
||||
* {@link SigncryptElement}, {@link CryptElement}).
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">
|
||||
* XEP-0373: §3.1 Exchanging OpenPGP Encrypted and Signed Data</a>
|
||||
*/
|
||||
public class OpenPgpElement implements ExtensionElement {
|
||||
|
||||
public static final String ELEMENT = "openpgp";
|
||||
public static final String NAMESPACE = "urn:xmpp:openpgp:0";
|
||||
|
||||
// Represents the OpenPGP message, but encoded using base64.
|
||||
private final String base64EncodedOpenPgpMessage;
|
||||
|
||||
public OpenPgpElement(String base64EncodedOpenPgpMessage) {
|
||||
this.base64EncodedOpenPgpMessage = StringUtils.requireNotNullNorEmpty(base64EncodedOpenPgpMessage,
|
||||
"base64 encoded message MUST NOT be null nor empty.");
|
||||
}
|
||||
|
||||
public InputStream toInputStream() {
|
||||
return new ByteArrayInputStream(base64EncodedOpenPgpMessage.getBytes(Util.UTF8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OpenPGP encrypted payload.
|
||||
*
|
||||
* @return OpenPGP encrypted payload.
|
||||
*/
|
||||
public String getEncryptedBase64MessageContent() {
|
||||
return base64EncodedOpenPgpMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngleBracket().append(base64EncodedOpenPgpMessage).closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static OpenPgpElement fromStanza(Stanza stanza) {
|
||||
return stanza.getExtension(ELEMENT, NAMESPACE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
*
|
||||
* 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.element;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
* Class representing a pubkey element which is used to transport OpenPGP public keys.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey">
|
||||
* XEP-0373: §4.1 The OpenPGP Public-Key Data Node</a>
|
||||
*/
|
||||
public class PubkeyElement implements ExtensionElement {
|
||||
|
||||
public static final String NAMESPACE = OpenPgpElement.NAMESPACE;
|
||||
public static final String ELEMENT = "pubkey";
|
||||
public static final String ATTR_DATE = "date";
|
||||
|
||||
private final PubkeyDataElement dataElement;
|
||||
private final Date date;
|
||||
|
||||
public PubkeyElement(PubkeyDataElement dataElement, Date date) {
|
||||
this.dataElement = Objects.requireNonNull(dataElement);
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the <data> element containing the base64 encoded public key.
|
||||
*
|
||||
* @return data element
|
||||
*/
|
||||
public PubkeyDataElement getDataElement() {
|
||||
return dataElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Date on which the key was last modified.
|
||||
*
|
||||
* @return last modification date
|
||||
*/
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this)
|
||||
.optAttribute(ATTR_DATE, date)
|
||||
.rightAngleBracket()
|
||||
.element(getDataElement())
|
||||
.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element that contains the base64 encoded public key.
|
||||
*/
|
||||
public static class PubkeyDataElement implements NamedElement {
|
||||
|
||||
public static final String ELEMENT = "data";
|
||||
|
||||
private final byte[] b64Data;
|
||||
|
||||
public PubkeyDataElement(byte[] b64Data) {
|
||||
this.b64Data = Objects.requireNonNull(b64Data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encoded public key.
|
||||
*
|
||||
* @return public key bytes.
|
||||
*/
|
||||
public byte[] getB64Data() {
|
||||
return b64Data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this)
|
||||
.rightAngleBracket()
|
||||
.append(new String(b64Data, Charset.forName("UTF-8")))
|
||||
.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
*
|
||||
* 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.element;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.packet.NamedElement;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
/**
|
||||
* Class that represents a public key list which was announced to a users metadata node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey-list">
|
||||
* XEP-0373: §4.2 The OpenPGP Public Key Metadata Node</a>
|
||||
*/
|
||||
public final class PublicKeysListElement implements ExtensionElement {
|
||||
|
||||
public static final String NAMESPACE = OpenPgpElement.NAMESPACE;
|
||||
public static final String ELEMENT = "public-keys-list";
|
||||
|
||||
private final Map<OpenPgpV4Fingerprint, PubkeyMetadataElement> metadata;
|
||||
|
||||
private PublicKeysListElement(TreeMap<OpenPgpV4Fingerprint, PubkeyMetadataElement> metadata) {
|
||||
this.metadata = Collections.unmodifiableMap(Objects.requireNonNull(metadata));
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public TreeMap<OpenPgpV4Fingerprint, PubkeyMetadataElement> getMetadata() {
|
||||
return new TreeMap<>(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
|
||||
for (PubkeyMetadataElement metadataElement : metadata.values()) {
|
||||
xml.element(metadataElement);
|
||||
}
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private final TreeMap<OpenPgpV4Fingerprint, PubkeyMetadataElement> metadata = new TreeMap<>();
|
||||
|
||||
private Builder() {
|
||||
// Empty
|
||||
}
|
||||
|
||||
public Builder addMetadata(PubkeyMetadataElement key) {
|
||||
Objects.requireNonNull(key);
|
||||
metadata.put(key.getV4Fingerprint(), key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PublicKeysListElement build() {
|
||||
return new PublicKeysListElement(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PubkeyMetadataElement implements NamedElement {
|
||||
|
||||
public static final String ELEMENT = "pubkey-metadata";
|
||||
public static final String ATTR_V4_FINGERPRINT = "v4-fingerprint";
|
||||
public static final String ATTR_DATE = "date";
|
||||
|
||||
private final OpenPgpV4Fingerprint v4_fingerprint;
|
||||
private final Date date;
|
||||
|
||||
public PubkeyMetadataElement(OpenPgpV4Fingerprint v4_fingerprint, Date date) {
|
||||
this.v4_fingerprint = Objects.requireNonNull(v4_fingerprint);
|
||||
this.date = Objects.requireNonNull(date);
|
||||
|
||||
if (v4_fingerprint.length() != 40) {
|
||||
throw new IllegalArgumentException("OpenPGP v4 fingerprint must be 40 characters long.");
|
||||
}
|
||||
}
|
||||
|
||||
public OpenPgpV4Fingerprint getV4Fingerprint() {
|
||||
return v4_fingerprint;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this)
|
||||
.attribute(ATTR_V4_FINGERPRINT, getV4Fingerprint())
|
||||
.attribute(ATTR_DATE, date).closeEmptyElement();
|
||||
return xml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getV4Fingerprint().hashCode() + 3 * getDate().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(o instanceof PubkeyMetadataElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (o == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return hashCode() == o.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
*
|
||||
* 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.element;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
* This class represents a secretkey element which contains a users OpenPGP secret key.
|
||||
*
|
||||
* TODO: Update reflink
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#sect-idm46443026813600">
|
||||
* XEP-0373: §5.2.2 PEP Service Success Response</a>
|
||||
*/
|
||||
public class SecretkeyElement implements ExtensionElement {
|
||||
|
||||
public static final String NAMESPACE = OpenPgpElement.NAMESPACE;
|
||||
public static final String ELEMENT = "secretkey";
|
||||
|
||||
private final byte[] b64Data;
|
||||
|
||||
public SecretkeyElement(byte[] b64Data) {
|
||||
this.b64Data = Objects.requireNonNull(b64Data);
|
||||
}
|
||||
|
||||
public byte[] getB64Data() {
|
||||
return b64Data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this)
|
||||
.rightAngleBracket()
|
||||
.append(new String(b64Data, Charset.forName("UTF-8")))
|
||||
.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.element;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* This class represents an OpenPGP content element which is not encrypted but signed.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">
|
||||
* XEP-0373: §3.1 Exchanging OpenPGP Encrypted and Signed Data</a>
|
||||
*/
|
||||
public class SignElement extends OpenPgpContentElement {
|
||||
|
||||
public SignElement(Set<Jid> to, Date timestamp, List<ExtensionElement> payload) {
|
||||
super(to, timestamp, payload);
|
||||
}
|
||||
|
||||
public static final String ELEMENT = "sign";
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
|
||||
addCommonXml(xml);
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.element;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
|
||||
/**
|
||||
* This class represents an OpenPGP content element which is encrypted and signed.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#exchange">
|
||||
* XEP-0373: §3.1 Exchanging OpenPGP Encrypted and Signed Data</a>
|
||||
*/
|
||||
public class SigncryptElement extends EncryptedOpenPgpContentElement {
|
||||
|
||||
public static final String ELEMENT = "signcrypt";
|
||||
|
||||
public SigncryptElement(Set<Jid> to, String rpad, Date timestamp, List<ExtensionElement> payload) {
|
||||
super(to, rpad, timestamp, payload);
|
||||
}
|
||||
|
||||
public SigncryptElement(Set<Jid> to, List<ExtensionElement> payload) {
|
||||
super(to, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlStringBuilder toXML(String enclosingNamespace) {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
|
||||
addCommonXml(xml);
|
||||
xml.closeElement(this);
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* XML elements for XEP-0373: OpenPGP for XMPP.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.element;
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* Exception that gets thrown if the backup code entered by the user is invalid.
|
||||
*/
|
||||
public class InvalidBackupCodeException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public InvalidBackupCodeException(String message, Throwable e) {
|
||||
super(message, e);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
*
|
||||
* 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.exception;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
/**
|
||||
* Exception that gets thrown when an operation is missing an OpenPGP key.
|
||||
*/
|
||||
public class MissingOpenPgpKeyException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final BareJid owner;
|
||||
private final OpenPgpV4Fingerprint fingerprint;
|
||||
|
||||
/**
|
||||
* Create a new {@link MissingOpenPgpKeyException}.
|
||||
*
|
||||
* @param owner {@link BareJid} of the keys owner.
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of the missing key.
|
||||
*/
|
||||
public MissingOpenPgpKeyException(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
|
||||
super("Missing key " + fingerprint.toString() + " for owner " + owner + ".");
|
||||
this.owner = owner;
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
public MissingOpenPgpKeyException(BareJid owner, OpenPgpV4Fingerprint fingerprint, Throwable e) {
|
||||
super("Missing key " + fingerprint.toString() + " for owner " + owner + ".", e);
|
||||
this.owner = owner;
|
||||
this.fingerprint = fingerprint;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@link BareJid} of the owner of the missing key.
|
||||
*
|
||||
* @return owner of missing key.
|
||||
*/
|
||||
public BareJid getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link OpenPgpV4Fingerprint} of the missing key.
|
||||
*
|
||||
* @return {@link OpenPgpV4Fingerprint} of the missing key.
|
||||
*/
|
||||
public OpenPgpV4Fingerprint getFingerprint() {
|
||||
return fingerprint;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
*
|
||||
* 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.exception;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
/**
|
||||
* This exception gets thrown, if the user tries to import a key of a user which is lacking a user-id with the users
|
||||
* jid.
|
||||
*/
|
||||
public class MissingUserIdOnKeyException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MissingUserIdOnKeyException(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
|
||||
super("Key " + fingerprint.toString() + " does not have a user-id of \"xmpp:" + owner.toString() + "\".");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
*
|
||||
* 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.exception;
|
||||
|
||||
/**
|
||||
* This exception gets thrown in case the user tries to restore a secret key backup from PubSub, but no backup has been
|
||||
* found.
|
||||
*/
|
||||
public class NoBackupFoundException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Exceptions for XEP-0373: OpenPGP for XMPP.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.exception;
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
*
|
||||
* 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.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
|
||||
public interface CryptElementReceivedListener {
|
||||
|
||||
/**
|
||||
* A {@link CryptElement} has been received and successfully decrypted.
|
||||
* This listener is intended to be used by implementors of different OX usage profiles.
|
||||
* @param contact sender of the message
|
||||
* @param originalMessage original message which contains the {@link CryptElement}.
|
||||
* @param cryptElement the {@link CryptElement} itself.
|
||||
* @param metadata metadata about the encryption
|
||||
*/
|
||||
void cryptElementReceived(OpenPgpContact contact, Message originalMessage, CryptElement cryptElement, OpenPgpMetadata metadata);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
*
|
||||
* 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.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.element.SignElement;
|
||||
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
|
||||
public interface SignElementReceivedListener {
|
||||
|
||||
/**
|
||||
* A {@link SignElement} has been received and successfully been verified.
|
||||
* This listener is intended to be used by implementors of different OX usage profiles.
|
||||
* @param contact sender of the message
|
||||
* @param originalMessage original message containing the {@link SignElement}
|
||||
* @param signElement the {@link SignElement} itself
|
||||
* @param metadata metadata about the signing
|
||||
*/
|
||||
void signElementReceived(OpenPgpContact contact, Message originalMessage, SignElement signElement, OpenPgpMetadata metadata);
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
*
|
||||
* 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.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
import org.jivesoftware.smackx.ox_im.OxMessageListener;
|
||||
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
|
||||
public interface SigncryptElementReceivedListener {
|
||||
|
||||
/**
|
||||
* A {@link SigncryptElement} has been received and successfully decrypted and verified.
|
||||
* This listener is intended to be used by implementors of different OX usage profiles. In order to listen for
|
||||
* OX-IM messages, please refer to the {@link OxMessageListener} instead.
|
||||
* @param contact sender of the message
|
||||
* @param originalMessage original message containing the the {@link SigncryptElement}
|
||||
* @param signcryptElement the {@link SigncryptElement} itself
|
||||
* @param metadata metadata about the encryption and signing
|
||||
*/
|
||||
void signcryptElementReceived(OpenPgpContact contact, Message originalMessage, SigncryptElement signcryptElement, OpenPgpMetadata metadata);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Internal OpenPgpContentElement listeners for XEP-0373: OpenPGP for XMPP.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.listener;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.
|
||||
*/
|
||||
/**
|
||||
* Smack API for XEP-0373: OpenPGP for XMPP.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox;
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.provider;
|
||||
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link CryptElement}.
|
||||
*/
|
||||
public class CryptElementProvider extends OpenPgpContentElementProvider<CryptElement> {
|
||||
|
||||
public static final CryptElementProvider INSTANCE = new CryptElementProvider();
|
||||
|
||||
@Override
|
||||
public CryptElement parse(XmlPullParser parser, int initialDepth)
|
||||
throws Exception {
|
||||
OpenPgpContentElementData data = parseOpenPgpContentElementData(parser, initialDepth);
|
||||
|
||||
return new CryptElement(data.to, data.rpad, data.timestamp, data.payload);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.provider;
|
||||
|
||||
import static org.xmlpull.v1.XmlPullParser.END_TAG;
|
||||
import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smack.provider.ProviderManager;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smackx.ox.element.CryptElement;
|
||||
import org.jivesoftware.smackx.ox.element.EncryptedOpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpContentElement;
|
||||
import org.jivesoftware.smackx.ox.element.SignElement;
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
|
||||
import org.jxmpp.jid.Jid;
|
||||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Abstract {@link ExtensionElementProvider} implementation for the also abstract {@link OpenPgpContentElement}.
|
||||
*
|
||||
* @param <O> Specialized subclass of {@link OpenPgpContentElement}.
|
||||
*/
|
||||
public abstract class OpenPgpContentElementProvider<O extends OpenPgpContentElement> extends ExtensionElementProvider<O> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OpenPgpContentElementProvider.class.getName());
|
||||
|
||||
public static OpenPgpContentElement parseOpenPgpContentElement(String element)
|
||||
throws XmlPullParserException, IOException {
|
||||
XmlPullParser parser = PacketParserUtils.getParserFor(element);
|
||||
return parseOpenPgpContentElement(parser);
|
||||
}
|
||||
|
||||
public static OpenPgpContentElement parseOpenPgpContentElement(XmlPullParser parser)
|
||||
throws XmlPullParserException {
|
||||
try {
|
||||
switch (parser.getName()) {
|
||||
case SigncryptElement.ELEMENT:
|
||||
return SigncryptElementProvider.INSTANCE.parse(parser);
|
||||
case SignElement.ELEMENT:
|
||||
return SignElementProvider.INSTANCE.parse(parser);
|
||||
case CryptElement.ELEMENT:
|
||||
return CryptElementProvider.INSTANCE.parse(parser);
|
||||
default: throw new XmlPullParserException("Expected <crypt/>, <sign/> or <signcrypt/> element, " +
|
||||
"but got neither of them.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract O parse(XmlPullParser parser, int initialDepth) throws Exception;
|
||||
|
||||
protected static OpenPgpContentElementData parseOpenPgpContentElementData(XmlPullParser parser, int initialDepth)
|
||||
throws Exception {
|
||||
Set<Jid> to = new HashSet<>();
|
||||
Date timestamp = null;
|
||||
String rpad = null;
|
||||
List<ExtensionElement> payload = new LinkedList<>();
|
||||
|
||||
outerloop: while (true) {
|
||||
int tag = parser.next();
|
||||
String name = parser.getName();
|
||||
switch (tag) {
|
||||
case START_TAG:
|
||||
switch (name) {
|
||||
|
||||
case OpenPgpContentElement.ELEM_TIME:
|
||||
String stamp = parser.getAttributeValue("", OpenPgpContentElement.ATTR_STAMP);
|
||||
timestamp = XmppDateTime.parseDate(stamp);
|
||||
break;
|
||||
|
||||
case OpenPgpContentElement.ELEM_TO:
|
||||
String jid = parser.getAttributeValue("", OpenPgpContentElement.ATTR_JID);
|
||||
to.add(JidCreate.bareFrom(jid));
|
||||
break;
|
||||
|
||||
case EncryptedOpenPgpContentElement.ELEM_RPAD:
|
||||
rpad = parser.nextText();
|
||||
break;
|
||||
|
||||
case OpenPgpContentElement.ELEM_PAYLOAD:
|
||||
innerloop: while (true) {
|
||||
int ptag = parser.next();
|
||||
String pname = parser.getName();
|
||||
String pns = parser.getNamespace();
|
||||
switch (ptag) {
|
||||
|
||||
case START_TAG:
|
||||
ExtensionElementProvider<ExtensionElement> provider =
|
||||
ProviderManager.getExtensionProvider(pname, pns);
|
||||
if (provider == null) {
|
||||
LOGGER.log(Level.INFO, "No provider found for " + pname + " " + pns);
|
||||
continue innerloop;
|
||||
}
|
||||
payload.add(provider.parse(parser));
|
||||
break;
|
||||
|
||||
case END_TAG:
|
||||
break innerloop;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case END_TAG:
|
||||
switch (name) {
|
||||
case CryptElement.ELEMENT:
|
||||
case SigncryptElement.ELEMENT:
|
||||
case SignElement.ELEMENT:
|
||||
break outerloop;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new OpenPgpContentElementData(to, timestamp, rpad, payload);
|
||||
}
|
||||
|
||||
protected static final class OpenPgpContentElementData {
|
||||
protected final Set<Jid> to;
|
||||
protected final Date timestamp;
|
||||
protected final String rpad;
|
||||
protected final List<ExtensionElement> payload;
|
||||
|
||||
private OpenPgpContentElementData(Set<Jid> to, Date timestamp, String rpad, List<ExtensionElement> payload) {
|
||||
this.to = to;
|
||||
this.timestamp = timestamp;
|
||||
this.rpad = rpad;
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.provider;
|
||||
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smackx.ox.element.OpenPgpElement;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* {@link ExtensionElementProvider} implementation for the {@link OpenPgpElement}.
|
||||
*/
|
||||
public class OpenPgpElementProvider extends ExtensionElementProvider<OpenPgpElement> {
|
||||
|
||||
public static final OpenPgpElementProvider TEST_INSTANCE = new OpenPgpElementProvider();
|
||||
|
||||
@Override
|
||||
public OpenPgpElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
String base64EncodedOpenPgpMessage = parser.nextText();
|
||||
return new OpenPgpElement(base64EncodedOpenPgpMessage);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
*
|
||||
* 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.provider;
|
||||
|
||||
import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smackx.ox.element.PubkeyElement;
|
||||
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* {@link ExtensionElementProvider} implementation for the {@link PubkeyElement}.
|
||||
*/
|
||||
public class PubkeyElementProvider extends ExtensionElementProvider<PubkeyElement> {
|
||||
|
||||
public static final PubkeyElementProvider TEST_INSTANCE = new PubkeyElementProvider();
|
||||
|
||||
@Override
|
||||
public PubkeyElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
String dateString = parser.getAttributeValue(null, PubkeyElement.ATTR_DATE);
|
||||
Date date = dateString != null ? XmppDateTime.parseXEP0082Date(dateString) : null;
|
||||
while (true) {
|
||||
int tag = parser.next();
|
||||
String name = parser.getName();
|
||||
if (tag == START_TAG) {
|
||||
switch (name) {
|
||||
case PubkeyElement.PubkeyDataElement.ELEMENT:
|
||||
String data = parser.nextText();
|
||||
if (data != null) {
|
||||
byte[] bytes = data.getBytes(Charset.forName("UTF-8"));
|
||||
return new PubkeyElement(new PubkeyElement.PubkeyDataElement(bytes), date);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
*
|
||||
* 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.provider;
|
||||
|
||||
import static org.xmlpull.v1.XmlPullParser.END_TAG;
|
||||
import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
||||
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
public final class PublicKeysListElementProvider extends ExtensionElementProvider<PublicKeysListElement> {
|
||||
|
||||
public static final PublicKeysListElementProvider TEST_INSTANCE = new PublicKeysListElementProvider();
|
||||
|
||||
@Override
|
||||
public PublicKeysListElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
|
||||
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
|
||||
|
||||
while (true) {
|
||||
|
||||
int tag = parser.nextTag();
|
||||
String name = parser.getName();
|
||||
|
||||
switch (tag) {
|
||||
case START_TAG:
|
||||
|
||||
if (PublicKeysListElement.PubkeyMetadataElement.ELEMENT.equals(name)) {
|
||||
String finger = parser.getAttributeValue(null,
|
||||
PublicKeysListElement.PubkeyMetadataElement.ATTR_V4_FINGERPRINT);
|
||||
String dt = parser.getAttributeValue(null,
|
||||
PublicKeysListElement.PubkeyMetadataElement.ATTR_DATE);
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(finger);
|
||||
Date date = XmppDateTime.parseXEP0082Date(dt);
|
||||
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, date));
|
||||
}
|
||||
break;
|
||||
|
||||
case END_TAG:
|
||||
|
||||
if (name.equals(PublicKeysListElement.ELEMENT)) {
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
*
|
||||
* 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.provider;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* {@link ExtensionElementProvider} implementation for the {@link SecretkeyElement}.
|
||||
*/
|
||||
public class SecretkeyElementProvider extends ExtensionElementProvider<SecretkeyElement> {
|
||||
|
||||
public static final SecretkeyElementProvider TEST_INSTANCE = new SecretkeyElementProvider();
|
||||
|
||||
@Override
|
||||
public SecretkeyElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
String data = parser.nextText();
|
||||
return new SecretkeyElement(data.getBytes(Charset.forName("UTF-8")));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.provider;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smackx.ox.element.SignElement;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link SignElement}.
|
||||
*/
|
||||
public class SignElementProvider extends OpenPgpContentElementProvider<SignElement> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SigncryptElementProvider.class.getName());
|
||||
public static final SignElementProvider INSTANCE = new SignElementProvider();
|
||||
|
||||
@Override
|
||||
public SignElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
OpenPgpContentElementData data = parseOpenPgpContentElementData(parser, initialDepth);
|
||||
|
||||
if (StringUtils.isNotEmpty(data.rpad)) {
|
||||
LOGGER.warning("Ignoring rpad in XEP-0373 <sign/> element");
|
||||
}
|
||||
|
||||
return new SignElement(data.to, data.timestamp, data.payload);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.provider;
|
||||
|
||||
import org.jivesoftware.smackx.ox.element.SigncryptElement;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
/**
|
||||
* {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link SigncryptElement}.
|
||||
*/
|
||||
public class SigncryptElementProvider extends OpenPgpContentElementProvider<SigncryptElement> {
|
||||
|
||||
public static final SigncryptElementProvider INSTANCE = new SigncryptElementProvider();
|
||||
|
||||
@Override
|
||||
public SigncryptElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
OpenPgpContentElementData data = parseOpenPgpContentElementData(parser, initialDepth);
|
||||
return new SigncryptElement(data.to, data.rpad, data.timestamp, data.payload);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Providers for XEP-0373: OpenPGP for XMPP.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.provider;
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
*
|
||||
* 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.selection_strategy;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
|
||||
public class AnnouncedKeys {
|
||||
|
||||
public static class PubKeyRingSelectionStrategy extends PublicKeyRingSelectionStrategy<Map<OpenPgpV4Fingerprint, Date>> {
|
||||
|
||||
@Override
|
||||
public boolean accept(Map<OpenPgpV4Fingerprint, Date> announcedKeys, PGPPublicKeyRing publicKeys) {
|
||||
return announcedKeys.keySet().contains(new OpenPgpV4Fingerprint(publicKeys));
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecKeyRingSelectionStrategy extends SecretKeyRingSelectionStrategy<Map<OpenPgpV4Fingerprint, Date>> {
|
||||
|
||||
@Override
|
||||
public boolean accept(Map<OpenPgpV4Fingerprint, Date> announcedKeys, PGPSecretKeyRing secretKeys) {
|
||||
return announcedKeys.keySet().contains(new OpenPgpV4Fingerprint(secretKeys));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
*
|
||||
* 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.selection_strategy;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy;
|
||||
import org.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy;
|
||||
|
||||
public class BareJidUserId {
|
||||
|
||||
public static class PubRingSelectionStrategy extends PublicKeyRingSelectionStrategy<BareJid> {
|
||||
|
||||
@Override
|
||||
public boolean accept(BareJid jid, PGPPublicKeyRing ring) {
|
||||
Iterator<String> userIds = ring.getPublicKey().getUserIDs();
|
||||
while (userIds.hasNext()) {
|
||||
String userId = userIds.next();
|
||||
if (userId.equals("xmpp:" + jid.toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecRingSelectionStrategy extends SecretKeyRingSelectionStrategy<BareJid> {
|
||||
|
||||
@Override
|
||||
public boolean accept(BareJid jid, PGPSecretKeyRing ring) {
|
||||
Iterator<String> userIds = ring.getPublicKey().getUserIDs();
|
||||
while (userIds.hasNext()) {
|
||||
String userId = userIds.next();
|
||||
if (userId.equals("xmpp:" + jid.toString())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Providers for XEP-0373: OpenPGP for XMPP using Bouncycastle.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.selection_strategy;
|
|
@ -0,0 +1,258 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.abstr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.selection_strategy.BareJidUserId;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.util.BCUtil;
|
||||
|
||||
public abstract class AbstractOpenPgpKeyStore implements OpenPgpKeyStore {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractOpenPgpKeyStore.class.getName());
|
||||
|
||||
protected Map<BareJid, PGPPublicKeyRingCollection> publicKeyRingCollections = new HashMap<>();
|
||||
protected Map<BareJid, PGPSecretKeyRingCollection> secretKeyRingCollections = new HashMap<>();
|
||||
protected Map<BareJid, Map<OpenPgpV4Fingerprint, Date>> keyFetchDates = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Read a {@link PGPPublicKeyRingCollection} from local storage.
|
||||
* This method returns null, if no keys were found.
|
||||
*
|
||||
* @param owner owner of the keys
|
||||
* @return public keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
protected abstract PGPPublicKeyRingCollection readPublicKeysOf(BareJid owner) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Write the {@link PGPPublicKeyRingCollection} of a user to local storage.
|
||||
*
|
||||
* @param owner owner of the keys
|
||||
* @param publicKeys keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract void writePublicKeysOf(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException;
|
||||
|
||||
/**
|
||||
* Read a {@link PGPSecretKeyRingCollection} from local storage.
|
||||
* This method returns null, if no keys were found.
|
||||
*
|
||||
* @param owner owner of the keys
|
||||
* @return secret keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
protected abstract PGPSecretKeyRingCollection readSecretKeysOf(BareJid owner) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Write the {@link PGPSecretKeyRingCollection} of a user to local storage.
|
||||
*
|
||||
* @param owner owner of the keys
|
||||
* @param secretKeys secret keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract void writeSecretKeysOf(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException;
|
||||
|
||||
/**
|
||||
* Read the key fetch dates for a users keys from local storage.
|
||||
*
|
||||
* @param owner owner
|
||||
* @return fetch dates for the owners keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract Map<OpenPgpV4Fingerprint, Date> readKeyFetchDates(BareJid owner) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the key fetch dates for a users keys to local storage.
|
||||
*
|
||||
* @param owner owner
|
||||
* @param dates fetch dates for the owners keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract void writeKeyFetchDates(BareJid owner, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException;
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> getPublicKeyFetchDates(BareJid contact) throws IOException {
|
||||
Map<OpenPgpV4Fingerprint, Date> dates = keyFetchDates.get(contact);
|
||||
if (dates == null) {
|
||||
dates = readKeyFetchDates(contact);
|
||||
keyFetchDates.put(contact, dates);
|
||||
}
|
||||
return dates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKeyFetchDates(BareJid contact, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException {
|
||||
keyFetchDates.put(contact, dates);
|
||||
writeKeyFetchDates(contact, dates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKeyRingCollection getPublicKeysOf(BareJid owner) throws IOException, PGPException {
|
||||
PGPPublicKeyRingCollection keys = publicKeyRingCollections.get(owner);
|
||||
if (keys == null) {
|
||||
keys = readPublicKeysOf(owner);
|
||||
if (keys != null) {
|
||||
publicKeyRingCollections.put(owner, keys);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSecretKeyRingCollection getSecretKeysOf(BareJid owner) throws IOException, PGPException {
|
||||
PGPSecretKeyRingCollection keys = secretKeyRingCollections.get(owner);
|
||||
if (keys == null) {
|
||||
keys = readSecretKeysOf(owner);
|
||||
if (keys != null) {
|
||||
secretKeyRingCollections.put(owner, keys);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importSecretKey(BareJid owner, PGPSecretKeyRing secretKeys)
|
||||
throws IOException, PGPException, MissingUserIdOnKeyException {
|
||||
|
||||
if (!new BareJidUserId.SecRingSelectionStrategy().accept(owner, secretKeys)) {
|
||||
throw new MissingUserIdOnKeyException(owner, new OpenPgpV4Fingerprint(secretKeys));
|
||||
}
|
||||
|
||||
PGPSecretKeyRing importKeys = BCUtil.removeUnassociatedKeysFromKeyRing(secretKeys, secretKeys.getPublicKey());
|
||||
|
||||
PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner);
|
||||
try {
|
||||
if (secretKeyRings != null) {
|
||||
secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(secretKeyRings, importKeys);
|
||||
} else {
|
||||
secretKeyRings = BCUtil.keyRingsToKeyRingCollection(importKeys);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.INFO, "Skipping secret key ring " + Long.toHexString(importKeys.getPublicKey().getKeyID()) +
|
||||
" as it is already in the key ring of " + owner.toString());
|
||||
}
|
||||
this.secretKeyRingCollections.put(owner, secretKeyRings);
|
||||
writeSecretKeysOf(owner, secretKeyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importPublicKey(BareJid owner, PGPPublicKeyRing publicKeys) throws IOException, PGPException, MissingUserIdOnKeyException {
|
||||
|
||||
if (!new BareJidUserId.PubRingSelectionStrategy().accept(owner, publicKeys)) {
|
||||
throw new MissingUserIdOnKeyException(owner, new OpenPgpV4Fingerprint(publicKeys));
|
||||
}
|
||||
|
||||
PGPPublicKeyRing importKeys = BCUtil.removeUnassociatedKeysFromKeyRing(publicKeys, publicKeys.getPublicKey());
|
||||
|
||||
PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner);
|
||||
try {
|
||||
if (publicKeyRings != null) {
|
||||
publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, importKeys);
|
||||
} else {
|
||||
publicKeyRings = BCUtil.keyRingsToKeyRingCollection(importKeys);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.INFO, "Skipping public key ring " + Long.toHexString(importKeys.getPublicKey().getKeyID()) +
|
||||
" as it is already in the key ring of " + owner.toString());
|
||||
}
|
||||
this.publicKeyRingCollections.put(owner, publicKeyRings);
|
||||
writePublicKeysOf(owner, publicKeyRings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKeyRing getPublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner);
|
||||
|
||||
if (publicKeyRings != null) {
|
||||
return publicKeyRings.getPublicKeyRing(fingerprint.getKeyId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSecretKeyRing getSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner);
|
||||
|
||||
if (secretKeyRings != null) {
|
||||
return secretKeyRings.getSecretKeyRing(fingerprint.getKeyId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deletePublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner);
|
||||
if (publicKeyRings.contains(fingerprint.getKeyId())) {
|
||||
publicKeyRings = PGPPublicKeyRingCollection.removePublicKeyRing(publicKeyRings, publicKeyRings.getPublicKeyRing(fingerprint.getKeyId()));
|
||||
if (!publicKeyRings.iterator().hasNext()) {
|
||||
publicKeyRings = null;
|
||||
}
|
||||
this.publicKeyRingCollections.put(owner, publicKeyRings);
|
||||
writePublicKeysOf(owner, publicKeyRings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner);
|
||||
if (secretKeyRings.contains(fingerprint.getKeyId())) {
|
||||
secretKeyRings = PGPSecretKeyRingCollection.removeSecretKeyRing(secretKeyRings, secretKeyRings.getSecretKeyRing(fingerprint.getKeyId()));
|
||||
if (!secretKeyRings.iterator().hasNext()) {
|
||||
secretKeyRings = null;
|
||||
}
|
||||
this.secretKeyRingCollections.put(owner, secretKeyRings);
|
||||
writeSecretKeysOf(owner, secretKeyRings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPKeyRing generateKeyRing(BareJid owner)
|
||||
throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
return PGPainless.generateKeyRing().simpleEcKeyRing("xmpp:" + owner.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.abstr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public abstract class AbstractOpenPgpMetadataStore implements OpenPgpMetadataStore {
|
||||
|
||||
private final Map<BareJid, Map<OpenPgpV4Fingerprint, Date>> announcedFingerprints = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> getAnnouncedFingerprintsOf(BareJid contact) throws IOException {
|
||||
Map<OpenPgpV4Fingerprint, Date> fingerprints = announcedFingerprints.get(contact);
|
||||
if (fingerprints == null) {
|
||||
fingerprints = readAnnouncedFingerprintsOf(contact);
|
||||
announcedFingerprints.put(contact, fingerprints);
|
||||
}
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> data) throws IOException {
|
||||
announcedFingerprints.put(contact, data);
|
||||
writeAnnouncedFingerprintsOf(contact, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the fingerprints and modification dates of announced keys of a user from local storage.
|
||||
*
|
||||
* @param contact contact
|
||||
* @return contacts announced key fingerprints and latest modification dates
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract Map<OpenPgpV4Fingerprint, Date> readAnnouncedFingerprintsOf(BareJid contact) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the fingerprints and modification dates of announced keys of a user to local storage.
|
||||
*
|
||||
* @param contact contact
|
||||
* @param metadata announced key fingerprints and latest modification dates
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract void writeAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> metadata) throws IOException;
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.abstr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Observable;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyPassphraseCallback;
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
|
||||
public abstract class AbstractOpenPgpStore extends Observable implements OpenPgpStore {
|
||||
|
||||
protected final OpenPgpKeyStore keyStore;
|
||||
protected final OpenPgpMetadataStore metadataStore;
|
||||
protected final OpenPgpTrustStore trustStore;
|
||||
|
||||
protected SecretKeyPassphraseCallback secretKeyPassphraseCallback;
|
||||
protected SecretKeyRingProtector unlocker = new UnprotectedKeysProtector();
|
||||
protected final Map<BareJid, OpenPgpContact> contacts = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void deletePublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
keyStore.deletePublicKeyRing(owner, fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
keyStore.deleteSecretKeyRing(owner, fingerprint);
|
||||
}
|
||||
|
||||
protected AbstractOpenPgpStore(OpenPgpKeyStore keyStore,
|
||||
OpenPgpMetadataStore metadataStore,
|
||||
OpenPgpTrustStore trustStore) {
|
||||
this.keyStore = Objects.requireNonNull(keyStore);
|
||||
this.metadataStore = Objects.requireNonNull(metadataStore);
|
||||
this.trustStore = Objects.requireNonNull(trustStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenPgpContact getOpenPgpContact(BareJid jid) {
|
||||
OpenPgpContact contact = contacts.get(jid);
|
||||
if (contact == null) {
|
||||
contact = new OpenPgpContact(jid, this);
|
||||
contacts.put(jid, contact);
|
||||
}
|
||||
return contact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyRingProtector(SecretKeyRingProtector protector) {
|
||||
this.unlocker = protector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretKeyRingProtector getKeyRingProtector() {
|
||||
return unlocker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSecretKeyPassphraseCallback(SecretKeyPassphraseCallback callback) {
|
||||
this.secretKeyPassphraseCallback = callback;
|
||||
}
|
||||
|
||||
/*
|
||||
OpenPgpKeyStore
|
||||
*/
|
||||
|
||||
@Override
|
||||
public PGPPublicKeyRingCollection getPublicKeysOf(BareJid owner) throws IOException, PGPException {
|
||||
return keyStore.getPublicKeysOf(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSecretKeyRingCollection getSecretKeysOf(BareJid owner) throws IOException, PGPException {
|
||||
return keyStore.getSecretKeysOf(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKeyRing getPublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
return keyStore.getPublicKeyRing(owner, fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSecretKeyRing getSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
|
||||
return keyStore.getSecretKeyRing(owner, fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPKeyRing generateKeyRing(BareJid owner) throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
return keyStore.generateKeyRing(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importSecretKey(BareJid owner, PGPSecretKeyRing secretKeys) throws IOException, PGPException, MissingUserIdOnKeyException {
|
||||
keyStore.importSecretKey(owner, secretKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importPublicKey(BareJid owner, PGPPublicKeyRing publicKeys) throws IOException, PGPException, MissingUserIdOnKeyException {
|
||||
keyStore.importPublicKey(owner, publicKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> getPublicKeyFetchDates(BareJid contact) throws IOException {
|
||||
return keyStore.getPublicKeyFetchDates(contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKeyFetchDates(BareJid contact, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException {
|
||||
keyStore.setPublicKeyFetchDates(contact, dates);
|
||||
}
|
||||
|
||||
/*
|
||||
OpenPgpMetadataStore
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> getAnnouncedFingerprintsOf(BareJid contact) throws IOException {
|
||||
return metadataStore.getAnnouncedFingerprintsOf(contact);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> data) throws IOException {
|
||||
metadataStore.setAnnouncedFingerprintsOf(contact, data);
|
||||
}
|
||||
|
||||
/*
|
||||
OpenPgpTrustStore
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Trust getTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException {
|
||||
return trustStore.getTrust(owner, fingerprint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException {
|
||||
trustStore.setTrust(owner, fingerprint, trust);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.abstr;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public abstract class AbstractOpenPgpTrustStore implements OpenPgpTrustStore {
|
||||
|
||||
private final Map<BareJid, Map<OpenPgpV4Fingerprint, Trust>> trustCache = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Read the trust record for the key with fingerprint {@code fingerprint} of user {@code owner} from local storage.
|
||||
* This method returns {@link Trust#undecided} in case that no trust record has been found.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @return trust state of the key
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract Trust readTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the trust record for the key with fingerprint {@code fingerprint} of user {@code owner} to local storage.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @param trust trust state of the key
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
protected abstract void writeTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException;
|
||||
|
||||
@Override
|
||||
public Trust getTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException {
|
||||
Trust trust;
|
||||
Map<OpenPgpV4Fingerprint, Trust> trustMap = trustCache.get(owner);
|
||||
|
||||
if (trustMap != null) {
|
||||
trust = trustMap.get(fingerprint);
|
||||
if (trust != null) {
|
||||
return trust;
|
||||
}
|
||||
} else {
|
||||
trustMap = new HashMap<>();
|
||||
trustCache.put(owner, trustMap);
|
||||
}
|
||||
|
||||
trust = readTrust(owner, fingerprint);
|
||||
trustMap.put(fingerprint, trust);
|
||||
|
||||
return trust;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException {
|
||||
Map<OpenPgpV4Fingerprint, Trust> trustMap = trustCache.get(owner);
|
||||
if (trustMap == null) {
|
||||
trustMap = new HashMap<>();
|
||||
trustCache.put(owner, trustMap);
|
||||
}
|
||||
|
||||
if (trustMap.get(fingerprint) == trust) {
|
||||
return;
|
||||
}
|
||||
|
||||
trustMap.put(fingerprint, trust);
|
||||
writeTrust(owner, fingerprint, trust);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Abstract OpenPGP store implementations.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.store.abstr;
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.definition;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.collection.PGPKeyRing;
|
||||
|
||||
public interface OpenPgpKeyStore {
|
||||
|
||||
/**
|
||||
* Return the {@link PGPPublicKeyRingCollection} containing all public keys of {@code owner} that are locally
|
||||
* available.
|
||||
* This method might return null.
|
||||
*
|
||||
* @param owner {@link BareJid} of the user we want to get keys from.
|
||||
* @return {@link PGPPublicKeyRingCollection} of the user.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
PGPPublicKeyRingCollection getPublicKeysOf(BareJid owner) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Return the {@link PGPSecretKeyRingCollection} containing all secret keys of {@code owner} which are locally
|
||||
* available.
|
||||
* This method might return null.
|
||||
*
|
||||
* @param owner {@link BareJid} of the user we want to get keys from.
|
||||
* @return {@link PGPSecretKeyRingCollection} of the user.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
PGPSecretKeyRingCollection getSecretKeysOf(BareJid owner) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Return the {@link PGPPublicKeyRing} of {@code owner} which contains the key described by {@code fingerprint}.
|
||||
* This method might return null.
|
||||
*
|
||||
* @param owner {@link BareJid} of the keys owner
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of a key contained in the key ring
|
||||
* @return {@link PGPPublicKeyRing} which contains the key described by {@code fingerprint}.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
PGPPublicKeyRing getPublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Return the {@link PGPSecretKeyRing} of {@code owner} which contains the key described by {@code fingerprint}.
|
||||
* This method might return null.
|
||||
*
|
||||
* @param owner {@link BareJid} of the keys owner
|
||||
* @param fingerprint {@link OpenPgpV4Fingerprint} of a key contained in the key ring
|
||||
* @return {@link PGPSecretKeyRing} which contains the key described by {@code fingerprint}.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
PGPSecretKeyRing getSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Remove a {@link PGPPublicKeyRing} which contains the key described by {@code fingerprint} from the
|
||||
* {@link PGPPublicKeyRingCollection} of {@code owner}.
|
||||
*
|
||||
* @param owner owner of the key ring
|
||||
* @param fingerprint fingerprint of the key whose key ring will be removed.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
void deletePublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Remove a {@link PGPSecretKeyRing} which contains the key described by {@code fingerprint} from the
|
||||
* {@link PGPSecretKeyRingCollection} of {@code owner}.
|
||||
*
|
||||
* @param owner owner of the key ring
|
||||
* @param fingerprint fingerprint of the key whose key ring will be removed.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
*/
|
||||
void deleteSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException;
|
||||
|
||||
/**
|
||||
* Generate a new {@link PGPKeyRing} for {@code owner}.
|
||||
* The key will have a user-id containing the users {@link BareJid} (eg. "xmpp:juliet@capulet.lit").
|
||||
* This method MUST NOT return null.
|
||||
*
|
||||
* @param owner owner of the key ring.
|
||||
* @return key ring
|
||||
*
|
||||
* @throws PGPException PGP is brittle
|
||||
* @throws NoSuchAlgorithmException in case there is no {@link java.security.Provider} registered for the used
|
||||
* OpenPGP algorithms.
|
||||
* @throws NoSuchProviderException in case there is no suitable {@link java.security.Provider} registered.
|
||||
* @throws InvalidAlgorithmParameterException in case an invalid algorithms configuration is used.
|
||||
*/
|
||||
PGPKeyRing generateKeyRing(BareJid owner) throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException;
|
||||
|
||||
/**
|
||||
* Import a {@link PGPSecretKeyRing} of {@code owner}.
|
||||
* In case the key ring is already available locally, the keys are skipped.
|
||||
*
|
||||
* @param owner owner of the keys
|
||||
* @param secretKeys secret keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
* @throws MissingUserIdOnKeyException in case the secret keys are lacking a user-id with the owners jid.
|
||||
*/
|
||||
void importSecretKey(BareJid owner, PGPSecretKeyRing secretKeys) throws IOException, PGPException, MissingUserIdOnKeyException;
|
||||
|
||||
/**
|
||||
* Import a {@link PGPPublicKeyRing} of {@code owner}.
|
||||
* In case the key ring is already available locally, the keys are skipped.
|
||||
*
|
||||
* @param owner owner of the keys
|
||||
* @param publicKeys public keys
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
* @throws PGPException PGP is brittle
|
||||
* @throws MissingUserIdOnKeyException in case the public keys are lacking a user-id with the owners jid.
|
||||
*/
|
||||
void importPublicKey(BareJid owner, PGPPublicKeyRing publicKeys) throws IOException, PGPException, MissingUserIdOnKeyException;
|
||||
|
||||
/**
|
||||
* Return the last date on which keys of {@code contact} were fetched from PubSub.
|
||||
* This method MUST NOT return null.
|
||||
*
|
||||
* @param contact contact in which we are interested.
|
||||
* @return dates of last key fetching.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
Map<OpenPgpV4Fingerprint, Date> getPublicKeyFetchDates(BareJid contact) throws IOException;
|
||||
|
||||
/**
|
||||
* Set the last date on which keys of {@code contact} were fetched from PubSub.
|
||||
*
|
||||
* @param contact contact in which we are interested.
|
||||
* @param dates dates of last key fetching.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
void setPublicKeyFetchDates(BareJid contact, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.definition;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public interface OpenPgpMetadataStore {
|
||||
|
||||
/**
|
||||
* Return a {@link Map} containing all announced fingerprints of a contact, as well as the dates on which they were
|
||||
* last modified by {@code contact}.
|
||||
* This method MUST NOT return null.
|
||||
*
|
||||
* @param contact contact in which we are interested.
|
||||
* @return announced fingerprints
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
Map<OpenPgpV4Fingerprint, Date> getAnnouncedFingerprintsOf(BareJid contact) throws IOException;
|
||||
|
||||
/**
|
||||
* Store a contacts announced fingerprints and dates of last modification.
|
||||
*
|
||||
* @param contact contact in which we are interested.
|
||||
* @param data {@link Map} containing the contacts announced fingerprints and dates of last modification.
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
void setAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> data) throws IOException;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.definition;
|
||||
|
||||
import org.jivesoftware.smackx.ox.OpenPgpContact;
|
||||
import org.jivesoftware.smackx.ox.callback.SecretKeyPassphraseCallback;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||
|
||||
public interface OpenPgpStore extends OpenPgpKeyStore, OpenPgpMetadataStore, OpenPgpTrustStore {
|
||||
|
||||
/**
|
||||
* Return an {@link OpenPgpContact} for a contacts jid.
|
||||
*
|
||||
* @param contactsJid {@link BareJid} of the contact.
|
||||
* @return {@link OpenPgpContact} object of the contact.
|
||||
*/
|
||||
OpenPgpContact getOpenPgpContact(BareJid contactsJid);
|
||||
|
||||
/**
|
||||
* Set a {@link SecretKeyRingProtector} which is used to decrypt password protected secret keys.
|
||||
*
|
||||
* @param unlocker unlocker which unlocks encrypted secret keys.
|
||||
*/
|
||||
void setKeyRingProtector(SecretKeyRingProtector unlocker);
|
||||
|
||||
/**
|
||||
* Return the {@link SecretKeyRingProtector} which is used to decrypt password protected secret keys.
|
||||
* In case no {@link SecretKeyRingProtector} has been set, this method MUST return an {@link UnprotectedKeysProtector}.
|
||||
*
|
||||
* @return secret key unlocker.
|
||||
*/
|
||||
SecretKeyRingProtector getKeyRingProtector();
|
||||
|
||||
/**
|
||||
* Set a {@link SecretKeyPassphraseCallback} which is called in case we stumble over a secret key for which we have
|
||||
* no passphrase.
|
||||
*
|
||||
* @param callback callback. MUST NOT be null.
|
||||
*/
|
||||
void setSecretKeyPassphraseCallback(SecretKeyPassphraseCallback callback);
|
||||
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.definition;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public interface OpenPgpTrustStore {
|
||||
|
||||
/**
|
||||
* Return the {@link Trust} state of {@code owner}s key with fingerprint {@code fingerprint}.
|
||||
* The trust state describes, whether the user trusts a certain key of a contact.
|
||||
* If no {@link Trust} record has been found, this method MUST return not null, nut {@link Trust#undecided}.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @return trust state
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
Trust getTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException;
|
||||
|
||||
/**
|
||||
* Store the {@link Trust} state of {@code owner}s key with fingerprint {@code fingerprint}.
|
||||
*
|
||||
* @param owner owner of the key
|
||||
* @param fingerprint fingerprint of the key
|
||||
* @param trust trust record
|
||||
*
|
||||
* @throws IOException IO is dangerous
|
||||
*/
|
||||
void setTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException;
|
||||
|
||||
enum Trust {
|
||||
/**
|
||||
* The user explicitly trusts the key.
|
||||
*/
|
||||
trusted,
|
||||
/**
|
||||
* The user explicitly distrusts the key.
|
||||
*/
|
||||
untrusted,
|
||||
/**
|
||||
* The user didn't yet describe, whether to trust the key or not.
|
||||
*/
|
||||
undecided
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* OpenPgp store class definitions.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.store.definition;
|
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.filebased;
|
||||
|
||||
import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileInputStream;
|
||||
import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileOutputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpKeyStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
|
||||
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
/**
|
||||
* This class is an implementation of the {@link OpenPgpKeyStore}, which stores keys in a file structure.
|
||||
* The keys are stored in the following directory structure:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <basePath>/
|
||||
* <userjid@server.tld>/
|
||||
* pubring.pkr // public keys of the user/contact
|
||||
* secring.pkr // secret keys of the user
|
||||
* fetchDates.list // date of the last time we fetched the users keys
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class FileBasedOpenPgpKeyStore extends AbstractOpenPgpKeyStore {
|
||||
|
||||
private static final String PUB_RING = "pubring.pkr";
|
||||
private static final String SEC_RING = "secring.skr";
|
||||
private static final String FETCH_DATES = "fetchDates.list";
|
||||
|
||||
private final File basePath;
|
||||
|
||||
public FileBasedOpenPgpKeyStore(File basePath) {
|
||||
this.basePath = Objects.requireNonNull(basePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writePublicKeysOf(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException {
|
||||
File file = getPublicKeyRingPath(owner);
|
||||
|
||||
if (publicKeys == null) {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("Could not delete file " + file.getAbsolutePath());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = prepareFileOutputStream(file);
|
||||
publicKeys.encode(outputStream);
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSecretKeysOf(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException {
|
||||
File file = getSecretKeyRingPath(owner);
|
||||
|
||||
if (secretKeys == null) {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("Could not delete file " + file.getAbsolutePath());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = prepareFileOutputStream(file);
|
||||
secretKeys.encode(outputStream);
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException ignored) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPPublicKeyRingCollection readPublicKeysOf(BareJid owner)
|
||||
throws IOException, PGPException {
|
||||
File file = getPublicKeyRingPath(owner);
|
||||
|
||||
FileInputStream inputStream;
|
||||
try {
|
||||
inputStream = prepareFileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPPublicKeyRingCollection collection = PGPainless.readKeyRing().publicKeyRingCollection(inputStream);
|
||||
inputStream.close();
|
||||
return collection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSecretKeyRingCollection readSecretKeysOf(BareJid owner) throws IOException, PGPException {
|
||||
File file = getSecretKeyRingPath(owner);
|
||||
|
||||
FileInputStream inputStream;
|
||||
try {
|
||||
inputStream = prepareFileInputStream(file);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PGPSecretKeyRingCollection collection = PGPainless.readKeyRing().secretKeyRingCollection(inputStream);
|
||||
inputStream.close();
|
||||
return collection;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<OpenPgpV4Fingerprint, Date> readKeyFetchDates(BareJid owner) throws IOException {
|
||||
return FileBasedOpenPgpMetadataStore.readFingerprintsAndDates(getFetchDatesPath(owner));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeKeyFetchDates(BareJid owner, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException {
|
||||
FileBasedOpenPgpMetadataStore.writeFingerprintsAndDates(dates, getFetchDatesPath(owner));
|
||||
}
|
||||
|
||||
private File getPublicKeyRingPath(BareJid jid) {
|
||||
return new File(FileBasedOpenPgpStore.getContactsPath(basePath, jid), PUB_RING);
|
||||
}
|
||||
|
||||
private File getSecretKeyRingPath(BareJid jid) {
|
||||
return new File(FileBasedOpenPgpStore.getContactsPath(basePath, jid), SEC_RING);
|
||||
}
|
||||
|
||||
private File getFetchDatesPath(BareJid jid) {
|
||||
return new File(FileBasedOpenPgpStore.getContactsPath(basePath, jid), FETCH_DATES);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.filebased;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpMetadataStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpMetadataStore;
|
||||
import org.jivesoftware.smackx.ox.util.FileUtils;
|
||||
import org.jivesoftware.smackx.ox.util.Util;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.jxmpp.util.XmppDateTime;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link OpenPgpMetadataStore}, which stores metadata information in a file structure.
|
||||
* The information is stored in the following directory structure:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <basePath>/
|
||||
* <userjid@server.tld>/
|
||||
* announced.list // list of the users announced key fingerprints and modification dates
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore {
|
||||
|
||||
public static final String ANNOUNCED = "announced.list";
|
||||
public static final String RETRIEVED = "retrieved.list";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(FileBasedOpenPgpMetadataStore.class.getName());
|
||||
|
||||
private final File basePath;
|
||||
|
||||
public FileBasedOpenPgpMetadataStore(File basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<OpenPgpV4Fingerprint, Date> readAnnouncedFingerprintsOf(BareJid contact) throws IOException {
|
||||
return readFingerprintsAndDates(getAnnouncedFingerprintsPath(contact));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> metadata)
|
||||
throws IOException {
|
||||
File destination = getAnnouncedFingerprintsPath(contact);
|
||||
writeFingerprintsAndDates(metadata, destination);
|
||||
}
|
||||
|
||||
static Map<OpenPgpV4Fingerprint, Date> readFingerprintsAndDates(File source) throws IOException {
|
||||
if (!source.exists() || source.isDirectory()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
InputStream inputStream = FileUtils.prepareFileInputStream(source);
|
||||
InputStreamReader isr = new InputStreamReader(inputStream, Util.UTF8);
|
||||
reader = new BufferedReader(isr);
|
||||
Map<OpenPgpV4Fingerprint, Date> fingerprintDateMap = new HashMap<>();
|
||||
|
||||
String line; int lineNr = 0;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lineNr++;
|
||||
|
||||
line = line.trim();
|
||||
String[] split = line.split(" ");
|
||||
if (split.length != 2) {
|
||||
LOGGER.log(Level.FINE, "Skipping invalid line " + lineNr + " in file " + source.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(split[0]);
|
||||
Date date = XmppDateTime.parseXEP0082Date(split[1]);
|
||||
fingerprintDateMap.put(fingerprint, date);
|
||||
} catch (IllegalArgumentException | ParseException e) {
|
||||
LOGGER.log(Level.WARNING, "Error parsing fingerprint/date touple in line " + lineNr +
|
||||
" of file " + source.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
reader.close();
|
||||
return fingerprintDateMap;
|
||||
|
||||
} catch (IOException e) {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeFingerprintsAndDates(Map<OpenPgpV4Fingerprint, Date> data, File destination)
|
||||
throws IOException {
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
if (!destination.exists()) {
|
||||
return;
|
||||
} else {
|
||||
if (!destination.delete()) {
|
||||
throw new IOException("Cannot delete file " + destination.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!destination.exists()) {
|
||||
File parent = destination.getParentFile();
|
||||
if (!parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Cannot create directory " + parent.getAbsolutePath());
|
||||
}
|
||||
|
||||
if (!destination.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + destination.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (destination.isDirectory()) {
|
||||
throw new IOException("File " + destination.getAbsolutePath() + " is a directory.");
|
||||
}
|
||||
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
OutputStream outputStream = FileUtils.prepareFileOutputStream(destination);
|
||||
OutputStreamWriter osw = new OutputStreamWriter(outputStream, Util.UTF8);
|
||||
writer = new BufferedWriter(osw);
|
||||
for (OpenPgpV4Fingerprint fingerprint : data.keySet()) {
|
||||
Date date = data.get(fingerprint);
|
||||
String line = fingerprint.toString() + " " +
|
||||
(date != null ? XmppDateTime.formatXEP0082Date(date) : XmppDateTime.formatXEP0082Date(new Date()));
|
||||
writer.write(line);
|
||||
writer.newLine();
|
||||
}
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ignored) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private File getAnnouncedFingerprintsPath(BareJid contact) {
|
||||
return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), ANNOUNCED);
|
||||
}
|
||||
|
||||
private File getRetrievedFingerprintsPath(BareJid contact) {
|
||||
return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), RETRIEVED);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.filebased;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.jivesoftware.smack.util.Objects;
|
||||
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link OpenPgpStore} which stores all information in a directory structure.
|
||||
*/
|
||||
public class FileBasedOpenPgpStore extends AbstractOpenPgpStore {
|
||||
|
||||
public FileBasedOpenPgpStore(File basePath) {
|
||||
super(new FileBasedOpenPgpKeyStore(basePath),
|
||||
new FileBasedOpenPgpMetadataStore(basePath),
|
||||
new FileBasedOpenPgpTrustStore(basePath));
|
||||
}
|
||||
|
||||
public static File getContactsPath(File basePath, BareJid jid) {
|
||||
return new File(basePath, Objects.requireNonNull(jid.toString()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
*
|
||||
* 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.store.filebased;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpTrustStore;
|
||||
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
|
||||
import org.jivesoftware.smackx.ox.util.FileUtils;
|
||||
import org.jivesoftware.smackx.ox.util.Util;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link OpenPgpTrustStore} which stores information in a directory structure.
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* <basePath>/
|
||||
* <userjid@server.tld>/
|
||||
* <fingerprint>.trust // Trust record for a key
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class FileBasedOpenPgpTrustStore extends AbstractOpenPgpTrustStore {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(FileBasedOpenPgpTrustStore.class.getName());
|
||||
|
||||
private final File basePath;
|
||||
|
||||
public static String TRUST_RECORD(OpenPgpV4Fingerprint fingerprint) {
|
||||
return fingerprint.toString() + ".trust";
|
||||
}
|
||||
|
||||
public FileBasedOpenPgpTrustStore(File basePath) {
|
||||
this.basePath = basePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Trust readTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException {
|
||||
File file = getTrustPath(owner, fingerprint);
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
InputStream inputStream = FileUtils.prepareFileInputStream(file);
|
||||
InputStreamReader isr = new InputStreamReader(inputStream, Util.UTF8);
|
||||
reader = new BufferedReader(isr);
|
||||
|
||||
Trust trust = null;
|
||||
String line; int lineNr = 0;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
lineNr++;
|
||||
try {
|
||||
trust = Trust.valueOf(line);
|
||||
break;
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.log(Level.WARNING, "Skipping invalid trust record in line " + lineNr + " of file " +
|
||||
file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
reader.close();
|
||||
return trust != null ? trust : Trust.undecided;
|
||||
} catch (IOException e) {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException ignored) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
|
||||
if (e instanceof FileNotFoundException) {
|
||||
return Trust.undecided;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException {
|
||||
File file = getTrustPath(owner, fingerprint);
|
||||
|
||||
if (trust == null || trust == Trust.undecided) {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!file.delete()) {
|
||||
throw new IOException("Could not delete file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
File parent = file.getParentFile();
|
||||
if (!parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Cannot create directory " + parent.getAbsolutePath());
|
||||
}
|
||||
if (!file.exists()) {
|
||||
if (!file.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + file.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
if (file.isDirectory()) {
|
||||
throw new IOException("File " + file.getAbsolutePath() + " is a directory.");
|
||||
}
|
||||
}
|
||||
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
OutputStream outputStream = FileUtils.prepareFileOutputStream(file);
|
||||
OutputStreamWriter osw = new OutputStreamWriter(outputStream, Util.UTF8);
|
||||
writer = new BufferedWriter(osw);
|
||||
|
||||
writer.write(trust.toString());
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException ignored) {
|
||||
// Don't care
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private File getTrustPath(BareJid owner, OpenPgpV4Fingerprint fingerprint) {
|
||||
return new File(FileBasedOpenPgpStore.getContactsPath(basePath, owner), TRUST_RECORD(fingerprint));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* File based store implementations.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.store.filebased;
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* OpenPGP store implementations.
|
||||
*/
|
||||
package org.jivesoftware.smackx.ox.store;
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2017 Florian Schmaus, 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.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public static FileOutputStream prepareFileOutputStream(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
|
||||
// Create parent directory
|
||||
File parent = file.getParentFile();
|
||||
if (!parent.exists() && !parent.mkdirs()) {
|
||||
throw new IOException("Cannot create directory " + parent.getAbsolutePath());
|
||||
}
|
||||
|
||||
// Create file
|
||||
if (!file.createNewFile()) {
|
||||
throw new IOException("Cannot create file " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
throw new AssertionError("File " + file.getAbsolutePath() + " is not a file!");
|
||||
}
|
||||
|
||||
return new FileOutputStream(file);
|
||||
}
|
||||
|
||||
public static FileInputStream prepareFileInputStream(File file) throws IOException {
|
||||
if (file.exists()) {
|
||||
if (file.isFile()) {
|
||||
return new FileInputStream(file);
|
||||
} else {
|
||||
throw new IOException("File " + file.getAbsolutePath() + " is not a file!");
|
||||
}
|
||||
} else {
|
||||
throw new FileNotFoundException("File " + file.getAbsolutePath() + " not found.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
*
|
||||
* 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.util;
|
||||
|
||||
import org.jivesoftware.smack.initializer.UrlInitializer;
|
||||
|
||||
/**
|
||||
* Initializer class which registers ExtensionElementProviders on startup.
|
||||
*/
|
||||
public class OpenPgpInitializer extends UrlInitializer {
|
||||
|
||||
@Override
|
||||
protected String getProvidersUri() {
|
||||
return "classpath:org.jivesoftware.smackx.ox/openpgp.providers";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,484 @@
|
|||
/**
|
||||
*
|
||||
* 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.util;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
import org.jivesoftware.smack.XMPPException;
|
||||
import org.jivesoftware.smack.packet.StanzaError;
|
||||
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
|
||||
import org.jivesoftware.smackx.ox.OpenPgpManager;
|
||||
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.pubsub.AccessModel;
|
||||
import org.jivesoftware.smackx.pubsub.ConfigureForm;
|
||||
import org.jivesoftware.smackx.pubsub.Item;
|
||||
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||
import org.jivesoftware.smackx.pubsub.Node;
|
||||
import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||
|
||||
import org.jxmpp.jid.BareJid;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
|
||||
public class OpenPgpPubSubUtil {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(OpenPgpPubSubUtil.class.getName());
|
||||
|
||||
/**
|
||||
* Name of the OX metadata node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey-list">XEP-0373 §4.2</a>
|
||||
*/
|
||||
public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys";
|
||||
|
||||
/**
|
||||
* Name of the OX secret key node.
|
||||
* TODO: Update once my PR gets merged.
|
||||
* @see <a href="https://github.com/xsf/xeps/pull/669">xsf/xeps#669</a>
|
||||
*/
|
||||
public static final String PEP_NODE_SECRET_KEY = "urn:xmpp:openpgp:secret-key:0";
|
||||
|
||||
/**
|
||||
* Feature to be announced using the {@link ServiceDiscoveryManager} to subscribe to the OX metadata node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a>
|
||||
*/
|
||||
public static final String PEP_NODE_PUBLIC_KEYS_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify";
|
||||
|
||||
/**
|
||||
* Name of the OX public key node, which contains the key with id {@code id}.
|
||||
*
|
||||
* @param id upper case hex encoded OpenPGP v4 fingerprint of the key.
|
||||
* @return PEP node name.
|
||||
*/
|
||||
public static String PEP_NODE_PUBLIC_KEY(OpenPgpV4Fingerprint id) {
|
||||
return PEP_NODE_PUBLIC_KEYS + ":" + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the access model of {@code node}. If it is different from {@code accessModel}, change the access model
|
||||
* of the node to {@code accessModel}.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0060.html#accessmodels">XEP-0060 §4.5 - Node Access Models</a>
|
||||
*
|
||||
* @param node {@link LeafNode} whose PubSub access model we want to change
|
||||
* @param accessModel new access model.
|
||||
*
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws InterruptedException if the thread is interrupted.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
*/
|
||||
public static void changeAccessModelIfNecessary(LeafNode node, AccessModel accessModel)
|
||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
|
||||
SmackException.NoResponseException {
|
||||
ConfigureForm current = node.getNodeConfiguration();
|
||||
if (current.getAccessModel() != accessModel) {
|
||||
ConfigureForm updateConfig = new ConfigureForm(DataForm.Type.submit);
|
||||
updateConfig.setAccessModel(accessModel);
|
||||
node.sendConfigurationForm(updateConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish the users OpenPGP public key to the public key node if necessary.
|
||||
* Also announce the key to other users by updating the metadata node.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#annoucning-pubkey">XEP-0373 §4.1</a>
|
||||
*
|
||||
* @param connection XMPP connection
|
||||
* @param pubkeyElement {@link PubkeyElement} containing the public key
|
||||
* @param fingerprint fingerprint of the public key
|
||||
*
|
||||
* @throws InterruptedException if the thread gets interrupted.
|
||||
* @throws PubSubException.NotALeafNodeException if either the metadata node or the public key node is not a
|
||||
* {@link LeafNode}.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
|
||||
* @throws SmackException.NotConnectedException if we are not connected.
|
||||
* @throws SmackException.NoResponseException if the server doesn't respond.
|
||||
*/
|
||||
public static void publishPublicKey(XMPPConnection connection, PubkeyElement pubkeyElement, OpenPgpV4Fingerprint fingerprint)
|
||||
throws InterruptedException, PubSubException.NotALeafNodeException,
|
||||
XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
|
||||
String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint);
|
||||
PubSubManager pm = PubSubManager.getInstance(connection, connection.getUser().asBareJid());
|
||||
|
||||
// Check if key available at data node
|
||||
// If not, publish key to data node
|
||||
LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName);
|
||||
changeAccessModelIfNecessary(keyNode, AccessModel.open);
|
||||
List<Item> items = keyNode.getItems(1);
|
||||
if (items.isEmpty()) {
|
||||
LOGGER.log(Level.FINE, "Node " + keyNodeName + " is empty. Publish.");
|
||||
keyNode.publish(new PayloadItem<>(pubkeyElement));
|
||||
} else {
|
||||
LOGGER.log(Level.FINE, "Node " + keyNodeName + " already contains key. Skip.");
|
||||
}
|
||||
|
||||
// Fetch IDs from metadata node
|
||||
LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS);
|
||||
changeAccessModelIfNecessary(metadataNode, AccessModel.open);
|
||||
List<PayloadItem<PublicKeysListElement>> metadataItems = metadataNode.getItems(1);
|
||||
|
||||
PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
|
||||
if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) {
|
||||
// Add old entries back to list.
|
||||
PublicKeysListElement publishedList = metadataItems.get(0).getPayload();
|
||||
for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) {
|
||||
builder.addMetadata(meta);
|
||||
}
|
||||
}
|
||||
builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, new Date()));
|
||||
|
||||
// Publish IDs to metadata node
|
||||
metadataNode.publish(new PayloadItem<>(builder.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Consult the public key metadata node and fetch a list of all of our published OpenPGP public keys.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey-list">
|
||||
* XEP-0373 §4.3: Discovering Public Keys of a User</a>
|
||||
*
|
||||
* @param connection XMPP connection
|
||||
* @return content of our metadata node.
|
||||
*
|
||||
* @throws InterruptedException if the thread gets interrupted.
|
||||
* @throws XMPPException.XMPPErrorException in case of an XMPP protocol exception.
|
||||
* @throws PubSubException.NotAPubSubNodeException in case the queried entity is not a PubSub node
|
||||
* @throws PubSubException.NotALeafNodeException in case the queried node is not a {@link LeafNode}
|
||||
* @throws SmackException.NotConnectedException in case we are not connected
|
||||
* @throws SmackException.NoResponseException in case the server doesn't respond
|
||||
*/
|
||||
public static PublicKeysListElement fetchPubkeysList(XMPPConnection connection)
|
||||
throws InterruptedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException,
|
||||
PubSubException.NotALeafNodeException, SmackException.NotConnectedException, SmackException.NoResponseException {
|
||||
return fetchPubkeysList(connection, connection.getUser().asBareJid());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consult the public key metadata node of {@code contact} to fetch the list of their published OpenPGP public keys.
|
||||
*
|
||||
* @see <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey-list">
|
||||
* XEP-0373 §4.3: Discovering Public Keys of a User</a>
|
||||
*
|
||||