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.DLTaggedObject;
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          DLTaggedObject taggedObject = findObject(aiaSequence, OCSP_RESPONDER_OID, DLTaggedObject.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 = 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             try (OutputStream out = connection.getOutputStream()) {
134                 out.write(encoded);
135                 out.flush();
136 
137                 try (InputStream in = connection.getInputStream()) {
138                     int code = connection.getResponseCode();
139                     if (code != HttpsURLConnection.HTTP_OK) {
140                         throw new IOException("Unexpected status-code=" + code);
141                     }
142 
143                     String contentType = connection.getContentType();
144                     if (!contentType.equalsIgnoreCase(OCSP_RESPONSE_TYPE)) {
145                         throw new IOException("Unexpected content-type=" + contentType);
146                     }
147 
148                     int contentLength = connection.getContentLength();
149                     if (contentLength == -1) {
150                         // Probably a terrible idea!
151                         contentLength = Integer.MAX_VALUE;
152                     }
153 
154                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
155                     byte[] buffer = new byte[8192];
156                     int length;
157 
158                     while ((length = in.read(buffer)) != -1) {
159                         baos.write(buffer, 0, length);
160 
161                         if (baos.size() >= contentLength) {
162                             break;
163                         }
164                     }
165                     return new OCSPResp(baos.toByteArray());
166                 }
167             }
168         } finally {
169             connection.disconnect();
170         }
171     }
172 }