c++ 服务端 java 客户端 tcp ssl 双向认证

6 篇文章 0 订阅
JavaWeb的应用程序,会接受页面参数组装成自定义的XML协议格式发送给后端服务程序。C++服务端解析XML并执行命令。传送XML过程中可能会包含一些隐私数据,需要用SSL加密。我负责Java客户端。

准备:
在网上找了若干资料,大多是Java服务器C++客户端的,无奈只有用英文搜索些国外站点的资料。
1.下载openssl,做测试(Linux版本的安装)。
安装openssl
下载:openssl-0.9.7m.tar.gz
解压:tar xzvf openssl-0.9.7m.tar.gz
cd openssl-0.9.7m/
默认配置:./config
重新建立依赖关系:make depend
测试:make test
安装:make install
安装完成后就可以用openssl自带的s_server和s_client做测试了,很方便。

2.下载bcprov-jdk15-143.jar,早知道有这个东西就省事了。(BouncyCastle.org出品的Java开源SSL工具包)

步骤:
一、跑通Java客户端与C++ openssl双向通讯。
1.建立证书(注:需要输入信息的时候需要与CA生成时统一,但不能完全一致,姓名可以不一)
新建目录:mkdir openssl_demo2
拷贝 openssl-0.9.7m/apps/ 下的CA.sh、openssl.cnf 到 openssl_demo2 下
cd openssl_demo2

2.新建CA公私钥
./CA.sh -newca

该命令的工作:
创建目录:demoCA/ 、 demoCA/private/ 、 demoCA/certs/ 、demoCA/private/ 、demoCA/newcerts/
文件:demoCA/serial(写入“01”) 、demoCA/index.txt
创建CA公私钥:demoCA/cacert.pem、demoCA/private/cakey.pem

3.新建服务器端私钥+公钥签名请求
openssl req -newkey rsa:1024 -out serverreq.pem -keyout sslserverkey.pem

4.用CA私钥为服务端请求签名生成服务端证书
openssl ca -in serverreq.pem -out sslservercert.pem -config ./openssl.cnf

下面的步骤注意版本,java版本过低可能keytool做证书有问题。
OpenSSL 0.9.6m 17 Mar 2004
java -version -- Java(TM) SE Runtime Environment (build 1.6.0_03-b05)

5.生成客户端私钥并存入sslclient.keystore
keytool -genkey -alias sslclient -validity 365 -keyalg RSA -keysize 1024 -keystore sslclient.keystore -keypass 123456 -storepass 123456

6.从sslclient.keystore中提取客户端签名请求
keytool -certreq -alias sslclient -sigalg SHA1withRSA -file sslclient.csr -keypass 123456 -storepass 123456 -keystore sslclient.keystore

7.用CA私钥为服务端请求签名生成客户端证书
openssl ca -in sslclient.csr -out sslclient.crt -cert demoCA/cacert.pem -keyfile demoCA/private/cakey.pem -notext -config openssl.cnf

8.转换客户端证书格式
openssl x509 -in sslclient.crt -out sslclient.der -outform DER

9.sslclient.keystore导入根证书
keytool -imp ort -v -trustcacerts -alias ca_root -file demoCA/cacert.pem -storepass 123456 -keystore sslclient.keystore

10.sslclient.keystore导入客户端证书
keytool -imp ort -v -alias sslclient -file sslclient.der -keypass 123456 -storepass 123456 -keystore sslclient.keystore

11.运行程序
运行openssl自带测试服务端:openssl s_server -cert sslservercert.pem -key sslserverkey.pem -CAfile demoCA/cacert.pem -state -Verify 1 -ssl3
运行Java客户端(略)。

(上面的步骤参考了这篇文章:http://www.blogjava.net/alwayscy/archive/2009/02/03/85161.html
由于下面的问题,不能完全采用上面的方案。)

二、问题:由于证书的生产和颁发是由另外一个VC程序统一管理的,所以不可能为了我的Java程序而做一个额外的功能。证书颁发程序是将CA根证书、客户端证书和客户端私钥下发到系统某个目录下然后我去取用的。麻烦在程序生成的证书都是PEM格式的,不能直接导入Java的JKS库,需要做个转换。
1.在网上的几篇文章分别搜到些 PEM证书+私钥合并为pkcs12格式密钥库、pkcs12格式密钥库导入JKS密钥库、CA证书导入JKS密钥库的文章,于是整理了一下做了个小例子。

package change_format;

imp ort java.security.KeyStore;
imp ort java.security.Key;
imp ort java.security.cert.Certificate;
imp ort java.security.cert.CertificateFactory;
imp ort java.security.cert.X509Certificate;
imp ort java.io.*;
imp ort java.util.*;

public class ConvertPEMToJKS {
    // certificate store format
    public static final String PKCS12 = "PKCS12";

    public static final String JKS = "JKS";

    // PKCS12 keystore properties
    public static final String INPUT_KEYSTORE_FILE = "src/change_format/keystore.pkcs12";

    public static final String KEYSTORE_PASSWORD = "123456";

    // JKS output file
    public static final String OUTPUT_KEYSTORE_FILE = "src/change_format/keystore.jks";

    public static final String CACERT_FILE = "src/change_format/ca2.crt";

    public static final String PEMPRIKEY_FILE = "src/change_format/clientssl.pri";

    public static final String PEMPUBKEY_FILE = "src/change_format/clientssl.crt";

    public static void pktojks() {
        try {
            KeyStore inputKeyStore = KeyStore.getInstance("PKCS12");
            FileInputStream fis = new FileInputStream(INPUT_KEYSTORE_FILE);

            // If the keystore password is empty(""), then we have to set
            // to null, otherwise it won't work!!!
            char[] nPassword = null;
            if ((KEYSTORE_PASSWORD == null)
                    || KEYSTORE_PASSWORD.trim().equals("")) {
                nPassword = null;
            } else {
                nPassword = KEYSTORE_PASSWORD.toCharArray();
            }
            inputKeyStore.load(fis, nPassword);
            fis.close();

            // System.out.println("keystore type=" + inputKeyStore.getType());

            // ----------------------------------------------------------------------
            // get a JKS keystore and initialize it.
            KeyStore outputKeyStore = KeyStore.getInstance("JKS");
            outputKeyStore.load(null, "123456".toCharArray());
            // Now we loop all the aliases, we need the alias to get keys.
            // It seems that this value is the "Friendly name" field in the
            // detals tab <-- Certificate window <-- view <-- Certificate
            // Button <-- Content tab <-- Internet Options <-- Tools menu
            // In MS IE 6.

            Enumeration enume = inputKeyStore.aliases();
            while (enume.hasMoreElements()) // we are readin just on e
            // certificate.
            {
                String keyAlias = (String) enume.nextElement();
                // System.out.println("alias=[" + keyAlias + "]");
                if (inputKeyStore.isKeyEntry(keyAlias)) {
                    Key key = inputKeyStore.getKey(keyAlias, nPassword);
                    Certificate[] certChain = inputKeyStore
                            .getCertificateChain(keyAlias);
                    outputKeyStore.setKeyEntry(keyAlias, key, "123456"
                            .toCharArray(), certChain);
                }
            }
            FileOutputStream out = new FileOutputStream(OUTPUT_KEYSTORE_FILE);
            outputKeyStore.store(out, nPassword);
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void jksaddcacert() {
        try {
            FileInputStream cacertfile = new FileInputStream(CACERT_FILE);
            FileInputStream oldjks = new FileInputStream(OUTPUT_KEYSTORE_FILE);

            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) cf
                    .generateCertificate(cacertfile);
            cacertfile.close();

            KeyStore.TrustedCertificateEntry trustedEntry = new KeyStore.TrustedCertificateEntry(
                    cert);

            KeyStore outputKeyStore = KeyStore.getInstance("JKS");
            outputKeyStore.load(oldjks, "123456".toCharArray());
            oldjks.close();

            outputKeyStore.setEntry("ca_root", trustedEntry, null);
            FileOutputStream out = new FileOutputStream(OUTPUT_KEYSTORE_FILE);
            outputKeyStore.store(out, KEYSTORE_PASSWORD.toCharArray());
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final String localDir = "src\\change_format\\";

    public static void pemaddpk() {
        try {
            Runtime rt = Runtime.getRuntime();
            Process ps = null;
            String exeStr = "openssl pkcs12 -export -in " + localDir
                    + "clientssl.crt -inkey " + localDir
                    + "clientssl.pri -out " + localDir
                    + "keystore.pkcs12 -passout pass:123456";
            System.out.println(exeStr);

            ps = rt.exec(exeStr);
            ps.waitFor();

            int i = ps.exitValue();
            if (i == 0) {
                System.out.println("Convert PKCS12 Sucess!");
            } else {
                System.out.println("Convert PKCS12 Error!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ConvertPEMToJKS.pemaddpk();
        ConvertPEMToJKS.pktojks();
        ConvertPEMToJKS.jksaddcacert();
    }
}

2.如上例子中遇到一个问题,就是我程序所在的部署机将来需要安装openssl才能执行pemaddpk()方法中的openssl脚本;或者需要部署 人员额外对证书颁发程序颁发的证书做个转化。这两种方法都是比较繁琐的,终于找到BouncyCastle的开源SSL工具包可以直接读入PEM格式并导入JKS,真是Java做OpenSSL的福音啊。

将bcprov-jdk15-143.jar导入工作路径,我抽出了一小段代码,以后要用到SSL的话可以方便的调用。

SSLContextBuild.java :
imp ort java.io.FileInputStream;
imp ort java.io.FileReader;
imp ort java.io.IOException;
imp ort java.security.KeyPair;
imp ort java.security.KeyStore;
imp ort java.security.Security;
imp ort java.security.cert.Certificate;
imp ort java.security.cert.CertificateFactory;
imp ort java.security.cert.X509Certificate;
imp ort java.util.Properties;
imp ort javax.net.ssl.KeyManagerFactory;
imp ort javax.net.ssl.SSLContext;
imp ort javax.net.ssl.TrustManagerFactory;
imp ort org.bouncycastle.openssl.PEMReader;
imp ort org.bouncycastle.openssl.PasswordFinder;

public class SSLContextBuild {
    public static SSLContext getSSLContext() {
        // 获取证书路径
        Properties prop = new Properties();
        try {
            //这里的路径是放在javaweb应用程序的web-inf/class下的
            prop.load(SSLContextBuild.class.getClassLoader()
            .getResourceAsStream("SSLCertPath.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        SSLContext sslContext = null;
        try {
            // 设定Security的Provider提供程序
            Security
            .addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

            // 读入客户端证书
            PEMReader pr = new PEMReader(new FileReader(prop.get("client_cert")
            .toString()));
            X509Certificate cert = (X509Certificate) pr.readObject();
            pr.close();

            // 读入客户端私钥
            PEMReader kr = new PEMReader(new FileReader(prop.get(
            "client_privatekey").toString()), new PasswordFinder() {
                public char[] getPassword() {
                    return "".toCharArray();
                }
            });
            KeyPair key = (KeyPair) kr.readObject();
            kr.close();

            // 建立空JKS
            KeyStore ksKeys = KeyStore.getInstance("JKS");
            ksKeys.load(null, "123456".toCharArray());

            // 导入客户端私钥和证书
            ksKeys.setKeyEntry("clientkey", key.getPrivate(), "123456"
            .toCharArray(), new Certificate[] { cert });
            ksKeys.setCertificateEntry("clientcert", cert);

            // 导入根证书作为trustedEntry,待改进代码,此处没用bouncycastle的API,用了应该能简化点
            FileInputStream cacertfile = new FileInputStream(prop.get("cacert")
            .toString());

            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate cacert = (X509Certificate) cf
            .generateCertificate(cacertfile);
            cacertfile.close();

            KeyStore.TrustedCertificateEntry trustedEntry = new KeyStore.TrustedCertificateEntry(
            cacert);
            ksKeys.setEntry("ca_root", trustedEntry, null);

            // 构建KeyManager、TrustManager
            KeyManagerFactory kmf = KeyManagerFactory
            .getInstance(KeyManagerFactory.getDefaultAlgorithm());
            kmf.init(ksKeys, "123456".toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory
            .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(ksKeys);

            // 构建SSLContext
            sslContext = SSLContext.getInstance("SSL");
            sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        } catch (Exception e) {
            e.printStackTrace();
        }

        return sslContext;
    }
}

SSLCertPath.properties :
cacert=E:\\Ben\\ca2.crt
client_cert=E:\\Ben\\clientcert.crt
client_privatekey=E:\\Ben\\clientkey.pri

调用的时候:
        try {
            sslSocket = (SSLSocket) SSLContextBuild.getSSLContext()
                    .getSocketFactory()
                    .createSocket(HOST, PORT); //HOST like "192.168.1.X";PORT like 8888

            sslSocket.setEnabledProtocols(new String[] { "SSLv3" }); //注意此处要与你服务器端的程序设定一致。现在比较常用的协议是SSLv3/TLSv1。
        } catch (Exception e) {
            e.printStackTrace();

        }


文章比较老了,特别是pem 的读取,没有什么借鉴意义。不过思路可以考虑,所以转载下来。

C++ 中实现 HTTPS 服务器通常需要结合 SSL/TLS 协议,因为 HTTPS 是 HTTP 加上 SSL/TLS 的安全版本。这个过程包括几个关键步骤: 1. **库选择**:首先,你需要选择一个支持 SSL/TLS 的网络库,如 OpenSSL 或者 Boost.Asio。它们提供了必要的函数和套接字操作以处理加密通信。 2. **SSL上下文**:创建一个 SSL_CTX 对象,这是 OpenSSL 中处理 SSL/TLS 连接的基本结构。你需要初始化并设置证书、密钥和其他相关信息。 ```cpp #include <openssl/ssl.h> SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); ``` 3. **服务器配置**:根据你的需求配置服务器,例如监听特定的端口,设置最大连接数等。 4. **接受连接**:在套接字级别,通过 `accept()` 函数等待客户端连接,并创建一个新的 SSL 连接。 5. **处理请求**:一旦有新的 SSL 连接建立,你可以像处理普通的 TCP 连接一样读取和发送数据,只是所有数据都是经过 SSL/TLS 加密的。 6. **解析和响应**:使用 SSL API 解析HTTPS请求,获取HTTP头信息,然后根据请求内容构建响应并发送回客户端。 7. **关闭连接**:当通信结束时,记得关闭 SSL 连接和底层套接字。 以下是一个简单的示例代码片段(注意,这只是一个基础框架,实际应用中还需要错误处理和异常管理): ```cpp void handle_connection(SSL* ssl) { char buffer[1024]; while (true) { int len = SSL_read(ssl, buffer, sizeof(buffer)); if (len <= 0) break; // 如果读取失败或已关闭 // 处理接收到的数据... SSL_write(ssl, buffer, len); // 发送响应 } SSL_shutdown(ssl); SSL_free(ssl); } int main() { // ... 设置SSL上下文和服务器配置 ... listen(sockfd, SOMAXCONN); while (true) { SSL* ssl = SSL_new(ctx); SSL_set_fd(ssl, accept(sockfd, nullptr, nullptr)); // 创建SSL连接 handle_connection(ssl); SSL_free(ssl); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值