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    * 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, 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.BufferedReader;
20  import java.io.FileNotFoundException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.io.Reader;
24  import java.math.BigInteger;
25  import java.net.URI;
26  import java.security.PrivateKey;
27  import java.security.cert.X509Certificate;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.concurrent.TimeUnit;
31  
32  import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
33  import org.bouncycastle.cert.X509CertificateHolder;
34  import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
35  import org.bouncycastle.cert.ocsp.BasicOCSPResp;
36  import org.bouncycastle.cert.ocsp.CertificateStatus;
37  import org.bouncycastle.cert.ocsp.OCSPReq;
38  import org.bouncycastle.cert.ocsp.OCSPResp;
39  import org.bouncycastle.cert.ocsp.SingleResp;
40  import org.bouncycastle.jce.provider.BouncyCastleProvider;
41  import org.bouncycastle.openssl.PEMParser;
42  
43  import io.netty.bootstrap.ServerBootstrap;
44  import io.netty.channel.Channel;
45  import io.netty.channel.ChannelInitializer;
46  import io.netty.channel.ChannelPipeline;
47  import io.netty.handler.ssl.OpenSsl;
48  import io.netty.handler.ssl.ReferenceCountedOpenSslContext;
49  import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
50  import io.netty.handler.ssl.SslContextBuilder;
51  import io.netty.handler.ssl.SslHandler;
52  import io.netty.handler.ssl.SslProvider;
53  import io.netty.util.CharsetUtil;
54  
55  /**
56   * ATTENTION: This is an incomplete example! In order to provide a fully functional
57   * end-to-end example we'd need an X.509 certificate and the matching PrivateKey.
58   */
59  @SuppressWarnings("unused")
60  public class OcspServerExample {
61      public static void main(String[] args) throws Exception {
62          // We assume there's a private key.
63          PrivateKey privateKey = null;
64  
65          // Step 1: Load the certificate chain for netty.io. We'll need the certificate
66          // and the issuer's certificate and we don't need any of the intermediate certs.
67          // The array is assumed to be a certain order to keep things simple.
68          X509Certificate[] keyCertChain = parseCertificates(OcspServerExample.class, "netty_io_chain.pem");
69  
70          X509Certificate certificate = keyCertChain[0];
71          X509Certificate issuer = keyCertChain[keyCertChain.length - 1];
72  
73          // Step 2: We need the URL of the CA's OCSP responder server. It's somewhere encoded
74          // into the certificate! Notice that it's an HTTP URL.
75          URI uri = OcspUtils.ocspUri(certificate);
76          System.out.println("OCSP Responder URI: " + uri);
77  
78          if (uri == null) {
79              throw new IllegalStateException("The CA/certificate doesn't have an OCSP responder");
80          }
81  
82          // Step 3: Construct the OCSP request
83          OCSPReq request = new OcspRequestBuilder()
84                  .certificate(certificate)
85                  .issuer(issuer)
86                  .build();
87  
88          // Step 4: Do the request to the CA's OCSP responder
89          OCSPResp response = OcspUtils.request(uri, request, 5L, TimeUnit.SECONDS);
90          if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
91              throw new IllegalStateException("response-status=" + response.getStatus());
92          }
93  
94          // Step 5: Is my certificate any good or has the CA revoked it?
95          BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
96          SingleResp first = basicResponse.getResponses()[0];
97  
98          CertificateStatus status = first.getCertStatus();
99          System.out.println("Status: " + (status == CertificateStatus.GOOD ? "Good" : status));
100         System.out.println("This Update: " + first.getThisUpdate());
101         System.out.println("Next Update: " + first.getNextUpdate());
102 
103         if (status != null) {
104             throw new IllegalStateException("certificate-status=" + status);
105         }
106 
107         BigInteger certSerial = certificate.getSerialNumber();
108         BigInteger ocspSerial = first.getCertID().getSerialNumber();
109         if (!certSerial.equals(ocspSerial)) {
110             throw new IllegalStateException("Bad Serials=" + certSerial + " vs. " + ocspSerial);
111         }
112 
113         // Step 6: Cache the OCSP response and use it as long as it's not
114         // expired. The exact semantics are beyond the scope of this example.
115 
116         if (!OpenSsl.isAvailable()) {
117             throw new IllegalStateException("OpenSSL is not available!");
118         }
119 
120         if (!OpenSsl.isOcspSupported()) {
121             throw new IllegalStateException("OCSP is not supported!");
122         }
123 
124         if (privateKey == null) {
125             throw new IllegalStateException("Because we don't have a PrivateKey we can't continue past this point.");
126         }
127 
128         ReferenceCountedOpenSslContext context
129             = (ReferenceCountedOpenSslContext) SslContextBuilder.forServer(privateKey, keyCertChain)
130                 .sslProvider(SslProvider.OPENSSL)
131                 .enableOcsp(true)
132                 .build();
133 
134         try {
135             ServerBootstrap bootstrap = new ServerBootstrap()
136                     .childHandler(newServerHandler(context, response));
137 
138             // so on and so forth...
139         } finally {
140             context.release();
141         }
142     }
143 
144     private static ChannelInitializer<Channel> newServerHandler(final ReferenceCountedOpenSslContext context,
145             final OCSPResp response) {
146         return new ChannelInitializer<Channel>() {
147             @Override
148             protected void initChannel(Channel ch) throws Exception {
149                 SslHandler sslHandler = context.newHandler(ch.alloc());
150 
151                 if (response != null) {
152                     ReferenceCountedOpenSslEngine engine
153                         = (ReferenceCountedOpenSslEngine) sslHandler.engine();
154 
155                     engine.setOcspResponse(response.getEncoded());
156                 }
157 
158                 ChannelPipeline pipeline = ch.pipeline();
159                 pipeline.addLast(sslHandler);
160 
161                 // so on and so forth...
162             }
163         };
164     }
165 
166     private static X509Certificate[] parseCertificates(Class<?> clazz, String name) throws Exception {
167         InputStream in = clazz.getResourceAsStream(name);
168         if (in == null) {
169             throw new FileNotFoundException("clazz=" + clazz + ", name=" + name);
170         }
171 
172         try {
173             BufferedReader reader = new BufferedReader(new InputStreamReader(in, CharsetUtil.US_ASCII));
174             try {
175                 return parseCertificates(reader);
176             } finally {
177                 reader.close();
178             }
179         } finally {
180             in.close();
181         }
182     }
183 
184     private static X509Certificate[] parseCertificates(Reader reader) throws Exception {
185 
186         JcaX509CertificateConverter converter = new JcaX509CertificateConverter()
187                 .setProvider(new BouncyCastleProvider());
188 
189         List<X509Certificate> dst = new ArrayList<X509Certificate>();
190 
191         PEMParser parser = new PEMParser(reader);
192         try {
193           X509CertificateHolder holder = null;
194 
195           while ((holder = (X509CertificateHolder) parser.readObject()) != null) {
196             X509Certificate certificate = converter.getCertificate(holder);
197             if (certificate == null) {
198               continue;
199             }
200 
201             dst.add(certificate);
202           }
203         } finally {
204             parser.close();
205         }
206 
207         return dst.toArray(new X509Certificate[0]);
208     }
209 }