diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java index 24ad712f8..20cb5a84a 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java @@ -16,10 +16,13 @@ */ package org.jivesoftware.smackx.ox; +import javax.xml.bind.DatatypeConverter; +import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.text.ParseException; +import java.util.Arrays; import org.jivesoftware.smack.util.Objects; -import org.jivesoftware.smackx.ox.util.Util; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.util.encoders.Hex; @@ -37,22 +40,22 @@ public class OpenPgpV4Fingerprint implements CharSequence, Comparable * @param fingerprint hexadecimal representation of the fingerprint. */ - public OpenPgpV4Fingerprint(String fingerprint) { + public OpenPgpV4Fingerprint(String fingerprint) throws ParseException { String fp = Objects.requireNonNull(fingerprint) .trim() .toUpperCase(); if (!isValid(fp)) { - throw new IllegalArgumentException("Fingerprint " + fingerprint + - " does not appear to be a valid OpenPGP v4 fingerprint."); + throw new ParseException("Fingerprint " + fingerprint + + " does not appear to be a valid OpenPGP v4 fingerprint.", 0); } this.fingerprint = fp; } - public OpenPgpV4Fingerprint(byte[] bytes) { + public OpenPgpV4Fingerprint(byte[] bytes) throws ParseException { this(new String(bytes, Charset.forName("UTF-8"))); } - public OpenPgpV4Fingerprint(PGPPublicKey key) { + public OpenPgpV4Fingerprint(PGPPublicKey key) throws ParseException { this(Hex.encode(key.getFingerprint())); } @@ -67,14 +70,18 @@ public class OpenPgpV4Fingerprint implements CharSequence, Comparable * RFC-4880 §12.2: Key IDs and Fingerprints * @return key id */ public long getKeyId() { - return Util.keyIdFromFingerprint(this); + byte[] bytes = DatatypeConverter.parseHexBinary(this.toString()); + byte[] lower8Bytes = Arrays.copyOfRange(bytes, 12, 20); + ByteBuffer byteBuffer = ByteBuffer.allocate(8); + byteBuffer.put(lower8Bytes); + byteBuffer.flip(); + return byteBuffer.getLong(); } @Override diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/Util.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/Util.java index 892c790fb..9285666c0 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/Util.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/Util.java @@ -16,27 +16,9 @@ */ package org.jivesoftware.smackx.ox.util; -import java.nio.ByteBuffer; -import java.util.Arrays; -import javax.xml.bind.DatatypeConverter; - -import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; +import java.nio.charset.Charset; public class Util { - /** - * Calculate the key id of the OpenPGP key, the given {@link OpenPgpV4Fingerprint} belongs to. - * - * @see RFC-4880 §12.2 - * @param fingerprint {@link OpenPgpV4Fingerprint}. - * @return key id - */ - public static long keyIdFromFingerprint(OpenPgpV4Fingerprint fingerprint) { - byte[] bytes = DatatypeConverter.parseHexBinary(fingerprint.toString()); - byte[] lower8Bytes = Arrays.copyOfRange(bytes, 12, 20); - ByteBuffer byteBuffer = ByteBuffer.allocate(8); - byteBuffer.put(lower8Bytes); - byteBuffer.flip(); - return byteBuffer.getLong(); - } + public static final Charset UTF8 = Charset.forName("UTF-8"); } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/AbstractOpenPgpMetadataStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/AbstractOpenPgpMetadataStore.java index 108604139..8a49f136b 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/AbstractOpenPgpMetadataStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/AbstractOpenPgpMetadataStore.java @@ -17,9 +17,9 @@ package org.jivesoftware.smackx.ox.v2.store; import java.io.IOException; +import java.util.Date; import java.util.HashMap; import java.util.Map; -import java.util.Set; import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; @@ -27,11 +27,11 @@ import org.jxmpp.jid.BareJid; public abstract class AbstractOpenPgpMetadataStore implements OpenPgpMetadataStore { - private final Map> announcedFingerprints = new HashMap<>(); + private final Map> announcedFingerprints = new HashMap<>(); @Override - public Set getAnnouncedFingerprintsOf(BareJid contact) throws IOException { - Set fingerprints = announcedFingerprints.get(contact); + public Map getAnnouncedFingerprintsOf(BareJid contact) throws IOException { + Map fingerprints = announcedFingerprints.get(contact); if (fingerprints == null) { fingerprints = readAnnouncedFingerprintsOf(contact); announcedFingerprints.put(contact, fingerprints); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/OpenPgpMetadataStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/OpenPgpMetadataStore.java index 9994f17dc..c28b2ea87 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/OpenPgpMetadataStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/OpenPgpMetadataStore.java @@ -17,7 +17,8 @@ package org.jivesoftware.smackx.ox.v2.store; import java.io.IOException; -import java.util.Set; +import java.util.Date; +import java.util.Map; import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; @@ -26,9 +27,9 @@ import org.jxmpp.jid.BareJid; public interface OpenPgpMetadataStore { - Set getAnnouncedFingerprintsOf(BareJid contact) throws IOException; + Map getAnnouncedFingerprintsOf(BareJid contact) throws IOException; - Set readAnnouncedFingerprintsOf(BareJid contact) throws IOException; + Map readAnnouncedFingerprintsOf(BareJid contact) throws IOException; void writeAnnouncedFingerprintsOf(BareJid contact, PublicKeysListElement metadata) throws IOException; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/filebased/FileBasedOpenPgpMetadataStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/filebased/FileBasedOpenPgpMetadataStore.java index ab9852fff..caaaa31bb 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/filebased/FileBasedOpenPgpMetadataStore.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/v2/store/filebased/FileBasedOpenPgpMetadataStore.java @@ -16,24 +16,32 @@ */ package org.jivesoftware.smackx.ox.v2.store.filebased; -import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileInputStream; -import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileOutputStream; - +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Set; +import java.nio.file.Files; +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.OpenPgpV4Fingerprint; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.util.Util; import org.jivesoftware.smackx.ox.v2.store.AbstractOpenPgpMetadataStore; import org.jxmpp.jid.BareJid; +import org.jxmpp.util.XmppDateTime; public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore { - public static final String METADATA = "announced.list"; + 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; @@ -42,17 +50,94 @@ public class FileBasedOpenPgpMetadataStore extends AbstractOpenPgpMetadataStore } @Override - public Set readAnnouncedFingerprintsOf(BareJid contact) throws IOException { - InputStream inputStream = prepareFileInputStream(getMetadataPath(contact)); - return null; + public Map readAnnouncedFingerprintsOf(BareJid contact) throws IOException { + return readFingerprintsAndDates(getAnnouncedFingerprintsPath(contact)); } @Override - public void writeAnnouncedFingerprintsOf(BareJid contact, PublicKeysListElement metadata) throws IOException { - OutputStream outputStream = prepareFileOutputStream(getMetadataPath(contact)); + public void writeAnnouncedFingerprintsOf(BareJid contact, PublicKeysListElement metadata) + throws IOException { + File destination = getAnnouncedFingerprintsPath(contact); + Map fingerprintDateMap = new HashMap<>(); + for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) { + fingerprintDateMap.put(fingerprint, metadata.getMetadata().get(fingerprint).getDate()); + } + + writeFingerprintsAndDates(fingerprintDateMap, destination); } - private File getMetadataPath(BareJid contact) { - return new File(FileBasedOpenPgpStore.getContactsPath(basePath, contact), METADATA); + private Map readFingerprintsAndDates(File source) throws IOException { + BufferedReader reader = null; + try { + reader = Files.newBufferedReader(source.toPath(), Util.UTF8); + Map 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 (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; + } + } + + private void writeFingerprintsAndDates(Map data, File destination) + throws IOException { + + BufferedWriter writer = null; + try { + writer = Files.newBufferedWriter(destination.toPath(), Util.UTF8); + 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.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); } }