鉴于公司有需求 需要Java服务端进行证书的校验,怎奈java实现的案列太少,苦苦搜寻2天,百度、谷歌、OCSP协议文档 、最后终于实现了,可以利用该 Demo 进行任何 OCSP协议的证书校验!!!
找了好多,最后使用 bouncycastle 完成了OCSP校验
Maven 需要引入的包:
org.bouncycastle
bcprov-jdk15on
1.64
org.bouncycastle
bcpkix-jdk15on
1.64
org.bouncycastle
bcmail-jdk15on
1.64
org.bouncycastle
bctls-jdk15on
1.64
org.bouncycastle
bcprov-ext-jdk15on
1.64
org.bouncycastle
bcpg-jdk15on
1.64
commons-codec
commons-codec
1.7
commons-lang
commons-lang
2.6
commons-io
commons-io
2.4
commons-beanutils
commons-beanutils
1.8.3
commons-collections
commons-collections
3.2.2
commons-net
commons-net
3.0
commons-configuration
commons-configuration
1.9
commons-digester
commons-digester
2.1
commons-httpclient
commons-httpclient
3.1
commons-cli
commons-cli
1.3.1
org.apache.commons
commons-pool2
2.4.2
普通java项目的包 :bouncycastle + commons 相关包
以下是工具代码:
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.cert.ocsp.UnknownStatus;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import sun.misc.BASE64Decoder;
import com.sun.xml.internal.messaging.saaj.util.ByteInputStream;
/**
* 使用 OCSP协议请求 验证证书状态(有效|撤销|未知)
* @author i4026
*
*/
public class OCSPTool {
//苹果根证书对象
private static X509Certificate issuerCert;
/**
* 初始实例化苹果根证书对象 ( 发行者证书 )
* @return
*/
private static X509Certificate getIssuerCert() {
if(issuerCert == null) {
//苹果根证书
String issuerCertStr = "MIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE\n" +
"BhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRp\n" +
"ZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEz\n" +
"MDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVTMRMwEQYD\n" +
"VQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv\n" +
"cGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3Bl\n" +
"ciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3\n" +
"DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0U3rOfGOA\n" +
"YXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkVCBmsqtsq\n" +
"Mu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8V25nNYB2\n" +
"NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHld0WNUEi6\n" +
"Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1qarunFjVg\n" +
"0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGjgaYwgaMw\n" +
"HQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQFMAMBAf8w\n" +
"HwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGg\n" +
"H4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGG\n" +
"MBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Zviz1smwv\n" +
"j+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/Nw0Uwj6OD\n" +
"Dc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJTleMa1s8\n" +
"Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1VAKmuu0sw\n" +
"ruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur+cmV6U/k\n" +
"TecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxRpVzscYqC\n" +
"tGwPDBUf";
try {
byte[] issuerByte = new BASE64Decoder().decodeBuffer(issuerCertStr);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
issuerCert = (X509Certificate) cf.generateCertificate(new ByteInputStream(issuerByte, issuerByte.length));
} catch (Exception e) {
e.printStackTrace();
}
}
return issuerCert;
}
/*
* 通过ocsp 验证证书状态
* 0 有效证书
* 1 未知证书
* -1 吊销证书
* retry 重试次数
*/
public static Integer checkCertStatus(String certStr, String pwd, int retry) {
int resultNum = -2;
while (retry > 0) {
try {
byte[] p12Byte = new BASE64Decoder().decodeBuffer(certStr);
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new ByteInputStream(p12Byte, p12Byte.length), pwd.toCharArray());
X509Certificate cert = (X509Certificate)ks.getCertificate("1");
OCSPReq ocspReq = GenOcspReq(cert, getIssuerCert());
OCSPResp ocspResp = requestOCSPResponse(getOCSPUrl(cert), ocspReq);
if(OCSPResp.SUCCESSFUL == ocspResp.getStatus()) {
BasicOCSPResp basic = (BasicOCSPResp) ocspResp.getResponseObject();
SingleResp[] resps = basic.getResponses();
if (resps != null && resps.length == 1) {
SingleResp resp = resps[0];
CertificateStatus certStatus = resp.getCertStatus();
if (certStatus == CertificateStatus.GOOD) {
resultNum = 0;
} else {
if (certStatus instanceof RevokedStatus) {
resultNum = -1;
} else if (certStatus instanceof UnknownStatus) {
resultNum = 1;
}
}
retry = 0;
}
}
} catch (Exception e) {
e.printStackTrace();
retry --;
}
}
return resultNum;
}
/**
* 创建 OCSP requestq 请求
* @param nextCert 需要检验 的证书
* @param nextIssuer 发行者证书 (苹果根证书)
* @return
* @throws OCSPException
* @throws OperatorCreationException
* @throws CertificateEncodingException
* @throws IOException
*/
public static OCSPReq GenOcspReq(X509Certificate nextCert,
X509Certificate nextIssuer) throws OCSPException, OperatorCreationException, CertificateEncodingException, IOException {
OCSPReqBuilder ocspRequestGenerator = new OCSPReqBuilder();
DigestCalculatorProvider digCalcProv = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
// 获取 certId
CertificateID certId = new CertificateID(
(new BcDigestCalculatorProvider())
.get(CertificateID.HASH_SHA1),
new X509CertificateHolder(nextIssuer.getEncoded()),
nextCert.getSerialNumber());
ocspRequestGenerator.addRequest(certId);
BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString(nonce.toByteArray()));
ocspRequestGenerator.setRequestExtensions(new Extensions(new Extension[]{ext}));
return ocspRequestGenerator.build();
}
/**
* 发送请求并接收返回值
* @param url 请求地址,可以从证书中获取
* @param ocspReq 请求对象
* @return
* @throws IOException
* @throws MalformedURLException
*/
public static OCSPResp requestOCSPResponse(String url, OCSPReq ocspReq) throws IOException, MalformedURLException {
byte[] ocspReqData = ocspReq.getEncoded();
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
try {
con.setRequestProperty("Content-Type", "application/ocsp-request");
con.setRequestProperty("Accept", "application/ocsp-response");
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false);
OutputStream out = con.getOutputStream();
try {
IOUtils.write(ocspReqData, out);
out.flush();
} finally {
IOUtils.closeQuietly(out);
}
byte[] responseBytes = IOUtils.toByteArray(con.getInputStream());
OCSPResp ocspResp = new OCSPResp(responseBytes);
return ocspResp;
} finally {
if (con != null) {
con.disconnect();
}
}
}
/**
* 获取证书中 OSCP的请求地址
* @param certificate
* @return
* @throws IOException
*/
public static String getOCSPUrl(X509Certificate certificate) throws IOException {
ASN1Primitive obj;
try {
obj = getExtensionValue(certificate, Extension.authorityInfoAccess.getId());
} catch (IOException ex) {
return null;
}
if (obj == null) {
return null;
}
AuthorityInformationAccess authorityInformationAccess = AuthorityInformationAccess.getInstance(obj);
AccessDescription[] accessDescriptions = authorityInformationAccess.getAccessDescriptions();
for (AccessDescription accessDescription : accessDescriptions) {
boolean correctAccessMethod = accessDescription.getAccessMethod().equals(X509ObjectIdentifiers.ocspAccessMethod);
if (!correctAccessMethod) {
continue;
}
GeneralName name = accessDescription.getAccessLocation();
if (name.getTagNo() != GeneralName.uniformResourceIdentifier) {
continue;
}
DERIA5String derStr = DERIA5String.getInstance((ASN1TaggedObject) name.toASN1Primitive(), false);
return derStr.getString();
}
return null;
}
/**
* @param certificate
* the certificate from which we need the ExtensionValue
* @param oid
* the Object Identifier value for the extension.
* @return the extension value as an ASN1Primitive object
* @throws IOException
*/
private static ASN1Primitive getExtensionValue(X509Certificate certificate, String oid) throws IOException {
byte[] bytes = certificate.getExtensionValue(oid);
if (bytes == null) {
return null;
}
ASN1InputStream aIn = new ASN1InputStream(new ByteArrayInputStream(bytes));
ASN1OctetString octs = (ASN1OctetString) aIn.readObject();
aIn = new ASN1InputStream(new ByteArrayInputStream(octs.getOctets()));
return aIn.readObject();
}
/**
* 测试Demo
* @param args
*/
public static void main(String[] args) {
String p12 = "MIIL1QIBAzCCC58GCSqGSIb3DQEHA----需要校验的证书----";
String pdw = "证书密码";
int checkStatus = checkCertStatus(p12, pdw, 3);
String data = "未知";
if (checkStatus == 0) {
data = "证书有效";
} else if (checkStatus == 1) {
data = "证书未知";
} else if (checkStatus == -1) {
data = "证书撤销";
} else if (checkStatus == -2) {
data = "验证异常";
}
System.out.println(data);
}
}
运行结果:
这代码没经过优化,纯属第一个Demo版本,但是能够正常校验,发这博客是因为java做这个的案列太少了,不希望其他开发人员遇到这种需求像我一样浪费那么多时间(大牛除外哈)!
有哪儿写的不对了可以指出哈