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.ByteArrayInputStream; 021 import java.io.IOException; 022 import java.math.BigInteger; 023 import java.security.PrivateKey; 024 import java.security.PublicKey; 025 import java.security.Signature; 026 import java.security.cert.Certificate; 027 import java.security.cert.CertificateFactory; 028 import java.util.Date; 029 030 import javax.security.auth.x500.X500Principal; 031 032 import org.apache.commons.logging.Log; 033 import org.apache.commons.logging.LogFactory; 034 import org.apache.geronimo.gbean.AbstractName; 035 import org.apache.geronimo.gbean.GBeanInfo; 036 import org.apache.geronimo.gbean.GBeanInfoBuilder; 037 import org.apache.geronimo.gbean.GBeanLifecycle; 038 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory; 039 import org.apache.geronimo.kernel.Kernel; 040 import org.apache.geronimo.management.geronimo.CertificateRequestStore; 041 import org.apache.geronimo.management.geronimo.CertificateStore; 042 import org.apache.geronimo.management.geronimo.CertificateStoreException; 043 import org.apache.geronimo.management.geronimo.CertificationAuthority; 044 import org.apache.geronimo.management.geronimo.CertificationAuthorityException; 045 import org.apache.geronimo.management.geronimo.KeystoreException; 046 import org.apache.geronimo.management.geronimo.KeystoreInstance; 047 import org.apache.geronimo.system.serverinfo.ServerInfo; 048 import org.apache.geronimo.util.CaUtils; 049 import org.apache.geronimo.util.asn1.ASN1InputStream; 050 import org.apache.geronimo.util.asn1.DERBitString; 051 import org.apache.geronimo.util.asn1.DEREncodableVector; 052 import org.apache.geronimo.util.asn1.DERInteger; 053 import org.apache.geronimo.util.asn1.DERObject; 054 import org.apache.geronimo.util.asn1.DERSequence; 055 import org.apache.geronimo.util.asn1.pkcs.PKCSObjectIdentifiers; 056 import org.apache.geronimo.util.asn1.x509.AlgorithmIdentifier; 057 import org.apache.geronimo.util.asn1.x509.SubjectPublicKeyInfo; 058 import org.apache.geronimo.util.asn1.x509.TBSCertificateStructure; 059 import org.apache.geronimo.util.asn1.x509.Time; 060 import org.apache.geronimo.util.asn1.x509.V3TBSCertificateGenerator; 061 import org.apache.geronimo.util.asn1.x509.X509Name; 062 063 /** 064 * A Certification Authority implementation using KeystoreInstance to store CA's private key, 065 * CertificateStore to store issued certificates and CertificateRequestStore to store certificate requests 066 * 067 * @version $Rev: 476291 $ $Date: 2006-11-17 15:05:24 -0500 (Fri, 17 Nov 2006) $ 068 */ 069 public class GeronimoCertificationAuthority implements CertificationAuthority, GBeanLifecycle { 070 private final static Log log = LogFactory.getLog(GeronimoCertificationAuthority.class); 071 072 private ServerInfo serverInfo; 073 private Kernel kernel; 074 private AbstractName abstractName; 075 076 // KeystoreInstance with CA's private key and certificate 077 private KeystoreInstance caKeystore = null; 078 // CertificateStore used to store all certificates issued by the CA 079 private CertificateStore certStore = null; 080 // Password for CA's keystore and private-key 081 private char[] password; 082 // CertificateRequestStore used to store certificate requests 083 private CertificateRequestStore certReqStore = null; 084 085 // Cache variables 086 // Key alias 087 private String alias; 088 // CA's private key 089 private PrivateKey caPrivateKey; 090 // CA's public key 091 private PublicKey caPublicKey; 092 // CA's own certificate 093 private Certificate caCert; 094 // CA's name 095 private X509Name caName; 096 097 /** 098 * Constructor 099 * 100 * @param instance KeystoreInstance containing CA's private-key and certificate 101 * @param certStore CertificateStore for storing certificates issued by this CA 102 * @param certReqStore CeetificateRequestStore for storing certificates requests 103 */ 104 public GeronimoCertificationAuthority(ServerInfo serverInfo, KeystoreInstance caKeystore, CertificateStore certStore, CertificateRequestStore certReqStore, Kernel kernel, AbstractName abstractName) { 105 if(caKeystore == null) throw new IllegalArgumentException("caKeystore is null."); 106 if(certStore == null) throw new IllegalArgumentException("certStore is null"); 107 if(certReqStore == null) throw new IllegalArgumentException("certReqStore is null"); 108 this.serverInfo = serverInfo; 109 this.kernel = kernel; 110 this.abstractName = abstractName; 111 this.caKeystore = caKeystore; 112 this.certStore = certStore; 113 this.certReqStore = certReqStore; 114 } 115 116 /** 117 * This method checks if the CA is locked. 118 * @return true if CA is locked, false otherwise. 119 */ 120 public boolean isLocked() { 121 return password == null; 122 } 123 124 /** 125 * This method locks the CA. 126 */ 127 public void lock() { 128 try { 129 caKeystore.lockKeystore(password); 130 } catch (KeystoreException e) { 131 log.error("Error locking CA.", e); 132 } 133 password = null; 134 caName = null; 135 caCert = null; 136 caPrivateKey = null; 137 alias = null; 138 } 139 140 /** 141 * This method unlocks the CA. 142 * @param password Password to unlock the CA. 143 */ 144 public void unlock(char[] password) throws CertificationAuthorityException{ 145 try { 146 this.password = password; 147 caKeystore.unlockKeystore(password); 148 alias = caKeystore.listPrivateKeys(password)[0]; 149 caKeystore.unlockPrivateKey(alias, password, password); 150 caCert = caKeystore.getCertificate(alias, password); 151 caName = CaUtils.getSubjectX509Name(caCert); 152 caPrivateKey = caKeystore.getPrivateKey(alias, password, password); 153 caPublicKey = caCert.getPublicKey(); 154 } catch(Exception e) { 155 throw new CertificationAuthorityException("Errors in unlocking CA.", e); 156 } 157 } 158 159 /** 160 * This method returns CA's name. 161 * @throws Exception if CA is locked. 162 */ 163 public X500Principal getName() throws CertificationAuthorityException { 164 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 165 try { 166 return new X500Principal(caName.getEncoded()); 167 } catch (IOException e) { 168 throw new CertificationAuthorityException("Error in getting CA name.", e); 169 } 170 } 171 172 /** 173 * This method returns CA's own certificate. 174 * @throws Exception if CA is locked. 175 */ 176 public Certificate getCertificate() throws CertificationAuthorityException { 177 if(caCert == null) throw new CertificationAuthorityException("CA Certificate is null. CA may be locked."); 178 try { 179 return caCert = caKeystore.getCertificate(alias, password); 180 } catch (KeystoreException e) { 181 log.error("Error getting CA's certificate.", e); 182 } 183 return null; 184 } 185 186 /** 187 * This method makes the CA issue a self-signed certificate with given details. This method is usually 188 * called while initializing the CA. 189 * 190 * @param sNo Serial number for self-signed certificate 191 * @param validFromDate Certificate validity period start date 192 * @param validToDate Certificate validity period end date 193 * @param algorithm Signature algorithm for self-signed certificate 194 */ 195 public void issueOwnCertificate(BigInteger sNo, Date validFromDate, Date validToDate, String algorithm) throws CertificationAuthorityException{ 196 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 197 try { 198 PublicKey publicKey = caCert.getPublicKey(); 199 Certificate cert = issueCertificate(getName(), publicKey, sNo, validFromDate, validToDate, algorithm); 200 caKeystore.importPKCS7Certificate(alias, CaUtils.base64Certificate(cert), password); 201 caCert = cert; 202 } catch(Exception e) { 203 throw new CertificationAuthorityException("Error in issuing own certificate.", e); 204 } 205 } 206 207 /** 208 * This method issues a certificate. 209 * 210 * @param subject Subject X500Principal 211 * @param publicKey Subject's public key 212 * @param sNo Serial number for the certificate to be issued 213 * @param validFromDate Certificate validity period start date 214 * @param validToDate Certificate validity period end date 215 * @param algorithm Signature algorithm for the certificate 216 * @return newly issued certificate 217 */ 218 public Certificate issueCertificate(X500Principal subject, PublicKey publicKey, BigInteger sNo, Date validFromDate, Date validToDate, String algorithm) throws CertificationAuthorityException{ 219 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 220 try { 221 X509Name subName = CaUtils.getX509Name(subject); 222 Certificate cert = issueCertificate(subName, caName, sNo, publicKey, caPrivateKey, validFromDate, validToDate, algorithm); 223 cert.verify(caPublicKey); 224 certStore.storeCertificate(cert); 225 return cert; 226 } catch(Exception e) { 227 throw new CertificationAuthorityException("Error in issuing certificate.", e); 228 } 229 } 230 231 /** 232 * This method returns the highest serial number used by the CA. 233 */ 234 public BigInteger getHighestSerialNumber() throws CertificationAuthorityException { 235 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 236 try { 237 return certStore.getHighestSerialNumber(); 238 } catch (CertificateStoreException e) { 239 throw new CertificationAuthorityException("Error in getting highest serial number for CA.", e); 240 } 241 } 242 243 /** 244 * This method checks if a Certificate with a given serial number is already issued. 245 * @param sNo The serial number of the the certificate to be looked for 246 * @return true if a certificate with the specified serial number has already been issued 247 */ 248 public boolean isCertificateIssued(BigInteger sNo) throws CertificationAuthorityException { 249 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 250 return certStore.containsCertificate(sNo); 251 } 252 253 /** 254 * This method returns the next serial number that can be used to issue a certificate and increments the 255 * highest serial number. 256 */ 257 public BigInteger getNextSerialNumber() throws CertificationAuthorityException { 258 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 259 try { 260 return certStore.getNextSerialNumber(); 261 } catch (CertificateStoreException e) { 262 throw new CertificationAuthorityException("Error in getting next serial number for CA.", e); 263 } 264 } 265 266 /** 267 * This method retrieves a certificate with the specified serial number. 268 * @param sNo The serial number of the certificate to be retrieved 269 * @return java.security.cert.Certificate instance of the certificate 270 */ 271 public Certificate getCertificate(BigInteger sNo) throws CertificationAuthorityException { 272 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 273 try { 274 return certStore.getCertificate(sNo); 275 } catch (CertificateStoreException e) { 276 throw new CertificationAuthorityException("Error getting certificate. serial number = "+sNo, e); 277 } 278 } 279 280 /** 281 * This method retrieves a certificate with the specified serial number. 282 * @param sNo The serial number of the certificate to be retrieved 283 * @return base64 encoded certificate text 284 */ 285 public String getCertificateBase64Text(BigInteger sNo) throws CertificationAuthorityException { 286 if(isLocked()) throw new CertificationAuthorityException("CA is locked."); 287 try { 288 return certStore.getCertificateBase64Text(sNo); 289 } catch (CertificateStoreException e) { 290 throw new CertificationAuthorityException("Error getting certificate. serial number = "+sNo, e); 291 } 292 } 293 294 /** 295 * This method issues a certificate. 296 * @param subName Subject's name 297 * @param caName Issuer's name 298 * @param serialNum Serial number for the certificate 299 * @param subPubKey Subject's public key 300 * @param caPriKey Issuer's private key 301 * @param validFromDate Certificate validity period start date 302 * @param validToDate Certificate validity period end date 303 * @param algorithm Signature algorithm for the certificate 304 * @return issued certificate 305 */ 306 private Certificate issueCertificate(X509Name subName, X509Name caName, BigInteger serialNum, PublicKey subPubKey, PrivateKey caPriKey, Date validFromDate, Date validToDate, String algorithm) throws Exception { 307 AlgorithmIdentifier algId = null; 308 if("MD2withRSA".equalsIgnoreCase(algorithm)) 309 algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.md2WithRSAEncryption); 310 else if("MD5withRSA".equalsIgnoreCase(algorithm)) 311 algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.md5WithRSAEncryption); 312 else if("SHA1withRSA".equalsIgnoreCase(algorithm)) 313 algId = new AlgorithmIdentifier(PKCSObjectIdentifiers.sha1WithRSAEncryption); 314 else 315 throw new CertificationAuthorityException("Signature algorithm "+algorithm+" is not supported."); 316 317 ASN1InputStream ais = new ASN1InputStream(subPubKey.getEncoded()); 318 DERObject subPubkeyDerObj = ais.readObject(); 319 SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(subPubkeyDerObj); 320 321 // Create certificate generator and initialize fields 322 // Certificate version is v3 323 V3TBSCertificateGenerator v3certGen = new V3TBSCertificateGenerator(); 324 // Subject info 325 v3certGen.setSubject(subName); 326 v3certGen.setSubjectPublicKeyInfo(subPubKeyInfo); 327 // Issuer info 328 v3certGen.setIssuer(caName); 329 // serial number 330 v3certGen.setSerialNumber(new DERInteger(serialNum)); 331 // validity 332 v3certGen.setStartDate(new Time(validFromDate)); 333 v3certGen.setEndDate(new Time(validToDate)); 334 // signature algorithm 335 v3certGen.setSignature(algId); 336 337 // Get the certificate info to be signed 338 TBSCertificateStructure tbsCert = v3certGen.generateTBSCertificate(); 339 byte[] tobesigned = tbsCert.getEncoded(); 340 341 // Create the signature 342 Signature signatureObj = Signature.getInstance(algorithm); 343 signatureObj.initSign(caPriKey); 344 signatureObj.update(tobesigned); 345 byte[] signature = signatureObj.sign(); 346 347 // Compose tbsCert, algId and signature into a DER sequence. 348 // This will be the certificate in DER encoded form 349 DEREncodableVector certDerVec = new DEREncodableVector(); 350 certDerVec.add(tbsCert); 351 certDerVec.add(algId); 352 certDerVec.add(new DERBitString(signature)); 353 DERSequence certDerSeq = new DERSequence(certDerVec); 354 byte[] certData = certDerSeq.getEncoded(); 355 356 // Create a java.security.cert.Certificate object 357 Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(certData)); 358 359 return certificate; 360 } 361 362 public void doFail() { 363 } 364 365 public void doStart() throws Exception { 366 if(caKeystore.isKeystoreLocked()) { 367 lock(); 368 } 369 } 370 371 public void doStop() throws Exception { 372 } 373 public static final GBeanInfo GBEAN_INFO; 374 375 static { 376 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(GeronimoCertificationAuthority.class, "CertificationAuthority"); 377 infoFactory.addAttribute("kernel", Kernel.class, false); 378 infoFactory.addAttribute("abstractName", AbstractName.class, false); 379 infoFactory.addReference("ServerInfo", ServerInfo.class, NameFactory.GERONIMO_SERVICE); 380 infoFactory.addReference("KeystoreInstance", KeystoreInstance.class, NameFactory.KEYSTORE_INSTANCE); 381 infoFactory.addReference("CertificateStore", CertificateStore.class, "CertificateStore"); 382 infoFactory.addReference("CertificateRequestStore", CertificateRequestStore.class, "CertificateRequestStore"); 383 infoFactory.addInterface(CertificationAuthority.class); 384 infoFactory.setConstructor(new String[]{"ServerInfo", "KeystoreInstance", "CertificateStore", "CertificateRequestStore", "kernel", "abstractName"}); 385 386 GBEAN_INFO = infoFactory.getBeanInfo(); 387 } 388 public static GBeanInfo getGBeanInfo() { 389 return GBEAN_INFO; 390 } 391 }