近期公司上线APP安全部门要求要对APP进行安全扫描,结果要求必须有SSL证书,没办法我们只能增加证书,公司为了省钱,我们只能自建证书,你懂的,毕竟APP并没有网站要求那么高,只要本身做好安全策略就问题不大,废话不多说,直接上干货。
一、单项认证
说白了就是前面是,https的,都是单向认证的 ,操作系统或者浏览器一般都内置了一堆相关CA的证书,所以可以直接访问;若是自签发的,浏览器会提示该证书不受信任。适用场景是站点访问,非高机密数据传输。
二、双向认证
就是在单向基础上,添加上了服务端要校验客户端的公钥,客户端要自己保存着自己的私钥来加密,客户端发过来的请求要用该私钥来加密后,才能跟服务器进行完整通信。按照小白的理解,就是分别需要客户端和服务器端的证书。
三、nginx
现在基本上大部分公司都不会把自己的ip地址暴露在外网系统。这样可以在域名跳转的时候进行访问拦截,双向认证时,不用修改后台最简单的办法就是在nginx层面增加服务端证书。
四、openssl
随便找了个安装教程网站, https://blog.csdn.net/qq_46550964/article/details/111507119
五、证书生成
方案一: 用CA签的证书(有收费/免费),这样浏览器就不会显示不信任提示,例如:阿里云、腾讯、360都有相关的证书申请。
方案二: 用openssl在本机,制作自签发的证书,浏览器也可以访问,但会有不信任提示。
本次使用个人签发证书,需要生成客户端(client)与服务端(server)证书。
1、生成服务端key
openssl genrsa -out server-key.key 1024
2、生成服务端证书请求文件
openssl req -new -out server-req.csr -key server-key.key
会出现如下信息:
PS E:\> openssl req -new -out client-req.csr -key client-key.key
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter ‘.’, the field will be left blank.
—–
Country Name (2 letter code) [AU]:国籍
State or Province Name (full name) [Some-State]:省会
Locality Name (eg, city) []:城市
Organization Name (eg, company) [Internet Widgits Pty Ltd]:公司
Organizational Unit Name (eg, section) []:组织
Common Name (e.g. server FQDN or YOUR name) []:域名
Email Address []:邮箱
3、生成服务端证书cer
openssl x509 -req -in server-req.csr -out server-cert.cer -signkey server-key.key -CAcreateserial -days 3650
4、生成客户端key(与上面方法一样):
openssl genrsa -out client-key.key 1024
5、 生成客户端证书请求文件(与服务端的最好一致)
openssl req -new -out client-req.csr -key client-key.key
6、 生成客户端证书cer(直接十年,十年后你的应用如果还在,你就成神了)
openssl x509 -req -in client-req.csr -out client-cert.cer -signkey client-key.key -CAcreateserial -days 3650
7、 生成客户端带密码的p12证书(双向认证的话,浏览器访问时候要导入该证书才行;Android请求的时候也需要把它转成bks来请求双向认证):
openssl pkcs12 -export -clcerts -in client-cert.cer -inkey client-key.key -out client.p12
8、p12转bks:
keytool -importkeystore -srckeystore client.p12 -srcstoretype pkcs12 -destkeystore client.bks -deststoretype bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-157.jar
9、p12转pem:
openssl pkcs12 -in client.p12 -nocerts -nodes -out client.pem
开发可能需要各种后缀的证书,反正用哪个就转哪个吧
六、nginx配置
1、将生成的证书,为了方便管理,建议放到nginx相同目录下,例如“/usr/local/nginx/conf/ssl_cust” 里面;
2、打开对应的 conf 里面要https访问的域名,如果之前有配过443端口,那么只需要指定下对应的证书即可,其中
单向认证:
ssl_certificate ssl_cust/server-cert.cer; ssl_certificate_key ssl_cust/server-key.key;
双向认证:
ssl_certificate ssl_cust/server-cert.cer; ssl_certificate_key ssl_cust/server-key.key; ssl_client_certificate ssl_cust/client-cert.cer; ssl_verify_client on;
3、具体配置如下:
server { listen 443 ssl; server_name 域名 ssl on; ssl_certificate ssl_cust/server-cert.cer; ssl_certificate_key ssl_cust/server-key.key; # 双向认证, #是注释 # ssl_client_certificate ssl_cust/client-cert.cer; # ssl_verify_client on; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!R**:!DHE;(这有个C----4,把**替换了,别问我为啥,反正我发布的时候就说就敏感词汇,我很无奈) ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; }
4、保存关闭后,执行以下命令让nginx重启生效
sudo nginx -s reload
七、Android代码请求
单向认证:
其实单向认证,用xUtils的话可以不需要设置setSslSocketFactory内容,因为库代码DefaultParamsBuilder.java里面判断了如果没有设置自定义ssl,那么就直接用操作系统自带的进行返回,从而来访问https。
我们这里为了演示下具体代码,所以自定义一个ssl的构造出来,操作如下:
把服务端server-cert.cer证书放到 assets 目录下,然后创建一个 SSLHelper.java 的辅助类:
public static SSLSocketFactory getSSLSingleFactory(Context context) { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); InputStream is = context.getAssets().open("server-cert.cer"); keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(is)); if(is != null) { is.close(); } SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (CertificateException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return null; }
对应的Activity请求里的代码:
RequestParams params = new RequestParams("https://域名"); params.setSslSocketFactory(SSLHelper.getSSLSingleFactory(this)); // 因为是自签不受权威机构认证,所以绕过不检查域名ssl。 params.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); x.http().get(params, new Callback.CommonCallback<String>() { @Override public void onSuccess(String result) { Log.d(TAG, "onSuccess...result = " + result); } @Override public void onError(Throwable ex, boolean isOnCallback) { Log.d(TAG, "onError...ex = " + ex.getMessage()); } @Override public void onCancelled(CancelledException cex) { } @Override public void onFinished() { } });
双向认证:
双向认证要求的是客户端也需要持有一份自己的私钥key、服务端要有一份客户端的公钥证书。但是由于Android系统限制,我们需要把client.p12转成client.bks格式,才能被访问到。。
对应的 SSLHelper.java添加一个双向认证方法:
public static SSLSocketFactory getSSLDoubleFactory(Context context) { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null); InputStream is = context.getAssets().open("server-cert.cer"); keyStore.setCertificateEntry("0", certificateFactory.generateCertificate(is)); if(is != null) { is.close(); } SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); // 初始化双向客户端keyStore KeyStore clientKeyStore = KeyStore.getInstance("BKS"); clientKeyStore.load(context.getAssets().open("client.bks"), "123456".toCharArray()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(clientKeyStore, "123456".toCharArray()); sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom()); return sslContext.getSocketFactory(); } catch (CertificateException e) { e.printStackTrace(); } catch (KeyStoreException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (UnrecoverableKeyException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return null; }
然后Activity里面,
params.setSslSocketFactory(SSLHelper.getSSLSingleFactory(this));
换成
params.setSslSocketFactory(SSLHelper.getSSLDoubleFactory(this));
即可。
注:双向证书其实不止Android使用,PC端也可使用,APP端可放置APK中,PC端就需要手工导入了,实际操作起来比较麻烦,所以市面浏览器访问一般都是单向证书(除特殊需求外),具体APP端就看各家情况了。
来源:freebuf.com 2021-04-23 19:22:27 by: Mr一凡先生
请登录后发表评论
注册