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

标签 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

我在这个网站上阅读了很多对我有帮助的问答,但我仍然找不到答案。

最佳答案

好的,我修改了上一个答案,使用 Bouncy CaSTLe v1.5 代替 OpenSSL。从 Bouncy CaSTLe 中找到未弃用的功能需要大量谷歌搜索。我仍然使用 Keygen HTML5 标签来为 Firefox 生成私钥。我只对使用 Firefox 感兴趣,但根据我的阅读,大多数其他网络浏览器应该可以使用。如果您使用此代码,请确保为每个证书设置不同的序列号,如果您尝试导入两个名称编号; 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;
    }

}

关于java - 如何以编程方式生成客户端身份验证证书,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22289124/

相关文章:

java - 在(使用 LWJGL 和 slick2D 的 java 游戏)中绘制字符串无法正常工作

windows - 在 RabbitMQ 中为 web stomp 配置 SSL?

ssl - 在 Windows 上查找私钥

ssl - 使用自签名证书是否可以防止中间人攻击?

ssl - 关于 HTTP 公钥固定 (HPKP) 的问题

java - 如何使用 Angular 方法将数据检索到 selenium java 脚本中

java - Android - EditText 不可编辑且不可选择

java - Spring - 显示反射(reflect)和更新 @ModelAttribute 字段的标签

java - 大文件的 Wiremock withBodyFile OutOfMemoryError

security - 数字证书 : signing vs verifying?