文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

如何轻松地设置双向TLS保护应用程序安全

2024-12-03 03:40

关注

【51CTO.com快译】在安全实践领域,TLS身份验证作为一种技术手段,通常能够保证任何用户通过证书,浏览到真实、安全的Web应用。而在此基础上发展而来的双向TLS(Two-Way TLS),则可以仅允许部分用户访问或调用目标应用。

下面,我将以示例的形式,并配合自动化脚本,依次向您展示:搭建服务器,向服务器发送未加密的hello消息,以HTTPS的方式在服务器上启用单向TLS,要求客户端通过双向TLS来标识自己,基于可信的CA(证书颁发机构)实现双向TLS,以及对HTTP客户端进行相关测试。

基本定义

实用链接

通过如下的统一参考页,我向社区里正在使用Apache http、Java、Kotlin、以及Scala等开发人员,提供了包含40多个http客户端的配置示例。

在处理http请求的过程中,它们可能会导致应用在初始构建时,需要花费一定的时间来下载大量依赖项。因此,我也通过GitHub - SSLContext Kickstart(https://github.com/Hakky54/sslcontext-kickstart)来简化客户端的配置。由于每一个http客户端都可能需要不同的ssl对象来启用ssl,因此代码库需要提供基本的ssl配置。

启动服务器

首先,我们需要做好如下准备:

如果您想在不安装任何软件的情况下,立即开始体验该项目,请点击链接,并通过在线开发环境的方式打开此项目。

由于该项目已经包含了一个maven包装器(wrapper),因此您可以在无需额外安装的情况下,运行该项目。同时,下面将涉及到的各种包含了maven包装器的命令,都已被默认包含在mvn命令中。

如果您想使用Java 8来运行该项目,则可以使用git命令:git checkout tags/java-8-compatible,来运行一个兼容的版本。有关参数的具体设置细节,请参见链接--https://github.com/Hakky54/mutual-tls-ssl/tree/java-8-compatible。

为了启动服务端,您可以在服务端的项目中运行App类的main方法,或者在终端的根目录下运行命令:cd server/ && mvn spring-boot:run,以及使用maven包装器:cd server-with-spring-boot/ && ./../mvnw spring-boot:run。

向服务器发送未加密的hello

由于当前运行在默认端口8080上的服务器端是未经加密的,因此您可以在终端中使用以下curl命令:curl -i -XGET http://localhost:8080/api/hello,来调用hello:

其响应内容如下(纯文本):

  1. HTTP/1.1 200 
  2. Content-Type: text/plain;charset=UTF-8 
  3. Content-Length: 5 
  4. Date: Sun, 11 Nov 2018 14:21:50 GMT 
  5.  
  6. Hello 

您还可以使用客户端目录中所提供的客户端应用,去调用服务器。由于客户端依赖于本项目的其他组件,所以您需要先在根目录下运行mvn install或./mvnw install。

此处的客户端是基于Cucumber的集成测试。您可以通过从IDE处运行ClientRunnerIT类、或从根目录中的终端运行:cd client/ && mvn exec:java、亦或使用maven的包装器命令:cd client/ && ./../mvnw exec:java,来启动之。您可以在客户端项目的测试资源中,通过Hello.feature文件,来获悉集成测试的具体步骤。

为了同时运行服务器和客户端中的方法,您可以在根目录中使用命令:mvn clean verify,或使用maven的包装器:./mvnw clean verify。如果服务端与客户端同处一台服务器,那么客户端会默认向localhost发送请求;如果它们在不同的主机上运行,您需要为客户端提供带有VM参数:-Durl=http://[HOST]:[PORT]的定制化的url。

在服务器上启用 HTTPS(即单向的TLS)

下面,我们来讨论如何通过启用TLS,来保护服务器端。您可以通过将如下所需的属性(YAML),添加到名为application.yml的应用属性文件中来实现:

  1. server: 
  2. port: 8443 
  3. ssl: 
  4.     enabled: true 

在此,您可能会对为何将端口设置为 8443表示疑惑。其原因在于:带有https的tomcat服务的约定端口为8443,而对于http则是8080。虽然我们可以使用端口8080进行https连接,但这并不是一种推荐的做法。具体有关端口约定的详细信息,请参阅维基百科的链接

您可以通过重启服务器,来生效那些对于应用的更改。当然,您也可能会收到异常信息:IllegalArgumentException: Resource location must not be null。该消息的产生,是因为服务器需要带有服务器证书的密钥库,以确保与外界的安全连接。如果您提供的VM参数为:Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake,那么服务器可以为您提供更多的信息。

显然,为了解决此问题,您需要创建一个带有服务器公钥和私钥的密钥库。其中的公钥可与用户共享,以便加密彼此之间的通信;而服务器的私钥则可用来解密。值得注意的是,我们绝对不可以共享服务器的私钥,以避免被其他人用来破解截获到的通信,进而获悉被加密的通信内容。

因此,若要创建带有公钥和私钥的密钥库,请在终端中执行以下命令(纯文本):

  1. keytool -v -genkeypair -dname "CN=Hakan,OU=Amsterdam,O=Thunderberry,C=NL" -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias server -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi.local,IP:12 0.1 

为了告知服务器密钥库的位置,以及具体的密码,请将如下YAML内容粘贴到您的application.yml文件中:

  1. server: 
  2.   port: 8443 
  3.   ssl: 
  4.     enabled: true 
  5.     key-store: classpath:identity.jks 
  6.     key-password: secret 
  7.     key-store-password: secret 

至此,您已成功启用了服务器和客户端之间的TLS加密连接。您可以尝试着使用curl命令:curl -i --insecure -v -XGET https://localhost:8443/api/hello,去调用服务器。

当您在ClientRunnerIT类中运行客户端时,您可能会看到一条错误消息:java.net.ConnectException: Connection refused (Connection refused)。从字面上看,它是指客户端试图向服务器建立连接,可以被拒绝了。其深层原因是:客户端试图使用的是端口8080,而服务器只在端口8443上处于活跃状态。因此,您需要进行如下修改,并将其应用到Constants类中,即从:

  1. private static final String DEFAULT_SERVER_URL = "http://localhost:8080"

改为:

  1. private static final String DEFAULT_SERVER_URL = "https://localhost:8443"

在完成修改之后,让我们再次运行客户端。您会看到另一条消息:“javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target”。这意味着客户端希望通过HTTPS进行通信,但是在它握手的过程中,收到了无法识别的服务器证书。可见,您还需要创建一个包含了各种受信任证书的信任库,以方便客户端在SSL握手过程中,将收到的证书与其信任库里的证书内容进行比较。如果相匹配的话,则可以继续SSL的握手过程。当然,在创建信任库之前,您需要事先获得服务器的证书。

导出服务器的证书

您可以使用如下命令,导出服务器的证书:

  1. keytool -v -exportcert -file shared-server-resources/src/main/resources/server.cer -alias server -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -rfc 

接着,您可以为客户端创建一个信任库,并使用如下命令导入服务器的证书:

  1. keytool -v -importcert -file shared-server-resources/src/main/resources/server.cer -alias server -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt 

为了让客户端知晓信任库的存在,您还需要告知其信任库的正确位置、密码、以及身份验证已启用。您可以在客户端的application.yml文件中,提供如下属性:

  1. client: 
  2.   ssl: 
  3.     one-way-authentication-enabled: true 
  4.     two-way-authentication-enabled: false 
  5.     trust-store: truststore.jks 
  6.     trust-store-password: secret 

对客户端进行身份验证(双向TLS)

接下来,服务器端需要验证客户端的身份,以判断其是否可信。其实现方式为:通过client-auth属性放入服务器的application.yml中,以告知服务器去验证客户端。

  1. server: 
  2.   port: 8443 
  3.   ssl: 
  4.     enabled: true 
  5.     key-store: classpath:identity.jks 
  6.     key-password: secret 
  7.     key-store-password: secret 
  8.     client-auth: need 

当然,如果您直接运行它,则会因为客户端根本没有证书,而产生错误消息:javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate(无效的证书信息)。因此,我们需要通过如下命令,来创建证书:

  1. keytool -v -genkeypair -dname "CN=Suleyman,OU=Altindag,O=Altindag,C=NL" -keystore client/src/test/resources/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -别名客户端 -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth 

同时,您还需要为服务器创建一个信任库。不过,在创建信任库之前,您需要通过如下命令获取客户端的证书:

  1. keytool -v -exportcert -file client/src/test/resources/client.cer -alias client -keystore client/src/test/resources/identity.jks -storepass secret -rfc 

下一步便是使用客户端的证书,来创建服务器的信任库:

  1. keytool -v -importcert -file client/src/test/resources/client.cer -alias client -keystore shared-server-resources/src/main/resources/truststore.jks -storepass secret -noprompt 

同样,为了让客户端获悉该密钥库的存在,您还需要告知其信任库的正确位置、密码、以及身份验证已启用。您可以在客户端的application.yml文件中,提供如下属性:

  1. client: 
  2.   ssl: 
  3.     one-way-authentication-enabled: false 
  4.     two-way-authentication-enabled: true 
  5.     key-store: identity.jks 
  6.     key-password: secret 
  7.     key-store-password: secret 
  8.     trust-store: truststore.jks 
  9.    trust-store-password: secret 

对应地,为了让服务器知晓新创建的信任库,我们需要将当前属性替换为以下属性:

  1. server: 
  2.   port: 8443 
  3.   ssl: 
  4.     enabled: true 
  5.     key-store: classpath:identity.jks 
  6.     key-password: secret 
  7.     key-store-password: secret 
  8.     trust-store: classpath:truststore.jks 
  9.     trust-store-password: secret 
  10.    client-auth: need 

至此,您已完成了双向TLS的安装。如果再次运行客户端,您将会发现客户端能够以安全的方式,从服务器端接收到hello消息了。

基于可信CA的双向TLS

有了前面的基础,我们便可以采用基于可信CA的双向(mutual)认证了。我们首先来看看它的优缺点:

优点

缺点

其具体实现步骤如下:

1. 创建CA

通常,您需要向某个已有的证书颁发机构,提供自己的证书以获取其签名。下面,我们将创建一个自己的CA,并用它去签发客户端和服务器的证书。

  1. keytool -v -genkeypair -dname "CN=Root-CA,OU=Certificate Authority,O=Thunderberry,C=NL" -keystore root-ca/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias root-ca -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3 

当然,您也可以使用存储库默认提供的那个,具体请参阅identity.jks

2. 创建证书签名请求

为了签发证书,您需要通过如下命令,提供一个证书签名请求 (.csr) 文件。其中,服务器的证书签名请求为:

  1. keytool -v -genkeypair -dname "CN=Root-CA,OU=Certificate Authority,O=Thunderberry,C=NL" -keystore root-ca/identity.jks -storepass secret -keypass secret -keyalg RSA -keysize 2048 -alias root-ca -validity 3650 -deststoretype pkcs12 -ext KeyUsage=digitalSignature,keyCertSign -ext BasicConstraints=ca:true,PathLen:3 

而客户端的证书签名请求为:

  1. keytool -v -certreq -file client/src/test/resources/client.csr -keystore client/src/test/resources/identity.jks -alias client -keypass secret -storepass secret -keyalg rsa 

3. 使用证书签名请求签发证书

签发客户证书:

  1. keytool -v -gencert -infile client/src/test/resources/client.csr -outfile client/src/test/resources/client-signed.cer -keystore root-ca/identity.jks -storepass secret -alias root-ca -validity 3650 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -rfc 

签发服务器证书:

  1. keytool -v -gencert -infile shared-server-resources/src/main/resources/server.csr -outfile shared-server-resources/src/main/resources/server-signed.cer -keystore root-ca/identity.jks -storepass secret -alias root-ca -validity 3650 -ext KeyUsage=digitalSignature,dataEncipherment,keyEncipherment,keyAgreement -ext ExtendedKeyUsage=serverAuth,clientAuth -ext SubjectAlternativeName:c=DNS:localhost,DNS:raspberrypi.local,IP:127.0.0.1 -rfc 

4. 用已签名的证书替换未签名的证书

由于我们无法直接用密钥工具(keytool)去导入已签名的证书,因此我们需要将由CA签发的证书存储到identity.jks中。先导出CA证书:

  1. keytool -v -exportcert-文件root-ca / root-ca.pem -alias root-ca -keystore root-ca / identity.jks -storepass secret -rfc 

然后是客户端:

  1. keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore client/src/test/resources/identity.jks -storepass secret -noprompt 
  2. keytool -v -importcert -file client/src/test/resources/client-signed.cer -alias client -keystore client/src/test/resources/identity.jks -storepass secret 
  3. keytool -v -delete -alias root-ca -keystore client/src/test/resources/identity.jks -storepass secret 

最后是服务器端:

  1. keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret -noprompt 
  2. keytool -v -importcert -file shared-server-resources/src/main/resources/server-signed.cer -alias server -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret 
  3. keytool -v -delete -alias root-ca -keystore shared-server-resources/src/main/resources/identity.jks -storepass secret 

5. 设置仅信任CA

为了将客户端和服务器配置为仅信任某个CA,我们需要通过将CA证书导入客户端和服务器的信任库来实现。其中在客户端,我们可以使用如下操作命令:

  1. keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore client/src/test/resources/truststore.jks -storepass secret -noprompt 

在服务器端则为:

  1. keytool -v -importcert -file root-ca/root-ca.pem -alias root-ca -keystore shared-server-resources/src/main/resources/truststore.jks -storepass secret -noprompt 

同时,由于信任库仍包含客户端和服务器的原有特定证书,因此我们需要将其删除。其中在客户端,我们可以使用如下操作命令:

  1. keytool -v -delete -alias server -keystore client/src/test/resources/truststore.jks -storepass secret 

在服务器端则为:

  1. keytool -v -delete -alias client -keystore shared-server-resources/src/main/resources/truststore.jks -storepass secret 

至此,如果您再次运行客户端,将能够顺利通过测试。客户端将会接收到来自服务器的hello消息,而且其中的证书是由该CA所颁发的。

带有TLS身份验证的自动化脚本

其实,您还可以使用该项目的脚本目录里的各种脚本,来自动化执行上述步骤。例如,对于单向认证而言,可以输入:./configure-one-way-authentication;而对于双向认证而言,则可以输入:./configure-two-way-authentication-by-trusting-each-other my-company-name;对于通过可信CA进行的双向身份验证,可以输入:./configure-two-way-authentication-by-trusting-root-ca my-company-name。

已测试的客户端

下面是已经通过测试的客户端列表。您可以在ClientConfig类中找到基于纯Java的http客户端配置。该服务目录包含了单个的http客户端请求示例。其中,基于Kotlin和Scala的http客户端配置是作为嵌套类被包含在内的。而且,所有客户端示例都使用的是在SSLConfig类中创建的相同的ssl基本配置。

Java

Kotlin

Scala

原文How to Easily Set Up Mutual TLS,作者:Hakan Altındağ

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】

 

来源:51CTO内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯