1 /* 2 * Copyright (C) 2004-2008 Jive Software. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.jivesoftware.openfire.net; 17 18 import java.io.IOException; 19 import java.io.InputStream; 20 import java.io.OutputStream; 21 import java.math.BigInteger; 22 import java.net.HttpURLConnection; 23 import java.net.MalformedURLException; 24 import java.net.URL; 25 import java.security.cert.CertPath; 26 import java.security.cert.CertPathValidatorException; 27 import java.security.cert.CertStore; 28 import java.security.cert.Certificate; 29 import java.security.cert.PKIXCertPathChecker; 30 import java.security.cert.PKIXParameters; 31 import java.security.cert.TrustAnchor; 32 import java.security.cert.X509CertSelector; 33 import java.security.cert.X509Certificate; 34 35 import java.util.Collection; 36 import java.util.Collections; 37 import java.util.Iterator; 38 import java.util.List; 39 import java.util.Set; 40 41 import javax.security.auth.x500.X500Principal; 42 43 import org.bouncycastle.cert.ocsp.BasicOCSPResp; 44 import org.bouncycastle.cert.ocsp.CertificateID; 45 import org.bouncycastle.cert.ocsp.CertificateStatus; 46 import org.bouncycastle.cert.ocsp.OCSPReq; 47 import org.bouncycastle.cert.ocsp.OCSPReqBuilder; 48 import org.bouncycastle.cert.ocsp.OCSPResp; 49 import org.bouncycastle.cert.ocsp.SingleResp; 50 import org.bouncycastle.cert.X509CertificateHolder; 51 import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; 52 import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; 53 54 import org.jivesoftware.util.JiveGlobals; 55 import org.slf4j.Logger; 56 import org.slf4j.LoggerFactory; 57 58 /** 59 * A <code>PKIXCertPathChecker</code> that uses 60 * Online Certificate Status Protocol (OCSP) 61 * 62 * See <a href="http://www.ietf.org/rfc/rfc2560.txt">RFC 2560</a>. 63 * 64 * @author Jay Kline 65 */ 66 public class OCSPChecker extends PKIXCertPathChecker { 67 68 private static final Logger Log = LoggerFactory.getLogger(OCSPChecker.class); 69 70 private static String ocspServerUrl = JiveGlobals.getProperty("ocsp.responderURL"); 71 private static String ocspServerSubject = JiveGlobals.getProperty("ocsp.responderCertSubjectName"); 72 private static final boolean dump = true; 73 private int certIndex; 74 private X509Certificate[] certs; 75 private CertPath cp; 76 private PKIXParameters pkixParams; 77 OCSPChecker(CertPath certPath, PKIXParameters pkixParams)78 OCSPChecker(CertPath certPath, PKIXParameters pkixParams) 79 throws CertPathValidatorException { 80 81 this.cp = certPath; 82 this.pkixParams = pkixParams; 83 List<? extends Certificate> tmp = cp.getCertificates(); 84 certs = 85 (X509Certificate[]) tmp.toArray(new X509Certificate[tmp.size()]); 86 init(false); 87 } 88 89 @Override init(boolean forward)90 public void init(boolean forward) throws CertPathValidatorException { 91 if (!forward) { 92 certIndex = certs.length - 1; 93 } else { 94 throw new CertPathValidatorException( 95 "Forward checking not supported"); 96 } 97 } 98 99 @Override isForwardCheckingSupported()100 public boolean isForwardCheckingSupported() { 101 return false; 102 } 103 104 @Override getSupportedExtensions()105 public Set<String> getSupportedExtensions() { 106 return Collections.<String>emptySet(); 107 } 108 109 @Override check(Certificate cert, Collection<String> unresolvedCritExts)110 public void check(Certificate cert, Collection<String> unresolvedCritExts) 111 throws CertPathValidatorException { 112 Log.debug("OCSPChecker: check called"); 113 InputStream in = null; 114 OutputStream out = null; 115 try { 116 // Examine OCSP properties 117 X509Certificate responderCert = null; 118 boolean haveResponderCert = true; //defaults to issuers cert 119 X500Principal responderSubjectName = null; 120 boolean haveIssuerCert = false; 121 122 // If we set the subject name, we need to find the certificate 123 if (ocspServerSubject != null) { 124 haveResponderCert = false; 125 responderSubjectName = new X500Principal(ocspServerSubject); 126 } 127 128 129 X509Certificate issuerCert = null; 130 X509Certificate currCert = (X509Certificate) cert; 131 132 // Set the issuer certificate if we were passed a chain 133 if (certIndex != 0) { 134 issuerCert = certs[certIndex]; 135 haveIssuerCert = true; 136 137 if (haveResponderCert) { 138 responderCert = certs[certIndex]; 139 } 140 } 141 142 143 if (!haveIssuerCert || !haveResponderCert) { 144 145 if (!haveResponderCert) { 146 Log.debug("OCSPChecker: Looking for responder's certificate"); 147 } 148 if (!haveIssuerCert) { 149 Log.debug("OCSPChecker: Looking for issuer's certificate"); 150 } 151 152 // Extract the anchor certs 153 Iterator anchors = pkixParams.getTrustAnchors().iterator(); 154 if (!anchors.hasNext()) { 155 throw new CertPathValidatorException( 156 "Must specify at least one trust anchor"); 157 } 158 159 X500Principal certIssuerName = 160 currCert.getIssuerX500Principal(); 161 while (anchors.hasNext() && 162 (!haveIssuerCert || !haveResponderCert)) { 163 164 TrustAnchor anchor = (TrustAnchor) anchors.next(); 165 X509Certificate anchorCert = anchor.getTrustedCert(); 166 X500Principal anchorSubjectName = 167 anchorCert.getSubjectX500Principal(); 168 169 // Check if this anchor cert is the issuer cert 170 if (!haveIssuerCert && certIssuerName.equals(anchorSubjectName)) { 171 172 issuerCert = anchorCert; 173 haveIssuerCert = true; 174 175 //If we have not set the responderCert at this point, set it to the issuer 176 if (haveResponderCert && responderCert == null) { 177 responderCert = anchorCert; 178 Log.debug("OCSPChecker: Responder's certificate = issuer certificate"); 179 } 180 } 181 182 // Check if this anchor cert is the responder cert 183 if (!haveResponderCert) { 184 if (responderSubjectName != null && 185 responderSubjectName.equals(anchorSubjectName)) { 186 187 responderCert = anchorCert; 188 haveResponderCert = true; 189 } 190 } 191 } 192 193 if (issuerCert == null) { 194 //No trust anchor was found matching the issuer 195 throw new CertPathValidatorException("No trusted certificate for " + currCert.getIssuerDN()); 196 } 197 198 // Check cert stores if responder cert has not yet been found 199 if (!haveResponderCert) { 200 Log.debug("OCSPChecker: Searching cert stores for responder's certificate"); 201 202 if (responderSubjectName != null) { 203 X509CertSelector filter = new X509CertSelector(); 204 filter.setSubject(responderSubjectName.getName()); 205 206 List<CertStore> certStores = pkixParams.getCertStores(); 207 for (CertStore certStore : certStores) { 208 Iterator i = certStore.getCertificates(filter).iterator(); 209 if (i.hasNext()) { 210 responderCert = (X509Certificate) i.next(); 211 haveResponderCert = true; 212 break; 213 } 214 } 215 } 216 } 217 } 218 219 // Could not find the responder cert 220 if (!haveResponderCert) { 221 throw new CertPathValidatorException("Cannot find the responder's certificate."); 222 } 223 224 // Construct an OCSP Request 225 OCSPReqBuilder gen = new OCSPReqBuilder(); 226 227 CertificateID certID = new CertificateID(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build().get(CertificateID.HASH_SHA1), new X509CertificateHolder(issuerCert.getEncoded()), currCert.getSerialNumber()); 228 gen.addRequest(certID); 229 OCSPReq ocspRequest = gen.build(); 230 231 232 URL url; 233 if (ocspServerUrl != null) { 234 try { 235 url = new URL(ocspServerUrl); 236 } catch (MalformedURLException e) { 237 throw new CertPathValidatorException(e); 238 } 239 } else { 240 throw new CertPathValidatorException("Must set OCSP Server URL"); 241 } 242 HttpURLConnection con = (HttpURLConnection) url.openConnection(); 243 Log.debug("OCSPChecker: connecting to OCSP service at: " + url); 244 245 con.setDoOutput(true); 246 con.setDoInput(true); 247 con.setRequestMethod("POST"); 248 con.setRequestProperty("Content-type", "application/ocsp-request"); 249 con.setRequestProperty("Accept","application/ocsp-response"); 250 byte[] bytes = ocspRequest.getEncoded(); 251 252 253 con.setRequestProperty("Content-length", String.valueOf(bytes.length)); 254 out = con.getOutputStream(); 255 out.write(bytes); 256 out.flush(); 257 258 // Check the response 259 if (con.getResponseCode() != HttpURLConnection.HTTP_OK) { 260 Log.debug("OCSPChecker: Received HTTP error: " + con.getResponseCode() + 261 " - " + con.getResponseMessage()); 262 } 263 in = con.getInputStream(); 264 OCSPResp ocspResponse = new OCSPResp(in); 265 BigInteger serialNumber = currCert.getSerialNumber(); 266 BasicOCSPResp brep = (BasicOCSPResp) ocspResponse.getResponseObject(); 267 try { 268 if( ! brep.isSignatureValid(new JcaContentVerifierProviderBuilder().setProvider("BC").build(responderCert.getPublicKey()))) { 269 throw new CertPathValidatorException("OCSP response is not verified"); 270 } 271 } catch (Exception e) { 272 throw new CertPathValidatorException("OCSP response could not be verified ("+e.getMessage()+")" ,null, cp, certIndex); 273 } 274 SingleResp[] singleResp = brep.getResponses(); 275 boolean foundResponse = false; 276 for (SingleResp resp : singleResp) { 277 CertificateID respCertID = resp.getCertID(); 278 if (respCertID.equals(certID)) { 279 Object status = resp.getCertStatus(); 280 if (status == CertificateStatus.GOOD) { 281 Log.debug("OCSPChecker: Status of certificate (with serial number " + 282 serialNumber.toString() + ") is: good"); 283 foundResponse = true; 284 break; 285 } else if (status instanceof org.bouncycastle.cert.ocsp.RevokedStatus) { 286 Log.debug("OCSPChecker: Status of certificate (with serial number " + 287 serialNumber.toString() + ") is: revoked"); 288 throw new CertPathValidatorException("Certificate has been revoked", null, cp, certIndex); 289 } else if (status instanceof org.bouncycastle.cert.ocsp.UnknownStatus) { 290 Log.debug("OCSPChecker: Status of certificate (with serial number " + 291 serialNumber.toString() + ") is: unknown"); 292 throw new CertPathValidatorException("Certificate's revocation status is unknown", null, cp, certIndex); 293 } else { 294 Log.debug("Status of certificate (with serial number " + 295 serialNumber.toString() + ") is: not recognized"); 296 throw new CertPathValidatorException("Unknown OCSP response for certificate", null, cp, certIndex); 297 } 298 } 299 } 300 301 // Check that response applies to the cert that was supplied 302 if (!foundResponse) { 303 throw new CertPathValidatorException( 304 "No certificates in the OCSP response match the " + 305 "certificate supplied in the OCSP request."); 306 } 307 } catch (CertPathValidatorException cpve) { 308 throw cpve; 309 } catch (Exception e) { 310 throw new CertPathValidatorException(e); 311 } finally { 312 if (in != null) { 313 try { 314 in.close(); 315 } catch (IOException ioe) { 316 throw new CertPathValidatorException(ioe); 317 } 318 } 319 if (out != null) { 320 try { 321 out.close(); 322 } catch (IOException ioe) { 323 throw new CertPathValidatorException(ioe); 324 } 325 } 326 } 327 } 328 } 329