JAVA获取服务器证书以及请求OCSP查询证书状态

应用场景:leader需要做个监控程序扫描自己网站的服务器证书状态是否符合设置,出现异常则发短信/Email给管理人员。

假设我们要监控的URL是https://baidu.com,首先使用URL建立https通道,连接正常则服务器会发送证书回客户端。

JAVA需要用到的jar包,bouncy castle相关的包,大家自行下载。maven配置:

<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>${bouncycastle.version}</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcpkix-jdk15on</artifactId>
			<version>${bouncycastle.version}</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcmail-jdk15on</artifactId>
			<version>${bouncycastle.version}</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bctls-jdk15on</artifactId>
			<version>${bouncycastle.version}</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-ext-jdk15on</artifactId>
			<version>${bouncycastle.version}</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcpg-jdk15on</artifactId>
			<version>${bouncycastle.version}</version>
		</dependency>

获取服务器证书代码如下:

public List<X509Certificate> getServerCertificates(String url) {
		try {
			// http转https
			if (url.startsWith("http:")) {
				url = url.replace("http", "https");
			} else if (url.indexOf("http") < 0) {
				url = "https://" + url;
			}
//			enableTLSChainTesting(false);
			ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
			URL urlConnect = new URL(url);
			HttpsURLConnection conn = (HttpsURLConnection) urlConnect.openConnection();
			// 设置请求基本属性
			conn.setRequestProperty("accept", "*/*");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            //创建sslsocket通道
			conn.setSSLSocketFactory(createSSLFactory());

			conn.connect();
			if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
				Certificate[] certs = conn.getServerCertificates();

				for (Certificate cert : certs) {
					if (cert instanceof X509Certificate) {
						list.add((X509Certificate) cert);
					} else {
						logger.info("Unsupported certificate type.  type=" + cert.getClass().getName());
					}
				}
				return list;
			}
			return null;
		} catch (SSLHandshakeException se) {
			// 无法验证
			throw new MonitorException(se.getMessage(), se.getCause());
		} catch (Exception e) {
			throw new MonitorException(e.getMessage());
		}
	}

在上面代码中需要创建SSLSocket,需要有个可信任源来验证服务器证书。如果没有信任源则会产生此类错误,unable to find valid certification path to requested target

下面是创建信任源以及返回sslsocket的两种方式:

public SSLSocketFactory createSSLFactory() {
		try {

			//此类型自己创建一个类实现TrustManager亦可在实现类里判断
			SSLContext sc = SSLContext.getInstance("TLS");
			sc.init(null, new TrustManager[] { new TrustAllX509TrustManager() }, new java.security.SecureRandom());
			return sc.getSocketFactory();
			/*
			//此类型自己创建jks文件导入信任证书
			  KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
			  InputStream is =
			  getClass().getClassLoader().getResourceAsStream("config/webtrust.jks");
			  keystore.load(is, null);
			  
			  TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
			  tmf.init(keystore); 
			  SSLContext ctx = SSLContext.getInstance("SSL");
			  ctx.init(null, tmf.getTrustManagers(), null); 
			  SSLSocketFactory sf =ctx.getSocketFactory();
			  return sf;
			 */
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

我这里业务需求需要信任所有证书,否则服务器证书过期,则获取不到certificates,出现握手错误。

TrustAllX509TrustManager.java如下:

class TrustAllX509TrustManager implements X509TrustManager {

	public X509Certificate[] getAcceptedIssuers() {
	        return new X509Certificate[0];
    }

    public void checkClientTrusted(java.security.cert.X509Certificate[] certs,
            String authType) {
    }

    public void checkServerTrusted(java.security.cert.X509Certificate[] certs,
            String authType) {
    	
    }

}

到目前为止,则可以获取服务器的证书了。

验证证书吊销状态在CA机构的CRL文件可以验证,但是想要获取证书的即时状态就需要访问CA机构的OCSP服务了。

CA机构的OCSP服务地址一般都是在证书上面的,如图:

那我们怎么通过代码获取到该扩展项并且去请求OCSP服务地址来验证证书呢?

首先将获取回来的证书强转到X509Certificate格式,然后获取证书的der格式证书,然后再获取扩展项

使用扩展项可以获取到颁发者机构信息,再根据特定的OID获取URL。代码如下:

TBSCertificate tbs = TBSCertificate.getInstance(cert.getTBSCertificate());
			Extensions extensions = tbs.getExtensions();
			AuthorityInformationAccess access = AuthorityInformationAccess.fromExtensions(extensions);
			AccessDescription[] descs = access.getAccessDescriptions();
			for (AccessDescription desc : descs) {
				if (desc.getAccessMethod().getId().equalsIgnoreCase("1.3.6.1.5.5.7.48.1")) {
					ocspUrl = desc.getAccessLocation().getName().toString();
				}
			}

到这里就能获取到证书上面CA机构的OCSP服务器地址。

还需要创建OCSP request去请求,所有CA机构的OCSP服务器都是按照RCF6960协议来进行定义的,需要了解的朋友可以看下

https://www.rfc-editor.org/rfc/pdfrfc/rfc6960.txt.pdf

具体可以参考4.1.1的ocsp request的参数结构。

 public static OCSPReq buildOCSPRequest(final X509Certificate x509Certificate, final X509Certificate issuerX509Certificate)  {

			try {

				CertificateID certId = new CertificateID(
						(new BcDigestCalculatorProvider())
								.get(CertificateID.HASH_SHA1),
						new X509CertificateHolder(issuerX509Certificate.getEncoded()),
						x509Certificate.getSerialNumber());
				final OCSPReqBuilder ocspReqBuilder = new OCSPReqBuilder();
				ocspReqBuilder.addRequest(certId);
				final OCSPReq ocspReq = ocspReqBuilder.build();
				
				return ocspReq;
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null; 
		}

贴下我这边的请求创建,对这块的了解也是初始阶段,有熟悉的老铁可以交流交流。

具体的请求:

使用http协议和get请求,将得到的ocsp request进行URLDecode,具体的意思英文不好的同学自己google翻译下。请求服务器返回的流反序列化成OCSPResp对象。这个对象是在bouncycastle包里面的。

下面是对OCSPResp对象的解析操作。判断证书处于哪个状态。

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) {
						// 证书正常状态
						gdcaCertResp.setStatus(OcspResp.CERT_STATUS_GOOD);
					} else {
						if (certStatus instanceof RevokedStatus) {
							// 证书吊销
							gdcaCertResp.setStatus(OcspResp.CERT_STATUS_REVOKED);
							gdcaCertResp.setRevokeTime(((RevokedStatus) certStatus).getRevocationTime());
							logger.warn("The Certificate was revoked!revokedTime:{}", gdcaCertResp.getRevokeTime());
						} else if (certStatus instanceof UnknownStatus) {
							gdcaCertResp.setStatus(OcspResp.CERT_STATUS_UNKNOWN);
						}
					}
				}
			}

到这里就完成了获取服务器证书和验证服务器证书的操作了。

 

发布了34 篇原创文章 · 获赞 4 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览