From 27e27f8b63bc4c45d25a8bc5e07ac3a8e199565e Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Mon, 6 Oct 2014 12:20:42 -0400 Subject: Added CLI to import/export certificates with private keys. New CLI commands have been added to import/export certificates and private keys into/from the client security database. The CLI can also be used to generate the file needed by Python client library for client certificate authentication. --- .../src/com/netscape/cmstools/cli/MainCLI.java | 4 +- .../com/netscape/cmstools/client/ClientCLI.java | 2 + .../cmstools/client/ClientCertImportCLI.java | 172 +++++++++++--- .../cmstools/client/ClientCertModifyCLI.java | 126 ++++++++++ .../cmstools/client/ClientCertShowCLI.java | 256 +++++++++++++++++++++ 5 files changed, 532 insertions(+), 28 deletions(-) create mode 100644 base/java-tools/src/com/netscape/cmstools/client/ClientCertModifyCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java (limited to 'base/java-tools/src/com/netscape/cmstools') diff --git a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java index 066a7d580..8c3805e00 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java @@ -494,7 +494,9 @@ public class MainCLI extends CLI { String command = cmdArgs[0]; if (!command.equals("client-init") && !command.equals("client-cert-import") && - !command.equals("client-cert-request")) { + !command.equals("client-cert-mod") && + !command.equals("client-cert-request") && + !command.equals("client-cert-show")) { init(); } diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCLI.java index 443d48bdf..c9c71521a 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCLI.java @@ -35,8 +35,10 @@ public class ClientCLI extends CLI { addModule(new ClientInitCLI(this)); addModule(new ClientCertFindCLI(this)); addModule(new ClientCertImportCLI(this)); + addModule(new ClientCertModifyCLI(this)); addModule(new ClientCertRemoveCLI(this)); addModule(new ClientCertRequestCLI(this)); + addModule(new ClientCertShowCLI(this)); } public String getFullName() { diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertImportCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertImportCLI.java index 5080c55ea..afa91d659 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertImportCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertImportCLI.java @@ -21,6 +21,7 @@ package com.netscape.cmstools.client; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; import java.net.URI; import java.util.Arrays; @@ -51,21 +52,33 @@ public class ClientCertImportCLI extends CLI { } public void printHelp() { - formatter.printHelp(getFullName() + " [OPTIONS...]", options); + formatter.printHelp(getFullName() + " [nickname] [OPTIONS...]", options); } public void createOptions() { - Option option = new Option(null, "cert", true, "Import certificate file"); + Option option = new Option(null, "cert", true, "Certificate file to import."); option.setArgName("path"); options.addOption(option); - option = new Option(null, "ca-cert", true, "Import CA certificate file"); + option = new Option(null, "ca-cert", true, "CA certificate file to import."); + option.setArgName("path"); + options.addOption(option); + + option = new Option(null, "pkcs12", true, "PKCS #12 file to import."); + option.setArgName("path"); + options.addOption(option); + + option = new Option(null, "pkcs12-password", true, "PKCS #12 password."); + option.setArgName("password"); + options.addOption(option); + + option = new Option(null, "pkcs12-password-file", true, "PKCS #12 password file."); option.setArgName("path"); options.addOption(option); options.addOption(null, "ca-server", false, "Import CA certificate from CA server"); - option = new Option(null, "serial", true, "Serial number of certificate in CA"); + option = new Option(null, "serial", true, "Serial number of certificate to import from CA server"); option.setArgName("serial number"); options.addOption(option); @@ -117,30 +130,72 @@ public class ClientCertImportCLI extends CLI { nickname = mainCLI.config.getCertNickname(); } - if (nickname == null) { - System.err.println("Error: Missing certificate nickname."); - System.exit(-1); - } + // nickname is not required to import PKCS #12 file String certPath = cmd.getOptionValue("cert"); String caCertPath = cmd.getOptionValue("ca-cert"); + String pkcs12Path = cmd.getOptionValue("pkcs12"); + String pkcs12Password = cmd.getOptionValue("pkcs12-password"); + String pkcs12PasswordPath = cmd.getOptionValue("pkcs12-password-file"); boolean importFromCAServer = cmd.hasOption("ca-server"); String serialNumber = cmd.getOptionValue("serial"); String trustAttributes = cmd.getOptionValue("trust", "u,u,u"); - File certFile; - // load the certificate if (certPath != null) { - if (verbose) System.out.println("Loading certificate from " + certPath + "."); - certFile = new File(certPath); + + if (verbose) System.out.println("Importing certificate from " + certPath + "."); + + importCert( + mainCLI.certDatabase.getAbsolutePath(), + certPath, + nickname, + trustAttributes); } else if (caCertPath != null) { - if (verbose) System.out.println("Loading CA certificate from " + caCertPath + "."); - certFile = new File(caCertPath); + + if (verbose) System.out.println("Importing CA certificate from " + caCertPath + "."); trustAttributes = "CT,c,"; + importCert( + mainCLI.certDatabase.getAbsolutePath(), + caCertPath, + nickname, + trustAttributes); + + } else if (pkcs12Path != null) { + + if (verbose) System.out.println("Importing certificates from " + pkcs12Path + "."); + + if (pkcs12Password != null && pkcs12PasswordPath != null) { + throw new Exception("PKCS #12 password and password file are mutually exclusive"); + + } else if (pkcs12Password != null) { + // store password into a temporary file + File pkcs12PasswordFile = File.createTempFile("pki-client-cert-import-", ".pwd"); + pkcs12PasswordFile.deleteOnExit(); + + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } + + pkcs12PasswordPath = pkcs12PasswordFile.getAbsolutePath(); + + } else if (pkcs12PasswordPath != null) { + // nothing to do + + } else { + throw new Exception("Missing PKCS #12 password"); + } + + // import certificates and private key into PKCS #12 file + importPKCS12( + mainCLI.certDatabase.getAbsolutePath(), + mainCLI.config.getCertPassword(), + pkcs12Path, + pkcs12PasswordPath); + } else if (importFromCAServer) { // late initialization @@ -152,10 +207,10 @@ public class ClientCertImportCLI extends CLI { String caServerURI = serverURI.getScheme() + "://" + serverURI.getHost() + ":" + serverURI.getPort() + "/ca"; - if (verbose) System.out.println("Downloading CA certificate from " + caServerURI + "."); + if (verbose) System.out.println("Importing CA certificate from " + caServerURI + "."); byte[] bytes = client.downloadCACertChain(caServerURI); - certFile = File.createTempFile("pki-client-cert-import-", ".crt", mainCLI.certDatabase); + File certFile = File.createTempFile("pki-client-cert-import-", ".crt"); certFile.deleteOnExit(); try (FileOutputStream out = new FileOutputStream(certFile)) { @@ -164,6 +219,12 @@ public class ClientCertImportCLI extends CLI { trustAttributes = "CT,c,"; + importCert( + mainCLI.certDatabase.getAbsolutePath(), + certFile.getAbsolutePath(), + nickname, + trustAttributes); + } else if (serialNumber != null) { // connect to CA anonymously @@ -172,12 +233,15 @@ public class ClientCertImportCLI extends CLI { config.setCertPassword(null); config.setCertNickname(null); + URI serverURI = config.getServerURI(); + if (verbose) System.out.println("Importing certificate " + serialNumber + " from " + serverURI + "."); + PKIClient client = new PKIClient(config, null); CertClient certClient = new CertClient(client, "ca"); CertData certData = certClient.getCert(new CertId(serialNumber)); - certFile = File.createTempFile("pki-client-cert-import-", ".crt", mainCLI.certDatabase); + File certFile = File.createTempFile("pki-client-cert-import-", ".crt"); certFile.deleteOnExit(); String encoded = certData.getEncoded(); @@ -185,6 +249,12 @@ public class ClientCertImportCLI extends CLI { out.write(encoded); } + importCert( + mainCLI.certDatabase.getAbsolutePath(), + certFile.getAbsolutePath(), + nickname, + trustAttributes); + } else { System.err.println("Error: Missing certificate to import"); printHelp(); @@ -192,23 +262,71 @@ public class ClientCertImportCLI extends CLI { return; } - String[] commands = { - "/usr/bin/certutil", "-A", - "-d", mainCLI.certDatabase.getAbsolutePath(), - "-i", certFile.getAbsolutePath(), + if (nickname == null) { + MainCLI.printMessage("Imported certificates from PKCS #12 file"); + + } else { + MainCLI.printMessage("Imported certificate \"" + nickname + "\""); + } + } + + public void importCert( + String dbPath, + String certPath, + String nickname, + String trustAttributes) throws Exception { + + if (nickname == null) { + System.err.println("Error: Missing certificate nickname."); + System.exit(-1); + } + + String[] command = { + "/bin/certutil", "-A", + "-d", dbPath, + "-i", certPath, "-n", nickname, "-t", trustAttributes }; - Runtime rt = Runtime.getRuntime(); - Process p = rt.exec(commands); + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to import certificate file", e); + } + } + + public void importPKCS12( + String dbPath, + String dbPassword, + String pkcs12Path, + String pkcs12PasswordPath) throws Exception { + + String[] command = { + "/bin/pk12util", + "-d", dbPath, + "-K", dbPassword, + "-i", pkcs12Path, + "-w", pkcs12PasswordPath + }; + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to import PKCS #12 file", e); + } + } + + public void run(String[] command) throws IOException, InterruptedException { + + Runtime rt = Runtime.getRuntime(); + Process p = rt.exec(command); int rc = p.waitFor(); + if (rc != 0) { - MainCLI.printMessage("Import failed"); - return; + throw new IOException("Command failed. RC: " + rc); } - - MainCLI.printMessage("Imported certificate \"" + nickname + "\""); } } diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertModifyCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertModifyCLI.java new file mode 100644 index 000000000..738dca07c --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertModifyCLI.java @@ -0,0 +1,126 @@ +// --- 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) 2014 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +package com.netscape.cmstools.client; + +import java.io.IOException; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; + +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +/** + * @author Endi S. Dewata + */ +public class ClientCertModifyCLI extends CLI { + + public ClientCLI clientCLI; + + public ClientCertModifyCLI(ClientCLI clientCLI) { + super("cert-mod", "Modify certificate in client security database", clientCLI); + this.clientCLI = clientCLI; + + createOptions(); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " [OPTIONS...]", options); + } + + public void createOptions() { + Option option = new Option(null, "trust", true, "Trust attributes. Default: u,u,u."); + option.setArgName("trust attributes"); + options.addOption(option); + } + + public void execute(String[] args) throws Exception { + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + if (cmd.hasOption("help")) { + // Display usage + printHelp(); + System.exit(0); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length > 1) { + System.err.println("Error: Too many arguments specified."); + printHelp(); + System.exit(-1); + } + + if (cmdArgs.length == 0) { + System.err.println("Error: Missing certificate nickname."); + printHelp(); + System.exit(-1); + } + + MainCLI mainCLI = (MainCLI)parent.getParent(); + + String nickname = cmdArgs[0]; + + String trustAttributes = cmd.getOptionValue("trust", "u,u,u"); + + int rc = modifyCert( + mainCLI.certDatabase.getAbsolutePath(), + nickname, + trustAttributes); + + if (rc != 0) { + MainCLI.printMessage("Modified failed"); + return; + } + + MainCLI.printMessage("Modified certificate \"" + nickname + "\""); + } + + public int modifyCert( + String dbPath, + String nickname, + String trustAttributes) throws IOException, InterruptedException { + + String[] command = { + "/usr/bin/certutil", "-M", + "-d", dbPath, + "-n", nickname, + "-t", trustAttributes + }; + + return run(command); + } + + public int run(String[] command) throws IOException, InterruptedException { + + Runtime rt = Runtime.getRuntime(); + Process p = rt.exec(command); + return p.waitFor(); + } +} diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java new file mode 100644 index 000000000..f79501cfc --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertShowCLI.java @@ -0,0 +1,256 @@ +// --- 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) 2014 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- + +package com.netscape.cmstools.client; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang.StringUtils; +import org.mozilla.jss.crypto.X509Certificate; + +import com.netscape.certsrv.cert.CertData; +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; +import com.netscape.cmsutil.util.Utils; + +/** + * @author Endi S. Dewata + */ +public class ClientCertShowCLI extends CLI { + + public ClientCLI clientCLI; + + public ClientCertShowCLI(ClientCLI clientCLI) { + super("cert-show", "Show certificate in client security database", clientCLI); + this.clientCLI = clientCLI; + + createOptions(); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " [OPTIONS...]", options); + } + + public void createOptions() { + Option option = new Option(null, "cert", true, "PEM file to store the certificate."); + option.setArgName("path"); + options.addOption(option); + + option = new Option(null, "client-cert", true, "PEM file to store the certificate and the private key."); + option.setArgName("path"); + options.addOption(option); + + option = new Option(null, "pkcs12", true, "PKCS #12 file to store the certificate chain and the private key."); + option.setArgName("path"); + options.addOption(option); + + option = new Option(null, "pkcs12-password", true, "PKCS #12 file password."); + option.setArgName("password"); + options.addOption(option); + } + + public void execute(String[] args) throws Exception { + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + if (cmd.hasOption("help")) { + // Display usage + printHelp(); + System.exit(0); + } + + String[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length > 1) { + System.err.println("Error: Too many arguments specified."); + printHelp(); + System.exit(-1); + } + + if (cmdArgs.length == 0) { + System.err.println("Error: Missing certificate nickname."); + printHelp(); + System.exit(-1); + } + + MainCLI mainCLI = (MainCLI)parent.getParent(); + + String nickname = cmdArgs[0]; + String certPath = cmd.getOptionValue("cert"); + String pkcs12Path = cmd.getOptionValue("pkcs12"); + String pkcs12Password = cmd.getOptionValue("pkcs12-password"); + String clientCertPath = cmd.getOptionValue("client-cert"); + + if (certPath != null) { + + if (verbose) System.out.println("Exporting certificate to " + clientCertPath + "."); + + // late initialization + mainCLI.init(); + + client = mainCLI.getClient(); + X509Certificate cert = client.getCert(nickname); + + try (PrintWriter out = new PrintWriter(new FileWriter(certPath))) { + out.println(CertData.HEADER); + out.println(Utils.base64encode(cert.getEncoded())); + out.println(CertData.FOOTER); + } + + } else if (pkcs12Path != null) { + + if (verbose) System.out.println("Exporting certificate chain and private key to " + pkcs12Path + "."); + + if (pkcs12Password == null) { + throw new Exception("Missing PKCS #12 password"); + } + + // store password into a temporary file + File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); + pkcs12PasswordFile.deleteOnExit(); + + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } + + // export certificate chain and private key into PKCS #12 file + exportPKCS12( + mainCLI.certDatabase.getAbsolutePath(), + mainCLI.config.getCertPassword(), + pkcs12Path, + pkcs12PasswordFile.getAbsolutePath(), + nickname); + + } else if (clientCertPath != null) { + + if (verbose) System.out.println("Exporting client certificate and private key to " + clientCertPath + "."); + + // generate random PKCS #12 password + pkcs12Password = RandomStringUtils.randomAlphanumeric(16); + + // store password into a temporary file + File pkcs12PasswordFile = File.createTempFile("pki-client-cert-show-", ".pwd"); + pkcs12PasswordFile.deleteOnExit(); + + try (PrintWriter out = new PrintWriter(new FileWriter(pkcs12PasswordFile))) { + out.print(pkcs12Password); + } + + // export certificate chain and private key into a temporary PKCS #12 file + File pkcs12File = File.createTempFile("pki-client-cert-show-", ".p12"); + pkcs12File.deleteOnExit(); + + exportPKCS12( + mainCLI.certDatabase.getAbsolutePath(), + mainCLI.config.getCertPassword(), + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + nickname); + + // export client certificate and private key into a PEM file + exportClientCertificate( + pkcs12File.getAbsolutePath(), + pkcs12PasswordFile.getAbsolutePath(), + clientCertPath); + + } else { + // late initialization + mainCLI.init(); + + client = mainCLI.getClient(); + X509Certificate cert = client.getCert(nickname); + + ClientCLI.printCertInfo(cert); + } + } + + public void exportPKCS12( + String dbPath, + String dbPassword, + String pkcs12Path, + String pkcs12PasswordPath, + String nickname) throws Exception { + + String[] command = { + "/bin/pk12util", + "-d", dbPath, + "-K", dbPassword, + "-o", pkcs12Path, + "-w", pkcs12PasswordPath, + "-n", nickname + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export PKCS #12 file", e); + } + } + + public void exportClientCertificate( + String pkcs12Path, + String pkcs12PasswordPath, + String clientCertPath) throws Exception { + + String[] command = { + "/bin/openssl", + "pkcs12", + "-clcerts", // client certificate only + "-nodes", // no encryption + "-in", pkcs12Path, + "-passin", "file:" + pkcs12PasswordPath, + "-out", clientCertPath + }; + + try { + run(command); + + } catch (Exception e) { + throw new Exception("Unable to export client certificate", e); + } + } + + public void run(String[] command) throws IOException, InterruptedException { + + if (verbose) System.out.println("Command: " + StringUtils.join(command, " ")); + + Runtime rt = Runtime.getRuntime(); + Process p = rt.exec(command); + int rc = p.waitFor(); + + if (rc != 0) { + throw new IOException("Command failed. RC: " + rc); + } + } +} -- cgit