diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/BasicOpenPgpInstantMessagingIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/BasicOpenPgpInstantMessagingIntegrationTest.java index 121a1387d..84d17f0d3 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/BasicOpenPgpInstantMessagingIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/openpgp/BasicOpenPgpInstantMessagingIntegrationTest.java @@ -110,7 +110,7 @@ public class BasicOpenPgpInstantMessagingIntegrationTest extends AbstractOpenPgp OpenPgpContact bobForAlice = aliceOpenPgp.getOpenPgpContact(bob.asEntityBareJidIfPossible()); OpenPgpContact aliceForBob = bobOpenPgp.getOpenPgpContact(alice.asEntityBareJidIfPossible()); - // TODO: Update keys + bobForAlice.updateKeys(aliceConnection); aliceInstantMessaging.sendOxMessage(bobForAlice, body); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpContact.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpContact.java index d1c505496..c7cb13f4f 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpContact.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpContact.java @@ -18,17 +18,28 @@ package org.jivesoftware.smackx.ox; import java.io.IOException; 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.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.AnnouncedKeys; import org.jivesoftware.smackx.ox.selection_strategy.BareJidUserId; import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; +import org.jivesoftware.smackx.ox.util.PubSubDelegate; +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.pgpainless.key.OpenPgpV4Fingerprint; @@ -79,4 +90,42 @@ public class OpenPgpContact { } return announcedKeysCollection; } + + public void updateKeys(XMPPConnection connection) throws InterruptedException, SmackException.NotConnectedException, + SmackException.NoResponseException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, + PubSubException.NotAPubSubNodeException, IOException { + PublicKeysListElement metadata = PubSubDelegate.fetchPubkeysList(connection, getJid()); + if (metadata == null) { + return; + } + + Map fingerprintsAndDates = new HashMap<>(); + for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) { + fingerprintsAndDates.put(fingerprint, metadata.getMetadata().get(fingerprint).getDate()); + } + + store.setAnnouncedFingerprintsOf(getJid(), fingerprintsAndDates); + + for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) { + try { + PubkeyElement key = PubSubDelegate.fetchPubkey(connection, getJid(), fingerprint); + if (key == null) { + LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) + + " can not be imported: 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); + } catch (PGPException | IOException e) { + LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) + + " can not be imported.", 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); + } + } + } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java index 89e024300..3f96e9db8 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java @@ -68,7 +68,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { .toRecipients(recipientKeys.toArray(new PGPPublicKeyRingCollection[]{})) .andToSelf(self.getAnnouncedPublicKeys()) .usingSecureAlgorithms() - .signWith(null, self.getSigningKeyRing()) + .signWith(store.getKeyRingProtector(), self.getSigningKeyRing()) .noArmor(); Streams.pipeAll(plainText, cipherStream); @@ -90,7 +90,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText) .doNotEncrypt() - .signWith(null, self.getSigningKeyRing()) + .signWith(store.getKeyRingProtector(), self.getSigningKeyRing()) .noArmor(); Streams.pipeAll(plainText, cipherStream); @@ -138,7 +138,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { InputStream cipherText = element.toInputStream(); DecryptionStream cipherStream = PGPainless.createDecryptor().onInputStream(cipherText) - .decryptWith(null, self.getSecretKeys()) + .decryptWith(store.getKeyRingProtector(), self.getSecretKeys()) .verifyWith(sender.getAnnouncedPublicKeys()) .ignoreMissingPublicKeys() .build(); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpKeyStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpKeyStore.java index b871fffff..41f07a415 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpKeyStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpKeyStore.java @@ -38,6 +38,7 @@ import org.jxmpp.jid.BareJid; import org.pgpainless.pgpainless.PGPainless; import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.pgpainless.key.generation.type.length.RsaLength; +import org.pgpainless.pgpainless.util.BCUtil; public abstract class AbstractOpenPgpKeyStore implements OpenPgpKeyStore { @@ -88,12 +89,17 @@ public abstract class AbstractOpenPgpKeyStore implements OpenPgpKeyStore { PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner); try { - secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(secretKeyRings, secretKeys); + if (secretKeyRings != null) { + secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(secretKeyRings, secretKeys); + } else { + secretKeyRings = BCUtil.keyRingsToKeyRingCollection(secretKeys); + } } catch (IllegalArgumentException e) { LOGGER.log(Level.INFO, "Skipping secret key ring " + Long.toHexString(secretKeys.getPublicKey().getKeyID()) + " as it is already in the key ring of " + owner.toString()); } this.secretKeyRingCollections.put(owner, secretKeyRings); + importPublicKey(owner, BCUtil.publicKeyRingFromSecretKeyRing(secretKeys)); writeSecretKeysOf(owner, secretKeyRings); } @@ -106,7 +112,11 @@ public abstract class AbstractOpenPgpKeyStore implements OpenPgpKeyStore { PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner); try { - publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, publicKeys); + if (publicKeyRings != null) { + publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, publicKeys); + } else { + publicKeyRings = BCUtil.keyRingsToKeyRingCollection(publicKeys); + } } catch (IllegalArgumentException e) { LOGGER.log(Level.INFO, "Skipping public key ring " + Long.toHexString(publicKeys.getPublicKey().getKeyID()) + " as it is already in the key ring of " + owner.toString()); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpStore.java index f00bd4196..ed7299ef6 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/abstr/AbstractOpenPgpStore.java @@ -64,12 +64,11 @@ public abstract class AbstractOpenPgpStore extends Observable implements OpenPgp @Override public OpenPgpContact getOpenPgpContact(BareJid jid) { OpenPgpContact contact = contacts.get(jid); - if (contact != null) { - return contact; + if (contact == null) { + contact = new OpenPgpContact(jid, this); + contacts.put(jid, contact); } - - // TODO - return null; + return contact; } @Override diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/definition/OpenPgpKeyStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/definition/OpenPgpKeyStore.java index 0528e5d6d..eef7b87a9 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/definition/OpenPgpKeyStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/definition/OpenPgpKeyStore.java @@ -33,8 +33,6 @@ import org.pgpainless.pgpainless.key.OpenPgpV4Fingerprint; public interface OpenPgpKeyStore { - - PGPPublicKeyRingCollection getPublicKeysOf(BareJid owner) throws IOException, PGPException; PGPSecretKeyRingCollection getSecretKeysOf(BareJid owner) throws IOException, PGPException; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/filebased/FileBasedOpenPgpMetadataStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/filebased/FileBasedOpenPgpMetadataStore.java index bcd6ae2a0..f41cb8875 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/filebased/FileBasedOpenPgpMetadataStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/store/filebased/FileBasedOpenPgpMetadataStore.java @@ -62,6 +62,10 @@ public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore } private Map readFingerprintsAndDates(File source) throws IOException { + if (!source.exists() || source.isDirectory()) { + return new HashMap<>(); + } + BufferedReader reader = null; try { reader = Files.newBufferedReader(source.toPath(), Util.UTF8); @@ -106,6 +110,21 @@ public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore private void writeFingerprintsAndDates(Map data, File destination) throws IOException { + 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 { writer = Files.newBufferedWriter(destination.toPath(), Util.UTF8); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/FileUtils.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/FileUtils.java index 0966fd16b..89f94d3aa 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/FileUtils.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/FileUtils.java @@ -29,7 +29,7 @@ public class FileUtils { // Create parent directory File parent = file.getParentFile(); - if (!parent.mkdirs()) { + if (!parent.exists() && !parent.mkdirs()) { throw new IOException("Cannot create directory " + parent.getAbsolutePath()); }