Source Code Cross Referenced for CertificateManager.java in  » Net » openfire » org » jivesoftware » util » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Net » openfire » org.jivesoftware.util 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


001:        /**
002:         * $RCSfile$
003:         * $Revision: $
004:         * $Date: $
005:         *
006:         * Copyright (C) 2006 Jive Software. All rights reserved.
007:         *
008:         * This software is published under the terms of the GNU Public License (GPL),
009:         * a copy of which is included in this distribution.
010:         */package org.jivesoftware.util;
011:
012:        import org.bouncycastle.asn1.*;
013:        import org.bouncycastle.asn1.x509.GeneralName;
014:        import org.bouncycastle.asn1.x509.GeneralNames;
015:        import org.bouncycastle.asn1.x509.X509Extensions;
016:        import org.bouncycastle.asn1.x509.X509Name;
017:        import org.bouncycastle.jce.PKCS10CertificationRequest;
018:        import org.bouncycastle.jce.provider.BouncyCastleProvider;
019:        import org.bouncycastle.openssl.PEMReader;
020:        import org.bouncycastle.openssl.PasswordFinder;
021:        import org.bouncycastle.x509.X509V3CertificateGenerator;
022:
023:        import java.io.*;
024:        import java.math.BigInteger;
025:        import java.security.*;
026:        import java.security.cert.Certificate;
027:        import java.security.cert.CertificateFactory;
028:        import java.security.cert.CertificateParsingException;
029:        import java.security.cert.X509Certificate;
030:        import java.util.*;
031:        import java.util.LinkedList;
032:        import java.util.concurrent.CopyOnWriteArrayList;
033:        import java.util.regex.Matcher;
034:        import java.util.regex.Pattern;
035:
036:        /**
037:         * Utility class that provides similar functionality to the keytool tool. Generated certificates
038:         * conform to the XMPP spec where domains are kept in the subject alternative names extension.
039:         *
040:         * @author Gaston Dombiak
041:         */
042:        public class CertificateManager {
043:
044:            private static Pattern cnPattern = Pattern
045:                    .compile("(?i)(cn=)([^,]*)");
046:            private static Pattern valuesPattern = Pattern
047:                    .compile("(?i)(=)([^,]*)");
048:
049:            private static Provider provider = new BouncyCastleProvider();
050:
051:            /**
052:             * The maximum length of lines in certification requests
053:             */
054:            private static final int CERT_REQ_LINE_LENGTH = 76;
055:
056:            private static List<CertificateEventListener> listeners = new CopyOnWriteArrayList<CertificateEventListener>();
057:
058:            static {
059:                // Add the BC provider to the list of security providers
060:                Security.addProvider(provider);
061:            }
062:
063:            /**
064:             * Creates a new X509 certificate using the DSA algorithm. The new certificate together with its private
065:             * key are stored in the specified key store. However, the key store is not saved to the disk. This means
066:             * that it is up to the "caller" to save the key store to disk after new certificates have been added
067:             * to the store.
068:             *
069:             * @param ksKeys    key store where the new certificate and private key are going to be stored.
070:             * @param keyPassword password of the keystore.
071:             * @param alias     name to use when storing the certificate in the key store.
072:             * @param issuerDN  Issuer string e.g "O=Grid,OU=OGSA,CN=ACME"
073:             * @param subjectDN Subject string e.g "O=Grid,OU=OGSA,CN=John Doe"
074:             * @param domain    domain of the server to store in the subject alternative name extension.
075:             * @return the new X509 V3 Certificate.
076:             * @throws GeneralSecurityException
077:             * @throws IOException
078:             */
079:            public static X509Certificate createDSACert(KeyStore ksKeys,
080:                    String keyPassword, String alias, String issuerDN,
081:                    String subjectDN, String domain)
082:                    throws GeneralSecurityException, IOException {
083:                // Generate public and private keys
084:                KeyPair keyPair = generateKeyPair("DSA", 1024);
085:                // Create X509 certificate with keys and specified domain
086:                X509Certificate cert = createX509V3Certificate(keyPair, 60,
087:                        issuerDN, subjectDN, domain, "SHA1withDSA");
088:                // Store new certificate and private key in the keystore
089:                ksKeys.setKeyEntry(alias, keyPair.getPrivate(), keyPassword
090:                        .toCharArray(), new X509Certificate[] { cert });
091:                // Notify listeners that a new certificate has been created
092:                for (CertificateEventListener listener : listeners) {
093:                    try {
094:                        listener.certificateCreated(ksKeys, alias, cert);
095:                    } catch (Exception e) {
096:                        Log.error(e);
097:                    }
098:                }
099:                // Return new certificate
100:                return cert;
101:            }
102:
103:            /**
104:             * Creates a new X509 certificate using the RSA algorithm. The new certificate together with its private
105:             * key are stored in the specified key store. However, the key store is not saved to the disk. This means
106:             * that it is up to the "caller" to save the key store to disk after new certificates have been added
107:             * to the store.
108:             *
109:             * @param ksKeys    key store where the new certificate and private key are going to be stored.
110:             * @param keyPassword password of the keystore.
111:             * @param alias     name to use when storing the certificate in the key store.
112:             * @param issuerDN  Issuer string e.g "O=Grid,OU=OGSA,CN=ACME"
113:             * @param subjectDN Subject string e.g "O=Grid,OU=OGSA,CN=John Doe"
114:             * @param domain    domain of the server to store in the subject alternative name extension.
115:             * @return the new X509 V3 Certificate.
116:             * @throws GeneralSecurityException
117:             * @throws IOException
118:             */
119:            public static X509Certificate createRSACert(KeyStore ksKeys,
120:                    String keyPassword, String alias, String issuerDN,
121:                    String subjectDN, String domain)
122:                    throws GeneralSecurityException, IOException {
123:                // Generate public and private keys
124:                KeyPair keyPair = generateKeyPair("RSA", 1024);
125:                // Create X509 certificate with keys and specified domain
126:                X509Certificate cert = createX509V3Certificate(keyPair, 60,
127:                        issuerDN, subjectDN, domain, "MD5withRSA");
128:                // Store new certificate and private key in the keystore
129:                ksKeys.setKeyEntry(alias, keyPair.getPrivate(), keyPassword
130:                        .toCharArray(), new X509Certificate[] { cert });
131:                // Notify listeners that a new certificate has been created
132:                for (CertificateEventListener listener : listeners) {
133:                    try {
134:                        listener.certificateCreated(ksKeys, alias, cert);
135:                    } catch (Exception e) {
136:                        Log.error(e);
137:                    }
138:                }
139:                // Return new certificate
140:                return cert;
141:            }
142:
143:            /**
144:             * Deletes the specified certificate from the
145:             *
146:             * @param ksKeys    key store where the certificate is stored.
147:             * @param alias     alias of the certificate to delete.
148:             * @throws GeneralSecurityException
149:             * @throws IOException
150:             */
151:            public static void deleteCertificate(KeyStore ksKeys, String alias)
152:                    throws GeneralSecurityException, IOException {
153:                ksKeys.deleteEntry(alias);
154:                // Notify listeners that a new certificate has been created
155:                for (CertificateEventListener listener : listeners) {
156:                    try {
157:                        listener.certificateDeleted(ksKeys, alias);
158:                    } catch (Exception e) {
159:                        Log.error(e);
160:                    }
161:                }
162:            }
163:
164:            /**
165:             * Returns the identities of the remote server as defined in the specified certificate. The
166:             * identities are defined in the subjectDN of the certificate and it can also be defined in
167:             * the subjectAltName extensions of type "xmpp". When the extension is being used then the
168:             * identities defined in the extension are going to be returned. Otherwise, the value stored in
169:             * the subjectDN is returned.
170:             *
171:             * @param x509Certificate the certificate the holds the identities of the remote server.
172:             * @return the identities of the remote server as defined in the specified certificate.
173:             */
174:            public static List<String> getPeerIdentities(
175:                    X509Certificate x509Certificate) {
176:                // Look the identity in the subjectAltName extension if available
177:                List<String> names = getSubjectAlternativeNames(x509Certificate);
178:                if (names.isEmpty()) {
179:                    String name = x509Certificate.getSubjectDN().getName();
180:                    Matcher matcher = cnPattern.matcher(name);
181:                    if (matcher.find()) {
182:                        name = matcher.group(2);
183:                    }
184:                    // Create an array with the unique identity
185:                    names = new ArrayList<String>();
186:                    names.add(name);
187:                }
188:                return names;
189:            }
190:
191:            /**
192:             * Returns the JID representation of an XMPP entity contained as a SubjectAltName extension
193:             * in the certificate. If none was found then return <tt>null</tt>.
194:             *
195:             * @param certificate the certificate presented by the remote entity.
196:             * @return the JID representation of an XMPP entity contained as a SubjectAltName extension
197:             *         in the certificate. If none was found then return <tt>null</tt>.
198:             */
199:            private static List<String> getSubjectAlternativeNames(
200:                    X509Certificate certificate) {
201:                List<String> identities = new ArrayList<String>();
202:                try {
203:                    Collection<List<?>> altNames = certificate
204:                            .getSubjectAlternativeNames();
205:                    // Check that the certificate includes the SubjectAltName extension
206:                    if (altNames == null) {
207:                        return Collections.emptyList();
208:                    }
209:                    // Use the type OtherName to search for the certified server name
210:                    for (List item : altNames) {
211:                        Integer type = (Integer) item.get(0);
212:                        if (type == 0) {
213:                            // Type OtherName found so return the associated value
214:                            try {
215:                                // Value is encoded using ASN.1 so decode it to get the server's identity
216:                                ASN1InputStream decoder = new ASN1InputStream(
217:                                        (byte[]) item.toArray()[1]);
218:                                DEREncodable encoded = decoder.readObject();
219:                                encoded = ((DERSequence) encoded)
220:                                        .getObjectAt(1);
221:                                encoded = ((DERTaggedObject) encoded)
222:                                        .getObject();
223:                                encoded = ((DERTaggedObject) encoded)
224:                                        .getObject();
225:                                String identity = ((DERUTF8String) encoded)
226:                                        .getString();
227:                                if (!"".equals(identity)) {
228:                                    // Add the decoded server name to the list of identities
229:                                    identities.add(identity);
230:                                }
231:                            } catch (UnsupportedEncodingException e) {
232:                                // Ignore
233:                            } catch (IOException e) {
234:                                // Ignore
235:                            } catch (Exception e) {
236:                                Log
237:                                        .error(
238:                                                "CertificateManager: Error decoding subjectAltName",
239:                                                e);
240:                            }
241:                        }
242:                        // Other types are not good for XMPP so ignore them
243:                        else if (Log.isDebugEnabled()) {
244:                            Log
245:                                    .debug("CertificateManager: SubjectAltName of invalid type found: "
246:                                            + certificate.getSubjectDN());
247:                        }
248:                    }
249:                } catch (CertificateParsingException e) {
250:                    Log.error(
251:                            "CertificateManager: Error parsing SubjectAltName in certificate: "
252:                                    + certificate.getSubjectDN(), e);
253:                }
254:                return identities;
255:            }
256:
257:            /**
258:             * Returns true if an RSA certificate was found in the specified keystore for the specified domain.
259:             *
260:             * @param ksKeys the keystore that contains the certificates.
261:             * @param domain domain of the server signed by the certificate.
262:             * @return true if an RSA certificate was found in the specified keystore for the specified domain.
263:             * @throws KeyStoreException
264:             */
265:            public static boolean isRSACertificate(KeyStore ksKeys,
266:                    String domain) throws KeyStoreException {
267:                return isCertificate(ksKeys, domain, "RSA");
268:            }
269:
270:            /**
271:             * Returns true if an DSA certificate was found in the specified keystore for the specified domain.
272:             *
273:             * @param ksKeys the keystore that contains the certificates.
274:             * @param domain domain of the server signed by the certificate.
275:             * @return true if an DSA certificate was found in the specified keystore for the specified domain.
276:             * @throws KeyStoreException
277:             */
278:            public static boolean isDSACertificate(KeyStore ksKeys,
279:                    String domain) throws KeyStoreException {
280:                return isCertificate(ksKeys, domain, "DSA");
281:            }
282:
283:            /**
284:             * Returns true if the specified certificate is using the DSA algorithm. The DSA algorithm is not
285:             * good for encryption but only for authentication. On the other hand, the RSA algorithm is good
286:             * for encryption and authentication.
287:             *
288:             * @param certificate the certificate to analyze.
289:             * @return true if the specified certificate is using the DSA algorithm.
290:             * @throws KeyStoreException
291:             */
292:            public static boolean isDSACertificate(X509Certificate certificate)
293:                    throws KeyStoreException {
294:                return certificate.getPublicKey().getAlgorithm().equals("DSA");
295:            }
296:
297:            /**
298:             * Returns true if a certificate with the specifed configuration was found in the key store.
299:             *
300:             * @param ksKeys the keystore to use for searching the certificate.
301:             * @param domain the domain present in the subjectAltName or "*" if anything is accepted.
302:             * @param algorithm the DSA or RSA algorithm used by the certificate.
303:             * @return true if a certificate with the specifed configuration was found in the key store.
304:             * @throws KeyStoreException
305:             */
306:            private static boolean isCertificate(KeyStore ksKeys,
307:                    String domain, String algorithm) throws KeyStoreException {
308:                for (Enumeration<String> aliases = ksKeys.aliases(); aliases
309:                        .hasMoreElements();) {
310:                    X509Certificate certificate = (X509Certificate) ksKeys
311:                            .getCertificate(aliases.nextElement());
312:                    if ("*".equals(domain)) {
313:                        // Any domain certified by the certificate is accepted
314:                        if (certificate.getPublicKey().getAlgorithm().equals(
315:                                algorithm)) {
316:                            return true;
317:                        }
318:                    } else {
319:                        // Only accept certified domains that match the specified domain
320:                        for (String identity : getPeerIdentities(certificate)) {
321:                            if (identity.endsWith(domain)
322:                                    && certificate.getPublicKey()
323:                                            .getAlgorithm().equals(algorithm)) {
324:                                return true;
325:                            }
326:                        }
327:                    }
328:                }
329:                return false;
330:            }
331:
332:            /**
333:             * Returns true if the specified certificate is a self-signed certificate.
334:             *
335:             * @param keyStore key store that holds the certificate to verify.
336:             * @param alias alias of the certificate in the key store.
337:             * @return true if the specified certificate is a self-signed certificate.
338:             * @throws KeyStoreException if an error happens while usign the keystore
339:             */
340:            public static boolean isSelfSignedCertificate(KeyStore keyStore,
341:                    String alias) throws KeyStoreException {
342:                // Get certificate chain
343:                java.security.cert.Certificate[] certificateChain = keyStore
344:                        .getCertificateChain(alias);
345:                // Verify that the chain is empty or was signed by himself
346:                return certificateChain == null || certificateChain.length == 1;
347:            }
348:
349:            /**
350:             * Returns true if the specified certificate is ready to be signed by a Certificate Authority. Self-signed
351:             * certificates need to get their issuer information entered to be able to generate a Certificate
352:             * Signing Request (CSR).
353:             *
354:             * @param keyStore key store that holds the certificate to verify.
355:             * @param alias alias of the certificate in the key store.
356:             * @return true if the specified certificate is ready to be signed by a Certificate Authority.
357:             * @throws KeyStoreException if an error happens while usign the keystore
358:             */
359:            public static boolean isSigningRequestPending(KeyStore keyStore,
360:                    String alias) throws KeyStoreException {
361:                // Verify that this is a self-signed certificate
362:                if (!isSelfSignedCertificate(keyStore, alias)) {
363:                    return false;
364:                }
365:                // Verify that the issuer information has been entered
366:                X509Certificate certificate = (X509Certificate) keyStore
367:                        .getCertificate(alias);
368:                Matcher matcher = valuesPattern.matcher(certificate
369:                        .getIssuerDN().toString());
370:                return matcher.find() && matcher.find();
371:            }
372:
373:            /**
374:             * Creates and returns the content of a new singing request for the specified certificate. Signing
375:             * requests are required by Certificate Authorities as part of their signing process. The signing request
376:             * contains information about the certificate issuer, subject DN, subject alternative names and public key.
377:             * Private keys are not included. After the Certificate Authority verified and signed the certificate a new
378:             * certificate is going to be returned. Use {@link #installReply(java.security.KeyStore, java.security.KeyStore, String, String, java.io.InputStream, boolean, boolean)}
379:             * to import the CA reply.
380:             *
381:             * @param cert the certificate to create a signing request.
382:             * @param privKey the private key of the certificate.
383:             * @return the content of a new singing request for the specified certificate.
384:             * @throws Exception
385:             */
386:            public static String createSigningRequest(X509Certificate cert,
387:                    PrivateKey privKey) throws Exception {
388:                StringBuilder sb = new StringBuilder();
389:
390:                String subject = cert.getSubjectDN().getName();
391:                X509Name xname = new X509Name(subject);
392:
393:                PublicKey pubKey = cert.getPublicKey();
394:
395:                String signatureAlgorithm = "DSA".equals(pubKey.getAlgorithm()) ? "SHA1withDSA"
396:                        : "MD5withRSA";
397:
398:                PKCS10CertificationRequest csr = new PKCS10CertificationRequest(
399:                        signatureAlgorithm, xname, pubKey, null, privKey);
400:
401:                ByteArrayOutputStream baos = new ByteArrayOutputStream();
402:                DEROutputStream deros = new DEROutputStream(baos);
403:                deros.writeObject(csr.getDERObject());
404:                String sTmp = new String(org.bouncycastle.util.encoders.Base64
405:                        .encode(baos.toByteArray()));
406:
407:                // Header
408:                sb.append("-----BEGIN NEW CERTIFICATE REQUEST-----\n");
409:
410:                // Add signing request content (base 64 encoded)
411:                for (int iCnt = 0; iCnt < sTmp.length(); iCnt += CERT_REQ_LINE_LENGTH) {
412:                    int iLineLength;
413:
414:                    if ((iCnt + CERT_REQ_LINE_LENGTH) > sTmp.length()) {
415:                        iLineLength = sTmp.length() - iCnt;
416:                    } else {
417:                        iLineLength = CERT_REQ_LINE_LENGTH;
418:                    }
419:
420:                    sb.append(sTmp.substring(iCnt, iCnt + iLineLength)).append(
421:                            "\n");
422:                }
423:
424:                // Footer
425:                sb.append("-----END NEW CERTIFICATE REQUEST-----\n");
426:                return sb.toString();
427:            }
428:
429:            /**
430:             * Installs the Certificate Authority reply returned as part of the signing request. The certificate
431:             * being signed will get its certificate chain updated with the imported certificate(s). An exception
432:             * will be thrown if the replied certificate does not match a local certificate or if the signing
433:             * authority is not known by the server (i.e. keystore and truststore files). When <tt>trustCACerts</tt>
434:             * is set to <tt>true</tt> then certificates present in the truststore file will be used to verify the
435:             * identity of the entity signing the certificate. In case the reply is composed of more than one
436:             * certificate then you can also specify if you want to verify that the root certificate in the chain
437:             * can be trusted.
438:             *
439:             * @param keyStore    key store where the certificate is stored.
440:             * @param trustStore  key store where ca certificates are stored.
441:             * @param keyPassword password of the keystore.
442:             * @param alias the alias of the existing certificate being signed.
443:             * @param inputStream the stream containing the CA reply.
444:             * @param trustCACerts true if certificates present in the truststore file will be used to verify the
445:             *        identity of the entity signing the certificate.
446:             * @param validateRoot true if you want to verify that the root certificate in the chain can be trusted
447:             *        based on the truststore.
448:             * @return true if the CA reply was successfully processed.
449:             * @throws Exception
450:             */
451:            public static boolean installReply(KeyStore keyStore,
452:                    KeyStore trustStore, String keyPassword, String alias,
453:                    InputStream inputStream, boolean trustCACerts,
454:                    boolean validateRoot) throws Exception {
455:
456:                // Check that there is a certificate for the specified alias
457:                X509Certificate certificate = (X509Certificate) keyStore
458:                        .getCertificate(alias);
459:                if (certificate == null) {
460:                    Log.warn("Certificate not found for alias: " + alias);
461:                    return false;
462:                }
463:                // Retrieve the private key of the stored certificate
464:                PrivateKey privKey = (PrivateKey) keyStore.getKey(alias,
465:                        keyPassword.toCharArray());
466:                // Load certificates found in the PEM input stream
467:                List<X509Certificate> certs = new ArrayList<X509Certificate>();
468:                for (Certificate cert : CertificateFactory.getInstance("X509")
469:                        .generateCertificates(inputStream)) {
470:                    certs.add((X509Certificate) cert);
471:                }
472:                if (certs.isEmpty()) {
473:                    throw new Exception("Reply has no certificates");
474:                }
475:                List<X509Certificate> newCerts;
476:                if (certs.size() == 1) {
477:                    // Reply has only one certificate
478:                    newCerts = establishCertChain(keyStore, trustStore, null,
479:                            certs.get(0), trustCACerts);
480:                } else {
481:                    // Reply has a chain of certificates
482:                    newCerts = validateReply(keyStore, trustStore, alias, null,
483:                            certs, trustCACerts, validateRoot);
484:                }
485:                if (newCerts != null) {
486:                    keyStore.setKeyEntry(alias, privKey, keyPassword
487:                            .toCharArray(), newCerts
488:                            .toArray(new X509Certificate[newCerts.size()]));
489:
490:                    // Notify listeners that a new certificate has been created
491:                    for (CertificateEventListener listener : listeners) {
492:                        try {
493:                            listener.certificateSigned(keyStore, alias,
494:                                    newCerts);
495:                        } catch (Exception e) {
496:                            Log.error(e);
497:                        }
498:                    }
499:
500:                    return true;
501:                } else {
502:                    return false;
503:                }
504:            }
505:
506:            /**
507:             * Imports a new signed certificate and its private key into the keystore. The certificate input
508:             * stream may contain the signed certificate as well as its CA chain.
509:             *
510:             * @param keyStore    key store where the certificate will be stored.
511:             * @param trustStore  key store where ca certificates are stored.
512:             * @param keyPassword password of the keystore.
513:             * @param alias the alias of the the new signed certificate.
514:             * @param pkInputStream the stream containing the private key.
515:             * @param passPhrase is the password phrased used when creating the private key.
516:             * @param inputStream the stream containing the signed certificate.
517:             * @param trustCACerts true if certificates present in the truststore file will be used to verify the
518:             *        identity of the entity signing the certificate.
519:             * @param validateRoot true if you want to verify that the root certificate in the chain can be trusted
520:             *        based on the truststore.
521:             * @return true if the certificate was successfully imported.
522:             * @throws Exception if no certificates were found in the inputStream.
523:             */
524:            public static boolean installCert(KeyStore keyStore,
525:                    KeyStore trustStore, String keyPassword, String alias,
526:                    InputStream pkInputStream, final String passPhrase,
527:                    InputStream inputStream, boolean trustCACerts,
528:                    boolean validateRoot) throws Exception {
529:                // Check that there is a certificate for the specified alias
530:                X509Certificate certificate = (X509Certificate) keyStore
531:                        .getCertificate(alias);
532:                if (certificate != null) {
533:                    Log.warn("Certificate already exists for alias: " + alias);
534:                    return false;
535:                }
536:                // Retrieve the private key of the stored certificate
537:                PasswordFinder passwordFinder = new PasswordFinder() {
538:                    public char[] getPassword() {
539:                        return passPhrase != null ? passPhrase.toCharArray()
540:                                : new char[] {};
541:                    }
542:                };
543:                PEMReader pemReader = new PEMReader(new InputStreamReader(
544:                        pkInputStream), passwordFinder);
545:                KeyPair kp = (KeyPair) pemReader.readObject();
546:                PrivateKey privKey = kp.getPrivate();
547:
548:                // Load certificates found in the PEM input stream
549:                List<X509Certificate> certs = new ArrayList<X509Certificate>();
550:                for (Certificate cert : CertificateFactory.getInstance("X509")
551:                        .generateCertificates(inputStream)) {
552:                    certs.add((X509Certificate) cert);
553:                }
554:                if (certs.isEmpty()) {
555:                    throw new Exception("No certificates were found");
556:                }
557:                List<X509Certificate> newCerts;
558:                if (certs.size() == 1) {
559:                    // Reply has only one certificate
560:                    newCerts = establishCertChain(keyStore, trustStore,
561:                            certificate, certs.get(0), trustCACerts);
562:                } else {
563:                    // Reply has a chain of certificates
564:                    newCerts = validateReply(keyStore, trustStore, alias,
565:                            certificate, certs, trustCACerts, validateRoot);
566:                }
567:                if (newCerts != null) {
568:                    keyStore.setKeyEntry(alias, privKey, keyPassword
569:                            .toCharArray(), newCerts
570:                            .toArray(new X509Certificate[newCerts.size()]));
571:
572:                    // Notify listeners that a new certificate has been created (and signed)
573:                    for (CertificateEventListener listener : listeners) {
574:                        try {
575:                            listener.certificateCreated(keyStore, alias, certs
576:                                    .get(0));
577:                            if (newCerts.size() > 1) {
578:                                listener.certificateSigned(keyStore, alias,
579:                                        newCerts);
580:                            }
581:                        } catch (Exception e) {
582:                            Log.error(e);
583:                        }
584:                    }
585:
586:                    return true;
587:                } else {
588:                    return false;
589:                }
590:            }
591:
592:            /**
593:             * Registers a listener to receive events.
594:             *
595:             * @param listener the listener.
596:             */
597:            public static void addListener(CertificateEventListener listener) {
598:                if (listener == null) {
599:                    throw new NullPointerException();
600:                }
601:                listeners.add(listener);
602:            }
603:
604:            /**
605:             * Unregisters a listener to receive events.
606:             *
607:             * @param listener the listener.
608:             */
609:            public static void removeListener(CertificateEventListener listener) {
610:                listeners.remove(listener);
611:            }
612:
613:            private static List<X509Certificate> establishCertChain(
614:                    KeyStore keyStore, KeyStore trustStore,
615:                    X509Certificate certificate, X509Certificate certReply,
616:                    boolean trustCACerts) throws Exception {
617:                if (certificate != null) {
618:                    PublicKey publickey = certificate.getPublicKey();
619:                    PublicKey publickey1 = certReply.getPublicKey();
620:                    if (!publickey.equals(publickey1)) {
621:                        throw new Exception(
622:                                "Public keys in reply and keystore don't match");
623:                    }
624:                    if (certReply.equals(certificate)) {
625:                        throw new Exception(
626:                                "Certificate reply and certificate in keystore are identical");
627:                    }
628:                }
629:                Map<Principal, List<X509Certificate>> knownCerts = new Hashtable<Principal, List<X509Certificate>>();
630:                if (keyStore.size() > 0) {
631:                    knownCerts.putAll(getCertsByIssuer(keyStore));
632:                }
633:                if (trustCACerts && trustStore.size() > 0) {
634:                    knownCerts.putAll(getCertsByIssuer(trustStore));
635:                }
636:                LinkedList<X509Certificate> answer = new LinkedList<X509Certificate>();
637:                if (buildChain(certReply, answer, knownCerts)) {
638:                    return answer;
639:                } else {
640:                    throw new Exception("Failed to establish chain from reply");
641:                }
642:            }
643:
644:            /**
645:             * Builds the certificate chain of the specified certificate based on the known list of certificates
646:             * that were issued by their respective Principals. Returns true if the entire chain of all certificates
647:             * was successfully built.
648:             *
649:             * @param certificate certificate to build its chain.
650:             * @param answer      the certificate chain for the corresponding certificate.
651:             * @param knownCerts  list of known certificates grouped by their issues (i.e. Principals).
652:             * @return true if the entire chain of all certificates was successfully built.
653:             */
654:            private static boolean buildChain(X509Certificate certificate,
655:                    LinkedList<X509Certificate> answer,
656:                    Map<Principal, List<X509Certificate>> knownCerts) {
657:                Principal subject = certificate.getSubjectDN();
658:                Principal issuer = certificate.getIssuerDN();
659:                // Check if the certificate is a root certificate (i.e. was issued by the same Principal that
660:                // is present in the subject)
661:                if (subject.equals(issuer)) {
662:                    answer.addFirst(certificate);
663:                    return true;
664:                }
665:                // Get the list of known certificates of the certificate's issuer
666:                List<X509Certificate> issuerCerts = knownCerts.get(issuer);
667:                if (issuerCerts == null || issuerCerts.isEmpty()) {
668:                    // No certificates were found so building of chain failed
669:                    return false;
670:                }
671:                for (X509Certificate issuerCert : issuerCerts) {
672:                    PublicKey publickey = issuerCert.getPublicKey();
673:                    try {
674:                        // Verify the certificate with the specified public key
675:                        certificate.verify(publickey);
676:                        // Certificate was verified successfully so build chain of issuer's certificate
677:                        if (!buildChain(issuerCert, answer, knownCerts)) {
678:                            return false;
679:                        }
680:                    } catch (Exception exception) {
681:                        // Failed to verify certificate
682:                        return false;
683:                    }
684:                }
685:                answer.addFirst(certificate);
686:                return true;
687:            }
688:
689:            /**
690:             * Returns a Map where the key holds the certificate issuers and values the certificates of each issuer.
691:             *
692:             * @param ks the keystore to get its certs per issuer.
693:             * @return a map with the certificates per issuer.
694:             * @throws Exception
695:             */
696:            private static Map<Principal, List<X509Certificate>> getCertsByIssuer(
697:                    KeyStore ks) throws Exception {
698:                Map<Principal, List<X509Certificate>> answer = new HashMap<Principal, List<X509Certificate>>();
699:                Enumeration<String> aliases = ks.aliases();
700:                while (aliases.hasMoreElements()) {
701:                    String alias = aliases.nextElement();
702:                    X509Certificate cert = (X509Certificate) ks
703:                            .getCertificate(alias);
704:                    if (cert != null) {
705:                        Principal subjectDN = cert.getSubjectDN();
706:                        List<X509Certificate> vec = answer.get(subjectDN);
707:                        if (vec == null) {
708:                            vec = new ArrayList<X509Certificate>();
709:                            vec.add(cert);
710:                        } else {
711:                            if (!vec.contains(cert)) {
712:                                vec.add(cert);
713:                            }
714:                        }
715:                        answer.put(subjectDN, vec);
716:                    }
717:                }
718:                return answer;
719:            }
720:
721:            /**
722:             * Validates chain in certification reply, and returns the ordered
723:             * elements of the chain (with user certificate first, and root
724:             * certificate last in the array).
725:             *
726:             * @param alias the alias name
727:             * @param userCert the user certificate of the alias
728:             * @param replyCerts the chain provided in the reply
729:             */
730:            private static List<X509Certificate> validateReply(
731:                    KeyStore keyStore, KeyStore trustStore, String alias,
732:                    X509Certificate userCert, List<X509Certificate> replyCerts,
733:                    boolean trustCACerts, boolean verifyRoot) throws Exception {
734:                // order the certs in the reply (bottom-up).
735:                int i;
736:                X509Certificate tmpCert;
737:                if (userCert != null) {
738:                    PublicKey userPubKey = userCert.getPublicKey();
739:                    for (i = 0; i < replyCerts.size(); i++) {
740:                        if (userPubKey.equals(replyCerts.get(i).getPublicKey())) {
741:                            break;
742:                        }
743:                    }
744:                    if (i == replyCerts.size()) {
745:                        throw new Exception(
746:                                "Certificate reply does not contain public key for <alias>: "
747:                                        + alias);
748:                    }
749:
750:                    tmpCert = replyCerts.get(0);
751:                    replyCerts.set(0, replyCerts.get(i));
752:                    replyCerts.set(i, tmpCert);
753:                }
754:
755:                Principal issuer = replyCerts.get(0).getIssuerDN();
756:
757:                for (i = 1; i < replyCerts.size() - 1; i++) {
758:                    // find a cert in the reply whose "subject" is the same as the
759:                    // given "issuer"
760:                    int j;
761:                    for (j = i; j < replyCerts.size(); j++) {
762:                        Principal subject = replyCerts.get(j).getSubjectDN();
763:                        if (subject.equals(issuer)) {
764:                            tmpCert = replyCerts.get(i);
765:                            replyCerts.set(i, replyCerts.get(j));
766:                            replyCerts.set(j, tmpCert);
767:                            issuer = replyCerts.get(i).getIssuerDN();
768:                            break;
769:                        }
770:                    }
771:                    if (j == replyCerts.size()) {
772:                        throw new Exception(
773:                                "Incomplete certificate chain in reply");
774:                    }
775:                }
776:
777:                // now verify each cert in the ordered chain
778:                for (i = 0; i < replyCerts.size() - 1; i++) {
779:                    PublicKey pubKey = replyCerts.get(i + 1).getPublicKey();
780:                    try {
781:                        replyCerts.get(i).verify(pubKey);
782:                    } catch (Exception e) {
783:                        throw new Exception(
784:                                "Certificate chain in reply does not verify: "
785:                                        + e.getMessage());
786:                    }
787:                }
788:
789:                if (!verifyRoot) {
790:                    return replyCerts;
791:                }
792:
793:                // do we trust the (root) cert at the top?
794:                X509Certificate topCert = replyCerts.get(replyCerts.size() - 1);
795:                boolean foundInKeyStore = keyStore.getCertificateAlias(topCert) != null;
796:                boolean foundInCAStore = trustCACerts
797:                        && (trustStore.getCertificateAlias(topCert) != null);
798:                if (!foundInKeyStore && !foundInCAStore) {
799:                    boolean verified = false;
800:                    X509Certificate rootCert = null;
801:                    if (trustCACerts) {
802:                        for (Enumeration<String> aliases = trustStore.aliases(); aliases
803:                                .hasMoreElements();) {
804:                            String name = aliases.nextElement();
805:                            rootCert = (X509Certificate) trustStore
806:                                    .getCertificate(name);
807:                            if (rootCert != null) {
808:                                try {
809:                                    topCert.verify(rootCert.getPublicKey());
810:                                    verified = true;
811:                                    break;
812:                                } catch (Exception e) {
813:                                    // Ignore
814:                                }
815:                            }
816:                        }
817:                    }
818:                    if (!verified) {
819:                        return null;
820:                    } else {
821:                        // Check if the cert is a self-signed cert
822:                        if (!topCert.getSubjectDN().equals(
823:                                topCert.getIssuerDN())) {
824:                            // append the (self-signed) root CA cert to the chain
825:                            replyCerts.add(rootCert);
826:                        }
827:                    }
828:                }
829:
830:                return replyCerts;
831:            }
832:
833:            /**
834:             * Creates an X509 version3 certificate.
835:             *
836:             * @param kp           KeyPair that keeps the public and private keys for the new certificate.
837:             * @param months       time to live
838:             * @param issuerDN     Issuer string e.g "O=Grid,OU=OGSA,CN=ACME"
839:             * @param subjectDN    Subject string e.g "O=Grid,OU=OGSA,CN=John Doe"
840:             * @param domain       Domain of the server.
841:             * @param signAlgoritm Signature algorithm. This can be either a name or an OID.
842:             * @return X509 V3 Certificate
843:             * @throws GeneralSecurityException
844:             * @throws IOException
845:             */
846:            private static synchronized X509Certificate createX509V3Certificate(
847:                    KeyPair kp, int months, String issuerDN, String subjectDN,
848:                    String domain, String signAlgoritm)
849:                    throws GeneralSecurityException, IOException {
850:                PublicKey pubKey = kp.getPublic();
851:                PrivateKey privKey = kp.getPrivate();
852:
853:                byte[] serno = new byte[8];
854:                SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
855:                random.setSeed((new Date().getTime()));
856:                random.nextBytes(serno);
857:                BigInteger serial = (new java.math.BigInteger(serno)).abs();
858:
859:                X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
860:                certGenerator.reset();
861:
862:                certGenerator.setSerialNumber(serial);
863:                certGenerator.setIssuerDN(new X509Name(issuerDN));
864:                certGenerator
865:                        .setNotBefore(new Date(System.currentTimeMillis()));
866:                certGenerator.setNotAfter(new Date(System.currentTimeMillis()
867:                        + months * (1000L * 60 * 60 * 24 * 30)));
868:                certGenerator.setSubjectDN(new X509Name(subjectDN));
869:                certGenerator.setPublicKey(pubKey);
870:                certGenerator.setSignatureAlgorithm(signAlgoritm);
871:
872:                // Generate the subject alternative name
873:                boolean critical = subjectDN == null
874:                        || "".equals(subjectDN.trim());
875:                DERSequence othernameSequence = new DERSequence(
876:                        new ASN1Encodable[] {
877:                                new DERObjectIdentifier("1.3.6.1.5.5.7.8.5"),
878:                                new DERTaggedObject(true, 0, new DERUTF8String(
879:                                        domain)) });
880:                GeneralName othernameGN = new GeneralName(
881:                        GeneralName.otherName, othernameSequence);
882:                GeneralNames subjectAltNames = new GeneralNames(
883:                        new DERSequence(new ASN1Encodable[] { othernameGN }));
884:                // Add subject alternative name extension
885:                certGenerator.addExtension(
886:                        X509Extensions.SubjectAlternativeName, critical,
887:                        subjectAltNames);
888:
889:                X509Certificate cert = certGenerator.generateX509Certificate(
890:                        privKey, "BC", new SecureRandom());
891:                cert.checkValidity(new Date());
892:                cert.verify(pubKey);
893:
894:                return cert;
895:            }
896:
897:            /**
898:             * Returns a new public & private key with the specified algorithm (e.g. DSA, RSA, etc.).
899:             *
900:             * @param algorithm DSA, RSA, etc.
901:             * @param keysize   the keysize. This is an algorithm-specific metric, such as modulus
902:             *                  length, specified in number of bits.
903:             * @return a new public & private key with the specified algorithm (e.g. DSA, RSA, etc.).
904:             * @throws GeneralSecurityException
905:             */
906:            private static KeyPair generateKeyPair(String algorithm, int keysize)
907:                    throws GeneralSecurityException {
908:                KeyPairGenerator generator;
909:                if (provider == null) {
910:                    generator = KeyPairGenerator.getInstance(algorithm);
911:                } else {
912:                    generator = KeyPairGenerator.getInstance(algorithm,
913:                            provider);
914:                }
915:                generator.initialize(keysize, new SecureRandom());
916:                return generator.generateKeyPair();
917:            }
918:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.