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.abstr;
018
019import java.io.IOException;
020import java.security.InvalidAlgorithmParameterException;
021import java.security.NoSuchAlgorithmException;
022import java.security.NoSuchProviderException;
023import java.util.Collections;
024import java.util.Date;
025import java.util.HashMap;
026import java.util.Map;
027import java.util.logging.Level;
028import java.util.logging.Logger;
029
030import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException;
031import org.jivesoftware.smackx.ox.store.definition.OpenPgpKeyStore;
032
033import org.bouncycastle.openpgp.PGPException;
034import org.bouncycastle.openpgp.PGPPublicKeyRing;
035import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
036import org.bouncycastle.openpgp.PGPSecretKeyRing;
037import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
038import org.jxmpp.jid.BareJid;
039import org.pgpainless.PGPainless;
040import org.pgpainless.key.OpenPgpV4Fingerprint;
041import org.pgpainless.key.info.KeyRingInfo;
042
043public abstract class AbstractOpenPgpKeyStore implements OpenPgpKeyStore {
044
045    protected static final Logger LOGGER = Logger.getLogger(AbstractOpenPgpKeyStore.class.getName());
046
047    protected Map<BareJid, PGPPublicKeyRingCollection> publicKeyRingCollections = new HashMap<>();
048    protected Map<BareJid, PGPSecretKeyRingCollection> secretKeyRingCollections = new HashMap<>();
049    protected Map<BareJid, Map<OpenPgpV4Fingerprint, Date>> keyFetchDates = new HashMap<>();
050
051    /**
052     * Read a {@link PGPPublicKeyRingCollection} from local storage.
053     * This method returns null, if no keys were found.
054     *
055     * @param owner owner of the keys
056     * @return public keys
057     *
058     * @throws IOException IO is dangerous
059     * @throws PGPException PGP is brittle
060     */
061    protected abstract PGPPublicKeyRingCollection readPublicKeysOf(BareJid owner) throws IOException, PGPException;
062
063    /**
064     * Write the {@link PGPPublicKeyRingCollection} of a user to local storage.
065     *
066     * @param owner owner of the keys
067     * @param publicKeys keys
068     *
069     * @throws IOException IO is dangerous
070     */
071    protected abstract void writePublicKeysOf(BareJid owner, PGPPublicKeyRingCollection publicKeys) throws IOException;
072
073    /**
074     * Read a {@link PGPSecretKeyRingCollection} from local storage.
075     * This method returns null, if no keys were found.
076     *
077     * @param owner owner of the keys
078     * @return secret keys
079     *
080     * @throws IOException IO is dangerous
081     * @throws PGPException PGP is brittle
082     */
083    protected abstract PGPSecretKeyRingCollection readSecretKeysOf(BareJid owner) throws IOException, PGPException;
084
085    /**
086     * Write the {@link PGPSecretKeyRingCollection} of a user to local storage.
087     *
088     * @param owner owner of the keys
089     * @param secretKeys secret keys
090     *
091     * @throws IOException IO is dangerous
092     */
093    protected abstract void writeSecretKeysOf(BareJid owner, PGPSecretKeyRingCollection secretKeys) throws IOException;
094
095    /**
096     * Read the key fetch dates for a users keys from local storage.
097     *
098     * @param owner owner
099     * @return fetch dates for the owners keys
100     *
101     * @throws IOException IO is dangerous
102     */
103    protected abstract Map<OpenPgpV4Fingerprint, Date> readKeyFetchDates(BareJid owner) throws IOException;
104
105    /**
106     * Write the key fetch dates for a users keys to local storage.
107     *
108     * @param owner owner
109     * @param dates fetch dates for the owners keys
110     *
111     * @throws IOException IO is dangerous
112     */
113    protected abstract void writeKeyFetchDates(BareJid owner, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException;
114
115    @Override
116    public Map<OpenPgpV4Fingerprint, Date> getPublicKeyFetchDates(BareJid contact) throws IOException {
117        Map<OpenPgpV4Fingerprint, Date> dates = keyFetchDates.get(contact);
118        if (dates == null) {
119            dates = readKeyFetchDates(contact);
120            keyFetchDates.put(contact, dates);
121        }
122        return dates;
123    }
124
125    @Override
126    public void setPublicKeyFetchDates(BareJid contact, Map<OpenPgpV4Fingerprint, Date> dates) throws IOException {
127        keyFetchDates.put(contact, dates);
128        writeKeyFetchDates(contact, dates);
129    }
130
131    @Override
132    public PGPPublicKeyRingCollection getPublicKeysOf(BareJid owner) throws IOException, PGPException {
133        PGPPublicKeyRingCollection keys = publicKeyRingCollections.get(owner);
134        if (keys == null) {
135            keys = readPublicKeysOf(owner);
136            if (keys != null) {
137                publicKeyRingCollections.put(owner, keys);
138            }
139        }
140        return keys;
141    }
142
143    @Override
144    public PGPSecretKeyRingCollection getSecretKeysOf(BareJid owner) throws IOException, PGPException {
145        PGPSecretKeyRingCollection keys = secretKeyRingCollections.get(owner);
146        if (keys == null) {
147            keys = readSecretKeysOf(owner);
148            if (keys != null) {
149                secretKeyRingCollections.put(owner, keys);
150            }
151        }
152        return keys;
153    }
154
155    @Override
156    public void importSecretKey(BareJid owner, PGPSecretKeyRing secretKeys)
157            throws IOException, PGPException, MissingUserIdOnKeyException {
158
159        if (!new KeyRingInfo(secretKeys).isUserIdValid("xmpp:" + owner.toString())) {
160            throw new MissingUserIdOnKeyException(owner, new OpenPgpV4Fingerprint(secretKeys));
161        }
162
163        PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner);
164        try {
165            if (secretKeyRings != null) {
166                secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(secretKeyRings, secretKeys);
167            } else {
168                secretKeyRings = new PGPSecretKeyRingCollection(Collections.singleton(secretKeys));
169            }
170        } catch (IllegalArgumentException e) {
171            LOGGER.log(Level.INFO, "Skipping secret key ring " + Long.toHexString(secretKeys.getPublicKey().getKeyID()) +
172                    " as it is already in the key ring of " + owner.toString());
173        }
174        this.secretKeyRingCollections.put(owner, secretKeyRings);
175        writeSecretKeysOf(owner, secretKeyRings);
176    }
177
178    @Override
179    public void importPublicKey(BareJid owner, PGPPublicKeyRing publicKeys) throws IOException, PGPException, MissingUserIdOnKeyException {
180
181        if (!new KeyRingInfo(publicKeys).isUserIdValid("xmpp:" + owner.toString())) {
182            throw new MissingUserIdOnKeyException(owner, new OpenPgpV4Fingerprint(publicKeys));
183        }
184
185        PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner);
186        try {
187            if (publicKeyRings != null) {
188                publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, publicKeys);
189            } else {
190                publicKeyRings = new PGPPublicKeyRingCollection(Collections.singleton(publicKeys));
191            }
192        } catch (IllegalArgumentException e) {
193            LOGGER.log(Level.FINE, "Skipping public key ring " + Long.toHexString(publicKeys.getPublicKey().getKeyID()) +
194                    " as it is already in the key ring of " + owner.toString(), e);
195        }
196        this.publicKeyRingCollections.put(owner, publicKeyRings);
197        writePublicKeysOf(owner, publicKeyRings);
198    }
199
200    @Override
201    public PGPPublicKeyRing getPublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
202        PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner);
203
204        if (publicKeyRings != null) {
205            return publicKeyRings.getPublicKeyRing(fingerprint.getKeyId());
206        }
207
208        return null;
209    }
210
211    @Override
212    public PGPSecretKeyRing getSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
213        PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner);
214
215        if (secretKeyRings != null) {
216            return secretKeyRings.getSecretKeyRing(fingerprint.getKeyId());
217        }
218
219        return null;
220    }
221
222    @Override
223    public void deletePublicKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
224        PGPPublicKeyRingCollection publicKeyRings = getPublicKeysOf(owner);
225        if (publicKeyRings.contains(fingerprint.getKeyId())) {
226            publicKeyRings = PGPPublicKeyRingCollection.removePublicKeyRing(publicKeyRings, publicKeyRings.getPublicKeyRing(fingerprint.getKeyId()));
227            if (!publicKeyRings.iterator().hasNext()) {
228                publicKeyRings = null;
229            }
230            this.publicKeyRingCollections.put(owner, publicKeyRings);
231            writePublicKeysOf(owner, publicKeyRings);
232        }
233    }
234
235    @Override
236    public void deleteSecretKeyRing(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException, PGPException {
237        PGPSecretKeyRingCollection secretKeyRings = getSecretKeysOf(owner);
238        if (secretKeyRings.contains(fingerprint.getKeyId())) {
239            secretKeyRings = PGPSecretKeyRingCollection.removeSecretKeyRing(secretKeyRings, secretKeyRings.getSecretKeyRing(fingerprint.getKeyId()));
240            if (!secretKeyRings.iterator().hasNext()) {
241                secretKeyRings = null;
242            }
243            this.secretKeyRingCollections.put(owner, secretKeyRings);
244            writeSecretKeysOf(owner, secretKeyRings);
245        }
246    }
247
248    @Override
249    public PGPSecretKeyRing generateKeyRing(BareJid owner)
250            throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
251        return PGPainless.generateKeyRing().modernKeyRing("xmpp:" + owner.toString(), null);
252    }
253}