blob: 30da87f2d32fb5f528a3c3d1f21ae2db76e09340 [file] [log] [blame]
/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* 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.
*/
package jdk.test.lib;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.process.ProcessTools;
/**
* Run security tools (including jarsigner and keytool) in a new process.
* The en_US locale is always used so a test can always match output to
* English text. {@code /dev/urandom} is used as entropy source so tool will
* not block because of entropy scarcity. {@code -Jvm-options} is supported
* as an argument.
*/
public class SecurityTools {
/**
* The response file name for keytool. Use {@link #setResponse} to
* create one. Do NOT manipulate it directly.
*/
public static final String RESPONSE_FILE = "security_tools_response.txt";
private SecurityTools() {}
public static ProcessBuilder getProcessBuilder(String tool, List<String> args) {
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK(tool)
.addVMArg("-Duser.language=en")
.addVMArg("-Duser.country=US");
if (!Platform.isWindows()) {
launcher.addVMArg("-Djava.security.egd=file:/dev/./urandom");
}
for (String arg : args) {
if (arg.startsWith("-J")) {
launcher.addVMArg(arg.substring(2));
} else if (Platform.isWindows() && arg.isEmpty()) {
// JDK-6518827: special handling for empty argument on Windows
launcher.addToolArg("\"\"");
} else {
launcher.addToolArg(arg);
}
}
return new ProcessBuilder(launcher.getCommand());
}
/**
* Runs keytool.
*
* @param args arguments to keytool
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer keytool(List<String> args)
throws Exception {
ProcessBuilder pb = getProcessBuilder("keytool", args);
Path p = Paths.get(RESPONSE_FILE);
if (!Files.exists(p)) {
Files.createFile(p);
}
pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE)));
try {
return execute(pb);
} finally {
Files.delete(p);
}
}
/**
* Runs keytool.
*
* @param args arguments to keytool in a single string. The string is
* converted to be List with makeList.
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer keytool(String args) throws Exception {
return keytool(makeList(args));
}
/**
* Runs keytool.
*
* @param args arguments to keytool
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer keytool(String... args) throws Exception {
return keytool(List.of(args));
}
/**
* Sets the responses (user input) for keytool.
* <p>
* For example, if keytool requires entering a password twice, call
* {@code setResponse("password", "password")}. Do NOT append a newline
* character to each response. If there are useless responses at the end,
* they will be discarded silently. If there are less responses than
* necessary, keytool will read EOF. The responses will be written into
* {@linkplain #RESPONSE_FILE a local file} and will only be used by the
* next keytool run. After the run, the file is removed. Calling this
* method will always overwrite the previous response file (if exists).
*
* @param responses response to keytool
* @throws IOException if there is an error
*/
public static void setResponse(String... responses) throws IOException {
String text;
if (responses.length > 0) {
text = Stream.of(responses).collect(
Collectors.joining("\n", "", "\n"));
} else {
text = "";
}
Files.write(Paths.get(RESPONSE_FILE), text.getBytes());
}
/**
* Runs jarsigner.
*
* @param args arguments to jarsigner
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer jarsigner(List<String> args)
throws Exception {
return execute(getProcessBuilder("jarsigner", args));
}
private static OutputAnalyzer execute(ProcessBuilder pb) throws Exception {
try {
OutputAnalyzer oa = ProcessTools.executeCommand(pb);
System.out.println("Exit value: " + oa.getExitValue());
return oa;
} catch (Throwable t) {
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new Exception(t);
}
}
}
/**
* Runs jarsigner.
*
* @param args arguments to jarsigner in a single string. The string is
* converted to be List with makeList.
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer jarsigner(String args) throws Exception {
return jarsigner(makeList(args));
}
/**
* Runs jarsigner.
*
* @param args arguments to jarsigner
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer jarsigner(String... args) throws Exception {
return jarsigner(List.of(args));
}
/**
* Runs ktab.
*
* @param args arguments to ktab in a single string. The string is
* converted to be List with makeList.
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer ktab(String args) throws Exception {
return execute(getProcessBuilder("ktab", makeList(args)));
}
/**
* Runs klist.
*
* @param args arguments to klist in a single string. The string is
* converted to be List with makeList.
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer klist(String args) throws Exception {
return execute(getProcessBuilder("klist", makeList(args)));
}
/**
* Runs jar.
*
* @param args arguments to jar in a single string. The string is
* converted to be List with makeList.
* @return an {@link OutputAnalyzer} object
* @throws Exception if there is an error
*/
public static OutputAnalyzer jar(String args) throws Exception {
return execute(getProcessBuilder("jar", makeList(args)));
}
/**
* Split a line to a list of string. All whitespaces are treated as
* delimiters unless quoted between ` and `.
*
* @param line the input
* @return the list
*/
public static List<String> makeList(String line) {
List<String> result = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean inBackTick = false;
for (char c : line.toCharArray()) {
if (inBackTick) {
if (c == '`') {
result.add(sb.toString());
sb.setLength(0);
inBackTick = false;
} else {
sb.append(c);
}
} else {
if (sb.length() == 0 && c == '`') {
// Allow ` inside a string
inBackTick = true;
} else {
if (Character.isWhitespace(c)) {
if (sb.length() != 0) {
result.add(sb.toString());
sb.setLength(0);
}
} else {
sb.append(c);
}
}
}
}
if (sb.length() != 0) {
result.add(sb.toString());
}
return result;
}
}