目录

TLS/https

安全通信在现代应用程序中起着重要作用。客户端和服务器之间通过纯 HTTP 进行的通信不安全。对于用于生产的应用程序,应在应用程序中通过TLS(传输层安全性)协议启用HTTPS。

HTTPS 即 HTTP over TLS。TLS为客户端与服务器之间的数据传输提供安全保护。安全套接层(SSL)和传输层安全性协议(TLS)通常可以互换使用,但它们并不相同,SSL是TLS的前身。 TLS 分为单向或双向认证。

在单向TLS中, 只有客户端验证服务器以确保客户端从受信任的服务器上接收数据。客户端共享服务器的公共证书。

双向 TLS (Mutual TLS,mTLS)中, 客户端和服务器端都需要验证对方的身份,以保证参与的双方都是可信的。mTLS中双方共享自己的公共证书。

在大多数情况下,当我们的应用程序需要通过 SSL/TLS 进行通信时,我们会使用 keystore 和 truststore。

  • Keystore 可以存放除了证书、公钥外其它敏感信息,比如密码、私钥……证书可以是关联的证书或者一个证书链。证书链有客户端证书和一个或多个CA证书。
  • Truststore 仅仅包含客户端信任的证书、公钥。它无法存放敏感信息。

通常,这些是受密码保护的文件,与我们正在运行的应用程序位于同一文件系统上。在 Java 8 之前,用于这些文件的默认格式是 JKS。

从 Java 9 开始,默认的 keystore 格式是 PKCS12。JKS 和 PKCS12 最大的区别在于 JKS 是 Java 特有的格式,而 PKCS12 是存储加密私钥和证书的标准化和语言中立的方式。

/images/security/tls/keystore.png
keystore

以Java程序为例,我们建立SSLContext时,需要生成KeyManager和TrustManager,对应的参数为javax.net.ssl.keyStore和javax.net.ssl.trustStore。而它们的作用正好体现了不同Store的作用:

  • TrustManager:决定对方来的cert是不是可信的;
  • KeyManager:决定自己发什么cert给对方。

如果不指定 TrustStore,默认是 $JAVA_HOME/jre/lib/security/cacerts 文件。可以通过命令查看这个文件都有什么 certs:

1
keytool -list -keystore cacerts

生成密钥对

要启用 TLS,我们需要创建一个公共/私有密钥对。使用 JDK 自带密钥工具 keytool。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 生成 PKCS#12 keystore JDK 17 执行 JDK 8 没有 HmacPBESHA256
keytool -genkeypair -alias ynthm-keystore -keyalg RSA -keysize 4096 \
  -validity 365 -dname "CN=localhost" -keypass changeit -keystore keystore.p12 \
  -storeType PKCS12 -storepass changeit
# 查看 keystore
keytool -list -v -keystore keystore.p12
# 导出公钥/证书
keytool -export -alias ynthm-keystore -keystore keystore.p12 -rfc -file pubkey.cer
# 导入证书到 truststore 文件中 提示创建密码
keytool -import -alias ynthm-truststore -file pubkey.cer -keystore truststore.p12
# 查看生成的 truststore 文件
keytool -list -v -keystore truststore.p12

# JDK 8
# jks keystore 文件
keytool -genkey -alias jks-keystore -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore keystore.jks -dname CN=localhost,OU=Test,O=pkslow,L=Guangzhou,C=CN -validity 365 -storepass changeit -keypass changeit
keytool -list -v -keystore keystore.jks
# 导出证书
keytool -export -alias jks-keystore -keystore keystore.jks -rfc -file selfsignedcert.cer
# 导入证书到 truststore 文件中 
keytool -import -alias jks-truststore -file selfsignedcert.cer -keystore truststore.jks
# 查看生成的 truststore 文件
keytool -list -v -keystore truststore.jks

keytool -genkeypair --help

JKS 密钥库使用专用格式。建议使用 “keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.jks -deststoretype pkcs12” 迁移到行业标准格式 PKCS12。

  • alias:密钥别名,放入 keystore 中不冲突就行;
  • keyalg:秘钥算法名称 RSA
  • keysize:密钥位大小,2048基本就不可能破解了
  • sigalg: 签名算法名称
  • keypass 密钥口令
  • keystore:密钥库名称
  • storepass 密钥库口令
  • storetype 密钥库类型 PKCS12
  • dname:这个很关键,特别是CN=后面要按正确的域名来写
  • validity:有效期天数
1
2
3
# To import a certificate into a PKCS12 keystore, we can also use openssl :
openssl pkcs12 -export -in ynthm.cer -inkey ynthm.key -out ynthm.keystore -name trustme
openssl pkcs12 -info -in ynthm.keystore

私钥是不能通过文件导出的,私钥文件只能通过代码来导出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static PublicKey getPublicKey(String certPath) {
    try (InputStream inputStream = Files.newInputStream(Paths.get(certPath))) {
      CertificateFactory certificatefactory = CertificateFactory.getInstance(X509);

      Certificate cert = certificatefactory.generateCertificate(inputStream);
      return cert.getPublicKey();
    } catch (IOException | CertificateException e) {
      throw new BaseException(e);
    }
  }

  public static Tuple2<PublicKey, PrivateKey> getFromKeyStore(
      KeyStoreType type, String storePath, String alias, String storePass, String keyPass) {
    try (FileInputStream is = new FileInputStream(storePath)) {
      KeyStore ks = KeyStore.getInstance(type.getType());
      ks.load(is, storePass.toCharArray());
      return Tuple2.of(
          ks.getCertificate(alias).getPublicKey(),
          (PrivateKey) ks.getKey(alias, keyPass.toCharArray()));

    } catch (IOException
        | KeyStoreException
        | UnrecoverableKeyException
        | CertificateException
        | NoSuchAlgorithmException e) {
      throw new BaseException(e);
    }
  }

Java 指定 keystore 和 truststore

1
2
3
4
5
# JVM启动参数
-Djavax.net.ssl.keyStore=keystore.jks
-Djavax.net.ssl.keyStorePassword=******
-Djavax.net.ssl.trustStore=truststore.jks
-Djavax.net.ssl.trustStorePassword=******

如果没有显示指定上述属性,那么 JVM 默认使用 $JAVA_HOME/lib/security/cacerts 文件作为 truststore,并读取其中的授信证书(默认密码为changeit)。

在 Spring 中配置单向 TLS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# enable/disable https
server:
  ssl:
    enabled: true
    protocol: TLS
    enabled-protocols: TLSv1.2
    # keystore format
    key-store-type: PKCS12
    key-store: classpath:keystore/keystore.p12
    key-store-password: changeit
 

因为这是自签名的cert,并不被Chrome所认可,所以会校验失败。以前的Chrome版本只是警告,但还是可以访问的,现在新版本的已经不能访问了。

1
curl --cacert ynthm.cer https://localhost:8443/demo

双向 mTLS

1
2
3
4
5
6
7
# enable/disable https
server:
  ssl:
    enabled: true
    client-auth: need
    trust-store: classpath:keystore/truststore.p12
    trust-store-password: changeit

Java 安全套接字扩展 (JSSE) 参考指南

JSSE Standard API The JSSE standard API, available in the javax.net and javax.net.ssl packages, provides:

  • Secure sockets tailored to client and server-side applications.
  • A non-blocking engine for producing and consuming streams of TLS/DTLS data (SSLEngine).
  • Factories for creating sockets, server sockets, SSL sockets, and SSL server sockets. By using socket factories, you can encapsulate socket creation and configuration behavior.
  • A class representing a secure socket context that acts as a factory for secure socket factories and engines.
  • Key and trust manager interfaces (including X.509-specific key and trust managers), and factories that can be used for creating them.
  • A class for secure HTTP URL connections (HTTPS).

OpenFeign 配置 https

在具体的 @FeignClient 上配置 configuration,此配置优先级次之,相同配置会被上一个覆盖。此类不能有 @Configuration 注解,否则会被全局扫描到,变成了全局配置,此外方法上必须标注 @Bean 才能生效。

1
2
3
4
  @Bean
  public Request.Options options() {
    return new Request.Options(10L, TimeUnit.SECONDS, 12L, TimeUnit.SECONDS, false);
  }

附录