1.引言

CA是Certificate Authority的缩写,也叫“证书授权中心”。它是负责管理和签发证书的第三方机构,作用是检查证书持有者身份的合法性,并签发证书,以防证书被伪造或篡改。

CA证书即由CA所签发的证书。

SSL双向认证需要服务端与客户端提供身份认证,只能是服务端允许的客户能去访问,安全性相对于要高一些。就是只有安装了由CA证书颁发的用户证书的机器才能进行系统的访问。

2.实现配置

接上一遍博文:HTTPS从了解到掌握(一):证书生成及启用HTTPS 单向认证中的Nginx配置,一般配置都使用购买得到的证书文件,除此之外需要追加两个配置项

ssl_verify_client on;  #开户客户端证书验证 
ssl_client_certificate /Users/kirra/Desktop/test.pem; #可以验证签发过的客户端证书,一般配置根证书

3.用户证书生成代码详解

3.1.KeyStore

首先我们看一下证书存储对象KeyStore

/**
 * This class represents a storage facility for cryptographic
 * keys and certificates
**/

其中类方法setKeyEntry(String alias, Key key, char[] password, Certificate[] chain),定义证书相关的信息

/**
     * @param alias the alias name
     * @param key the key to be associated with the alias
     * @param password the password to protect the key
     * @param chain the certificate chain for the corresponding public
**/

3.2.生成证书参数

在方法中我们有个步骤是必须做的,就是先引入Bouncy Castle:Bouncy Castle(轻量级密码术包)是一种用于 Java 平台的开放源码的轻量级密码术包;它支持大量的密码术算法,并提供JCE 1.2.1的实现。为了启用它,在Maven项目我们需要引用bcprov-jdk15on,并且动态加入。

<dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk15on</artifactId>
            <version>1.65</version>
        </dependency>
static {
        Security.addProvider(new BouncyCastleProvider());
    }

使用xca不能生成priavte文件,因此我们需要通过可生成的文件转化为priavte文件类,因此有

public static PrivateKey generatePrivate(String pemfilesourceurl)
    {
        try
        {
            //使用PemReader类读取
            PemReader pemReader = new PemReader(new InputStreamReader(new ClassPathResource(pemfilesourceurl).getInputStream()));
            //获取encoded的pem信息
            PemObject pemObject = pemReader.readPemObject();
            byte[] pemcontent = pemObject.getContent();
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            //当证书提供方提供的是一个pem文件时候如何通过java生成对应的PrivateKey类
            return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pemcontent));
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return null;
        }
    }

为了生成证书链路Certificate[] chain,我们需要使用到X509CertInfo,证书的主体信息由该类进行封装

X509CertInfo info = new X509CertInfo();
            info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
            info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new java.util.Random().nextInt() & 0x7fffffff));
            info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(AlgorithmId.get("SHA256withRSA")));
            info.set(X509CertInfo.SUBJECT, subject);
            info.set(X509CertInfo.KEY, new CertificateX509Key(subjectKeyPair.getPublic()));
            info.set(X509CertInfo.VALIDITY, validity);
            info.set(X509CertInfo.ISSUER, x509Certificate.getIssuerDN());

 

各信息为:X509CertInfo.VERSION 证书版本号            

X509CertInfo.SERIAL_NUMBER 证书序列号            

X509CertInfo.ALGORITHM_ID 签名算法            

X509CertInfo.SUBJECT 证书使用者信息,由工具方法createSubject来生成,其中信息可以根据实际需要进行自定义  

/**
     * 创建证书使用者信息
     * @param CN comonName
     * @param OU organizationalUnitName
     * @param O organizationName
     * @param C countryName
     * @param L localityName
     * @param ST stateOrProvinceName
     * @return
     * @throws IOException
     */
    public static X500Name createSubject(String CN,String OU,String O,String C,String L,String ST) throws IOException {
        X500Name subject = new X500Name(new StringBuilder()
                .append("CN=").append(CN)
                .append(",OU=").append(OU)
                .append(",O=").append(O)
                .append(",C=").append(C)
                .append(",L=").append(L)
                .append(",ST=").append("abc").toString());
        return subject;
    }

         

X509CertInfo.KEY 公钥算法            

X509CertInfo.VALIDITY 有效期限,由工具方法createValidity来生成,其中信息可以根据实际需要进行自定义

public static CertificateValidity createValidity(Integer startYear, Integer blank) throws Exception {
        if(blank<1)
            throw new Exception("间隔不能少于1");
        Calendar calendar = Calendar.getInstance();
        calendar.set(startYear,0,1,0,0);
        Date firstDate = calendar.getTime();
        calendar.set(startYear+blank,0,1,0,0);
        Date lastDate = calendar.getTime();
        return new CertificateValidity(firstDate, lastDate);
    }

           

X509CertInfo.ISSUER 颁发者,颁发者必须与CA证书信息一致才能通过双向认证,因此该参数由CA证书提供

3.3 输出用户证书

利用CA证书对用户证书签名后就可以输出用户证书文件

X509Certificate certificate = (X509Certificate) cert;
            X509Certificate[] X509Certificates = new X509Certificate[]{certificate};
            // 生成用户安装证书并输出到指定路径
            KeyStore keyStore = KeyStore.getInstance("pkcs12");
            keyStore.load(null, null);
            keyStore.setKeyEntry("test",
                    subjectKeyPair.getPrivate(),
                    "123456".toCharArray(),
                    X509Certificates);
            File file = new File(userPFXfilePath);
            if (file.exists()) file.delete();
            FileOutputStream fos = new FileOutputStream(userPFXfilePath);
            keyStore.store(fos, "123456".toCharArray());
            fos.close();

4.结果

4.1 选用证书

java ssl国密双向认证 ssl双向证书_java ssl国密双向认证

java ssl国密双向认证 ssl双向证书_HTTPS_02

wo

java ssl国密双向认证 ssl双向证书_HTTPS_03

4.2 不选用证书

java ssl国密双向认证 ssl双向证书_java ssl国密双向认证

java ssl国密双向认证 ssl双向证书_双向认证_05

java ssl国密双向认证 ssl双向证书_bc_06

参考:1.SSL双向认证和SSL单向认证的区别

2.Https单向认证和双向认证

3.HTTPS双向认证指南

GitHub:https://github.com/tale2009/nginx-using/tree/master/CA-Demo