import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.http.HttpUtil;
import com.faraway.pdemo.exception.DefinedException;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.xbill.DNS.CNAMERecord;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Type;
import java.io.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.util.Date;
/**
* <dependency>
* <groupId>cn.hutool</groupId>
* <artifactId>hutool-all</artifactId>
* <version>5.4.5</version>
* </dependency>
*
* <dependency>
* <groupId>dnsjava</groupId>
* <artifactId>dnsjava</artifactId>
* <version>3.3.1</version>
* </dependency>
*
* <dependency>
* <groupId>org.bouncycastle</groupId>
* <artifactId>bcprov-jdk15on</artifactId>
* <version>1.67</version>
* </dependency>
*
* <dependency>
* <groupId>org.projectlombok</groupId>
* <artifactId>lombok</artifactId>
* <version>1.18.12</version>
* <scope>provided</scope>
* </dependency>
*/
@Slf4j
public class DNSAndSSLUtil {
/**
* 校验域名和记录值是否匹配
* 作用:甲方域名指向乙方域名,乙方域名指向自己的服务,确保甲方填写的记录值是乙方提供的,这样才能正常提供服务
*
* @param domainFromA 域名 甲方提供
* @param valueFromB 记录值 乙方提供
* @throws DefinedException 自定义异常
* @throws Exception
* @Description 流程:用阿里云举例
* 1.(甲方)登录阿里云的云解析DNS/域名解析/解析设置
* 2.(甲方)添加记录 > 记录类型:CNAME;主机记录:起一个二级域名;记录值:乙方提供(例如七牛云:image.langflying.com.qiniudns.com)
* 3.(乙方)通过第三方工具(dnsjava),甲方将二级域名给乙方,通过下方代码反解析出记录值,将反解析的记录值与我们提供的对比,看是否一致,一致则校验通过
*/
public static void checkDNS(String domainFromA, String valueFromB) throws DefinedException, Exception {
// 提供域名给第三方
Lookup lookup = new Lookup(domainFromA, Type.CNAME);
lookup.run();
if (lookup.getResult() != Lookup.SUCCESSFUL) {
throw new DefinedException("未查询到该域名");
}
Record[] records = lookup.getAnswers();
for (int i = 0; i < records.length; i++) {
CNAMERecord cnameRecord = (CNAMERecord) records[i];
if (cnameRecord == null || cnameRecord.getTarget() == null || StrUtil.isBlank(cnameRecord.getTarget().toString())) {
throw new DefinedException("域名的记录值为空");
}
// 通过域名查询到记录值
String parseValue = cnameRecord.getTarget().toString();
// 获取到的记录值最后会多一个".",例如(image.langflying.com.qiniudns.com.),这里处理一下,移除掉
String valueWithRemoveLastPoint = parseValue.substring(0, parseValue.length() - 1);
// 将反解析的记录值与我们提供的记录值对比
if (!valueWithRemoveLastPoint.trim().equals(valueFromB.trim())) {
throw new DefinedException("域名与记录值不匹配");
}
}
}
/**
* 证书校验:是否过期,拥有者是否正确
*
* @param domain 域名
* @param certificateUrl https证书
* @throws DefinedException 自定义异常
*/
public static void checkCert(String domain, String certificateUrl) throws DefinedException {
try {
// 拆分域名并一级域名
String[] split = domain.split("\\.");
String domainName = split[split.length - 2];
// 从远程地址读取证书文件(本地更好,直接读文件)
File certificateFile = HttpUtil.downloadFileFromUrl(certificateUrl, "");
InputStream is = new FileInputStream(certificateFile);
// 创建X509工厂类
CertificateFactory factory = CertificateFactory.getInstance("X.509");
// 创建证书对象
X509Certificate cert = (X509Certificate) factory.generateCertificate(is);
is.close();
// 获取证书过期时间
Date afterDate = cert.getNotAfter();
Date now = new Date();
if (now.compareTo(afterDate) == 1) {
throw new DefinedException("证书已过期");
}
//获得证书主体信息(即一级域名)
String owner = cert.getSubjectDN().getName();
if (!owner.contains(domainName)) {
throw new DefinedException("该证书不属于[" + domain + "]");
}
} catch (Exception e) {
e.printStackTrace();
throw new DefinedException("解析证书出错");
}
}
/*public static void showCertInfo() {
try {
//读取证书文件
File file = new File("e://ICINFOCERTS//天谷证书.cer");
InputStream inStream = new FileInputStream(file);
//创建X509工厂类
CertificateFactory cf = CertificateFactory.getInstance("X.509");
//创建证书对象
X509Certificate oCert = (X509Certificate) cf.generateCertificate(inStream);
inStream.close();
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy/MM/dd");
String info;
//获得证书版本
info = String.valueOf(oCert.getVersion());
System.out.println("证书版本:" + info);
//获得证书序列号
info = oCert.getSerialNumber().toString(16);
System.out.println("证书序列号:" + info);
//获得证书有效期
Date beforeDate = oCert.getNotBefore();
info = dateformat.format(beforeDate);
System.out.println("证书生效日期:" + info);
Date afterDate = oCert.getNotAfter();
info = dateformat.format(afterDate);
System.out.println("证书失效日期:" + info);
//获得证书主体信息
info = oCert.getSubjectDN().getName();
System.out.println("证书拥有者:" + info);
//获得证书颁发者信息
info = oCert.getIssuerDN().getName();
System.out.println("证书颁发者:" + info);
//获得证书签名算法名称
info = oCert.getSigAlgName();
System.out.println("证书签名算法:" + info);
// 下方一般不需要
*//*byte[] byt = oCert.getExtensionValue("1.2.86.11.7.9");
String strExt = new String(byt);
System.out.println("证书扩展域:" + strExt);
byt = oCert.getExtensionValue("1.2.86.11.7.1.8");
String strExt2 = new String(byt);
System.out.println("证书扩展域2:" + strExt2);*//*
} catch (Exception e) {
System.out.println("解析证书出错!");
}
}*/
/**
* 验证证书私钥是否匹配(即文件通过公钥加密私钥解密后不变)
*
* @param publicKeyFileUrl 公钥文件远程地址(不是远程更好,直接获取文件)
* @param privateKeyFileUrl 私钥文件远程地址(不是远程更好,直接获取文件)
* @return
*/
public static void certValid(String publicKeyFileUrl, String privateKeyFileUrl) throws DefinedException {
try {
File publicKeyFile = HttpUtil.downloadFileFromUrl(publicKeyFileUrl, "");
File privateKeyFile = HttpUtil.downloadFileFromUrl(privateKeyFileUrl, "");
PublicKey publicKey = getPublicKey(publicKeyFile);
if (publicKey == null) {
throw new DefinedException("获取公钥失败");
}
PrivateKey privateKey = getPrivateKey(privateKeyFile);
if (privateKey == null) {
throw new DefinedException("获取私钥失败");
}
// tutool工具包
RSA rsa = new RSA(privateKey, publicKey);
String sourceStr = "HelloWorld";
// 公钥加密
String encryptStr = rsa.encryptBase64(sourceStr, KeyType.PublicKey);
// 私钥解密
String decryptStr = rsa.decryptStr(encryptStr, KeyType.PrivateKey);
// 字符串根据证书公钥加密,私钥解密后不能还原说明证书与私钥不匹配
if (!sourceStr.equals(decryptStr)) {
throw new DefinedException("证书私钥不匹配");
}
} catch (Exception e) {
log.error("验证证书私钥是否匹配失败!", e);
throw new DefinedException("验证证书私钥是否匹配失败");
}
}
/**
* 获取Https私钥
*
* @param privateKeyFile 私钥文件
* @return
*/
public static PrivateKey getPrivateKey(File privateKeyFile) {
PemReader pemReader = null;
PrivateKey privateKey = null;
try {
pemReader = new PemReader(new FileReader(privateKeyFile));
PemObject pemObject = pemReader.readPemObject();
byte[] pemContent = pemObject.getContent();
//支持从PKCS#1或PKCS#8 格式的私钥文件中提取私钥
if (pemObject.getType().endsWith("RSA PRIVATE KEY")) {
// 取得私钥 for PKCS#1
RSAPrivateKey rsaPrivateKey = RSAPrivateKey.getInstance(pemContent);
RSAPrivateKeySpec rsaPrivateKeySpec = new RSAPrivateKeySpec(rsaPrivateKey.getModulus(), rsaPrivateKey.getPrivateExponent());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
privateKey = keyFactory.generatePrivate(rsaPrivateKeySpec);
} else if (pemObject.getType().endsWith("PRIVATE KEY")) {
// 取得私钥 for PKCS#8
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(pemContent);
KeyFactory kf = KeyFactory.getInstance("RSA");
privateKey = kf.generatePrivate(pkcs8EncodedKeySpec);
}
} catch (Exception e) {
log.error("获取私钥失败!", e);
} finally {
try {
if (pemReader != null) {
pemReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return privateKey;
}
/**
* 获取Https公钥
*
* @param publicKeyFile 公钥文件
* @return
*/
public static PublicKey getPublicKey(File publicKeyFile) {
PublicKey publicKey = null;
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
FileInputStream in = new FileInputStream(publicKeyFile);
Certificate cert = factory.generateCertificate(in);
publicKey = cert.getPublicKey();
} catch (Exception e) {
log.error("获取公钥文件失败!", e);
}
return publicKey;
}
}