001/**
002 *
003 * Copyright 2018 Paul Schaub.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.ox.store.filebased;
018
019import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileInputStream;
020import static org.jivesoftware.smackx.ox.util.FileUtils.prepareFileOutputStream;
021
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.FileNotFoundException;
025import java.io.IOException;
026import java.io.OutputStream;
027import java.util.Date;
028import java.util.Map;
029
030import org.jivesoftware.smack.util.Objects;
031import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpKeyStore;
032import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
033
034import org.bouncycastle.openpgp.PGPException;
035import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
036import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
037import org.jxmpp.jid.BareJid;
038import org.pgpainless.PGPainless;
039import org.pgpainless.key.OpenPgpV4Fingerprint;
040
041/**
042 * This class is an implementation of the {@link OpenPgpKeyStore}, which stores keys in a file structure.
043 * The keys are stored in the following directory structure:
044 *
045 * <pre>
046 * {@code
047 * <basePath>/
048 *     <userjid@server.tld>/
049 *         pubring.pkr      // public keys of the user/contact
050 *         secring.pkr      // secret keys of the user
051 *         fetchDates.list  // date of the last time we fetched the users keys
052 * }
053 * </pre>
054 */
055public class FileBasedOpenPgpKeyStore extends AbstractOpenPgpKeyStore {
056
057    private static final String PUB_RING = "pubring.pkr";
058    private static final String SEC_RING = "secring.skr";
059    private static final String FETCH_DATES = "fetchDates.list";
060
061    private final File basePath;
062
063    public FileBasedOpenPgpKeyStore(File basePath) {
064        this.basePath = Objects.requireNonNull(basePath);
065    }
066
067    @Override
068    public void writePublicKeysOf(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException {
069        File file = getPublicKeyRingPath(owner);
070
071        if (publicKeys == null) {
072            if (!file.exists()) {
073                return;
074            }
075            if (!file.delete()) {
076                throw new IOException("Could not delete file " + file.getAbsolutePath());
077            }
078            return;
079        }
080
081        OutputStream outputStream = null;
082        try {
083            outputStream = prepareFileOutputStream(file);
084            publicKeys.encode(outputStream);
085            outputStream.close();
086        } catch (IOException e) {
087            if (outputStream != null) {
088                try {
089                    outputStream.close();
090                } catch (IOException ignored) {
091                    // Don't care
092                }
093            }
094            throw e;
095        }
096    }
097
098    @Override
099    public void writeSecretKeysOf(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException {
100        File file = getSecretKeyRingPath(owner);
101
102        if (secretKeys == null) {
103            if (!file.exists()) {
104                return;
105            }
106            if (!file.delete()) {
107                throw new IOException("Could not delete file " + file.getAbsolutePath());
108            }
109            return;
110        }
111
112        OutputStream outputStream = null;
113        try {
114            outputStream = prepareFileOutputStream(file);
115            secretKeys.encode(outputStream);
116            outputStream.close();
117        } catch (IOException e) {
118            if (outputStream != null) {
119                try {
120                    outputStream.close();
121                } catch (IOException ignored) {
122                    // Don't care
123                }
124            }
125            throw e;
126        }
127    }
128
129    @Override
130    public PGPPublicKeyRingCollection readPublicKeysOf(BareJid owner)
131            throws IOException, PGPException {
132        File file = getPublicKeyRingPath(owner);
133
134        FileInputStream inputStream;
135        try {
136            inputStream = prepareFileInputStream(file);
137        } catch (FileNotFoundException e) {
138            return null;
139        }
140
141        PGPPublicKeyRingCollection collection = PGPainless.readKeyRing().publicKeyRingCollection(inputStream);
142        inputStream.close();
143        return collection;
144    }
145
146    @Override
147    public PGPSecretKeyRingCollection readSecretKeysOf(BareJid owner) throws IOException, PGPException {
148        File file = getSecretKeyRingPath(owner);
149
150        FileInputStream inputStream;
151        try {
152            inputStream = prepareFileInputStream(file);
153        } catch (FileNotFoundException e) {
154            return null;
155        }
156
157        PGPSecretKeyRingCollection collection = PGPainless.readKeyRing().secretKeyRingCollection(inputStream);
158        inputStream.close();
159        return collection;
160    }
161
162    @Override
163    protected Map<OpenPgpV4Fingerprint, Date> readKeyFetchDates(BareJid owner) throws IOException {
164        return FileBasedOpenPgpMetadataStore.readFingerprintsAndDates(getFetchDatesPath(owner));
165    }
166
167    @Override
168    protected void writeKeyFetchDates(BareJid owner, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException {
169        FileBasedOpenPgpMetadataStore.writeFingerprintsAndDates(dates, getFetchDatesPath(owner));
170    }
171
172    private File getPublicKeyRingPath(BareJid jid) {
173        return new File(FileBasedOpenPgpStore.getContactsPath(basePath, jid), PUB_RING);
174    }
175
176    private File getSecretKeyRingPath(BareJid jid) {
177        return new File(FileBasedOpenPgpStore.getContactsPath(basePath, jid), SEC_RING);
178    }
179
180    private File getFetchDatesPath(BareJid jid) {
181        return new File(FileBasedOpenPgpStore.getContactsPath(basePath, jid), FETCH_DATES);
182    }
183}