Android+Nginx配置HTTPS单双向认证请求(自签证书) – 作者:Mr一凡先生

近期公司上线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一凡先生

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论