From 489fb993aeadf6f21f6a4a9655c2af2dc13eebcf Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Thu, 25 Feb 2016 21:31:24 +0100 Subject: Added workaround for JSS limitation in pki pkcs12-import. Currently JSS is unable to import CA certificates while preserving their nicknames. As a workaround, the pki pkcs12-import has been modified such that it exports individual CA certificates from PKCS The remaining user certificates will continue to be imported using JSS. A new pki pkcs12-cert-export command has been added to export individual certificates from PKCS #12 file into PEM files. The pki pkcs12-import has been modified to take a list of nicknames of the certificates to be imported into NSS database. https://fedorahosted.org/pki/ticket/1742 --- base/common/python/pki/cli/pkcs12.py | 127 +++++++++++++++ base/common/python/pki/nssdb.py | 76 ++++++--- .../netscape/cmstools/pkcs12/PKCS12CertCLI.java | 3 +- .../cmstools/pkcs12/PKCS12CertExportCLI.java | 173 +++++++++++++++++++++ .../netscape/cmstools/pkcs12/PKCS12ImportCLI.java | 16 +- .../src/netscape/security/pkcs/PKCS12Util.java | 65 ++++---- 6 files changed, 402 insertions(+), 58 deletions(-) create mode 100644 base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java diff --git a/base/common/python/pki/cli/pkcs12.py b/base/common/python/pki/cli/pkcs12.py index c0bf9aff0..a57dfd9ba 100644 --- a/base/common/python/pki/cli/pkcs12.py +++ b/base/common/python/pki/cli/pkcs12.py @@ -21,9 +21,14 @@ from __future__ import absolute_import from __future__ import print_function import getopt +import os +import re +import shutil import sys +import tempfile import pki.cli +import pki.nssdb class PKCS12CLI(pki.cli.CLI): @@ -107,6 +112,126 @@ class PKCS12ImportCLI(pki.cli.CLI): main_cli = self.parent.parent + # Due to JSS limitation, CA certificates need to be imported + # using certutil in order to preserve the nickname stored in + # the PKCS #12 file. + + if main_cli.verbose: + print('Getting certificate infos in PKCS #12 file') + + ca_certs = [] + user_certs = [] + + tmpdir = tempfile.mkdtemp() + + try: + + # find all certs in PKCS #12 file + output_file = os.path.join(tmpdir, 'pkcs12-cert-find.txt') + with open(output_file, 'wb') as f: + + cmd = ['pkcs12-cert-find'] + + if pkcs12_file: + cmd.extend(['--pkcs12', pkcs12_file]) + + if pkcs12_password: + cmd.extend(['--pkcs12-password', pkcs12_password]) + + if password_file: + cmd.extend(['--pkcs12-password-file', password_file]) + + if no_trust_flags: + cmd.extend(['--no-trust-flags']) + + main_cli.execute_java(cmd, stdout=f) + + # determine cert types + with open(output_file, 'r') as f: + + cert_info = None + + for line in f.readlines(): + + match = re.match(r' Nickname: (.*)$', line) + if match: + # store previous cert + if cert_info: + if 'key_id' in cert_info: + # if cert has key, it's a user cert + user_certs.append(cert_info) + else: + # otherwise it's a CA cert + ca_certs.append(cert_info) + + cert_info = {} + cert_info['nickname'] = match.group(1) + continue + + match = re.match(r' Key ID: (.*)$', line) + if match: + cert_info['key_id'] = match.group(1) + continue + + match = re.match(r' Trust Flags: (.*)$', line) + if match: + cert_info['trust_flags'] = match.group(1) + continue + + # store last cert + if cert_info: + if 'key_id' in cert_info: + # if cert has key, it's a user cert + user_certs.append(cert_info) + else: + # otherwise it's a CA cert + ca_certs.append(cert_info) + + cert_file = os.path.join(tmpdir, 'ca-cert.pem') + + nssdb = pki.nssdb.NSSDatabase( + main_cli.database, + token=main_cli.token, + password=main_cli.password, + password_file=main_cli.password_file) + + for cert_info in ca_certs: + + nickname = cert_info['nickname'] + trust_flags = cert_info['trust_flags'] + + if main_cli.verbose: + print('Exporting %s from PKCS #12 file' % nickname) + + cmd = ['pkcs12-cert-export'] + + if pkcs12_file: + cmd.extend(['--pkcs12', pkcs12_file]) + + if pkcs12_password: + cmd.extend(['--pkcs12-password', pkcs12_password]) + + if password_file: + cmd.extend(['--pkcs12-password-file', password_file]) + + cmd.extend(['--cert-file', cert_file, nickname]) + + main_cli.execute_java(cmd) + + if main_cli.verbose: + print('Importing %s' % nickname) + + nssdb.add_cert(nickname, cert_file, trust_flags) + + finally: + shutil.rmtree(tmpdir) + + # importing user certs + + nicknames = [] + for cert_info in user_certs: + nicknames.append(cert_info['nickname']) + cmd = ['pkcs12-import'] if pkcs12_file: @@ -121,4 +246,6 @@ class PKCS12ImportCLI(pki.cli.CLI): if no_trust_flags: cmd.extend(['--no-trust-flags']) + cmd.extend(nicknames) + main_cli.execute_java(cmd) diff --git a/base/common/python/pki/nssdb.py b/base/common/python/pki/nssdb.py index 3b34805b1..a428e397a 100644 --- a/base/common/python/pki/nssdb.py +++ b/base/common/python/pki/nssdb.py @@ -99,7 +99,11 @@ def get_file_type(filename): class NSSDatabase(object): - def __init__(self, directory, token='internal', password=None, password_file=None): + def __init__(self, directory=None, token=None, password=None, password_file=None): + + if not directory: + directory = os.path.join(os.path.expanduser("~"), '.dogtag', 'nssdb') + self.directory = directory self.token = token @@ -127,13 +131,18 @@ class NSSDatabase(object): cmd = [ 'certutil', '-A', - '-d', self.directory, - '-h', self.token, + '-d', self.directory + ] + + if self.token: + cmd.extend(['-h', self.token]) + + cmd.extend([ '-f', self.password_file, '-n', nickname, '-i', cert_file, '-t', trust_attributes - ] + ]) subprocess.check_call(cmd) @@ -144,12 +153,17 @@ class NSSDatabase(object): cmd = [ 'certutil', '-M', - '-d', self.directory, - '-h', self.token, + '-d', self.directory + ] + + if self.token: + cmd.extend(['-h', self.token]) + + cmd.extend([ '-f', self.password_file, '-n', nickname, '-t', trust_attributes - ] + ]) subprocess.check_call(cmd) @@ -189,13 +203,18 @@ class NSSDatabase(object): cmd = [ 'certutil', '-R', - '-d', self.directory, - '-h', self.token, + '-d', self.directory + ] + + if self.token: + cmd.extend(['-h', self.token]) + + cmd.extend([ '-f', self.password_file, '-s', subject_dn, '-o', binary_request_file, '-z', noise_file - ] + ]) if key_type: cmd.extend(['-k', key_type]) @@ -241,8 +260,13 @@ class NSSDatabase(object): 'certutil', '-C', '-x', - '-d', self.directory, - '-h', self.token, + '-d', self.directory + ] + + if self.token: + cmd.extend(['-h', self.token]) + + cmd.extend([ '-f', self.password_file, '-c', subject_dn, '-a', @@ -255,7 +279,7 @@ class NSSDatabase(object): '-3', '--extSKID', '--extAIA' - ] + ]) p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -334,12 +358,17 @@ class NSSDatabase(object): cmd = [ 'certutil', '-L', - '-d', self.directory, - '-h', self.token, + '-d', self.directory + ] + + if self.token: + cmd.extend(['-h', self.token]) + + cmd.extend([ '-f', self.password_file, '-n', nickname, output_format_option - ] + ]) cert_data = subprocess.check_output(cmd) @@ -353,11 +382,16 @@ class NSSDatabase(object): cmd = [ 'certutil', '-D', - '-d', self.directory, - '-h', self.token, + '-d', self.directory + ] + + if self.token: + cmd.extend(['-h', self.token]) + + cmd.extend([ '-f', self.password_file, '-n', nickname - ] + ]) subprocess.check_call(cmd) @@ -494,7 +528,7 @@ class NSSDatabase(object): '-C', self.password_file ] - if self.token and self.token != 'internal': + if self.token: cmd.extend(['--token', self.token]) cmd.extend([ @@ -531,7 +565,7 @@ class NSSDatabase(object): '-C', self.password_file ] - if self.token and self.token != 'internal': + if self.token: cmd.extend(['--token', self.token]) cmd.extend(['pkcs12-export']) diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java index 934b799dc..1ed88b1fa 100644 --- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertCLI.java @@ -32,6 +32,7 @@ public class PKCS12CertCLI extends CLI { super("cert", "PKCS #12 certificate management commands", parent); addModule(new PKCS12CertAddCLI(this)); + addModule(new PKCS12CertExportCLI(this)); addModule(new PKCS12CertFindCLI(this)); addModule(new PKCS12CertRemoveCLI(this)); } @@ -47,7 +48,7 @@ public class PKCS12CertCLI extends CLI { } if (certInfo.getTrustFlags() != null) { - System.out.println(" Trust flags: " + certInfo.getTrustFlags()); + System.out.println(" Trust Flags: " + certInfo.getTrustFlags()); } } } diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java new file mode 100644 index 000000000..c8ceab757 --- /dev/null +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12CertExportCLI.java @@ -0,0 +1,173 @@ +// --- 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.FileOutputStream; +import java.io.FileReader; +import java.io.PrintStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.ParseException; +import org.mozilla.jss.util.Password; + +import com.netscape.cmstools.cli.CLI; +import com.netscape.cmsutil.util.Utils; + +import netscape.security.pkcs.PKCS12; +import netscape.security.pkcs.PKCS12CertInfo; +import netscape.security.pkcs.PKCS12Util; +import netscape.security.x509.X509CertImpl; + +/** + * @author Endi S. Dewata + */ +public class PKCS12CertExportCLI extends CLI { + + public PKCS12CertExportCLI(PKCS12CertCLI certCLI) { + super("export", "Export certificate from 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); + + option = new Option(null, "cert-file", true, "Certificate 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); + } catch (ParseException 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[] cmdArgs = cmd.getArgs(); + + if (cmdArgs.length < 1) { + System.err.println("Error: Missing certificate nickname."); + printHelp(); + System.exit(-1); + } + + String nickname = cmdArgs[0]; + + String pkcs12File = cmd.getOptionValue("pkcs12"); + + if (pkcs12File == 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()); + + String certFile = cmd.getOptionValue("cert-file"); + + if (certFile == null) { + System.err.println("Error: Missing certificate file."); + printHelp(); + System.exit(-1); + } + + try { + PKCS12Util util = new PKCS12Util(); + PKCS12 pkcs12 = util.loadFromFile(pkcs12File, password); + + PKCS12CertInfo certInfo = pkcs12.getCertInfoByNickname(nickname); + if (certInfo == null) { + System.err.println("Error: Certificate not found."); + System.exit(-1); + } + + X509CertImpl cert = certInfo.getCert(); + try (PrintStream os = new PrintStream(new FileOutputStream(certFile))) { + os.println("-----BEGIN CERTIFICATE-----"); + os.print(Utils.base64encode(cert.getEncoded())); + os.println("-----END CERTIFICATE-----"); + } + + } finally { + password.clear(); + } + } +} diff --git a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java index 3e42efcbc..bdd8f52bc 100644 --- a/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/pkcs12/PKCS12ImportCLI.java @@ -44,7 +44,7 @@ public class PKCS12ImportCLI extends CLI { } public void printHelp() { - formatter.printHelp(getFullName() + " [OPTIONS...]", options); + formatter.printHelp(getFullName() + " [OPTIONS...] [nicknames...]", options); } public void createOptions() { @@ -95,6 +95,7 @@ public class PKCS12ImportCLI extends CLI { Logger.getLogger("netscape").setLevel(Level.FINE); } + String[] nicknames = cmd.getArgs(); String filename = cmd.getOptionValue("pkcs12"); if (filename == null) { @@ -130,7 +131,18 @@ public class PKCS12ImportCLI extends CLI { util.setTrustFlagsEnabled(trustFlagsEnabled); PKCS12 pkcs12 = util.loadFromFile(filename, password); - util.storeIntoNSS(pkcs12, password); + + if (nicknames.length == 0) { + // store all certificates + util.storeIntoNSS(pkcs12); + + } else { + // load specified certificates + for (String nickname : nicknames) { + util.storeCertIntoNSS(pkcs12, nickname); + } + } + } finally { password.clear(); diff --git a/base/util/src/netscape/security/pkcs/PKCS12Util.java b/base/util/src/netscape/security/pkcs/PKCS12Util.java index 665998e2f..b2c8f8667 100644 --- a/base/util/src/netscape/security/pkcs/PKCS12Util.java +++ b/base/util/src/netscape/security/pkcs/PKCS12Util.java @@ -209,14 +209,16 @@ public class PKCS12Util { attrs.addElement(nicknameAttr); - SEQUENCE localKeyAttr = new SEQUENCE(); - localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID); + if (certInfo.keyID != null) { + SEQUENCE localKeyAttr = new SEQUENCE(); + localKeyAttr.addElement(SafeBag.LOCAL_KEY_ID); - SET localKeySet = new SET(); - localKeySet.addElement(new OCTET_STRING(certInfo.keyID.toByteArray())); - localKeyAttr.addElement(localKeySet); + SET localKeySet = new SET(); + localKeySet.addElement(new OCTET_STRING(certInfo.keyID.toByteArray())); + localKeyAttr.addElement(localKeySet); - attrs.addElement(localKeyAttr); + attrs.addElement(localKeyAttr); + } if (certInfo.trustFlags != null && trustFlagsEnabled) { SEQUENCE trustFlagsAttr = new SEQUENCE(); @@ -252,13 +254,11 @@ public class PKCS12Util { loadCertChainFromNSS(pkcs12, cert); } - public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert) throws Exception { + public void loadCertFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger keyID) throws Exception { String nickname = cert.getNickname(); logger.info("Loading certificate \"" + nickname + "\" from NSS database"); - BigInteger keyID = createLocalKeyID(cert); - PKCS12CertInfo certInfo = new PKCS12CertInfo(); certInfo.keyID = keyID; certInfo.nickname = nickname; @@ -267,7 +267,7 @@ public class PKCS12Util { pkcs12.addCertInfo(certInfo); } - public void loadCertKeyFromNSS(PKCS12 pkcs12, X509Certificate cert) throws Exception { + public void loadCertKeyFromNSS(PKCS12 pkcs12, X509Certificate cert, BigInteger keyID) throws Exception { String nickname = cert.getNickname(); logger.info("Loading private key for certificate \"" + nickname + "\" from NSS database"); @@ -278,10 +278,8 @@ public class PKCS12Util { PrivateKey privateKey = cm.findPrivKeyByCert(cert); logger.fine("Certificate \"" + nickname + "\" has private key"); - PKCS12CertInfo certInfo = pkcs12.getCertInfoByNickname(nickname); - PKCS12KeyInfo keyInfo = new PKCS12KeyInfo(); - keyInfo.id = certInfo.getKeyID(); + keyInfo.id = keyID; keyInfo.subjectDN = cert.getSubjectDN().toString(); byte[] privateData = getEncodedKey(privateKey); @@ -299,15 +297,17 @@ public class PKCS12Util { CryptoManager cm = CryptoManager.getInstance(); + BigInteger keyID = createLocalKeyID(cert); + // load cert with key - loadCertFromNSS(pkcs12, cert); - loadCertKeyFromNSS(pkcs12, cert); + loadCertFromNSS(pkcs12, cert, keyID); + loadCertKeyFromNSS(pkcs12, cert, keyID); // load parent certs without key X509Certificate[] certChain = cm.buildCertificateChain(cert); for (int i = 1; i < certChain.length; i++) { X509Certificate c = certChain[i]; - loadCertFromNSS(pkcs12, c); + loadCertFromNSS(pkcs12, c, null); } } @@ -591,21 +591,19 @@ public class PKCS12Util { wrapper.unwrapPrivate(encpkey, getPrivateKeyType(publicKey), publicKey); } - public void importKeys(PKCS12 pkcs12) throws Exception { - - for (PKCS12KeyInfo keyInfo : pkcs12.getKeyInfos()) { - importKey(pkcs12, keyInfo); - } - } - - public void importCert(PKCS12 pkcs12, PKCS12CertInfo certInfo) throws Exception { + public void storeCertIntoNSS(PKCS12 pkcs12, PKCS12CertInfo certInfo) throws Exception { CryptoManager cm = CryptoManager.getInstance(); X509Certificate cert; + BigInteger keyID = certInfo.getKeyID(); + PKCS12KeyInfo keyInfo = keyID == null ? null : pkcs12.getKeyInfoByID(keyID); + + if (keyInfo != null) { // cert has key + logger.fine("Importing user key for " + certInfo.nickname); + importKey(pkcs12, keyInfo); - if (pkcs12.getKeyInfoByID(certInfo.getKeyID()) != null) { // cert has key - logger.fine("Importing user CA certificate " + certInfo.nickname); + logger.fine("Importing user certificate " + certInfo.nickname); cert = cm.importUserCACertPackage(certInfo.cert.getEncoded(), certInfo.nickname); } else { // cert has no key @@ -617,18 +615,17 @@ public class PKCS12Util { setTrustFlags(cert, certInfo.trustFlags); } - public void importCerts(PKCS12 pkcs12) throws Exception { - - for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) { - importCert(pkcs12, certInfo); - } + public void storeCertIntoNSS(PKCS12 pkcs12, String nickname) throws Exception { + PKCS12CertInfo certInfo = pkcs12.getCertInfoByNickname(nickname); + storeCertIntoNSS(pkcs12, certInfo); } - public void storeIntoNSS(PKCS12 pkcs12, Password password) throws Exception { + public void storeIntoNSS(PKCS12 pkcs12) throws Exception { logger.info("Storing data into NSS database"); - importKeys(pkcs12); - importCerts(pkcs12); + for (PKCS12CertInfo certInfo : pkcs12.getCertInfos()) { + storeCertIntoNSS(pkcs12, certInfo); + } } } -- cgit