Java源码中证书相关逻辑的逻辑
在设计外部PKI CA体系的TLS逻辑的时候突然感觉有些疑问,所以就又翻看了一下JAVA TLS过程相关的源码,现在把这些逻辑拿出来做个解读,便于更好的理解TLS和证书有关的原理
获取可信证书
//获取本地信任的证书
private X509Certificate getTrustedCertificate(X509Certificate cert) {
//获取证书subject信息
Principal certSubjectName = cert.getSubjectX500Principal();
List<X509Certificate> list = trustedX500Principals.get(certSubjectName);
//如果证书不在信任列表中 返回空
if (list == null) {
return null;
}
//获取证书Issuer签发者信息
Principal certIssuerName = cert.getIssuerX500Principal();
//获取证书公钥信息
PublicKey certPublicKey = cert.getPublicKey();
for (X509Certificate mycert : list) {
//如果信任列表中证书等于本证书,则返回
if (mycert.equals(cert)) {
return cert;
}
//如果信任列表中证书的签发者与本证书签发者不同,则继续
if (!mycert.getIssuerX500Principal().equals(certIssuerName)) {
continue;
}
//如果信任列表中证书的签发者与本证书签发者不同,则继续
if (!mycert.getPublicKey().equals(certPublicKey)) {
continue;
}
return mycert;
}
return null;
}
这个方法的核心作用就是将TLS握手阶段server certificate包返回的服务器证书数据中的证书与本地信任列表中的证书进行匹配,选择证书主题项、证书颁发者项、证书公钥都相同 以及 证书完全相同 这两种情况的证书
证书类的equals方法重写
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Certificate)) {
return false;
}
try {
//获取证书的二进制编码
byte[] thisCert = X509CertImpl.getEncodedInternal(this);
//获取证书的二进制编码
byte[] otherCert = X509CertImpl.getEncodedInternal((Certificate)other);
//比较字节是否相同
return Arrays.equals(thisCert, otherCert);
} catch (CertificateException e) {
return false;
}
}
这个就简单了,比较两张证书的字节数组是否相同,意思为两张证书是否完全相同
构建信任证书链
private X509Certificate[] buildTrustedChain(X509Certificate[] chain)
throws CertificateException {
List<X509Certificate> c = new ArrayList<X509Certificate>(chain.length);
//遍历证书链
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
//判断证书是否为受信证书
X509Certificate trustedCert = getTrustedCertificate(cert);
//如果是 则返回证书
if (trustedCert != null) {
c.add(trustedCert);
return c.toArray(CHAIN0);
}
c.add(cert);
}
//遍历完都没找到可信证书,则再次check最后一张证书是否为可信的
X509Certificate cert = chain[chain.length - 1];
X500Principal subject = cert.getSubjectX500Principal();
X500Principal issuer = cert.getIssuerX500Principal();
List<X509Certificate> list = trustedX500Principals.get(issuer);
if (list != null) {
X509Certificate trustedCert = list.iterator().next();
c.add(trustedCert);
return c.toArray(CHAIN0);
}
throw new ValidatorException(ValidatorException.T_NO_TRUST_ANCHOR);
}
本方法用于找到本地可信的证书链,便于后续验证证书
判断证书是否为不受信的
public static boolean isUntrusted(X509Certificate cert) {
if (algorithm == null) {
return false;
}
String key;
if (cert instanceof X509CertImpl) {
//获取证书指纹
key = ((X509CertImpl)cert).getFingerprint(algorithm);
} else {
try {
key = new X509CertImpl(cert.getEncoded()).getFingerprint(algorithm);
} catch (CertificateException cee) {
return false;
}
}
//判断是否在不受信列表中
return props.containsKey(key);
}
这里可以讲一下props变量,这是一个Properties对象,初始化的时候会加载
java.home环境变量下lib/security/blacklisted.certs目录的内容,具体内容可以看一下自己电脑对应的配置,就是key=value这种格式
获取并校验可信认证的合法性
X509Certificate[] engineValidate(X509Certificate[] chain,
Collection<X509Certificate> otherCerts,
List<byte[]> responseList,
AlgorithmConstraints constraints,
Object parameter) throws CertificateException {
if ((chain == null) || (chain.length == 0)) {
throw new CertificateException
("null or zero-length certificate chain");
}
//构建可信证书链
chain = buildTrustedChain(chain);
//构建不可信验证器
UntrustedChecker untrustedChecker = new UntrustedChecker();
// 判断是否为不可信列表中证书
X509Certificate anchorCert = chain[chain.length - 1];
try {
untrustedChecker.check(anchorCert);
} catch (CertPathValidatorException cpve) {
throw new ValidatorException(
"Untrusted certificate: "+ anchorCert.getSubjectX500Principal(),
ValidatorException.T_UNTRUSTED_CERT, anchorCert, cpve);
}
//构建信任锚点 信任起点
TrustAnchor anchor = new TrustAnchor(anchorCert, null);
//构建默认算法检查器
AlgorithmChecker defaultAlgChecker =
new AlgorithmChecker(anchor, variant);
AlgorithmChecker appAlgChecker = null;
if (constraints != null) {
appAlgChecker = new AlgorithmChecker(anchor, constraints, null,
variant);
}
// 遍历证书链 从链顶开始 就是从最上级开始
int maxPathLength = chain.length - 1;
for (int i = chain.length - 2; i >= 0; i--) {
//第一次遍历 issuerCert就是信任锚点证书
X509Certificate issuerCert = chain[i + 1];
X509Certificate cert = chain[i];
// 检查本证书是否是不可信证书
try {
untrustedChecker.check(cert, Collections.<String>emptySet());
} catch (CertPathValidatorException cpve) {
throw new ValidatorException(
"Untrusted certificate: " + cert.getSubjectX500Principal(),
ValidatorException.T_UNTRUSTED_CERT, cert, cpve);
}
//检查证书算法
try {
defaultAlgChecker.check(cert, Collections.<String>emptySet());
if (appAlgChecker != null) {
appAlgChecker.check(cert, Collections.<String>emptySet());
}
} catch (CertPathValidatorException cpve) {
throw new ValidatorException
(ValidatorException.T_ALGORITHM_DISABLED, cert, cpve);
}
// 如果是code signing与jce signing,则不校验日期
if ((variant.equals(VAR_CODE_SIGNING) == false)
&& (variant.equals(VAR_JCE_SIGNING) == false)) {
cert.checkValidity(date);
}
// 检查本证书的上级和上级证书的证书信息是否匹配
if (cert.getIssuerX500Principal().equals(
issuerCert.getSubjectX500Principal()) == false) {
throw new ValidatorException
(ValidatorException.T_NAME_CHAINING, cert);
}
// 校验签名
try {
cert.verify(issuerCert.getPublicKey());
} catch (GeneralSecurityException e) {
throw new ValidatorException
(ValidatorException.T_SIGNATURE_ERROR, cert, e);
}
// 如果是CA证书,则校验拓展信息
if (i != 0) {
maxPathLength = checkExtensions(cert, maxPathLength);
}
}
return chain;
}
证书算法校验
public void check(Certificate cert,
Collection<String> unresolvedCritExts)
throws CertPathValidatorException {
if (!(cert instanceof X509Certificate) || constraints == null) {
// 非x509证书不校验
return;
}
// 检查证书用法 将证书用法转换成boolean数组
boolean[] keyUsage = ((X509Certificate) cert).getKeyUsage();
//如果不存在密钥用法或长度不够标准的9 报错
if (keyUsage != null && keyUsage.length < 9) {
throw new CertPathValidatorException(
"incorrect KeyUsage extension",
null, null, -1, PKIXReason.INVALID_KEY_USAGE);
}
X509CertImpl x509Cert;
AlgorithmId algorithmId;
try {
x509Cert = X509CertImpl.toImpl((X509Certificate)cert);
//获取证书的签名算法ID
algorithmId = (AlgorithmId)x509Cert.get(X509CertImpl.SIG_ALG);
} catch (CertificateException ce) {
throw new CertPathValidatorException(ce);
}
//获取算法
AlgorithmParameters currSigAlgParams = algorithmId.getParameters();
//获取公钥
PublicKey currPubKey = cert.getPublicKey();
//获取签名算法 sun.security.x509.AlgorithmId
String currSigAlg = x509Cert.getSigAlgName();
//这里没太懂为什么要这样验证 有可能和TLS握手传递的允许签名的算法有关
if (!constraints.permits(SIGNATURE_PRIMITIVE_SET, currSigAlg,
currSigAlgParams)) {
throw new CertPathValidatorException(
"Algorithm constraints check failed on signature " +
"algorithm: " + currSigAlg, null, null, -1,
BasicReason.ALGORITHM_CONSTRAINED);
}
// 默认允许所有证书用法 java.security.CryptoPrimitive
Set<CryptoPrimitive> primitives = KU_PRIMITIVE_SET;
if (keyUsage != null) {
primitives = EnumSet.noneOf(CryptoPrimitive.class);
//设置为签名证书
if (keyUsage[0] || keyUsage[1] || keyUsage[5] || keyUsage[6]) {
// keyUsage[0]: KeyUsage.digitalSignature
// keyUsage[1]: KeyUsage.nonRepudiation
// keyUsage[5]: KeyUsage.keyCertSign
// keyUsage[6]: KeyUsage.cRLSign
primitives.add(CryptoPrimitive.SIGNATURE);
}
//设置为密钥封装
if (keyUsage[2]) { // KeyUsage.keyEncipherment
primitives.add(CryptoPrimitive.KEY_ENCAPSULATION);
}
//设置为数据加密
if (keyUsage[3]) { // KeyUsage.dataEncipherment
primitives.add(CryptoPrimitive.PUBLIC_KEY_ENCRYPTION);
}
//设置为密钥协商
if (keyUsage[4]) { // KeyUsage.keyAgreement
primitives.add(CryptoPrimitive.KEY_AGREEMENT);
}
if (primitives.isEmpty()) {
throw new CertPathValidatorException(
"incorrect KeyUsage extension bits",
null, null, -1, PKIXReason.INVALID_KEY_USAGE);
}
}
ConstraintsParameters cp =
new CertPathConstraintsParameters(x509Cert, variant,
anchor, date);
if (constraints instanceof DisabledAlgorithmConstraints) {
((DisabledAlgorithmConstraints)constraints).permits(currSigAlg,
currSigAlgParams, cp)
} else {
certPathDefaultConstraints.permits(currSigAlg, currSigAlgParams, cp);
// 根据配置的禁用算法限制条件进行校验
// 包括TLS握手获取的禁用
// 包括TLS本地配置的禁用
// 包括环境变量配置的禁用
if (!constraints.permits(primitives, currPubKey)) {
throw new CertPathValidatorException(
"Algorithm constraints check failed on key " +
currPubKey.getAlgorithm() + " with size of " +
sun.security.util.KeyUtil.getKeySize(currPubKey) +
"bits",
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
}
}
if (prevPubKey == null) {
prevPubKey = currPubKey;
return;
}
// 验证签名算法与issuer的公钥的兼容性
if (!constraints.permits(
SIGNATURE_PRIMITIVE_SET,
currSigAlg, prevPubKey, currSigAlgParams)) {
throw new CertPathValidatorException(
"Algorithm constraints check failed on " +
"signature algorithm: " + currSigAlg,
null, null, -1, BasicReason.ALGORITHM_CONSTRAINED);
}
// DSA公钥缺少参数场景
if (PKIX.isDSAPublicKeyWithoutParams(currPubKey)) {
if (!(prevPubKey instanceof DSAPublicKey)) {
throw new CertPathValidatorException("Input key is not " +
"of a appropriate type for inheriting parameters");
}
DSAParams params = ((DSAPublicKey)prevPubKey).getParams();
if (params == null) {
throw new CertPathValidatorException(
"Key parameters missing from public key.");
}
try {
BigInteger y = ((DSAPublicKey)currPubKey).getY();
KeyFactory kf = KeyFactory.getInstance("DSA");
DSAPublicKeySpec ks = new DSAPublicKeySpec(y, params.getP(),
params.getQ(), params.getG());
currPubKey = kf.generatePublic(ks);
} catch (GeneralSecurityException e) {
throw new CertPathValidatorException("Unable to generate " +
"key with inherited parameters: " + e.getMessage(), e);
}
}
prevPubKey = currPubKey;
}
KeyUsage ::= BIT STRING {
digitalSignature (0),
nonRepudiation (1),
keyEncipherment (2),
dataEncipherment (3),
keyAgreement (4),
keyCertSign (5),
cRLSign (6),
encipherOnly (7),
decipherOnly (8)}
校验CA拓展信息
private int checkExtensions(X509Certificate cert, int maxPathLen)
throws CertificateException {
Set<String> critSet = cert.getCriticalExtensionOIDs();
if (critSet == null) {
critSet = Collections.<String>emptySet();
}
// 检查关键信息,规则如下:
// CA证书证书的 basic constraints不能小于0,因为需要签发下级证书
// 非自签名证书 max length不能小于等于0,
// 以实际
int pathLenConstraint =
checkBasicConstraints(cert, critSet, maxPathLen);
// 检查中间证书用法 key cert sign不能为false
checkKeyUsage(cert, critSet);
checkNetscapeCertType(cert, critSet);
if (!critSet.isEmpty()) {
throw new ValidatorException
("Certificate contains unknown critical extensions: " + critSet,
ValidatorException.T_CA_EXTENSIONS, cert);
}
return pathLenConstraint;
}
其实对于应用层关心的还是:
1、证书链可信校验
2、证书校验
3、证书有效性校验