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 }