方法简介
本篇博文以Okhttp 4.6.0来解析hostnameVerfier
的作用,顾名思义,该方法的主要作用就是鉴定hostnname
的合法性。Okhttp在初始化的时候我们可以自己配置hostnameVerfier
:
new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(35, TimeUnit.SECONDS)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
//注意这里在生产环境中千万不要直接写死true
return true;
}
})
.build();
但是网上好多资料将verfiy
直接返回true是十分危险的。当然如果vertify返回fasle意味着hostname验证不通过,http请求无法成功,比如我以自己的博客地址发起http请求,错误信息如下:
{http errorCode=-500, mErrorMsg=Hostname yanchen.blog.csdn.net not verified:
certificate: sha256/tlnf6pbfeu257hnJ9e6j4A1ZWH3vVMzn3Zn3F9kLHdg=
DN: CN=*.blog.csdn.net
subjectAltNames: [*.blog.csdn.net]}
执行vertify的地方是在RealConnection里面,执行之后。
除了自定义hostnameVerfier
之外,Okhttp提供了默认实现,现在就分析下起内部原理。
核心原理
在Okhttp内置了OkHostnameVerifier,该方法通过session.peerCertificates[0] as X509Certificate
获取证书的对象
override fun verify(host: String, session: SSLSession): Boolean {
return try {
verify(host, session.peerCertificates[0] as X509Certificate)
} catch (_: SSLException) {
false
}
}
fun verify(host: String, certificate: X509Certificate): Boolean {
return when {
host.canParseAsIpAddress() -> verifyIpAddress(host, certificate)
else -> verifyHostname(host, certificate)
}
}
通过X509Certificate对象提供了一系列get方法可以获取到证书的公钥,序列号等一系列信息。见下图:
最终会调用verifyHostname
方法,通过certificate获取getSubjectAltNames
拿到SubjectAltName
之后,将hostname与SubjectAltName
进行比对,如果符合就返回true,否则就返回fasle.
private fun verifyHostname(hostname: String, certificate: X509Certificate): Boolean {
val hostname = hostname.toLowerCase(Locale.US)
return getSubjectAltNames(certificate, ALT_DNS_NAME).any {
verifyHostname(hostname, it)
}
}
//hostname和SubjectAltName比对
private fun verifyHostname(hostname: String?, pattern: String?): Boolean {
var hostname = hostname
var pattern = pattern
//检验客户端域名的有效性
if (hostname.isNullOrEmpty() ||
hostname.startsWith(".") ||
hostname.endsWith("..")) {
// Invalid domain name
return false
}
//检验证书中SubjectAltName的有效性
if (pattern.isNullOrEmpty() ||
pattern.startsWith(".") ||
pattern.endsWith("..")) {
// Invalid pattern/domain name
return false
}
// Normalize hostname and pattern by turning them into absolute domain names if they are not
// yet absolute. This is needed because server certificates do not normally contain absolute
// names or patterns, but they should be treated as absolute. At the same time, any hostname
// presented to this method should also be treated as absolute for the purposes of matching
// to the server certificate.
// www.android.com matches www.android.com
// www.android.com matches www.android.com.
// www.android.com. matches www.android.com.
// www.android.com. matches www.android.com
if (!hostname.endsWith(".")) {
hostname += "."
}
if (!pattern.endsWith(".")) {
pattern += "."
}
// Hostname and pattern are now absolute domain names.
pattern = pattern.toLowerCase(Locale.US)
// Hostname and pattern are now in lower case -- domain names are case-insensitive.
if ("*" !in pattern) {
// Not a wildcard pattern -- hostname and pattern must match exactly.
return hostname == pattern
}
// Wildcard pattern
// WILDCARD PATTERN RULES:
// 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
// only character in that label (i.e., must match the whole left-most label).
// For example, *.example.com is permitted, while *a.example.com, a*.example.com,
// a*b.example.com, a.*.example.com are not permitted.
// 2. Asterisk (*) cannot match across domain name labels.
// For example, *.example.com matches test.example.com but does not match
// sub.test.example.com.
// 3. Wildcard patterns for single-label domain names are not permitted.
if (!pattern.startsWith("*.") || pattern.indexOf('*', 1) != -1) {
// Asterisk (*) is only permitted in the left-most domain name label and must be the only
// character in that label
return false
}
// Optimization: check whether hostname is too short to match the pattern. hostName must be at
// least as long as the pattern because asterisk must match the whole left-most label and
// hostname starts with a non-empty label. Thus, asterisk has to match one or more characters.
if (hostname.length < pattern.length) {
return false // Hostname too short to match the pattern.
}
if ("*." == pattern) {
return false // Wildcard pattern for single-label domain name -- not permitted.
}
// Hostname must end with the region of pattern following the asterisk.
val suffix = pattern.substring(1)
if (!hostname.endsWith(suffix)) {
return false // Hostname does not end with the suffix.
}
// Check that asterisk did not match across domain name labels.
val suffixStartIndexInHostname = hostname.length - suffix.length
if (suffixStartIndexInHostname > 0 &&
hostname.lastIndexOf('.', suffixStartIndexInHostname - 1) != -1) {
return false // Asterisk is matching across domain name labels -- not permitted.
}
// Hostname matches pattern.
return true
}
那么SubjectAltName
是什么?我们可以通过如下方法获取:
new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
try {
X509Certificate x509Certificate= (X509Certificate) session.getPeerCertificates()[0];
Collection<List<?>> subjectAltNames = x509Certificate.getSubjectAlternativeNames();
for (List<?> subjectAltName : subjectAltNames) {
if (subjectAltName == null || subjectAltName.size() < 2) continue;
int type = (int)subjectAltName.get(0);
if (type!= 2) continue;
String altName = (String)subjectAltName.get(1);
LogUtil.logD("hostnameVerifier","x509Certificate altName=="+altName);
}
} catch (Exception e) {
}
return true;
}
}
Okhttp 内置的hostname校验逻辑很简单,大家可以自行查看起源码即可。
参考资料
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » Okhttp hostnameVerifier详解
发表评论 取消回复