>From ac088b1f7b2be5a07c6f63afb069daef288a88e3 Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Tue, 9 Feb 2016 18:41:40 +0100 Subject: [PATCH] Added CLIs to import and export PKCS #12. The pki pkcs12-import and pki pkcs12-export commands have been added to import and export PKCS #12 file into and from NSS database. https://fedorahosted.org/pki/ticket/1742 --- .../src/com/netscape/cmstools/PKCS12Export.java | 2 + .../com/netscape/cmstools/pkcs12/PKCS12CLI.java | 2 + .../netscape/cmstools/pkcs12/PKCS12ExportCLI.java | 132 ++++++++++++++++++ .../netscape/cmstools/pkcs12/PKCS12ImportCLI.java | 132 ++++++++++++++++++ .../src/netscape/security/pkcs/PKCS12Util.java | 154 +++++++++++++++++++++ 5 files changed, 422 insertions(+) create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java diff --git a/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java b/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java index c23aa1fe1aa00ff43eb3c1da46bb8811c3ecbe31..5d698bea3f7ebd0b01984b99d45d927d81e525c2 100644 --- a/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java +++ b/base/java-tools/src/com/netscape/cmstools/PKCS12Export.java @@ -195,6 +195,8 @@ public class PKCS12Export { tool.initDatabase(); tool.exportData(); + System.out.println("Export complete."); + } catch (Exception e) { if (debug) { logger.log(Level.SEVERE, "Unable to export PKCS #12 file", e); diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java index f69fcac53bb8afef502e8b3656fd02731892b43b..b841ec0ee16ebdf3d3a66e1fe1fbdc0d4971b909 100644 --- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CLI.java @@ -30,6 +30,8 @@ public class PKCS12CLI extends CLI { super("pkcs12", "PKCS #12 utilities", parent); addModule(new PKCS12CertCLI(this)); + addModule(new PKCS12ExportCLI(this)); + addModule(new PKCS12ImportCLI(this)); addModule(new PKCS12KeyCLI(this)); } diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..1e67740047a8f191b5b38548de8704e81dea431f --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ExportCLI.java @@ -0,0 +1,132 @@ +// --- 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) 2016 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmstools.pkcs12; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.mozilla.jss.util.Password; + +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +import netscape.security.pkcs.PKCS12Util; + +/** + * Tool for exporting NSS database into PKCS #12 file + */ +public class PKCS12ExportCLI extends CLI { + + public PKCS12ExportCLI(PKCS12CLI certCLI) { + super("export", "Export NSS database into PKCS #12 file", certCLI); + + createOptions(); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " [OPTIONS...]", options); + } + + public void createOptions() { + Option option = new Option(null, "pkcs12", true, "PKCS #12 file"); + 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("v", "verbose", false, "Run in verbose mode."); + options.addOption(null, "debug", false, "Run in debug mode."); + options.addOption(null, "help", false, "Show help message."); + } + + public void execute(String[] args) throws Exception { + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args, true); + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + if (cmd.hasOption("help")) { + printHelp(); + System.exit(0); + } + + if (cmd.hasOption("verbose")) { + Logger.getLogger("org.dogtagpki").setLevel(Level.INFO); + Logger.getLogger("com.netscape").setLevel(Level.INFO); + Logger.getLogger("netscape").setLevel(Level.INFO); + + } else if (cmd.hasOption("debug")) { + Logger.getLogger("org.dogtagpki").setLevel(Level.FINE); + Logger.getLogger("com.netscape").setLevel(Level.FINE); + Logger.getLogger("netscape").setLevel(Level.FINE); + } + + String filename = cmd.getOptionValue("pkcs12"); + + if (filename == null) { + System.err.println("Error: Missing PKCS #12 file."); + printHelp(); + System.exit(-1); + } + + String passwordString = cmd.getOptionValue("pkcs12-password"); + + if (passwordString == null) { + + String passwordFile = cmd.getOptionValue("pkcs12-password-file"); + if (passwordFile != null) { + try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) { + passwordString = in.readLine(); + } + } + } + + if (passwordString == null) { + System.err.println("Error: Missing PKCS #12 password."); + printHelp(); + System.exit(-1); + } + + Password password = new Password(passwordString.toCharArray()); + + try { + PKCS12Util util = new PKCS12Util(); + util.exportData(filename, password); + } finally { + password.clear(); + } + + MainCLI.printMessage("Export complete"); + } +} diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java new file mode 100644 index 0000000000000000000000000000000000000000..8add346fb5ef78adb217061b1d1815a6b4ebd320 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java @@ -0,0 +1,132 @@ +// --- 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) 2016 Red Hat, Inc. +// All rights reserved. +// --- END COPYRIGHT BLOCK --- +package com.netscape.cmstools.pkcs12; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.mozilla.jss.util.Password; + +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmstools.cli.MainCLI; + +import netscape.security.pkcs.PKCS12Util; + +/** + * Tool for importing NSS database from PKCS #12 file + */ +public class PKCS12ImportCLI extends CLI { + + public PKCS12ImportCLI(PKCS12CLI certCLI) { + super("import", "Import PKCS #12 file into NSS database", certCLI); + + createOptions(); + } + + public void printHelp() { + formatter.printHelp(getFullName() + " [OPTIONS...]", options); + } + + public void createOptions() { + Option option = new Option(null, "pkcs12", true, "PKCS #12 file"); + 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("v", "verbose", false, "Run in verbose mode."); + options.addOption(null, "debug", false, "Run in debug mode."); + options.addOption(null, "help", false, "Show help message."); + } + + public void execute(String[] args) throws Exception { + + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args, true); + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + printHelp(); + System.exit(-1); + } + + if (cmd.hasOption("help")) { + printHelp(); + System.exit(0); + } + + if (cmd.hasOption("verbose")) { + Logger.getLogger("org.dogtagpki").setLevel(Level.INFO); + Logger.getLogger("com.netscape").setLevel(Level.INFO); + Logger.getLogger("netscape").setLevel(Level.INFO); + + } else if (cmd.hasOption("debug")) { + Logger.getLogger("org.dogtagpki").setLevel(Level.FINE); + Logger.getLogger("com.netscape").setLevel(Level.FINE); + Logger.getLogger("netscape").setLevel(Level.FINE); + } + + String filename = cmd.getOptionValue("pkcs12"); + + if (filename == null) { + System.err.println("Error: Missing PKCS #12 file."); + printHelp(); + System.exit(-1); + } + + String passwordString = cmd.getOptionValue("pkcs12-password"); + + if (passwordString == null) { + + String passwordFile = cmd.getOptionValue("pkcs12-password-file"); + if (passwordFile != null) { + try (BufferedReader in = new BufferedReader(new FileReader(passwordFile))) { + passwordString = in.readLine(); + } + } + } + + if (passwordString == null) { + System.err.println("Error: Missing PKCS #12 password."); + printHelp(); + System.exit(-1); + } + + Password password = new Password(passwordString.toCharArray()); + + try { + PKCS12Util util = new PKCS12Util(); + util.importData(filename, password); + } finally { + password.clear(); + } + + MainCLI.printMessage("Import complete"); + } +} diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java index 9d66ac6fb035ac5c11aeb71b086be41838d86c6d..bcd48970e3b5f66835987e1b65fee81dfcc4963e 100644 --- a/base/util/src/netscape/security/pkcs/PKCS12Util.java +++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java @@ -24,6 +24,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; +import java.security.Principal; +import java.security.PublicKey; +import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; @@ -46,6 +49,7 @@ import org.mozilla.jss.crypto.KeyGenAlgorithm; import org.mozilla.jss.crypto.KeyGenerator; import org.mozilla.jss.crypto.KeyWrapAlgorithm; import org.mozilla.jss.crypto.KeyWrapper; +import org.mozilla.jss.crypto.NoSuchItemOnTokenException; import org.mozilla.jss.crypto.ObjectNotFoundException; import org.mozilla.jss.crypto.PBEAlgorithm; import org.mozilla.jss.crypto.PrivateKey; @@ -61,6 +65,7 @@ import org.mozilla.jss.pkix.primitive.EncryptedPrivateKeyInfo; import org.mozilla.jss.pkix.primitive.PrivateKeyInfo; import org.mozilla.jss.util.Password; +import netscape.ldap.LDAPDN; import netscape.security.x509.X509CertImpl; public class PKCS12Util { @@ -387,4 +392,153 @@ public class PKCS12Util { pfx = (PFX) (new PFX.Template()).decode(bis); } + + public PrivateKey.Type getPrivateKeyType(PublicKey publicKey) { + if (publicKey.getAlgorithm().equals("EC")) { + return PrivateKey.Type.EC; + } + return PrivateKey.Type.RSA; + } + + public X509CertImpl getCertBySubjectDN(String subjectDN, List certInfos) + throws CertificateException { + + for (PKCS12CertInfo certInfo : certInfos) { + X509CertImpl cert = certInfo.cert; + Principal certSubjectDN = cert.getSubjectDN(); + if (LDAPDN.equals(certSubjectDN.toString(), subjectDN)) return cert; + } + + return null; + } + + public void importKey( + PKCS12KeyInfo keyInfo, + Password password, + List certInfos) throws Exception { + + PrivateKeyInfo privateKeyInfo = keyInfo.privateKeyInfo; + + if (privateKeyInfo == null) { + privateKeyInfo = keyInfo.encPrivateKeyInfo.decrypt(password, new PasswordConverter()); + } + + String subjectDN = keyInfo.subjectDN; + + logger.fine("Importing private key " + subjectDN); + + // encode private key + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + privateKeyInfo.encode(bos); + byte[] privateKey = bos.toByteArray(); + + X509CertImpl x509cert = getCertBySubjectDN(subjectDN, certInfos); + if (x509cert == null) { + logger.fine("Private key nas no certificate, ignore"); + return; + } + + CryptoManager cm = CryptoManager.getInstance(); + CryptoToken token = cm.getInternalKeyStorageToken(); + CryptoStore store = token.getCryptoStore(); + + X509Certificate cert = cm.importCACertPackage(x509cert.getEncoded()); + + // get public key + PublicKey publicKey = cert.getPublicKey(); + + // delete the cert again + try { + store.deleteCert(cert); + } catch (NoSuchItemOnTokenException e) { + // this is OK + } + + // encrypt private key + KeyGenerator kg = token.getKeyGenerator(KeyGenAlgorithm.DES3); + SymmetricKey sk = kg.generate(); + byte iv[] = { 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 }; + IVParameterSpec param = new IVParameterSpec(iv); + Cipher c = token.getCipherContext(EncryptionAlgorithm.DES3_CBC_PAD); + c.initEncrypt(sk, param); + byte[] encpkey = c.doFinal(privateKey); + + // unwrap private key to load into database + KeyWrapper wrapper = token.getKeyWrapper(KeyWrapAlgorithm.DES3_CBC_PAD); + wrapper.initUnwrap(sk, param); + wrapper.unwrapPrivate(encpkey, getPrivateKeyType(publicKey), publicKey); + } + + public void importKeys( + List keyInfos, + Password password, + List certInfos + ) throws Exception { + + for (int i = 0; i < keyInfos.size(); i++) { + PKCS12KeyInfo keyInfo = keyInfos.get(i); + importKey(keyInfo, password, certInfos); + } + } + + public X509Certificate importCert(X509CertImpl cert) throws Exception { + + logger.fine("Importing certificate " + cert.getSubjectDN()); + + CryptoManager cm = CryptoManager.getInstance(); + return cm.importCACertPackage(cert.getEncoded()); + } + + public X509Certificate importCert(X509CertImpl cert, String nickname) throws Exception { + + logger.fine("Importing certificate " + cert.getSubjectDN() + " (" + nickname + ")"); + + CryptoManager cm = CryptoManager.getInstance(); + return cm.importUserCACertPackage(cert.getEncoded(), nickname); + } + + public void importCerts(List certInfos) throws Exception { + + for (PKCS12CertInfo certInfo : certInfos) { + + X509CertImpl cert = certInfo.cert; + String nickname = certInfo.nickname; + + if (nickname == null) { + importCert(cert); + continue; + } + + importCert(cert, nickname); + } + } + + public void verifyPassword(Password password) throws Exception { + + StringBuffer reason = new StringBuffer(); + boolean valid = pfx.verifyAuthSafes(password, reason); + + if (!valid) { + throw new Exception(reason.toString()); + } + } + + public void storeIntoNSS(Password password) throws Exception { + + logger.info("Storing data into NSS database"); + + verifyPassword(password); + + List keyInfos = getKeyInfos(); + List certInfos = getCertInfos(); + + importKeys(keyInfos, password, certInfos); + importCerts(certInfos); + } + + public void importData(String filename, Password password) throws Exception { + + loadFromPKCS12(filename); + storeIntoNSS(password); + } } -- 2.4.3