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}