001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.midp.jadtool;
028:
029: import java.io.*;
030: import java.util.*;
031: import java.security.cert.X509Certificate;
032:
033: /**
034: * The JadTool is a command line interface to the AppDescriptor class.
035: *
036: * @see AppDescriptor
037: * @see SignCert
038: * @see java.security.KeyStore
039: */
040: public class JadTool {
041: /** Usage text. */
042: private static String USAGE = "\nJadTool arguments:\n"
043: + "-help\n"
044: + "-addcert\n"
045: + "\t-alias <key alias> [-storepass <password>] "
046: + "[-keystore <keystore>]\n"
047: + "\t[-certnum <number>] [-chainnum <number>]\n"
048: + "\t[-encoding <encoding>] -inputjad <filename> "
049: + "-outputjad <filename>\n"
050: + "-addjarsig\n"
051: + "\t[-jarfile <filename>] -keypass <password> -alias <key alias>\n"
052: + "\t-storepass <password> [-keystore <keystore>] "
053: + "[-encoding <encoding>]\n"
054: + "\t-inputjad <filename> -outputjad <filename>\n"
055: + "-showcert\n"
056: + "\t[([-certnum <number>] [-chainnum <number>]) | [-all]]\n"
057: + "\t[-encoding <encoding>] -inputjad <filename>\n" + "\n"
058: + "The default for -encoding is UTF-8.\n"
059: + "The default for -jarfile is the MIDlet-Jar-URL "
060: + "property in the JAD.\n"
061: + "The default for -keystore is \"$HOME/.keystore\".\n"
062: + "The default for -certnum is 1.\n"
063: + "The default for -chainnum is 1.\n";
064:
065: /** Holds the command given on the command line. */
066: private String command = null;
067: /** Holds the JAD encoding given on the command line. */
068: private String encoding = null;
069: /** Holds the input JAD filename given on the command line. */
070: private String infile = null;
071: /** Holds the output JAD filename given on the command line. */
072: private String outfile = null;
073: /** Holds the JAR filename given on the command line. */
074: private String jarfile = null;
075: /** Holds the keystore filename given on the command line. */
076: private String keystore = null;
077: /** Holds the key alias given on the command line. */
078: private String alias = null;
079: /** Holds the certificate number given on the command line. */
080: private String certnum = null;
081: /** Holds the certificate chain number given on the command line. */
082: private String chainNum = null;
083: /** Holds the keystore password given on the command line. */
084: private char[] storepass = null;
085: /** Holds the private key password given on the command line. */
086: private char[] keypass = null;
087:
088: /** The converted certificate number. */
089: private int certIndex = 1;
090: /** The converted certificate chain number. */
091: private int chainIndex = 1;
092: /** The in-memory JAD. */
093: private AppDescriptor appdesc = null;
094: /** Stream to the output JAD. */
095: private OutputStream outstream;
096:
097: /** Keeps this class from being instantiated by the public. */
098: private JadTool() {
099: }
100:
101: /**
102: * Performs the command specified in the command line arguments.
103: * <p>
104: * Exits with a 0 status if the command was successful.
105: * Exits and prints out an error message with a -1 status if the command
106: * failed.</p>
107: * <pre>
108: * Command Summary
109: * ---------------
110: *
111: * -help - print a usage summary
112: *
113: * -addjarsig - add a SHA1withRSA PKCS v1.5 signature of a jarfile to an
114: * app descriptor file.
115: *
116: * -addcert - add a https or content provider certificate
117: * to an app descriptor file.
118: *
119: * -showcert - show a certificate from the app descriptor
120: * file in human readable form.
121: *
122: * Options
123: * -------
124: *
125: * These options are valid (and in some cases required to be use) with
126: * the commands above.
127: *
128: * -inputjad <original app descriptor file>
129: *
130: * -outputjad <output app descriptor file, can be the same as infile>
131: *
132: * -encoding <encoding type>
133: * (default is UTF-8)
134: *
135: * -jarfile <jar file>
136: *
137: * -keystore <keystore file to use>
138: * (default is .keystore - same as with keytool)
139: *
140: * -storepass <password to unlock chosen keystore>
141: *
142: * -alias <alias of key or certificate in keystore>
143: *
144: * -keypass <password to unlock signing key>
145: *
146: * -certnum <number> (default is "1" or calculated for addcert)
147: *
148: * -chainnum <number> (default is "1")
149: *
150: * -all show all certificates
151: *
152: *
153: * Command Descriptions
154: * --------------------
155: * help: Shows a list of use options.
156: *
157: * addcert: Given a certificate and an unsigned jad file
158: * JadTool will create a base64 encoding of the
159: * certificate file and include it in the jad file as
160: *
161: * MIDlet-Certificate-1-1: <base64 string>
162: *
163: * "base64 string" is a base64 encoding of the certificate.
164: * If the certificate already exists in the jad file
165: * it will not be duplicated.
166: *
167: * showcert: Usage: java JadTool -showcert -certnum <num>
168: * Allows choice of certificate to view using this command.
169: *
170: * addjarsig: Given the alias of a key (stored by the J2SE "keytool"
171: * utility" and an unsigned jad file, the JadTool will
172: * check to see that the jad file contains a
173: * a MIDlet-Certificate-1-1
174: * field. It will then sign the jar file.
175: * In the event that no target jar file is specified
176: * JadTool will hash the jar file located at the URL
177: * specified by the MIDlet-Jar-URL property if no
178: * explicit jar file is given.
179: *</pre>
180: *
181: * @param args command line arguments given by user.
182: */
183: public static void main(String[] args) {
184: int exitStatus = -1;
185:
186: try {
187: new JadTool().run(args);
188: exitStatus = 0;
189: } catch (Exception e) {
190: System.err.println("\n" + e.getMessage() + "\n");
191: }
192:
193: System.exit(exitStatus);
194: }
195:
196: /**
197: * Parses the command line arguments and runs the selected command.
198: * <p>
199: * Does not return.
200: * Exits with a 0 status if the command was successful.
201: * Exits and prints out an error message with a -1 status if the command
202: * failed.</p>
203: *
204: * @param args The command line arguments given to main.
205: *
206: * @exception Exception if there are any errors
207: */
208: private void run(String[] args) throws Exception {
209: if (args.length == 0) {
210: usageError("No command given");
211: }
212:
213: command = args[0];
214:
215: try {
216: if (command.equals("-addjarsig")) {
217: performAddJarSigCommand(args);
218: return;
219: }
220:
221: if (command.equals("-addcert")) {
222: performAddCertCommand(args);
223: return;
224: }
225:
226: if (command.equals("-showcert")) {
227: performShowCertCommand(args);
228: return;
229: }
230:
231: if (command.equals("-help")) {
232: for (int i = 1; i < args.length; i++) {
233: usageError("Illegal option for " + command + ": "
234: + args[i]);
235: }
236:
237: // help exits
238: help();
239: }
240:
241: usageError("Illegal command: " + command);
242: } finally {
243: // zero-out passwords
244: if (storepass != null) {
245: Arrays.fill(storepass, ' ');
246: storepass = null;
247: }
248:
249: if (keypass != null) {
250: Arrays.fill(keypass, ' ');
251: keypass = null;
252: }
253:
254: try {
255: if (outstream != null) {
256: outstream.close();
257: }
258: } catch (IOException ioe) {
259: // do nothing.
260: }
261: }
262: }
263:
264: /**
265: * Perform the -addjarsig command, including parsing the line arguments
266: * for the -addjarsig command.
267: * <p>
268: * If there is a problem parsing an argument, print the error,
269: * print the usage, and exit with a -1.
270: *
271: * @param args The command line arguments given to main.
272: *
273: * @exception Exception if there are any errors
274: */
275: private void performAddJarSigCommand(String[] args)
276: throws Exception {
277: int i = 1;
278:
279: try {
280: for (i = 1; i < args.length; i++) {
281:
282: if (args[i].equals("-encoding")) {
283: encoding = args[++i];
284: } else if (args[i].equals("-keystore")) {
285: keystore = args[++i];
286: } else if (args[i].equals("-storepass")) {
287: storepass = args[++i].toCharArray();
288: } else if (args[i].equals("-keypass")) {
289: keypass = args[++i].toCharArray();
290: } else if (args[i].equals("-alias")) {
291: alias = args[++i];
292: } else if (args[i].equals("-jarfile")) {
293: jarfile = args[++i];
294: } else if (args[i].equals("-inputjad")) {
295: infile = args[++i];
296: } else if (args[i].equals("-outputjad")) {
297: outfile = args[++i];
298: } else {
299: usageError("Illegal option for " + command + ": "
300: + args[i]);
301: }
302: }
303: } catch (ArrayIndexOutOfBoundsException aiobe) {
304: usageError("Missing value for " + args[--i]);
305: }
306:
307: if (keypass == null) {
308: usageError(command + " requires -keypass");
309: }
310:
311: // these methods will check for the presence of the args they need
312: initJadUtil();
313: openKeystoreAndOutputJad();
314:
315: if (jarfile != null) {
316: // a jar file was specified for use
317: FileInputStream jarinput;
318:
319: try {
320: jarinput = new FileInputStream(jarfile);
321: } catch (FileNotFoundException fnfe) {
322: throw new Exception("JAR does not exist: " + jarfile);
323: }
324:
325: try {
326: appdesc.addJarSignature(alias, keypass, jarinput);
327: } catch (Exception e) {
328: throw new Exception(command + " failed: "
329: + e.toString());
330: }
331:
332: try {
333: jarinput.close();
334: } catch (Exception e) {
335: // ignore
336: }
337: } else {
338: // Use the JAR at MIDlet-Jar-URL in the JAD
339: try {
340: appdesc.addJarSignature(alias, keypass);
341: } catch (Exception e) {
342: throw new Exception(command + " failed: "
343: + e.toString());
344: }
345: }
346:
347: appdesc.store(outstream, encoding);
348: return;
349: }
350:
351: /**
352: * Perform the -addcert command, including parsing the line arguments
353: * for the -addcert command.
354: * <p>
355: * If there is a problem parsing an argument, print the error,
356: * print the usage, and exit with a -1.
357: *
358: * @param args The command line arguments given to main.
359: *
360: * @exception Exception if there are any errors
361: */
362: private void performAddCertCommand(String[] args) throws Exception {
363: int i = 1;
364:
365: // change the default for cert number for this command
366: certIndex = 0;
367:
368: try {
369: for (i = 1; i < args.length; i++) {
370:
371: if (args[i].equals("-encoding")) {
372: encoding = args[++i];
373: } else if (args[i].equals("-keystore")) {
374: keystore = args[++i];
375: } else if (args[i].equals("-storepass")) {
376: storepass = args[++i].toCharArray();
377: } else if (args[i].equals("-alias")) {
378: alias = args[++i];
379: } else if (args[i].equals("-certnum")) {
380: certnum = args[++i];
381: } else if (args[i].equals("-chainnum")) {
382: chainNum = args[++i];
383: } else if (args[i].equals("-inputjad")) {
384: infile = args[++i];
385: } else if (args[i].equals("-outputjad")) {
386: outfile = args[++i];
387: } else {
388: usageError("Illegal option for " + command + ": "
389: + args[i]);
390: }
391: }
392: } catch (ArrayIndexOutOfBoundsException aiobe) {
393: usageError("Missing value for " + args[--i]);
394: }
395:
396: // these methods will check for the presence of the args they need
397: checkCertAndChainNum();
398: initJadUtil();
399: openKeystoreAndOutputJad();
400:
401: try {
402: appdesc.addCert(alias, chainIndex, certIndex);
403: appdesc.store(outstream, encoding);
404: return;
405: } catch (Exception e) {
406: throw new Exception(command + " failed: " + e.toString());
407: }
408: }
409:
410: /**
411: * Perform the -showcert command, including parsing the line arguments
412: * for the -showcert command.
413: * <p>
414: * If there is a problem parsing an argument, print the error,
415: * print the usage, and exit with a -1.
416: *
417: * @param args The command line arguments given to main.
418: *
419: * @exception Exception if there are any errors
420: */
421: private void performShowCertCommand(String[] args) throws Exception {
422: int i = 1;
423: X509Certificate c;
424: boolean listAll = false;
425:
426: try {
427: for (i = 1; i < args.length; i++) {
428:
429: if (args[i].equals("-encoding")) {
430: encoding = args[++i];
431: } else if (args[i].equals("-certnum")) {
432: certnum = args[++i];
433: } else if (args[i].equals("-chainnum")) {
434: chainNum = args[++i];
435: } else if (args[i].equals("-all")) {
436: listAll = true;
437: } else if (args[i].equals("-inputjad")) {
438: infile = args[++i];
439: } else {
440: usageError("Illegal option for " + command + ": "
441: + args[i]);
442: }
443: }
444: } catch (ArrayIndexOutOfBoundsException aiobe) {
445: usageError("Missing value for " + args[--i]);
446: }
447:
448: if (listAll && (chainNum != null || certnum != null)) {
449: usageError("-all cannot be used with -certnum or -chainnum");
450: }
451:
452: // these methods will check for the presence of the args they need
453: checkCertAndChainNum();
454: initJadUtil();
455:
456: if (listAll) {
457: Vector certs = appdesc.getAllCerts();
458:
459: if (certs.size() == 0) {
460: System.out.println("\nNo certificates found in JAD.\n");
461: return;
462: }
463:
464: System.out.println();
465:
466: for (i = 0; i < certs.size(); i++) {
467: Object[] temp = (Object[]) certs.elementAt(i);
468:
469: System.out.println((String) temp[AppDescriptor.KEY]
470: + ":");
471:
472: displayCert((X509Certificate) temp[AppDescriptor.CERT]);
473: }
474:
475: return;
476: }
477:
478: try {
479: c = appdesc.getCert(chainIndex, certIndex);
480: } catch (Exception e) {
481: throw new Exception("-showcert failed: " + e.toString());
482: }
483:
484: if (c == null) {
485: throw new Exception("Certificate " + chainIndex + "-"
486: + certIndex + " not in JAD");
487: }
488:
489: try {
490: displayCert(c);
491: return;
492: } catch (Exception e) {
493: throw new Exception("-showcert failed: " + e.toString());
494: }
495: }
496:
497: /**
498: * Check the format of the certificate and chain numbers. If there is a
499: * problem, print the error, print usage, and exit with -1.
500: */
501: private void checkCertAndChainNum() {
502: if (certnum != null) {
503: try {
504: certIndex = (Integer.valueOf(certnum)).intValue();
505: if (certIndex <= 0) {
506: usageError("-certnum must be a positive number");
507: }
508: } catch (NumberFormatException nfe) {
509: usageError("-certnum must be a positive number");
510: }
511: }
512:
513: if (chainNum != null) {
514: try {
515: chainIndex = (Integer.valueOf(chainNum)).intValue();
516: if (chainIndex <= 0) {
517: usageError("-chainnum must be a positive number");
518: }
519: } catch (NumberFormatException nfe) {
520: usageError("-chainnum must be a positive number");
521: }
522: }
523: }
524:
525: /**
526: * Initializes an instance of the AppDescriptor class.
527: * <p>
528: * If the input file has not been specified, print an error and the usage,
529: * then exit with a -1
530: *
531: * @exception Exception if there are any errors
532: */
533: private void initJadUtil() throws Exception {
534: InputStream instream;
535:
536: if (infile == null) {
537: usageError(command + " requires an input JAD");
538: }
539:
540: try {
541: FileInputStream fis = new FileInputStream(infile);
542: instream = new BufferedInputStream(fis);
543: } catch (FileNotFoundException fnfe) {
544: throw new Exception("Input JAD does not exist: " + infile);
545: }
546:
547: try {
548: appdesc = new AppDescriptor();
549: appdesc.load(instream, encoding);
550: } catch (UnsupportedEncodingException uee) {
551: throw new Exception("Encoding type " + encoding
552: + " not supported");
553: } catch (IOException ioe) {
554: throw new Exception("Error parsing input JAD: " + infile);
555: } finally {
556: try {
557: // close now so the input and output JAD can be the same.
558: instream.close();
559: } catch (Exception e) {
560: // ignore
561: }
562: }
563: }
564:
565: /**
566: * Open the keystore and output JAD file.
567: * <p>
568: * If the key alias or output file has not been specified, print an
569: * error and the usage, then exit with a -1
570: *
571: * @exception Exception if there are any errors
572: */
573: private void openKeystoreAndOutputJad() throws Exception {
574: File ksfile;
575: FileInputStream ksstream;
576:
577: if (alias == null) {
578: usageError(command + " requires -alias");
579: }
580:
581: if (outfile == null) {
582: usageError(command + " requires an output JAD");
583: }
584:
585: if (keystore == null) {
586: keystore = System.getProperty("user.home") + File.separator
587: + ".keystore";
588: }
589:
590: try {
591: ksfile = new File(keystore);
592: // Check if keystore file is empty
593: if (ksfile.exists() && ksfile.length() == 0) {
594: throw new Exception("Keystore exists, but is empty: "
595: + keystore);
596: }
597:
598: ksstream = new FileInputStream(ksfile);
599: } catch (FileNotFoundException fnfe) {
600: throw new Exception("Keystore does not exist: " + keystore);
601: }
602:
603: try {
604: try {
605: // the stream will be closed later
606: outstream = new FileOutputStream(outfile);
607: } catch (IOException ioe) {
608: throw new Exception("Error opening output JAD: "
609: + outfile);
610: }
611:
612: try {
613: // load the keystore into the AppDescriptor
614: appdesc.loadKeyStore(ksstream, storepass);
615: } catch (Exception e) {
616: throw new Exception("Keystore could not be loaded: "
617: + e.toString());
618: }
619: } finally {
620: try {
621: ksstream.close();
622: } catch (IOException e) {
623: // ignore
624: }
625: }
626: }
627:
628: /**
629: * Prints the usage cases of this tool and exits with 0 status.
630: */
631: private void help() {
632: usage(0);
633: }
634:
635: /**
636: * Prints the usage cases of this tool and exits with -1 status.
637: *
638: * @param error usage error message to print
639: */
640: private void usageError(String error) {
641: System.err.println("\n" + error);
642: usage(-1);
643: }
644:
645: /**
646: * Prints the usage cases of this tool and exits.
647: *
648: * @param exitStatus status to exit with
649: */
650: private void usage(int exitStatus) {
651: System.out.println(USAGE);
652: System.exit(exitStatus);
653: }
654:
655: /**
656: * Display a certificate.
657: *
658: * @param c certificate to display
659: *
660: * @exception Exception if there are any errors
661: */
662: private void displayCert(X509Certificate c) throws Exception {
663: String digest;
664:
665: System.out.println();
666:
667: System.out.println("Subject: " + c.getSubjectDN().getName());
668:
669: System.out.println("Issuer : " + c.getIssuerDN().getName());
670:
671: System.out.println("Serial number: "
672: + c.getSerialNumber().toString(16));
673:
674: System.out.println("Valid from " + c.getNotBefore() + " to "
675: + c.getNotAfter());
676:
677: System.out.println("Certificate fingerprints:");
678:
679: System.out.print(" MD5: ");
680: digest = AppDescriptor.createFingerprint(c.getEncoded(), "MD5");
681: System.out.println(digest);
682:
683: System.out.print(" SHA: ");
684: digest = AppDescriptor.createFingerprint(c.getEncoded(), "SHA");
685: System.out.println(digest);
686:
687: System.out.println();
688: }
689: }
|