001: package org.bouncycastle.openssl;
002:
003: import org.bouncycastle.asn1.ASN1InputStream;
004: import org.bouncycastle.asn1.ASN1Object;
005: import org.bouncycastle.asn1.ASN1Sequence;
006: import org.bouncycastle.asn1.DERInteger;
007: import org.bouncycastle.asn1.DERObjectIdentifier;
008: import org.bouncycastle.asn1.cms.ContentInfo;
009: import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
010: import org.bouncycastle.asn1.sec.ECPrivateKeyStructure;
011: import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
012: import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
013: import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
014: import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
015: import org.bouncycastle.jce.ECNamedCurveTable;
016: import org.bouncycastle.jce.PKCS10CertificationRequest;
017: import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
018: import org.bouncycastle.util.encoders.Base64;
019: import org.bouncycastle.util.encoders.Hex;
020: import org.bouncycastle.x509.X509AttributeCertificate;
021: import org.bouncycastle.x509.X509V2AttributeCertificate;
022:
023: import java.io.BufferedReader;
024: import java.io.ByteArrayInputStream;
025: import java.io.ByteArrayOutputStream;
026: import java.io.IOException;
027: import java.io.Reader;
028: import java.security.KeyFactory;
029: import java.security.KeyPair;
030: import java.security.NoSuchAlgorithmException;
031: import java.security.NoSuchProviderException;
032: import java.security.PublicKey;
033: import java.security.cert.CertificateFactory;
034: import java.security.cert.X509CRL;
035: import java.security.cert.X509Certificate;
036: import java.security.spec.DSAPrivateKeySpec;
037: import java.security.spec.DSAPublicKeySpec;
038: import java.security.spec.InvalidKeySpecException;
039: import java.security.spec.KeySpec;
040: import java.security.spec.PKCS8EncodedKeySpec;
041: import java.security.spec.RSAPrivateCrtKeySpec;
042: import java.security.spec.RSAPublicKeySpec;
043: import java.security.spec.X509EncodedKeySpec;
044: import java.util.StringTokenizer;
045:
046: /**
047: * Class for reading OpenSSL PEM encoded streams containing
048: * X509 certificates, PKCS8 encoded keys and PKCS7 objects.
049: * <p>
050: * In the case of PKCS7 objects the reader will return a CMS ContentInfo object. Keys and
051: * Certificates will be returned using the appropriate java.security type.
052: */
053: public class PEMReader extends BufferedReader {
054: private final PasswordFinder pFinder;
055: private final String provider;
056:
057: /**
058: * Create a new PEMReader
059: *
060: * @param reader the Reader
061: */
062: public PEMReader(Reader reader) {
063: this (reader, null, "BC");
064: }
065:
066: /**
067: * Create a new PEMReader with a password finder
068: *
069: * @param reader the Reader
070: * @param pFinder the password finder
071: */
072: public PEMReader(Reader reader, PasswordFinder pFinder) {
073: this (reader, pFinder, "BC");
074: }
075:
076: /**
077: * Create a new PEMReader with a password finder
078: *
079: * @param reader the Reader
080: * @param pFinder the password finder
081: * @param provider the cryptography provider to use
082: */
083: public PEMReader(Reader reader, PasswordFinder pFinder,
084: String provider) {
085: super (reader);
086:
087: this .pFinder = pFinder;
088: this .provider = provider;
089: }
090:
091: public Object readObject() throws IOException {
092: String line;
093:
094: while ((line = readLine()) != null) {
095: if (line.indexOf("-----BEGIN PUBLIC KEY") != -1) {
096: return readPublicKey("-----END PUBLIC KEY");
097: }
098: if (line.indexOf("-----BEGIN RSA PUBLIC KEY") != -1) {
099: return readRSAPublicKey("-----END RSA PUBLIC KEY");
100: }
101: if (line.indexOf("-----BEGIN CERTIFICATE REQUEST") != -1) {
102: return readCertificateRequest("-----END CERTIFICATE REQUEST");
103: }
104: if (line.indexOf("-----BEGIN NEW CERTIFICATE REQUEST") != -1) {
105: return readCertificateRequest("-----END NEW CERTIFICATE REQUEST");
106: }
107: if (line.indexOf("-----BEGIN CERTIFICATE") != -1) {
108: return readCertificate("-----END CERTIFICATE");
109: }
110: if (line.indexOf("-----BEGIN PKCS7") != -1) {
111: return readPKCS7("-----END PKCS7");
112: }
113: if (line.indexOf("-----BEGIN X509 CERTIFICATE") != -1) {
114: return readCertificate("-----END X509 CERTIFICATE");
115: }
116: if (line.indexOf("-----BEGIN X509 CRL") != -1) {
117: return readCRL("-----END X509 CRL");
118: }
119: if (line.indexOf("-----BEGIN ATTRIBUTE CERTIFICATE") != -1) {
120: return readAttributeCertificate("-----END ATTRIBUTE CERTIFICATE");
121: }
122: if (line.indexOf("-----BEGIN RSA PRIVATE KEY") != -1) {
123: try {
124: return readKeyPair("RSA",
125: "-----END RSA PRIVATE KEY");
126: } catch (Exception e) {
127: throw new IOException(
128: "problem creating RSA private key: "
129: + e.toString());
130: }
131: }
132: if (line.indexOf("-----BEGIN DSA PRIVATE KEY") != -1) {
133: try {
134: return readKeyPair("DSA",
135: "-----END DSA PRIVATE KEY");
136: } catch (Exception e) {
137: throw new IOException(
138: "problem creating DSA private key: "
139: + e.toString());
140: }
141: }
142: if (line.indexOf("-----BEGIN EC PARAMETERS-----") != -1) {
143: return readECParameters("-----END EC PARAMETERS-----");
144: }
145: if (line.indexOf("-----BEGIN EC PRIVATE KEY-----") != -1) {
146: return readECPrivateKey("-----END EC PRIVATE KEY-----");
147: }
148: }
149:
150: return null;
151: }
152:
153: private byte[] readBytes(String endMarker) throws IOException {
154: String line;
155: StringBuffer buf = new StringBuffer();
156:
157: while ((line = readLine()) != null) {
158: if (line.indexOf(endMarker) != -1) {
159: break;
160: }
161: buf.append(line.trim());
162: }
163:
164: if (line == null) {
165: throw new IOException(endMarker + " not found");
166: }
167:
168: return Base64.decode(buf.toString());
169: }
170:
171: private PublicKey readRSAPublicKey(String endMarker)
172: throws IOException {
173: ByteArrayInputStream bAIS = new ByteArrayInputStream(
174: readBytes(endMarker));
175: ASN1InputStream ais = new ASN1InputStream(bAIS);
176: Object asnObject = ais.readObject();
177: ASN1Sequence sequence = (ASN1Sequence) asnObject;
178: RSAPublicKeyStructure rsaPubStructure = new RSAPublicKeyStructure(
179: sequence);
180: RSAPublicKeySpec keySpec = new RSAPublicKeySpec(rsaPubStructure
181: .getModulus(), rsaPubStructure.getPublicExponent());
182:
183: try {
184: KeyFactory keyFact = KeyFactory
185: .getInstance("RSA", provider);
186:
187: return keyFact.generatePublic(keySpec);
188: } catch (NoSuchProviderException e) {
189: throw new IOException("can't find provider " + provider);
190: } catch (Exception e) {
191: throw new IOException("problem extracting key: "
192: + e.toString());
193: }
194: }
195:
196: private PublicKey readPublicKey(String endMarker)
197: throws IOException {
198: KeySpec keySpec = new X509EncodedKeySpec(readBytes(endMarker));
199: String[] algorithms = { "DSA", "RSA" };
200: for (int i = 0; i < algorithms.length; i++) {
201: try {
202: KeyFactory keyFact = KeyFactory.getInstance(
203: algorithms[i], provider);
204: PublicKey pubKey = keyFact.generatePublic(keySpec);
205:
206: return pubKey;
207: } catch (NoSuchAlgorithmException e) {
208: // ignore
209: } catch (InvalidKeySpecException e) {
210: // ignore
211: } catch (NoSuchProviderException e) {
212: throw new RuntimeException("can't find provider "
213: + provider);
214: }
215: }
216:
217: return null;
218: }
219:
220: /**
221: * Reads in a X509Certificate.
222: *
223: * @return the X509Certificate
224: * @throws IOException if an I/O error occured
225: */
226: private X509Certificate readCertificate(String endMarker)
227: throws IOException {
228: ByteArrayInputStream bIn = new ByteArrayInputStream(
229: readBytes(endMarker));
230:
231: try {
232: CertificateFactory certFact = CertificateFactory
233: .getInstance("X.509", provider);
234:
235: return (X509Certificate) certFact.generateCertificate(bIn);
236: } catch (Exception e) {
237: throw new IOException("problem parsing cert: "
238: + e.toString());
239: }
240: }
241:
242: /**
243: * Reads in a X509CRL.
244: *
245: * @return the X509Certificate
246: * @throws IOException if an I/O error occured
247: */
248: private X509CRL readCRL(String endMarker) throws IOException {
249: ByteArrayInputStream bIn = new ByteArrayInputStream(
250: readBytes(endMarker));
251:
252: try {
253: CertificateFactory certFact = CertificateFactory
254: .getInstance("X.509", provider);
255:
256: return (X509CRL) certFact.generateCRL(bIn);
257: } catch (Exception e) {
258: throw new IOException("problem parsing cert: "
259: + e.toString());
260: }
261: }
262:
263: /**
264: * Reads in a PKCS10 certification request.
265: *
266: * @return the certificate request.
267: * @throws IOException if an I/O error occured
268: */
269: private PKCS10CertificationRequest readCertificateRequest(
270: String endMarker) throws IOException {
271: try {
272: return new PKCS10CertificationRequest(readBytes(endMarker));
273: } catch (Exception e) {
274: throw new IOException("problem parsing cert: "
275: + e.toString());
276: }
277: }
278:
279: /**
280: * Reads in a X509 Attribute Certificate.
281: *
282: * @return the X509 Attribute Certificate
283: * @throws IOException if an I/O error occured
284: */
285: private X509AttributeCertificate readAttributeCertificate(
286: String endMarker) throws IOException {
287: return new X509V2AttributeCertificate(readBytes(endMarker));
288: }
289:
290: /**
291: * Reads in a PKCS7 object. This returns a ContentInfo object suitable for use with the CMS
292: * API.
293: *
294: * @return the X509Certificate
295: * @throws IOException if an I/O error occured
296: */
297: private ContentInfo readPKCS7(String endMarker) throws IOException {
298: String line;
299: StringBuffer buf = new StringBuffer();
300: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
301:
302: while ((line = readLine()) != null) {
303: if (line.indexOf(endMarker) != -1) {
304: break;
305: }
306:
307: line = line.trim();
308:
309: buf.append(line.trim());
310:
311: Base64.decode(buf.substring(0, (buf.length() / 4) * 4),
312: bOut);
313:
314: buf.delete(0, (buf.length() / 4) * 4);
315: }
316:
317: if (buf.length() != 0) {
318: throw new RuntimeException(
319: "base64 data appears to be truncated");
320: }
321:
322: if (line == null) {
323: throw new IOException(endMarker + " not found");
324: }
325:
326: ByteArrayInputStream bIn = new ByteArrayInputStream(bOut
327: .toByteArray());
328:
329: try {
330: ASN1InputStream aIn = new ASN1InputStream(bIn);
331:
332: return ContentInfo.getInstance(aIn.readObject());
333: } catch (Exception e) {
334: throw new IOException("problem parsing PKCS7 object: "
335: + e.toString());
336: }
337: }
338:
339: /**
340: * Read a Key Pair
341: */
342: private KeyPair readKeyPair(String type, String endMarker)
343: throws Exception {
344: boolean isEncrypted = false;
345: String line = null;
346: String dekInfo = null;
347: StringBuffer buf = new StringBuffer();
348:
349: while ((line = readLine()) != null) {
350: if (line.startsWith("Proc-Type: 4,ENCRYPTED")) {
351: isEncrypted = true;
352: } else if (line.startsWith("DEK-Info:")) {
353: dekInfo = line.substring(10);
354: } else if (line.indexOf(endMarker) != -1) {
355: break;
356: } else {
357: buf.append(line.trim());
358: }
359: }
360:
361: //
362: // extract the key
363: //
364: byte[] keyBytes = Base64.decode(buf.toString());
365:
366: if (isEncrypted) {
367: if (pFinder == null) {
368: throw new IOException(
369: "No password finder specified, but a password is required");
370: }
371:
372: char[] password = pFinder.getPassword();
373:
374: if (password == null) {
375: throw new IOException(
376: "Password is null, but a password is required");
377: }
378:
379: StringTokenizer tknz = new StringTokenizer(dekInfo, ",");
380: String dekAlgName = tknz.nextToken();
381: byte[] iv = Hex.decode(tknz.nextToken());
382:
383: keyBytes = PEMUtilities.crypt(false, provider, keyBytes,
384: password, dekAlgName, iv);
385: }
386:
387: KeySpec pubSpec, privSpec;
388: ByteArrayInputStream bIn = new ByteArrayInputStream(keyBytes);
389: ASN1InputStream aIn = new ASN1InputStream(bIn);
390: ASN1Sequence seq = (ASN1Sequence) aIn.readObject();
391:
392: if (type.equals("RSA")) {
393: // DERInteger v = (DERInteger)seq.getObjectAt(0);
394: DERInteger mod = (DERInteger) seq.getObjectAt(1);
395: DERInteger pubExp = (DERInteger) seq.getObjectAt(2);
396: DERInteger privExp = (DERInteger) seq.getObjectAt(3);
397: DERInteger p1 = (DERInteger) seq.getObjectAt(4);
398: DERInteger p2 = (DERInteger) seq.getObjectAt(5);
399: DERInteger exp1 = (DERInteger) seq.getObjectAt(6);
400: DERInteger exp2 = (DERInteger) seq.getObjectAt(7);
401: DERInteger crtCoef = (DERInteger) seq.getObjectAt(8);
402:
403: pubSpec = new RSAPublicKeySpec(mod.getValue(), pubExp
404: .getValue());
405: privSpec = new RSAPrivateCrtKeySpec(mod.getValue(), pubExp
406: .getValue(), privExp.getValue(), p1.getValue(), p2
407: .getValue(), exp1.getValue(), exp2.getValue(),
408: crtCoef.getValue());
409: } else // "DSA"
410: {
411: // DERInteger v = (DERInteger)seq.getObjectAt(0);
412: DERInteger p = (DERInteger) seq.getObjectAt(1);
413: DERInteger q = (DERInteger) seq.getObjectAt(2);
414: DERInteger g = (DERInteger) seq.getObjectAt(3);
415: DERInteger y = (DERInteger) seq.getObjectAt(4);
416: DERInteger x = (DERInteger) seq.getObjectAt(5);
417:
418: privSpec = new DSAPrivateKeySpec(x.getValue(),
419: p.getValue(), q.getValue(), g.getValue());
420: pubSpec = new DSAPublicKeySpec(y.getValue(), p.getValue(),
421: q.getValue(), g.getValue());
422: }
423:
424: KeyFactory fact = KeyFactory.getInstance(type, provider);
425:
426: return new KeyPair(fact.generatePublic(pubSpec), fact
427: .generatePrivate(privSpec));
428: }
429:
430: private ECNamedCurveParameterSpec readECParameters(String endMarker)
431: throws IOException {
432: DERObjectIdentifier oid = (DERObjectIdentifier) ASN1Object
433: .fromByteArray(readBytes(endMarker));
434:
435: return ECNamedCurveTable.getParameterSpec(oid.getId());
436: }
437:
438: private KeyPair readECPrivateKey(String endMarker)
439: throws IOException {
440: try {
441: ECPrivateKeyStructure pKey = new ECPrivateKeyStructure(
442: (ASN1Sequence) ASN1Object
443: .fromByteArray(readBytes(endMarker)));
444: AlgorithmIdentifier algId = new AlgorithmIdentifier(
445: X9ObjectIdentifiers.id_ecPublicKey, pKey
446: .getParameters());
447: PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey
448: .getDERObject());
449: SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
450: algId, pKey.getPublicKey().getBytes());
451: PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(
452: privInfo.getEncoded());
453: X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(pubInfo
454: .getEncoded());
455: KeyFactory fact = KeyFactory.getInstance("ECDSA", provider);
456:
457: return new KeyPair(fact.generatePublic(pubSpec), fact
458: .generatePrivate(privSpec));
459: } catch (ClassCastException e) {
460: throw new IOException("wrong ASN.1 object found in stream");
461: } catch (Exception e) {
462: throw new IOException("problem parsing EC private key: "
463: + e);
464: }
465: }
466: }
|