diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 474fe6f401f..4c0637806d5 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -311,6 +311,42 @@ keystore.type=pkcs12 # fips.keystore.type=PKCS11 +# +# Location of the NSS DB keystore (PKCS11) in FIPS mode. +# +# The syntax for this property is identical to the 'nssSecmodDirectory' +# attribute available in the SunPKCS11 NSS configuration file. Use the +# 'sql:' prefix to refer to an SQLite DB. +# +# If the system property fips.nssdb.path is also specified, it supersedes +# the security property value defined here. +# +# Note: the default value for this property points to an NSS DB that might be +# readable by multiple operating system users and unsuitable to store keys. +# +fips.nssdb.path=sql:/etc/pki/nssdb + +# +# PIN for the NSS DB keystore (PKCS11) in FIPS mode. +# +# Values must take any of the following forms: +# 1) pin: +# Value: clear text PIN value. +# 2) env: +# Value: environment variable containing the PIN value. +# 3) file: +# Value: path to a file containing the PIN value in its first +# line. +# +# If the system property fips.nssdb.pin is also specified, it supersedes +# the security property value defined here. +# +# When used as a system property, UTF-8 encoded values are valid. When +# used as a security property (such as in this file), encode non-Basic +# Latin Unicode characters with \uXXXX. +# +fips.nssdb.pin=pin: + # # Controls compatibility mode for JKS and PKCS12 keystore types. # diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/FIPSTokenLoginHandler.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/FIPSTokenLoginHandler.java new file mode 100644 index 00000000000..6d910eaac6e --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/FIPSTokenLoginHandler.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2022, Red Hat, Inc. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.pkcs11; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.security.ProviderException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +import sun.security.util.Debug; +import sun.security.util.SecurityProperties; + +final class FIPSTokenLoginHandler implements CallbackHandler { + + private static final String FIPS_NSSDB_PIN_PROP = "fips.nssdb.pin"; + + private static final Debug debug = Debug.getInstance("sunpkcs11"); + + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + if (!(callbacks[0] instanceof PasswordCallback)) { + throw new UnsupportedCallbackException(callbacks[0]); + } + PasswordCallback pc = (PasswordCallback)callbacks[0]; + pc.setPassword(getFipsNssdbPin()); + } + + private static char[] getFipsNssdbPin() throws ProviderException { + if (debug != null) { + debug.println("FIPS: Reading NSS DB PIN for token..."); + } + String pinProp = SecurityProperties + .privilegedGetOverridable(FIPS_NSSDB_PIN_PROP); + if (pinProp != null && !pinProp.isEmpty()) { + String[] pinPropParts = pinProp.split(":", 2); + if (pinPropParts.length < 2) { + throw new ProviderException("Invalid " + FIPS_NSSDB_PIN_PROP + + " property value."); + } + String prefix = pinPropParts[0].toUpperCase(); + String value = pinPropParts[1]; + String pin = null; + if (prefix.equals("ENV")) { + if (debug != null) { + debug.println("FIPS: PIN value from the '" + value + + "' environment variable."); + } + pin = System.getenv(value); + } else if (prefix.equals("FILE")) { + if (debug != null) { + debug.println("FIPS: PIN value from the '" + value + + "' file."); + } + pin = getPinFromFile(Paths.get(value)); + } else if (prefix.equals("PIN")) { + if (debug != null) { + debug.println("FIPS: PIN value from the " + + FIPS_NSSDB_PIN_PROP + " property."); + } + pin = value; + } else { + throw new ProviderException("Unsupported prefix for " + + FIPS_NSSDB_PIN_PROP + "."); + } + if (pin != null && !pin.isEmpty()) { + if (debug != null) { + debug.println("FIPS: non-empty PIN."); + } + /* + * C_Login in libj2pkcs11 receives the PIN in a char[] and + * discards the upper byte of each char, before passing + * the value to the NSS Software Token. However, the + * NSS Software Token accepts any UTF-8 PIN value. Thus, + * expand the PIN here to account for later truncation. + */ + byte[] pinUtf8 = pin.getBytes(StandardCharsets.UTF_8); + char[] pinChar = new char[pinUtf8.length]; + for (int i = 0; i < pinChar.length; i++) { + pinChar[i] = (char)(pinUtf8[i] & 0xFF); + } + return pinChar; + } + } + if (debug != null) { + debug.println("FIPS: empty PIN."); + } + return new char[] {}; + } + + /* + * This method extracts the token PIN from the first line of a password + * file in the same way as NSS modutil. See for example the -newpwfile + * argument used to change the password for an NSS DB. + */ + private static String getPinFromFile(Path f) throws ProviderException { + try (InputStream is = + Files.newInputStream(f, StandardOpenOption.READ)) { + /* + * SECU_FilePasswd in NSS (nss/cmd/lib/secutil.c), used by modutil, + * reads up to 4096 bytes. In addition, the NSS Software Token + * does not accept PINs longer than 500 bytes (see SFTK_MAX_PIN + * in nss/lib/softoken/pkcs11i.h). + */ + BufferedReader in = + new BufferedReader(new InputStreamReader( + new ByteArrayInputStream(is.readNBytes(4096)), + StandardCharsets.UTF_8)); + return in.readLine(); + } catch (IOException ioe) { + throw new ProviderException("Error reading " + FIPS_NSSDB_PIN_PROP + + " from the '" + f + "' file.", ioe); + } + } +} \ No newline at end of file diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 977e5332bd1..1ac807fb512 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -51,6 +51,7 @@ import sun.security.util.Debug; import sun.security.util.ResourcesMgr; import static sun.security.util.SecurityConstants.PROVIDER_VER; +import sun.security.util.SecurityProperties; import sun.security.pkcs11.Secmod.*; @@ -88,6 +89,8 @@ public final class SunPKCS11 extends AuthProvider { fipsImportKey = fipsImportKeyTmp; } + private static final String FIPS_NSSDB_PATH_PROP = "fips.nssdb.path"; + private static final long serialVersionUID = -1354835039035306505L; static final Debug debug = Debug.getInstance("sunpkcs11"); @@ -140,6 +143,18 @@ public Provider configure(String configArg) throws InvalidParameterException { return AccessController.doPrivileged(new PrivilegedExceptionAction<>() { @Override public SunPKCS11 run() throws Exception { + if (systemFipsEnabled) { + /* + * The nssSecmodDirectory attribute in the SunPKCS11 + * NSS configuration file takes the value of the + * fips.nssdb.path System property after expansion. + * Security properties expansion is unsupported. + */ + System.setProperty( + FIPS_NSSDB_PATH_PROP, + SecurityProperties.privilegedGetOverridable( + FIPS_NSSDB_PATH_PROP)); + } return new SunPKCS11(new Config(newConfigName)); } }); @@ -409,24 +424,6 @@ private static T checkNull(T obj) { if (nssModule != null) { nssModule.setProvider(this); } - if (systemFipsEnabled) { - // The NSS Software Token in FIPS 140-2 mode requires a user - // login for most operations. See sftk_fipsCheck. The NSS DB - // (/etc/pki/nssdb) PIN is empty. - Session session = null; - try { - session = token.getOpSession(); - p11.C_Login(session.id(), CKU_USER, new char[] {}); - } catch (PKCS11Exception p11e) { - if (debug != null) { - debug.println("Error during token login: " + - p11e.getMessage()); - } - throw p11e; - } finally { - token.releaseSession(session); - } - } } catch (Exception e) { if (config.getHandleStartupErrors() == Config.ERR_IGNORE_ALL) { throw new UnsupportedOperationException @@ -1215,6 +1212,27 @@ public Object newInstance(Object param) if (token.isValid() == false) { throw new NoSuchAlgorithmException("Token has been removed"); } + if (systemFipsEnabled && !token.fipsLoggedIn && + !getType().equals("KeyStore")) { + /* + * The NSS Software Token in FIPS 140-2 mode requires a + * user login for most operations. See sftk_fipsCheck + * (nss/lib/softoken/fipstokn.c). In case of a KeyStore + * service, let the caller perform the login with + * KeyStore::load. Keytool, for example, does this to pass a + * PIN from either the -srcstorepass or -deststorepass + * argument. In case of a non-KeyStore service, perform the + * login now with the PIN available in the fips.nssdb.pin + * property. + */ + try { + token.ensureLoggedIn(null); + } catch (PKCS11Exception | LoginException e) { + throw new ProviderException("FIPS: error during the Token" + + " login required for the " + getType() + + " service.", e); + } + } try { return newInstance0(param); } catch (PKCS11Exception e) { @@ -1567,6 +1585,9 @@ public void logout() throws LoginException { try { session = token.getOpSession(); p11.C_Logout(session.id()); + if (systemFipsEnabled) { + token.fipsLoggedIn = false; + } if (debug != null) { debug.println("logout succeeded"); } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java index 1d0c0a7fc22..da21dcd8e35 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Token.java @@ -33,6 +33,7 @@ import java.security.*; import javax.security.auth.login.LoginException; +import jdk.internal.misc.SharedSecrets; import sun.security.jca.JCAUtil; import sun.security.pkcs11.wrapper.*; @@ -47,6 +48,9 @@ */ class Token implements Serializable { + private static final boolean systemFipsEnabled = SharedSecrets + .getJavaSecuritySystemConfiguratorAccess().isSystemFipsEnabled(); + // need to be serializable to allow SecureRandom to be serialized private static final long serialVersionUID = 2541527649100571747L; @@ -113,6 +117,10 @@ class Token implements Serializable { // flag indicating whether we are logged in private volatile boolean loggedIn; + // Flag indicating the login status for the NSS Software Token in FIPS mode. + // This Token is never asynchronously removed. Used from SunPKCS11. + volatile boolean fipsLoggedIn; + // time we last checked login status private long lastLoginCheck; @@ -231,7 +239,12 @@ boolean isLoggedInNow(Session session) throws PKCS11Exception { // call provider.login() if not void ensureLoggedIn(Session session) throws PKCS11Exception, LoginException { if (isLoggedIn(session) == false) { - provider.login(null, null); + if (systemFipsEnabled) { + provider.login(null, new FIPSTokenLoginHandler()); + fipsLoggedIn = true; + } else { + provider.login(null, null); + } } } diff --git a/test/jdk/sun/security/pkcs11/fips/NssdbPin.java b/test/jdk/sun/security/pkcs11/fips/NssdbPin.java new file mode 100644 index 00000000000..f806faaf2b2 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/fips/NssdbPin.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2022, Red Hat, Inc. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.List; +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import jdk.test.lib.util.FileUtils; + +/* + * @test + * @bug 9999999 + * @summary + * Test that the fips.nssdb.path and fips.nssdb.pin properties can be used + * for a successful login into an NSS DB. Some additional unitary testing + * is then performed. This test depends on NSS modutil and must be run in + * FIPS mode (the SunPKCS11-NSS-FIPS security provider has to be available). + * @modules jdk.crypto.cryptoki/sun.security.pkcs11:+open + * java.base/jdk.internal.misc + * @library /java/security/testlibrary + * /test/lib + * @requires (jdk.version.major >= 8) + * @run main/othervm/timeout=600 NssdbPin + * @author Martin Balao (mbalao@redhat.com) + */ + +public final class NssdbPin { + + // Public properties and names + private static final String FIPS_NSSDB_PATH_PROP = "fips.nssdb.path"; + private static final String FIPS_NSSDB_PIN_PROP = "fips.nssdb.pin"; + private static final String FIPS_PROVIDER_NAME = "SunPKCS11-NSS-FIPS"; + private static final String NSSDB_TOKEN_NAME = + "NSS FIPS 140-2 Certificate DB"; + + // Data to be tested + private static final String[] PINS_TO_TEST = + new String[] { + "", + "1234567890abcdef1234567890ABCDEF\uA4F7" + }; + private static enum PropType { SYSTEM, SECURITY } + private static enum LoginType { IMPLICIT, EXPLICIT } + + // Internal test fields + private static final boolean DEBUG = true; + private static class TestContext { + String pin; + PropType propType; + Path workspace; + String nssdbPath; + Path nssdbPinFile; + LoginType loginType; + TestContext(String pin, Path workspace) { + this.pin = pin; + this.workspace = workspace; + this.nssdbPath = "sql:" + workspace; + this.loginType = LoginType.IMPLICIT; + } + } + + public static void main(String[] args) throws Throwable { + if (args.length == 3) { + // Executed by a child process. + mainChild(args[0], args[1], LoginType.valueOf(args[2])); + } else if (args.length == 0) { + // Executed by the parent process. + mainLauncher(); + // Test defaults + mainChild("sql:/etc/pki/nssdb", "", LoginType.IMPLICIT); + System.out.println("TEST PASS - OK"); + } else { + throw new Exception("Unexpected number of arguments."); + } + } + + private static void mainChild(String expectedPath, String expectedPin, + LoginType loginType) throws Throwable { + if (DEBUG) { + for (String prop : Arrays.asList(FIPS_NSSDB_PATH_PROP, + FIPS_NSSDB_PIN_PROP)) { + System.out.println(prop + " (System): " + + System.getProperty(prop)); + System.out.println(prop + " (Security): " + + Security.getProperty(prop)); + } + } + + /* + * Functional cross-test against an NSS DB generated by modutil + * with the same PIN. Check that we can perform a crypto operation + * that requires a login. The login might be explicit or implicit. + */ + Provider p = Security.getProvider(FIPS_PROVIDER_NAME); + if (DEBUG) { + System.out.println(FIPS_PROVIDER_NAME + ": " + p); + } + if (p == null) { + throw new Exception(FIPS_PROVIDER_NAME + " initialization failed."); + } + if (DEBUG) { + System.out.println("Login type: " + loginType); + } + if (loginType == LoginType.EXPLICIT) { + // Do the expansion to account for truncation, so C_Login in + // the NSS Software Token gets a UTF-8 encoded PIN. + byte[] pinUtf8 = expectedPin.getBytes(StandardCharsets.UTF_8); + char[] pinChar = new char[pinUtf8.length]; + for (int i = 0; i < pinChar.length; i++) { + pinChar[i] = (char)(pinUtf8[i] & 0xFF); + } + KeyStore.getInstance("PKCS11", p).load(null, pinChar); + if (DEBUG) { + System.out.println("Explicit login succeeded."); + } + } + if (DEBUG) { + System.out.println("Trying a crypto operation..."); + } + final int blockSize = 16; + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", p); + cipher.init(Cipher.ENCRYPT_MODE, + new SecretKeySpec(new byte[blockSize], "AES")); + if (cipher.doFinal(new byte[blockSize]).length != blockSize) { + throw new Exception("Could not perform a crypto operation."); + } + if (DEBUG) { + if (loginType == LoginType.IMPLICIT) { + System.out.println("Implicit login succeeded."); + } + System.out.println("Crypto operation after login succeeded."); + } + + if (loginType == LoginType.IMPLICIT) { + /* + * Additional unitary testing. Expected to succeed at this point. + */ + if (DEBUG) { + System.out.println("Trying unitary test..."); + } + String sysPathProp = System.getProperty(FIPS_NSSDB_PATH_PROP); + if (DEBUG) { + System.out.println("Path value (as a System property): " + + sysPathProp); + } + if (!expectedPath.equals(sysPathProp)) { + throw new Exception("Path is different than expected: " + + sysPathProp + " (actual) vs " + expectedPath + + " (expected)."); + } + Class c = Class + .forName("sun.security.pkcs11.FIPSTokenLoginHandler"); + Method m = c.getDeclaredMethod("getFipsNssdbPin"); + m.setAccessible(true); + char[] pinChar = (char[]) m.invoke(c); + byte[] pinUtf8 = new byte[pinChar.length]; + for (int i = 0; i < pinUtf8.length; i++) { + pinUtf8[i] = (byte) pinChar[i]; + } + String pin = new String(pinUtf8, StandardCharsets.UTF_8); + if (!pin.equals(expectedPin)) { + throw new Exception("PIN is different than expected: " + pin + + " (actual) vs " + expectedPin + " (expected)."); + } + if (DEBUG) { + System.out.println("PIN value: " + pin); + System.out.println("Unitary test succeeded."); + } + } + } + + private static void mainLauncher() throws Throwable { + for (String pin : PINS_TO_TEST) { + Path workspace = Files.createTempDirectory(null); + try { + TestContext ctx = new TestContext(pin, workspace); + createNSSDB(ctx); + { + ctx.loginType = LoginType.IMPLICIT; + for (PropType propType : PropType.values()) { + ctx.propType = propType; + pinLauncher(ctx); + envLauncher(ctx); + fileLauncher(ctx); + } + } + explicitLoginLauncher(ctx); + } finally { + FileUtils.deleteFileTreeWithRetry(workspace); + } + } + } + + private static void pinLauncher(TestContext ctx) throws Throwable { + launchTest(p -> {}, "pin:" + ctx.pin, ctx); + } + + private static void envLauncher(TestContext ctx) throws Throwable { + final String NSSDB_PIN_ENV_VAR = "NSSDB_PIN_ENV_VAR"; + launchTest(p -> p.env(NSSDB_PIN_ENV_VAR, ctx.pin), + "env:" + NSSDB_PIN_ENV_VAR, ctx); + } + + private static void fileLauncher(TestContext ctx) throws Throwable { + // The file containing the PIN (ctx.nssdbPinFile) was created by the + // generatePinFile method, called from createNSSDB. + launchTest(p -> {}, "file:" + ctx.nssdbPinFile, ctx); + } + + private static void explicitLoginLauncher(TestContext ctx) + throws Throwable { + ctx.loginType = LoginType.EXPLICIT; + ctx.propType = PropType.SYSTEM; + launchTest(p -> {}, "Invalid PIN, must be ignored", ctx); + } + + private static void launchTest(Consumer procCb, String pinPropVal, + TestContext ctx) throws Throwable { + if (DEBUG) { + System.out.println("Launching JVM with " + FIPS_NSSDB_PATH_PROP + + "=" + ctx.nssdbPath + " and " + FIPS_NSSDB_PIN_PROP + + "=" + pinPropVal); + } + Proc p = Proc.create(NssdbPin.class.getName()) + .args(ctx.nssdbPath, ctx.pin, ctx.loginType.name()); + if (ctx.propType == PropType.SYSTEM) { + p.prop(FIPS_NSSDB_PATH_PROP, ctx.nssdbPath); + p.prop(FIPS_NSSDB_PIN_PROP, pinPropVal); + // Make sure that Security properties defaults are not used. + p.secprop(FIPS_NSSDB_PATH_PROP, ""); + p.secprop(FIPS_NSSDB_PIN_PROP, ""); + } else if (ctx.propType == PropType.SECURITY) { + p.secprop(FIPS_NSSDB_PATH_PROP, ctx.nssdbPath); + pinPropVal = escapeForPropsFile(pinPropVal); + p.secprop(FIPS_NSSDB_PIN_PROP, pinPropVal); + } else { + throw new Exception("Unsupported property type."); + } + if (DEBUG) { + p.inheritIO(); + p.prop("java.security.debug", "sunpkcs11"); + p.debug(NssdbPin.class.getName()); + + // Need the launched process to connect to a debugger? + //System.setProperty("test.vm.opts", "-Xdebug -Xrunjdwp:" + + // "transport=dt_socket,address=localhost:8000,suspend=y"); + } else { + p.nodump(); + } + procCb.accept(p); + p.start().waitFor(0); + } + + private static String escapeForPropsFile(String str) throws Throwable { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < str.length(); i++) { + int cp = str.codePointAt(i); + if (Character.UnicodeBlock.of(cp) + == Character.UnicodeBlock.BASIC_LATIN) { + sb.append(Character.toChars(cp)); + } else { + sb.append("\\u").append(String.format("%04X", cp)); + } + } + return sb.toString(); + } + + private static void createNSSDB(TestContext ctx) throws Throwable { + ProcessBuilder pb = getModutilPB(ctx, "-create"); + if (DEBUG) { + System.out.println("Creating an NSS DB in " + ctx.workspace + + "..."); + System.out.println("cmd: " + String.join(" ", pb.command())); + } + if (pb.start().waitFor() != 0) { + throw new Exception("NSS DB creation failed."); + } + generatePinFile(ctx); + pb = getModutilPB(ctx, "-changepw", NSSDB_TOKEN_NAME, + "-newpwfile", ctx.nssdbPinFile.toString()); + if (DEBUG) { + System.out.println("NSS DB created."); + System.out.println("Changing NSS DB PIN..."); + System.out.println("cmd: " + String.join(" ", pb.command())); + } + if (pb.start().waitFor() != 0) { + throw new Exception("NSS DB PIN change failed."); + } + if (DEBUG) { + System.out.println("NSS DB PIN changed."); + } + } + + private static ProcessBuilder getModutilPB(TestContext ctx, String... args) + throws Throwable { + ProcessBuilder pb = new ProcessBuilder("modutil", "-force"); + List pbCommand = pb.command(); + if (args != null) { + pbCommand.addAll(Arrays.asList(args)); + } + pbCommand.add("-dbdir"); + pbCommand.add(ctx.nssdbPath); + if (DEBUG) { + pb.inheritIO(); + } else { + pb.redirectError(ProcessBuilder.Redirect.INHERIT); + } + return pb; + } + + private static void generatePinFile(TestContext ctx) throws Throwable { + ctx.nssdbPinFile = Files.createTempFile(ctx.workspace, null, null); + Files.writeString(ctx.nssdbPinFile, ctx.pin + System.lineSeparator() + + "2nd line with garbage"); + } +}