编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Nginx okhttp 双向验证 配置(将验证文件放置于您所配置域名的根目录下视频教程)

wxchong 2024-09-25 22:48:30 开源技术 9 ℃ 0 评论

最近在忙App安全,而双向验证是第一步,本文主要记录我在双向验证过程中遇到的坑,以及解决。虽然网上类似的教程贼多,但大部分基于 Tomcat… 小打小闹尚可,毕竟反代还是 Nginx 好。通过本文,你将顺利获知如何实现 android 与 nginx 的双向验证。

我们以一段 Nginx 的配置开始吧:

  1. server {
  2. listen 443 ssl;
  3. server_name www.kpromise.top;
  4. ssl_certificate /etc/letsencrypt/live/kpromise.top/fullchain.pem;
  5. ssl_certificate_key /etc/letsencrypt/live/kpromise.top/privkey.pem;
  6. ... // 此处省略 N 多配置

这里,域名是本站域名,另外配置了证书和key,这是普通的 https 配置了。接下来,我们看另外一个配置:

  1. server {
  2. listen 443 ssl;
  3. server_name api.kpromise.top;
  4. ssl_certificate /root/ssl/server.crt;
  5. ssl_certificate_key /root/ssl/server.key;
  6. ssl_password_file /root/ssl/password_file;
  7. ssl_client_certificate /root/ssl/ca.crt;
  8. ssl_verify_client optional_no_ca;
  9. location / {
  10. root /root/ssl/api/;
  11. }
  12. }

这是一段中规中矩的服务端验证客户端的配置,主要变化是多了 ssl_client_certificate 这行。具体请看 http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_client_certificate

那么,这里的ca.crt 又是如何产生的呢,下面进入主题,生成证书:

首先,生成 ca.key 接下来签名证书的时候会用到:

1、openssl genrsa -des3 -out ca.key 4096

接着,生成 ca.crt 文件,crt 文件是客户端认证的证书文件,同样的,在你签名证书的时候会用到

2、openssl req -new -x509 -days 365 -key ca.key -out ca.crt

3、生成证书:

  1. openssl genrsa -des3 -out server.key 1024
  2. openssl req -new -key server.key -out server.csr

会提示你输入 Common Name 一般是你的域名哦

4、用 ca.key 和 ca.crt 签名证书:

  1. openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

5、重复 3-4 两步,但是 -out 改为 client.x 来生成并签署另一份证书,比如:

  1. openssl genrsa -des3 -out client.key 1024
  2. openssl req -new -key client.key -out client.csr
  3. openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

6、修改 Nginx 配置为 我刚开始提供的第二份配置那样,即:

  1. server {
  2. listen 443 ssl;
  3. server_name api.kpromise.top;
  4. ssl_certificate /root/ssl/server.crt;
  5. ssl_certificate_key /root/ssl/server.key;
  6. ssl_password_file /root/ssl/password_file;
  7. ssl_client_certificate /root/ssl/ca.crt;
  8. ssl_verify_client on;
  9. location / {
  10. root /root/ssl/api/;
  11. }
  12. }

然后 把相关文件移入配置所列的目录,并执行 nginx -s reload,此时,服务端就会校验客户端证书了。这里的 ssl_password_file 对应的是一个普通文件,里面是 创建证书时设置的密码哦。

接下来,我们看下 android 端的修改。首先查看 android 支持 的 keystore 类型: https://developer.android.com/reference/java/security/KeyStore#summary 大致如下:

AndroidCAStore 14+

AndroidKeyStore 18+

BCPKCS12 1-8

BKS 1+

BouncyCastle 1+

PKCS12 1+

PKCS12-DEF 1-8

可以看得出,AndroidCAStore、PKCS12、BouncyCastle、BKS 都是不错的选择,但是我看网上资料,基本都说 转为 android 支持的类型 BKS 这又是什么鬼?明明有 PKCS12 可以选择啊。我们接下来导出 client.p12 以及 server.p12 文件,命令如下:

  1. openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "client"
  2. openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name "client"

然后把 client.p12 和 server.p12 都给android 客户端,android 客户端将他们放到 /src/main/assets/ 下面。最后,我们修改 okhttp 吧:

  1. class SSLHelper {
  2. private var clientPw: String? = null
  3. private var clientPKCSFileName: String = "client.p12"
  4. private var serverPKCSFileName: String = "server.p12"
  5. private var serverPw: String? = null
  6. private val protocolType = "TLS"
  7. private val keyStoreType: String = "PKCS12"
  8. private val certificateFormat: String = "X509"
  9. fun initClient(clientPKCSFileName: String, clientPw: String): SSLHelper {
  10. this.clientPKCSFileName = clientPKCSFileName
  11. this.clientPw = clientPw
  12. return this
  13. }
  14. fun initServer(serverPKCSFileName: String, serverPw: String): SSLHelper {
  15. this.serverPKCSFileName = serverPKCSFileName
  16. this.serverPw = serverPw
  17. return this
  18. }
  19. fun getSSLCertification(context: Context): SSLSocketFactory? {
  20. if (clientPw == null) throw RuntimeException("please call initClient first...")
  21. if (serverPw == null) throw RuntimeException("please call initServer first...")
  22. var sslSocketFactory: SSLSocketFactory? = null
  23. try {
  24. val clientKeyStore = KeyStore.getInstance(keyStoreType)
  25. val serverKeyStore = KeyStore.getInstance(keyStoreType)
  26. val clientPrivateKeyInputStream = context.assets.open(clientPKCSFileName)
  27. val serverPublicKeyInputStream = context.assets.open(serverPKCSFileName)
  28. clientKeyStore.load(clientPrivateKeyInputStream, clientPw?.toCharArray())
  29. serverKeyStore.load(serverPublicKeyInputStream, serverPw?.toCharArray())
  30. clientPrivateKeyInputStream.close()
  31. serverPublicKeyInputStream.close()
  32. val sslContext = SSLContext.getInstance(protocolType)
  33. val trustManagerFactory = TrustManagerFactory.getInstance(certificateFormat)
  34. val keyManagerFactory = KeyManagerFactory.getInstance(certificateFormat)
  35. trustManagerFactory.init(serverKeyStore)
  36. keyManagerFactory.init(clientKeyStore, clientPw?.toCharArray())
  37. sslContext.init(keyManagerFactory.keyManagers,
  38. trustManagerFactory.trustManagers, null)
  39. sslSocketFactory = sslContext.socketFactory
  40. } catch (e: Exception) {
  41. e.printStackTrace()
  42. }
  43. return sslSocketFactory
  44. }
  45. }
  46. fun getBuilder(showLog: Boolean): OkHttpClient.Builder {
  47. val builder = OkHttpClient.Builder()
  48. val sslSocketFactory = this.sslSocketFactory
  49. if (sslSocketFactory != null) {
  50. builder.sslSocketFactory(sslSocketFactory)
  51. }
  52. builder.hostnameVerifier { _, _ ->
  53. true
  54. }
  55. ... // 此处省略 N 多 代码

完美了,当然,hostnameVerifier 你也可以指定自己的域名啊。

遇到的天坑:

在生成 ca server 以及 client 时 会 要求输入 CN 即:Common Name (e.g. server FQDN or YOUR name) 这行,请注意这三处虽然都是域名,但千万别设一样,否则会出现 400 The SSL certificate error

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表