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