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