com.netscape.cmstools.cli.MainCLI.java Source code

Java tutorial

Introduction

Here is the source code for com.netscape.cmstools.cli.MainCLI.java

Source

// --- BEGIN COPYRIGHT BLOCK ---
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; version 2 of the License.
//
// This program 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 for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// (C) 2012 Red Hat, Inc.
// All rights reserved.
// --- END COPYRIGHT BLOCK ---

package com.netscape.cmstools.cli;

import java.io.BufferedReader;
import java.io.Console;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.ws.rs.ProcessingException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.UnrecognizedOptionException;
import org.apache.commons.lang.StringUtils;
import org.dogtagpki.common.Info;
import org.dogtagpki.common.InfoClient;
import org.mozilla.jss.CryptoManager;
import org.mozilla.jss.NotInitializedException;
import org.mozilla.jss.crypto.CryptoToken;
import org.mozilla.jss.ssl.SSLCertificateApprovalCallback;
import org.mozilla.jss.ssl.SSLVersion;
import org.mozilla.jss.ssl.SSLVersionRange;
import org.mozilla.jss.util.IncorrectPasswordException;
import org.mozilla.jss.util.Password;

import com.netscape.certsrv.ca.CAClient;
import com.netscape.certsrv.client.ClientConfig;
import com.netscape.certsrv.client.PKIClient;
import com.netscape.certsrv.client.PKIConnection;
import com.netscape.cmstools.ca.CACLI;
import com.netscape.cmstools.cert.ProxyCertCLI;
import com.netscape.cmstools.client.ClientCLI;
import com.netscape.cmstools.group.ProxyGroupCLI;
import com.netscape.cmstools.key.ProxyKeyCLI;
import com.netscape.cmstools.kra.KRACLI;
import com.netscape.cmstools.ocsp.OCSPCLI;
import com.netscape.cmstools.pkcs11.PKCS11CLI;
import com.netscape.cmstools.pkcs12.PKCS12CLI;
import com.netscape.cmstools.pkcs7.PKCS7CLI;
import com.netscape.cmstools.system.SecurityDomainCLI;
import com.netscape.cmstools.tks.TKSCLI;
import com.netscape.cmstools.tps.TPSCLI;
import com.netscape.cmstools.user.ProxyUserCLI;
import com.netscape.cmsutil.crypto.CryptoUtil;

/**
 * @author Endi S. Dewata
 */
public class MainCLI extends CLI {

    /**
     * These commands should not be executed after CryptoManager.initialize()
     * since they may modify the NSS database or execute external commands
     * using the same NSS database.
     */
    public final static Collection<String> RESTRICTED_COMMANDS = Arrays.asList("client-init", "client-cert-import",
            "client-cert-mod", "client-cert-request", "client-cert-show");

    public ClientConfig config = new ClientConfig();

    public Collection<Integer> rejectedCertStatuses = new HashSet<Integer>();
    public Collection<Integer> ignoredCertStatuses = new HashSet<Integer>();

    public boolean ignoreBanner;
    public File certDatabase;

    String output;

    public MainCLI() throws Exception {
        super("pki", "PKI command-line interface");

        addModule(new HelpCLI(this));

        addModule(new ClientCLI(this));

        addModule(new ProxyCertCLI(this));
        addModule(new ProxyGroupCLI(this));
        addModule(new ProxyKeyCLI(this));
        addModule(new ProxyCLI(new SecurityDomainCLI(this), "ca"));
        addModule(new ProxyUserCLI(this));

        addModule(new CACLI(this));
        addModule(new KRACLI(this));
        addModule(new OCSPCLI(this));
        addModule(new TKSCLI(this));
        addModule(new TPSCLI(this));

        addModule(new PKCS7CLI(this));
        addModule(new PKCS11CLI(this));
        addModule(new PKCS12CLI(this));

        createOptions();
    }

    public String getFullModuleName(String moduleName) {
        return moduleName;
    }

    @Override
    public String getManPage() {
        return "pki";
    }

    public void printVersion() {
        Package pkg = MainCLI.class.getPackage();
        System.out.println("PKI Command-Line Interface " + pkg.getImplementationVersion());
    }

    public void printHelp() {

        formatter.printHelp(name + " [OPTIONS..] <command> [ARGS..]", options);
        System.out.println();

        super.printHelp();
    }

    public void createOptions() throws UnknownHostException {

        Option option = new Option("U", true, "Server URL");
        option.setArgName("uri");
        options.addOption(option);

        option = new Option("P", true, "Protocol (default: http)");
        option.setArgName("protocol");
        options.addOption(option);

        option = new Option("h", true,
                "Hostname (default: " + InetAddress.getLocalHost().getCanonicalHostName() + ")");
        option.setArgName("hostname");
        options.addOption(option);

        option = new Option("p", true, "Port (default: 8080)");
        option.setArgName("port");
        options.addOption(option);

        option = new Option("t", true, "Subsystem type (deprecated)");
        option.setArgName("type");
        options.addOption(option);

        option = new Option("d", true, "NSS database location (default: ~/.dogtag/nssdb)");
        option.setArgName("database");
        options.addOption(option);

        option = new Option("c", true, "NSS database password (mutually exclusive to -C and -f options)");
        option.setArgName("password");
        options.addOption(option);

        option = new Option("C", true, "NSS database password file (mutually exclusive to -c and -f options)");
        option.setArgName("password file");
        options.addOption(option);

        option = new Option("f", true,
                "NSS database password configuration (mutually exclusive to -c and -C options)");
        option.setArgName("password config");
        options.addOption(option);

        option = new Option("n", true,
                "Nickname for client certificate authentication (mutually exclusive to -u option)");
        option.setArgName("nickname");
        options.addOption(option);

        option = new Option("u", true, "Username for basic authentication (mutually exclusive to -n option)");
        option.setArgName("username");
        options.addOption(option);

        option = new Option("w", true, "Password for basic authentication (mutually exclusive to -W option)");
        option.setArgName("password");
        options.addOption(option);

        option = new Option("W", true, "Password file for basic authentication (mutually exclusive to -w option)");
        option.setArgName("passwordfile");
        options.addOption(option);

        option = new Option(null, "token", true, "Security token name");
        option.setArgName("token");
        options.addOption(option);

        option = new Option(null, "output", true, "Folder to store HTTP messages");
        option.setArgName("folder");
        options.addOption(option);

        option = new Option(null, "reject-cert-status", true,
                "Comma-separated list of rejected certificate validity statuses");
        option.setArgName("list");
        options.addOption(option);

        option = new Option(null, "ignore-cert-status", true,
                "Comma-separated list of ignored certificate validity statuses");
        option.setArgName("list");
        options.addOption(option);

        option = new Option(null, "ignore-banner", false, "Ignore access banner");
        options.addOption(option);

        option = new Option(null, "message-format", true, "Message format: xml (default), json");
        option.setArgName("format");
        options.addOption(option);

        options.addOption("v", "verbose", false, "Run in verbose mode.");
        options.addOption(null, "help", false, "Show help message.");
        options.addOption(null, "version", false, "Show version number.");
    }

    public String loadPassword(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }

    public Map<String, String> loadPasswordConfig(String filename) throws Exception {

        Map<String, String> passwords = new LinkedHashMap<String, String>();

        List<String> list = Files.readAllLines(Paths.get(filename));
        String[] lines = list.toArray(new String[list.size()]);

        for (int i = 0; i < lines.length; i++) {

            String line = lines[i].trim();

            if (line.isEmpty()) { // skip blanks
                continue;
            }

            if (line.startsWith("#")) { // skip comments
                continue;
            }

            int p = line.indexOf("=");
            if (p < 0) {
                throw new Exception("Missing delimiter in " + filename + ":" + (i + 1));
            }

            String token = line.substring(0, p).trim();
            String password = line.substring(p + 1).trim();

            if (token.equals("internal")) {
                passwords.put(token, password);

            } else if (token.startsWith("hardware-")) {
                token = token.substring(9); // remove hardware- prefix
                passwords.put(token, password);

            } else {
                // skip non-token passwords
            }
        }

        return passwords;
    }

    public String promptForPassword(String prompt) throws IOException {
        char[] password = null;
        Console console = System.console();
        System.out.print(prompt);
        password = console.readPassword();
        return new String(password);
    }

    public String promptForPassword() throws IOException {
        return promptForPassword("Enter Password: ");
    }

    public static CAClient createCAClient(PKIClient client) throws Exception {

        ClientConfig config = client.getConfig();
        CAClient caClient = new CAClient(client);

        while (!caClient.exists()) {
            System.err.println("Error: CA subsystem not available");

            URL serverURI = config.getServerURL();
            String uri = serverURI.getProtocol() + "://" + serverURI.getHost() + ":" + serverURI.getPort();

            System.out.print("CA server URL [" + uri + "]: ");
            System.out.flush();

            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            String line = reader.readLine().trim();
            if (!line.equals("")) {
                uri = line;
            }

            config = new ClientConfig(client.getConfig());
            config.setServerURL(uri);

            client = new PKIClient(config);
            caClient = new CAClient(client);
        }

        return caClient;
    }

    public void parseOptions(CommandLine cmd) throws Exception {

        verbose = cmd.hasOption("v");
        output = cmd.getOptionValue("output");

        String url = cmd.getOptionValue("U");

        String protocol = cmd.getOptionValue("P", "http");
        String hostname = cmd.getOptionValue("h", InetAddress.getLocalHost().getCanonicalHostName());
        String port = cmd.getOptionValue("p", "8080");
        String subsystem = cmd.getOptionValue("t");

        if (url == null)
            url = protocol + "://" + hostname + ":" + port;

        if (subsystem != null) {
            System.err.println(
                    "WARNING: The -t option has been deprecated. Use pki " + subsystem + " command instead.");
            url = url + "/" + subsystem;
        }

        config.setServerURL(url);

        if (verbose)
            System.out.println("Server URL: " + url);

        String nssDatabase = cmd.getOptionValue("d");
        String nssPassword = cmd.getOptionValue("c");
        String nssPasswordFile = cmd.getOptionValue("C");
        String nssPasswordConfig = cmd.getOptionValue("f");

        String tokenName = cmd.getOptionValue("token");
        String certNickname = cmd.getOptionValue("n");

        String username = cmd.getOptionValue("u");
        String password = cmd.getOptionValue("w");
        String passwordFile = cmd.getOptionValue("W");

        // make sure no conflicting NSS passwords
        int nssPasswordCounter = 0;
        if (nssPassword != null)
            nssPasswordCounter++;
        if (nssPasswordFile != null)
            nssPasswordCounter++;
        if (nssPasswordConfig != null)
            nssPasswordCounter++;

        if (nssPasswordCounter > 1) {
            throw new Exception("The -c, -C, -f options are mutually exclusive.");
        }

        // make sure no conflicting authentication methods
        if (certNickname != null && username != null) {
            throw new Exception("The -n and -u options are mutually exclusive.");
        }

        // make sure no conflicting basic authentication passwords
        if (username != null) {

            if (password != null && passwordFile != null) {
                throw new Exception("The -w and -W options are mutually exclusive.");

            } else if (password == null && passwordFile == null) {
                throw new Exception("Missing user password.");
            }
        }

        if (nssDatabase != null) {
            // store user-provided NSS database location
            config.setNSSDatabase(new File(nssDatabase).getAbsolutePath());
        } else {
            // store default NSS database location
            config.setNSSDatabase(
                    System.getProperty("user.home") + File.separator + ".dogtag" + File.separator + "nssdb");
        }

        // store token name
        config.setTokenName(tokenName);

        // store certificate nickname
        config.setCertNickname(certNickname);

        if (nssPassword != null) {
            config.setNSSPassword(nssPassword);

        } else if (nssPasswordFile != null) {
            if (verbose)
                System.out.println("Loading NSS password from " + nssPasswordFile);
            nssPassword = loadPassword(nssPasswordFile);
            config.setNSSPassword(nssPassword);

        } else if (nssPasswordConfig != null) {
            if (verbose)
                System.out.println("Loading NSS password configuration from " + nssPasswordConfig);
            Map<String, String> nssPasswords = loadPasswordConfig(nssPasswordConfig);
            config.setNSSPasswords(nssPasswords);
        }

        // store user name
        config.setUsername(username);

        if (passwordFile != null) {
            if (verbose)
                System.out.println("Loading user password from " + passwordFile);
            password = loadPassword(passwordFile);

        } else if (username != null && password == null) {
            // prompt for user password if required for authentication
            password = promptForPassword();
        }

        // store user password
        config.setPassword(password);

        String list = cmd.getOptionValue("reject-cert-status");
        convertCertStatusList(list, rejectedCertStatuses);

        list = cmd.getOptionValue("ignore-cert-status");
        convertCertStatusList(list, ignoredCertStatuses);

        ignoreBanner = cmd.hasOption("ignore-banner");

        this.certDatabase = new File(config.getNSSDatabase());
        if (verbose)
            System.out.println("NSS database: " + this.certDatabase.getAbsolutePath());

        String messageFormat = cmd.getOptionValue("message-format");
        config.setMessageFormat(messageFormat);
        if (verbose)
            System.out.println("Message format: " + messageFormat);
    }

    public ClientConfig getConfig() {
        return config;
    }

    public void convertCertStatusList(String list, Collection<Integer> statuses) throws Exception {

        if (list == null)
            return;

        Class<SSLCertificateApprovalCallback.ValidityStatus> clazz = SSLCertificateApprovalCallback.ValidityStatus.class;

        for (String status : list.split(",")) {
            try {
                Field field = clazz.getField(status);
                statuses.add(field.getInt(null));

            } catch (NoSuchFieldException e) {
                throw new Exception("Invalid cert status \"" + status + "\".", e);
            }
        }
    }

    public void init() throws Exception {

        // Create NSS database if it doesn't exist
        if (!certDatabase.exists()) {

            if (verbose)
                System.out.println("Creating NSS database");

            certDatabase.mkdirs();

            String[] commands = { "/usr/bin/certutil", "-N", "-d", certDatabase.getAbsolutePath(),
                    "--empty-password" };

            try {
                runExternal(commands);
            } catch (Exception e) {
                throw new Exception("Unable to create NSS database", e);
            }
        }

        // Main program should initialize NSS
        if (verbose)
            System.out.println("Initializing NSS");
        CryptoManager.initialize(certDatabase.getAbsolutePath());

        CryptoManager manager;
        try {
            manager = CryptoManager.getInstance();

        } catch (NotInitializedException e) {
            // The original exception doesn't contain a message.
            throw new Exception("NSS has not been initialized", e);
        }

        // If password is specified, use password to access security token
        if (config.getNSSPassword() != null) {

            String tokenName = config.getTokenName();
            tokenName = tokenName == null ? CryptoUtil.INTERNAL_TOKEN_NAME : tokenName;

            if (verbose)
                System.out.println("Logging into " + tokenName + " token");

            CryptoToken token = CryptoUtil.getKeyStorageToken(tokenName);
            Password password = new Password(config.getNSSPassword().toCharArray());

            try {
                token.login(password);

            } catch (IncorrectPasswordException e) {
                // The original exception doesn't contain a message.
                throw new Exception("Incorrect password for " + tokenName + " token", e);

            } finally {
                password.clear();
            }

        } else {

            Map<String, String> passwords = config.getNSSPasswords();

            for (String tokenName : passwords.keySet()) {

                if (verbose)
                    System.out.println("Logging into " + tokenName + " token");

                CryptoToken token = CryptoUtil.getKeyStorageToken(tokenName);
                Password password = new Password(passwords.get(tokenName).toCharArray());

                try {
                    token.login(password);

                } catch (IncorrectPasswordException e) {
                    // The original exception doesn't contain a message.
                    throw new Exception("Incorrect password for " + tokenName + " token", e);

                } finally {
                    password.clear();
                }
            }
        }

        String tokenName = config.getTokenName();
        tokenName = tokenName == null ? CryptoUtil.INTERNAL_TOKEN_NAME : tokenName;
        if (verbose)
            System.out.println("Using " + tokenName + " token");

        CryptoToken token = CryptoUtil.getKeyStorageToken(tokenName);
        manager.setThreadToken(token);

        // See default SSL configuration in /usr/share/pki/etc/pki.conf.

        String streamVersionMin = System.getenv("SSL_STREAM_VERSION_MIN");
        String streamVersionMax = System.getenv("SSL_STREAM_VERSION_MAX");

        SSLVersionRange streamRange = CryptoUtil.boundSSLStreamVersionRange(
                streamVersionMin == null ? SSLVersion.TLS_1_0 : SSLVersion.valueOf(streamVersionMin),
                streamVersionMax == null ? SSLVersion.TLS_1_2 : SSLVersion.valueOf(streamVersionMax));
        CryptoUtil.setSSLStreamVersionRange(streamRange.getMinVersion(), streamRange.getMaxVersion());

        String datagramVersionMin = System.getenv("SSL_DATAGRAM_VERSION_MIN");
        String datagramVersionMax = System.getenv("SSL_DATAGRAM_VERSION_MAX");

        SSLVersionRange datagramRange = CryptoUtil.boundSSLDatagramVersionRange(
                datagramVersionMin == null ? SSLVersion.TLS_1_1 : SSLVersion.valueOf(datagramVersionMin),
                datagramVersionMax == null ? SSLVersion.TLS_1_2 : SSLVersion.valueOf(datagramVersionMax));
        CryptoUtil.setSSLDatagramVersionRange(datagramRange.getMinVersion(), datagramRange.getMaxVersion());

        String defaultCiphers = System.getenv("SSL_DEFAULT_CIPHERS");
        if (defaultCiphers == null || Boolean.parseBoolean(defaultCiphers)) {
            CryptoUtil.setDefaultSSLCiphers();
        } else {
            CryptoUtil.unsetSSLCiphers();
        }

        String ciphers = System.getenv("SSL_CIPHERS");
        CryptoUtil.setSSLCiphers(ciphers);
    }

    public PKIClient getClient() throws Exception {

        if (client != null)
            return client;

        if (verbose) {
            System.out.println("Initializing PKIClient");
        }

        client = new PKIClient(config, null);
        client.setVerbose(verbose);

        client.setRejectedCertStatuses(rejectedCertStatuses);
        client.setIgnoredCertStatuses(ignoredCertStatuses);

        if (output != null) {
            File file = new File(output);
            file.mkdirs();

            PKIConnection connection = client.getConnection();
            connection.setOutput(file);
        }

        if (!ignoreBanner) {

            InfoClient infoClient = new InfoClient(client);
            Info info = infoClient.getInfo();
            String banner = info.getBanner();

            if (banner != null) {

                System.out.println(banner);
                System.out.println();
                System.out.print("Do you want to proceed (y/N)? ");
                System.out.flush();

                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                String line = reader.readLine().trim();

                if (!line.equalsIgnoreCase("Y")) {
                    throw new CLIException();
                }
            }
        }

        return client;
    }

    public void execute(String[] args) throws Exception {

        CommandLine cmd = parser.parse(options, args, true);

        String[] cmdArgs = cmd.getArgs();

        if (cmd.hasOption("version")) {
            printVersion();
            return;
        }

        if (cmdArgs.length == 0 || cmd.hasOption("help")) {
            // Print 'pki' usage
            printHelp();
            return;
        }

        parseOptions(cmd);

        if (verbose) {
            System.out.print("Command:");
            for (String arg : cmdArgs) {
                if (arg.contains(" "))
                    arg = "\"" + arg + "\"";
                System.out.print(" " + arg);
            }
            System.out.println();
        }

        // Do not call CryptoManager.initialize() on some commands
        // because otherwise the database will be locked.
        String command = cmdArgs[0];
        if (!RESTRICTED_COMMANDS.contains(command)) {
            init();
        }

        super.execute(cmdArgs);
    }

    public static void printMessage(String message) {
        System.out.println(StringUtils.repeat("-", message.length()));
        System.out.println(message);
        System.out.println(StringUtils.repeat("-", message.length()));
    }

    public static void handleException(Throwable t) {

        if (verbose) {
            t.printStackTrace(System.err);

        } else if (t.getClass() == Exception.class) {
            // display a generic error
            System.err.println("Error: " + t.getMessage());

        } else if (t.getClass() == UnrecognizedOptionException.class) {
            // display only the error message
            System.err.println(t.getMessage());

        } else if (t instanceof ProcessingException) {
            // display the cause of the exception
            t = t.getCause();
            System.err.println(t.getClass().getSimpleName() + ": " + t.getMessage());

        } else {
            // display the actual Exception
            System.err.println(t.getClass().getSimpleName() + ": " + t.getMessage());
        }
    }

    public static void main(String args[]) {
        try {
            MainCLI cli = new MainCLI();
            cli.execute(args);

        } catch (CLIException e) {
            String message = e.getMessage();
            if (message != null) {
                System.err.println(message);
            }
            System.exit(e.getCode());

        } catch (Throwable t) {
            handleException(t);
            System.exit(-1);
        }
    }
}