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