那天同事换ELB证书后访问域名出现错误:End user tried to act as a CA
最终用户想冒充CA,什么意思嘞? 我在github上找到open-jdk代码,搜索它,看到是类SimpleValidator
,接着回到IDE,找出这个异常抛出的地儿:
就是说,没有找到BasicConstraints这个拓展属性,它的Id是2.5.29.19
,可以从类sun.security.x509.X509CertImpl
再追到sun.security.util.ObjectIdentifier.PKIXExtensions
中列举了所有证书属性OID与名称
代码分析
我直接复制反编译的代码,这种无名变量看起来问题也不是很大。
private int checkBasicConstraints(X509Certificate var1, Set<String> var2, int var3) throws CertificateException {
var2.remove("2.5.29.19");
int var4 = var1.getBasicConstraints();
if (var4 < 0) {
throw new ValidatorException("End user tried to act as a CA", ValidatorException.T_CA_EXTENSIONS, var1);
} else {
if (!X509CertImpl.isSelfIssued(var1)) {
if (var3 <= 0) {
throw new ValidatorException("Violated path length constraints", ValidatorException.T_CA_EXTENSIONS, var1);
}
--var3;
}
if (var3 > var4) {
var3 = var4;
}
return var3;
}
}
再追,看到拓展属性检测里先获取证书OID,检测BasicConstraints,再检测key的作用等
private int checkExtensions(X509Certificate var1, int var2) throws CertificateException {
Set var3 = var1.getCriticalExtensionOIDs();
if (var3 == null) {
var3 = Collections.emptySet();
}
int var4 = this.checkBasicConstraints(var1, var3, var2);
this.checkKeyUsage(var1, var3);
this.checkNetscapeCertType(var1, var3);
if (!var3.isEmpty()) {
throw new ValidatorException("Certificate contains unknown critical extensions: " + var3, ValidatorException.T_CA_EXTENSIONS, var1);
} else {
return var4;
}
}
哪些证书会被检测扩展属性呢?我们再跟一下,来到关键的SimpleValiator
的engineValidate
方法。var1就是服务器发来的证书数组,这个数组的最后一个就是服务器证书,往前依次是中间CA证书;这里var2方法里根本没用,var3是指定的受限算法
总得验证过程是先验证服务器证书,再验证证书链中前后验证
服务器证书验证中:只验证证书是否在不受新信证书黑名单里$JAVA_HOME/lib/security/blacklisted.certs
,
然后再构造算法验证器var10,
接下来的关键:一个for循环,把所有中间CA证书都验了个遍。注意var1.length - 2
,从长度-2上看即跳过了最后一个证书,也就是服务器证书,倒着一个个验证:取出这个序列的证书,和下个证书(第一个循环里var13就是服务器证书),
- 先进行证书黑名单var6验证
- 再进行受限算法验证,可以看到有指定受限算法和系统指定的受限算法,
sun.security.provider.certpath.AlgorithmChecker#check
方法可是非常复杂,这里只指出系统指定的受限算法获取方法:Security.getProperty("jdk.certpath.disabledAlgorithms")
windows输出MD2, MD5, RSA keySize < 1024
同时这里有个IBM参考文章:customization-disabled-restricted-cryptographic-algorithms 这里的算法是指什么呢?证书签名算法啊!其中(AlgorithmChecker
)使用TrustAnchor
主要是取出其publicKey
作为prevPubKey
- 接下来进行有效性验证
- 前后证书签发者principal与主题principal验证(我也不知道怎么翻译):
var14.getIssuerX500Principal().equals(var13.getSubjectX500Principal()
比对的就是这个东西:CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US
X500Principal This class represents an X.500 Principal. X500Principals are represented by distinguished names such as "CN=Duke, OU=JavaSoft, O=Sun Microsystems, C=US".
- 再用较上级CA证书验证下级证书的公钥签名,可以参考:
X509CertImpl#verify(PublicKey var1)
,步骤也很多 - 最后,非最后一个证书,验证扩展属性,到了我们出问题那里!
X509Certificate[] engineValidate(X509Certificate[] var1, Collection<X509Certificate> var2, AlgorithmConstraints var3, Object var4) throws CertificateException {
if (var1 != null && var1.length != 0) {
var1 = this.buildTrustedChain(var1);
Date var5 = this.validationDate;
if (var5 == null) {
var5 = new Date();
}
UntrustedChecker var6 = new UntrustedChecker();
X509Certificate var7 = var1[var1.length - 1];
try {
var6.check(var7);
} catch (CertPathValidatorException var18) {
throw new ValidatorException("Untrusted certificate: " + var7.getSubjectX500Principal(), ValidatorException.T_UNTRUSTED_CERT, var7, var18);
}
TrustAnchor var8 = new TrustAnchor(var7, (byte[])null);
AlgorithmChecker var9 = new AlgorithmChecker(var8);
AlgorithmChecker var10 = null;
if (var3 != null) {
var10 = new AlgorithmChecker(var8, var3);
}
int var11 = var1.length - 1;
for(int var12 = var1.length - 2; var12 >= 0; --var12) {
X509Certificate var13 = var1[var12 + 1];
X509Certificate var14 = var1[var12];
try {
var6.check(var14, Collections.emptySet());
} catch (CertPathValidatorException var17) {
throw new ValidatorException("Untrusted certificate: " + var14.getSubjectX500Principal(), ValidatorException.T_UNTRUSTED_CERT, var14, var17);
}
try {
var9.check(var14, Collections.emptySet());
if (var10 != null) {
var10.check(var14, Collections.emptySet());
}
} catch (CertPathValidatorException var19) {
throw new ValidatorException(ValidatorException.T_ALGORITHM_DISABLED, var14, var19);
}
if (!this.variant.equals("code signing") && !this.variant.equals("jce signing")) {
var14.checkValidity(var5);
}
if (!var14.getIssuerX500Principal().equals(var13.getSubjectX500Principal())) {
throw new ValidatorException(ValidatorException.T_NAME_CHAINING, var14);
}
try {
var14.verify(var13.getPublicKey());
} catch (GeneralSecurityException var16) {
throw new ValidatorException(ValidatorException.T_SIGNATURE_ERROR, var14, var16);
}
if (var12 != 0) {
var11 = this.checkExtensions(var14, var11);
}
}
return var1;
} else {
throw new CertificateException("null or zero-length certificate chain");
}
}
错误的原因
所以我们证书验证不通过是扩展属性验证不通过,为什么呢?
- 自己颁发的证书错误,没有填充拓展字段 OID
2.5.29.19 BasicConstrains
- AWS上传证书错误,AWS上传证书那里有三个框,服务器证书,证书链,CA;我们在证书链那里也上传的pem也含了服务器证书
后记
java.security.
为什么继承自Properties
难以理解。