/Users/lyon/j4p/src/security/KeyUtils.java
|
1 package security;
2
3 import gui.In;
4 import sun.security.pkcs.PKCS10;
5 import sun.security.x509.CertAndKeyGen;
6 import sun.security.x509.X500Name;
7 import sun.security.x509.X500Signer;
8
9 import java.io.*;
10 import java.security.*;
11 import java.security.cert.Certificate;
12 import java.security.cert.CertificateException;
13 import java.security.cert.CertificateFactory;
14 import java.security.cert.X509Certificate;
15 import java.security.interfaces.DSAKey;
16 import java.text.MessageFormat;
17 import java.util.*;
18
19 import gui.In;
20
21 /**
22 * DocJava, Inc. User: lyon Date: May 20, 2004
23 * Time: 6:35:50 AM
24 */
25 public class KeyUtils {
26 public static final ResourceBundle rb = ResourceBundle.getBundle(
27 "sun.security.util.Resources");
28
29 public static void main(String[] args) {
30 //testX500Name();
31 //testGenerateKeyPair();
32 runImportCertificate();
33 }
34
35 /**
36 * a gui for importing certificates
37 */
38 public static void runImportCertificate() {
39 try {
40 importCertificate();
41 } catch (Exception e) {
42 In.message(e);
43 }
44 }
45
46 private static void testX500Name() {
47 X500Name x500Name = null;
48 try {
49 x500Name = getX500Name();
50 } catch (IOException e) {
51 e.printStackTrace();
52 }
53 System.out.println(x500Name);
54 }
55
56 /**
57 * Open the <code>.keystore</code> file.
58 * Prompt the user for an alias.
59 * Open the new certificate from the CA.
60 * Modify the <code>.keystore</code> file with
61 * the new certificate.
62 * If files are missing, recover from the
63 * error with grace.
64 * @throws FileNotFoundException
65 * @throws KeyStoreException
66 * @throws CertificateException
67 * @throws IOException
68 */
69 public static void importCertificate()
70 throws FileNotFoundException,
71 KeyStoreException,
72 CertificateException,
73 IOException {
74 final File ksf = getKeystoreFile();
75 final String password = getPassword();
76 KeyStore ks = getKeyStore(ksf, password);
77 final String alias = getAlias(ks);
78 X509Certificate cert = (X509Certificate) getCertificate(
79 getCertificateFile());
80 if (ks.containsAlias(alias))
81 ks.deleteEntry(alias);
82 verifyCert(cert);
83 ks.setCertificateEntry(alias, cert);
84 save(ksf, ks, password);
85 In.message("import complete!");
86 }
87 /**
88 * @param cert
89 * @return true if the subjectDN and issuer are the same.
90 */
91 public static boolean isSelfSigned(
92 X509Certificate cert) {
93 return cert.getSubjectDN().equals(
94 cert.getIssuerDN());
95 }
96
97 /**
98 * Display exception if certificate
99 * cannot be verified. Kill program.
100 * @param cert
101 */
102 public static void verifyCert(
103 X509Certificate cert) {
104 try {
105 if (isSelfSigned(cert))
106 cert.verify(cert.getPublicKey());
107 } catch (Exception e) {
108 In.message(e);
109 System.exit(0);
110 }
111 }
112 /**
113 * Use a certificate file to to
114 * make a certificate instances.
115 * @param certF
116 * @return
117 * @throws CertificateException
118 * @throws FileNotFoundException
119 * @throws IOException
120 */
121 public static Certificate getCertificate(
122 File certF)
123 throws CertificateException,
124 FileNotFoundException,
125 IOException {
126 CertificateFactory cf =
127 CertificateFactory.getInstance(
128 "X509");
129 Collection col = importCertificates(cf,
130 certF);
131 Iterator i = col.iterator();
132 X509Certificate c = null;
133 while (i.hasNext()) {
134 Certificate cert = (Certificate) i.next();
135 if (cert instanceof X509Certificate)
136 c = (X509Certificate) cert;
137 //System.out.println(cert);
138 }
139 return c;
140 }
141 /**
142 * get a collection of certificates from
143 * a certificateFactory.
144 * @param cf
145 * @param certF
146 * @return
147 * @throws CertificateException
148 * @throws FileNotFoundException
149 * @throws IOException
150 */
151 private static Collection importCertificates(
152 CertificateFactory cf, File certF)
153 throws CertificateException,
154 FileNotFoundException,
155 IOException {
156 final FileInputStream fis = new FileInputStream(
157 certF);
158 Collection col = null;
159 try {
160 col = cf.generateCertificates(fis);
161 } catch (CertificateException e) {
162 boolean b = gui.In.getBoolean(
163 "Cert exception, would you like me to try to translate it?");
164 if (!b) return importCertificates(cf);
165 CertUtils.cleanThawtes();
166 return importCertificates(cf);
167 }
168 return col;
169 }
170
171 private static Collection importCertificates(
172 CertificateFactory cf)
173 throws CertificateException,
174 IOException {
175 return importCertificates(cf,
176 futils.Futil.getReadFile(
177 "select pcks7 cert"));
178 }
179
180 /**
181 * Prompt the user for a PKCS7 format
182 * certificate.
183 * @return a <code>File</code> instance that is unchecked.
184 * That is, the file may not even exist.
185 */
186 private static File getCertificateFile() {
187 return
188 futils.Futil.getReadFile(
189 "select a cert in pkcs7 format");
190 }
191 /**
192 * List the security providers in their order of
193 * preference.
194 */
195 public static void printProviders() {
196 System.out.println("provider list:");
197 Provider p[] = Security.getProviders();
198 for (int i = 0; i < p.length; i++)
199 printProvider(p[i]);
200 }
201
202 private static void printProvider(
203 final Provider provider) {
204 System.out.println(
205 "Name:" + provider.getName());
206 System.out.println(
207 "Provider:" + provider);
208 System.out.println(
209 "Info:" + provider.getInfo());
210 System.out.println("algorithm:" +
211 provider.getProperty(
212 "algorithm"));
213 System.out.println("-----");
214 }
215 /**
216 * print out a nicely formatted version of
217 * a given key.
218 * @param key
219 */
220 public static void printKey(Key key) {
221 if (key instanceof DSAKey) {
222 System.out.println("key is DSA");
223 System.out.println("P value is " +
224 ((DSAKey) key).getParams()
225 .getP());
226 } else {
227 System.out.println("key is NOT DSA");
228 System.out.println(key);
229 }
230 }
231
232 /**
233 * get a certificate based on a GUI prompt
234 * to the user for a password and an alias.
235 * @return Certificate
236 */
237 public static Certificate getCertificate() {
238 String password = getPassword();
239 String alias = getAlias();
240 return
241 getCertificate(alias,
242 password);
243 }
244 /**
245 * Prompt the user for a password.
246 * Do not echo it on the screen
247 * @return a string containing the password.
248 */
249 public static String getPassword() {
250 String password = gui.In.getPassword(
251 "please enter keystore password");
252 return password;
253 }
254
255 private static String getAlias() {
256 String alias = gui.In.getString(
257 "please enter certificate alias");
258 return alias;
259 }
260 /**
261 * Given an alias and password, open the
262 * default keystore and return the certificate.
263 * @param alias
264 * @param password
265 * @return a Certificate instance.
266 */
267 public static Certificate getCertificate(
268 String alias, String password) {
269 try {
270 // Load the keystore in the user's home directory
271 KeyStore keystore = getKeystore(
272 password);
273
274 // Get certificate
275
276 return keystore.getCertificate(alias);
277 } catch (KeyStoreException e) {
278 e.printStackTrace();
279 } catch (CertificateException e) {
280 e.printStackTrace();
281 } catch (NoSuchAlgorithmException e) {
282 e.printStackTrace();
283 } catch (java.io.IOException e) {
284 e.printStackTrace();
285 }
286 return null;
287 }
288
289 /**
290 * Return a <code>KeyStore</code> assuming
291 * that one already exists.
292 * If the <code>.keystore</code> file does not
293 * exist, then offer to create one or
294 * look for one.
295 *
296 * @param password to the keystore
297 * @return KeyStore instance
298 */
299 public static KeyStore getKeystore(
300 String password)
301 throws KeyStoreException,
302 IOException,
303 NoSuchAlgorithmException,
304 CertificateException {
305 File f = getKeystoreFile();
306 FileInputStream is = new FileInputStream(
307 f);
308 KeyStore keystore = KeyStore.getInstance(
309 KeyStore.getDefaultType());
310 keystore.load(is,
311 password.toCharArray());
312 return keystore;
313 }
314
315 public static void testGenerateKeyPair() {
316 System.out.println("generateKeyPair:" +
317 generateKeyPair());
318 }
319
320 /**
321 * Creates a keystore, then generates a
322 * keypair for it.
323 *
324 * @return KeyStore with keypair in it.
325 */
326 public static KeyStore generateKeyPair() {
327 KeyStore ks = null;
328 try {
329 ks = generateKeyStore();
330 generateKeyPair(ks);
331 } catch (Exception e) {
332 e.printStackTrace();
333 }
334 return ks;
335 }
336
337 /**
338 * Generate a keystore without reading it from
339 * a file. Excellent for when no keystore is
340 * found.
341 *
342 * @return a default type keystore instance
343 */
344 public static KeyStore generateKeyStore()
345 throws KeyStoreException,
346 IOException,
347 NoSuchAlgorithmException,
348 CertificateException {
349 KeyStore ks =
350 KeyStore.getInstance(
351 KeyStore.getDefaultType());
352 ks.load(null, null);
353 return ks;
354 }
355
356 /**
357 * Prompts the user for X.509 certificate
358 * information. Generates a private and public
359 * keypair, then add it to the keystore. Uses
360 * RSA algorithm with a 1024 bit key size.
361 */
362 public static void generateKeyPair(
363 KeyStore ks)
364 throws Exception {
365 X500Name x500Name = getX500Name();
366 int validity = 90;
367 int keysize = 1024;
368 String alias = In.getString(
369 "enter alias");
370 String keyPass = In.getPassword(
371 "enter password");
372 String sigAlgName = "MD5WithRSA";
373 String keyAlgName = "RSA";
374 CertAndKeyGen keypair = new CertAndKeyGen(
375 keyAlgName, sigAlgName);
376 keypair.generate(keysize);
377 PrivateKey privKey = keypair.getPrivateKey();
378 X509Certificate chain[] = new X509Certificate[1];
379 chain[0] =
380 keypair.getSelfCertificate(x500Name,
381 validity * 24 *
382 60 *
383 60);
384 ks.setKeyEntry(alias,
385 privKey,
386 keyPass.toCharArray(),
387 chain);
388 }
389
390 /**
391 * get a public and private key, given that a
392 * KeyStore exists and a certificate exists
393 * that corresponds to the the given <code>
394 * alias</code>.
395 */
396 public static KeyPair getKeyPair(
397 KeyStore keystore,
398 String alias,
399 String password) {
400 try {
401 // Get private key
402 Key key = keystore.getKey(alias,
403 password.toCharArray());
404 if (key instanceof PrivateKey) {
405 // Get certificate of public key
406 java.security.cert.Certificate cert = keystore.getCertificate(
407 alias);
408
409 // Get public key
410 PublicKey publicKey = cert.getPublicKey();
411
412 // Return a key pair
413 return new KeyPair(publicKey,
414 (PrivateKey) key);
415 }
416 } catch (UnrecoverableKeyException e) {
417 } catch (NoSuchAlgorithmException e) {
418 } catch (KeyStoreException e) {
419 }
420 return null;
421 }
422
423 public static KeyStore getKeyStore() {
424 return getKeyStore(getPassword());
425 }
426
427 /**
428 * Selects the <code>.keystore</code> file in
429 * the users home directory.
430 *
431 * @return KeyStore
432 */
433 public static KeyStore getKeyStore(
434 String password) {
435 File ksf = getKeystoreFile();
436 if (ksf.exists())
437 return getKeyStore(ksf, password);
438 In.getBoolean(
439 "I could not find the .keystore file...sorry");
440 return null;
441 }
442
443 public static KeyStore getKeyStore(
444 File keyStoreFile, String password) {
445 KeyStore keystore = null;
446 try {
447 FileInputStream is = new FileInputStream(
448 keyStoreFile);
449 keystore =
450 KeyStore.getInstance(
451 KeyStore.getDefaultType());
452 keystore.load(is,
453 password.toCharArray());
454 is.close();
455 } catch (Exception e) {
456 In.message(e);
457 }
458 return keystore;
459 }
460 /**
461 * Given a key store, list all the alias
462 * elements there. Certificates are located
463 * via the alias.
464 * @param keystore
465 * @return An array of alias members.
466 */
467 public static String[] getAliasArray(
468 KeyStore keystore) {
469 Enumeration enum = null;
470 try {
471 // List the aliases
472 enum = keystore.aliases();
473 } catch (KeyStoreException e) {
474 e.printStackTrace();
475 }
476 Vector v = new Vector();
477 for (; enum.hasMoreElements();) {
478 String alias = (String) enum.nextElement();
479 v.addElement(alias);
480 }
481 String s[] = new String[v.size()];
482 v.copyInto(s);
483 return s;
484 }
485
486 /**
487 * Write our key store instance out to the
488 * given file. A GUI prints out exceptions,
489 * should they be thrown.
490 *
491 * @param ksFile a file to be created or
492 * overwritten.
493 * @param ks the key store to be saved.
494 * @param password verifies the file.
495 */
496 public static void save(File ksFile,
497 KeyStore ks,
498 String password) {
499 try {
500 FileOutputStream fos = new FileOutputStream(
501 ksFile.getAbsolutePath());
502 ks.store(fos,
503 password.toCharArray());
504 fos.close();
505 } catch (Exception e) {
506 In.message(e);
507 }
508 }
509
510 /**
511 * Look for the <code>.keystore</code>
512 * file in the home directory. If it is
513 * not there, offer to look for it.
514 * If the user does not have it, offer to
515 * create one. If you have to create a keystore,
516 * offer to create a certificate request, as well.
517 *
518 * @return the keystore file
519 */
520 public static File getKeystoreFile() {
521 File file = getDefaultKeyStoreFile();
522 if (file.exists()) return file;
523 final String prompt = "file:" + file +
524 " does not exist:";
525 boolean b = In.getBoolean(
526 prompt +
527 "do you have a keystore?");
528 if (b)
529 return futils.Futil.getReadFile(
530 prompt);
531 b =
532 In.getBoolean(
533 "would you like me to generate a keystore for you?");
534 if (b) return makeKeyStoreFile();
535 In.message(
536 "program exits try:keytool -genkey -keyalg RSA -alias docjava");
537 System.exit(0);
538 return null;
539 }
540 /**
541 * Ouput a file based on user prompts,
542 * that contains the text for a Certificate
543 * Request. This is used with a CA to obtain
544 * a signed certificate.
545 * @param alias
546 * @param keyPass
547 * @param ks
548 */
549 public static void writeCertReq(String alias,
550 String keyPass,
551 KeyStore ks) {
552 Object objs[] = recoverPrivateKey(ks,
553 alias,
554 keyPass.toCharArray());
555 PrivateKey privKey = (PrivateKey) objs[0];
556 if (keyPass == null)
557 keyPass =
558 new String((char[]) objs[1]);
559 PKCS10 request = null;
560 try {
561 Certificate cert = ks.getCertificate(
562 alias);
563 request = new PKCS10(
564 cert.getPublicKey());
565 String sigAlgName = "MD5WithRSA";
566 Signature signature = Signature.getInstance(
567 sigAlgName);
568 signature.initSign(privKey);
569 X500Name subject = new X500Name(
570 ((X509Certificate) cert).getSubjectDN()
571 .toString());
572 X500Signer signer = new X500Signer(
573 signature, subject);
574 request.encodeAndSign(signer);
575 request.print(System.out);
576 boolean b = In.getBoolean(
577 "would you like to save the certificate request to a file?");
578 if (!b) return;
579 File f = futils.Futil.getWriteFile(
580 "select file.cert");
581 FileOutputStream fos = new FileOutputStream(
582 f);
583 PrintStream ps = new PrintStream(fos);
584 request.print(ps);
585 fos.close();
586 } catch (Exception e) {
587 In.message(e);
588 }
589 }
590
591 private static Object[] recoverPrivateKey(
592 KeyStore ks,
593 String alias,
594 char keyPass[]) {
595 Key key = null;
596 try {
597 key = ks.getKey(alias, keyPass);
598 } catch (Exception e) {
599 In.message(e);
600 }
601 return (new Object[]{
602 (PrivateKey) key, keyPass
603 });
604 }
605
606 /**
607 * Creates the default .keystore file,
608 * assuming that it does not already exist.
609 * Prompts the user to create a key pair.
610 */
611 public static File makeKeyStoreFile() {
612 File f = getDefaultKeyStoreFile();
613 if (f.exists()) {
614 In.message(
615 "file:" + f +
616 " exists, program terminates");
617 System.exit(0);
618 }
619 try {
620 KeyStore ks = KeyUtils.generateKeyStore();
621 generateKeyPair(ks);
622 String pswd = In.getPassword(
623 "enter keystore password");
624 KeyUtils.save(f, ks, pswd);
625 In.message("file:" + f + " created");
626 promptForCertReq(ks, pswd);
627 } catch (Exception e) {
628 In.message(e);
629 }
630 return f;
631 }
632
633 private static void promptForCertReq(
634 KeyStore ks, String pswd) {
635 boolean b = In.getBoolean(
636 "would you like me to generate a certificate request?");
637 if (!b) return;
638 writeCertReq(getAlias(ks), pswd, ks);
639 }
640
641 /**
642 * Check to make sure this file exists.
643 *
644 * @return .keystore file in users home.
645 */
646 public static File getDefaultKeyStoreFile() {
647 return new File(System.getProperty(
648 "user.home") +
649 File.separatorChar +
650 ".keystore");
651 }
652
653
654 private static void testGetAlias() {
655 String password = gui.In.getString(
656 "please enter keystore password");
657 KeyStore keystore = getKeyStore(password);
658 String s[] = getAliasArray(keystore);
659 for (int i = 0; i < s.length; i++)
660 System.out.println("alias:" + s[i]);
661 }
662
663 private static void print(Certificate c) {
664 System.out.println("-----" +
665 "\n" +
666 c.getClass().toString() +
667 "\n" + c);
668 }
669
670 private static void print(Certificate[] c) {
671 for (int i = 0; i < c.length; i++)
672 print(c[i]);
673 }
674
675 /**
676 * Given a keystore instance, provide
677 * a multiple choice GUI that enables the
678 * user to select a certificate alias.
679 * @param keyStore
680 * @return A string version of the certificate alias
681 */
682 public static String getAlias(KeyStore keyStore) {
683 String a[] = getAliasArray(keyStore);
684 return In.multiPrompt(a,
685 "select an alias",
686 "alias dialog")
687 .toString();
688 }
689 /**
690 * Given an X509 Certificate,
691 * print out all the relevant detail.
692 * @param cert
693 * @param out
694 * @throws Exception
695 */
696 public static void printX509Cert(
697 X509Certificate cert,
698 PrintStream out)
699 throws Exception {
700 MessageFormat form = new MessageFormat(
701 rb.getString(
702 "*PATTERN* printX509Cert"));
703 Object source[] = {
704 cert.getSubjectDN().toString(), cert.getIssuerDN()
705 .toString(), cert.getSerialNumber()
706 .toString(16), cert.getNotBefore()
707 .toString(), cert.getNotAfter()
708 .toString(), getCertFingerPrint(
709 "MD5", cert), getCertFingerPrint(
710 "SHA1", cert)
711 };
712 out.println(
713 form.format(((Object) (source))));
714 }
715
716 private static void byte2hex(byte b,
717 StringBuffer buf) {
718 char hexChars[] = {
719 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
720 'A', 'B', 'C', 'D', 'E', 'F'
721 };
722 int high = (b & 0xf0) >> 4;
723 int low = b & 0xf;
724 buf.append(hexChars[high]);
725 buf.append(hexChars[low]);
726 }
727
728 private static String toHexString(
729 byte block[]) {
730 StringBuffer buf = new StringBuffer();
731 int len = block.length;
732 for (int i = 0; i < len; i++) {
733 byte2hex(block[i], buf);
734 if (i < len - 1)
735 buf.append(":");
736 }
737 return buf.toString();
738 }
739
740 private static String getCertFingerPrint(
741 String mdAlg, Certificate cert)
742 throws Exception {
743 byte encCertInfo[] = cert.getEncoded();
744 MessageDigest md = MessageDigest.getInstance(
745 mdAlg);
746 byte digest[] = md.digest(encCertInfo);
747 return toHexString(digest);
748 }
749 /**
750 * Prompt the user for all the details
751 * needed to generate a self-signed
752 * x500 certificate. This is
753 * stored in a datastructure called
754 * the <code>X500Name</code>
755 * @return
756 * @throws IOException
757 */
758 public static X500Name getX500Name()
759 throws IOException {
760 boolean promptP = false;
761 String commonName = "Unknown";
762 String organizationalUnit = "Unknown";
763 String organization = "Unknown";
764 String city = "Unknown";
765 String state = "Unknown";
766 String country = "Unknown";
767 X500Name name;
768 do {
769 commonName =
770 In.getString(
771 "What is your first and last name?");
772 organizationalUnit =
773 In.getString("What is the name of your" +
774 " organizational unit?");
775 organization =
776 In.getString("What is the name " +
777 "of your organization?");
778 city =
779 In.getString(
780 "What is the name " +
781 "of your City or Locality?");
782 state =
783 In.getString(
784 "What is the name of " +
785 "your State or Province?");
786 country =
787 In.getString(
788 "What is the two-letter " +
789 "country code for this unit?");
790 name =
791 new X500Name(commonName,
792 organizationalUnit,
793 organization,
794 city,
795 state,
796 country);
797 promptP =
798 In.getBoolean(
799 "Is " + name + " correct?");
800 } while (!promptP);
801 return name;
802 }
803 }
804