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 }