赞
踩
╰─$ openssl genrsa -out server.key 1024
╰─$ openssl req -new -x509 -key server.key -out server.crt -days 3650
╰─$ openssl genrsa -out client.key 1024
╰─$ openssl req -new -x509 -key client.key -out client.crt -days 3650
将client.key和client.crt合成client.p12。p12文件可以认为是一对公私钥的合体文件,通常会有密码保护;可以通过openssl命令生成(将公私钥两个文件合成得到一个p12文件)
╰─$ openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
╰─$ ls
client.crt client.key client.p12 server.crt server.key
最关键的是域名信息Common Name,这里需要填写服务器的域名地址,比如test.com;也可以填写泛域名,比如*.test.com;如果没有域名,可以直接填写服务端ip地址。
from flask import Flask
app = Flask(__name__)
from flask import request
@app.route("/")
def hello():
print(dict(request.headers))
print('客户端证书: ' + request.headers.get('X-SSL-Client-Cert', '').replace('\n\t', '\n'))
print('证书序列号: ' + request.headers.get('X-SSL-serial', ''))
print('证书主体: ' + request.headers.get('cert-subject', ''))
return "SSLPinning Test"
if __name__ == "__main__":
app.run(ssl_context=('/Users/wiliam/temp/certificate/server.crt', '/Users/wiliam/temp/certificate/server.key'))
因为用的自签名证书,提示“不安全”是正常的,点击“红色三角形感叹号”可以查看证书
╰─$ brew install nginx
╰─$ nginx
访问http://localhost:8080/可以看到nginx的欢迎页面
使用nginx的 -t 参数进行配置检查,即可知道实际调用的配置文件路径及是否调用有效
╰─$ nginx -t
nginx: the configuration file /opt/homebrew/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /opt/homebrew/etc/nginx/nginx.conf test is successful
修改/opt/homebrew/etc/nginx/nginx.conf
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 8080; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} } # another virtual host using mix of IP-, name-, and port-based configuration # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # server { listen 443 ssl; server_name localhost; ssl_certificate /Users/wiliam/temp/certificate/server.crt; ssl_certificate_key /Users/wiliam/temp/certificate/server.key; ssl_client_certificate /Users/wiliam/temp/certificate/client.crt; # ssl_verify_client on/optional/optional_no_ca/off; ssl_verify_client optional_no_ca; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; } location /flask/ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-PORT $remote_port; proxy_set_header X-SSL-Client-Cert $ssl_client_cert; proxy_set_header X-SSL-serial $ssl_client_serial; proxy_set_header cert-subject $ssl_client_s_dn; proxy_pass https://127.0.0.1:5000/; } } include servers/*; }
这里开启https服务器,
设置ssl_certificate、ssl_certificate_key、ssl_client_certificate、ssl_verify_client,
添加location /flask/,通过proxy_pass转发请求到flask接口,
通过proxy_set_header把证书信息$ssl_client_cert、$ssl_client_serial、$ssl_client_s_dn设置到请求头里供后端查看
# 重新启动,热启动,修改配置重启不影响线上
╰─$ nginx -s reload;
此时访问https://localhost/flask/可以看到请求转发成功
用postman请求https://localhost/flask/
没导入客户端证书时请求,flask日志显示无客户端证书
postman导入客户端证书
postman再次请求
nginx配置文件里设置 ssl_verify_client on;
─$ nginx -s reload
此时访问https://localhost/flask/,nginx会提示“No required SSL certificate was sent”
package my.app; import android.util.Log; import okhttp3.CertificatePinner; import okhttp3.OkHttpClient; import javax.net.ssl.*; import java.io.IOException; import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; public class OkHttpClintUtil { public static InputStream client_p12 = null; public static InputStream server2_crt = null; public static OkHttpClient getClientByAI(){ try { //服务端证书 CertificateFactory certificateFactory = CertificateFactory.getInstance("X509"); Certificate serverCertificate = certificateFactory.generateCertificate(server2_crt); //keyStore存储服务端证书 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null,null); //In order to create an empty keystore, pass null as the stream argument. keyStore.setCertificateEntry("my_server_certificate", serverCertificate); // 创建 TrustManager TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); // 加载客户端证书文件 KeyStore keyStore2 = KeyStore.getInstance("PKCS12"); keyStore2.load(client_p12, "123456".toCharArray()); // 创建 KeyManager KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); keyManagerFactory.init(keyStore2, "123456".toCharArray()); KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); // 创建 SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, new SecureRandom()); //certificatePinner验证服务端证书,可以校验证书,也可以校验证书哈希值 CertificatePinner certificatePinner = new CertificatePinner.Builder().add("172.16.90.134", CertificatePinner.pin(serverCertificate)).build(); CertificatePinner certificatePinner2 = new CertificatePinner.Builder().add("172.16.90.134", "sha256/Rkw2hU165pITX+5rqOJCPrgDL3Y3TRHLPbygl/wxpsY=").build(); // 创建 OkHttpClient OkHttpClient client = new OkHttpClient.Builder() //sslContext携带了客户端证书 //trustManagers[0]信任服务端自签名证书 .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]) //可以在这里对hostname进行校验 .hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { Log.d("my.app", "hostname: " + hostname); try { Certificate[] peerCertificates = session.getPeerCertificates(); for (Certificate c: peerCertificates) { Log.d("my.app", "接收到的服务端证书: \n" + c.toString()); } } catch (SSLPeerUnverifiedException e) { e.printStackTrace(); return false; } return true; } }) //服务端证书绑定 .certificatePinner(certificatePinner2) .build(); return client; } catch (CertificateException | KeyStoreException | KeyManagementException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e) { e.printStackTrace(); } return null; } }
String url = "https://172.16.90.134/flask"; Log.i("my.app", url); try { OkHttpClintUtil.server2_crt = getAssets().open("server2.crt"); OkHttpClintUtil.client_p12 = getAssets().open("client.p12"); } catch (IOException e) { e.printStackTrace(); } OkHttpClient okHttpClient = OkHttpClintUtil.getClientByAI(); Request request = new Request.Builder().url(url).get().build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d("my.app", "okhttp请求失败" + e.toString()); //使用Looper解决在子线程中调用Toast出现异常 Looper.prepare(); Toast.makeText(getApplicationContext(),"okhttp请求失败" + e.toString(),Toast.LENGTH_SHORT).show(); Looper.loop(); } @Override public void onResponse(Call call, Response response) throws IOException { //response.body().string() 获得服务器返回的数据 String res = response.body().string(); Log.d("my.app", "onResponse: " + res); //使用Looper解决在子线程中调用Toast出现异常 Looper.prepare(); Toast.makeText(getApplicationContext(),"onResponse: " + res,Toast.LENGTH_SHORT).show(); Looper.loop(); } });
TCP 三次握手:用于建立 TCP 连接(不论是否使用 SSL/TLS)。
SSL/TLS 握手:在 TCP 连接建立后,进行SSL/TLS握手,包括:
客户端发送 Client Hello。
服务端响应 Server Hello 并返回其证书。
客户端验证服务端证书后,发送自己的证书给服务端。
服务端验证客户端证书。
双方完成密钥交换,建立安全通道。
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "SSLPinning Test"
if __name__ == "__main__":
# app.run(ssl_context='adhoc') # Flask的临时证书并不是那么好,因为每次服务器运行时,都会通过pyOpenSSL动态生成不同的证书
app.run(ssl_context=('/Users/wiliam/temp/cert.pem', '/Users/wiliam/temp/key.pem'))
flask不支持验证客户端证书!
(好像可以???看不懂 https://stackoverflow.com/questions/23262768/two-way-ssl-authentication-for-flask)
╰─$ brew install nginx
╰─$ where nginx
/opt/homebrew/bin/nginx
使用nginx的 -t 参数进行配置检查,即可知道实际调用的配置文件路径及是否调用有效
╰─$ nginx -t
nginx: the configuration file /opt/homebrew/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /opt/homebrew/etc/nginx/nginx.conf test is successful
# 重新启动,热启动,修改配置重启不影响线上
nginx -s reload;
# 关闭
nginx -s stop;
proxy_set_header X-SSL-CERT $ssl_client_cert;
will save the client certificate (from the incoming request to nginx) into ssl_client_cert variable.
NOTE: You’d have to set the
ssl_verify_client on/optional/optional_no_ca/off;
configuration and it should be anything other than off.
on: will do the full verification on client cert, will require the cert from the client side.
optional: cert isn’t required but if cert is provided, will verify it.
optional_no_ca: cert isn’t required, and won’t be verified.
off: turning the option off. (Not asking for the certs so nothing to save in ssl_client_cert)
╰─$ openssl x509 -in server.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Rkw2hU165pITX+5rqOJCPrgDL3Y3TRHLPbygl/wxpsY=
使用缓存即可的意思
关闭缓存
etag off;
add_header Last-Modified "";
add_header Cache-Control no-cache;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。