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