10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.security.tools.keytool;
27
28 import java.io.*;
29 import java.security.CodeSigner;
30 import java.security.KeyStore;
31 import java.security.KeyStoreException;
32 import java.security.MessageDigest;
33 import java.security.Key;
34 import java.security.PublicKey;
35 import java.security.PrivateKey;
36 import java.security.Signature;
37 import java.security.Timestamp;
38 import java.security.UnrecoverableEntryException;
39 import java.security.UnrecoverableKeyException;
40 import java.security.Principal;
41 import java.security.cert.Certificate;
42 import java.security.cert.CertificateFactory;
43 import java.security.cert.CertStoreException;
44 import java.security.cert.CRL;
45 import java.security.cert.X509Certificate;
46 import java.security.cert.CertificateException;
47 import java.security.cert.URICertStoreParameters;
48
49
139 private char[] storePassNew = null;
140 private char[] keyPass = null;
141 private char[] keyPassNew = null;
142 private char[] newPass = null;
143 private char[] destKeyPass = null;
144 private char[] srckeyPass = null;
145 private String ksfname = null;
146 private File ksfile = null;
147 private InputStream ksStream = null; // keystore stream
148 private String sslserver = null;
149 private String jarfile = null;
150 private KeyStore keyStore = null;
151 private boolean token = false;
152 private boolean nullStream = false;
153 private boolean kssave = false;
154 private boolean noprompt = false;
155 private boolean trustcacerts = false;
156 private boolean protectedPath = false;
157 private boolean srcprotectedPath = false;
158 private boolean cacerts = false;
159 private CertificateFactory cf = null;
160 private KeyStore caks = null; // "cacerts" keystore
161 private char[] srcstorePass = null;
162 private String srcstoretype = null;
163 private Set<char[]> passwords = new HashSet<>();
164 private String startDate = null;
165
166 private List<String> ids = new ArrayList<>(); // used in GENCRL
167 private List<String> v3ext = new ArrayList<>();
168
169 enum Command {
170 CERTREQ("Generates.a.certificate.request",
171 ALIAS, SIGALG, FILEOUT, KEYPASS, KEYSTORE, DNAME,
172 STOREPASS, STORETYPE, PROVIDERNAME, ADDPROVIDER,
173 PROVIDERCLASS, PROVIDERPATH, V, PROTECTED),
174 CHANGEALIAS("Changes.an.entry.s.alias",
175 ALIAS, DESTALIAS, KEYPASS, KEYSTORE, CACERTS, STOREPASS,
176 STORETYPE, PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
177 PROVIDERPATH, V, PROTECTED),
178 DELETE("Deletes.an.entry",
179 ALIAS, KEYSTORE, CACERTS, STOREPASS, STORETYPE,
180 PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
181 PROVIDERPATH, V, PROTECTED),
182 EXPORTCERT("Exports.certificate",
183 RFC, ALIAS, FILEOUT, KEYSTORE, CACERTS, STOREPASS,
184 STORETYPE, PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
185 PROVIDERPATH, V, PROTECTED),
186 GENKEYPAIR("Generates.a.key.pair",
187 ALIAS, KEYALG, KEYSIZE, SIGALG, DESTALIAS, DNAME,
188 STARTDATE, EXT, VALIDITY, KEYPASS, KEYSTORE,
334 STORETYPE("storetype", "<type>", "keystore.type"),
335 TRUSTCACERTS("trustcacerts", null, "trust.certificates.from.cacerts"),
336 V("v", null, "verbose.output"),
337 VALIDITY("validity", "<days>", "validity.number.of.days");
338
339 final String name, arg, description;
340 Option(String name, String arg, String description) {
341 this.name = name;
342 this.arg = arg;
343 this.description = description;
344 }
345 @Override
346 public String toString() {
347 return "-" + name;
348 }
349 };
350
351 private static final String NONE = "NONE";
352 private static final String P11KEYSTORE = "PKCS11";
353 private static final String P12KEYSTORE = "PKCS12";
354 private final String keyAlias = "mykey";
355
356 // for i18n
357 private static final java.util.ResourceBundle rb =
358 java.util.ResourceBundle.getBundle(
359 "sun.security.tools.keytool.Resources");
360 private static final Collator collator = Collator.getInstance();
361 static {
362 // this is for case insensitive string comparisons
363 collator.setStrength(Collator.PRIMARY);
364 };
365
366 private Main() { }
367
368 public static void main(String[] args) throws Exception {
369 Main kt = new Main();
370 kt.run(args, System.out);
371 }
372
373 private void run(String[] args, PrintStream out) throws Exception {
374 try {
375 args = parseArgs(args);
376 if (command != null) {
377 doCommands(out);
378 }
379 } catch (Exception e) {
380 System.out.println(rb.getString("keytool.error.") + e);
381 if (verbose) {
382 e.printStackTrace(System.out);
383 }
384 if (!debug) {
385 System.exit(1);
386 } else {
387 throw e;
388 }
389 } finally {
390 for (char[] pass : passwords) {
391 if (pass != null) {
392 Arrays.fill(pass, ' ');
393 pass = null;
394 }
395 }
396
397 if (ksStream != null) {
398 ksStream.close();
399 }
400 }
401 }
402
403 /**
404 * Parse command line arguments.
405 */
406 String[] parseArgs(String[] args) throws Exception {
407
408 int i=0;
409 boolean help = args.length == 0;
459 * Check modifiers
460 */
461 String modifier = null;
462 int pos = flags.indexOf(':');
463 if (pos > 0) {
464 modifier = flags.substring(pos+1);
465 flags = flags.substring(0, pos);
466 }
467
468 /*
469 * command modes
470 */
471 Command c = Command.getCommand(flags);
472
473 if (c != null) {
474 command = c;
475 } else if (collator.compare(flags, "-help") == 0) {
476 help = true;
477 } else if (collator.compare(flags, "-conf") == 0) {
478 i++;
479 } else if (collator.compare(flags, "-keystore") == 0) {
480 ksfname = args[++i];
481 if (new File(ksfname).getCanonicalPath().equals(
482 new File(KeyStoreUtil.getCacerts()).getCanonicalPath())) {
483 System.err.println(rb.getString("warning.cacerts.option"));
484 }
485 } else if (collator.compare(flags, "-destkeystore") == 0) {
486 ksfname = args[++i];
487 } else if (collator.compare(flags, "-cacerts") == 0) {
488 cacerts = true;
489 } else if (collator.compare(flags, "-storepass") == 0 ||
490 collator.compare(flags, "-deststorepass") == 0) {
491 storePass = getPass(modifier, args[++i]);
492 passwords.add(storePass);
493 } else if (collator.compare(flags, "-storetype") == 0 ||
494 collator.compare(flags, "-deststoretype") == 0) {
495 storetype = args[++i];
496 hasStoretypeOption = true;
497 } else if (collator.compare(flags, "-srcstorepass") == 0) {
498 srcstorePass = getPass(modifier, args[++i]);
1135
1136 doCloneEntry(alias, dest, true); // Now everything can be cloned
1137 kssave = true;
1138 } else if (command == CHANGEALIAS) {
1139 if (alias == null) {
1140 alias = keyAlias;
1141 }
1142 doCloneEntry(alias, dest, false);
1143 // in PKCS11, clone a PrivateKeyEntry will delete the old one
1144 if (keyStore.containsAlias(alias)) {
1145 doDeleteEntry(alias);
1146 }
1147 kssave = true;
1148 } else if (command == KEYPASSWD) {
1149 keyPassNew = newPass;
1150 doChangeKeyPasswd(alias);
1151 kssave = true;
1152 } else if (command == LIST) {
1153 if (storePass == null
1154 && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
1155 printWarning();
1156 }
1157
1158 if (alias != null) {
1159 doPrintEntry(alias, out);
1160 } else {
1161 doPrintEntries(out);
1162 }
1163 } else if (command == PRINTCERT) {
1164 doPrintCert(out);
1165 } else if (command == SELFCERT) {
1166 doSelfCert(alias, dname, sigAlgName);
1167 kssave = true;
1168 } else if (command == STOREPASSWD) {
1169 storePassNew = newPass;
1170 if (storePassNew == null) {
1171 storePassNew = getNewPasswd("keystore password", storePass);
1172 }
1173 kssave = true;
1174 } else if (command == GENCERT) {
1175 if (alias == null) {
1176 alias = keyAlias;
1177 }
1178 InputStream inStream = System.in;
1179 if (infilename != null) {
1236 } else {
1237 ByteArrayOutputStream bout = new ByteArrayOutputStream();
1238 keyStore.store(bout, pass);
1239 try (FileOutputStream fout = new FileOutputStream(ksfname)) {
1240 fout.write(bout.toByteArray());
1241 }
1242 }
1243 }
1244 }
1245 }
1246
1247 /**
1248 * Generate a certificate: Read PKCS10 request from in, and print
1249 * certificate to out. Use alias as CA, sigAlgName as the signature
1250 * type.
1251 */
1252 private void doGenCert(String alias, String sigAlgName, InputStream in, PrintStream out)
1253 throws Exception {
1254
1255
1256 Certificate signerCert = keyStore.getCertificate(alias);
1257 byte[] encoded = signerCert.getEncoded();
1258 X509CertImpl signerCertImpl = new X509CertImpl(encoded);
1259 X509CertInfo signerCertInfo = (X509CertInfo)signerCertImpl.get(
1260 X509CertImpl.NAME + "." + X509CertImpl.INFO);
1261 X500Name issuer = (X500Name)signerCertInfo.get(X509CertInfo.SUBJECT + "." +
1262 X509CertInfo.DN_NAME);
1263
1264 Date firstDate = getStartDate(startDate);
1265 Date lastDate = new Date();
1266 lastDate.setTime(firstDate.getTime() + validity*1000L*24L*60L*60L);
1267 CertificateValidity interval = new CertificateValidity(firstDate,
1268 lastDate);
1269
1270 PrivateKey privateKey =
1271 (PrivateKey)recoverKey(alias, storePass, keyPass).fst;
1272 if (sigAlgName == null) {
1273 sigAlgName = getCompatibleSigAlgName(privateKey);
1274 }
1275 Signature signature = Signature.getInstance(sigAlgName);
1289 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
1290 boolean canRead = false;
1291 StringBuffer sb = new StringBuffer();
1292 while (true) {
1293 String s = reader.readLine();
1294 if (s == null) break;
1295 // OpenSSL does not use NEW
1296 //if (s.startsWith("-----BEGIN NEW CERTIFICATE REQUEST-----")) {
1297 if (s.startsWith("-----BEGIN") && s.indexOf("REQUEST") >= 0) {
1298 canRead = true;
1299 //} else if (s.startsWith("-----END NEW CERTIFICATE REQUEST-----")) {
1300 } else if (s.startsWith("-----END") && s.indexOf("REQUEST") >= 0) {
1301 break;
1302 } else if (canRead) {
1303 sb.append(s);
1304 }
1305 }
1306 byte[] rawReq = Pem.decode(new String(sb));
1307 PKCS10 req = new PKCS10(rawReq);
1308
1309 info.set(X509CertInfo.KEY, new CertificateX509Key(req.getSubjectPublicKeyInfo()));
1310 info.set(X509CertInfo.SUBJECT,
1311 dname==null?req.getSubjectName():new X500Name(dname));
1312 CertificateExtensions reqex = null;
1313 Iterator<PKCS10Attribute> attrs = req.getAttributes().getAttributes().iterator();
1314 while (attrs.hasNext()) {
1315 PKCS10Attribute attr = attrs.next();
1316 if (attr.getAttributeId().equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) {
1317 reqex = (CertificateExtensions)attr.getAttributeValue();
1318 }
1319 }
1320 CertificateExtensions ext = createV3Extensions(
1321 reqex,
1322 null,
1323 v3ext,
1324 req.getSubjectPublicKeyInfo(),
1325 signerCert.getPublicKey());
1326 info.set(X509CertInfo.EXTENSIONS, ext);
1327 X509CertImpl cert = new X509CertImpl(info);
1328 cert.sign(privateKey, sigAlgName);
1329 dumpCert(cert, out);
1330 for (Certificate ca: keyStore.getCertificateChain(alias)) {
1331 if (ca instanceof X509Certificate) {
1332 X509Certificate xca = (X509Certificate)ca;
1333 if (!KeyStoreUtil.isSelfSigned(xca)) {
1334 dumpCert(xca, out);
1335 }
1336 }
1337 }
1338 }
1339
1340 private void doGenCRL(PrintStream out)
1341 throws Exception {
1342 if (ids == null) {
1343 throw new Exception("Must provide -id when -gencrl");
1344 }
1345 Certificate signerCert = keyStore.getCertificate(alias);
1346 byte[] encoded = signerCert.getEncoded();
1347 X509CertImpl signerCertImpl = new X509CertImpl(encoded);
1348 X509CertInfo signerCertInfo = (X509CertInfo)signerCertImpl.get(
1349 X509CertImpl.NAME + "." + X509CertImpl.INFO);
1350 X500Name owner = (X500Name)signerCertInfo.get(X509CertInfo.SUBJECT + "." +
1351 X509CertInfo.DN_NAME);
1352
1353 Date firstDate = getStartDate(startDate);
1354 Date lastDate = (Date) firstDate.clone();
1355 lastDate.setTime(lastDate.getTime() + validity*1000*24*60*60);
1356 CertificateValidity interval = new CertificateValidity(firstDate,
1357 lastDate);
1368 String id = ids.get(i);
1369 int d = id.indexOf(':');
1370 if (d >= 0) {
1371 CRLExtensions ext = new CRLExtensions();
1372 ext.set("Reason", new CRLReasonCodeExtension(Integer.parseInt(id.substring(d+1))));
1373 badCerts[i] = new X509CRLEntryImpl(new BigInteger(id.substring(0, d)),
1374 firstDate, ext);
1375 } else {
1376 badCerts[i] = new X509CRLEntryImpl(new BigInteger(ids.get(i)), firstDate);
1377 }
1378 }
1379 X509CRLImpl crl = new X509CRLImpl(owner, firstDate, lastDate, badCerts);
1380 crl.sign(privateKey, sigAlgName);
1381 if (rfc) {
1382 out.println("-----BEGIN X509 CRL-----");
1383 out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(crl.getEncodedInternal()));
1384 out.println("-----END X509 CRL-----");
1385 } else {
1386 out.write(crl.getEncodedInternal());
1387 }
1388 }
1389
1390 /**
1391 * Creates a PKCS#10 cert signing request, corresponding to the
1392 * keys (and name) associated with a given alias.
1393 */
1394 private void doCertReq(String alias, String sigAlgName, PrintStream out)
1395 throws Exception
1396 {
1397 if (alias == null) {
1398 alias = keyAlias;
1399 }
1400
1401 Pair<Key,char[]> objs = recoverKey(alias, storePass, keyPass);
1402 PrivateKey privKey = (PrivateKey)objs.fst;
1403 if (keyPass == null) {
1404 keyPass = objs.snd;
1405 }
1406
1407 Certificate cert = keyStore.getCertificate(alias);
1414 PKCS10 request = new PKCS10(cert.getPublicKey());
1415 CertificateExtensions ext = createV3Extensions(null, null, v3ext, cert.getPublicKey(), null);
1416 // Attribute name is not significant
1417 request.getAttributes().setAttribute(X509CertInfo.EXTENSIONS,
1418 new PKCS10Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID, ext));
1419
1420 // Construct a Signature object, so that we can sign the request
1421 if (sigAlgName == null) {
1422 sigAlgName = getCompatibleSigAlgName(privKey);
1423 }
1424
1425 Signature signature = Signature.getInstance(sigAlgName);
1426 signature.initSign(privKey);
1427 X500Name subject = dname == null?
1428 new X500Name(((X509Certificate)cert).getSubjectDN().toString()):
1429 new X500Name(dname);
1430
1431 // Sign the request and base-64 encode it
1432 request.encodeAndSign(subject, signature);
1433 request.print(out);
1434 }
1435
1436 /**
1437 * Deletes an entry from the keystore.
1438 */
1439 private void doDeleteEntry(String alias) throws Exception {
1440 if (keyStore.containsAlias(alias) == false) {
1441 MessageFormat form = new MessageFormat
1442 (rb.getString("Alias.alias.does.not.exist"));
1443 Object[] source = {alias};
1444 throw new Exception(form.format(source));
1445 }
1446 keyStore.deleteEntry(alias);
1447 }
1448
1449 /**
1450 * Exports a certificate from the keystore.
1451 */
1452 private void doExportCert(String alias, PrintStream out)
1453 throws Exception
1454 {
1455 if (storePass == null
1456 && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
1457 printWarning();
1458 }
1459 if (alias == null) {
1460 alias = keyAlias;
1461 }
1462 if (keyStore.containsAlias(alias) == false) {
1463 MessageFormat form = new MessageFormat
1464 (rb.getString("Alias.alias.does.not.exist"));
1465 Object[] source = {alias};
1466 throw new Exception(form.format(source));
1467 }
1468
1469 X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias);
1470 if (cert == null) {
1471 MessageFormat form = new MessageFormat
1472 (rb.getString("Alias.alias.has.no.certificate"));
1473 Object[] source = {alias};
1474 throw new Exception(form.format(source));
1475 }
1476 dumpCert(cert, out);
1477 }
1478
1479 /**
1480 * Prompt the user for a keypass when generating a key entry.
1481 * @param alias the entry we will set password for
1482 * @param orig the original entry of doing a dup, null if generate new
1483 * @param origPass the password to copy from if user press ENTER
1484 */
1485 private char[] promptForKeyPass(String alias, String orig, char[] origPass) throws Exception{
1486 if (P12KEYSTORE.equalsIgnoreCase(storetype)) {
1487 return origPass;
1488 } else if (!token && !protectedPath) {
1489 // Prompt for key password
1490 int count;
1491 for (count = 0; count < 3; count++) {
1492 MessageFormat form = new MessageFormat(rb.getString
1493 ("Enter.key.password.for.alias."));
1494 Object[] source = {alias};
1495 System.err.println(form.format(source));
1496 if (orig == null) {
1712
1713 X509Certificate[] chain = new X509Certificate[1];
1714 chain[0] = keypair.getSelfCertificate(
1715 x500Name, getStartDate(startDate), validity*24L*60L*60L, ext);
1716
1717 if (verbose) {
1718 MessageFormat form = new MessageFormat(rb.getString
1719 ("Generating.keysize.bit.keyAlgName.key.pair.and.self.signed.certificate.sigAlgName.with.a.validity.of.validality.days.for"));
1720 Object[] source = {keysize,
1721 privKey.getAlgorithm(),
1722 chain[0].getSigAlgName(),
1723 validity,
1724 x500Name};
1725 System.err.println(form.format(source));
1726 }
1727
1728 if (keyPass == null) {
1729 keyPass = promptForKeyPass(alias, null, storePass);
1730 }
1731 keyStore.setKeyEntry(alias, privKey, keyPass, chain);
1732 }
1733
1734 /**
1735 * Clones an entry
1736 * @param orig original alias
1737 * @param dest destination alias
1738 * @changePassword if the password can be changed
1739 */
1740 private void doCloneEntry(String orig, String dest, boolean changePassword)
1741 throws Exception
1742 {
1743 if (orig == null) {
1744 orig = keyAlias;
1745 }
1746
1747 if (keyStore.containsAlias(dest)) {
1748 MessageFormat form = new MessageFormat
1749 (rb.getString("Destination.alias.dest.already.exists"));
1750 Object[] source = {dest};
1751 throw new Exception(form.format(source));
1793 }
1794 keyStore.setKeyEntry(alias, privKey, keyPassNew,
1795 keyStore.getCertificateChain(alias));
1796 }
1797
1798 /**
1799 * Imports a JDK 1.1-style identity database. We can only store one
1800 * certificate per identity, because we use the identity's name as the
1801 * alias (which references a keystore entry), and aliases must be unique.
1802 */
1803 private void doImportIdentityDatabase(InputStream in)
1804 throws Exception
1805 {
1806 System.err.println(rb.getString
1807 ("No.entries.from.identity.database.added"));
1808 }
1809
1810 /**
1811 * Prints a single keystore entry.
1812 */
1813 private void doPrintEntry(String alias, PrintStream out)
1814 throws Exception
1815 {
1816 if (keyStore.containsAlias(alias) == false) {
1817 MessageFormat form = new MessageFormat
1818 (rb.getString("Alias.alias.does.not.exist"));
1819 Object[] source = {alias};
1820 throw new Exception(form.format(source));
1821 }
1822
1823 if (verbose || rfc || debug) {
1824 MessageFormat form = new MessageFormat
1825 (rb.getString("Alias.name.alias"));
1826 Object[] source = {alias};
1827 out.println(form.format(source));
1828
1829 if (!token) {
1830 form = new MessageFormat(rb.getString
1831 ("Creation.date.keyStore.getCreationDate.alias."));
1832 Object[] src = {keyStore.getCreationDate(alias)};
1833 out.println(form.format(src));
1864 }
1865
1866 // Get the chain
1867 Certificate[] chain = keyStore.getCertificateChain(alias);
1868 if (chain != null) {
1869 if (verbose || rfc || debug) {
1870 out.println(rb.getString
1871 ("Certificate.chain.length.") + chain.length);
1872 for (int i = 0; i < chain.length; i ++) {
1873 MessageFormat form = new MessageFormat
1874 (rb.getString("Certificate.i.1."));
1875 Object[] source = {(i + 1)};
1876 out.println(form.format(source));
1877 if (verbose && (chain[i] instanceof X509Certificate)) {
1878 printX509Cert((X509Certificate)(chain[i]), out);
1879 } else if (debug) {
1880 out.println(chain[i].toString());
1881 } else {
1882 dumpCert(chain[i], out);
1883 }
1884 }
1885 } else {
1886 // Print the digest of the user cert only
1887 out.println
1888 (rb.getString("Certificate.fingerprint.SHA.256.") +
1889 getCertFingerPrint("SHA-256", chain[0]));
1890 }
1891 }
1892 } else if (keyStore.entryInstanceOf(alias,
1893 KeyStore.TrustedCertificateEntry.class)) {
1894 // We have a trusted certificate entry
1895 Certificate cert = keyStore.getCertificate(alias);
1896 Object[] source = {"trustedCertEntry"};
1897 String mf = new MessageFormat(
1898 rb.getString("Entry.type.type.")).format(source) + "\n";
1899 if (verbose && (cert instanceof X509Certificate)) {
1900 out.println(mf);
1901 printX509Cert((X509Certificate)cert, out);
1902 } else if (rfc) {
1903 out.println(mf);
1904 dumpCert(cert, out);
1905 } else if (debug) {
1906 out.println(cert.toString());
1907 } else {
1908 out.println("trustedCertEntry, ");
1909 out.println(rb.getString("Certificate.fingerprint.SHA.256.")
1910 + getCertFingerPrint("SHA-256", cert));
1911 }
1912 } else {
1913 out.println(rb.getString("Unknown.Entry.Type"));
1914 }
1915 }
1916
1917 /**
1918 * Load the srckeystore from a stream, used in -importkeystore
1919 * @return the src KeyStore
1920 */
1921 KeyStore loadSourceKeyStore() throws Exception {
1922 boolean isPkcs11 = false;
1923
1924 InputStream is = null;
1925 File srcksfile = null;
1926
1927 if (P11KEYSTORE.equalsIgnoreCase(srcstoretype) ||
1928 KeyStoreUtil.isWindowsKeyStore(srcstoretype)) {
1929 if (!NONE.equals(srcksfname)) {
1930 System.err.println(MessageFormat.format(rb.getString
1931 (".keystore.must.be.NONE.if.storetype.is.{0}"), srcstoretype));
1975 if (P12KEYSTORE.equalsIgnoreCase(srcstoretype)) {
1976 if (srckeyPass != null && srcstorePass != null &&
1977 !Arrays.equals(srcstorePass, srckeyPass)) {
1978 MessageFormat form = new MessageFormat(rb.getString(
1979 "Warning.Different.store.and.key.passwords.not.supported.for.PKCS12.KeyStores.Ignoring.user.specified.command.value."));
1980 Object[] source = {"-srckeypass"};
1981 System.err.println(form.format(source));
1982 srckeyPass = srcstorePass;
1983 }
1984 }
1985
1986 store.load(is, srcstorePass); // "is" already null in PKCS11
1987 } finally {
1988 if (is != null) {
1989 is.close();
1990 }
1991 }
1992
1993 if (srcstorePass == null
1994 && !KeyStoreUtil.isWindowsKeyStore(srcstoretype)) {
1995 // anti refactoring, copied from printWarning(),
1996 // but change 2 lines
1997 System.err.println();
1998 System.err.println(rb.getString
1999 (".WARNING.WARNING.WARNING."));
2000 System.err.println(rb.getString
2001 (".The.integrity.of.the.information.stored.in.the.srckeystore."));
2002 System.err.println(rb.getString
2003 (".WARNING.WARNING.WARNING."));
2004 System.err.println();
2005 }
2006
2007 return store;
2008 }
2009
2010 /**
2011 * import all keys and certs from importkeystore.
2012 * keep alias unchanged if no name conflict, otherwise, prompt.
2013 * keep keypass unchanged for keys
2014 */
2015 private void doImportKeyStore() throws Exception {
2098 MessageFormat form = new MessageFormat(rb.getString(
2099 "Problem.importing.entry.for.alias.alias.exception.Entry.for.alias.alias.not.imported."));
2100 System.err.println(form.format(source2));
2101 return 2;
2102 }
2103 }
2104
2105 private void doImportKeyStoreAll(KeyStore srckeystore) throws Exception {
2106
2107 int ok = 0;
2108 int count = srckeystore.size();
2109 for (Enumeration<String> e = srckeystore.aliases();
2110 e.hasMoreElements(); ) {
2111 String alias = e.nextElement();
2112 int result = doImportKeyStoreSingle(srckeystore, alias);
2113 if (result == 1) {
2114 ok++;
2115 Object[] source = {alias};
2116 MessageFormat form = new MessageFormat(rb.getString("Entry.for.alias.alias.successfully.imported."));
2117 System.err.println(form.format(source));
2118 } else if (result == 2) {
2119 if (!noprompt) {
2120 String reply = getYesNoReply("Do you want to quit the import process? [no]: ");
2121 if ("YES".equals(reply)) {
2122 break;
2123 }
2124 }
2125 }
2126 }
2127 Object[] source = {ok, count-ok};
2128 MessageFormat form = new MessageFormat(rb.getString(
2129 "Import.command.completed.ok.entries.successfully.imported.fail.entries.failed.or.cancelled"));
2130 System.err.println(form.format(source));
2131 }
2132
2133 /**
2134 * Prints all keystore entries.
2135 */
2136 private void doPrintEntries(PrintStream out)
2137 throws Exception
2138 {
2139 out.println(rb.getString("Keystore.type.") + keyStore.getType());
2140 out.println(rb.getString("Keystore.provider.") +
2141 keyStore.getProvider().getName());
2142 out.println();
2143
2144 MessageFormat form;
2145 form = (keyStore.size() == 1) ?
2146 new MessageFormat(rb.getString
2147 ("Your.keystore.contains.keyStore.size.entry")) :
2148 new MessageFormat(rb.getString
2149 ("Your.keystore.contains.keyStore.size.entries"));
2150 Object[] source = {keyStore.size()};
2151 out.println(form.format(source));
2152 out.println();
2153
2154 for (Enumeration<String> e = keyStore.aliases();
2155 e.hasMoreElements(); ) {
2156 String alias = e.nextElement();
2157 doPrintEntry(alias, out);
2158 if (verbose || rfc) {
2159 out.println(rb.getString("NEWLINE"));
2160 out.println(rb.getString
2161 ("STAR"));
2162 out.println(rb.getString
2163 ("STARNN"));
2164 }
2165 }
2166 }
2167
2168 private static <T> Iterable<T> e2i(final Enumeration<T> e) {
2169 return new Iterable<T>() {
2170 @Override
2171 public Iterator<T> iterator() {
2172 return new Iterator<T>() {
2173 @Override
2174 public boolean hasNext() {
2175 return e.hasMoreElements();
2176 }
2177 @Override
2283 Certificate cert = ks.getCertificate(s);
2284 if (cert instanceof X509Certificate) {
2285 X509Certificate xcert = (X509Certificate)cert;
2286 if (xcert.getSubjectX500Principal().equals(issuer)) {
2287 try {
2288 ((X509CRLImpl)crl).verify(cert.getPublicKey());
2289 return s;
2290 } catch (Exception e) {
2291 }
2292 }
2293 }
2294 }
2295 return null;
2296 }
2297
2298 private void doPrintCRL(String src, PrintStream out)
2299 throws Exception {
2300 for (CRL crl: loadCRLs(src)) {
2301 printCRL(crl, out);
2302 String issuer = null;
2303 if (caks != null) {
2304 issuer = verifyCRL(caks, crl);
2305 if (issuer != null) {
2306 out.printf(rb.getString(
2307 "verified.by.s.in.s"), issuer, "cacerts");
2308 out.println();
2309 }
2310 }
2311 if (issuer == null && keyStore != null) {
2312 issuer = verifyCRL(keyStore, crl);
2313 if (issuer != null) {
2314 out.printf(rb.getString(
2315 "verified.by.s.in.s"), issuer, "keystore");
2316 out.println();
2317 }
2318 }
2319 if (issuer == null) {
2320 out.println(rb.getString
2321 ("STAR"));
2322 out.println(rb.getString
2323 ("warning.not.verified.make.sure.keystore.is.correct"));
2324 out.println(rb.getString
2325 ("STARNN"));
2326 }
2327 }
2328 }
2329
2330 private void printCRL(CRL crl, PrintStream out)
2331 throws Exception {
2332 if (rfc) {
2333 X509CRL xcrl = (X509CRL)crl;
2334 out.println("-----BEGIN X509 CRL-----");
2335 out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(xcrl.getEncoded()));
2336 out.println("-----END X509 CRL-----");
2337 } else {
2338 out.println(crl.toString());
2339 }
2340 }
2341
2342 private void doPrintCertReq(InputStream in, PrintStream out)
2343 throws Exception {
2344
2345 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
2346 StringBuffer sb = new StringBuffer();
2347 boolean started = false;
2348 while (true) {
2349 String s = reader.readLine();
2350 if (s == null) break;
2351 if (!started) {
2352 if (s.startsWith("-----")) {
2353 started = true;
2354 }
2355 } else {
2356 if (s.startsWith("-----")) {
2357 break;
2358 }
2359 sb.append(s);
2360 }
2361 }
2362 PKCS10 req = new PKCS10(Pem.decode(new String(sb)));
2363
2364 PublicKey pkey = req.getSubjectPublicKeyInfo();
2365 out.printf(rb.getString("PKCS.10.Certificate.Request.Version.1.0.Subject.s.Public.Key.s.format.s.key."),
2366 req.getSubjectName(), pkey.getFormat(), pkey.getAlgorithm());
2367 for (PKCS10Attribute attr: req.getAttributes().getAttributes()) {
2368 ObjectIdentifier oid = attr.getAttributeId();
2369 if (oid.equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) {
2370 CertificateExtensions exts = (CertificateExtensions)attr.getAttributeValue();
2371 if (exts != null) {
2372 printExtensions(rb.getString("Extension.Request."), exts, out);
2373 }
2374 } else {
2375 out.println("Attribute: " + attr.getAttributeId());
2376 PKCS9Attribute pkcs9Attr =
2377 new PKCS9Attribute(attr.getAttributeId(),
2378 attr.getAttributeValue());
2379 out.print(pkcs9Attr.getName() + ": ");
2380 Object attrVal = attr.getAttributeValue();
2381 out.println(attrVal instanceof String[] ?
2382 Arrays.toString((String[]) attrVal) :
2383 attrVal);
2384 }
2385 }
2386 if (debug) {
2387 out.println(req); // Just to see more, say, public key length...
2388 }
2389 }
2390
2391 /**
2392 * Reads a certificate (or certificate chain) and prints its contents in
2393 * a human readable format.
2394 */
2395 private void printCertFromStream(InputStream in, PrintStream out)
2396 throws Exception
2397 {
2398 Collection<? extends Certificate> c = null;
2399 try {
2400 c = cf.generateCertificates(in);
2401 } catch (CertificateException ce) {
2402 throw new Exception(rb.getString("Failed.to.parse.input"), ce);
2403 }
2404 if (c.isEmpty()) {
2405 throw new Exception(rb.getString("Empty.input"));
2406 }
2407 Certificate[] certs = c.toArray(new Certificate[c.size()]);
2408 for (int i=0; i<certs.length; i++) {
2409 X509Certificate x509Cert = null;
2410 try {
2411 x509Cert = (X509Certificate)certs[i];
2412 } catch (ClassCastException cce) {
2413 throw new Exception(rb.getString("Not.X.509.certificate"));
2414 }
2415 if (certs.length > 1) {
2416 MessageFormat form = new MessageFormat
2417 (rb.getString("Certificate.i.1."));
2418 Object[] source = {i + 1};
2419 out.println(form.format(source));
2420 }
2421 if (rfc)
2422 dumpCert(x509Cert, out);
2423 else
2424 printX509Cert(x509Cert, out);
2425 if (i < (certs.length-1)) {
2426 out.println();
2427 }
2428 }
2429 }
2430
2431 private void doPrintCert(final PrintStream out) throws Exception {
2432 if (jarfile != null) {
2433 // reset "jdk.certpath.disabledAlgorithms" security property
2434 // to be able to read jars which were signed with weak algorithms
2435 Security.setProperty(DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS, "");
2436
2437 JarFile jf = new JarFile(jarfile, true);
2438 Enumeration<JarEntry> entries = jf.entries();
2439 Set<CodeSigner> ss = new HashSet<>();
2440 byte[] buffer = new byte[8192];
2441 int pos = 0;
2442 while (entries.hasMoreElements()) {
2443 JarEntry je = entries.nextElement();
2444 try (InputStream is = jf.getInputStream(je)) {
2445 while (is.read(buffer) != -1) {
2446 // we just read. this will throw a SecurityException
2447 // if a signature/digest check fails. This also
2448 // populate the signers
2449 }
2450 }
2451 CodeSigner[] signers = je.getCodeSigners();
2452 if (signers != null) {
2453 for (CodeSigner signer: signers) {
2454 if (!ss.contains(signer)) {
2455 ss.add(signer);
2456 out.printf(rb.getString("Signer.d."), ++pos);
2457 out.println();
2458 out.println();
2459 out.println(rb.getString("Signature."));
2460 out.println();
2461 for (Certificate cert: signer.getSignerCertPath().getCertificates()) {
2462 X509Certificate x = (X509Certificate)cert;
2463 if (rfc) {
2464 out.println(rb.getString("Certificate.owner.") + x.getSubjectDN() + "\n");
2465 dumpCert(x, out);
2466 } else {
2467 printX509Cert(x, out);
2468 }
2469 out.println();
2470 }
2471 Timestamp ts = signer.getTimestamp();
2472 if (ts != null) {
2473 out.println(rb.getString("Timestamp."));
2474 out.println();
2475 for (Certificate cert: ts.getSignerCertPath().getCertificates()) {
2476 X509Certificate x = (X509Certificate)cert;
2477 if (rfc) {
2478 out.println(rb.getString("Certificate.owner.") + x.getSubjectDN() + "\n");
2479 dumpCert(x, out);
2480 } else {
2481 printX509Cert(x, out);
2482 }
2483 out.println();
2484 }
2485 }
2486 }
2487 }
2488 }
2489 }
2490 jf.close();
2491 if (ss.isEmpty()) {
2492 out.println(rb.getString("Not.a.signed.jar.file"));
2493 }
2494 } else if (sslserver != null) {
2495 CertStore cs = SSLServerCertStore.getInstance(new URI("https://" + sslserver));
2496 Collection<? extends Certificate> chain;
2497 try {
2498 chain = cs.getCertificates(null);
2499 if (chain.isEmpty()) {
2500 // If the certs are not retrieved, we consider it an error
2501 // even if the URL connection is successful.
2502 throw new Exception(rb.getString(
2503 "No.certificate.from.the.SSL.server"));
2506 if (cse.getCause() instanceof IOException) {
2507 throw new Exception(rb.getString(
2508 "No.certificate.from.the.SSL.server"),
2509 cse.getCause());
2510 } else {
2511 throw cse;
2512 }
2513 }
2514
2515 int i = 0;
2516 for (Certificate cert : chain) {
2517 try {
2518 if (rfc) {
2519 dumpCert(cert, out);
2520 } else {
2521 out.println("Certificate #" + i++);
2522 out.println("====================================");
2523 printX509Cert((X509Certificate)cert, out);
2524 out.println();
2525 }
2526 } catch (Exception e) {
2527 if (debug) {
2528 e.printStackTrace();
2529 }
2530 }
2531 }
2532 } else {
2533 if (filename != null) {
2534 try (FileInputStream inStream = new FileInputStream(filename)) {
2535 printCertFromStream(inStream, out);
2536 }
2537 } else {
2538 printCertFromStream(System.in, out);
2539 }
2540 }
2541 }
2542 /**
2543 * Creates a self-signed certificate, and stores it as a single-element
2544 * certificate chain.
2545 */
2681 Object[] source = {alias};
2682 throw new Exception(form.format(source));
2683 }
2684
2685 // Read the certificates in the reply
2686 Collection<? extends Certificate> c = cf.generateCertificates(in);
2687 if (c.isEmpty()) {
2688 throw new Exception(rb.getString("Reply.has.no.certificates"));
2689 }
2690 Certificate[] replyCerts = c.toArray(new Certificate[c.size()]);
2691 Certificate[] newChain;
2692 if (replyCerts.length == 1) {
2693 // single-cert reply
2694 newChain = establishCertChain(userCert, replyCerts[0]);
2695 } else {
2696 // cert-chain reply (e.g., PKCS#7)
2697 newChain = validateReply(alias, userCert, replyCerts);
2698 }
2699
2700 // Now store the newly established chain in the keystore. The new
2701 // chain replaces the old one.
2702 if (newChain != null) {
2703 keyStore.setKeyEntry(alias, privKey,
2704 (keyPass != null) ? keyPass : storePass,
2705 newChain);
2706 return true;
2707 } else {
2708 return false;
2709 }
2710 }
2711
2712 /**
2713 * Imports a certificate and adds it to the list of trusted certificates.
2714 *
2715 * @return true if the certificate was added, otherwise false.
2716 */
2717 private boolean addTrustedCert(String alias, InputStream in)
2718 throws Exception
2719 {
2720 if (alias == null) {
2721 throw new Exception(rb.getString("Must.specify.alias"));
2722 }
2723 if (keyStore.containsAlias(alias)) {
2724 MessageFormat form = new MessageFormat(rb.getString
2725 ("Certificate.not.imported.alias.alias.already.exists"));
2726 Object[] source = {alias};
2727 throw new Exception(form.format(source));
2728 }
2729
2730 // Read the certificate
2731 X509Certificate cert = null;
2732 try {
2733 cert = (X509Certificate)cf.generateCertificate(in);
2734 } catch (ClassCastException | CertificateException ce) {
2735 throw new Exception(rb.getString("Input.not.an.X.509.certificate"));
2736 }
2737
2738 // if certificate is self-signed, make sure it verifies
2739 boolean selfSigned = false;
2740 if (KeyStoreUtil.isSelfSigned(cert)) {
2741 cert.verify(cert.getPublicKey());
2742 selfSigned = true;
2743 }
2744
2745 if (noprompt) {
2746 keyStore.setCertificateEntry(alias, cert);
2747 return true;
2748 }
2749
2750 // check if cert already exists in keystore
2751 String reply = null;
2752 String trustalias = keyStore.getCertificateAlias(cert);
2753 if (trustalias != null) {
2754 MessageFormat form = new MessageFormat(rb.getString
2755 ("Certificate.already.exists.in.keystore.under.alias.trustalias."));
2756 Object[] source = {trustalias};
2757 System.err.println(form.format(source));
2758 reply = getYesNoReply
2759 (rb.getString("Do.you.still.want.to.add.it.no."));
2760 } else if (selfSigned) {
2761 if (trustcacerts && (caks != null) &&
2762 ((trustalias=caks.getCertificateAlias(cert)) != null)) {
2763 MessageFormat form = new MessageFormat(rb.getString
2764 ("Certificate.already.exists.in.system.wide.CA.keystore.under.alias.trustalias."));
2765 Object[] source = {trustalias};
2766 System.err.println(form.format(source));
2767 reply = getYesNoReply
2768 (rb.getString("Do.you.still.want.to.add.it.to.your.own.keystore.no."));
2769 }
2770 if (trustalias == null) {
2771 // Print the cert and ask user if they really want to add
2772 // it to their keystore
2773 printX509Cert(cert, System.out);
2774 reply = getYesNoReply
2775 (rb.getString("Trust.this.certificate.no."));
2776 }
2777 }
2778 if (reply != null) {
2779 if ("YES".equals(reply)) {
2780 keyStore.setCertificateEntry(alias, cert);
2781 return true;
2782 } else {
2783 return false;
2784 }
2785 }
2786
2787 // Try to establish trust chain
2788 try {
2789 Certificate[] chain = establishCertChain(null, cert);
2790 if (chain != null) {
2791 keyStore.setCertificateEntry(alias, cert);
2792 return true;
2793 }
2794 } catch (Exception e) {
2795 // Print the cert and ask user if they really want to add it to
2796 // their keystore
2797 printX509Cert(cert, System.out);
2798 reply = getYesNoReply
2799 (rb.getString("Trust.this.certificate.no."));
2800 if ("YES".equals(reply)) {
2801 keyStore.setCertificateEntry(alias, cert);
2802 return true;
2803 } else {
2804 return false;
2805 }
2806 }
2807
2808 return false;
2809 }
2810
2811 /**
2812 * Prompts user for new password. New password must be different from
2813 * old one.
2814 *
2815 * @param prompt the message that gets prompted on the screen
2816 * @param oldPasswd the current (i.e., old) password
2817 */
2916 ("Enter.key.password.for.alias."));
2917 Object[] source = {alias};
2918 System.err.print(form.format(source));
2919 }
2920 System.err.flush();
2921 keyPass = Password.readPassword(System.in);
2922 passwords.add(keyPass);
2923 if (keyPass == null) {
2924 keyPass = otherKeyPass;
2925 }
2926 count++;
2927 } while ((keyPass == null) && count < 3);
2928
2929 if (keyPass == null) {
2930 throw new Exception(rb.getString("Too.many.failures.try.later"));
2931 }
2932
2933 return keyPass;
2934 }
2935
2936 /**
2937 * Prints a certificate in a human readable format.
2938 */
2939 private void printX509Cert(X509Certificate cert, PrintStream out)
2940 throws Exception
2941 {
2942
2943 MessageFormat form = new MessageFormat
2944 (rb.getString(".PATTERN.printX509Cert"));
2945 PublicKey pkey = cert.getPublicKey();
2946 Object[] source = {cert.getSubjectDN().toString(),
2947 cert.getIssuerDN().toString(),
2948 cert.getSerialNumber().toString(16),
2949 cert.getNotBefore().toString(),
2950 cert.getNotAfter().toString(),
2951 getCertFingerPrint("SHA-1", cert),
2952 getCertFingerPrint("SHA-256", cert),
2953 cert.getSigAlgName(),
2954 pkey.getAlgorithm(),
2955 KeyUtil.getKeySize(pkey),
2956 cert.getVersion(),
2957 };
2958 out.println(form.format(source));
2959
2960 if (cert instanceof X509CertImpl) {
2961 X509CertImpl impl = (X509CertImpl)cert;
2962 X509CertInfo certInfo = (X509CertInfo)impl.get(X509CertImpl.NAME
2963 + "." +
2964 X509CertImpl.INFO);
2965 CertificateExtensions exts = (CertificateExtensions)
2966 certInfo.get(X509CertInfo.EXTENSIONS);
2967 if (exts != null) {
2968 printExtensions(rb.getString("Extensions."), exts, out);
2969 }
2970 }
2971 }
2972
2973 private static void printExtensions(String title, CertificateExtensions exts, PrintStream out)
2974 throws Exception {
2975 int extnum = 0;
2976 Iterator<Extension> i1 = exts.getAllExtensions().iterator();
3264 }
3265 }
3266
3267 return Pair.of(entry, pkey);
3268 }
3269 /**
3270 * Gets the requested finger print of the certificate.
3271 */
3272 private String getCertFingerPrint(String mdAlg, Certificate cert)
3273 throws Exception
3274 {
3275 byte[] encCertInfo = cert.getEncoded();
3276 MessageDigest md = MessageDigest.getInstance(mdAlg);
3277 byte[] digest = md.digest(encCertInfo);
3278 return toHexString(digest);
3279 }
3280
3281 /**
3282 * Prints warning about missing integrity check.
3283 */
3284 private void printWarning() {
3285 System.err.println();
3286 System.err.println(rb.getString
3287 (".WARNING.WARNING.WARNING."));
3288 System.err.println(rb.getString
3289 (".The.integrity.of.the.information.stored.in.your.keystore."));
3290 System.err.println(rb.getString
3291 (".WARNING.WARNING.WARNING."));
3292 System.err.println();
3293 }
3294
3295 /**
3296 * Validates chain in certification reply, and returns the ordered
3297 * elements of the chain (with user certificate first, and root
3298 * certificate last in the array).
3299 *
3300 * @param alias the alias name
3301 * @param userCert the user certificate of the alias
3302 * @param replyCerts the chain provided in the reply
3303 */
3304 private Certificate[] validateReply(String alias,
3305 Certificate userCert,
3306 Certificate[] replyCerts)
3307 throws Exception
3308 {
3309 // order the certs in the reply (bottom-up).
3310 // we know that all certs in the reply are of type X.509, because
3311 // we parsed them using an X.509 certificate factory
3312 int i;
3313 PublicKey userPubKey = userCert.getPublicKey();
3314
3315 // Remove duplicated certificates.
3316 HashSet<Certificate> nodup = new HashSet<>(Arrays.asList(replyCerts));
3317 replyCerts = nodup.toArray(new Certificate[nodup.size()]);
3318
3319 for (i=0; i<replyCerts.length; i++) {
3320 if (userPubKey.equals(replyCerts[i].getPublicKey())) {
3321 break;
3322 }
3323 }
3324 if (i == replyCerts.length) {
3325 MessageFormat form = new MessageFormat(rb.getString
3326 ("Certificate.reply.does.not.contain.public.key.for.alias."));
3327 Object[] source = {alias};
3328 throw new Exception(form.format(source));
3352 }
3353 }
3354
3355 if (noprompt) {
3356 return replyCerts;
3357 }
3358
3359 // do we trust the cert at the top?
3360 Certificate topCert = replyCerts[replyCerts.length-1];
3361 Certificate root = getTrustedSigner(topCert, keyStore);
3362 if (root == null && trustcacerts && caks != null) {
3363 root = getTrustedSigner(topCert, caks);
3364 }
3365 if (root == null) {
3366 System.err.println();
3367 System.err.println
3368 (rb.getString("Top.level.certificate.in.reply."));
3369 printX509Cert((X509Certificate)topCert, System.out);
3370 System.err.println();
3371 System.err.print(rb.getString(".is.not.trusted."));
3372 String reply = getYesNoReply
3373 (rb.getString("Install.reply.anyway.no."));
3374 if ("NO".equals(reply)) {
3375 return null;
3376 }
3377 } else {
3378 if (root != topCert) {
3379 // append the root CA cert to the chain
3380 Certificate[] tmpCerts =
3381 new Certificate[replyCerts.length+1];
3382 System.arraycopy(replyCerts, 0, tmpCerts, 0,
3383 replyCerts.length);
3384 tmpCerts[tmpCerts.length-1] = root;
3385 replyCerts = tmpCerts;
3386 }
3387 }
3388
3389 return replyCerts;
3390 }
3391
3392 /**
3393 * Establishes a certificate chain (using trusted certificates in the
3394 * keystore), starting with the user certificate
3395 * and ending at a self-signed certificate found in the keystore.
3396 *
3397 * @param userCert the user certificate of the alias
3398 * @param certToVerify the single certificate provided in the reply
3399 */
3400 private Certificate[] establishCertChain(Certificate userCert,
3401 Certificate certToVerify)
3402 throws Exception
3403 {
3404 if (userCert != null) {
3405 // Make sure that the public key of the certificate reply matches
3406 // the original public key in the keystore
3407 PublicKey origPubKey = userCert.getPublicKey();
3408 PublicKey replyPubKey = certToVerify.getPublicKey();
3409 if (!origPubKey.equals(replyPubKey)) {
3410 throw new Exception(rb.getString
3411 ("Public.keys.in.reply.and.keystore.don.t.match"));
3412 }
3413
3414 // If the two certs are identical, we're done: no need to import
3415 // anything
3416 if (certToVerify.equals(userCert)) {
3417 throw new Exception(rb.getString
3418 ("Certificate.reply.and.certificate.in.keystore.are.identical"));
3419 }
3420 }
3421
3422 // Build a hash table of all certificates in the keystore.
3423 // Use the subject distinguished name as the key into the hash table.
3424 // All certificates associated with the same subject distinguished
3425 // name are stored in the same hash table entry as a vector.
3426 Hashtable<Principal, Vector<Certificate>> certs = null;
3427 if (keyStore.size() > 0) {
3428 certs = new Hashtable<Principal, Vector<Certificate>>(11);
3429 keystorecerts2Hashtable(keyStore, certs);
3430 }
3431 if (trustcacerts) {
3432 if (caks!=null && caks.size()>0) {
3433 if (certs == null) {
3434 certs = new Hashtable<Principal, Vector<Certificate>>(11);
3435 }
3436 keystorecerts2Hashtable(caks, certs);
3437 }
3438 }
3439
3440 // start building chain
3441 Vector<Certificate> chain = new Vector<>(2);
3442 if (buildChain((X509Certificate)certToVerify, chain, certs)) {
3443 Certificate[] newChain = new Certificate[chain.size()];
3444 // buildChain() returns chain with self-signed root-cert first and
3445 // user-cert last, so we need to invert the chain before we store
3446 // it
3447 int j=0;
3448 for (int i=chain.size()-1; i>=0; i--) {
3449 newChain[j] = chain.elementAt(i);
3450 j++;
3451 }
3452 return newChain;
3453 } else {
3454 throw new Exception
3455 (rb.getString("Failed.to.establish.chain.from.reply"));
3456 }
3457 }
3458
3459 /**
3460 * Recursively tries to establish chain from pool of trusted certs.
3461 *
3462 * @param certToVerify the cert that needs to be verified.
3463 * @param chain the chain that's being built.
3464 * @param certs the pool of trusted certs
3465 *
3466 * @return true if successful, false otherwise.
3467 */
3468 private boolean buildChain(X509Certificate certToVerify,
3469 Vector<Certificate> chain,
3470 Hashtable<Principal, Vector<Certificate>> certs) {
3471 Principal issuer = certToVerify.getIssuerDN();
3472 if (KeyStoreUtil.isSelfSigned(certToVerify)) {
3473 // reached self-signed root cert;
3474 // no verification needed because it's trusted.
3475 chain.addElement(certToVerify);
3476 return true;
3477 }
3478
3479 // Get the issuer's certificate(s)
3480 Vector<Certificate> vec = certs.get(issuer);
3481 if (vec == null) {
3482 return false;
3483 }
3484
3485 // Try out each certificate in the vector, until we find one
3486 // whose public key verifies the signature of the certificate
3487 // in question.
3488 for (Enumeration<Certificate> issuerCerts = vec.elements();
3489 issuerCerts.hasMoreElements(); ) {
3490 X509Certificate issuerCert
3491 = (X509Certificate)issuerCerts.nextElement();
3492 PublicKey issuerPubKey = issuerCert.getPublicKey();
3493 try {
3494 certToVerify.verify(issuerPubKey);
3495 } catch (Exception e) {
3496 continue;
3497 }
3498 if (buildChain(issuerCert, chain, certs)) {
3499 chain.addElement(certToVerify);
3500 return true;
3501 }
3502 }
3503 return false;
3504 }
3505
3506 /**
3507 * Prompts user for yes/no decision.
3508 *
3509 * @return the user's decision, can only be "YES" or "NO"
3510 */
3511 private String getYesNoReply(String prompt)
3512 throws IOException
3513 {
3514 String reply = null;
3524 (System.in))).readLine();
3525 if (reply == null ||
3526 collator.compare(reply, "") == 0 ||
3527 collator.compare(reply, rb.getString("n")) == 0 ||
3528 collator.compare(reply, rb.getString("no")) == 0) {
3529 reply = "NO";
3530 } else if (collator.compare(reply, rb.getString("y")) == 0 ||
3531 collator.compare(reply, rb.getString("yes")) == 0) {
3532 reply = "YES";
3533 } else {
3534 System.err.println(rb.getString("Wrong.answer.try.again"));
3535 reply = null;
3536 }
3537 } while (reply == null);
3538 return reply;
3539 }
3540
3541 /**
3542 * Stores the (leaf) certificates of a keystore in a hashtable.
3543 * All certs belonging to the same CA are stored in a vector that
3544 * in turn is stored in the hashtable, keyed by the CA's subject DN
3545 */
3546 private void keystorecerts2Hashtable(KeyStore ks,
3547 Hashtable<Principal, Vector<Certificate>> hash)
3548 throws Exception {
3549
3550 for (Enumeration<String> aliases = ks.aliases();
3551 aliases.hasMoreElements(); ) {
3552 String alias = aliases.nextElement();
3553 Certificate cert = ks.getCertificate(alias);
3554 if (cert != null) {
3555 Principal subjectDN = ((X509Certificate)cert).getSubjectDN();
3556 Vector<Certificate> vec = hash.get(subjectDN);
3557 if (vec == null) {
3558 vec = new Vector<Certificate>();
3559 vec.addElement(cert);
3560 } else {
3561 if (!vec.contains(cert)) {
3562 vec.addElement(cert);
3563 }
3564 }
3565 hash.put(subjectDN, vec);
3566 }
3567 }
3568 }
3569
3570 /**
3571 * Returns the issue time that's specified the -startdate option
3572 * @param s the value of -startdate option
3573 */
3574 private static Date getStartDate(String s) throws IOException {
3575 Calendar c = new GregorianCalendar();
3576 if (s != null) {
3577 IOException ioe = new IOException(
3578 rb.getString("Illegal.startdate.value"));
3579 int len = s.length();
3580 if (len == 0) {
3581 throw ioe;
3582 }
4138 setExt(result, new Extension(oid, isCritical,
4139 new DerValue(DerValue.tag_OctetString, data)
4140 .toByteArray()));
4141 break;
4142 default:
4143 throw new Exception(rb.getString(
4144 "Unknown.extension.type.") + extstr);
4145 }
4146 }
4147 // always non-critical
4148 setExt(result, new SubjectKeyIdentifierExtension(
4149 new KeyIdentifier(pkey).getIdentifier()));
4150 if (akey != null && !pkey.equals(akey)) {
4151 setExt(result, new AuthorityKeyIdentifierExtension(
4152 new KeyIdentifier(akey), null, null));
4153 }
4154 } catch(IOException e) {
4155 throw new RuntimeException(e);
4156 }
4157 return result;
4158 }
4159
4160 /**
4161 * Prints the usage of this tool.
4162 */
4163 private void usage() {
4164 if (command != null) {
4165 System.err.println("keytool " + command +
4166 rb.getString(".OPTION."));
4167 System.err.println();
4168 System.err.println(rb.getString(command.description));
4169 System.err.println();
4170 System.err.println(rb.getString("Options."));
4171 System.err.println();
4172
4173 // Left and right sides of the options list. Both might
4174 // contain "\n" and span multiple lines
4175 String[] left = new String[command.options.length];
4176 String[] right = new String[command.options.length];
4177
|
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.security.tools.keytool;
27
28 import java.io.*;
29 import java.security.CodeSigner;
30 import java.security.CryptoPrimitive;
31 import java.security.KeyStore;
32 import java.security.KeyStoreException;
33 import java.security.MessageDigest;
34 import java.security.Key;
35 import java.security.PublicKey;
36 import java.security.PrivateKey;
37 import java.security.Signature;
38 import java.security.Timestamp;
39 import java.security.UnrecoverableEntryException;
40 import java.security.UnrecoverableKeyException;
41 import java.security.Principal;
42 import java.security.cert.Certificate;
43 import java.security.cert.CertificateFactory;
44 import java.security.cert.CertStoreException;
45 import java.security.cert.CRL;
46 import java.security.cert.X509Certificate;
47 import java.security.cert.CertificateException;
48 import java.security.cert.URICertStoreParameters;
49
50
140 private char[] storePassNew = null;
141 private char[] keyPass = null;
142 private char[] keyPassNew = null;
143 private char[] newPass = null;
144 private char[] destKeyPass = null;
145 private char[] srckeyPass = null;
146 private String ksfname = null;
147 private File ksfile = null;
148 private InputStream ksStream = null; // keystore stream
149 private String sslserver = null;
150 private String jarfile = null;
151 private KeyStore keyStore = null;
152 private boolean token = false;
153 private boolean nullStream = false;
154 private boolean kssave = false;
155 private boolean noprompt = false;
156 private boolean trustcacerts = false;
157 private boolean protectedPath = false;
158 private boolean srcprotectedPath = false;
159 private boolean cacerts = false;
160 private boolean nowarn = false;
161 private CertificateFactory cf = null;
162 private KeyStore caks = null; // "cacerts" keystore
163 private char[] srcstorePass = null;
164 private String srcstoretype = null;
165 private Set<char[]> passwords = new HashSet<>();
166 private String startDate = null;
167
168 private List<String> ids = new ArrayList<>(); // used in GENCRL
169 private List<String> v3ext = new ArrayList<>();
170
171 // Warnings on weak algorithms
172 private List<String> weakWarnings = new ArrayList<>();
173
174 private static final DisabledAlgorithmConstraints DISABLED_CHECK =
175 new DisabledAlgorithmConstraints(
176 DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS);
177
178 private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET = Collections
179 .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
180
181 enum Command {
182 CERTREQ("Generates.a.certificate.request",
183 ALIAS, SIGALG, FILEOUT, KEYPASS, KEYSTORE, DNAME,
184 STOREPASS, STORETYPE, PROVIDERNAME, ADDPROVIDER,
185 PROVIDERCLASS, PROVIDERPATH, V, PROTECTED),
186 CHANGEALIAS("Changes.an.entry.s.alias",
187 ALIAS, DESTALIAS, KEYPASS, KEYSTORE, CACERTS, STOREPASS,
188 STORETYPE, PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
189 PROVIDERPATH, V, PROTECTED),
190 DELETE("Deletes.an.entry",
191 ALIAS, KEYSTORE, CACERTS, STOREPASS, STORETYPE,
192 PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
193 PROVIDERPATH, V, PROTECTED),
194 EXPORTCERT("Exports.certificate",
195 RFC, ALIAS, FILEOUT, KEYSTORE, CACERTS, STOREPASS,
196 STORETYPE, PROVIDERNAME, ADDPROVIDER, PROVIDERCLASS,
197 PROVIDERPATH, V, PROTECTED),
198 GENKEYPAIR("Generates.a.key.pair",
199 ALIAS, KEYALG, KEYSIZE, SIGALG, DESTALIAS, DNAME,
200 STARTDATE, EXT, VALIDITY, KEYPASS, KEYSTORE,
346 STORETYPE("storetype", "<type>", "keystore.type"),
347 TRUSTCACERTS("trustcacerts", null, "trust.certificates.from.cacerts"),
348 V("v", null, "verbose.output"),
349 VALIDITY("validity", "<days>", "validity.number.of.days");
350
351 final String name, arg, description;
352 Option(String name, String arg, String description) {
353 this.name = name;
354 this.arg = arg;
355 this.description = description;
356 }
357 @Override
358 public String toString() {
359 return "-" + name;
360 }
361 };
362
363 private static final String NONE = "NONE";
364 private static final String P11KEYSTORE = "PKCS11";
365 private static final String P12KEYSTORE = "PKCS12";
366 private static final String keyAlias = "mykey";
367
368 // for i18n
369 private static final java.util.ResourceBundle rb =
370 java.util.ResourceBundle.getBundle(
371 "sun.security.tools.keytool.Resources");
372 private static final Collator collator = Collator.getInstance();
373 static {
374 // this is for case insensitive string comparisons
375 collator.setStrength(Collator.PRIMARY);
376 };
377
378 private Main() { }
379
380 public static void main(String[] args) throws Exception {
381 Main kt = new Main();
382 kt.run(args, System.out);
383 }
384
385 private void run(String[] args, PrintStream out) throws Exception {
386 try {
387 args = parseArgs(args);
388 if (command != null) {
389 doCommands(out);
390 }
391 } catch (Exception e) {
392 System.out.println(rb.getString("keytool.error.") + e);
393 if (verbose) {
394 e.printStackTrace(System.out);
395 }
396 if (!debug) {
397 System.exit(1);
398 } else {
399 throw e;
400 }
401 } finally {
402 printWeakWarningsWithoutNewLine();
403 for (char[] pass : passwords) {
404 if (pass != null) {
405 Arrays.fill(pass, ' ');
406 pass = null;
407 }
408 }
409
410 if (ksStream != null) {
411 ksStream.close();
412 }
413 }
414 }
415
416 /**
417 * Parse command line arguments.
418 */
419 String[] parseArgs(String[] args) throws Exception {
420
421 int i=0;
422 boolean help = args.length == 0;
472 * Check modifiers
473 */
474 String modifier = null;
475 int pos = flags.indexOf(':');
476 if (pos > 0) {
477 modifier = flags.substring(pos+1);
478 flags = flags.substring(0, pos);
479 }
480
481 /*
482 * command modes
483 */
484 Command c = Command.getCommand(flags);
485
486 if (c != null) {
487 command = c;
488 } else if (collator.compare(flags, "-help") == 0) {
489 help = true;
490 } else if (collator.compare(flags, "-conf") == 0) {
491 i++;
492 } else if (collator.compare(flags, "-nowarn") == 0) {
493 nowarn = true;
494 } else if (collator.compare(flags, "-keystore") == 0) {
495 ksfname = args[++i];
496 if (new File(ksfname).getCanonicalPath().equals(
497 new File(KeyStoreUtil.getCacerts()).getCanonicalPath())) {
498 System.err.println(rb.getString("warning.cacerts.option"));
499 }
500 } else if (collator.compare(flags, "-destkeystore") == 0) {
501 ksfname = args[++i];
502 } else if (collator.compare(flags, "-cacerts") == 0) {
503 cacerts = true;
504 } else if (collator.compare(flags, "-storepass") == 0 ||
505 collator.compare(flags, "-deststorepass") == 0) {
506 storePass = getPass(modifier, args[++i]);
507 passwords.add(storePass);
508 } else if (collator.compare(flags, "-storetype") == 0 ||
509 collator.compare(flags, "-deststoretype") == 0) {
510 storetype = args[++i];
511 hasStoretypeOption = true;
512 } else if (collator.compare(flags, "-srcstorepass") == 0) {
513 srcstorePass = getPass(modifier, args[++i]);
1150
1151 doCloneEntry(alias, dest, true); // Now everything can be cloned
1152 kssave = true;
1153 } else if (command == CHANGEALIAS) {
1154 if (alias == null) {
1155 alias = keyAlias;
1156 }
1157 doCloneEntry(alias, dest, false);
1158 // in PKCS11, clone a PrivateKeyEntry will delete the old one
1159 if (keyStore.containsAlias(alias)) {
1160 doDeleteEntry(alias);
1161 }
1162 kssave = true;
1163 } else if (command == KEYPASSWD) {
1164 keyPassNew = newPass;
1165 doChangeKeyPasswd(alias);
1166 kssave = true;
1167 } else if (command == LIST) {
1168 if (storePass == null
1169 && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
1170 printNoIntegrityWarning();
1171 }
1172
1173 if (alias != null) {
1174 doPrintEntry(null, alias, out);
1175 } else {
1176 doPrintEntries(out);
1177 }
1178 } else if (command == PRINTCERT) {
1179 doPrintCert(out);
1180 } else if (command == SELFCERT) {
1181 doSelfCert(alias, dname, sigAlgName);
1182 kssave = true;
1183 } else if (command == STOREPASSWD) {
1184 storePassNew = newPass;
1185 if (storePassNew == null) {
1186 storePassNew = getNewPasswd("keystore password", storePass);
1187 }
1188 kssave = true;
1189 } else if (command == GENCERT) {
1190 if (alias == null) {
1191 alias = keyAlias;
1192 }
1193 InputStream inStream = System.in;
1194 if (infilename != null) {
1251 } else {
1252 ByteArrayOutputStream bout = new ByteArrayOutputStream();
1253 keyStore.store(bout, pass);
1254 try (FileOutputStream fout = new FileOutputStream(ksfname)) {
1255 fout.write(bout.toByteArray());
1256 }
1257 }
1258 }
1259 }
1260 }
1261
1262 /**
1263 * Generate a certificate: Read PKCS10 request from in, and print
1264 * certificate to out. Use alias as CA, sigAlgName as the signature
1265 * type.
1266 */
1267 private void doGenCert(String alias, String sigAlgName, InputStream in, PrintStream out)
1268 throws Exception {
1269
1270
1271 if (keyStore.containsAlias(alias) == false) {
1272 MessageFormat form = new MessageFormat
1273 (rb.getString("Alias.alias.does.not.exist"));
1274 Object[] source = {alias};
1275 throw new Exception(form.format(source));
1276 }
1277 Certificate signerCert = keyStore.getCertificate(alias);
1278 byte[] encoded = signerCert.getEncoded();
1279 X509CertImpl signerCertImpl = new X509CertImpl(encoded);
1280 X509CertInfo signerCertInfo = (X509CertInfo)signerCertImpl.get(
1281 X509CertImpl.NAME + "." + X509CertImpl.INFO);
1282 X500Name issuer = (X500Name)signerCertInfo.get(X509CertInfo.SUBJECT + "." +
1283 X509CertInfo.DN_NAME);
1284
1285 Date firstDate = getStartDate(startDate);
1286 Date lastDate = new Date();
1287 lastDate.setTime(firstDate.getTime() + validity*1000L*24L*60L*60L);
1288 CertificateValidity interval = new CertificateValidity(firstDate,
1289 lastDate);
1290
1291 PrivateKey privateKey =
1292 (PrivateKey)recoverKey(alias, storePass, keyPass).fst;
1293 if (sigAlgName == null) {
1294 sigAlgName = getCompatibleSigAlgName(privateKey);
1295 }
1296 Signature signature = Signature.getInstance(sigAlgName);
1310 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
1311 boolean canRead = false;
1312 StringBuffer sb = new StringBuffer();
1313 while (true) {
1314 String s = reader.readLine();
1315 if (s == null) break;
1316 // OpenSSL does not use NEW
1317 //if (s.startsWith("-----BEGIN NEW CERTIFICATE REQUEST-----")) {
1318 if (s.startsWith("-----BEGIN") && s.indexOf("REQUEST") >= 0) {
1319 canRead = true;
1320 //} else if (s.startsWith("-----END NEW CERTIFICATE REQUEST-----")) {
1321 } else if (s.startsWith("-----END") && s.indexOf("REQUEST") >= 0) {
1322 break;
1323 } else if (canRead) {
1324 sb.append(s);
1325 }
1326 }
1327 byte[] rawReq = Pem.decode(new String(sb));
1328 PKCS10 req = new PKCS10(rawReq);
1329
1330 checkWeak(rb.getString("the.input"), req);
1331
1332 info.set(X509CertInfo.KEY, new CertificateX509Key(req.getSubjectPublicKeyInfo()));
1333 info.set(X509CertInfo.SUBJECT,
1334 dname==null?req.getSubjectName():new X500Name(dname));
1335 CertificateExtensions reqex = null;
1336 Iterator<PKCS10Attribute> attrs = req.getAttributes().getAttributes().iterator();
1337 while (attrs.hasNext()) {
1338 PKCS10Attribute attr = attrs.next();
1339 if (attr.getAttributeId().equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) {
1340 reqex = (CertificateExtensions)attr.getAttributeValue();
1341 }
1342 }
1343 CertificateExtensions ext = createV3Extensions(
1344 reqex,
1345 null,
1346 v3ext,
1347 req.getSubjectPublicKeyInfo(),
1348 signerCert.getPublicKey());
1349 info.set(X509CertInfo.EXTENSIONS, ext);
1350 X509CertImpl cert = new X509CertImpl(info);
1351 cert.sign(privateKey, sigAlgName);
1352 dumpCert(cert, out);
1353 for (Certificate ca: keyStore.getCertificateChain(alias)) {
1354 if (ca instanceof X509Certificate) {
1355 X509Certificate xca = (X509Certificate)ca;
1356 if (!KeyStoreUtil.isSelfSigned(xca)) {
1357 dumpCert(xca, out);
1358 }
1359 }
1360 }
1361
1362 checkWeak(rb.getString("the.issuer"), keyStore.getCertificateChain(alias));
1363 checkWeak(rb.getString("the.output"), cert);
1364 }
1365
1366 private void doGenCRL(PrintStream out)
1367 throws Exception {
1368 if (ids == null) {
1369 throw new Exception("Must provide -id when -gencrl");
1370 }
1371 Certificate signerCert = keyStore.getCertificate(alias);
1372 byte[] encoded = signerCert.getEncoded();
1373 X509CertImpl signerCertImpl = new X509CertImpl(encoded);
1374 X509CertInfo signerCertInfo = (X509CertInfo)signerCertImpl.get(
1375 X509CertImpl.NAME + "." + X509CertImpl.INFO);
1376 X500Name owner = (X500Name)signerCertInfo.get(X509CertInfo.SUBJECT + "." +
1377 X509CertInfo.DN_NAME);
1378
1379 Date firstDate = getStartDate(startDate);
1380 Date lastDate = (Date) firstDate.clone();
1381 lastDate.setTime(lastDate.getTime() + validity*1000*24*60*60);
1382 CertificateValidity interval = new CertificateValidity(firstDate,
1383 lastDate);
1394 String id = ids.get(i);
1395 int d = id.indexOf(':');
1396 if (d >= 0) {
1397 CRLExtensions ext = new CRLExtensions();
1398 ext.set("Reason", new CRLReasonCodeExtension(Integer.parseInt(id.substring(d+1))));
1399 badCerts[i] = new X509CRLEntryImpl(new BigInteger(id.substring(0, d)),
1400 firstDate, ext);
1401 } else {
1402 badCerts[i] = new X509CRLEntryImpl(new BigInteger(ids.get(i)), firstDate);
1403 }
1404 }
1405 X509CRLImpl crl = new X509CRLImpl(owner, firstDate, lastDate, badCerts);
1406 crl.sign(privateKey, sigAlgName);
1407 if (rfc) {
1408 out.println("-----BEGIN X509 CRL-----");
1409 out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(crl.getEncodedInternal()));
1410 out.println("-----END X509 CRL-----");
1411 } else {
1412 out.write(crl.getEncodedInternal());
1413 }
1414 checkWeak(null, crl, privateKey);
1415 }
1416
1417 /**
1418 * Creates a PKCS#10 cert signing request, corresponding to the
1419 * keys (and name) associated with a given alias.
1420 */
1421 private void doCertReq(String alias, String sigAlgName, PrintStream out)
1422 throws Exception
1423 {
1424 if (alias == null) {
1425 alias = keyAlias;
1426 }
1427
1428 Pair<Key,char[]> objs = recoverKey(alias, storePass, keyPass);
1429 PrivateKey privKey = (PrivateKey)objs.fst;
1430 if (keyPass == null) {
1431 keyPass = objs.snd;
1432 }
1433
1434 Certificate cert = keyStore.getCertificate(alias);
1441 PKCS10 request = new PKCS10(cert.getPublicKey());
1442 CertificateExtensions ext = createV3Extensions(null, null, v3ext, cert.getPublicKey(), null);
1443 // Attribute name is not significant
1444 request.getAttributes().setAttribute(X509CertInfo.EXTENSIONS,
1445 new PKCS10Attribute(PKCS9Attribute.EXTENSION_REQUEST_OID, ext));
1446
1447 // Construct a Signature object, so that we can sign the request
1448 if (sigAlgName == null) {
1449 sigAlgName = getCompatibleSigAlgName(privKey);
1450 }
1451
1452 Signature signature = Signature.getInstance(sigAlgName);
1453 signature.initSign(privKey);
1454 X500Name subject = dname == null?
1455 new X500Name(((X509Certificate)cert).getSubjectDN().toString()):
1456 new X500Name(dname);
1457
1458 // Sign the request and base-64 encode it
1459 request.encodeAndSign(subject, signature);
1460 request.print(out);
1461
1462 checkWeak(null, request);
1463 }
1464
1465 /**
1466 * Deletes an entry from the keystore.
1467 */
1468 private void doDeleteEntry(String alias) throws Exception {
1469 if (keyStore.containsAlias(alias) == false) {
1470 MessageFormat form = new MessageFormat
1471 (rb.getString("Alias.alias.does.not.exist"));
1472 Object[] source = {alias};
1473 throw new Exception(form.format(source));
1474 }
1475 keyStore.deleteEntry(alias);
1476 }
1477
1478 /**
1479 * Exports a certificate from the keystore.
1480 */
1481 private void doExportCert(String alias, PrintStream out)
1482 throws Exception
1483 {
1484 if (storePass == null
1485 && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
1486 printNoIntegrityWarning();
1487 }
1488 if (alias == null) {
1489 alias = keyAlias;
1490 }
1491 if (keyStore.containsAlias(alias) == false) {
1492 MessageFormat form = new MessageFormat
1493 (rb.getString("Alias.alias.does.not.exist"));
1494 Object[] source = {alias};
1495 throw new Exception(form.format(source));
1496 }
1497
1498 X509Certificate cert = (X509Certificate)keyStore.getCertificate(alias);
1499 if (cert == null) {
1500 MessageFormat form = new MessageFormat
1501 (rb.getString("Alias.alias.has.no.certificate"));
1502 Object[] source = {alias};
1503 throw new Exception(form.format(source));
1504 }
1505 dumpCert(cert, out);
1506 checkWeak(null, cert);
1507 }
1508
1509 /**
1510 * Prompt the user for a keypass when generating a key entry.
1511 * @param alias the entry we will set password for
1512 * @param orig the original entry of doing a dup, null if generate new
1513 * @param origPass the password to copy from if user press ENTER
1514 */
1515 private char[] promptForKeyPass(String alias, String orig, char[] origPass) throws Exception{
1516 if (P12KEYSTORE.equalsIgnoreCase(storetype)) {
1517 return origPass;
1518 } else if (!token && !protectedPath) {
1519 // Prompt for key password
1520 int count;
1521 for (count = 0; count < 3; count++) {
1522 MessageFormat form = new MessageFormat(rb.getString
1523 ("Enter.key.password.for.alias."));
1524 Object[] source = {alias};
1525 System.err.println(form.format(source));
1526 if (orig == null) {
1742
1743 X509Certificate[] chain = new X509Certificate[1];
1744 chain[0] = keypair.getSelfCertificate(
1745 x500Name, getStartDate(startDate), validity*24L*60L*60L, ext);
1746
1747 if (verbose) {
1748 MessageFormat form = new MessageFormat(rb.getString
1749 ("Generating.keysize.bit.keyAlgName.key.pair.and.self.signed.certificate.sigAlgName.with.a.validity.of.validality.days.for"));
1750 Object[] source = {keysize,
1751 privKey.getAlgorithm(),
1752 chain[0].getSigAlgName(),
1753 validity,
1754 x500Name};
1755 System.err.println(form.format(source));
1756 }
1757
1758 if (keyPass == null) {
1759 keyPass = promptForKeyPass(alias, null, storePass);
1760 }
1761 keyStore.setKeyEntry(alias, privKey, keyPass, chain);
1762
1763 checkWeak(null, chain[0]);
1764 }
1765
1766 /**
1767 * Clones an entry
1768 * @param orig original alias
1769 * @param dest destination alias
1770 * @changePassword if the password can be changed
1771 */
1772 private void doCloneEntry(String orig, String dest, boolean changePassword)
1773 throws Exception
1774 {
1775 if (orig == null) {
1776 orig = keyAlias;
1777 }
1778
1779 if (keyStore.containsAlias(dest)) {
1780 MessageFormat form = new MessageFormat
1781 (rb.getString("Destination.alias.dest.already.exists"));
1782 Object[] source = {dest};
1783 throw new Exception(form.format(source));
1825 }
1826 keyStore.setKeyEntry(alias, privKey, keyPassNew,
1827 keyStore.getCertificateChain(alias));
1828 }
1829
1830 /**
1831 * Imports a JDK 1.1-style identity database. We can only store one
1832 * certificate per identity, because we use the identity's name as the
1833 * alias (which references a keystore entry), and aliases must be unique.
1834 */
1835 private void doImportIdentityDatabase(InputStream in)
1836 throws Exception
1837 {
1838 System.err.println(rb.getString
1839 ("No.entries.from.identity.database.added"));
1840 }
1841
1842 /**
1843 * Prints a single keystore entry.
1844 */
1845 private void doPrintEntry(String label, String alias, PrintStream out)
1846 throws Exception
1847 {
1848 if (keyStore.containsAlias(alias) == false) {
1849 MessageFormat form = new MessageFormat
1850 (rb.getString("Alias.alias.does.not.exist"));
1851 Object[] source = {alias};
1852 throw new Exception(form.format(source));
1853 }
1854
1855 if (verbose || rfc || debug) {
1856 MessageFormat form = new MessageFormat
1857 (rb.getString("Alias.name.alias"));
1858 Object[] source = {alias};
1859 out.println(form.format(source));
1860
1861 if (!token) {
1862 form = new MessageFormat(rb.getString
1863 ("Creation.date.keyStore.getCreationDate.alias."));
1864 Object[] src = {keyStore.getCreationDate(alias)};
1865 out.println(form.format(src));
1896 }
1897
1898 // Get the chain
1899 Certificate[] chain = keyStore.getCertificateChain(alias);
1900 if (chain != null) {
1901 if (verbose || rfc || debug) {
1902 out.println(rb.getString
1903 ("Certificate.chain.length.") + chain.length);
1904 for (int i = 0; i < chain.length; i ++) {
1905 MessageFormat form = new MessageFormat
1906 (rb.getString("Certificate.i.1."));
1907 Object[] source = {(i + 1)};
1908 out.println(form.format(source));
1909 if (verbose && (chain[i] instanceof X509Certificate)) {
1910 printX509Cert((X509Certificate)(chain[i]), out);
1911 } else if (debug) {
1912 out.println(chain[i].toString());
1913 } else {
1914 dumpCert(chain[i], out);
1915 }
1916 checkWeak(label, chain[i]);
1917 }
1918 } else {
1919 // Print the digest of the user cert only
1920 out.println
1921 (rb.getString("Certificate.fingerprint.SHA.256.") +
1922 getCertFingerPrint("SHA-256", chain[0]));
1923 checkWeak(label, chain);
1924 }
1925 }
1926 } else if (keyStore.entryInstanceOf(alias,
1927 KeyStore.TrustedCertificateEntry.class)) {
1928 // We have a trusted certificate entry
1929 Certificate cert = keyStore.getCertificate(alias);
1930 Object[] source = {"trustedCertEntry"};
1931 String mf = new MessageFormat(
1932 rb.getString("Entry.type.type.")).format(source) + "\n";
1933 if (verbose && (cert instanceof X509Certificate)) {
1934 out.println(mf);
1935 printX509Cert((X509Certificate)cert, out);
1936 } else if (rfc) {
1937 out.println(mf);
1938 dumpCert(cert, out);
1939 } else if (debug) {
1940 out.println(cert.toString());
1941 } else {
1942 out.println("trustedCertEntry, ");
1943 out.println(rb.getString("Certificate.fingerprint.SHA.256.")
1944 + getCertFingerPrint("SHA-256", cert));
1945 }
1946 checkWeak(label, cert);
1947 } else {
1948 out.println(rb.getString("Unknown.Entry.Type"));
1949 }
1950 }
1951
1952 /**
1953 * Load the srckeystore from a stream, used in -importkeystore
1954 * @return the src KeyStore
1955 */
1956 KeyStore loadSourceKeyStore() throws Exception {
1957 boolean isPkcs11 = false;
1958
1959 InputStream is = null;
1960 File srcksfile = null;
1961
1962 if (P11KEYSTORE.equalsIgnoreCase(srcstoretype) ||
1963 KeyStoreUtil.isWindowsKeyStore(srcstoretype)) {
1964 if (!NONE.equals(srcksfname)) {
1965 System.err.println(MessageFormat.format(rb.getString
1966 (".keystore.must.be.NONE.if.storetype.is.{0}"), srcstoretype));
2010 if (P12KEYSTORE.equalsIgnoreCase(srcstoretype)) {
2011 if (srckeyPass != null && srcstorePass != null &&
2012 !Arrays.equals(srcstorePass, srckeyPass)) {
2013 MessageFormat form = new MessageFormat(rb.getString(
2014 "Warning.Different.store.and.key.passwords.not.supported.for.PKCS12.KeyStores.Ignoring.user.specified.command.value."));
2015 Object[] source = {"-srckeypass"};
2016 System.err.println(form.format(source));
2017 srckeyPass = srcstorePass;
2018 }
2019 }
2020
2021 store.load(is, srcstorePass); // "is" already null in PKCS11
2022 } finally {
2023 if (is != null) {
2024 is.close();
2025 }
2026 }
2027
2028 if (srcstorePass == null
2029 && !KeyStoreUtil.isWindowsKeyStore(srcstoretype)) {
2030 // anti refactoring, copied from printNoIntegrityWarning(),
2031 // but change 2 lines
2032 System.err.println();
2033 System.err.println(rb.getString
2034 (".WARNING.WARNING.WARNING."));
2035 System.err.println(rb.getString
2036 (".The.integrity.of.the.information.stored.in.the.srckeystore."));
2037 System.err.println(rb.getString
2038 (".WARNING.WARNING.WARNING."));
2039 System.err.println();
2040 }
2041
2042 return store;
2043 }
2044
2045 /**
2046 * import all keys and certs from importkeystore.
2047 * keep alias unchanged if no name conflict, otherwise, prompt.
2048 * keep keypass unchanged for keys
2049 */
2050 private void doImportKeyStore() throws Exception {
2133 MessageFormat form = new MessageFormat(rb.getString(
2134 "Problem.importing.entry.for.alias.alias.exception.Entry.for.alias.alias.not.imported."));
2135 System.err.println(form.format(source2));
2136 return 2;
2137 }
2138 }
2139
2140 private void doImportKeyStoreAll(KeyStore srckeystore) throws Exception {
2141
2142 int ok = 0;
2143 int count = srckeystore.size();
2144 for (Enumeration<String> e = srckeystore.aliases();
2145 e.hasMoreElements(); ) {
2146 String alias = e.nextElement();
2147 int result = doImportKeyStoreSingle(srckeystore, alias);
2148 if (result == 1) {
2149 ok++;
2150 Object[] source = {alias};
2151 MessageFormat form = new MessageFormat(rb.getString("Entry.for.alias.alias.successfully.imported."));
2152 System.err.println(form.format(source));
2153 Certificate c = srckeystore.getCertificate(alias);
2154 if (c != null) {
2155 checkWeak(null, c);
2156 }
2157 } else if (result == 2) {
2158 if (!noprompt) {
2159 String reply = getYesNoReply("Do you want to quit the import process? [no]: ");
2160 if ("YES".equals(reply)) {
2161 break;
2162 }
2163 }
2164 }
2165 }
2166 Object[] source = {ok, count-ok};
2167 MessageFormat form = new MessageFormat(rb.getString(
2168 "Import.command.completed.ok.entries.successfully.imported.fail.entries.failed.or.cancelled"));
2169 System.err.println(form.format(source));
2170 }
2171
2172 /**
2173 * Prints all keystore entries.
2174 */
2175 private void doPrintEntries(PrintStream out)
2176 throws Exception
2177 {
2178 out.println(rb.getString("Keystore.type.") + keyStore.getType());
2179 out.println(rb.getString("Keystore.provider.") +
2180 keyStore.getProvider().getName());
2181 out.println();
2182
2183 MessageFormat form;
2184 form = (keyStore.size() == 1) ?
2185 new MessageFormat(rb.getString
2186 ("Your.keystore.contains.keyStore.size.entry")) :
2187 new MessageFormat(rb.getString
2188 ("Your.keystore.contains.keyStore.size.entries"));
2189 Object[] source = {keyStore.size()};
2190 out.println(form.format(source));
2191 out.println();
2192
2193 for (Enumeration<String> e = keyStore.aliases();
2194 e.hasMoreElements(); ) {
2195 String alias = e.nextElement();
2196 doPrintEntry("<" + alias + ">", alias, out);
2197 if (verbose || rfc) {
2198 out.println(rb.getString("NEWLINE"));
2199 out.println(rb.getString
2200 ("STAR"));
2201 out.println(rb.getString
2202 ("STARNN"));
2203 }
2204 }
2205 }
2206
2207 private static <T> Iterable<T> e2i(final Enumeration<T> e) {
2208 return new Iterable<T>() {
2209 @Override
2210 public Iterator<T> iterator() {
2211 return new Iterator<T>() {
2212 @Override
2213 public boolean hasNext() {
2214 return e.hasMoreElements();
2215 }
2216 @Override
2322 Certificate cert = ks.getCertificate(s);
2323 if (cert instanceof X509Certificate) {
2324 X509Certificate xcert = (X509Certificate)cert;
2325 if (xcert.getSubjectX500Principal().equals(issuer)) {
2326 try {
2327 ((X509CRLImpl)crl).verify(cert.getPublicKey());
2328 return s;
2329 } catch (Exception e) {
2330 }
2331 }
2332 }
2333 }
2334 return null;
2335 }
2336
2337 private void doPrintCRL(String src, PrintStream out)
2338 throws Exception {
2339 for (CRL crl: loadCRLs(src)) {
2340 printCRL(crl, out);
2341 String issuer = null;
2342 Certificate signer = null;
2343 if (caks != null) {
2344 issuer = verifyCRL(caks, crl);
2345 if (issuer != null) {
2346 signer = caks.getCertificate(issuer);
2347 out.printf(rb.getString(
2348 "verified.by.s.in.s.weak"),
2349 issuer,
2350 "cacerts",
2351 withWeak(signer.getPublicKey()));
2352 out.println();
2353 }
2354 }
2355 if (issuer == null && keyStore != null) {
2356 issuer = verifyCRL(keyStore, crl);
2357 if (issuer != null) {
2358 signer = keyStore.getCertificate(issuer);
2359 out.printf(rb.getString(
2360 "verified.by.s.in.s.weak"),
2361 issuer,
2362 "keystore",
2363 withWeak(signer.getPublicKey()));
2364 out.println();
2365 }
2366 }
2367 if (issuer == null) {
2368 out.println(rb.getString
2369 ("STAR"));
2370 out.println(rb.getString
2371 ("warning.not.verified.make.sure.keystore.is.correct"));
2372 out.println(rb.getString
2373 ("STARNN"));
2374 }
2375 checkWeak(null, crl, signer == null ? null : signer.getPublicKey());
2376 }
2377 }
2378
2379 private void printCRL(CRL crl, PrintStream out)
2380 throws Exception {
2381 X509CRL xcrl = (X509CRL)crl;
2382 if (rfc) {
2383 out.println("-----BEGIN X509 CRL-----");
2384 out.println(Base64.getMimeEncoder(64, CRLF).encodeToString(xcrl.getEncoded()));
2385 out.println("-----END X509 CRL-----");
2386 } else {
2387 // Hack: Inject "(weak)" into X509CRLImpl::toString.
2388 String s = xcrl.toString();
2389 String title = "Signature Algorithm: ";
2390 int pos = s.indexOf(title);
2391 int pos2 = s.indexOf(",", pos);
2392 if (pos > 0 && pos2 > 0) {
2393 pos += title.length();
2394 s = s.substring(0, pos)
2395 + withWeak(s.substring(pos, pos2))
2396 + s.substring(pos2);
2397 }
2398 out.println(s);
2399 }
2400 }
2401
2402 private void doPrintCertReq(InputStream in, PrintStream out)
2403 throws Exception {
2404
2405 BufferedReader reader = new BufferedReader(new InputStreamReader(in));
2406 StringBuffer sb = new StringBuffer();
2407 boolean started = false;
2408 while (true) {
2409 String s = reader.readLine();
2410 if (s == null) break;
2411 if (!started) {
2412 if (s.startsWith("-----")) {
2413 started = true;
2414 }
2415 } else {
2416 if (s.startsWith("-----")) {
2417 break;
2418 }
2419 sb.append(s);
2420 }
2421 }
2422 PKCS10 req = new PKCS10(Pem.decode(new String(sb)));
2423
2424 PublicKey pkey = req.getSubjectPublicKeyInfo();
2425 out.printf(rb.getString("PKCS.10.with.weak"),
2426 req.getSubjectName(),
2427 pkey.getFormat(),
2428 withWeak(pkey),
2429 withWeak(req.getSigAlg()));
2430 for (PKCS10Attribute attr: req.getAttributes().getAttributes()) {
2431 ObjectIdentifier oid = attr.getAttributeId();
2432 if (oid.equals(PKCS9Attribute.EXTENSION_REQUEST_OID)) {
2433 CertificateExtensions exts = (CertificateExtensions)attr.getAttributeValue();
2434 if (exts != null) {
2435 printExtensions(rb.getString("Extension.Request."), exts, out);
2436 }
2437 } else {
2438 out.println("Attribute: " + attr.getAttributeId());
2439 PKCS9Attribute pkcs9Attr =
2440 new PKCS9Attribute(attr.getAttributeId(),
2441 attr.getAttributeValue());
2442 out.print(pkcs9Attr.getName() + ": ");
2443 Object attrVal = attr.getAttributeValue();
2444 out.println(attrVal instanceof String[] ?
2445 Arrays.toString((String[]) attrVal) :
2446 attrVal);
2447 }
2448 }
2449 if (debug) {
2450 out.println(req); // Just to see more, say, public key length...
2451 }
2452 checkWeak(null, req);
2453 }
2454
2455 /**
2456 * Reads a certificate (or certificate chain) and prints its contents in
2457 * a human readable format.
2458 */
2459 private void printCertFromStream(InputStream in, PrintStream out)
2460 throws Exception
2461 {
2462 Collection<? extends Certificate> c = null;
2463 try {
2464 c = cf.generateCertificates(in);
2465 } catch (CertificateException ce) {
2466 throw new Exception(rb.getString("Failed.to.parse.input"), ce);
2467 }
2468 if (c.isEmpty()) {
2469 throw new Exception(rb.getString("Empty.input"));
2470 }
2471 Certificate[] certs = c.toArray(new Certificate[c.size()]);
2472 for (int i=0; i<certs.length; i++) {
2473 X509Certificate x509Cert = null;
2474 try {
2475 x509Cert = (X509Certificate)certs[i];
2476 } catch (ClassCastException cce) {
2477 throw new Exception(rb.getString("Not.X.509.certificate"));
2478 }
2479 if (certs.length > 1) {
2480 MessageFormat form = new MessageFormat
2481 (rb.getString("Certificate.i.1."));
2482 Object[] source = {i + 1};
2483 out.println(form.format(source));
2484 }
2485 if (rfc)
2486 dumpCert(x509Cert, out);
2487 else
2488 printX509Cert(x509Cert, out);
2489 if (i < (certs.length-1)) {
2490 out.println();
2491 }
2492 checkWeak(oneInMany(i, certs.length), x509Cert);
2493 }
2494 }
2495
2496 private static String oneInMany(int i, int num) {
2497 if (num == 1) {
2498 return null;
2499 } else {
2500 return String.format(rb.getString("one.in.many"), i+1, num);
2501 }
2502 }
2503
2504 private void doPrintCert(final PrintStream out) throws Exception {
2505 if (jarfile != null) {
2506 // reset "jdk.certpath.disabledAlgorithms" security property
2507 // to be able to read jars which were signed with weak algorithms
2508 Security.setProperty(DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS, "");
2509
2510 JarFile jf = new JarFile(jarfile, true);
2511 Enumeration<JarEntry> entries = jf.entries();
2512 Set<CodeSigner> ss = new HashSet<>();
2513 byte[] buffer = new byte[8192];
2514 int pos = 0;
2515 while (entries.hasMoreElements()) {
2516 JarEntry je = entries.nextElement();
2517 try (InputStream is = jf.getInputStream(je)) {
2518 while (is.read(buffer) != -1) {
2519 // we just read. this will throw a SecurityException
2520 // if a signature/digest check fails. This also
2521 // populate the signers
2522 }
2523 }
2524 CodeSigner[] signers = je.getCodeSigners();
2525 if (signers != null) {
2526 for (CodeSigner signer: signers) {
2527 if (!ss.contains(signer)) {
2528 ss.add(signer);
2529 out.printf(rb.getString("Signer.d."), ++pos);
2530 out.println();
2531 out.println();
2532 out.println(rb.getString("Signature."));
2533 out.println();
2534
2535 List<? extends Certificate> certs
2536 = signer.getSignerCertPath().getCertificates();
2537 int cc = 0;
2538 for (Certificate cert: certs) {
2539 X509Certificate x = (X509Certificate)cert;
2540 if (rfc) {
2541 out.println(rb.getString("Certificate.owner.") + x.getSubjectDN() + "\n");
2542 dumpCert(x, out);
2543 } else {
2544 printX509Cert(x, out);
2545 }
2546 out.println();
2547 checkWeak(oneInMany(cc++, certs.size()), x);
2548 }
2549 Timestamp ts = signer.getTimestamp();
2550 if (ts != null) {
2551 out.println(rb.getString("Timestamp."));
2552 out.println();
2553 certs = ts.getSignerCertPath().getCertificates();
2554 cc = 0;
2555 for (Certificate cert: certs) {
2556 X509Certificate x = (X509Certificate)cert;
2557 if (rfc) {
2558 out.println(rb.getString("Certificate.owner.") + x.getSubjectDN() + "\n");
2559 dumpCert(x, out);
2560 } else {
2561 printX509Cert(x, out);
2562 }
2563 out.println();
2564 checkWeak(oneInMany(cc++, certs.size()), x);
2565 }
2566 }
2567 }
2568 }
2569 }
2570 }
2571 jf.close();
2572 if (ss.isEmpty()) {
2573 out.println(rb.getString("Not.a.signed.jar.file"));
2574 }
2575 } else if (sslserver != null) {
2576 CertStore cs = SSLServerCertStore.getInstance(new URI("https://" + sslserver));
2577 Collection<? extends Certificate> chain;
2578 try {
2579 chain = cs.getCertificates(null);
2580 if (chain.isEmpty()) {
2581 // If the certs are not retrieved, we consider it an error
2582 // even if the URL connection is successful.
2583 throw new Exception(rb.getString(
2584 "No.certificate.from.the.SSL.server"));
2587 if (cse.getCause() instanceof IOException) {
2588 throw new Exception(rb.getString(
2589 "No.certificate.from.the.SSL.server"),
2590 cse.getCause());
2591 } else {
2592 throw cse;
2593 }
2594 }
2595
2596 int i = 0;
2597 for (Certificate cert : chain) {
2598 try {
2599 if (rfc) {
2600 dumpCert(cert, out);
2601 } else {
2602 out.println("Certificate #" + i++);
2603 out.println("====================================");
2604 printX509Cert((X509Certificate)cert, out);
2605 out.println();
2606 }
2607 checkWeak(oneInMany(i, chain.size()), cert);
2608 } catch (Exception e) {
2609 if (debug) {
2610 e.printStackTrace();
2611 }
2612 }
2613 }
2614 } else {
2615 if (filename != null) {
2616 try (FileInputStream inStream = new FileInputStream(filename)) {
2617 printCertFromStream(inStream, out);
2618 }
2619 } else {
2620 printCertFromStream(System.in, out);
2621 }
2622 }
2623 }
2624 /**
2625 * Creates a self-signed certificate, and stores it as a single-element
2626 * certificate chain.
2627 */
2763 Object[] source = {alias};
2764 throw new Exception(form.format(source));
2765 }
2766
2767 // Read the certificates in the reply
2768 Collection<? extends Certificate> c = cf.generateCertificates(in);
2769 if (c.isEmpty()) {
2770 throw new Exception(rb.getString("Reply.has.no.certificates"));
2771 }
2772 Certificate[] replyCerts = c.toArray(new Certificate[c.size()]);
2773 Certificate[] newChain;
2774 if (replyCerts.length == 1) {
2775 // single-cert reply
2776 newChain = establishCertChain(userCert, replyCerts[0]);
2777 } else {
2778 // cert-chain reply (e.g., PKCS#7)
2779 newChain = validateReply(alias, userCert, replyCerts);
2780 }
2781
2782 // Now store the newly established chain in the keystore. The new
2783 // chain replaces the old one. The chain can be null if user chooses no.
2784 if (newChain != null) {
2785 keyStore.setKeyEntry(alias, privKey,
2786 (keyPass != null) ? keyPass : storePass,
2787 newChain);
2788 return true;
2789 } else {
2790 return false;
2791 }
2792 }
2793
2794 /**
2795 * Imports a certificate and adds it to the list of trusted certificates.
2796 *
2797 * @return true if the certificate was added, otherwise false.
2798 */
2799 private boolean addTrustedCert(String alias, InputStream in)
2800 throws Exception
2801 {
2802 if (alias == null) {
2803 throw new Exception(rb.getString("Must.specify.alias"));
2804 }
2805 if (keyStore.containsAlias(alias)) {
2806 MessageFormat form = new MessageFormat(rb.getString
2807 ("Certificate.not.imported.alias.alias.already.exists"));
2808 Object[] source = {alias};
2809 throw new Exception(form.format(source));
2810 }
2811
2812 // Read the certificate
2813 X509Certificate cert = null;
2814 try {
2815 cert = (X509Certificate)cf.generateCertificate(in);
2816 } catch (ClassCastException | CertificateException ce) {
2817 throw new Exception(rb.getString("Input.not.an.X.509.certificate"));
2818 }
2819
2820 if (noprompt) {
2821 keyStore.setCertificateEntry(alias, cert);
2822 checkWeak(null, cert);
2823 return true;
2824 }
2825
2826 // if certificate is self-signed, make sure it verifies
2827 boolean selfSigned = false;
2828 if (KeyStoreUtil.isSelfSigned(cert)) {
2829 cert.verify(cert.getPublicKey());
2830 selfSigned = true;
2831 }
2832
2833 // check if cert already exists in keystore
2834 String reply = null;
2835 String trustalias = keyStore.getCertificateAlias(cert);
2836 if (trustalias != null) {
2837 MessageFormat form = new MessageFormat(rb.getString
2838 ("Certificate.already.exists.in.keystore.under.alias.trustalias."));
2839 Object[] source = {trustalias};
2840 System.err.println(form.format(source));
2841 checkWeak(null, cert);
2842 printWeakWarnings();
2843 reply = getYesNoReply
2844 (rb.getString("Do.you.still.want.to.add.it.no."));
2845 } else if (selfSigned) {
2846 if (trustcacerts && (caks != null) &&
2847 ((trustalias=caks.getCertificateAlias(cert)) != null)) {
2848 MessageFormat form = new MessageFormat(rb.getString
2849 ("Certificate.already.exists.in.system.wide.CA.keystore.under.alias.trustalias."));
2850 Object[] source = {trustalias};
2851 System.err.println(form.format(source));
2852 checkWeak(null, cert);
2853 printWeakWarnings();
2854 reply = getYesNoReply
2855 (rb.getString("Do.you.still.want.to.add.it.to.your.own.keystore.no."));
2856 }
2857 if (trustalias == null) {
2858 // Print the cert and ask user if they really want to add
2859 // it to their keystore
2860 printX509Cert(cert, System.out);
2861 checkWeak(null, cert);
2862 printWeakWarnings();
2863 reply = getYesNoReply
2864 (rb.getString("Trust.this.certificate.no."));
2865 }
2866 }
2867 if (reply != null) {
2868 if ("YES".equals(reply)) {
2869 keyStore.setCertificateEntry(alias, cert);
2870 return true;
2871 } else {
2872 return false;
2873 }
2874 }
2875
2876 // Not found in this keystore and not self-signed
2877 // Try to establish trust chain
2878 try {
2879 Certificate[] chain = establishCertChain(null, cert);
2880 if (chain != null) {
2881 keyStore.setCertificateEntry(alias, cert);
2882 return true;
2883 }
2884 } catch (Exception e) {
2885 // Print the cert and ask user if they really want to add it to
2886 // their keystore
2887 printX509Cert(cert, System.out);
2888 checkWeak(null, cert);
2889 printWeakWarnings();
2890 reply = getYesNoReply
2891 (rb.getString("Trust.this.certificate.no."));
2892 if ("YES".equals(reply)) {
2893 keyStore.setCertificateEntry(alias, cert);
2894 return true;
2895 } else {
2896 return false;
2897 }
2898 }
2899
2900 return false;
2901 }
2902
2903 /**
2904 * Prompts user for new password. New password must be different from
2905 * old one.
2906 *
2907 * @param prompt the message that gets prompted on the screen
2908 * @param oldPasswd the current (i.e., old) password
2909 */
3008 ("Enter.key.password.for.alias."));
3009 Object[] source = {alias};
3010 System.err.print(form.format(source));
3011 }
3012 System.err.flush();
3013 keyPass = Password.readPassword(System.in);
3014 passwords.add(keyPass);
3015 if (keyPass == null) {
3016 keyPass = otherKeyPass;
3017 }
3018 count++;
3019 } while ((keyPass == null) && count < 3);
3020
3021 if (keyPass == null) {
3022 throw new Exception(rb.getString("Too.many.failures.try.later"));
3023 }
3024
3025 return keyPass;
3026 }
3027
3028 private String withWeak(String alg) {
3029 if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, alg, null)) {
3030 return alg;
3031 } else {
3032 return String.format(rb.getString("with.weak"), alg);
3033 }
3034 }
3035
3036 private String withWeak(PublicKey key) {
3037 if (DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
3038 return String.format(rb.getString("key.bit"),
3039 KeyUtil.getKeySize(key), key.getAlgorithm());
3040 } else {
3041 return String.format(rb.getString("key.bit.weak"),
3042 KeyUtil.getKeySize(key), key.getAlgorithm());
3043 }
3044 }
3045
3046 /**
3047 * Prints a certificate in a human readable format.
3048 */
3049 private void printX509Cert(X509Certificate cert, PrintStream out)
3050 throws Exception
3051 {
3052
3053 MessageFormat form = new MessageFormat
3054 (rb.getString(".PATTERN.printX509Cert.with.weak"));
3055 PublicKey pkey = cert.getPublicKey();
3056 Object[] source = {cert.getSubjectDN().toString(),
3057 cert.getIssuerDN().toString(),
3058 cert.getSerialNumber().toString(16),
3059 cert.getNotBefore().toString(),
3060 cert.getNotAfter().toString(),
3061 getCertFingerPrint("SHA-1", cert),
3062 getCertFingerPrint("SHA-256", cert),
3063 withWeak(cert.getSigAlgName()),
3064 withWeak(pkey),
3065 cert.getVersion()
3066 };
3067 out.println(form.format(source));
3068
3069 if (cert instanceof X509CertImpl) {
3070 X509CertImpl impl = (X509CertImpl)cert;
3071 X509CertInfo certInfo = (X509CertInfo)impl.get(X509CertImpl.NAME
3072 + "." +
3073 X509CertImpl.INFO);
3074 CertificateExtensions exts = (CertificateExtensions)
3075 certInfo.get(X509CertInfo.EXTENSIONS);
3076 if (exts != null) {
3077 printExtensions(rb.getString("Extensions."), exts, out);
3078 }
3079 }
3080 }
3081
3082 private static void printExtensions(String title, CertificateExtensions exts, PrintStream out)
3083 throws Exception {
3084 int extnum = 0;
3085 Iterator<Extension> i1 = exts.getAllExtensions().iterator();
3373 }
3374 }
3375
3376 return Pair.of(entry, pkey);
3377 }
3378 /**
3379 * Gets the requested finger print of the certificate.
3380 */
3381 private String getCertFingerPrint(String mdAlg, Certificate cert)
3382 throws Exception
3383 {
3384 byte[] encCertInfo = cert.getEncoded();
3385 MessageDigest md = MessageDigest.getInstance(mdAlg);
3386 byte[] digest = md.digest(encCertInfo);
3387 return toHexString(digest);
3388 }
3389
3390 /**
3391 * Prints warning about missing integrity check.
3392 */
3393 private void printNoIntegrityWarning() {
3394 System.err.println();
3395 System.err.println(rb.getString
3396 (".WARNING.WARNING.WARNING."));
3397 System.err.println(rb.getString
3398 (".The.integrity.of.the.information.stored.in.your.keystore."));
3399 System.err.println(rb.getString
3400 (".WARNING.WARNING.WARNING."));
3401 System.err.println();
3402 }
3403
3404 /**
3405 * Validates chain in certification reply, and returns the ordered
3406 * elements of the chain (with user certificate first, and root
3407 * certificate last in the array).
3408 *
3409 * @param alias the alias name
3410 * @param userCert the user certificate of the alias
3411 * @param replyCerts the chain provided in the reply
3412 */
3413 private Certificate[] validateReply(String alias,
3414 Certificate userCert,
3415 Certificate[] replyCerts)
3416 throws Exception
3417 {
3418
3419 checkWeak(rb.getString("reply"), replyCerts);
3420
3421 // order the certs in the reply (bottom-up).
3422 // we know that all certs in the reply are of type X.509, because
3423 // we parsed them using an X.509 certificate factory
3424 int i;
3425 PublicKey userPubKey = userCert.getPublicKey();
3426
3427 // Remove duplicated certificates.
3428 HashSet<Certificate> nodup = new HashSet<>(Arrays.asList(replyCerts));
3429 replyCerts = nodup.toArray(new Certificate[nodup.size()]);
3430
3431 for (i=0; i<replyCerts.length; i++) {
3432 if (userPubKey.equals(replyCerts[i].getPublicKey())) {
3433 break;
3434 }
3435 }
3436 if (i == replyCerts.length) {
3437 MessageFormat form = new MessageFormat(rb.getString
3438 ("Certificate.reply.does.not.contain.public.key.for.alias."));
3439 Object[] source = {alias};
3440 throw new Exception(form.format(source));
3464 }
3465 }
3466
3467 if (noprompt) {
3468 return replyCerts;
3469 }
3470
3471 // do we trust the cert at the top?
3472 Certificate topCert = replyCerts[replyCerts.length-1];
3473 Certificate root = getTrustedSigner(topCert, keyStore);
3474 if (root == null && trustcacerts && caks != null) {
3475 root = getTrustedSigner(topCert, caks);
3476 }
3477 if (root == null) {
3478 System.err.println();
3479 System.err.println
3480 (rb.getString("Top.level.certificate.in.reply."));
3481 printX509Cert((X509Certificate)topCert, System.out);
3482 System.err.println();
3483 System.err.print(rb.getString(".is.not.trusted."));
3484 printWeakWarnings();
3485 String reply = getYesNoReply
3486 (rb.getString("Install.reply.anyway.no."));
3487 if ("NO".equals(reply)) {
3488 return null;
3489 }
3490 } else {
3491 if (root != topCert) {
3492 // append the root CA cert to the chain
3493 Certificate[] tmpCerts =
3494 new Certificate[replyCerts.length+1];
3495 System.arraycopy(replyCerts, 0, tmpCerts, 0,
3496 replyCerts.length);
3497 tmpCerts[tmpCerts.length-1] = root;
3498 replyCerts = tmpCerts;
3499 checkWeak(rb.getString("root"), root);
3500 }
3501 if (!weakWarnings.isEmpty()) {
3502 printWeakWarnings();
3503 String reply = getYesNoReply
3504 (rb.getString("Install.reply.anyway.no."));
3505 if ("NO".equals(reply)) {
3506 return null;
3507 }
3508 }
3509 }
3510
3511 return replyCerts;
3512 }
3513
3514 /**
3515 * Establishes a certificate chain (using trusted certificates in the
3516 * keystore and cacerts), starting with the reply (certToVerify)
3517 * and ending at a self-signed certificate found in the keystore.
3518 *
3519 * @param userCert optional existing certificate, mostly likely be the
3520 * original self-signed cert created by -genkeypair.
3521 * It must have the same public key as certToVerify
3522 * but cannot be the same cert.
3523 * @param certToVerify the starting certificate to build the chain
3524 * @returns the established chain, might be null if user decides not
3525 */
3526 private Certificate[] establishCertChain(Certificate userCert,
3527 Certificate certToVerify)
3528 throws Exception
3529 {
3530 if (userCert != null) {
3531 // Make sure that the public key of the certificate reply matches
3532 // the original public key in the keystore
3533 PublicKey origPubKey = userCert.getPublicKey();
3534 PublicKey replyPubKey = certToVerify.getPublicKey();
3535 if (!origPubKey.equals(replyPubKey)) {
3536 throw new Exception(rb.getString
3537 ("Public.keys.in.reply.and.keystore.don.t.match"));
3538 }
3539
3540 // If the two certs are identical, we're done: no need to import
3541 // anything
3542 if (certToVerify.equals(userCert)) {
3543 throw new Exception(rb.getString
3544 ("Certificate.reply.and.certificate.in.keystore.are.identical"));
3545 }
3546 }
3547
3548 // Build a hash table of all certificates in the keystore.
3549 // Use the subject distinguished name as the key into the hash table.
3550 // All certificates associated with the same subject distinguished
3551 // name are stored in the same hash table entry as a vector.
3552 Hashtable<Principal, Vector<Pair<String,X509Certificate>>> certs = null;
3553 if (keyStore.size() > 0) {
3554 certs = new Hashtable<>(11);
3555 keystorecerts2Hashtable(keyStore, certs);
3556 }
3557 if (trustcacerts) {
3558 if (caks!=null && caks.size()>0) {
3559 if (certs == null) {
3560 certs = new Hashtable<>(11);
3561 }
3562 keystorecerts2Hashtable(caks, certs);
3563 }
3564 }
3565
3566 // start building chain
3567 Vector<Pair<String,X509Certificate>> chain = new Vector<>(2);
3568 if (buildChain(
3569 new Pair<>(rb.getString("the.input"),
3570 (X509Certificate) certToVerify),
3571 chain, certs)) {
3572 for (Pair<String,X509Certificate> p : chain) {
3573 checkWeak(p.fst, p.snd);
3574 }
3575 if (!weakWarnings.isEmpty() && !noprompt) {
3576 printWeakWarnings();
3577 String reply = getYesNoReply
3578 (rb.getString("Trust.this.certificate.no."));
3579 if (!"YES".equals(reply)) {
3580 return null;
3581 }
3582
3583 }
3584 Certificate[] newChain =
3585 new Certificate[chain.size()];
3586 // buildChain() returns chain with self-signed root-cert first and
3587 // user-cert last, so we need to invert the chain before we store
3588 // it
3589 int j=0;
3590 for (int i=chain.size()-1; i>=0; i--) {
3591 newChain[j] = chain.elementAt(i).snd;
3592 j++;
3593 }
3594 return newChain;
3595 } else {
3596 throw new Exception
3597 (rb.getString("Failed.to.establish.chain.from.reply"));
3598 }
3599 }
3600
3601 /**
3602 * Recursively tries to establish chain from pool of certs starting from
3603 * certToVerify until a self-signed cert is found, and fill the certs found
3604 * into chain. Each cert in the chain signs the next one.
3605 *
3606 * This method is able to recover from an error, say, if certToVerify
3607 * is signed by certA but certA has no issuer in certs and itself is not
3608 * self-signed, the method can try another certB that also signs
3609 * certToVerify and look for signer of certB, etc, etc.
3610 *
3611 * Each cert in chain comes with a label showing its origin. The label is
3612 * used in the warning message when the cert is considered a risk.
3613 *
3614 * @param certToVerify the cert that needs to be verified.
3615 * @param chain the chain that's being built.
3616 * @param certs the pool of trusted certs
3617 *
3618 * @return true if successful, false otherwise.
3619 */
3620 private boolean buildChain(Pair<String,X509Certificate> certToVerify,
3621 Vector<Pair<String,X509Certificate>> chain,
3622 Hashtable<Principal, Vector<Pair<String,X509Certificate>>> certs) {
3623 if (KeyStoreUtil.isSelfSigned(certToVerify.snd)) {
3624 // reached self-signed root cert;
3625 // no verification needed because it's trusted.
3626 chain.addElement(certToVerify);
3627 return true;
3628 }
3629
3630 Principal issuer = certToVerify.snd.getIssuerDN();
3631
3632 // Get the issuer's certificate(s)
3633 Vector<Pair<String,X509Certificate>> vec = certs.get(issuer);
3634 if (vec == null) {
3635 return false;
3636 }
3637
3638 // Try out each certificate in the vector, until we find one
3639 // whose public key verifies the signature of the certificate
3640 // in question.
3641 for (Enumeration<Pair<String,X509Certificate>> issuerCerts = vec.elements();
3642 issuerCerts.hasMoreElements(); ) {
3643 Pair<String,X509Certificate> issuerCert = issuerCerts.nextElement();
3644 PublicKey issuerPubKey = issuerCert.snd.getPublicKey();
3645 try {
3646 certToVerify.snd.verify(issuerPubKey);
3647 } catch (Exception e) {
3648 continue;
3649 }
3650 if (buildChain(issuerCert, chain, certs)) {
3651 chain.addElement(certToVerify);
3652 return true;
3653 }
3654 }
3655 return false;
3656 }
3657
3658 /**
3659 * Prompts user for yes/no decision.
3660 *
3661 * @return the user's decision, can only be "YES" or "NO"
3662 */
3663 private String getYesNoReply(String prompt)
3664 throws IOException
3665 {
3666 String reply = null;
3676 (System.in))).readLine();
3677 if (reply == null ||
3678 collator.compare(reply, "") == 0 ||
3679 collator.compare(reply, rb.getString("n")) == 0 ||
3680 collator.compare(reply, rb.getString("no")) == 0) {
3681 reply = "NO";
3682 } else if (collator.compare(reply, rb.getString("y")) == 0 ||
3683 collator.compare(reply, rb.getString("yes")) == 0) {
3684 reply = "YES";
3685 } else {
3686 System.err.println(rb.getString("Wrong.answer.try.again"));
3687 reply = null;
3688 }
3689 } while (reply == null);
3690 return reply;
3691 }
3692
3693 /**
3694 * Stores the (leaf) certificates of a keystore in a hashtable.
3695 * All certs belonging to the same CA are stored in a vector that
3696 * in turn is stored in the hashtable, keyed by the CA's subject DN.
3697 * Each cert comes with a string label that shows its origin and alias.
3698 */
3699 private void keystorecerts2Hashtable(KeyStore ks,
3700 Hashtable<Principal, Vector<Pair<String,X509Certificate>>> hash)
3701 throws Exception {
3702
3703 for (Enumeration<String> aliases = ks.aliases();
3704 aliases.hasMoreElements(); ) {
3705 String alias = aliases.nextElement();
3706 Certificate cert = ks.getCertificate(alias);
3707 if (cert != null) {
3708 Principal subjectDN = ((X509Certificate)cert).getSubjectDN();
3709 Pair<String,X509Certificate> pair = new Pair<>(
3710 ks == caks ?
3711 (String.format(rb.getString("alias.in.cacerts"),
3712 alias)) :
3713 ("<" + alias + ">"),
3714 (X509Certificate)cert);
3715 Vector<Pair<String,X509Certificate>> vec = hash.get(subjectDN);
3716 if (vec == null) {
3717 vec = new Vector<>();
3718 vec.addElement(pair);
3719 } else {
3720 if (!vec.contains(pair)) {
3721 vec.addElement(pair);
3722 }
3723 }
3724 hash.put(subjectDN, vec);
3725 }
3726 }
3727 }
3728
3729 /**
3730 * Returns the issue time that's specified the -startdate option
3731 * @param s the value of -startdate option
3732 */
3733 private static Date getStartDate(String s) throws IOException {
3734 Calendar c = new GregorianCalendar();
3735 if (s != null) {
3736 IOException ioe = new IOException(
3737 rb.getString("Illegal.startdate.value"));
3738 int len = s.length();
3739 if (len == 0) {
3740 throw ioe;
3741 }
4297 setExt(result, new Extension(oid, isCritical,
4298 new DerValue(DerValue.tag_OctetString, data)
4299 .toByteArray()));
4300 break;
4301 default:
4302 throw new Exception(rb.getString(
4303 "Unknown.extension.type.") + extstr);
4304 }
4305 }
4306 // always non-critical
4307 setExt(result, new SubjectKeyIdentifierExtension(
4308 new KeyIdentifier(pkey).getIdentifier()));
4309 if (akey != null && !pkey.equals(akey)) {
4310 setExt(result, new AuthorityKeyIdentifierExtension(
4311 new KeyIdentifier(akey), null, null));
4312 }
4313 } catch(IOException e) {
4314 throw new RuntimeException(e);
4315 }
4316 return result;
4317 }
4318
4319 private void checkWeak(String label, String sigAlg, Key key) {
4320
4321 if (!DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, sigAlg, null)) {
4322 if (label == null) {
4323 weakWarnings.add(String.format(
4324 rb.getString("sigalg.risk"), sigAlg));
4325 } else {
4326 weakWarnings.add(String.format(
4327 rb.getString("whose.sigalg.risk"), label, sigAlg));
4328 }
4329 }
4330 if (key != null && !DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
4331 if (label == null) {
4332 weakWarnings.add(String.format(
4333 rb.getString("key.risk"),
4334 String.format(rb.getString("key.bit"),
4335 KeyUtil.getKeySize(key), key.getAlgorithm())));
4336 } else {
4337 weakWarnings.add(String.format(
4338 rb.getString("whose.key.risk"),
4339 label,
4340 String.format(rb.getString("key.bit"),
4341 KeyUtil.getKeySize(key), key.getAlgorithm())));
4342 }
4343 }
4344 }
4345
4346 private void checkWeak(String label, Certificate[] certs) {
4347 for (int i = 0; i < certs.length; i++) {
4348 Certificate cert = certs[i];
4349 if (cert instanceof X509Certificate) {
4350 X509Certificate xc = (X509Certificate)cert;
4351 String fullLabel = label;
4352 if (certs.length > 1) {
4353 fullLabel = oneInMany(i, certs.length);
4354 if (label != null) {
4355 fullLabel = String.format(rb.getString("label.which"), label, fullLabel);
4356 }
4357 }
4358 checkWeak(fullLabel, xc.getSigAlgName(), xc.getPublicKey());
4359 }
4360 }
4361 }
4362
4363 private void checkWeak(String label, Certificate cert) {
4364 if (cert instanceof X509Certificate) {
4365 X509Certificate xc = (X509Certificate)cert;
4366 checkWeak(label, xc.getSigAlgName(), xc.getPublicKey());
4367 }
4368 }
4369
4370 private void checkWeak(String label, PKCS10 p10) {
4371 checkWeak(label, p10.getSigAlg(), p10.getSubjectPublicKeyInfo());
4372 }
4373
4374 private void checkWeak(String label, CRL crl, Key key) {
4375 if (crl instanceof X509CRLImpl) {
4376 X509CRLImpl impl = (X509CRLImpl)crl;
4377 checkWeak(label, impl.getSigAlgName(), key);
4378 }
4379 }
4380
4381 private void printWeakWarningsWithoutNewLine() {
4382 if (!weakWarnings.isEmpty() && !nowarn) {
4383 System.err.println("\nWarning:");
4384 for (String warning : weakWarnings) {
4385 System.err.println(warning);
4386 }
4387 }
4388 weakWarnings.clear();
4389 }
4390
4391 private void printWeakWarnings() {
4392 if (!weakWarnings.isEmpty() && !nowarn) {
4393 System.err.println("\nWarning:");
4394 for (String warning : weakWarnings) {
4395 System.err.println(warning);
4396 }
4397 System.err.println();
4398 }
4399 weakWarnings.clear();
4400 }
4401
4402 /**
4403 * Prints the usage of this tool.
4404 */
4405 private void usage() {
4406 if (command != null) {
4407 System.err.println("keytool " + command +
4408 rb.getString(".OPTION."));
4409 System.err.println();
4410 System.err.println(rb.getString(command.description));
4411 System.err.println();
4412 System.err.println(rb.getString("Options."));
4413 System.err.println();
4414
4415 // Left and right sides of the options list. Both might
4416 // contain "\n" and span multiple lines
4417 String[] left = new String[command.options.length];
4418 String[] right = new String[command.options.length];
4419
|