001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.geronimo.security.ca;
019    
020    import java.io.File;
021    import java.io.FileInputStream;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.math.BigInteger;
025    import java.net.URI;
026    import java.security.cert.Certificate;
027    import java.security.cert.CertificateFactory;
028    import java.security.cert.X509Certificate;
029    import java.util.Properties;
030    
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    import org.apache.geronimo.gbean.AbstractName;
034    import org.apache.geronimo.gbean.GBeanInfo;
035    import org.apache.geronimo.gbean.GBeanInfoBuilder;
036    import org.apache.geronimo.gbean.GBeanLifecycle;
037    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
038    import org.apache.geronimo.kernel.Kernel;
039    import org.apache.geronimo.management.geronimo.CertificateStore;
040    import org.apache.geronimo.management.geronimo.CertificateStoreException;
041    import org.apache.geronimo.system.serverinfo.ServerInfo;
042    import org.apache.geronimo.util.CaUtils;
043    
044    /**
045     * A certificate store implementation using disk files.
046     *
047     * @version $Rev: 476291 $ $Date: 2006-11-17 15:05:24 -0500 (Fri, 17 Nov 2006) $
048     */
049    
050    public class FileCertificateStore implements CertificateStore, GBeanLifecycle {
051        private static final Log log = LogFactory.getLog(FileCertificateStore.class);
052    
053        private ServerInfo serverInfo;
054        private Kernel kernel;
055        private AbstractName abstractName;
056        private URI directoryPath;
057        
058        // File name for storing the highest serial number in the store
059        private static final String SERIAL_NUMBER_FILE = "highest-serial-number.txt";
060        // Extension for certificate files.  Filename would be <serial-number>+CERT_FILE_SUFFIX
061        private static final String CERT_FILE_SUFFIX = ".txt";
062        // File name for storing CA's certificate
063        private static final String CA_CERT_FILE = "ca-cert.txt";
064        // File name for storing Certificate Challenges
065        private static final String CHALLENGE_FILENAME = "challenge.properties";
066        private static final String CHALLENGE_FILE_HEADER = "Challenge File";
067        
068        // directory for the certificate store
069        private File storeDir = null;
070        
071        // File object of SERIAL_NUMBER_FILE cached
072        private File highestSerialFile = null;
073        // highest serial number cached
074        private BigInteger highestSerialNumber = null;
075        // Cerificate Challenges
076        private Properties challenges = null;
077        
078        /**
079         * Constructor
080         * @param storeDir directory for the certificate store
081         */
082        public FileCertificateStore(ServerInfo serverInfo, URI directoryPath, Kernel kernel, AbstractName abstractName) {
083            this.serverInfo = serverInfo;
084            this.kernel = kernel;
085            this.abstractName = abstractName;
086            this.directoryPath = directoryPath;
087        }
088        /**
089         * This method stores a given certificate.
090         * 
091         * @param cert Certificate to be stored
092         */
093        public void storeCertificate(Certificate cert) throws CertificateStoreException {
094            BigInteger sNo = ((X509Certificate)cert).getSerialNumber();
095            File certFile = new File(storeDir, sNo+CERT_FILE_SUFFIX);
096            try {
097                // Check if the highest serial number is less than the serial number of certificate to be stored.
098                if(sNo.compareTo(getHighestSerialNumber()) == 1) {
099                    // store the current serial number so that getNextSerialNumber() will not result in duplicate
100                    // serial number
101                    setHighestSerialNumber(sNo);
102                }
103                
104                // Store the certificate to disk in base64 format
105                FileOutputStream fout = new FileOutputStream(certFile);
106                CaUtils.storeInBase64(fout, cert.getEncoded(), CaUtils.CERT_HEADER, CaUtils.CERT_FOOTER, CaUtils.B64_LINE_SIZE);
107                fout.close();
108            } catch (Exception e) {
109                throw new CertificateStoreException("Error while storing certificate.", e);
110            }
111        }
112    
113        /**
114         * This method returns a Certificate with a given serial number (if it exists in the store)
115         * 
116         * @param sNo Serial Number of the certificate to be retrieved.
117         */
118        public Certificate getCertificate(BigInteger sNo) throws CertificateStoreException {
119            File certFile = new File(storeDir, sNo+CERT_FILE_SUFFIX);
120            if(!certFile.exists()) {
121                // No such certificate in the store.
122                throw new CertificateStoreException("No certificate with serial number "+sNo+" found.");
123            }
124            
125            // Read the certificate from disk and generate a java.security.cert.Certificate
126            try {
127                FileInputStream fin = new FileInputStream(certFile);
128                CertificateFactory certFac = CertificateFactory.getInstance("X.509");
129                Certificate cert = certFac.generateCertificate(fin);
130                fin.close();
131                return cert;
132            } catch (Exception e) {
133                throw new CertificateStoreException("Error while retrieving certificate.", e);
134            }
135        }
136    
137        /**
138         * This method returns base64 encoded certificate with a given serial number (if it exists in the store)
139         * 
140         * @param sNo Serial Number of the certificate to be retrieved.
141         */
142        public String getCertificateBase64Text(BigInteger sNo) throws CertificateStoreException {
143            File certFile = new File(storeDir, sNo+CERT_FILE_SUFFIX);
144            if(!certFile.exists()) {
145                throw new CertificateStoreException("No certificate with serial number "+sNo+" found.");
146            }
147            FileInputStream fin;
148            try {
149                fin = new FileInputStream(certFile);
150                byte[] data = new byte[fin.available()];
151                fin.read(data);
152                fin.close();
153                return new String(data);
154            } catch (Exception e) {
155                throw new CertificateStoreException("Error while retrieving certificate.", e);
156            }
157        }
158        
159        /**
160         * This method returns the highest certificate serial number in the store.
161         */
162        public BigInteger getHighestSerialNumber() throws CertificateStoreException{
163            if(highestSerialNumber == null) {
164                // Value has not been cached.  Read from the disk.
165                try {
166                    FileInputStream finp = new FileInputStream(highestSerialFile);
167                    byte[] data = new byte[finp.available()];
168                    finp.read(data);
169                    finp.close();
170                    highestSerialNumber = new BigInteger(new String(data).trim());
171                } catch (Exception e) {
172                    throw new CertificateStoreException("Error while getting serial number.", e);
173                }
174            }
175            return highestSerialNumber;
176        }
177    
178        /**
179         * This method returns the 'highest certificate serial number plus ONE' and increments the highest
180         * serial number in the store.
181         */
182        public BigInteger getNextSerialNumber() throws CertificateStoreException{
183            setHighestSerialNumber(getHighestSerialNumber().add(BigInteger.ONE));
184            return highestSerialNumber;
185        }
186    
187        /**
188         * This method checks if a certificate with a given serial number exists in the store.
189         * 
190         * @param sNo Serial number of the certificate to be checked
191         */
192        public boolean containsCertificate(BigInteger sNo) {
193            File certFile = new File(storeDir, sNo+CERT_FILE_SUFFIX);
194            return certFile.exists();
195        }
196        
197        /**
198         * This method sets the highest serial number to a given value and updates the same to disk.
199         * @param sNo The serial number to be set
200         */
201        private void setHighestSerialNumber(BigInteger sNo) throws CertificateStoreException{
202            try {
203                highestSerialNumber = sNo;
204                FileOutputStream fout = new FileOutputStream(highestSerialFile);
205                fout.write(highestSerialNumber.toString().getBytes());
206                fout.close();
207            } catch (Exception e) {
208                throw new CertificateStoreException("Error while setting highest serial number.", e);
209            }
210        }
211        
212        /**
213         * This method stores the CA's certificate in the store.
214         * @param cert CA's certificate
215         */
216        public boolean storeCACertificate(Certificate cert) throws CertificateStoreException{
217            FileOutputStream fout = null;
218            try {
219                fout = new FileOutputStream(new File(storeDir, CA_CERT_FILE));
220                CaUtils.storeInBase64(fout, cert.getEncoded(), CaUtils.CERT_HEADER, CaUtils.CERT_FOOTER, CaUtils.B64_LINE_SIZE);
221                fout.close();
222                return true;
223            } catch (Exception e) {
224                throw new CertificateStoreException("Exception in storing CA certificate", e);
225            }
226        }
227    
228        /**
229         * This method returns the CA's certificate stored in the store.
230         */
231        public Certificate getCACertificate() throws CertificateStoreException {
232            FileInputStream fin = null;
233            try {
234                fin = new FileInputStream(new File(storeDir, CA_CERT_FILE));
235                CertificateFactory certFac = CertificateFactory.getInstance("X.509");
236                Certificate cert = certFac.generateCertificate(fin);
237                fin.close();
238                return cert;
239            } catch (Exception e) {
240                throw new CertificateStoreException("Exception in getting CA certificate", e);
241            }
242        }
243        
244        /**
245         * This method stores the challenge phrase against the specified certificate serial number
246         * @param sNo  Serial number of the certificate
247         * @param challenge Challenge phrase
248         */
249        public boolean setCertificateChallenge(BigInteger sNo, String challenge) {
250            if(challenges == null) {
251                loadChallenges();
252            }
253            if(!challenges.containsKey(sNo.toString())) {
254                challenges.setProperty(sNo.toString(), challenge);
255                storeChallenges();
256                return true;
257            }
258            return false;
259        }
260    
261        /**
262         * This methods stores the challenges map to disk
263         */
264        private void storeChallenges() {
265            if(challenges == null) loadChallenges();
266            File chFile = new File(storeDir, CHALLENGE_FILENAME);
267            FileOutputStream fout = null;
268            try {
269                fout = new FileOutputStream(chFile);
270                challenges.store(fout, CHALLENGE_FILE_HEADER);
271                fout.close();
272            } catch (Exception e) {
273                log.error("Exceptions while storing challenges file. File = "+chFile.getAbsolutePath(), e);
274            }
275            
276        }
277        
278        /**
279         * This method loads the challenges map from disk.
280         */
281        private void loadChallenges() {
282            File chFile = new File(storeDir, CHALLENGE_FILENAME);
283            FileInputStream fin = null;
284            try {
285                if(!chFile.exists())
286                    chFile.createNewFile();
287                fin = new FileInputStream(chFile);
288                challenges = new Properties();
289                challenges.load(fin);
290                fin.close();
291            } catch (IOException e) {
292                log.error("Exceptions while loading challenges file. File = "+chFile.getAbsolutePath(), e);
293            }
294        }
295        
296        public void doFail() {
297        }
298    
299        public void doStart() throws Exception {
300            serverInfo.resolveServer(directoryPath);
301            URI dirURI;
302            if (serverInfo != null) {
303                dirURI = serverInfo.resolve(directoryPath);
304            } else {
305                dirURI = directoryPath;
306            }
307            if (!dirURI.getScheme().equals("file")) {
308                throw new IllegalStateException("FileCertificateStore must have a root that's a local directory (not " + dirURI + ")");
309            }
310            storeDir = new File(dirURI);
311            if(!storeDir.exists()) {
312                storeDir.mkdirs();
313                log.debug("Created directory "+storeDir.getAbsolutePath());
314            } else if(!storeDir.isDirectory() || !storeDir.canRead()) {
315                throw new IllegalStateException("FileCertificateStore must have a root that's a valid readable directory (not " + storeDir.getAbsolutePath() + ")");
316            }
317            log.debug("CertificateStore directory is " + storeDir.getAbsolutePath());
318            highestSerialFile = new File(storeDir, SERIAL_NUMBER_FILE);
319            if(!highestSerialFile.exists()) {
320                // If the file does not exist, it means the certificate store is a new one.
321                // Start with ZERO
322                try {
323                    setHighestSerialNumber(BigInteger.ZERO);
324                } catch(CertificateStoreException e) {
325                    log.error("Error initializing certificate store. storeDir="+storeDir, e);
326                }
327            }
328            loadChallenges();
329        }
330    
331        public void doStop() throws Exception {
332        }
333    
334        public static final GBeanInfo GBEAN_INFO;
335    
336        static {
337            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(FileCertificateStore.class, "CertificateStore");
338            infoFactory.addAttribute("directoryPath", URI.class, true, false);
339            infoFactory.addAttribute("kernel", Kernel.class, false);
340            infoFactory.addAttribute("abstractName", AbstractName.class, false);
341            infoFactory.addReference("ServerInfo", ServerInfo.class, NameFactory.GERONIMO_SERVICE);
342            infoFactory.addInterface(CertificateStore.class);
343            infoFactory.setConstructor(new String[]{"ServerInfo", "directoryPath", "kernel", "abstractName"});
344    
345            GBEAN_INFO = infoFactory.getBeanInfo();
346        }
347        
348        public static GBeanInfo getGBeanInfo() {
349            return GBEAN_INFO;
350        }
351    }