From 17468f319691b1f40ed355231d4f43dbd5b8c7be Mon Sep 17 00:00:00 2001 From: "Endi S. Dewata" Date: Tue, 27 Jan 2015 00:35:59 -0500 Subject: [PATCH] Refactored CRMFPopClient. The CRMFPopClient has been refactored such that it is easier to understand and reuse. The code has been fixed such that it can read a normal PEM transport certificate. It also has been fixed to parse the request submission result properly. The client-cert-request CLI command was modified to support CRMF requests. The MainCLI and ClientConfig were modified to accept a security token name. The pki_java_command_wrapper.in was modified to include the Apache Commons IO library. https://fedorahosted.org/pki/ticket/1074 --- .../com/netscape/certsrv/client/ClientConfig.java | 22 + .../src/com/netscape/cmstools/CRMFPopClient.java | 1039 ++++++++++---------- .../src/com/netscape/cmstools/cli/MainCLI.java | 24 +- .../cmstools/client/ClientCertRequestCLI.java | 240 ++++- .../templates/pki_java_command_wrapper.in | 1 + 5 files changed, 788 insertions(+), 538 deletions(-) diff --git a/base/common/src/com/netscape/certsrv/client/ClientConfig.java b/base/common/src/com/netscape/certsrv/client/ClientConfig.java index f59f7320ee427ddf0dcf21f50f2ed5f8f4029aa9..a816695362c41a8c72254b70f32234ce0a18fa30 100644 --- a/base/common/src/com/netscape/certsrv/client/ClientConfig.java +++ b/base/common/src/com/netscape/certsrv/client/ClientConfig.java @@ -51,10 +51,13 @@ public class ClientConfig { URI serverURI; String certDatabase; + String tokenName; String certNickname; String certPassword; + String username; String password; + String messageFormat; public ClientConfig() { @@ -62,11 +65,15 @@ public class ClientConfig { public ClientConfig(ClientConfig config) { serverURI = config.serverURI; + certDatabase = config.certDatabase; + tokenName = config.tokenName; certNickname = config.certNickname; certPassword = config.certPassword; + username = config.username; password = config.password; + messageFormat = config.messageFormat; } @@ -101,6 +108,15 @@ public class ClientConfig { this.certDatabase = certDatabase; } + @XmlElement(name="Token") + public String getTokenName() { + return tokenName; + } + + public void setTokenName(String tokenName) { + this.tokenName = tokenName; + } + @XmlElement(name="CertNickname") public String getCertNickname() { return certNickname; @@ -156,6 +172,7 @@ public class ClientConfig { result = prime * result + ((messageFormat == null) ? 0 : messageFormat.hashCode()); result = prime * result + ((password == null) ? 0 : password.hashCode()); result = prime * result + ((serverURI == null) ? 0 : serverURI.hashCode()); + result = prime * result + ((tokenName == null) ? 0 : tokenName.hashCode()); result = prime * result + ((username == null) ? 0 : username.hashCode()); return result; } @@ -199,6 +216,11 @@ public class ClientConfig { return false; } else if (!serverURI.equals(other.serverURI)) return false; + if (tokenName == null) { + if (other.tokenName != null) + return false; + } else if (!tokenName.equals(other.tokenName)) + return false; if (username == null) { if (other.username != null) return false; diff --git a/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java b/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java index 7edac0af6a99ed077ffc715c570441d9e6bad8de..17782eb34bf33d781a1770d75e7924a52390d44a 100644 --- a/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java +++ b/base/java-tools/src/com/netscape/cmstools/CRMFPopClient.java @@ -19,21 +19,22 @@ package com.netscape.cmstools; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; +import java.io.File; +import java.io.FileWriter; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.security.KeyPair; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import netscape.security.x509.X500Name; +import org.apache.commons.io.FileUtils; import org.mozilla.jss.CryptoManager; import org.mozilla.jss.asn1.ASN1Util; import org.mozilla.jss.asn1.BIT_STRING; @@ -46,7 +47,6 @@ import org.mozilla.jss.asn1.SEQUENCE; import org.mozilla.jss.asn1.TeletexString; import org.mozilla.jss.asn1.UTF8String; import org.mozilla.jss.asn1.UniversalString; -import org.mozilla.jss.crypto.AlreadyInitializedException; import org.mozilla.jss.crypto.CryptoToken; import org.mozilla.jss.crypto.IVParameterSpec; import org.mozilla.jss.crypto.KeyGenAlgorithm; @@ -74,6 +74,7 @@ import org.mozilla.jss.pkix.primitive.SubjectPublicKeyInfo; import org.mozilla.jss.util.Password; import com.netscape.cmsutil.crypto.CryptoUtil; +import com.netscape.cmsutil.util.Cert; import com.netscape.cmsutil.util.HMACDigest; import com.netscape.cmsutil.util.Utils; @@ -82,10 +83,8 @@ import com.netscape.cmsutil.util.Utils; * Format (CRMF) request with proof of possesion (POP). * *
- * IMPORTANT:  The file "transport.txt" needs to be created to contain the
- *             transport certificate in its base64 encoded format.  This
- *             file should consist of one line containing a single certificate
- *             in base64 encoded format with the header and footer removed.
+ * IMPORTANT:  The transport certificate file needs to be created to contain the
+ *             transport certificate in its base64 encoded format.
  * 
*

* @@ -93,6 +92,8 @@ import com.netscape.cmsutil.util.Utils; */ public class CRMFPopClient { + public boolean verbose; + private static void usage() { System.out.println("Usage: CRMFPopClient -d -p -h -o -n -a -l -c -m -f -u -r -q \n"); @@ -103,652 +104,686 @@ public class CRMFPopClient { System.out.println(" -e <1 for extractable; 0 for non-extractable; -1 token dependent; default is -1>\n"); System.out.println(" Also optional for ECC key generation:\n"); System.out.println(" -x \n"); + System.out.println(" --transport-cert \n"); System.out.println(" note: '-x true' can only be used with POP_NONE"); System.out.println(" available ECC curve names (if provided by the crypto module): nistp256 (secp256r1),nistp384 (secp384r1),nistp521 (secp521r1),nistk163 (sect163k1),sect163r1,nistb163 (sect163r2),sect193r1,sect193r2,nistk233 (sect233k1),nistb233 (sect233r1),sect239k1,nistk283 (sect283k1),nistb283 (sect283r1),nistk409 (sect409k1),nistb409 (sect409r1),nistk571 (sect571k1),nistb571 (sect571r1),secp160k1,secp160r1,secp160r2,secp192k1,nistp192 (secp192r1, prime192v1),secp224k1,nistp224 (secp224r1),secp256k1,prime192v2,prime192v3,prime239v1,prime239v2,prime239v3,c2pnb163v1,c2pnb163v2,c2pnb163v3,c2pnb176v1,c2tnb191v1,c2tnb191v2,c2tnb191v3,c2pnb208w1,c2tnb239v1,c2tnb239v2,c2tnb239v3,c2pnb272w1,c2pnb304w1,c2tnb359w1,c2pnb368w1,c2tnb431r1,secp112r1,secp112r2,secp128r1,secp128r2,sect113r1,sect113r2,sect131r1,sect131r2\n"); System.out.println("\n"); - System.out.println("IMPORTANT: The file \"transport.txt\" needs to be created to contain the"); - System.out.println(" transport certificate in its base64 encoded format. This"); - System.out.println(" file should consist of one line containing a single certificate"); - System.out.println(" in base64 encoded format with the header and footer removed.\n"); + System.out.println("IMPORTANT: The transport certificate file needs to be created to contain the"); + System.out.println(" transport certificate in its base64 encoded format."); } - public static void main(String args[]) { + public static void main(String args[]) throws Exception { -// int argsLen = getRealArgsLength(args); - - System.out.println("\n\nCRMF Proof Of Possession Utility...."); - System.out.println(""); - - if (args.length < 4) - { + if (args.length < 4) { usage(); System.exit(1); } - String DB_DIR = "./"; - String TOKEN_PWD = null; - String TOKEN_NAME = null; + CRMFPopClient client = new CRMFPopClient(); + + String databaseDir = "."; + String tokenPassword = null; + String tokenName = null; // "rsa" or "ec" - String alg = "rsa"; + String algorithm = "rsa"; /* default RSA key size */ - int RSA_keylen = 2048; + int keySize = 2048; + /* default ECC key curve name */ - String ECC_curve = "nistp256"; - boolean enable_encoding = false; /* enable encoding attribute values if true */ - boolean ec_temporary = true; /* session if true; token if false */ - int ec_sensitive = -1; /* -1, 0, or 1 */ - int ec_extractable = -1; /* -1, 0, or 1 */ - boolean ec_ssl_ecdh = false; + String curve = "nistp256"; - String USER_NAME = null; - String REQUESTOR_NAME = null; - String PROFILE_NAME = null; + boolean encodingEnabled = false; /* enable encoding attribute values if true */ + boolean temporary = true; /* session if true; token if false */ + int sensitive = -1; /* -1, 0, or 1 */ + int extractable = -1; /* -1, 0, or 1 */ + boolean sslECDH = false; + + String username = null; + String requestor = null; + String profileName = null; // format: "host:port" - String HOST_PORT = null; - String SUBJ_DN = null; - int doServerHit = 0; + String hostPort = null; + String subjectDN = null; + boolean submitRequest = false; // POP_NONE, POP_SUCCESS, or POP_FAIL - String POP_OPTION = "POP_SUCCESS"; - int dont_do_pop = 0; + String popOption = "POP_SUCCESS"; + boolean withPop = true; - String REQ_OUT_FILE = null; + String output = null; + String transportCertFilename = "transport.txt"; for (int i=0; i 0)? elementValue.substring(0, n): null; - String nameValue = (n > 0)? elementValue.substring(n+1): null; - if (encodingType != null && encodingType.length() > 0 && - nameValue != null && nameValue.length() > 0) { - if (encodingType.equals("UTF8String")) { - name.addElement( new AVA(oid, new UTF8String(nameValue))); - } else if (encodingType.equals("PrintableString")) { - name.addElement( new AVA(oid, new PrintableString(nameValue))); - } else if (encodingType.equals("BMPString")) { - name.addElement( new AVA(oid, new BMPString(nameValue))); - } else if (encodingType.equals("TeletexString")) { - name.addElement( new AVA(oid, new TeletexString(nameValue))); - } else if (encodingType.equals("UniversalString")) { - name.addElement( new AVA(oid, new UniversalString(nameValue))); + + signer.initSign((org.mozilla.jss.crypto.PrivateKey) keyPair.getPrivate()); + + return signer; + } + + public ProofOfPossession createPop(String algorithm, byte[] signature) throws Exception { + + AlgorithmIdentifier algorithmID; + if (algorithm.equals("rsa")) { + algorithmID = new AlgorithmIdentifier(SignatureAlgorithm.RSASignatureWithMD5Digest.toOID(), null); + + } else if (algorithm.equals("ec")) { + algorithmID = new AlgorithmIdentifier(SignatureAlgorithm.ECSignatureWithSHA1Digest.toOID(), null); + + } else { + throw new Exception("Unknown algorithm: " + algorithm); + } + + POPOSigningKey popoKey = new POPOSigningKey(null, algorithmID, new BIT_STRING(signature, 0)); + return ProofOfPossession.createSignature(popoKey); + } + + public String createCRMFRequest( + CertRequest certRequest, + ProofOfPossession pop) throws Exception { + + CertReqMsg crmfMessage = new CertReqMsg(certRequest, pop, null); + //crmfMessage.verify(); + + SEQUENCE seq = new SEQUENCE(); + seq.addElement(crmfMessage); + + byte[] encodedCrmfMessage = ASN1Util.encode(seq); + return Utils.base64encode(encodedCrmfMessage); + } + + public void submitRequest( + String request, + String hostPort, + String username, + String profileName, + String requestor) throws Exception { + + String encodedRequest = URLEncoder.encode(request, "UTF-8"); + + URL url = new URL( + "http://" + hostPort + "/ca/ee/ca/profileSubmit" + + "?cert_request_type=crmf" + + "&cert_request=" + encodedRequest + + "&renewal=false&uid=" + username + + "&xmlOutput=false" + + "&profileId=" + profileName + + "&sn_uid=" + username + + "&SubId=profile" + + "&requestor_name=" + requestor); + + if (verbose) System.out.println("Opening " + url); + + URLConnection conn = url.openConnection(); + InputStream is = conn.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + System.out.println("--------------------"); + String line = null; + String status = null; + String requestId = null; + while ((line = reader.readLine()) != null) { + System.out.println(line); + + if (line.startsWith("errorCode=")) { + int i = line.indexOf("\""); + int j = line.indexOf("\";", i+1); + String errorCode = line.substring(i+1, j); + + if ("0".equals(errorCode)) { + status = "completed"; + + } else if ("1".equals(errorCode)) { + status = "failed"; + + } else if ("2".equals(errorCode)) { + status = "pending"; + + } else { + status = "unknown"; } + + } else if (line.startsWith("requestList.requestId=")) { + int i = line.indexOf("\""); + int j = line.indexOf("\";", i+1); + requestId = line.substring(i+1, j); } - } catch (Exception e) { - System.out.println("CRMFPopClient: Error adding name element: " + elementValue + " Error: " + e.toString()); } - return name; + System.out.println("--------------------"); + + if (requestId != null) { + System.out.println("Request ID: " + requestId); + } + + if (status != null) { + System.out.println("Request Status: " + status); + } } - static Name getJssName(boolean enable_encoding, String dn) { + public boolean isEncoded(String elementValue) { - X500Name x5Name = null; + if (elementValue == null) return false; - try { - x5Name = new X500Name(dn); + return elementValue.startsWith("UTF8String:") + || elementValue.startsWith("PrintableString:") + || elementValue.startsWith("BMPString:") + || elementValue.startsWith("TeletexString:") + || elementValue.startsWith("UniversalString:"); + } - } catch (IOException e) { + public AVA createAVA(OBJECT_IDENTIFIER oid, int n, String elementValue) throws Exception { - System.out.println("CRMFPopClient: Illegal Subject Name: " + dn + " Error: " + e.toString()); - System.out.println("CRMFPopClient: Filling in default Subject Name......"); - return null; - } + String encodingType = n > 0 ? elementValue.substring(0, n) : null; + String nameValue = n > 0 ? elementValue.substring(n+1) : null; - Name ret = new Name(); + if (encodingType != null && encodingType.length() > 0 + && nameValue != null && nameValue.length() > 0) { - netscape.security.x509.RDN[] names = null; + if (encodingType.equals("UTF8String")) { + return new AVA(oid, new UTF8String(nameValue)); - names = x5Name.getNames(); + } else if (encodingType.equals("PrintableString")) { + return new AVA(oid, new PrintableString(nameValue)); - int nameLen = x5Name.getNamesLength(); + } else if (encodingType.equals("BMPString")) { + return new AVA(oid, new BMPString(nameValue)); + + } else if (encodingType.equals("TeletexString")) { + return new AVA(oid, new TeletexString(nameValue)); + + } else if (encodingType.equals("UniversalString")) { + return new AVA(oid, new UniversalString(nameValue)); + + } else { + throw new Exception("Unsupported encoding: " + encodingType); + } + } + + return null; + } - // System.out.println("x5Name len: " + nameLen); + public Name createName(String dn, boolean encodingEnabled) throws Exception { - netscape.security.x509.RDN cur = null; + X500Name x500Name = new X500Name(dn); + Name jssName = new Name(); - for (int i = 0; i < nameLen; i++) { - cur = names[i]; + for (netscape.security.x509.RDN rdn : x500Name.getNames()) { - String rdnStr = cur.toString(); + String rdnStr = rdn.toString(); + if (verbose) System.out.println("RDN: " + rdnStr); String[] split = rdnStr.split("="); + if (split.length != 2) continue; - if (split.length != 2) - continue; - int n = split[1].indexOf(':'); + String attribute = split[0]; + String value = split[1]; - try { - - if (split[0].equals("UID")) { - if (enable_encoding && isEncoded(split[1])) { - ret = addNameElement(ret, new OBJECT_IDENTIFIER("0.9.2342.19200300.100.1.1"), - n, split[1]); - } else { - ret.addElement(new AVA(new OBJECT_IDENTIFIER("0.9.2342.19200300.100.1.1"), - new PrintableString(split[1]))); - } - // System.out.println("UID found : " + split[1]); + int n = value.indexOf(':'); + if (attribute.equalsIgnoreCase("UID")) { + AVA ava; + if (encodingEnabled && isEncoded(value)) { + ava = createAVA(new OBJECT_IDENTIFIER("0.9.2342.19200300.100.1.1"), n, value); + } else { + ava = new AVA(new OBJECT_IDENTIFIER("0.9.2342.19200300.100.1.1"), new PrintableString(value)); } + jssName.addElement(ava); - if (split[0].equals("C")) { - ret.addCountryName(split[1]); - // System.out.println("C found : " + split[1]); - continue; - - } + } else if (attribute.equalsIgnoreCase("C")) { + jssName.addCountryName(value); - if (split[0].equals("CN")) { - if (enable_encoding && isEncoded(split[1])) { - ret = addNameElement (ret, Name.commonName, n, split[1]); - } else { - ret.addCommonName(split[1]); - } - // System.out.println("CN found : " + split[1]); - continue; + } else if (attribute.equalsIgnoreCase("CN")) { + if (encodingEnabled && isEncoded(value)) { + jssName.addElement(createAVA(Name.commonName, n, value)); + } else { + jssName.addCommonName(value); } - if (split[0].equals("L")) { - if (enable_encoding && isEncoded(split[1])) { - ret = addNameElement (ret, Name.localityName, n, split[1]); - } else { - ret.addLocalityName(split[1]); - } - // System.out.println("L found : " + split[1]); - continue; + } else if (attribute.equalsIgnoreCase("L")) { + if (encodingEnabled && isEncoded(value)) { + jssName.addElement(createAVA(Name.localityName, n, value)); + } else { + jssName.addLocalityName(value); } - if (split[0].equals("O")) { - if (enable_encoding && isEncoded(split[1])) { - ret = addNameElement (ret, Name.organizationName, n, split[1]); - } else { - ret.addOrganizationName(split[1]); - } - // System.out.println("O found : " + split[1]); - continue; + } else if (attribute.equalsIgnoreCase("O")) { + if (encodingEnabled && isEncoded(value)) { + jssName.addElement(createAVA(Name.organizationName, n, value)); + } else { + jssName.addOrganizationName(value); } - if (split[0].equals("ST")) { - if (enable_encoding && isEncoded(split[1])) { - ret = addNameElement (ret, Name.stateOrProvinceName, n, split[1]); - } else { - ret.addStateOrProvinceName(split[1]); - } - // System.out.println("ST found : " + split[1]); - continue; + } else if (attribute.equalsIgnoreCase("ST")) { + if (encodingEnabled && isEncoded(value)) { + jssName.addElement(createAVA(Name.stateOrProvinceName, n, value)); + } else { + jssName.addStateOrProvinceName(value); } - if (split[0].equals("OU")) { - if (enable_encoding && isEncoded(split[1])) { - ret = addNameElement (ret, Name.organizationalUnitName, n, split[1]); - } else { - ret.addOrganizationalUnitName(split[1]); - } - // System.out.println("OU found : " + split[1]); - continue; + } else if (attribute.equalsIgnoreCase("OU")) { + if (encodingEnabled && isEncoded(value)) { + jssName.addElement(createAVA(Name.organizationalUnitName, n, value)); + } else { + jssName.addOrganizationalUnitName(value); } - } catch (Exception e) { - System.out.println("CRMFPopClient: Error constructing RDN: " + rdnStr + " Error: " + e.toString()); - continue; + } else { + throw new Exception("Unsupported attribute: " + attribute); } - } - return ret; - + return jssName; } } 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 8c3805e007b7384f0b073e92bad88076e39ab2b6..1dbf027606f9b83e5319284038d31f02248f4cde 100644 --- a/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/cli/MainCLI.java @@ -168,6 +168,10 @@ public class MainCLI extends CLI { 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); @@ -286,6 +290,8 @@ public class MainCLI extends CLI { String certNickname = cmd.getOptionValue("n"); String certPassword = cmd.getOptionValue("c"); String certPasswordFile = cmd.getOptionValue("C"); + String tokenName = cmd.getOptionValue("token"); + String username = cmd.getOptionValue("u"); String password = cmd.getOptionValue("w"); String passwordFile = cmd.getOptionValue("W"); @@ -323,6 +329,9 @@ public class MainCLI extends CLI { if (certDatabase != null) config.setCertDatabase(new File(certDatabase).getAbsolutePath()); + // store token name + config.setTokenName(tokenName); + // store certificate nickname config.setCertNickname(certNickname); @@ -420,14 +429,25 @@ public class MainCLI extends CLI { // Main program should initialize client security database if (certDatabase.exists()) { + if (verbose) System.out.println("Initializing client security database"); CryptoManager.initialize(certDatabase.getAbsolutePath()); } - // If password is specified, use password to access client security database + // If password is specified, use password to access security token if (config.getCertPassword() != null) { + if (verbose) System.out.println("Logging into security token"); try { CryptoManager manager = CryptoManager.getInstance(); - CryptoToken token = manager.getInternalKeyStorageToken(); + + CryptoToken token; + String tokenName = config.getTokenName(); + if (tokenName == null) { + token = manager.getInternalKeyStorageToken(); + } else { + token = manager.getTokenByName(tokenName); + } + manager.setThreadToken(token); + Password password = new Password(config.getCertPassword().toCharArray()); token.login(password); diff --git a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java index 9e7b7e3f1c007ce16cb10e0cf8926cc644a5b88a..996bcf27586c2dead71d723e2777f6e99ea832fc 100644 --- a/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java +++ b/base/java-tools/src/com/netscape/cmstools/client/ClientCertRequestCLI.java @@ -18,7 +18,9 @@ package com.netscape.cmstools.client; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.security.KeyPair; import java.util.Vector; import netscape.ldap.util.DN; @@ -27,15 +29,26 @@ import netscape.ldap.util.RDN; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.io.FileUtils; +import org.mozilla.jss.CryptoManager; +import org.mozilla.jss.crypto.CryptoToken; +import org.mozilla.jss.crypto.Signature; +import org.mozilla.jss.crypto.X509Certificate; +import org.mozilla.jss.pkix.crmf.CertRequest; +import org.mozilla.jss.pkix.crmf.ProofOfPossession; +import org.mozilla.jss.pkix.primitive.Name; import com.netscape.certsrv.cert.CertClient; import com.netscape.certsrv.cert.CertEnrollmentRequest; import com.netscape.certsrv.cert.CertRequestInfos; import com.netscape.certsrv.profile.ProfileAttribute; import com.netscape.certsrv.profile.ProfileInput; +import com.netscape.certsrv.system.SystemCertClient; +import com.netscape.cmstools.CRMFPopClient; import com.netscape.cmstools.cert.CertCLI; import com.netscape.cmstools.cli.CLI; import com.netscape.cmstools.cli.MainCLI; +import com.netscape.cmsutil.util.Cert; +import com.netscape.cmsutil.util.Utils; /** * @author Endi S. Dewata @@ -56,15 +69,47 @@ public class ClientCertRequestCLI extends CLI { } public void createOptions() { - Option option = new Option(null, "algorithm", true, "Algorithm (default: rsa)"); - option.setArgName("algorithm"); + Option option = new Option(null, "type", true, "Request type (default: pkcs10)"); + option.setArgName("request type"); + options.addOption(option); + + option = new Option(null, "attribute-encoding", true, "Attribute encoding (default: false)"); + option.setArgName("boolean"); + options.addOption(option); + + option = new Option(null, "algorithm", true, "Algorithm (default: rsa)"); + option.setArgName("algorithm name"); options.addOption(option); option = new Option(null, "length", true, "RSA key length (default: 1024)"); - option.setArgName("length"); + option.setArgName("key length"); options.addOption(option); - option = new Option(null, "profile", true, "Certificate profile (default: caUserCert)"); + option = new Option(null, "curve", true, "ECC key curve name (default: nistp256)"); + option.setArgName("curve name"); + options.addOption(option); + + option = new Option(null, "ssl-ecdh", true, "SSL certificate with ECDH ECDSA (default: false)"); + option.setArgName("boolean"); + options.addOption(option); + + option = new Option(null, "temporary", true, "Temporary (default: true)"); + option.setArgName("boolean"); + options.addOption(option); + + option = new Option(null, "sensitive", true, "Sensitive"); + option.setArgName("boolean"); + options.addOption(option); + + option = new Option(null, "extractable", true, "Extractable"); + option.setArgName("boolean"); + options.addOption(option); + + option = new Option(null, "transport-cert", true, "Transport certificate"); + option.setArgName("path"); + options.addOption(option); + + option = new Option(null, "profile", true, "Certificate profile (default: caEncUserCert)"); option.setArgName("profile"); options.addOption(option); @@ -104,10 +149,45 @@ public class ClientCertRequestCLI extends CLI { String subjectDN = cmdArgs[0]; + // pkcs10, crmf + String requestType = cmd.getOptionValue("type", "pkcs10"); + + boolean attributeEncoding = Boolean.parseBoolean(cmd.getOptionValue("attribute-encoding", "false")); + + // rsa, ec String algorithm = cmd.getOptionValue("algorithm", "rsa"); - String length = cmd.getOptionValue("length", "1024"); - String profileID = cmd.getOptionValue("profile", "caUserCert"); - String requestType = "pkcs10"; + int length = Integer.parseInt(cmd.getOptionValue("length", "1024")); + + String curve = cmd.getOptionValue("curve", "nistp256"); + boolean sslECDH = Boolean.parseBoolean(cmd.getOptionValue("ssl-ecdh", "false")); + boolean temporary = Boolean.parseBoolean(cmd.getOptionValue("temporary", "true")); + + String s = cmd.getOptionValue("sensitive"); + int sensitive; + if (s == null) { + sensitive = -1; + } else { + sensitive = Boolean.parseBoolean(s) ? 1 : 0; + } + + s = cmd.getOptionValue("extractable"); + int extractable; + if (s == null) { + extractable = -1; + } else { + extractable = Boolean.parseBoolean(s) ? 1 : 0; + } + + String transportCertFilename = cmd.getOptionValue("transport-cert"); + + String profileID = cmd.getOptionValue("profile"); + if (profileID == null) { + if ("rsa".equals(algorithm)) { + profileID = "caUserCert"; + } else if ("ec".equals(algorithm)) { + profileID = "caEncECUserCert"; + } + } MainCLI mainCLI = (MainCLI)parent.getParent(); File certDatabase = mainCLI.certDatabase; @@ -118,38 +198,48 @@ public class ClientCertRequestCLI extends CLI { System.exit(-1); } - File csrFile = File.createTempFile("pki-client-cert-request-", ".csr", certDatabase); - csrFile.deleteOnExit(); - - String[] commands = { - "/usr/bin/PKCS10Client", - "-d", certDatabase.getAbsolutePath(), - "-p", password, - "-a", algorithm, - "-l", length, - "-o", csrFile.getAbsolutePath(), - "-n", subjectDN - }; - - Runtime rt = Runtime.getRuntime(); - Process p = rt.exec(commands); - - int rc = p.waitFor(); - if (rc != 0) { - MainCLI.printMessage("CSR generation failed"); - return; + String csr; + if ("pkcs10".equals(requestType)) { + csr = generatePkcs10Request(certDatabase, password, algorithm, length, subjectDN); + + // initialize database after PKCS10Client to avoid conflict + mainCLI.init(); + client = mainCLI.getClient(); + + + } else if ("crmf".equals(requestType)) { + + // initialize database before CRMFPopClient to load transport certificate + mainCLI.init(); + client = mainCLI.getClient(); + + String encoded; + if (transportCertFilename == null) { + SystemCertClient certClient = new SystemCertClient(client, "kra"); + encoded = certClient.getTransportCert().getEncoded(); + + } else { + encoded = FileUtils.readFileToString(new File(transportCertFilename)); + } + + encoded = Cert.normalizeCertStrAndReq(encoded); + encoded = Cert.stripBrackets(encoded); + byte[] transportCertData = Utils.base64decode(encoded); + + CryptoManager manager = CryptoManager.getInstance(); + X509Certificate transportCert = manager.importCACertPackage(transportCertData); + + csr = generateCrmfRequest(transportCert, subjectDN, attributeEncoding, algorithm, length, curve, sslECDH, temporary, sensitive, extractable); + + } else { + throw new Exception("Unknown request type: " + requestType); } if (verbose) { - System.out.println("CSR generated: " + csrFile); + System.out.println("CSR:"); + System.out.println(csr); } - String csr = FileUtils.readFileToString(csrFile); - - // late initialization - mainCLI.init(); - client = mainCLI.getClient(); - CertClient certClient = new CertClient(client, "ca"); if (verbose) { @@ -188,4 +278,86 @@ public class ClientCertRequestCLI extends CLI { MainCLI.printMessage("Submitted certificate request"); CertCLI.printCertRequestInfos(infos); } + + public String generatePkcs10Request( + File certDatabase, + String password, + String algorithm, + int length, + String subjectDN + ) throws Exception { + + File csrFile = File.createTempFile("pki-client-cert-request-", ".csr", certDatabase); + csrFile.deleteOnExit(); + + String[] commands = { + "/usr/bin/PKCS10Client", + "-d", certDatabase.getAbsolutePath(), + "-p", password, + "-a", algorithm, + "-l", "" + length, + "-o", csrFile.getAbsolutePath(), + "-n", subjectDN + }; + + Runtime rt = Runtime.getRuntime(); + Process p = rt.exec(commands); + + int rc = p.waitFor(); + if (rc != 0) { + throw new Exception("CSR generation failed"); + } + + if (verbose) { + System.out.println("CSR generated: " + csrFile); + } + + return FileUtils.readFileToString(csrFile); + } + + public String generateCrmfRequest( + X509Certificate transportCert, + String subjectDN, + boolean attributeEncoding, + String algorithm, + int length, + String curve, + boolean sslECDH, + boolean temporary, + int sensitive, + int extractable + ) throws Exception { + + CryptoManager manager = CryptoManager.getInstance(); + CryptoToken token = manager.getThreadToken(); + + CRMFPopClient client = new CRMFPopClient(); + + Name subject = client.createName(subjectDN, attributeEncoding); + + KeyPair keyPair; + if (algorithm.equals("rsa")) { + keyPair = client.generateRSAKeyPair(token, length); + + } else if (algorithm.equals("ec")) { + keyPair = client.generateECCKeyPair(token, curve, sslECDH, temporary, sensitive, extractable); + + } else { + throw new Exception("Unknown algorithm: " + algorithm); + } + + CertRequest certRequest = client.createCertRequest(token, transportCert, algorithm, keyPair, subject); + + Signature signer = client.createSigner(token, algorithm, keyPair); + + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + certRequest.encode(bo); + signer.update(bo.toByteArray()); + + byte[] signature = signer.sign(); + + ProofOfPossession pop = client.createPop(algorithm, signature); + + return client.createCRMFRequest(certRequest, pop); + } } diff --git a/base/java-tools/templates/pki_java_command_wrapper.in b/base/java-tools/templates/pki_java_command_wrapper.in index c66cb538d2518cc4d4a2f133787e43dcc039b1f6..dbc4986160241ebe60bfe5538467982d5c393535 100644 --- a/base/java-tools/templates/pki_java_command_wrapper.in +++ b/base/java-tools/templates/pki_java_command_wrapper.in @@ -132,6 +132,7 @@ fi JNI_JAR_DIR=`source /usr/share/pki/etc/pki.conf && source /etc/pki/pki.conf && echo $JNI_JAR_DIR` CP=${JNI_JAR_DIR}/jss4.jar CP=/usr/share/java/commons-codec.jar:${CP} +CP=/usr/share/java/commons-io.jar:${CP} CP=/usr/share/java/ldapjdk.jar:${CP} CP=/usr/share/java/${PRODUCT}/pki-nsutil.jar:${CP} CP=/usr/share/java/${PRODUCT}/pki-cmsutil.jar:${CP} -- 1.8.4.2