Add methods to store update dates

This commit is contained in:
Paul Schaub 2018-06-02 14:27:18 +02:00
parent 48862962db
commit dab342e97e
2 changed files with 120 additions and 15 deletions

View File

@ -23,13 +23,16 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import java.text.ParseException;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; 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.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.util.XmppDateTime;
public class FileBasedBcOpenPgpStore implements BCOpenPgpStore { public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
@ -133,8 +137,8 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
} }
@Override @Override
public Set<OpenPgpV4Fingerprint> announcedOpenPgpKeyFingerprints(BareJid contact) { public Map<OpenPgpV4Fingerprint, Date> announcedOpenPgpKeyFingerprints(BareJid contact) {
Set<OpenPgpV4Fingerprint> announcedKeys = new HashSet<>(); Map<OpenPgpV4Fingerprint, Date> announcedKeys = new HashMap<>();
File listPath = contactsList(contact); File listPath = contactsList(contact);
if (listPath.exists() && listPath.isFile()) { if (listPath.exists() && listPath.isFile()) {
BufferedReader reader = null; BufferedReader reader = null;
@ -149,12 +153,25 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
continue; continue;
} }
String[] split = line.split(" ");
OpenPgpV4Fingerprint fingerprint;
Date date = null;
try { try {
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(line); fingerprint = new OpenPgpV4Fingerprint(split[0]);
announcedKeys.add(fingerprint);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOGGER.log(Level.INFO, "Skip malformed fingerprint " + line + " of " + contact.toString()); 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(); reader.close();
} catch (IOException e) { } catch (IOException e) {
@ -204,8 +221,11 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
writer = new BufferedWriter(new OutputStreamWriter( writer = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(listPath), "UTF8")); new FileOutputStream(listPath), "UTF8"));
for (OpenPgpV4Fingerprint fingerprint : listElement.getMetadata().keySet()) { for (PublicKeysListElement.PubkeyMetadataElement entry : listElement.getMetadata().values()) {
writer.write(fingerprint.toString()); OpenPgpV4Fingerprint fingerprint = entry.getV4Fingerprint();
Date date = entry.getDate();
String line = fingerprint.toString() + (date != null ? date : "");
writer.write(line);
writer.newLine(); writer.newLine();
} }
@ -238,7 +258,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
} }
@Override @Override
public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element, Date latestMetadataDate)
throws SmackOpenPgpException { throws SmackOpenPgpException {
byte[] base64decoded = Base64.decode(element.getDataElement().getB64Data()); byte[] base64decoded = Base64.decode(element.getDataElement().getB64Data());
try { try {
@ -249,6 +269,13 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
LOGGER.log(Level.WARNING, "Public Key with ID " + fingerprint.toString() + " of " + LOGGER.log(Level.WARNING, "Public Key with ID " + fingerprint.toString() + " of " +
owner + " is already in memory. Skip."); 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)); .getSecretKey(Util.keyIdFromFingerprint(fingerprint));
if (secretKey == null) { if (secretKey == null) {
// TODO: Close streams buffer.close();
throw new MissingOpenPgpKeyPairException(user); throw new MissingOpenPgpKeyPairException(user);
} }
@ -303,7 +330,7 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
@Override @Override
public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) 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()); byte[] base64Decoded = Base64.decode(secretkeyElement.getB64Data());
try { 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() { private File secretKeyringPath() {
return new File(contactsPath(user), "secring.skr"); return new File(contactsPath(user), "secring.skr");
} }
@ -389,6 +421,66 @@ public class FileBasedBcOpenPgpStore implements BCOpenPgpStore {
return new File(contactsPath(contact), "metadata.list"); 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, private static void addPublicKeysFromFile(InMemoryKeyring keyring,
File pubring, File pubring,
KeyringConfigCallback passwordCallback) KeyringConfigCallback passwordCallback)

View File

@ -18,6 +18,8 @@ package org.jivesoftware.smackx.ox;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException; import java.security.NoSuchProviderException;
import java.util.Date;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
@ -53,18 +55,18 @@ public interface OpenPgpStore {
Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints(); Set<OpenPgpV4Fingerprint> availableOpenPgpKeyPairFingerprints();
/** /**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP * Return a {@link Map} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP
* public keys of a contact. * public keys of a contact along with the dates of their latest update.
* <br> * <br>
* Note: Those are the keys announced in the latest received metadata 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)}. * {@link #availableOpenPgpPublicKeysFingerprints(BareJid)}.
* Messages should be encrypted to the intersection of both sets. * Messages should be encrypted to the intersection of both sets.
* *
* @param contact contact. * @param contact contact.
* @return list of contacts last announced public keys. * @return map of contacts last announced public keys and their update dates.
*/ */
Set<OpenPgpV4Fingerprint> announcedOpenPgpKeyFingerprints(BareJid contact); Map<OpenPgpV4Fingerprint, Date> announcedOpenPgpKeyFingerprints(BareJid contact);
/** /**
* Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a * 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"}). * (example: {@code "xmpp:juliet@capulet.lit"}).
* Store the key pair in persistent storage and return the public keys {@link OpenPgpV4Fingerprint}. * 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 NoSuchAlgorithmException if a Hash algorithm is not available
* @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider) * @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider)
* is registered. * is registered.
@ -121,12 +124,22 @@ public interface OpenPgpStore {
* @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}. * @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}.
* @param fingerprint {@link OpenPgpV4Fingerprint} of the key. * @param fingerprint {@link OpenPgpV4Fingerprint} of the key.
* @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}. * @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} * @throws SmackOpenPgpException if the key found in the {@link PubkeyElement}
* can not be deserialized or imported. * 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; 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. * Create an encrypted backup of our secret keys.
* *