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.util;
019    
020    import java.io.BufferedReader;
021    import java.io.ByteArrayInputStream;
022    import java.io.ByteArrayOutputStream;
023    import java.io.FileOutputStream;
024    import java.io.IOException;
025    import java.io.InputStreamReader;
026    import java.io.OutputStream;
027    import java.io.PrintWriter;
028    import java.security.InvalidKeyException;
029    import java.security.KeyFactory;
030    import java.security.NoSuchAlgorithmException;
031    import java.security.NoSuchProviderException;
032    import java.security.PublicKey;
033    import java.security.Signature;
034    import java.security.SignatureException;
035    import java.security.cert.Certificate;
036    import java.security.cert.CertificateEncodingException;
037    import java.security.spec.RSAPublicKeySpec;
038    import java.util.HashMap;
039    import java.util.Hashtable;
040    import java.util.Map;
041    import java.util.Vector;
042    
043    import javax.security.auth.x500.X500Principal;
044    
045    import org.apache.commons.logging.Log;
046    import org.apache.commons.logging.LogFactory;
047    import org.apache.geronimo.util.asn1.ASN1InputStream;
048    import org.apache.geronimo.util.asn1.ASN1Sequence;
049    import org.apache.geronimo.util.asn1.DERBitString;
050    import org.apache.geronimo.util.asn1.DERObject;
051    import org.apache.geronimo.util.asn1.DERSequence;
052    import org.apache.geronimo.util.asn1.DERString;
053    import org.apache.geronimo.util.asn1.pkcs.CertificationRequestInfo;
054    import org.apache.geronimo.util.asn1.pkcs.PKCSObjectIdentifiers;
055    import org.apache.geronimo.util.asn1.x509.RSAPublicKeyStructure;
056    import org.apache.geronimo.util.asn1.x509.SubjectPublicKeyInfo;
057    import org.apache.geronimo.util.asn1.x509.X509CertificateStructure;
058    import org.apache.geronimo.util.asn1.x509.X509Name;
059    import org.apache.geronimo.util.encoders.Base64;
060    import org.apache.geronimo.util.jce.PKCS10CertificationRequest;
061    
062    /**
063     * This class implements some utility methods used by CA
064     *
065     * @version $Rev: 582981 $ $Date: 2007-10-08 17:31:46 -0400 (Mon, 08 Oct 2007) $
066     */
067    public class CaUtils {
068        private static final Log log = LogFactory.getLog(CaUtils.class);
069        public static final String CERT_HEADER = "-----BEGIN CERTIFICATE-----";
070        public static final String CERT_FOOTER = "-----END CERTIFICATE-----";
071        public static final String CERT_REQ_HEADER = "-----BEGIN CERTIFICATE REQUEST-----";
072        public static final String CERT_REQ_FOOTER = "-----END CERTIFICATE REQUEST-----";
073        public static final int B64_LINE_SIZE = 76;
074        public static final String CERT_REQ_SUBJECT = "subject";
075        public static final String CERT_REQ_PUBLICKEY = "publickey";
076        public static final String CERT_REQ_PUBLICKEY_OBJ = "publickeyObj";
077        public static final String CERT_REQ_VERSION = "version";
078        public static final String PKAC_CHALLENGE = "challenge";
079    
080        /**
081         * This method returns base64 encoded text of a given certificate.
082         * @param cert The certificate that needs to be encoded in base64
083         */
084        public static String base64Certificate(Certificate cert) throws CertificateEncodingException, Exception {
085            return base64Text(cert.getEncoded(), CaUtils.CERT_HEADER, CaUtils.CERT_FOOTER, CaUtils.B64_LINE_SIZE);
086        }
087        
088        /**
089         * This method encodes a given byte array into base64 along with specified header and footers.
090         * @param data The byte array to be encoded in base64
091         * @param header Header for base64 encoded text
092         * @param footer Footer for base64 encoded text
093         * @param lineSize Maximum line size to split base64 encoded text if required
094         */
095        public static String base64Text(byte[] data, String header, String footer, int lineSize) throws Exception {
096            ByteArrayOutputStream bout = new ByteArrayOutputStream();
097            storeInBase64(bout, data, header, footer, lineSize);
098            bout.close();
099            return bout.toString();
100        }
101        /**
102         * This method encodes a given byte array into base64 along with specified header and footers and writes
103         * the output to a specified OutputStream.
104         * @param fout Output stream to write the encoded text
105         * @param data The byte array to be encoded in base64
106         * @param header Header for base64 encoded text
107         * @param footer Footer for base64 encoded text
108         * @param lineSize Maximum line size to split base64 encoded text if required
109         */
110        public static void storeInBase64(OutputStream fout, byte[] data, String header, String footer, int lineSize) throws Exception {
111            PrintWriter out = new PrintWriter(fout);
112            if(header != null) out.println(header);
113    
114            byte[] encodedData = Base64.encode(data);
115            int i = 0;
116            do {
117                out.println(new String(encodedData, i, Math.min(lineSize, encodedData.length-i)));
118                i += lineSize;
119            } while(i < encodedData.length);
120    
121            if(footer != null) out.println(footer);
122            out.flush();
123        }
124    
125        /**
126         * This method encodes a given byte array into base64 along with specified header and footers and writes
127         * the output to a specified file.
128         * @param outfile File name to write the output to
129         * @param data The byte array to be encoded in base64
130         * @param header Header for base64 encoded text
131         * @param footer Footer for base64 encoded text
132         * @param lineSize Maximum line size to split base64 encoded text if required
133         */
134        public static void storeInBase64(String outfile, byte[] data, String header, String footer, int lineSize) throws Exception {
135            FileOutputStream fout = new FileOutputStream(outfile);
136            storeInBase64(fout, data, header, footer, lineSize);
137            fout.close();
138        }
139    
140        /**
141         * This method creates a java.security.PublicKey object based on the public key information given in SubjectPublicKeyInfo
142         * @param pubKeyInfo SubjectPublicKeyInfo instance containing the public key information.
143         */
144        public static PublicKey getPublicKeyObject(SubjectPublicKeyInfo pubKeyInfo) throws Exception{
145            RSAPublicKeyStructure pubkeyStruct = new RSAPublicKeyStructure((ASN1Sequence)pubKeyInfo.getPublicKey());
146            RSAPublicKeySpec pubkeySpec = new RSAPublicKeySpec(pubkeyStruct.getModulus(), pubkeyStruct.getPublicExponent());
147            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
148            PublicKey pubKey = keyFactory.generatePublic(pubkeySpec);
149            return pubKey;
150        }
151        
152        /**
153         * This method returns a X509Name object corresponding to the subject in a given certificate
154         * @param cert Certificate from which subject needs to be retrieved
155         */
156        public static X509Name getSubjectX509Name(Certificate cert) throws CertificateEncodingException, IOException {
157            ASN1InputStream ais = new ASN1InputStream(cert.getEncoded());
158            X509CertificateStructure x509Struct = new X509CertificateStructure((ASN1Sequence)ais.readObject());
159            ais.close();
160            return x509Struct.getSubject();
161        }
162    
163        /**
164         * This method returns a X509Name object corresponding to a given principal
165         */
166        public static X509Name getX509Name(X500Principal principal) throws CertificateEncodingException, IOException {
167            ASN1InputStream ais = new ASN1InputStream(principal.getEncoded());
168            X509Name name = new X509Name((ASN1Sequence)ais.readObject());
169            ais.close();
170            return name;
171        }
172    
173        /**
174         * This method processes a certificate request and returns a map containing subject
175         * and public key in the request.
176         * @param certreq base64 encoded PKCS10 certificate request
177         */
178        public static Map processPKCS10Request(String certreq) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException, Exception {
179            if(certreq.indexOf("-----") != -1) {
180                // Strip any header and footer
181                BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(certreq.getBytes())));
182                String line = null;
183                String b64data = "";
184                while((line = br.readLine()) != null) {
185                    if(!line.startsWith("-----")) {
186                        b64data += line;
187                    }
188                }
189                br.close();
190                certreq = b64data;
191            }
192            byte[] data = Base64.decode(certreq);
193            
194            PKCS10CertificationRequest pkcs10certreq = new PKCS10CertificationRequest(data);
195            if(!pkcs10certreq.verify()) {
196                throw new Exception("CSR verification failed.");
197            }
198            CertificationRequestInfo certReqInfo = pkcs10certreq.getCertificationRequestInfo();
199            Map map = new HashMap();
200            map.put(CERT_REQ_SUBJECT, certReqInfo.getSubject());
201            map.put(CERT_REQ_PUBLICKEY, certReqInfo.getSubjectPublicKeyInfo());
202            map.put(CERT_REQ_PUBLICKEY_OBJ, getPublicKeyObject(certReqInfo.getSubjectPublicKeyInfo()));
203            map.put(CERT_REQ_VERSION, certReqInfo.getVersion());
204            return map;
205        }
206        
207        /**
208         * This method processes a DER encoded SignedPublicKeyAndChallenge in base64 format.
209         * @param spkac SignedPublicKeyAndChallenge in base64 text format
210         * @return a Map with Subject, public-key and challenge 
211         */
212        public static Map processSPKAC(String spkac) throws IOException, NoSuchAlgorithmException, InvalidKeyException, SignatureException, Exception {
213            Map map = new HashMap();
214            byte[]data = Base64.decode(spkac);
215            ASN1InputStream ais = new ASN1InputStream(new ByteArrayInputStream(data));
216            DERSequence spkacSeq = (DERSequence)ais.readObject();
217    
218            // SPKAC = SEQ {PKAC, SIGN-ALG, SIGN}
219            // Get PKAC and obtain PK and C
220            DERSequence pkacSeq = (DERSequence)spkacSeq.getObjectAt(0);
221            DERObject pk = (DERObject)pkacSeq.getObjectAt(0);
222            DERObject ch = (DERObject)pkacSeq.getObjectAt(1);
223            SubjectPublicKeyInfo pkInfo = new SubjectPublicKeyInfo((DERSequence)pk);
224            PublicKey pubKey =  getPublicKeyObject(pkInfo);
225    
226            // Get SIGN-ALG
227            DERSequence signAlg = (DERSequence) spkacSeq.getObjectAt(1);
228            DERObject alg0 = (DERObject)signAlg.getObjectAt(0);
229    
230            // Get SIGN
231            DERBitString sign = (DERBitString) spkacSeq.getObjectAt(2);
232            byte[] signature = sign.getBytes();
233            
234            // Verify the signature on SPKAC
235            String signAlgString = PKCSObjectIdentifiers.md5WithRSAEncryption.equals(alg0) ? "MD5withRSA" :
236                                   PKCSObjectIdentifiers.md2WithRSAEncryption.equals(alg0) ? "MD2withRSA" :
237                                   PKCSObjectIdentifiers.sha1WithRSAEncryption.equals(alg0) ? "SHA1withRSA" : null;
238            Signature signObj = Signature.getInstance(signAlgString);
239            signObj.initVerify(pubKey);
240            signObj.update(pkacSeq.getEncoded());
241            boolean verified = signObj.verify(signature);
242            if(!verified) throw new Exception("SignedPublicKeyAndChallenge verification failed.");
243            map.put(CERT_REQ_PUBLICKEY, pkInfo);
244            map.put(CERT_REQ_PUBLICKEY_OBJ, pubKey);
245            if(((DERString)ch).getString() != null) map.put(PKAC_CHALLENGE, ((DERString)ch).getString());
246            return map;
247        }
248        
249        /**
250         * This method creates a X509Name object using the name attributes specified.
251         * @param cn Common Name
252         * @param ou Organization Unit
253         * @param o Organization
254         * @param l Locality
255         * @param st State
256         * @param c Country
257         */
258        public static X509Name getX509Name(String cn, String ou, String o, String l, String st, String c)  {
259            Vector order = new Vector();
260            Hashtable attrmap = new Hashtable();
261            if (c != null) {
262                attrmap.put(X509Name.C, c);
263                order.add(X509Name.C);
264            }
265    
266            if (st != null) {
267                attrmap.put(X509Name.ST, st);
268                order.add(X509Name.ST);
269            }
270    
271            if (l != null) {
272                attrmap.put(X509Name.L, l);
273                order.add(X509Name.L);
274            }
275    
276            if (o != null) {
277                attrmap.put(X509Name.O, o);
278                order.add(X509Name.O);
279            }
280    
281            if (ou != null) {
282                attrmap.put(X509Name.OU, ou);
283                order.add(X509Name.OU);
284            }
285    
286            if (cn != null) {
287                attrmap.put(X509Name.CN, cn);
288                order.add(X509Name.CN);
289            }
290    
291            return new X509Name(order, attrmap);
292        }
293    }