1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8076190
  27  * @library /lib/testlibrary /lib
  28  * @modules java.base/sun.security.pkcs
  29  *          java.base/sun.security.x509
  30  *          java.base/sun.security.util
  31  * @summary Customizing the generation of a PKCS12 keystore
  32  */
  33 
  34 import jdk.test.lib.Asserts;
  35 import jdk.test.lib.SecurityTools;
  36 import jdk.test.lib.process.OutputAnalyzer;
  37 
  38 import java.io.File;
  39 import java.io.FileInputStream;
  40 import java.io.FileOutputStream;
  41 import java.io.IOException;
  42 import java.io.InputStream;
  43 import java.io.OutputStream;
  44 import java.io.UncheckedIOException;
  45 import java.nio.file.Files;
  46 import java.nio.file.Paths;
  47 import java.security.KeyStore;
  48 import java.util.Base64;
  49 import java.util.Objects;
  50 
  51 import static jdk.testlibrary.security.DerUtils.*;
  52 import static sun.security.x509.AlgorithmId.*;
  53 import static sun.security.pkcs.ContentInfo.*;
  54 
  55 public class ParamsTest  {
  56 
  57     public static void main(String[] args) throws Throwable {
  58 
  59         // De-BASE64 textual files in ./params to `pwd`
  60         Files.newDirectoryStream(Paths.get(System.getProperty("test.src"), "params"))
  61                 .forEach(p -> {
  62                     try (InputStream is = Base64.getMimeDecoder().wrap(Files.newInputStream(p));
  63                          OutputStream os = Files.newOutputStream(p.getFileName())){
  64                             byte[] buffer = new byte[2048];
  65                             int read;
  66                             while ((read = is.read(buffer, 0, 2048)) >= 0) {
  67                                 os.write(buffer, 0, read);
  68                             }
  69                     } catch (IOException e) {
  70                         throw new UncheckedIOException(e);
  71                     }
  72                 });
  73 
  74         byte[] data;
  75 
  76         // openssl -> keytool interop check
  77 
  78         // os2. no cert pbe, no mac.
  79         check("os2", "a", null, "changeit", true, true, true);
  80         check("os2", "a", "changeit", "changeit", true, true, true);
  81         // You can even load it with a wrong storepass, controversial
  82         check("os2", "a", "wrongpass", "changeit", true, true, true);
  83 
  84         // os3. no cert pbe, has mac. just like JKS
  85         check("os3", "a", null, "changeit", true, true, true);
  86         check("os3", "a", "changeit", "changeit", true, true, true);
  87         // Cannot load with a wrong storepass, same as JKS
  88         check("os3", "a", "wrongpass", "-", IOException.class, "-", "-");
  89 
  90         // os4. non default algs
  91         check("os4", "a", "changeit", "changeit", true, true, true);
  92         check("os4", "a", "wrongpass", "-", IOException.class, "-", "-");
  93         // no storepass no cert
  94         check("os4", "a", null, "changeit", true, false, true);
  95 
  96         // os5. strong non default algs
  97         check("os5", "a", "changeit", "changeit", true, true, true);
  98         check("os5", "a", "wrongpass", "-", IOException.class, "-", "-");
  99         // no storepass no cert
 100         check("os5", "a", null, "changeit", true, false, true);
 101 
 102         // keytool
 103 
 104         // Current default pkcs12 setting
 105         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
 106                 + "-deststoretype PKCS12 -destkeystore ksnormal -deststorepass changeit");
 107         data = Files.readAllBytes(Paths.get("ksnormal"));
 108         checkInt(data, "22", 100000); // Mac ic
 109         checkAlg(data, "2000", SHA_oid); // Mac alg
 110         checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg
 111         checkInt(data, "110c010c010011", 50000); // key ic
 112         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
 113         checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg
 114         checkInt(data, "110c1101111", 50000); // cert ic
 115 
 116         check("ksnormal", "a", "changeit", "changeit", true, true, true);
 117         check("ksnormal", "a", null, "changeit", true, false, true);
 118         check("ksnormal", "a", "wrongpass", "-", IOException.class, "-", "-");
 119 
 120         // Add a new entry with password-less settings, still has a storepass
 121         keytool("-keystore ksnormal -genkeypair -storepass changeit -alias b -dname CN=b "
 122                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
 123                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE");
 124         data = Files.readAllBytes(Paths.get("ksnormal"));
 125         checkInt(data, "22", 100000); // Mac ic
 126         checkAlg(data, "2000", SHA_oid); // Mac alg
 127         checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg
 128         checkInt(data, "110c010c010011", 50000); // key ic
 129         checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid); // new key alg
 130         checkInt(data, "110c010c110011", 50000); // new key ic
 131         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
 132         checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg
 133         checkInt(data, "110c1101111", 50000); // cert ic
 134         check("ksnormal", "b", null, "changeit", true, false, true);
 135         check("ksnormal", "b", "changeit", "changeit", true, true, true);
 136 
 137         // Different keypbe alg, no cert pbe and no mac
 138         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
 139                 + "-deststoretype PKCS12 -destkeystore ksnopass -deststorepass changeit "
 140                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128 "
 141                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
 142                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE");
 143         data = Files.readAllBytes(Paths.get("ksnopass"));
 144         shouldNotExist(data, "2"); // no Mac
 145         checkAlg(data, "110c010c01000", pbeWithSHA1AndRC4_128_oid);
 146         checkInt(data, "110c010c010011", 50000);
 147         checkAlg(data, "110c10", DATA_OID);
 148         check("ksnopass", "a", null, "changeit", true, true, true);
 149         check("ksnopass", "a", "changeit", "changeit", true, true, true);
 150         check("ksnopass", "a", "wrongpass", "changeit", true, true, true);
 151 
 152         // Add a new entry with normal settings, still password-less
 153         keytool("-keystore ksnopass -genkeypair -storepass changeit -alias b -dname CN=B");
 154         data = Files.readAllBytes(Paths.get("ksnopass"));
 155         shouldNotExist(data, "2"); // no Mac
 156         checkAlg(data, "110c010c01000", pbeWithSHA1AndRC4_128_oid);
 157         checkInt(data, "110c010c010011", 50000);
 158         checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid);
 159         checkInt(data, "110c010c110011", 50000);
 160         checkAlg(data, "110c10", DATA_OID);
 161         check("ksnopass", "a", null, "changeit", true, true, true);
 162         check("ksnopass", "b", null, "changeit", true, true, true);
 163 
 164         keytool("-importkeystore -srckeystore ks -srcstorepass changeit "
 165                 + "-deststoretype PKCS12 -destkeystore ksnewic -deststorepass changeit "
 166                 + "-J-Dkeystore.pkcs12.macIterationCount=5555 "
 167                 + "-J-Dkeystore.pkcs12.certPbeIterationCount=6666 "
 168                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=7777");
 169         data = Files.readAllBytes(Paths.get("ksnewic"));
 170         checkInt(data, "22", 5555); // Mac ic
 171         checkAlg(data, "2000", SHA_oid); // Mac alg
 172         checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg
 173         checkInt(data, "110c010c010011", 7777); // key ic
 174         checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg
 175         checkInt(data, "110c1101111", 6666); // cert ic
 176 
 177         // keypbe alg cannot be NONE
 178         keytool("-keystore ksnewic -genkeypair -storepass changeit -alias b -dname CN=B "
 179                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=NONE")
 180                 .shouldContain("NONE AlgorithmParameters not available")
 181                 .shouldHaveExitValue(1);
 182 
 183         // new entry new keypbe alg (and default ic), else unchanged
 184         keytool("-keystore ksnewic -genkeypair -storepass changeit -alias b -dname CN=B "
 185                 + "-J-Dkeystore.pkcs12.keyProtectionAlgorithm=PBEWithSHA1AndRC4_128");
 186         data = Files.readAllBytes(Paths.get("ksnewic"));
 187         checkInt(data, "22", 5555); // Mac ic
 188         checkAlg(data, "2000", SHA_oid); // Mac alg
 189         checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg
 190         checkInt(data, "110c010c010011", 7777); // key ic
 191         checkAlg(data, "110c010c11000", pbeWithSHA1AndRC4_128_oid); // new key alg
 192         checkInt(data, "110c010c110011", 50000); // new key ic
 193         checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg
 194         checkInt(data, "110c1101111", 6666); // cert ic
 195 
 196         // Check KeyStore loading multiple keystores
 197         KeyStore ks = KeyStore.getInstance("pkcs12");
 198         try (FileInputStream fis = new FileInputStream("ksnormal");
 199                 FileOutputStream fos = new FileOutputStream("ksnormaldup")) {
 200             ks.load(fis, "changeit".toCharArray());
 201             ks.store(fos, "changeit".toCharArray());
 202         }
 203         data = Files.readAllBytes(Paths.get("ksnormaldup"));
 204         checkInt(data, "22", 100000); // Mac ic
 205         checkAlg(data, "2000", SHA_oid); // Mac alg
 206         checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg
 207         checkInt(data, "110c010c010011", 50000); // key ic
 208         checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid); // new key alg
 209         checkInt(data, "110c010c110011", 50000); // new key ic
 210         checkAlg(data, "110c10", ENCRYPTED_DATA_OID);
 211         checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg
 212         checkInt(data, "110c1101111", 50000); // cert ic
 213 
 214         try (FileInputStream fis = new FileInputStream("ksnopass");
 215              FileOutputStream fos = new FileOutputStream("ksnopassdup")) {
 216             ks.load(fis, "changeit".toCharArray());
 217             ks.store(fos, "changeit".toCharArray());
 218         }
 219         data = Files.readAllBytes(Paths.get("ksnopassdup"));
 220         shouldNotExist(data, "2"); // no Mac
 221         checkAlg(data, "110c010c01000", pbeWithSHA1AndRC4_128_oid);
 222         checkInt(data, "110c010c010011", 50000);
 223         checkAlg(data, "110c010c11000", pbeWithSHA1AndDESede_oid);
 224         checkInt(data, "110c010c110011", 50000);
 225         checkAlg(data, "110c10", DATA_OID);
 226 
 227         try (FileInputStream fis = new FileInputStream("ksnewic");
 228              FileOutputStream fos = new FileOutputStream("ksnewicdup")) {
 229             ks.load(fis, "changeit".toCharArray());
 230             ks.store(fos, "changeit".toCharArray());
 231         }
 232         data = Files.readAllBytes(Paths.get("ksnewicdup"));
 233         checkInt(data, "22", 5555); // Mac ic
 234         checkAlg(data, "2000", SHA_oid); // Mac alg
 235         checkAlg(data, "110c010c01000", pbeWithSHA1AndDESede_oid); // key alg
 236         checkInt(data, "110c010c010011", 7777); // key ic
 237         checkAlg(data, "110c010c11000", pbeWithSHA1AndRC4_128_oid); // new key alg
 238         checkInt(data, "110c010c110011", 50000); // new key ic
 239         checkAlg(data, "110c110110", pbeWithSHA1AndRC2_40_oid); // cert alg
 240         checkInt(data, "110c1101111", 6666); // cert ic
 241 
 242         // Check keytool behavior
 243 
 244         // ksnormal has password
 245 
 246         keytool("-list -keystore ksnormal")
 247                 .shouldContain("WARNING WARNING WARNING")
 248                 .shouldContain("Certificate chain length: 0");
 249 
 250         SecurityTools.setResponse("changeit");
 251         keytool("-list -keystore ksnormal")
 252                 .shouldNotContain("WARNING WARNING WARNING")
 253                 .shouldContain("Certificate fingerprint");
 254 
 255         // ksnopass is password-less
 256 
 257         keytool("-list -keystore ksnopass")
 258                 .shouldNotContain("WARNING WARNING WARNING")
 259                 .shouldContain("Certificate fingerprint");
 260 
 261         // -certreq prompts for keypass
 262         SecurityTools.setResponse("changeit");
 263         keytool("-certreq -alias a -keystore ksnopass")
 264                 .shouldContain("Enter key password for <a>")
 265                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
 266                 .shouldHaveExitValue(0);
 267 
 268         // -certreq -storepass works fine
 269         keytool("-certreq -alias a -keystore ksnopass -storepass changeit")
 270                 .shouldNotContain("Enter key password for <a>")
 271                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
 272                 .shouldHaveExitValue(0);
 273 
 274         // -certreq -keypass also works fine
 275         keytool("-certreq -alias a -keystore ksnopass -keypass changeit")
 276                 .shouldNotContain("Enter key password for <a>")
 277                 .shouldContain("-----BEGIN NEW CERTIFICATE REQUEST-----")
 278                 .shouldHaveExitValue(0);
 279 
 280         // -importkeystore prompts for srckeypass
 281         SecurityTools.setResponse("changeit", "changeit");
 282         keytool("-importkeystore -srckeystore ksnopass "
 283                 + "-destkeystore jks3 -deststoretype PKCS12 -deststorepass changeit")
 284                 .shouldContain("Enter key password for <a>")
 285                 .shouldContain("Enter key password for <b>")
 286                 .shouldContain("2 entries successfully imported");
 287 
 288         // ksnopass2 is ksnopass + 2 cert entries
 289         ks = KeyStore.getInstance("pkcs12");
 290         try (FileInputStream fis = new FileInputStream("ksnopass")) {
 291             ks.load(fis, (char[])null);
 292         }
 293         ks.setCertificateEntry("aa", ks.getCertificate("a"));
 294         ks.setCertificateEntry("bb", ks.getCertificate("b"));
 295         try (FileOutputStream fos = new FileOutputStream("ksnopass2")) {
 296             ks.store(fos, null);
 297         }
 298 
 299         // -importkeystore prompts for srckeypass for private keys
 300         // and no prompt for certs
 301         SecurityTools.setResponse("changeit", "changeit");
 302         keytool("-importkeystore -srckeystore ksnopass2 "
 303                 + "-destkeystore jks5 -deststorepass changeit")
 304                 .shouldContain("Enter key password for <a>")
 305                 .shouldContain("Enter key password for <b>")
 306                 .shouldNotContain("Enter key password for <aa>")
 307                 .shouldNotContain("Enter key password for <bb>")
 308                 .shouldContain("4 entries successfully imported");
 309 
 310         // ksonlycert has only cert entries
 311 
 312         ks.deleteEntry("a");
 313         ks.deleteEntry("b");
 314         try (FileOutputStream fos = new FileOutputStream("ksonlycert")) {
 315             ks.store(fos, null);
 316         }
 317 
 318         // -importkeystore does not prompt at all
 319         keytool("-importkeystore -srckeystore ksonlycert "
 320                 + "-destkeystore jks6 -deststorepass changeit")
 321                 .shouldNotContain("Enter key password for <aa>")
 322                 .shouldNotContain("Enter key password for <bb>")
 323                 .shouldContain("2 entries successfully imported");
 324 
 325         // create a new password-less keystore
 326         keytool("-keystore ksnopass -exportcert -alias a -file a.cert -rfc");
 327 
 328         // Normally storepass is prompted for
 329         keytool("-keystore kscert1 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt")
 330                 .shouldContain("Enter keystore password:");
 331         keytool("-keystore kscert2 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt "
 332                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE")
 333                 .shouldContain("Enter keystore password:");
 334         keytool("-keystore kscert3 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt "
 335                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
 336                 .shouldContain("Enter keystore password:");
 337         // ... but not if it's password-less
 338         keytool("-keystore kscert4 -storetype PKCS12 -importcert -alias a -file a.cert -noprompt "
 339                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
 340                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
 341                 .shouldNotContain("Enter keystore password:");
 342 
 343         // still prompt for keypass for genkeypair and certreq
 344         SecurityTools.setResponse("changeit", "changeit");
 345         keytool("-keystore ksnopassnew -storetype PKCS12 -genkeypair -alias a -dname CN=A "
 346                 + "-J-Dkeystore.pkcs12.certProtectionAlgorithm=NONE "
 347                 + "-J-Dkeystore.pkcs12.macAlgorithm=NONE")
 348                 .shouldNotContain("Enter keystore password:")
 349                 .shouldContain("Enter key password for <a>");
 350         keytool("-keystore ksnopassnew -certreq -alias a")
 351                 .shouldNotContain("Enter keystore password:")
 352                 .shouldContain("Enter key password for <a>");
 353         keytool("-keystore ksnopassnew -list -v -alias a")
 354                 .shouldNotContain("Enter keystore password:")
 355                 .shouldNotContain("Enter key password for <a>");
 356 
 357         // params only read on demand
 358 
 359         // keyPbeIterationCount is used by -genkeypair
 360         keytool("-keystore ksgenbadkeyic -storetype PKCS12 -genkeypair -alias a -dname CN=A "
 361                 + "-storepass changeit "
 362                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc")
 363                 .shouldContain("keyPbeIterationCount is not a number: abc")
 364                 .shouldHaveExitValue(1);
 365 
 366         keytool("-keystore ksnopassnew -exportcert -alias a -file a.cert");
 367 
 368         // but not used by -importcert
 369         keytool("-keystore ksimpbadkeyic -importcert -alias a -file a.cert "
 370                 + "-noprompt -storepass changeit "
 371                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc")
 372                 .shouldHaveExitValue(0);
 373 
 374         // None is used by -list
 375         keytool("-keystore ksnormal -storepass changeit -list "
 376                 + "-J-Dkeystore.pkcs12.keyPbeIterationCount=abc "
 377                 + "-J-Dkeystore.pkcs12.certPbeIterationCount=abc "
 378                 + "-J-Dkeystore.pkcs12.macIterationCount=abc")
 379                 .shouldHaveExitValue(0);
 380     }
 381 
 382     /**
 383      * Check keystore loading and key/cert reading.
 384      *
 385      * @param keystore the file name of keystore
 386      * @param alias the key/cert to read
 387      * @param storePass store pass to try out, can be null
 388      * @param keypass key pass to try, can not be null
 389      * @param expectedLoad expected result of keystore loading, true if non
 390      *                     null, false if null, exception class if exception
 391      * @param expectedCert expected result of cert reading
 392      * @param expectedKey expected result of key reading
 393      */
 394     private static void check(
 395             String keystore,
 396             String alias,
 397             String storePass,
 398             String keypass,
 399             Object expectedLoad,
 400             Object expectedCert,
 401             Object expectedKey) {
 402         KeyStore ks = null;
 403         Object actualLoad, actualCert, actualKey;
 404         String label = keystore + "-" + alias + "-" + storePass + "-" + keypass;
 405         try {
 406             ks = KeyStore.getInstance("pkcs12");
 407             try (FileInputStream fis = new FileInputStream(keystore)) {
 408                 ks.load(fis, storePass == null ? null : storePass.toCharArray());
 409             }
 410             actualLoad = ks != null;
 411         } catch (Exception e) {
 412             e.printStackTrace(System.out);
 413             actualLoad = e.getClass();
 414         }
 415         Asserts.assertEQ(expectedLoad, actualLoad, label + "-load");
 416 
 417         // If not loaded correctly, skip cert/key reading
 418         if (!Objects.equals(actualLoad, true)) {
 419             return;
 420         }
 421 
 422         try {
 423             actualCert = (ks.getCertificate(alias) != null);
 424         } catch (Exception e) {
 425             e.printStackTrace(System.out);
 426             actualCert = e.getClass();
 427         }
 428         Asserts.assertEQ(expectedCert, actualCert, label + "-cert");
 429 
 430         try {
 431             actualKey = (ks.getKey(alias, keypass.toCharArray()) != null);
 432         } catch (Exception e) {
 433             e.printStackTrace(System.out);
 434             actualKey = e.getClass();
 435         }
 436         Asserts.assertEQ(expectedKey, actualKey, label + "-key");
 437     }
 438 
 439     static OutputAnalyzer keytool(String s) throws Throwable {
 440         return SecurityTools.keytool(s);
 441     }
 442 }