ssl证书验证失败问题
错误信息
javax.net.ssl.SSLHandshakeException: KeyUsage does not allow key encipherment}, caused by {sun.security.validator.ValidatorException: KeyUsage does not allow key encipherment
调查方式底层代码debug查找
通过小编费力的debug,找到了报错代码的位置。
通常解决问题的方式都是找到根源报错位置才可以。
代码片段,只保留需要部分。
package sun.security.validator;
import java.security.cert.CertificateException;
...
class EndEntityChecker {
private boolean checkKeyUsage(X509Certificate var1, int var2) throws CertificateException {
boolean[] var3 = var1.getKeyUsage();
if (var3 == null) {
return true;
} else {
return var3.length > var2 && var3[var2];
}
}
...
private void checkTLSServer(X509Certificate var1, String var2, Set<String> var3) throws CertificateException {
if (KU_SERVER_ENCRYPTION.contains(var2)) {
if (!this.checkKeyUsage(var1, 2)) {
throw new ValidatorException("KeyUsage does not allow key encipherment", ValidatorException.T_EE_EXTENSIONS, var1);
}
} else if (KU_SERVER_SIGNATURE.contains(var2)) {
if (!this.checkKeyUsage(var1, 0)) {
throw new ValidatorException("KeyUsage does not allow digital signatures", ValidatorException.T_EE_EXTENSIONS, var1);
}
} else {
if (!KU_SERVER_KEY_AGREEMENT.contains(var2)) {
throw new CertificateException("Unknown authType: " + var2);
}
if (!this.checkKeyUsage(var1, 4)) {
throw new ValidatorException("KeyUsage does not allow key agreement", ValidatorException.T_EE_EXTENSIONS, var1);
}
}
if (!this.checkEKU(var1, var3, "1.3.6.1.5.5.7.3.1") && !this.checkEKU(var1, var3, "1.3.6.1.4.1.311.10.3.3") && !this.checkEKU(var1, var3, "2.16.840.1.113730.4.1")) {
throw new ValidatorException("Extended key usage does not permit use for TLS server authentication", ValidatorException.T_EE_EXTENSIONS, var1);
} else if (!SimpleValidator.getNetscapeCertTypeBit(var1, "ssl_server")) {
throw new ValidatorException("Netscape cert type does not permit use for SSL server", ValidatorException.T_EE_EXTENSIONS, var1);
} else {
var3.remove("2.5.29.15");
var3.remove("2.5.29.37");
var3.remove("2.16.840.1.113730.1.1");
}
}
想必你已经发现了报错的代码位置,就是log日志中的报错信息的位置。
if (KU_SERVER_ENCRYPTION.contains(var2)) {
if (!this.checkKeyUsage(var1, 2)) {
throw new ValidatorException("KeyUsage does not allow key encipherment", ValidatorException.T_EE_EXTENSIONS, var1);
}
报错的原因是KU_SERVER_ENCRYPTION中包含了var2,但是调用checkKeyUsage方法时返回了false,验证失败,所以抛出异常。
private static final Collection<String> KU_SERVER_ENCRYPTION = Arrays.asList("RSA");
不错var2就是字符串“RSA”.
var2是怎么取到的呢?
这个问题很好,通过小编的debug,发现是再向目标服务第二次握手后走就会走到这段验证的代码,所以应该是从目标服务中的加密套件组ciphers中取到的。
具体过程就不再赘述了。
接下来看认证过程。
private boolean checkKeyUsage(X509Certificate var1, int var2) throws CertificateException {
boolean[] var3 = var1.getKeyUsage();
if (var3 == null) {
return true;
} else {
return var3.length > var2 && var3[var2];
}
}
通过上面的认证代码不难看出参数所代表的含义。
var1代表证书信息,var2 就是调用时所传入的int值 2
通过***var1.getKeyUsage()***取出证书KeyUsage中的配置信息。
如果没有配置,不需要验证直接返回true。
如果有配置,则长度要大于var2的值,因为var2是下标,防止数组下标越界的判断。var3的长度是9准定大于2.
数组的值[true, false, false, false …] index 2所对应的数值是false。
所以代入方法,var3[var2] = false, 因为是&&所以返回false。
**var3[var2]**就是KeyUsage中的某个配置项,那是什么呢?怎么配置呢?
package sun.security.x509;
...
public class X509CertImpl extends X509Certificate implements DerEncoder {
...
public boolean[] getKeyUsage() {
try {
String var1 = OIDMap.getName(PKIXExtensions.KeyUsage_Id);
if (var1 == null) {
return null;
} else {
KeyUsageExtension var2 = (KeyUsageExtension)this.get(var1);
if (var2 == null) {
return null;
} else {
boolean[] var3 = var2.getBits();
if (var3.length < 9) {
boolean[] var4 = new boolean[9];
System.arraycopy(var3, 0, var4, 0, var3.length);
var3 = var4;
}
return var3;
}
}
} catch (Exception var5) {
return null;
}
}
package sun.security.x509;
...
public class KeyUsageExtension extends Extension implements CertAttrSet<String> {
...
public boolean[] getBits() {
return (boolean[])this.bitString.clone();
}
...
public void set(String var1, Object var2) throws IOException {
if (!(var2 instanceof Boolean)) {
throw new IOException("Attribute must be of type Boolean.");
} else {
boolean var3 = (Boolean)var2;
if (var1.equalsIgnoreCase("digital_signature")) {
this.set(0, var3);
} else if (var1.equalsIgnoreCase("non_repudiation")) {
this.set(1, var3);
} else if (var1.equalsIgnoreCase("key_encipherment")) {
this.set(2, var3);
} else if (var1.equalsIgnoreCase("data_encipherment")) {
this.set(3, var3);
} else if (var1.equalsIgnoreCase("key_agreement")) {
this.set(4, var3);
} else if (var1.equalsIgnoreCase("key_certsign")) {
this.set(5, var3);
} else if (var1.equalsIgnoreCase("crl_sign")) {
this.set(6, var3);
} else if (var1.equalsIgnoreCase("encipher_only")) {
this.set(7, var3);
} else {
if (!var1.equalsIgnoreCase("decipher_only")) {
throw new IOException("Attribute name not recognized by CertAttrSet:KeyUsage.");
}
this.set(8, var3);
}
this.encodeThis();
}
}
通过代码的调用我们找到了KeyUsageExtension 类。
我们通过这个set方法来看,因为我们获取的值准定是先在读取证书的时候已经set了,所以看这块代码才能知道如何控制着数组。
index 为2的值是 key_encipherment
所以说明需要在证书的KeyUsage中添加此项才能通过此项认证,还有一种方式就是不配置KeyUsage.
解决方式在制作证书脚本中增加keyUsage = keyEncipherment
...
keyUsage = Digital Signature, keyEncipherment
...
验证通过!亲测!