View Javadoc
1   /*
2    * Copyright 2017 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License, version
5    * 2.0 (the "License"); you may not use this file except in compliance with the
6    * License. You may obtain a copy of the License at:
7    *
8    * https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  
17  package io.netty.example.ocsp;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.net.HttpURLConnection;
24  import java.net.URI;
25  import java.net.URL;
26  import java.security.cert.X509Certificate;
27  import java.util.concurrent.TimeUnit;
28  
29  import javax.net.ssl.HttpsURLConnection;
30  
31  import org.bouncycastle.asn1.ASN1Encodable;
32  import org.bouncycastle.asn1.ASN1ObjectIdentifier;
33  import org.bouncycastle.asn1.ASN1Primitive;
34  import org.bouncycastle.asn1.BERTags;
35  import org.bouncycastle.asn1.DERTaggedObject;
36  import org.bouncycastle.asn1.DLSequence;
37  import org.bouncycastle.asn1.x509.Extension;
38  import org.bouncycastle.cert.ocsp.OCSPReq;
39  import org.bouncycastle.cert.ocsp.OCSPResp;
40  import org.bouncycastle.x509.extension.X509ExtensionUtil;
41  
42  import io.netty.util.CharsetUtil;
43  
44  public final class OcspUtils {
45      /**
46       * The OID for OCSP responder URLs.
47       *
48       * https://www.alvestrand.no/objectid/1.3.6.1.5.5.7.48.1.html
49       */
50      private static final ASN1ObjectIdentifier OCSP_RESPONDER_OID
51          = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1").intern();
52  
53      private static final String OCSP_REQUEST_TYPE = "application/ocsp-request";
54  
55      private static final String OCSP_RESPONSE_TYPE = "application/ocsp-response";
56  
57      private OcspUtils() {
58      }
59  
60      /**
61       * Returns the OCSP responder {@link URI} or {@code null} if it doesn't have one.
62       */
63      public static URI ocspUri(X509Certificate certificate) throws IOException {
64          byte[] value = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
65          if (value == null) {
66              return null;
67          }
68  
69          ASN1Primitive authorityInfoAccess = X509ExtensionUtil.fromExtensionValue(value);
70          if (!(authorityInfoAccess instanceof DLSequence)) {
71              return null;
72          }
73  
74          DLSequence aiaSequence = (DLSequence) authorityInfoAccess;
75          DERTaggedObject taggedObject = findObject(aiaSequence, OCSP_RESPONDER_OID, DERTaggedObject.class);
76          if (taggedObject == null) {
77              return null;
78          }
79  
80          if (taggedObject.getTagNo() != BERTags.OBJECT_IDENTIFIER) {
81              return null;
82          }
83  
84          byte[] encoded = taggedObject.getEncoded();
85          int length = (int) encoded[1] & 0xFF;
86          String uri = new String(encoded, 2, length, CharsetUtil.UTF_8);
87          return URI.create(uri);
88      }
89  
90      private static <T> T findObject(DLSequence sequence, ASN1ObjectIdentifier oid, Class<T> type) {
91          for (ASN1Encodable element : sequence) {
92              if (!(element instanceof DLSequence)) {
93                  continue;
94              }
95  
96              DLSequence subSequence = (DLSequence) element;
97              if (subSequence.size() != 2) {
98                  continue;
99              }
100 
101             ASN1Encodable key = subSequence.getObjectAt(0);
102             ASN1Encodable value = subSequence.getObjectAt(1);
103 
104             if (key.equals(oid) && type.isInstance(value)) {
105                 return type.cast(value);
106             }
107         }
108 
109         return null;
110     }
111 
112     /**
113      * TODO: This is a very crude and non-scalable HTTP client to fetch the OCSP response from the
114      * CA's OCSP responder server. It's meant to demonstrate the basic building blocks on how to
115      * interact with the responder server and you should consider using Netty's HTTP client instead.
116      */
117     public static OCSPResp request(URI uri, OCSPReq request, long timeout, TimeUnit unit) throws IOException {
118         byte[] encoded = request.getEncoded();
119 
120         URL url = uri.toURL();
121         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
122         try {
123             connection.setConnectTimeout((int) unit.toMillis(timeout));
124             connection.setReadTimeout((int) unit.toMillis(timeout));
125             connection.setDoOutput(true);
126             connection.setDoInput(true);
127             connection.setRequestMethod("POST");
128             connection.setRequestProperty("host", uri.getHost());
129             connection.setRequestProperty("content-type", OCSP_REQUEST_TYPE);
130             connection.setRequestProperty("accept", OCSP_RESPONSE_TYPE);
131             connection.setRequestProperty("content-length", String.valueOf(encoded.length));
132 
133             OutputStream out = connection.getOutputStream();
134             try {
135                 out.write(encoded);
136                 out.flush();
137 
138                 InputStream in = connection.getInputStream();
139                 try {
140                     int code = connection.getResponseCode();
141                     if (code != HttpsURLConnection.HTTP_OK) {
142                         throw new IOException("Unexpected status-code=" + code);
143                     }
144 
145                     String contentType = connection.getContentType();
146                     if (!contentType.equalsIgnoreCase(OCSP_RESPONSE_TYPE)) {
147                         throw new IOException("Unexpected content-type=" + contentType);
148                     }
149 
150                     int contentLength = connection.getContentLength();
151                     if (contentLength == -1) {
152                         // Probably a terrible idea!
153                         contentLength = Integer.MAX_VALUE;
154                     }
155 
156                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
157                     try {
158                         byte[] buffer = new byte[8192];
159                         int length = -1;
160 
161                         while ((length = in.read(buffer)) != -1) {
162                             baos.write(buffer, 0, length);
163 
164                             if (baos.size() >= contentLength) {
165                                 break;
166                             }
167                         }
168                     } finally {
169                         baos.close();
170                     }
171                     return new OCSPResp(baos.toByteArray());
172                 } finally {
173                     in.close();
174                 }
175             } finally {
176                 out.close();
177             }
178         } finally {
179             connection.disconnect();
180         }
181     }
182 }