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 }