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