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.crypto.CaUtils;
049 import org.apache.geronimo.crypto.asn1.ASN1InputStream;
050 import org.apache.geronimo.crypto.asn1.DERBitString;
051 import org.apache.geronimo.crypto.asn1.DEREncodableVector;
052 import org.apache.geronimo.crypto.asn1.DERInteger;
053 import org.apache.geronimo.crypto.asn1.DERObject;
054 import org.apache.geronimo.crypto.asn1.DERSequence;
055 import org.apache.geronimo.crypto.asn1.pkcs.PKCSObjectIdentifiers;
056 import org.apache.geronimo.crypto.asn1.x509.AlgorithmIdentifier;
057 import org.apache.geronimo.crypto.asn1.x509.SubjectPublicKeyInfo;
058 import org.apache.geronimo.crypto.asn1.x509.TBSCertificateStructure;
059 import org.apache.geronimo.crypto.asn1.x509.Time;
060 import org.apache.geronimo.crypto.asn1.x509.V3TBSCertificateGenerator;
061 import org.apache.geronimo.crypto.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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
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 }