如何以编程方式生成客户端身份验证证书

时间:2014-03-09 22:34:30

标签: java ssl

我想创建一个网站,让最终用户生成可以进行身份​​验证的证书。 我能够使用openssl使用这些命令创建客户端身份验证证书。

#Make client ssl cert.
CLIENT_PASSWORD="password"
CLIENT_ALIAS="client"

touch ./client.cnf
echo "
[ req ]
prompt = no
distinguished_name = req_distinguished_name
output_password = $CLIENT_PASSWORD

[ req_distinguished_name ]
localityName           = "L" # L=
organizationName       = "O" # O=
organizationalUnitName = "OU" # OU=
commonName             = "CN" # CN=
" > ./client.cnf

openssl req -config client.cnf -newkey rsa:2048 -keyout client.key -out client.csr
openssl ca -keyfile ca.key -cert ca.crt -out client.crt -policy policy_anything -days 3650 -batch -passin pass:$CLIENT_PASSWORD -infiles client.csr
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -passin pass:$CLIENT_PASSWORD -passout pass:$CLIENT_PASSWORD -name $CLIENT_ALIAS

我找到了Bouncy Castle v1.5,我找到了几个例子。我把它放在一起,但它没有提供可用的证书。

private static void MakeP12() throws Exception {
    Security.addProvider(new BouncyCastleProvider());

    String sigName = "SHA1withRSA";

    KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");

    kpg.initialize(2048);

    KeyPair kp = kpg.genKeyPair();

    X500NameBuilder x500NameBld = new X500NameBuilder(BCStyle.INSTANCE);

    x500NameBld.addRDN(BCStyle.C, "AU");
    x500NameBld.addRDN(BCStyle.ST, "Victoria");
    x500NameBld.addRDN(BCStyle.L, "Melbourne");
    x500NameBld.addRDN(BCStyle.O, "The Legion of the Bouncy Castle");

    X500Name subject = x500NameBld.build();

    PKCS10CertificationRequestBuilder requestBuilder = new JcaPKCS10CertificationRequestBuilder(subject, kp.getPublic());

    ExtensionsGenerator extGen = new ExtensionsGenerator();

    extGen.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(new GeneralName(GeneralName.rfc822Name, "feedback-crypto@bouncycastle.org")));

    requestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());

    PKCS10CertificationRequest req1 = requestBuilder.build(new JcaContentSignerBuilder(sigName).setProvider("BC").build(kp.getPrivate()));

    if (req1.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(kp.getPublic())))
    {
        System.out.println(sigName + ": PKCS#10 request verified.");
    }
    else
    {
        System.out.println(sigName + ": Failed verify check.");
    }

    PKCS10CertificationRequest csr = req1;




    FileInputStream is = new FileInputStream("D:/Sun/certs/ca.jks");
    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
    keystore.load(is, "password".toCharArray());

    PrivateKey cakey = (PrivateKey)keystore.getKey("my_ca", "password".toCharArray());
    X509Certificate cacert = (X509Certificate)keystore.getCertificate("my_ca");

    AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
    AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
    X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName());
    BigInteger serial = new BigInteger(32, new SecureRandom());
    Date from = new Date();

    int validity = 1;
    Date to = new Date(System.currentTimeMillis() + (validity * 86400000L));

    X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo());
    certgen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
    certgen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
    certgen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(cacert.getSubjectX500Principal().getName()))), cacert.getSerialNumber()));

    ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(cakey.getEncoded()));
    X509CertificateHolder holder = certgen.build(signer);

    /*===========================================================================*/

    PKCS12SafeBagBuilder eeCertBagBuilder = new JcaPKCS12SafeBagBuilder(new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( holder ));
    eeCertBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("Eric's Key"));

    JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
    SubjectKeyIdentifier pubKeyId = extUtils.createSubjectKeyIdentifier(kp.getPublic());
    eeCertBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);

    OutputEncryptor encOut = new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_CBC).setProvider("BC").build(JcaUtils.KEY_PASSWD);
    PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(kp.getPrivate(), encOut);

    keyBagBuilder.addBagAttribute(PKCS12SafeBag.friendlyNameAttribute, new DERBMPString("Eric's Key"));
    keyBagBuilder.addBagAttribute(PKCS12SafeBag.localKeyIdAttribute, pubKeyId);

    PKCS12PfxPduBuilder builder = new PKCS12PfxPduBuilder();
    builder.addData(keyBagBuilder.build());
    builder.addEncryptedData(new JcePKCSPBEOutputEncryptorBuilder(
            PKCSObjectIdentifiers.pbeWithSHAAnd128BitRC2_CBC).setProvider("BC").build(JcaUtils.KEY_PASSWD), 
            new PKCS12SafeBag[]{eeCertBagBuilder.build()});

    PKCS12PfxPdu pfx = builder.build(new JcePKCS12MacCalculatorBuilder(NISTObjectIdentifiers.id_sha256), JcaUtils.KEY_PASSWD);

    readPKCS12File(pfx, "password".toCharArray());
    /*===========================================================================*/

}

它也没有给我一致的错误信息。

    Exception in thread "main" org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: failed to construct sequence from byte[]: DER length more than 4 bytes: 79
    at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source)
    at cwguide.JcePKCS12Example.readPKCS12File(JcePKCS12Example.java:272)
    at cwguide.JcePKCS12Example.MakeP12(JcePKCS12Example.java:211)
    at cwguide.JcePKCS12Example.main(JcePKCS12Example.java:81)
Caused by: java.lang.IllegalArgumentException: failed to construct sequence from byte[]: DER length more than 4 bytes: 79
    at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
    at org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(Unknown Source)
    ... 4 more

Exception in thread "main" org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: unknown object in getInstance: org.bouncycastle.asn1.DERApplicationSpecific
    at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source)
    at cwguide.JcePKCS12Example.readPKCS12File(JcePKCS12Example.java:272)
    at cwguide.JcePKCS12Example.MakeP12(JcePKCS12Example.java:211)
    at cwguide.JcePKCS12Example.main(JcePKCS12Example.java:81)
Caused by: java.lang.IllegalArgumentException: unknown object in getInstance: org.bouncycastle.asn1.DERApplicationSpecific
    at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
    at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
    at org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(Unknown Source)
    ... 4 more

Exception in thread "main" org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: failed to construct sequence from byte[]: unknown tag 41 encountered
    at org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(Unknown Source)
    at cwguide.JcePKCS12Example.readPKCS12File(JcePKCS12Example.java:272)
    at cwguide.JcePKCS12Example.MakeP12(JcePKCS12Example.java:211)
    at cwguide.JcePKCS12Example.main(JcePKCS12Example.java:81)
Caused by: java.lang.IllegalArgumentException: failed to construct sequence from byte[]: unknown tag 41 encountered
    at org.bouncycastle.asn1.ASN1Sequence.getInstance(Unknown Source)
    at org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(Unknown Source)
    ... 4 more

我在这个网站上看过许多有帮助我的问答,但答案仍然没有找到。

2 个答案:

答案 0 :(得分:2)

好的,我重写了我的最后一个答案,使用Bouncy Castle v1.5代替OpenSSL。从Bouncy Castle找到非弃用的功能需要大量的Google搜索。我仍然使用Keygen HTML5标记来生成Firefox的私钥。我只对使用Firefox感兴趣,但从我读到的大多数其他Web浏览器应该可以工作。如果您使用此代码,请确保为每个证书设置不同的序列号,如果您尝试导入两个名称号; Firefox只是忽略了请求。

import java.io.*;
import java.math.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import org.bouncycastle.asn1.misc.*;
import org.bouncycastle.asn1.x500.*;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.*;
import org.bouncycastle.cert.jcajce.*;
import org.bouncycastle.jce.netscape.*;
import org.bouncycastle.jce.provider.*;
import org.bouncycastle.operator.*;
import org.bouncycastle.operator.jcajce.*;
import org.bouncycastle.util.encoders.*;

public class ExampleClientAuth extends HttpServlet {

    private static final long serialVersionUID = 5599842503981845987L;

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        out.println("<html>");
        out.println("<head>");
        out.println("<title>SSL Generator</title>");
        out.println("</head>");
        out.println("<body>");

        out.println("<form method=\"post\">");
        out.println("<keygen name=\"pubkey\" challenge=\"randomchars\">");
        out.println("Username: <input type=\"text\" name=\"username\" value=\"John Doe\">");
        out.println("<input type=\"submit\" name=\"createcert\" value=\"Generate\">");
        out.println("</form>");

        out.println("</body>");
        out.println("</html>");     
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String CN = "/var/lib/tomcat7/webapps/ROOT/WEB-INF/certs/";
        PrintWriter out = new PrintWriter(CN+"log.txt", "UTF-8");
        try {
            response.setContentType("application/x-x509-user-cert");
            OutputStream os=response.getOutputStream();
            String pubkey = request.getParameter("pubkey");
            pubkey = pubkey.replace("\n", "").replace("\r", "").replace("\t", "").replace("\0", "").replace("\u000B", "");
            String username = request.getParameter("username");

            BC_SingCert_Spkac(os,pubkey,username,out);

            os.close();
            os.flush();         
        } catch (Throwable t) {
            t.printStackTrace(out);
        } finally {
            out.close();
            out.flush();
        }
    }

    protected void BC_SingCert_Spkac(OutputStream os,String Spkac, String ID, PrintWriter log) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(new FileInputStream("/usr/share/tomcat7/bin/cacerts.jks"),
            "password".toCharArray());
        PrivateKey cakey = (PrivateKey)keystore.getKey("my_ca", "password".toCharArray());
        X509Certificate cacert = (X509Certificate)keystore.getCertificate("my_ca");

        X509Certificate ncert = createCertFromSpkac(cacert, cakey, Spkac, ID);

        byte[] buf = ncert.getEncoded();
        os.write(buf, 0, buf.length);
    }

    private X509Certificate createCertFromSpkac(X509Certificate cacert,
        PrivateKey caPrivKey, String spkacData, String ID) throws Exception {

        X500Name subject = new X500Name("CN=\""+ID+"\",OU=\"Organizational Unit\",O=\"Organizational\",L=\"City\",ST=\"California\",C=\"US\",E=\"email@example.com\"");
        X500Name issuer = JcaX500NameUtil.getIssuer(cacert);
        int VALIDITY_PERIOD = 365 * 24 * 60 * 60 * 1000; // one year
        Date startDate = new Date(System.currentTimeMillis());
        Date endDate = new Date(System.currentTimeMillis() + VALIDITY_PERIOD);
        String subjAltNameURI = "http://example.com";
        BigInteger serialNumber = BigInteger.valueOf(1000);

        PublicKey caPubKey = cacert.getPublicKey();
        NetscapeCertRequest netscapeCertReq = new NetscapeCertRequest(Base64.decode(spkacData));
        PublicKey certPubKey = netscapeCertReq.getPublicKey();

        X509v3CertificateBuilder certGenerator = new X509v3CertificateBuilder(
            issuer, 
            serialNumber, 
            startDate, 
            endDate, 
            subject, 
            SubjectPublicKeyInfo.getInstance(certPubKey.getEncoded())
        );
        // Adds the Basic Constraint (CA: false) extension.
        certGenerator.addExtension(Extension.basicConstraints, true,
            new BasicConstraints(false));

        // Adds the Key Usage extension.
        certGenerator.addExtension(Extension.keyUsage, true, new KeyUsage(
            KeyUsage.digitalSignature | KeyUsage.nonRepudiation
            | KeyUsage.keyEncipherment | KeyUsage.keyAgreement
            | KeyUsage.keyCertSign));

        // Adds the Netscape certificate type extension.
        certGenerator.addExtension(MiscObjectIdentifiers.netscapeCertType,
            false, new NetscapeCertType(NetscapeCertType.sslClient
            | NetscapeCertType.smime));

        // Adds the subject key identifier extension.
        SubjectKeyIdentifier subjectKeyIdentifier =  
            new JcaX509ExtensionUtils().createSubjectKeyIdentifier(certPubKey);
        certGenerator.addExtension(Extension.subjectKeyIdentifier, false,
            subjectKeyIdentifier);

        // Adds the subject alternative-name extension (critical).
        if (subjAltNameURI != null) {
            GeneralNames subjectAltNames = new GeneralNames(new GeneralName(
                GeneralName.uniformResourceIdentifier, subjAltNameURI));
            certGenerator.addExtension(Extension.subjectAlternativeName,
                false, subjectAltNames);
        }

        // Creates and sign this certificate with the private key corresponding
        // to the public key of the certificate 

        ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(caPrivKey);

        X509CertificateHolder holder = certGenerator.build(signer);
        X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);

        // Checks that this certificate has indeed been correctly signed.
        cert.verify(caPubKey);

        return cert;
    }

}

答案 1 :(得分:-1)

好的,我找到了合适的答案。我在openssl中使用了ProcessBuilder,并使用HTML标签“keygen”生成了客户端密钥。我从中得到了这个想法 http://www.scriptjunkie.us/2013/11/adding-easy-ssl-client-authentication-to-any-webapp/ 这个例子使v1 SSL证书我将在下面看看如何用v3做这个。

class RunProcessBuilder {
    public File path;
    public PrintWriter out;
    public RunProcessBuilder(File p, PrintWriter o) throws Exception {
        path = p;
        out = o;
    }
    public void Run(String args[]) throws Exception {
        out.printf("Output of running %s is:",Arrays.toString(args));
        out.flush();

        ProcessBuilder pb = new ProcessBuilder(args);
        Map<String, String> env = pb.environment();
        env.put("HOME", "/var/lib/tomcat7/webapps/ROOT/WEB-INF/certs/");        
        env.put("RANDFILE", "/var/lib/tomcat7/webapps/ROOT/WEB-INF/certs/demoCA/.rnd");     
        pb.directory(path);
        pb.redirectErrorStream(true);

        Process shell = pb.start();
        InputStream shellIn = shell.getInputStream();
        int shellExitStatus = shell.waitFor();
        int c;
        while ((c = shellIn.read()) != -1) { out.write(c); out.flush();}
        try {shellIn.close();} catch (IOException ignoreMe) { out.write(ignoreMe.toString()); out.flush();}

    }
}

public class ClientCert2 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        out.println("<html>");
        out.println("<head>");
        out.println("<title>Example</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");

        out.println("<form method=\"post\">");
        out.println("<keygen name=\"pubkey\" challenge=\"randomchars\">");
        out.println("Username: <input type=\"text\" name=\"username\" value=\"Sam Sanders\">");
        out.println("<input type=\"submit\" name=\"createcert\" value=\"Generate\">");
        out.println("</form>");

        out.println("</body>");
        out.println("</html>");     

    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/x-x509-user-cert");

        String CN = "/var/lib/tomcat7/webapps/ROOT/WEB-INF/certs/";
        PrintWriter out = new PrintWriter(CN+"log.txt", "UTF-8");
        try {
            String ClientID = "CilentX";
            String pass = "password";
            String pubkey = request.getParameter("pubkey");
            String username = request.getParameter("username");

            PrintWriter writer = new PrintWriter(CN+ClientID+".spkac", "UTF-8");
            writer.println("SPKAC="+pubkey.replace("\n", "").replace("\r", "").replace("\t", "").replace("\0", "") );
            //.replace("\x0B", "")

            //username needs to be unique
            writer.println("CN="+username);
            writer.println("emailAddress=test@test.com");
            writer.println("0.OU=test client certificate");
            writer.println("organizationName=organizationName");
            writer.println("countryName=AU");
            writer.println("stateOrProvinceName=State");
            writer.println("localityName=City");
            writer.close();
            writer.flush();

            writer = new PrintWriter(CN+ClientID+".cnf", "UTF-8");
            writer.println("[ ca ]");
            writer.println("default_ca      = CA_default");
            writer.println("[ CA_default ]");
            writer.println("dir            = "+CN);
            writer.println("database       = "+CN+"demoCA/index.txt");
            writer.println("new_certs_dir  = "+CN+"demoCA/newcerts");
            writer.println("certificate    = "+CN+"ca.crt");
            writer.println("serial         = "+CN+"demoCA/serial");
            writer.println("private_key    = "+CN+"ca.key");
            writer.println("RANDFILE       = "+CN+"demoCA/.rnd");
            writer.println("HOME           = "+CN);
            writer.println("default_days   = 3650");
            writer.println("default_crl_days= 60");
            writer.println("default_md     = sha1");
            writer.println("policy         = policy_any");
            writer.println("email_in_dn    = yes");
            writer.println("name_opt       = ca_default");
            writer.println("cert_opt       = ca_default");
            writer.println("copy_extensions = none");
            writer.println("[ policy_any ]");
            writer.println("countryName            = supplied");
            writer.println("stateOrProvinceName    = optional");
            writer.println("organizationName       = optional");
            writer.println("organizationalUnitName = optional");
            writer.println("commonName             = supplied");
            writer.println("emailAddress           = optional");
            writer.close();
            writer.flush();

            RunProcessBuilder x = new RunProcessBuilder(new File(CN), out);
            x.Run(new String[] {"openssl","ca","-config",CN+ClientID+".cnf","-days","365","-notext","-batch","-spkac",CN+ClientID+".spkac","-passin","pass:"+pass,"-out",CN+ClientID+".crt"});

            InputStream is=new BufferedInputStream(new FileInputStream(new File(CN+ClientID+".crt")));
            OutputStream os=response.getOutputStream();
            byte[] buf = new byte[1024];
            for (int nChunk = is.read(buf); nChunk!=-1; nChunk = is.read(buf)) {
                os.write(buf, 0, nChunk);
            } 
            os.close();
            os.flush();
        } catch (Throwable t) {
            t.printStackTrace(out);
        }
        out.close();
        out.flush();
    }

}