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    }