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