package org.ovirt.engine.core.cryptotool;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.ovirt.engine.core.uutils.cli.parser.ArgumentsParser;
import org.ovirt.engine.core.uutils.crypto.EnvelopeEncryptDecrypt;
import org.ovirt.engine.core.uutils.crypto.EnvelopePBE;
public class Main {
private static final Map<String, String> substitutions = new HashMap<>();
private static class ExitException extends RuntimeException {
private int exitCode;
public ExitException() {
this(0);
}
public ExitException(int exitCode) {
this(null, exitCode);
}
public ExitException(String msg, int exitCode) {
super(msg);
this.exitCode = exitCode;
}
public int getExitCode() {
return exitCode;
}
}
@FunctionalInterface
private interface Logic {
void execute(Map<String, Object> argMap) throws IOException, GeneralSecurityException;
}
private enum Action {
PBE_ENCODE(
argMap -> System.out.println(
EnvelopePBE.encode(
(String)argMap.get("algorithm"),
(Integer)argMap.get("key-size"),
(Integer)argMap.get("iterations"),
null,
getPassword("Password: ", (String)argMap.get("password"))
)
)
),
PBE_CHECK(
argMap -> {
if (
!EnvelopePBE.check(
new String(readStream(System.in), StandardCharsets.UTF_8),
getPassword("Password: ", (String)argMap.get("password"))
)
) {
System.err.println("FAILED");
throw new ExitException("Check failed", 1);
}
}
),
ENC_ENCODE(
argMap -> {
try(InputStream in = new FileInputStream((String)argMap.get("certificate"))) {
System.out.println(
EnvelopeEncryptDecrypt.encrypt(
(String)argMap.get("algorithm"),
(Integer)argMap.get("key-size"),
CertificateFactory.getInstance("X.509").generateCertificate(in),
(Integer)argMap.get("block-size"),
readStream(System.in)
)
);
}
}
),
ENC_DECODE(
argMap -> {
String keystorePassword = getPassword("Key store password: ", (String)argMap.get("keystore-password"));
System.out.write(
EnvelopeEncryptDecrypt.decrypt(
getPrivateKeyEntry(
getKeyStore(
(String)argMap.get("keystore-type"),
(String)argMap.get("keystore"),
keystorePassword
),
(String)argMap.get("keystore-alias"),
argMap.get("key-password") != null ?
getPassword("Key password: ", (String)argMap.get("key-password")) :
keystorePassword
),
new String(readStream(System.in), StandardCharsets.UTF_8)
)
);
}
);
private Logic logic;
private Action(Logic logic) {
this.logic = logic;
}
void execute(Properties props, List<String> cmdArgs) throws IOException, GeneralSecurityException {
ArgumentsParser parser = new ArgumentsParser(props, cmdArgs.remove(0));
parser.getSubstitutions().putAll(substitutions);
parser.parse(cmdArgs);
Map<String, Object> argMap = parser.getParsedArgs();
if((Boolean)argMap.get("help")) {
System.out.format("Usage: %s", parser.getUsage());
throw new ExitException("Help", 0);
}
if(!parser.getErrors().isEmpty()) {
for(Throwable t : parser.getErrors()) {
System.err.format("FATAL: %s%n", t.getMessage());
}
throw new ExitException("Parsing error", 1);
}
logic.execute(argMap);
}
};
private static String PROGRAM_NAME = System.getProperty("org.ovirt.engine.cryptotool.core.programName");
private static String PACKAGE_NAME = System.getProperty("org.ovirt.engine.cryptotool.core.packageName");
private static String PACKAGE_VERSION = System.getProperty("org.ovirt.engine.cryptotool.core.packageVersion");
private static String PACKAGE_DISPLAY_NAME = System.getProperty("org.ovirt.engine.cryptotool.core.packageDisplayName");
private static KeyStore getKeyStore(String storeType, String store, String password) throws IOException, GeneralSecurityException {
KeyStore ks = KeyStore.getInstance(storeType);
try (InputStream is = new FileInputStream(store)) {
ks.load(is, password.toCharArray());
}
return ks;
}
private static KeyStore.PrivateKeyEntry getPrivateKeyEntry(KeyStore ks, String alias, String password) throws IOException, GeneralSecurityException {
KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)ks.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));
if (entry == null) {
throw new IllegalArgumentException(String.format("Keystore alias '%s' is missing", alias));
}
return entry;
}
private static byte[] readStream(InputStream in) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) != -1) {
os.write(buf, 0, n);
}
return os.toByteArray();
}
private static String getPassword(String prompt, String what) throws IOException {
String[] keyValue = what.split(":", 2);
String type = keyValue[0];
String value = keyValue[1];
String password = null;
if ("pass".equals(type)) {
password = value;
} else if ("file".equals(type)) {
try(
InputStream is = new FileInputStream(value);
Reader reader = new InputStreamReader(is);
BufferedReader breader = new BufferedReader(reader);
) {
password = breader.readLine();
}
} else if ("env".equals(type)) {
password = System.getenv(value);
} else if ("interactive".equals(type)) {
if (System.console() == null) {
throw new RuntimeException("Console is not available, interactive password prompt is impossible");
}
System.console().printf("%s", prompt);
char[] passwordChars = System.console().readPassword();
if (passwordChars == null) {
throw new RuntimeException("Cannot read password");
}
password = new String(passwordChars);
} else {
throw new IllegalArgumentException(String.format("Invalid type: '%s'", type));
}
if (password == null) {
throw new IllegalArgumentException("No password");
}
return password;
}
public static void main(String... args) throws IOException {
int exitStatus = 1;
List<String> cmdArgs = new ArrayList<>(Arrays.asList(args));
substitutions.put("@PROGRAM_NAME@", PROGRAM_NAME);
try {
Properties props = new Properties();
try (
InputStream in = Main.class.getResourceAsStream("arguments.properties");
Reader reader = new InputStreamReader(in);
) {
props.load(reader);
}
ArgumentsParser parser = new ArgumentsParser(props, "core");
parser.getSubstitutions().putAll(substitutions);
parser.parse(cmdArgs);
Map<String, Object> argMap = parser.getParsedArgs();
if((Boolean)argMap.get("help")) {
System.out.format("Usage: %s", parser.getUsage());
throw new ExitException("Help", 0);
} else if((Boolean)argMap.get("version")) {
System.out.format("%s-%s (%s)%n", PACKAGE_NAME, PACKAGE_VERSION, PACKAGE_DISPLAY_NAME);
throw new ExitException("Version", 0);
}
if(!parser.getErrors().isEmpty()) {
for(Throwable t : parser.getErrors()) {
System.err.format("FATAL: %s%n", t.getMessage());
}
throw new ExitException("Parsing error", 1);
}
if (cmdArgs.size() < 1) {
System.err.println("Action not provided");
throw new ExitException("Action not provided", 1);
}
Action action;
try {
action = Action.valueOf(cmdArgs.get(0).toUpperCase().replace("-", "_"));
} catch(IllegalArgumentException e) {
System.err.printf("Invalid action '%s'%n", cmdArgs.get(0));
throw new ExitException("Invalid action", 1);
}
action.execute(props, cmdArgs);
exitStatus = 0;
} catch (ExitException e) {
exitStatus = e.getExitCode();
} catch (Exception e) {
System.err.format("FATAL: %s%n", e.getMessage());
e.printStackTrace(System.err);
}
System.exit(exitStatus);
}
}