package org.ovirt.engine.core.utils.hostinstall;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.utils.EngineLocalConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OpenSslCAWrapper {
private static final Logger log = LoggerFactory.getLogger(OpenSslCAWrapper.class);
private static String escapeSubjectComponent(String input) {
StringBuilder ret = new StringBuilder();
for (char x : input.toCharArray()) {
if (x == '/' || x == '\\') {
ret.append('\\');
}
ret.append(x);
}
return ret.toString();
}
public static String signCertificateRequest(
String request,
String name,
String hostname
) throws IOException {
EngineLocalConfig config = EngineLocalConfig.getInstance();
try (
final OutputStream os = new FileOutputStream(
new File(
new File(config.getPKIDir(), "requests"),
String.format("%s.req", name)
)
)
) {
os.write(request.getBytes(StandardCharsets.UTF_8));
}
if (
!new OpenSslCAWrapper().signCertificateRequest(
new File(new File(config.getUsrDir(), "bin"), "pki-enroll-request.sh"),
name,
hostname
)
) {
throw new RuntimeException("Certificate enrollment failed");
}
return new String(Files.readAllBytes(
Paths.get(config.getPKIDir().getPath(), "certs",
String.format("%s.cer", name))));
}
public static String signOpenSSHCertificate(
String name,
String hostname,
String principal
) throws IOException {
EngineLocalConfig config = EngineLocalConfig.getInstance();
if (
!new OpenSslCAWrapper().signOpenSSHCertificate(
new File(new File(config.getUsrDir(), "bin"), "pki-enroll-openssh-cert.sh"),
name,
hostname,
principal
)
) {
throw new RuntimeException("OpenSSH certificate enrollment failed");
}
return new String(Files.readAllBytes(
Paths.get(config.getPKIDir().getPath(), "certs",
String.format("%s-cert.pub", name))));
}
public final boolean signCertificateRequest(
File executable,
String name,
String hostname
) {
log.debug("Entered signCertificateRequest");
boolean returnValue;
if (executable.exists()) {
String organization = Config.getValue(ConfigValues.OrganizationName);
int days = Config.<Integer> getValue(ConfigValues.VdsCertificateValidityInYears) * 365;
Integer signatureTimeout = Config.<Integer> getValue(ConfigValues.SignCertTimeoutInSeconds);
returnValue = runCommandArray(
new String[] {
executable.getAbsolutePath(),
String.format("--name=%s", name),
String.format("--subject=/O=%s/CN=%s", escapeSubjectComponent(organization), escapeSubjectComponent(hostname)),
String.format("--days=%s", days),
String.format("--timeout=%s", signatureTimeout / 2)
},
signatureTimeout
);
} else {
log.error("Sign certificate request file '{}' not found", executable.getPath());
returnValue = false;
}
log.debug("End of signCertificateRequest");
return returnValue;
}
public final boolean signOpenSSHCertificate(
File executable,
String name,
String hostname,
String principal
) {
log.debug("Entered signOpenSSHCertificate");
boolean returnValue;
if (executable.exists()) {
int days = Config.<Integer> getValue(ConfigValues.VdsCertificateValidityInYears) * 365;
returnValue = runCommandArray(
new String[] {
executable.getAbsolutePath(),
String.format("--name=%s", name),
"--host",
String.format("--id=%s", hostname),
String.format("--principals=%s", principal),
String.format("--days=%s", days)
},
Config.<Integer> getValue(ConfigValues.SignCertTimeoutInSeconds)
);
} else {
log.error("Sign certificate request file '{}' not found", executable.getPath());
returnValue = false;
}
log.debug("End of signOpenSSHCertificate");
return returnValue;
}
/**
* Runs the SignReq.sh script
* @return whether or not the certificate signing was successful
*/
private boolean runCommandArray(String[] command_array, int signatureTimeout) {
boolean returnValue = true;
String outputString = null;
String errorString = null;
try {
log.debug("Running Sign Certificate request script");
Process process = getRuntime().exec(command_array);
try (
final BufferedReader stdOutput = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));
final BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream(), StandardCharsets.UTF_8))
) {
int exitCode = 0;
boolean completed = false;
for (int x = 0; x < signatureTimeout; x++) {
try {
Thread.sleep(1000);
exitCode = process.exitValue();
completed = true;
break;
} catch (IllegalThreadStateException | InterruptedException e) {
// keep going
}
}
outputString = readAllLines(stdOutput);
errorString = readAllLines(stdError);
if (!completed) {
process.destroy();
returnValue = false;
log.error("Sign Certificate request script killed due to timeout");
logOutputAndErrors(outputString, errorString);
} else if (exitCode != 0) {
returnValue = false;
log.error("Sign Certificate request failed with exit code {}", exitCode);
logOutputAndErrors(outputString, errorString);
} else {
log.debug("Successfully completed certificate signing script");
}
}
} catch (IOException e) {
log.error("Exception signing the certificate", e);
logOutputAndErrors(outputString, errorString);
returnValue = false;
}
return returnValue;
}
/**
* Returns a formatted String from the content of the given bufferedReader
*/
private String readAllLines(BufferedReader bufferedReader) {
String tempString;
StringBuilder returnString = new StringBuilder();
try {
while ((tempString = bufferedReader.readLine()) != null) {
returnString.append(tempString).append('\n');
}
} catch (IOException e) {
log.error("IOException while trying to read from buffer", e);
}
return returnString.toString();
}
/**
* Logs the given output to DEBUG, and the given errors to ERROR
* @param output
* The formatted output from the script
* @param errors
* The formatted errors from the script
*/
private void logOutputAndErrors(String output, String errors) {
if (errors != null && errors.length() != 0) {
log.error("Sign Certificate request script errors:\n{}", errors);
}
if (output != null && output.length() != 0) {
log.debug("Sign Certificate request script output:\n{}", output);
}
}
/**
* This method is meant to enable testing, since Runtime cannot be mocked statically
*/
protected Runtime getRuntime() {
return Runtime.getRuntime();
}
}