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 }