From dab342e97e274d327bfcc07cfe22194229bfc378 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 2 Jun 2018 14:27:18 +0200 Subject: [PATCH] Add methods to store update dates --- .../bouncycastle/FileBasedBcOpenPgpStore.java | 110 ++++++++++++++++-- .../jivesoftware/smackx/ox/OpenPgpStore.java | 25 +++- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java index 262b91b12..2118182ce 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java @@ -23,13 +23,16 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.text.ParseException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -74,6 +77,7 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBu import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.jxmpp.jid.BareJid; +import org.jxmpp.util.XmppDateTime; public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { @@ -133,8 +137,8 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } @Override - public Set announcedOpenPgpKeyFingerprints(BareJid contact) { - Set announcedKeys = new HashSet<>(); + public Map announcedOpenPgpKeyFingerprints(BareJid contact) { + Map announcedKeys = new HashMap<>(); File listPath = contactsList(contact); if (listPath.exists() && listPath.isFile()) { BufferedReader reader = null; @@ -149,12 +153,25 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { continue; } + String[] split = line.split(" "); + + OpenPgpV4Fingerprint fingerprint; + Date date = null; try { - OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(line); - announcedKeys.add(fingerprint); + fingerprint = new OpenPgpV4Fingerprint(split[0]); } catch (IllegalArgumentException e) { LOGGER.log(Level.INFO, "Skip malformed fingerprint " + line + " of " + contact.toString()); + continue; } + + try { + if (split.length > 1) + date = XmppDateTime.parseXEP0082Date(split[1]); + } + catch (ParseException e) { + LOGGER.log(Level.WARNING, "Could not parse date", e); + } + announcedKeys.put(fingerprint, date); } reader.close(); } catch (IOException e) { @@ -204,8 +221,11 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { writer = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(listPath), "UTF8")); - for (OpenPgpV4Fingerprint fingerprint : listElement.getMetadata().keySet()) { - writer.write(fingerprint.toString()); + for (PublicKeysListElement.PubkeyMetadataElement entry : listElement.getMetadata().values()) { + OpenPgpV4Fingerprint fingerprint = entry.getV4Fingerprint(); + Date date = entry.getDate(); + String line = fingerprint.toString() + (date != null ? date : ""); + writer.write(line); writer.newLine(); } @@ -238,7 +258,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } @Override - public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) + public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element, Date latestMetadataDate) throws SmackOpenPgpException { byte[] base64decoded = Base64.decode(element.getDataElement().getB64Data()); try { @@ -249,6 +269,13 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } catch (IllegalArgumentException e) { LOGGER.log(Level.WARNING, "Public Key with ID " + fingerprint.toString() + " of " + owner + " is already in memory. Skip."); + return; + } + + try { + writeDateToFile(publicKeyUpdateDatePath(owner, fingerprint), latestMetadataDate); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Could not store update date for " + fingerprint.toString(), e); } } @@ -280,7 +307,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { .getSecretKey(Util.keyIdFromFingerprint(fingerprint)); if (secretKey == null) { - // TODO: Close streams + buffer.close(); throw new MissingOpenPgpKeyPairException(user); } @@ -303,7 +330,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { @Override public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) - throws SmackOpenPgpException, InvalidBackupCodeException { + throws SmackOpenPgpException, InvalidBackupCodeException { // TODO: Figure out InvalidBackupCodeException byte[] base64Decoded = Base64.decode(secretkeyElement.getB64Data()); try { @@ -369,6 +396,11 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { } } + @Override + public Date getPubkeysLatestUpdateDate(BareJid owner, OpenPgpV4Fingerprint fingerprint) { + return readDateFromFile(publicKeyUpdateDatePath(owner, fingerprint)); + } + private File secretKeyringPath() { return new File(contactsPath(user), "secring.skr"); } @@ -389,6 +421,66 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { return new File(contactsPath(contact), "metadata.list"); } + private File publicKeyUpdateDatePath(BareJid owner, OpenPgpV4Fingerprint fingerprint) { + return new File(contactsPath(owner), fingerprint.toString() + "-update.date"); + } + + private static void writeDateToFile(File file, Date date) throws IOException { + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(file), "UTF8")); + writer.write(XmppDateTime.formatXEP0082Date(date)); + writer.flush(); + writer.close(); + } catch (IOException e) { + if (writer != null) { + writer.close(); + } + throw e; + } + } + + private static Date readDateFromFile(File file) { + if (!file.exists()) { + return null; + } + + BufferedReader reader = null; + Date result = null; + try { + reader = new BufferedReader(new InputStreamReader( + new FileInputStream(file), "UTF8")); + String line = reader.readLine(); + if (!line.isEmpty()) { + result = XmppDateTime.parseXEP0082Date(line); + } + reader.close(); + reader = null; + } catch (UnsupportedEncodingException | FileNotFoundException e) { + throw new AssertionError(e); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Exception while reading date.", e); + } catch (ParseException e) { + LOGGER.log(Level.WARNING, "Could not parse date", e); + result = null; + } + + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Could not close reader.", e); + } + } + return result; + } + private static void addPublicKeysFromFile(InMemoryKeyring keyring, File pubring, KeyringConfigCallback passwordCallback) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java index d393cb34d..17485f9d0 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java @@ -18,6 +18,8 @@ package org.jivesoftware.smackx.ox; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.util.Date; +import java.util.Map; import java.util.Set; import org.jivesoftware.smack.XMPPConnection; @@ -53,18 +55,18 @@ public interface OpenPgpStore { Set availableOpenPgpKeyPairFingerprints(); /** - * Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP - * public keys of a contact. + * Return a {@link Map} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP + * public keys of a contact along with the dates of their latest update. *
* Note: Those are the keys announced in the latest received metadata update. - * This returns a {@link Set} which might be different from the result of + * This returns a {@link Map} which might contain different {@link OpenPgpV4Fingerprint}s than the result of * {@link #availableOpenPgpPublicKeysFingerprints(BareJid)}. * Messages should be encrypted to the intersection of both sets. * * @param contact contact. - * @return list of contacts last announced public keys. + * @return map of contacts last announced public keys and their update dates. */ - Set announcedOpenPgpKeyFingerprints(BareJid contact); + Map announcedOpenPgpKeyFingerprints(BareJid contact); /** * Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a @@ -95,6 +97,7 @@ public interface OpenPgpStore { * (example: {@code "xmpp:juliet@capulet.lit"}). * Store the key pair in persistent storage and return the public keys {@link OpenPgpV4Fingerprint}. * + * @return {@link OpenPgpV4Fingerprint} of the generated key pair. * @throws NoSuchAlgorithmException if a Hash algorithm is not available * @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider) * is registered. @@ -121,12 +124,22 @@ public interface OpenPgpStore { * @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}. * @param fingerprint {@link OpenPgpV4Fingerprint} of the key. * @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}. + * @param currentMetadataDate {@link Date} which is currently found in the metadata node for this key. * @throws SmackOpenPgpException if the key found in the {@link PubkeyElement} * can not be deserialized or imported. */ - void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) + void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element, Date currentMetadataDate) throws SmackOpenPgpException; + /** + * Return the {@link Date} of the last time on which the key has been fetched from PubSub. + * + * @param owner owner of the key + * @param fingerprint fingerprint of the key. + * @return {@link Date} or {@code null} if no record found. + */ + Date getPubkeysLatestUpdateDate(BareJid owner, OpenPgpV4Fingerprint fingerprint); + /** * Create an encrypted backup of our secret keys. *