你的位置:首页 > 操作系统

[操作系统]Tomcat双向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端超安全通信


紧接着《Tomcat单向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端安全通信》,此处演示下更安全的双向Https认证的通信机制,为了清晰明了,以下进行单独描述,你不需要去看《Tomcat单向Https验证搭建,亲自实现与主流浏览器、Android/iOS移动客户端安全通信》一样可以完全理解。

 

众所周知,iOS9已经开始在联网方面默认强制使用Https替换原来的Http请求了,虽然Http和Https各有各的优势,但是总得来说,到了现在这个安全的信息时代,开发者已经离不开Https了。

网上有很多搭建Https的教程,但是比较零散,Web浏览器端和移动端具体部署也不是特别明确,如果真的用于项目中,还需要折腾一番,本人直接来个项目级别的Demo。

 

在开始之前,我总结一下keytool这个证书工具需要处理的几种常见后缀格式的意义:

jsk/keystore, 表示一个密钥库,里面可以包含多个密钥条目(证书),密钥条目(证书)还可以分私有的和信任的等,私有的一般包括私钥、公钥和密钥条目信息,信任的一般包 括公钥和密钥条目信息(公钥证书)。打开密钥库需要一个密码,同时打开每个私有密钥条目也需要一个密码(但一般建议将打开私有密钥条目的密码设置跟打开密钥库密码相同,省的弄乱了,以下我的Demo演示是设置相同的),做过给安卓apk签名打包的一定能体会到这个。

csr/certreq,证书请求文件,你把这个提交给CA,CA会给你颁发cer格式的含有公钥和密钥条目信息的证书(公钥证书)给你。

cer, 用于存储某个密钥条目(证书)的公钥文件,一般你提交了csr/certreq给CA后,CA会颁发给你,你也可以通过自签名的CA颁发,如果你已经有密 钥条目(证书)在密钥库里,也可以从jsk/keystore中的某个密钥条目(证书)导出其公钥和密钥条目内容的证书(公钥证书)。

p12,表示一个密钥库,跟jsk/keystore类似,但一般用于客户端,表示客户端密钥库,比如IE/火狐浏览器可以直接导入。另外不同于jsk/keystore的是,一般密钥条目(证书)的打开密码跟密钥库打开密码一样。

bks,表示一个密钥库,跟p12类似,一般用于Android客户端,下面的Demo示例在Android客户端则需要用到,可以直接从p12格式转换而来。

综上,其实最简单的理解就是密钥库就相当于SQL数据库,各种密钥条目(证书)就相当于SQL数据库表 ,一个SQL数据库表其实跟其它的表又有父子(外键)关系的,这种关系叫做密钥条目(证书)的密钥链。为了描述更加方便,以下将“密钥库”描述词叫做《证书库》,“密钥条目”描述词叫做《证书》,将“cer格式的公钥和密钥条目内容的证书”叫做《公钥证书》。

 

接下来开始演示Demo示例:

1、生成服务器端证书库和证书:(生成服务器端证书库和证书可以有多种方式,推荐通过走第三方CA方式,这样生成的证书以后更具有保障性和安全性(尤其是对Web客户端,可以启动“绿色地址栏/安全锁 地址栏显示单位名称 EV国际认证标识”等等))

1-1-1、方式一、使用keytool,生成自签名的CA证书和自签名的server证书(下面生成的CA是自签名的,当然下面生成的server也是自签名的,这些证书在浏览器上使用绝对不会出现绿条):

1.生成自签名CA:keytool -genkey -v -alias ca -keyalg RSA -keystore D:\ca_cert_lib.jks -validity 3650
2.生成服务器证书:keytool -genkey -v -alias server -keyalg RSA -keystore D:\server_cert_lib.jks -validity 365

注意证书名叫ca定义为自签名的CA证书,证书名叫server定义为服务器证书,它们分别保存在证书库路径为 D:\ca_cert_lib.jksD:\server_cert_lib.jks 中
之所以要分自签名的CA证书server服务器证书,是因为正常情况下我们的server服务器证书是需要向第三方CA申请的,第三方CA会用它的根证书给你生成一份公钥证书(这个过程叫做第三方CA给你签名),而此处就是要自导自演展示自签名的CA给server证书签名这个过程

1-1-2、用自签名的CA给server签上CA的签名(server本身也是自签名的,下面要做的相当于将server的自签名换成CA的签名,也许你会问CA的签名是谁的,CA也可以是别人的,比如如果沃通愿意给你的CA签名的话,那么CA的颁发者就是沃通,我这里的Demo演示没有权威机构给它签名,所以我这个CA就是自己给自己签名的,这个CA其实就是ROOT证书,只不过不会被任何客户端信任(如:浏览器等)而已,即用我这个CA签发的所有server服务器证书在任何浏览器上绝对不会出现绿条):

在给server签名之前,查看一下当前证书库情况,它们的确都是各自给自己签名的:
keytool -list -v -keystore D:\ca_cert_lib.jks
keytool -list -v -keystore D:\server_cert_lib.jks

现在使用自签名CA给server签名(如果你要沃通CA给你server签名,就把下面的csr交给沃通):
1.生成server的证书请求文件:keytool -certreq -alias server -keystore D:\server_cert_lib.jks > D:\server.csr (linux上:keytool -certreq -alias server -keystore <路径>/server_cert_lib.jks | tee <路径>/server.csr)
2.使用自签名的CA对server的证书请求文件进行签名颁发服务器server.cer公钥证书:keytool -gencert -alias ca -keystore D:\ca_cert_lib.jks -infile D:\server.csr -outfile D:\server.cer
3.生成自签名CA的公钥文件:keytool -export -alias ca -keystore D:\ca_cert_lib.jks -rfc -file D:\ca.cer

此时可以先查看以下ca.cer和server.cer公钥证书具体内容(注意ca.cer是自签名CA的公钥文件,其颁发者还是它自己,而server.cer是server服务器的公钥文件,其颁发者是自签名的CA,两者是有本质区别的,下面安装回复后可以看到这个区别),不过其实他们都是个Base64过的字符串:
keytool -printcert -rfc -file D:\ca.cer
keytool -printcert -rfc -file D:\server.cer

          ,以下演示使用沃通申请免费的DV SSL证书。

1-2-1、登陆沃通,申请一个免费的DV SSL证书。

1-2-3、申请完后需要验证域名,验证域名这个事就自己去搞定吧

1-2-4、上面用自签名的CA给server证书签名已经提到了如何生成csr文件,此处通过提交证书申请文件csr申请的步骤略。以下演示在线生成,即本次讲的通过沃通CA自动生成公钥证书,顺便把server服务器证书库也一并生成好并将公钥证书导入到这个证书库,此处输入的密码实际上既是server服务器证书库密码也是server服务器证书(此种方式生成的证书名字叫做1,这个1对应上面自签名CA导入的server证书)的密码

输入密码生成证书之后就可以下载沃通CA颁布给你server服务器端用的证书库和证书了,然后部署到对应的服务器程序中,本案例部署到tomcat,为了保持统一性和直观性此处将沃通CA颁发的证书库名andy5.me.jks改名为server_cert_lib.jks

一般通过此时生成的证书名字(alias)叫做:1,对应自签名CA方式中的server证书

 因此你拿到了上面的沃通颁发的证书后,你还可以继续颁发给别人,这些你颁发的证书都是可信任的,因为沃通上面的根证书一定是可信任的,不然沃通本身就是不可以信任的。

 

2、由于是双向认证,所以同理,需要生成客户端证书库和证书:(同样使用keytool生成)

keytool -genkey -v -alias client -keyalg RSA -storetype PKCS12 -keystore D:\client_cert_lib.p12 -validity 90

注意证书名叫做client,保存的证书库路径为 D:\client_cert_lib.p12

3-2、将cer格式的client证书公钥导入到服务器端证书库里

keytool -import -alias client -v -file D:\client.cer -keystore D:\server_cert_lib.jks

               maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
               clientAuth="true" sslProtocol="TLS"
               keystoreFile="D:\\server_cert_lib.jks" keystorePass="s123456"
               truststoreFile="D:\\server_cert_lib.jks" truststorePass="s123456"
               />

 

4、同样的,做双向认证还需要同时将服务器端证书导入到客户端证书库里:(这个过程叫做让客户端证书库信任指定的服务器端证书,需要server的公钥证书即可)

4-1、先从jks格式的服务器端证书库导出cer格式的服务器端公钥证书

keytool -keystore D:\server_cert_lib.jks -export -alias server -file D:\server.cer

4-2、将cer格式的服务器端证书导入到客户端证书库(这个实现不能用keytool导入了,而是要根据具体的各个客户端平台进行实现,此步骤可以理解为给客户端安装客户端证书库需要信任的服务器端server公钥证书

4-2-1、Web浏览器实现:使用浏览器安装客户端证书库和信任的服务器端server证书

4-2-1-1、IE浏览器

1、安装客户端证书库:Windows下直接双击client_cert_lib.p12,输入客户端证书库密码后,一直下一步安装客户端证书库

 4-2-1-2、火狐浏览器

1、导入client_cert_lib.p12

 

4-2-2-2、将服务器端公钥证书server.cer客户端证书库client_cert_lib.bks放入安卓assert目录,然后使用HttpsUtil.getSslSocketFactory进行初始化(该工具类的方法具体实现见后面的Demo代码)

private void initHttpsEngine(boolean isSelfCa) {    try {      // 初始化服务器端公钥证书,得到SSLSocketFactory      SSLSocketFactory sslSocketFactory = HttpsUtil.getSslSocketFactory(new InputStream[]{getAssets().open          ("server.cer")}, getAssets().open("client_cert_lib.bks"), "c123456");      OkHttpClient.Builder builder = new OkHttpClient.Builder();      if (isSelfCa) {        /**         * 注意;如果你的server.cer是来自自签名CA颁发的,那么就要设置下面的customVerifier,主要是为了解决报以下异常,         * 即跳过Hostname www.andy5.me在CA上的验证,如果你的server.cer是来自第三方SSL权威机构颁发的,不用设置这个customVerifier         *         * javax.net.ssl.SSLPeerUnverifiedException: Hostname www.andy5.me not verified:         * certificate: sha1/EnrjjhNxjvuDkO/rJqPmJ9XaIMs=         * DN: CN=Andy Wu(www.andy5.me),OU=Andy5 Server,O=www.andy5.me,L=Guangzhou,ST=Guangdong,C=CN         * subjectAltNames: []         */        HostnameVerifier customVerifier = new HostnameVerifier() {          @Override          public boolean verify(String hostname, SSLSession session) {            // 指定SERVER_URL一定可以通过            if (SERVER_URL.equalsIgnoreCase(hostname)) {              return true;            } else {              // 使用默认的OkHostnameVerifier进行验证              return OkHostnameVerifier.INSTANCE.verify(hostname, session);            }          }        };        mOkHttpClient = builder.sslSocketFactory(sslSocketFactory).connectTimeout(30, TimeUnit.SECONDS)            .hostnameVerifier(customVerifier).build();      } else {        mOkHttpClient = builder.sslSocketFactory(sslSocketFactory).connectTimeout(30, TimeUnit.SECONDS).build();      }    } catch (IOException e) {      e.printStackTrace();    }  }

4-2-2-3、可以在任何地方对该服务器发起Https请求了,如果是自签名CA签发的服务器端server证书,需要忽略域名验证才能正常通信(具体看Demo代码),显然也是不安全的。

  public void testHttps(View v) {    if (mOkHttpClient == null) {      Toast.makeText(getApplicationContext(), "请先初始化客户端密钥库和服务器端公钥!", Toast.LENGTH_SHORT).show();      return;    }    mTvResult.setText("正在从 " + HTTPS_SERVER_URL + " 获取数据....");    mWvResult.loadData("", "text/html", "UTF-8");    Request request = new Request.Builder().url(HTTPS_SERVER_URL).build();    Call call = mOkHttpClient.newCall(request);    call.enqueue(new Callback() {      @Override      public void onFailure(Call call, final IOException e) {        e.printStackTrace();        runOnUiThread(new Runnable() {          @Override          public void run() {            mTvResult.setText("从 " + HTTPS_SERVER_URL + " 获取数据失败!\n" + e);          }        });      }      @Override      public void onResponse(Call call, Response response) throws IOException {        final String html = response.body().string();        runOnUiThread(new Runnable() {          @Override          public void run() {            mTvResult.setText("从 " + HTTPS_SERVER_URL + " 获取数据成功!");            mWvResult.loadData(html, "text/html", "UTF-8");          }        });      }    });  }

4-2-2-4、成功请求界面如下,软件环境:MIUI 6(Android 4.4.2) + AS 1.5.1

4-2-3、iOS客户端实现

4-2-3-1、将服务器端公钥证书server.cer和客户端证书库client_cert_lib.p12放入根目录,然后使用HttpsUtil.configHTTPSessionManager配置AFNetworking安全选项(该工具类的方法具体实现见后面的Demo代码)

- (IBAction)testHttps:(UIButton *)sender {    AFHTTPSessionManager *manager =[AFHTTPSessionManager manager];    NSArray *serverCersNames = [[NSArray alloc] initWithObjects:@"server.cer", nil];  [HttpsUtil configHTTPSessionManager:manager serverCers:serverCersNames clientP12:@"client_cert_lib.p12" clientP12Password:@"c123456" isSelfCa:true];// 使用自签名CA给服务器server证书签名的isSelfCa为true,第三方权威CA签名的isSelfCa为false,当设置isSelfCa为false时,需要注释掉Info.plist中整个NSAppTransportSecurity节点的配置  manager.responseSerializer = [AFHTTPResponseSerializer serializer];  manager.requestSerializer.timeoutInterval = 30.0f;    [_tvResult setText:[NSString stringWithFormat:@"正在从%@获取数据....",HTTPS_SERVER_URL]];  [_wvResult loadHTMLString:@"" baseURL:nil];    [manager GET:HTTPS_SERVER_URL parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {    //  } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {    NSString *result = [[NSString alloc]initWithData:responseObject encoding:NSUTF8StringEncoding];    NSLog(@"获取数据成功\n%@",result);    [_tvResult setText:[NSString stringWithFormat:@"从%@获取数据成功!",HTTPS_SERVER_URL]];    [_wvResult loadHTMLString:result baseURL:nil];  } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {    NSLog(@"获取数据失败\n%@",error);    [_tvResult setText:[NSString stringWithFormat:@"从%@获取数据失败!\n%@",HTTPS_SERVER_URL,error]];  }];}

4-2-3-2、成功请求界面如下,软件环境:iOS 9.2.1 + Xcode 7.2.1

 

 

参考文章:

用Tomcat服务器配置https双向认证过程实战

Android Https相关完全解析 当OkHttp遇到Https

Creating self-signed certificates for use on Android

JDK中的证书生成和管理工具keytool

HTTPS双向认证的原理与实现 (Nginx + iOS)

 

Demo下载:

双向Https验证Demo相关文件.7z

 

原创随笔,转载注明出处。