CA机构:颁发证书的机构,你的域名想上Https,需要在CA机构注册,申请证书,如百度https://www.baidu.com/的证书
服务器证书:如https://www.baidu.com/,对应一家公司,去CA申请证书后,会有一个证书(服务器程序会用到),下文中将此类证书称为服务器证书
根证书:CA机构用来加解密,验证你申请证书的证书,每个平台会内置这些CA机构的证书(也叫根证书)如 Android,程序与https通信的时候会用到根证书验证Https URL对应的证书是否合法
如:https://www.baidu.com/域名
在谷歌浏览器中打开https://www.baidu.com/可以看到(如下图),点击安全图标出现如下弹框
点击证书可以查看证书相关内容:颁发者,有效期等
导出服务器证书 : 点击详细信息->复制到文件->下一步->选择格式DER ->下一步,直到导出成功 最后会有一个**.cer**的证书文件
导出CA根证书 :点击根证书路径->点击最上面的根证书->剩下步骤就和导出服务器证书一样
window+R 下输入certmgr.msc,确定
出现证书列表:选择某个证书,双击出现证书界面,导出就和上面的操作一样
查看设备上的证书
adb shell
cd system/etc/security/cacerts/
#列出所有证书,如下图 ,一堆57692373.0文件
ls
#查找某个机构颁发的证书 如 cfca
grep -inr "cfca" ./
#查看某个证书文件
cat 9282e51c.0
列出所有证书
查找某个机构颁发的证书 如 cfca,查看cfca证书文件
安装证书
adb push "C:\Users\my\Desktop\CFCA EV ROOT.cer" /sdcard/baidu
上面的错误是在一个Android 5.0的设备的Https请求中的,其他设备没问题;通过上面的步骤安装完证书就好了,然而这种方法只能验证问题,不能当做解决问题的方案。下面提供两个解决思路
OkHttp的https证书验证逻辑
使用OkHttp默认证书验证逻辑:不设置sslSocketFactory和hostnameVerifier,默认使用系统的根证书验证服务器证书,如果对应的根证书不存在,报错Trust anchor for certification path not found.
;这种就适合使用CA的证书(也有可能CA机构的根证书不够通用而没有内置),不适合像12306这种自己颁发的证书
自定义自己的证书验证逻辑:直接让所有的服务器证书通过不验证;比较服务器证书公钥,颁发机构,颁发给谁等信息验证某个特定的服务器证书
private X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
}
}
private SSLSocketFactory systemDefaultSslSocketFactory(X509TrustManager trustManager) {
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
return sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
}
}
大体意思就是使用系统的CA根证书验证服务器证书
忽略https根证书验证,设置信任所有服务器证书
X509TrustManager接口就是用来验证证书的,实现一个空的接口,就是不验证证书合法性;验证服务器证书不合格就抛出异常
通过:OkHttpClient的sslSocketFactory和hostnameVerifier方法设置;测试有效
封装的工具类:
public class HttpsCertificateVerifyHelper {
/*------------------------设置信任所有证书 start------------------------*/
public static OkHttpClient.Builder trustAllCertificate(OkHttpClient.Builder okBuilder) {
initSSLSocketFactoryAndX509TrustManager();
//sslSocketFactory 方法: 设置信任所有证书,无需使用CA证书对服务器证书验证
//hostnameVerifier方法:设置域名验证成功,我没有设置这个,https接口也是可以使用的,可能默认返回的true;
// 但是我让其返回false,接口出错Hostname xxxxhost not verified
okBuilder.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return okBuilder;
}
private static X509TrustManager trustManager;
private static SSLSocketFactory sslSocketFactory;
private static void initSSLSocketFactoryAndX509TrustManager() {
try {
trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
/*------------------------设置信任所有证书 end------------------------*/
/*------------------------设置信任内置的服务器证书 start------------------------*/
/**
* 指定本地的服务器证书来认证 服务器的证书(服务器证书)
*
* @param okBuilder
* @param context
* @param assetsFileName 证书在assets中的文件名 .cer 格式或者 .pem格式
* @return
*/
public static OkHttpClient.Builder trustSpecificCertificate(OkHttpClient.Builder okBuilder, Context context, String assetsFileName) {
initSSLSocketFactoryAndX509TrustManager(context, assetsFileName);
//sslSocketFactory 方法: 设置信任所有证书,无需使用CA证书对服务器证书验证
//hostnameVerifier方法:设置域名验证成功,我没有设置这个,https接口也是可以使用的,可能默认返回的true;
// 但是我让其返回false,接口出错Hostname xxxxhost not verified
okBuilder.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
return okBuilder;
}
public static void initSSLSocketFactoryAndX509TrustManager(Context context, String assetsFileName) {
try {
trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
if (chain == null || chain.length == 0) {
throw new CertificateException("checkServerTrusted: X509Certificate array is null");
}
if (!(null != authType && authType.equals("ECDHE_RSA"))) {
throw new CertificateException("checkServerTrusted: AuthType is not ECDHE_RSA");
}
//判断证书是否是本地信任列表里颁发的证书(系统默认的验证)
try {
TrustManagerFactory factory = TrustManagerFactory.getInstance("X509");
factory.init((KeyStore) null);
for (TrustManager trustManager : factory.getTrustManagers()) {
((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
}
return;//用系统的证书验证服务器证书,验证通过就不需要继续验证证书信息;也可以注释掉,继续走自己的服务器证书逻辑
} catch (Exception e) {
e.printStackTrace();
//注意这个地方不能抛异常,用系统的证书验证服务器证书,没通过就用自己的验证规则
// throw new CertificateException(e);
}
//获取本地证书中的信息
String clientEncoded = "";//公钥
String clientSubject = "";//颁发给
String clientIssUser = "";//颁发机构
try (InputStream inputStream = getAssetFileInputStream(context, assetsFileName)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate clientCertificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
clientEncoded = new BigInteger(1, clientCertificate.getPublicKey().getEncoded()).toString(16);
clientSubject = clientCertificate.getSubjectDN().getName();
clientIssUser = clientCertificate.getIssuerDN().getName();
} catch (Exception e) {
e.printStackTrace();
throw new CertificateException(e);
}
//获取网络中的证书信息
X509Certificate certificate = chain[0];
PublicKey publicKey = certificate.getPublicKey();
String serverEncoded = new BigInteger(1, publicKey.getEncoded()).toString(16);
if (!clientEncoded.equals(serverEncoded)) {
throw new CertificateException("server's PublicKey is not equals to client's PublicKey");
}
String subject = certificate.getSubjectDN().getName();
if (!clientSubject.equals(subject)) {
throw new CertificateException("server's SubjectDN is not equals to client's SubjectDN");
}
String issuser = certificate.getIssuerDN().getName();
if (!clientIssUser.equals(issuser)) {
throw new CertificateException("server's IssuerDN is not equals to client's IssuerDN");
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
sslSocketFactory = sslContext.getSocketFactory();
} catch (Exception e) {
e.printStackTrace();
}
}
private static InputStream getAssetFileInputStream(Context context, String assetsFileName) {
try {
return context.getAssets().open(assetsFileName);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/*------------------------设置信任内置的服务器证书 end------------------------*/
}
使用工具类设置OkHttpClient
HttpsCertificateVerifyHelper.trustAllCertificate(new OkHttpClient.Builder()
//.cache(cache)
.eventListenerFactory(HttpEventListener.FACTORY)
.addInterceptor(cacheControlInterceptor)
// .addNetworkInterceptor(cacheControlInterceptor)
.addInterceptor(logInterceptor)
.retryOnConnectionFailure(false)
.connectTimeout(getConnectTimeout(), TimeUnit.SECONDS)
.readTimeout(getReadTimeout(), TimeUnit.SECONDS))
.build();
参考:https://www.jianshu.com/p/8a16ac7ee444
终端内置服务器证书
my.cer放在main/assets目录下
大概的验证逻辑:先使用默认的本地根证书验证服务器证书,看自己项目情况可以去掉(代码 return可以注释掉);再使用自己的服务器证书验证逻辑(比较证书的公钥,颁发者,颁发给信息);测试有效
使用工具类设置OkHttpClient
HttpsCertificateVerifyHelper.trustSpecificCertificate(new OkHttpClient.Builder()
//.cache(cache)
.eventListenerFactory(HttpEventListener.FACTORY)
.addInterceptor(cacheControlInterceptor)
// .addNetworkInterceptor(cacheControlInterceptor)
.addInterceptor(logInterceptor)
.retryOnConnectionFailure(false)
.connectTimeout(getConnectTimeout(), TimeUnit.SECONDS)
.readTimeout(getReadTimeout(), TimeUnit.SECONDS), ContextUtil.getInstance().getContext(),"my.cer")
.build();
参考:https://blog.csdn.net/masonblog/article/details/77712047
终端内置根证书,代码安装根证书
下面是安装证书的代码,没有验证过;有需要的可以参考
/**
* 安裝指定的CA根证书证书
*
* @param context
* @param assetsFileName
*/
public static void installSpecificCertificate(Context context, String assetsFileName) {
Intent intent = KeyChain.createInstallIntent();
try (InputStream inputStream = getAssetFileInputStream(context, assetsFileName)) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate clientCertificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
//将证书传给系统
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, clientCertificate.getEncoded());
//此处为给证书设置默认别名,第二个参数可自定义,设置后无需用户输入
intent.putExtra("name", "别名(可设置默认)");
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
参考:https://blog.csdn.net/wwp9527/article/details/82757850