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.FilenameFilter;
024    import java.math.BigInteger;
025    import java.net.URI;
026    import java.util.ArrayList;
027    import java.util.Iterator;
028    import java.util.Map;
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.CertificateRequestStore;
040    import org.apache.geronimo.system.serverinfo.ServerInfo;
041    
042    /**
043     * A certificate request store implementation using disk files.
044     *
045     * @version $Rev: 476291 $ $Date: 2006-11-17 15:05:24 -0500 (Fri, 17 Nov 2006) $
046     */
047    public class FileCertificateRequestStore implements CertificateRequestStore, GBeanLifecycle {
048        private final static Log log = LogFactory.getLog(FileCertificateRequestStore.class);
049        
050        // File name to store certificate request status
051        private static final String CSR_STATUS_FILENAME = "csr-status.properties";
052        // File header for certificate request status file
053        private static final String CSR_STATUS_FILE_HEADER = "CSR Status File";
054        // Status showing the request as received
055        private static final String STATUS_RECEIVED = "R";
056        // Status showing the request as verified
057        private static final String STATUS_VERIFIED = "V";
058        // Prefix for certificate request files
059        private static final String CERT_REQ_FILE_PREFIX = "csr";
060        // Extension for certificate request files
061        private static final String CERT_REQ_FILE_SUFFIX = ".txt";
062        
063        private ServerInfo serverInfo;
064        private Kernel kernel;
065        private AbstractName abstractName;
066        private URI directoryPath;
067        private File dir;
068        private Properties requestStatus;
069    
070        /**
071         * Constructor
072         */
073        public FileCertificateRequestStore(ServerInfo serverInfo, URI directoryPath, Kernel kernel, AbstractName abstractName) {
074            this.serverInfo = serverInfo;
075            this.kernel = kernel;
076            this.abstractName = abstractName;
077            this.directoryPath = directoryPath;
078        }
079    
080        /**
081         * This method deletes a certificate request with the specified id.
082         * @param id Id of the certificate request to be deleted.
083         * @return True if the request is deleted succssfully
084         */
085        public boolean deleteRequest(String id) {
086            if(requestStatus.containsKey(id)) {
087                requestStatus.remove(id);
088                storeRequestStatusFile();
089            }
090            return new File(dir, id+CERT_REQ_FILE_SUFFIX).delete();
091        }
092    
093        /**
094         * This method returns the ids of all certificate requests in the store.
095         */
096        public String[] getAllRequestIds() {
097            File[] results = dir.listFiles(new FilenameFilter(){
098                                public boolean accept(File dir, String name) {
099                                    return name.endsWith(CERT_REQ_FILE_SUFFIX);
100                                }});
101            String[] reqIds = new String[results.length];
102            int suffixLength = CERT_REQ_FILE_SUFFIX.length();
103            for(int i = 0; i < results.length; ++i) {
104                String name = results[i].getName();
105                reqIds[i] = name.substring(0, name.length() - suffixLength);
106            }
107            return reqIds;
108        }
109    
110        /**
111         * This method returns the ids of all certificate requests with verification due.
112         */
113        public String[] getVerificatonDueRequestIds() {
114            ArrayList ids = new ArrayList();
115            for(Iterator itr = requestStatus.entrySet().iterator(); itr.hasNext();) {
116                Map.Entry entry = (Map.Entry) itr.next();
117                if(entry.getValue().equals(STATUS_RECEIVED)) {
118                    ids.add(entry.getKey());
119                }
120            }
121            
122            return (String[]) ids.toArray(new String[0]);
123        }
124    
125        /**
126         * This method returns the ids of all certificate requests that are verified.
127         */
128        public String[] getVerifiedRequestIds() {
129            ArrayList ids = new ArrayList();
130            for(Iterator itr = requestStatus.entrySet().iterator(); itr.hasNext();) {
131                Map.Entry entry = (Map.Entry) itr.next();
132                if(entry.getValue().equals(STATUS_VERIFIED)) {
133                    ids.add(entry.getKey());
134                }
135            }
136            
137            return (String[]) ids.toArray(new String[0]);
138        }
139        
140        /**
141         * This method sets the status of the specifed certificate request as verified.
142         * @param id Id of the certificate request
143         * @return True if the status is set successfully.
144         */
145        public boolean setRequestVerified(String id) {
146            if(requestStatus.containsKey(id)) {
147                requestStatus.setProperty(id, STATUS_VERIFIED);
148                storeRequestStatusFile();
149                return true;
150            } else {
151                return false;
152            }
153        }
154        
155        /**
156         * This method sets the status of a certificate request as fulfilled.
157         * @param id Id of the certificate request
158         * @param sNo Serial number of the certificate issued against the certificate request.
159         * @return True if the operation is successfull.
160         */
161        public boolean setRequestFulfilled(String id, BigInteger sNo) {
162            if(requestStatus.containsKey(id)) {
163                deleteRequest(id);
164                requestStatus.setProperty(id, sNo.toString());
165                storeRequestStatusFile();
166                return true;
167            } else {
168                return false;
169            }
170        }
171    
172        /**
173         * This method returns the certificate request text corresponding to a specified id.
174         * @param id Id of the certificate request.
175         */
176        public String getRequest(String id) {
177            try {
178                FileInputStream fin = new FileInputStream(new File(dir, id+CERT_REQ_FILE_SUFFIX));
179                byte[] data = new byte[fin.available()];
180                fin.read(data);
181                fin.close();
182                return new String(data);
183            } catch (Exception e) {
184                log.error("Error reading CSR. id = "+id, e);
185            }
186            return null;
187        }
188    
189        /**
190         * This method stores the given certificate request under the given id.  If a request with the id
191         * exists in the store, it will generate a new id and store the request under that id.
192         * @param id Id under which the certificate request is to be stored
193         * @param csrText Certificate Request text
194         * @return Id under which the certificate request is stored
195         */
196        public String storeRequest(String id, String csr) {
197            try {
198                File csrFile = null;
199                if(id == null || new File(dir, id+CERT_REQ_FILE_SUFFIX).exists()) {
200                    csrFile = File.createTempFile(CERT_REQ_FILE_PREFIX, CERT_REQ_FILE_SUFFIX, dir);
201                    id = csrFile.getName().substring(0, csrFile.getName().length() - CERT_REQ_FILE_SUFFIX.length());
202                } else {
203                    csrFile = new File(dir, id+CERT_REQ_FILE_SUFFIX);
204                }
205                FileOutputStream fout = new FileOutputStream(csrFile);
206                fout.write(csr.getBytes());
207                requestStatus.setProperty(id, STATUS_RECEIVED);
208                storeRequestStatusFile();
209                fout.close();
210                return id;
211            } catch(Exception e) {
212                log.error("Error storing CSR. id = "+id, e);
213            }
214            return null;
215        }
216        
217        /**
218         * This method returns the Serial number of the certificate issued against the certificate request
219         * specified by the given id.
220         * @param id Id of the certificate request
221         * @return Serial number of the certificate issued.
222         * @return null if there is no such certificate request or the certificate request is not fulfilled.
223         */
224        public BigInteger getSerialNumberForRequest(String id) {
225            BigInteger sNo = null;
226            try {
227                sNo = new BigInteger(requestStatus.getProperty(id));
228            } catch(NumberFormatException e) {
229                // happens if the certificate request is not fulfilled
230            }
231            return sNo;
232        }
233    
234        /**
235         * This method removes the certificate request id from the status list.
236         * @param id Id of the certificate request to be removed.
237         * @param sNo Serial number of certificate issued against the certificate request whose Id is to be removed.
238         */
239        public void removeRequestStatus(String id, BigInteger sNo) {
240            if(id != null && requestStatus.containsKey(id)) {
241                requestStatus.remove(id);
242                storeRequestStatusFile();
243            } else if(sNo != null && requestStatus.containsValue(sNo.toString())) {
244                String sNoTemp = sNo.toString();
245                for(Iterator itr = requestStatus.entrySet().iterator(); itr.hasNext(); ) {
246                    Map.Entry entry = (Map.Entry)itr.next();
247                    if(sNoTemp.equals(entry.getValue())) {
248                        requestStatus.remove(entry.getKey());
249                        break;
250                    }
251                }
252                storeRequestStatusFile();
253            }
254        }
255    
256        public void doFail() {
257        }
258    
259        public void doStart() throws Exception {
260            serverInfo.resolveServer(directoryPath);
261            URI dirURI;
262            if (serverInfo != null) {
263                dirURI = serverInfo.resolve(directoryPath);
264            } else {
265                dirURI = directoryPath;
266            }
267            if (!dirURI.getScheme().equals("file")) {
268                throw new IllegalStateException("FileCertificateRequestStore must have a root that's a local directory (not " + dirURI + ")");
269            }
270            dir = new File(dirURI);
271            if(!dir.exists()) {
272                dir.mkdirs();
273                log.debug("Created directory "+dir.getAbsolutePath());
274            } else if(!dir.isDirectory() || !dir.canRead()) {
275                throw new IllegalStateException("FileCertificateRequestStore must have a root that's a valid readable directory (not " + dir.getAbsolutePath() + ")");
276            }
277            log.debug("CertificateRequestStore directory is " + dir.getAbsolutePath());
278            File statusFile = new File(dir, CSR_STATUS_FILENAME);
279            if(!statusFile.exists()) {
280                statusFile.createNewFile();
281                log.debug("Created request status file "+statusFile.getAbsolutePath());
282            }
283            requestStatus = new Properties();
284            FileInputStream fin = new FileInputStream(statusFile);
285            requestStatus.load(fin);
286            fin.close();
287        }
288    
289        public void doStop() throws Exception {
290        }
291        public static final GBeanInfo GBEAN_INFO;
292    
293        static {
294            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(FileCertificateRequestStore.class, "CertificateRequestStore");
295            infoFactory.addAttribute("directoryPath", URI.class, true, false);
296            infoFactory.addAttribute("kernel", Kernel.class, false);
297            infoFactory.addAttribute("abstractName", AbstractName.class, false);
298            infoFactory.addReference("ServerInfo", ServerInfo.class, NameFactory.GERONIMO_SERVICE);
299            infoFactory.addInterface(CertificateRequestStore.class);
300            infoFactory.setConstructor(new String[]{"ServerInfo", "directoryPath", "kernel", "abstractName"});
301    
302            GBEAN_INFO = infoFactory.getBeanInfo();
303        }
304        
305        public static GBeanInfo getGBeanInfo() {
306            return GBEAN_INFO;
307        }
308    
309        /**
310         * This methods stores the certificate request status file to disk.
311         */
312        private void storeRequestStatusFile() {
313            File statusFile = new File(dir, CSR_STATUS_FILENAME);
314            FileOutputStream fout = null;
315            try {
316                fout = new FileOutputStream(statusFile);
317                requestStatus.store(fout, CSR_STATUS_FILE_HEADER);
318                fout.close();
319            } catch (Exception e) {
320                log.error("Errors while storing request status file "+statusFile.getAbsolutePath(), e);
321            }
322        }
323    }