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