OCSP stapling support for Netty using netty-tcnative.
https://github.com/netty/netty-tcnative/pull/215 Motivation OCSP stapling (formally known as TLS Certificate Status Request extension) is alternative approach for checking the revocation status of X.509 Certificates. Servers can preemptively fetch the OCSP response from the CA's responder, cache it for some period of time, and pass it along during (a.k.a. staple) the TLS handshake. The client no longer has to reach out on its own to the CA to check the validity of a cetitficate. Some of the key benefits are: 1) Speed. The client doesn't have to crosscheck the certificate. 2) Efficiency. The Internet is no longer DDoS'ing the CA's OCSP responder servers. 3) Safety. Less operational dependence on the CA. Certificate owners can sustain short CA outages. 4) Privacy. The CA can lo longer track the users of a certificate. https://en.wikipedia.org/wiki/OCSP_stapling https://letsencrypt.org/2016/10/24/squarespace-ocsp-impl.html Modifications https://www.openssl.org/docs/man1.0.2/ssl/SSL_set_tlsext_status_type.html Result High-level API to enable OCSP stapling
This commit is contained in:
parent
34e0007f07
commit
077a1988b9
@ -129,6 +129,16 @@
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Needed for OCSP -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
81
example/src/main/java/io/netty/example/ocsp/Digester.java
Normal file
81
example/src/main/java/io/netty/example/ocsp/Digester.java
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.ocsp;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.io.DigestOutputStream;
|
||||
import org.bouncycastle.operator.DigestCalculator;
|
||||
|
||||
/**
|
||||
* BC's {@link OCSPReqBuilder} needs a {@link DigestCalculator} but BC doesn't
|
||||
* provide any public implementations of that interface. That's why we need to
|
||||
* write our own. There's a default SHA-1 implementation and one for SHA-256.
|
||||
* Which one to use will depend on the Certificate Authority (CA).
|
||||
*/
|
||||
public final class Digester implements DigestCalculator {
|
||||
|
||||
public static DigestCalculator sha1() {
|
||||
Digest digest = new SHA1Digest();
|
||||
AlgorithmIdentifier algId = new AlgorithmIdentifier(
|
||||
OIWObjectIdentifiers.idSHA1);
|
||||
|
||||
return new Digester(digest, algId);
|
||||
}
|
||||
|
||||
public static DigestCalculator sha256() {
|
||||
Digest digest = new SHA256Digest();
|
||||
|
||||
// The OID for SHA-256: http://www.oid-info.com/get/2.16.840.1.101.3.4.2.1
|
||||
ASN1ObjectIdentifier oid = new ASN1ObjectIdentifier(
|
||||
"2.16.840.1.101.3.4.2.1").intern();
|
||||
AlgorithmIdentifier algId = new AlgorithmIdentifier(oid);
|
||||
|
||||
return new Digester(digest, algId);
|
||||
}
|
||||
|
||||
private final DigestOutputStream dos;
|
||||
|
||||
private final AlgorithmIdentifier algId;
|
||||
|
||||
private Digester(Digest digest, AlgorithmIdentifier algId) {
|
||||
this.dos = new DigestOutputStream(digest);
|
||||
this.algId = algId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlgorithmIdentifier getAlgorithmIdentifier() {
|
||||
return algId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getOutputStream() {
|
||||
return dos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getDigest() {
|
||||
return dos.getDigest();
|
||||
}
|
||||
}
|
@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.ocsp;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.security.cert.X509Certificate;
|
||||
|
||||
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
|
||||
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
|
||||
import org.bouncycastle.cert.ocsp.CertificateStatus;
|
||||
import org.bouncycastle.cert.ocsp.OCSPResp;
|
||||
import org.bouncycastle.cert.ocsp.SingleResp;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpClientCodec;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslContext;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.ocsp.OcspClientHandler;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import io.netty.util.concurrent.Promise;
|
||||
|
||||
/**
|
||||
* This is a very simple example for a HTTPS client that uses OCSP stapling.
|
||||
* The client connects to a HTTPS server that has OCSP stapling enabled and
|
||||
* then uses BC to parse and validate it.
|
||||
*/
|
||||
public class OcspClientExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (!OpenSsl.isAvailable()) {
|
||||
throw new IllegalStateException("OpenSSL is not available!");
|
||||
}
|
||||
|
||||
if (!OpenSsl.isOcspSupported()) {
|
||||
throw new IllegalStateException("OCSP is not supported!");
|
||||
}
|
||||
|
||||
// Using Wikipedia as an example. I'd rather use Netty's own website
|
||||
// but the server (Cloudflare) doesn't support OCSP stapling. A few
|
||||
// other examples could be Microsoft or Squarespace. Use OpenSSL's
|
||||
// CLI client to assess if a server supports OCSP stapling. E.g.:
|
||||
//
|
||||
// openssl s_client -tlsextdebug -status -connect www.squarespace.com:443
|
||||
//
|
||||
String host = "www.wikipedia.org";
|
||||
|
||||
ReferenceCountedOpenSslContext context
|
||||
= (ReferenceCountedOpenSslContext) SslContextBuilder.forClient()
|
||||
.sslProvider(SslProvider.OPENSSL)
|
||||
.enableOcsp(true)
|
||||
.build();
|
||||
|
||||
try {
|
||||
EventLoopGroup group = new NioEventLoopGroup();
|
||||
try {
|
||||
Promise<FullHttpResponse> promise = group.next().newPromise();
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(group)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5 * 1000)
|
||||
.handler(newClientHandler(context, host, promise));
|
||||
|
||||
Channel channel = bootstrap.connect(host, 443)
|
||||
.syncUninterruptibly()
|
||||
.channel();
|
||||
|
||||
try {
|
||||
FullHttpResponse response = promise.get();
|
||||
ReferenceCountUtil.release(response);
|
||||
} finally {
|
||||
channel.close();
|
||||
}
|
||||
} finally {
|
||||
group.shutdownGracefully();
|
||||
}
|
||||
} finally {
|
||||
context.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static ChannelInitializer<Channel> newClientHandler(final ReferenceCountedOpenSslContext context,
|
||||
final String host, final Promise<FullHttpResponse> promise) {
|
||||
|
||||
return new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
SslHandler sslHandler = context.newHandler(ch.alloc());
|
||||
ReferenceCountedOpenSslEngine engine
|
||||
= (ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(sslHandler);
|
||||
pipeline.addLast(new ExampleOcspClientHandler(engine));
|
||||
|
||||
pipeline.addLast(new HttpClientCodec());
|
||||
pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
|
||||
pipeline.addLast(new HttpClientHandler(host, promise));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!promise.isDone()) {
|
||||
promise.tryFailure(new IllegalStateException("Connection closed and Promise was not done."));
|
||||
}
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (!promise.tryFailure(cause)) {
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static class HttpClientHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final String host;
|
||||
|
||||
private final Promise<FullHttpResponse> promise;
|
||||
|
||||
public HttpClientHandler(String host, Promise<FullHttpResponse> promise) {
|
||||
this.host = host;
|
||||
this.promise = promise;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
|
||||
request.headers().set(HttpHeaderNames.HOST, host);
|
||||
request.headers().set(HttpHeaderNames.USER_AGENT, "netty-ocsp-example/1.0");
|
||||
|
||||
ctx.writeAndFlush(request).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
|
||||
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
if (!promise.isDone()) {
|
||||
promise.tryFailure(new IllegalStateException("Connection closed and Promise was not done."));
|
||||
}
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (msg instanceof FullHttpResponse) {
|
||||
if (!promise.trySuccess((FullHttpResponse) msg)) {
|
||||
ReferenceCountUtil.release(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
if (!promise.tryFailure(cause)) {
|
||||
ctx.fireExceptionCaught(cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExampleOcspClientHandler extends OcspClientHandler {
|
||||
|
||||
public ExampleOcspClientHandler(ReferenceCountedOpenSslEngine engine) {
|
||||
super(engine);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception {
|
||||
byte[] staple = engine.getOcspResponse();
|
||||
if (staple == null) {
|
||||
throw new IllegalStateException("Server didn't provide an OCSP staple!");
|
||||
}
|
||||
|
||||
OCSPResp response = new OCSPResp(staple);
|
||||
if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SSLSession session = engine.getSession();
|
||||
X509Certificate[] chain = session.getPeerCertificateChain();
|
||||
BigInteger certSerial = chain[0].getSerialNumber();
|
||||
|
||||
BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
|
||||
SingleResp first = basicResponse.getResponses()[0];
|
||||
|
||||
// ATTENTION: CertificateStatus.GOOD is actually a null value! Do not use
|
||||
// equals() or you'll NPE!
|
||||
CertificateStatus status = first.getCertStatus();
|
||||
BigInteger ocspSerial = first.getCertID().getSerialNumber();
|
||||
String message = new StringBuilder()
|
||||
.append("OCSP status of ").append(ctx.channel().remoteAddress())
|
||||
.append("\n Status: ").append(status == CertificateStatus.GOOD ? "Good" : status)
|
||||
.append("\n This Update: ").append(first.getThisUpdate())
|
||||
.append("\n Next Update: ").append(first.getNextUpdate())
|
||||
.append("\n Cert Serial: ").append(certSerial)
|
||||
.append("\n OCSP Serial: ").append(ocspSerial)
|
||||
.toString();
|
||||
System.out.println(message);
|
||||
|
||||
return status == CertificateStatus.GOOD && certSerial.equals(ocspSerial);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.ocsp;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import org.bouncycastle.asn1.DEROctetString;
|
||||
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.Extensions;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.ocsp.CertificateID;
|
||||
import org.bouncycastle.cert.ocsp.OCSPException;
|
||||
import org.bouncycastle.cert.ocsp.OCSPReq;
|
||||
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
|
||||
import org.bouncycastle.operator.DigestCalculator;
|
||||
|
||||
/**
|
||||
* This is a simplified version of BC's own {@link OCSPReqBuilder}.
|
||||
*
|
||||
* @see OCSPReqBuilder
|
||||
*/
|
||||
public class OcspRequestBuilder {
|
||||
|
||||
private static final SecureRandom GENERATOR = new SecureRandom();
|
||||
|
||||
private SecureRandom generator = GENERATOR;
|
||||
|
||||
private DigestCalculator calculator = Digester.sha1();
|
||||
|
||||
private X509Certificate certificate;
|
||||
|
||||
private X509Certificate issuer;
|
||||
|
||||
public OcspRequestBuilder generator(SecureRandom generator) {
|
||||
this.generator = generator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OcspRequestBuilder calculator(DigestCalculator calculator) {
|
||||
this.calculator = calculator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OcspRequestBuilder certificate(X509Certificate certificate) {
|
||||
this.certificate = certificate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public OcspRequestBuilder issuer(X509Certificate issuer) {
|
||||
this.issuer = issuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: The returned {@link OCSPReq} is not re-usable/cacheable! It contains a one-time nonce
|
||||
* and CA's will (should) reject subsequent requests that have the same nonce value.
|
||||
*/
|
||||
public OCSPReq build() throws OCSPException, IOException, CertificateEncodingException {
|
||||
SecureRandom generator = checkNotNull(this.generator, "generator");
|
||||
DigestCalculator calculator = checkNotNull(this.calculator, "calculator");
|
||||
X509Certificate certificate = checkNotNull(this.certificate, "certificate");
|
||||
X509Certificate issuer = checkNotNull(this.issuer, "issuer");
|
||||
|
||||
BigInteger serial = certificate.getSerialNumber();
|
||||
|
||||
CertificateID certId = new CertificateID(calculator,
|
||||
new X509CertificateHolder(issuer.getEncoded()), serial);
|
||||
|
||||
OCSPReqBuilder builder = new OCSPReqBuilder();
|
||||
builder.addRequest(certId);
|
||||
|
||||
byte[] nonce = new byte[8];
|
||||
generator.nextBytes(nonce);
|
||||
|
||||
Extension[] extensions = new Extension[] {
|
||||
new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false,
|
||||
new DEROctetString(nonce)) };
|
||||
|
||||
builder.setRequestExtensions(new Extensions(extensions));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.ocsp;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
|
||||
import org.bouncycastle.cert.ocsp.CertificateStatus;
|
||||
import org.bouncycastle.cert.ocsp.OCSPReq;
|
||||
import org.bouncycastle.cert.ocsp.OCSPResp;
|
||||
import org.bouncycastle.cert.ocsp.SingleResp;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslContext;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* ATTENTION: This is an incomplete example! In order to provide a fully functional
|
||||
* end-to-end example we'd need a X.509 certificate and the matching PrivateKey.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class OcspServerExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// We assume there's a private key.
|
||||
PrivateKey privateKey = null;
|
||||
|
||||
// Step 1: Load the certificate chain for netty.io. We'll need the certificate
|
||||
// and the issuer's certificate and we don't need any of the intermediate certs.
|
||||
// The array is assumed to be a certain order to keep things simple.
|
||||
X509Certificate[] keyCertChain = parseCertificates(OcspServerExample.class, "netty_io_chain.pem");
|
||||
|
||||
X509Certificate certificate = keyCertChain[0];
|
||||
X509Certificate issuer = keyCertChain[keyCertChain.length - 1];
|
||||
|
||||
// Step 2: We need the URL of the CA's OCSP responder server. It's somewhere encoded
|
||||
// into the certificate! Notice that it's a HTTP URL.
|
||||
URI uri = OcspUtils.ocspUri(certificate);
|
||||
System.out.println("OCSP Responder URI: " + uri);
|
||||
|
||||
if (uri == null) {
|
||||
throw new IllegalStateException("The CA/certificate doesn't have an OCSP responder");
|
||||
}
|
||||
|
||||
// Step 3: Construct the OCSP request
|
||||
OCSPReq request = new OcspRequestBuilder()
|
||||
.certificate(certificate)
|
||||
.issuer(issuer)
|
||||
.build();
|
||||
|
||||
// Step 4: Do the request to the CA's OCSP responder
|
||||
OCSPResp response = OcspUtils.request(uri, request, 5L, TimeUnit.SECONDS);
|
||||
if (response.getStatus() != OCSPResponseStatus.SUCCESSFUL) {
|
||||
throw new IllegalStateException("response-status=" + response.getStatus());
|
||||
}
|
||||
|
||||
// Step 5: Is my certificate any good or has the CA revoked it?
|
||||
BasicOCSPResp basicResponse = (BasicOCSPResp) response.getResponseObject();
|
||||
SingleResp first = basicResponse.getResponses()[0];
|
||||
|
||||
CertificateStatus status = first.getCertStatus();
|
||||
System.out.println("Status: " + (status == CertificateStatus.GOOD ? "Good" : status));
|
||||
System.out.println("This Update: " + first.getThisUpdate());
|
||||
System.out.println("Next Update: " + first.getNextUpdate());
|
||||
|
||||
if (status != null) {
|
||||
throw new IllegalStateException("certificate-status=" + status);
|
||||
}
|
||||
|
||||
BigInteger certSerial = certificate.getSerialNumber();
|
||||
BigInteger ocspSerial = first.getCertID().getSerialNumber();
|
||||
if (!certSerial.equals(ocspSerial)) {
|
||||
throw new IllegalStateException("Bad Serials=" + certSerial + " vs. " + ocspSerial);
|
||||
}
|
||||
|
||||
// Step 6: Cache the OCSP response and use it as long as it's not
|
||||
// expired. The exact semantics are beyond the scope of this example.
|
||||
|
||||
if (!OpenSsl.isAvailable()) {
|
||||
throw new IllegalStateException("OpenSSL is not available!");
|
||||
}
|
||||
|
||||
if (!OpenSsl.isOcspSupported()) {
|
||||
throw new IllegalStateException("OCSP is not supported!");
|
||||
}
|
||||
|
||||
if (privateKey == null) {
|
||||
throw new IllegalStateException("Because we don't have a PrivateKey we can't continue past this point.");
|
||||
}
|
||||
|
||||
ReferenceCountedOpenSslContext context
|
||||
= (ReferenceCountedOpenSslContext) SslContextBuilder.forServer(privateKey, keyCertChain)
|
||||
.sslProvider(SslProvider.OPENSSL)
|
||||
.enableOcsp(true)
|
||||
.build();
|
||||
|
||||
try {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.childHandler(newServerHandler(context, response));
|
||||
|
||||
// so on and so forth...
|
||||
} finally {
|
||||
context.release();
|
||||
}
|
||||
}
|
||||
|
||||
private static ChannelInitializer<Channel> newServerHandler(final ReferenceCountedOpenSslContext context,
|
||||
final OCSPResp response) {
|
||||
return new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
SslHandler sslHandler = context.newHandler(ch.alloc());
|
||||
|
||||
if (response != null) {
|
||||
ReferenceCountedOpenSslEngine engine
|
||||
= (ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
|
||||
engine.setOcspResponse(response.getEncoded());
|
||||
}
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(sslHandler);
|
||||
|
||||
// so on and so forth...
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static X509Certificate[] parseCertificates(Class<?> clazz, String name) throws Exception {
|
||||
InputStream in = clazz.getResourceAsStream(name);
|
||||
if (in == null) {
|
||||
throw new FileNotFoundException("clazz=" + clazz + ", name=" + name);
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in, CharsetUtil.US_ASCII));
|
||||
try {
|
||||
return parseCertificates(reader);
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static X509Certificate[] parseCertificates(Reader reader) throws Exception {
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter()
|
||||
.setProvider(new BouncyCastleProvider());
|
||||
|
||||
List<X509Certificate> dst = new ArrayList<X509Certificate>();
|
||||
|
||||
PEMParser parser = new PEMParser(reader);
|
||||
try {
|
||||
X509CertificateHolder holder = null;
|
||||
|
||||
while ((holder = (X509CertificateHolder) parser.readObject()) != null) {
|
||||
X509Certificate certificate = converter.getCertificate(holder);
|
||||
if (certificate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
dst.add(certificate);
|
||||
}
|
||||
} finally {
|
||||
parser.close();
|
||||
}
|
||||
|
||||
return dst.toArray(new X509Certificate[0]);
|
||||
}
|
||||
}
|
182
example/src/main/java/io/netty/example/ocsp/OcspUtils.java
Normal file
182
example/src/main/java/io/netty/example/ocsp/OcspUtils.java
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.example.ocsp;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Encodable;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1Primitive;
|
||||
import org.bouncycastle.asn1.BERTags;
|
||||
import org.bouncycastle.asn1.DERTaggedObject;
|
||||
import org.bouncycastle.asn1.DLSequence;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.cert.ocsp.OCSPReq;
|
||||
import org.bouncycastle.cert.ocsp.OCSPResp;
|
||||
import org.bouncycastle.x509.extension.X509ExtensionUtil;
|
||||
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
public final class OcspUtils {
|
||||
/**
|
||||
* The OID for OCSP responder URLs.
|
||||
*
|
||||
* http://www.alvestrand.no/objectid/1.3.6.1.5.5.7.48.1.html
|
||||
*/
|
||||
private static final ASN1ObjectIdentifier OCSP_RESPONDER_OID
|
||||
= new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1").intern();
|
||||
|
||||
private static final String OCSP_REQUEST_TYPE = "application/ocsp-request";
|
||||
|
||||
private static final String OCSP_RESPONSE_TYPE = "application/ocsp-response";
|
||||
|
||||
private OcspUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OCSP responder {@link URI} or {@code null} if it doesn't have one.
|
||||
*/
|
||||
public static URI ocspUri(X509Certificate certificate) throws IOException {
|
||||
byte[] value = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ASN1Primitive authorityInfoAccess = X509ExtensionUtil.fromExtensionValue(value);
|
||||
if (!(authorityInfoAccess instanceof DLSequence)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DLSequence aiaSequence = (DLSequence) authorityInfoAccess;
|
||||
DERTaggedObject taggedObject = findObject(aiaSequence, OCSP_RESPONDER_OID, DERTaggedObject.class);
|
||||
if (taggedObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (taggedObject.getTagNo() != BERTags.OBJECT_IDENTIFIER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] encoded = taggedObject.getEncoded();
|
||||
int length = (int) encoded[1] & 0xFF;
|
||||
String uri = new String(encoded, 2, length, CharsetUtil.UTF_8);
|
||||
return URI.create(uri);
|
||||
}
|
||||
|
||||
private static <T> T findObject(DLSequence sequence, ASN1ObjectIdentifier oid, Class<T> type) {
|
||||
for (ASN1Encodable element : sequence) {
|
||||
if (!(element instanceof DLSequence)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DLSequence subSequence = (DLSequence) element;
|
||||
if (subSequence.size() != 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ASN1Encodable key = subSequence.getObjectAt(0);
|
||||
ASN1Encodable value = subSequence.getObjectAt(1);
|
||||
|
||||
if (key.equals(oid) && type.isInstance(value)) {
|
||||
return type.cast(value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: This is a very crude and non-scalable HTTP client to fetch the OCSP response from the
|
||||
* CA's OCSP responder server. It's meant to demonstrate the basic building blocks on how to
|
||||
* interact with the responder server and you should consider using Netty's HTTP client instead.
|
||||
*/
|
||||
public static OCSPResp request(URI uri, OCSPReq request, long timeout, TimeUnit unit) throws IOException {
|
||||
byte[] encoded = request.getEncoded();
|
||||
|
||||
URL url = uri.toURL();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
try {
|
||||
connection.setConnectTimeout((int) unit.toMillis(timeout));
|
||||
connection.setReadTimeout((int) unit.toMillis(timeout));
|
||||
connection.setDoOutput(true);
|
||||
connection.setDoInput(true);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("host", uri.getHost());
|
||||
connection.setRequestProperty("content-type", OCSP_REQUEST_TYPE);
|
||||
connection.setRequestProperty("accept", OCSP_RESPONSE_TYPE);
|
||||
connection.setRequestProperty("content-length", String.valueOf(encoded.length));
|
||||
|
||||
OutputStream out = connection.getOutputStream();
|
||||
try {
|
||||
out.write(encoded);
|
||||
out.flush();
|
||||
|
||||
InputStream in = connection.getInputStream();
|
||||
try {
|
||||
int code = connection.getResponseCode();
|
||||
if (code != HttpsURLConnection.HTTP_OK) {
|
||||
throw new IOException("Unexpected status-code=" + code);
|
||||
}
|
||||
|
||||
String contentType = connection.getContentType();
|
||||
if (!contentType.equalsIgnoreCase(OCSP_RESPONSE_TYPE)) {
|
||||
throw new IOException("Unexpected content-type=" + contentType);
|
||||
}
|
||||
|
||||
int contentLength = connection.getContentLength();
|
||||
if (contentLength == -1) {
|
||||
// Probably a terrible idea!
|
||||
contentLength = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try {
|
||||
byte[] buffer = new byte[8192];
|
||||
int length = -1;
|
||||
|
||||
while ((length = in.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, length);
|
||||
|
||||
if (baos.size() >= contentLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
baos.close();
|
||||
}
|
||||
return new OCSPResp(baos.toByteArray());
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} finally {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
The netty_io_chain.pem file is the cert chain of <https://netty.io>. The file
|
||||
was created using the browser's export functionality for certs. The cert was
|
||||
issued by COMODO (via Cloudflare) and its purpose is to demonstrate how to
|
||||
extract the CA's OCSP responder server URL from the certificate and then
|
||||
interact with the responder server to check the revocation status of the
|
||||
certificate. The cert will at some point expire or get revoked. It's probably
|
||||
a good idea to save it once that happens as it's an excellent example to
|
||||
demonstrate negative responses from the CA.
|
@ -0,0 +1,63 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIHIDCCBsagAwIBAgIQWBZ3c+IkpJfV6xSQfc79MzAKBggqhkjOPQQDAjCBkjEL
|
||||
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
|
||||
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxODA2BgNVBAMT
|
||||
L0NPTU9ETyBFQ0MgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQSAy
|
||||
MB4XDTE3MDIyMTAwMDAwMFoXDTE3MDgwNjIzNTk1OVowazEhMB8GA1UECxMYRG9t
|
||||
YWluIENvbnRyb2wgVmFsaWRhdGVkMSEwHwYDVQQLExhQb3NpdGl2ZVNTTCBNdWx0
|
||||
aS1Eb21haW4xIzAhBgNVBAMTGnNuaTQ5NjI5LmNsb3VkZmxhcmVzc2wuY29tMFkw
|
||||
EwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbBzbKRFsB8VD5/T1ruGNByqTP3b+7zRN
|
||||
TJ0uaaHb5BzDd7+bdfYeQLfxbIe9yIX/InRWxcPrxZxNFe0pe26zWaOCBSIwggUe
|
||||
MB8GA1UdIwQYMBaAFEAJYWfwvINxT94SCCxv1NQrdj2WMB0GA1UdDgQWBBQB5zit
|
||||
yT/cG3KZA9q/xPDNgwVu4DAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAd
|
||||
BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwTwYDVR0gBEgwRjA6BgsrBgEE
|
||||
AbIxAQICBzArMCkGCCsGAQUFBwIBFh1odHRwczovL3NlY3VyZS5jb21vZG8uY29t
|
||||
L0NQUzAIBgZngQwBAgEwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5jb21v
|
||||
ZG9jYTQuY29tL0NPTU9ET0VDQ0RvbWFpblZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJD
|
||||
QTIuY3JsMIGIBggrBgEFBQcBAQR8MHowUQYIKwYBBQUHMAKGRWh0dHA6Ly9jcnQu
|
||||
Y29tb2RvY2E0LmNvbS9DT01PRE9FQ0NEb21haW5WYWxpZGF0aW9uU2VjdXJlU2Vy
|
||||
dmVyQ0EyLmNydDAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AuY29tb2RvY2E0LmNv
|
||||
bTCCA2kGA1UdEQSCA2AwggNcghpzbmk0OTYyOS5jbG91ZGZsYXJlc3NsLmNvbYIQ
|
||||
Ki4wOTMzMTk0NDIzLmNvbYISKi5hbGlhbW90ZWwuY29tLnR3ghMqLmFuaW1hbGNv
|
||||
bGxlZ2Uub3JnghEqLmFyZG9yLWRjLmNvbS50d4IOKi5hc2lhbmlhZ2EubXmCDCou
|
||||
YmlsbG93cy50d4ISKi5jYW5keXNvdXJjZXMuY29tgiIqLmNvbmNyZXRlY29tbWFu
|
||||
ZG9zY29uc3RydWN0aW9uLmNhghYqLmZsYXRsaW5lc2VjdXJpdHkuY29tgggqLmcy
|
||||
Yy50d4IOKi5nbGluZS5jb20udHeCEioubGVtaXJhYmVhdXR5LmNvbYIWKi5saWJl
|
||||
cnR5ZGVzaWduLmNvbS50d4ISKi5saWJlcnR5ZGVzaWduLnR3ghIqLmxpYmVydHlv
|
||||
Y2Vhbi5jb22CEioubGl1aC1qaWFuLmNvbS50d4IQKi5samhzYWx1bW5pLm9yZ4Ia
|
||||
Ki5sb21hbmthbS13aW5nY2h1bi5vcmcudHeCCioubmV0dHkuaW+CDCoub3BsaWZ0
|
||||
LmNvbYITKi5zb3V0aGVybmJlbGxzLm5ldIIZKi50ZW5yeW8tY2xlYW5yb29tLmNv
|
||||
bS50d4IOMDkzMzE5NDQyMy5jb22CEGFsaWFtb3RlbC5jb20udHeCEWFuaW1hbGNv
|
||||
bGxlZ2Uub3Jngg9hcmRvci1kYy5jb20udHeCDGFzaWFuaWFnYS5teYIKYmlsbG93
|
||||
cy50d4IQY2FuZHlzb3VyY2VzLmNvbYIgY29uY3JldGVjb21tYW5kb3Njb25zdHJ1
|
||||
Y3Rpb24uY2GCFGZsYXRsaW5lc2VjdXJpdHkuY29tggZnMmMudHeCDGdsaW5lLmNv
|
||||
bS50d4IQbGVtaXJhYmVhdXR5LmNvbYIUbGliZXJ0eWRlc2lnbi5jb20udHeCEGxp
|
||||
YmVydHlkZXNpZ24udHeCEGxpYmVydHlvY2Vhbi5jb22CEGxpdWgtamlhbi5jb20u
|
||||
dHeCDmxqaHNhbHVtbmkub3Jnghhsb21hbmthbS13aW5nY2h1bi5vcmcudHeCCG5l
|
||||
dHR5LmlvggpvcGxpZnQuY29tghFzb3V0aGVybmJlbGxzLm5ldIIXdGVucnlvLWNs
|
||||
ZWFucm9vbS5jb20udHcwCgYIKoZIzj0EAwIDSAAwRQIgCqAcYW//4ucQu4WiCM/l
|
||||
bsaz7ZIJb6vKxyMoWRoCuJkCIQDEIK02bfsKicBK9DJGCq7j6TkmIwwvnRuuRA7W
|
||||
08APyA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDnzCCAyWgAwIBAgIQWyXOaQfEJlVm0zkMmalUrTAKBggqhkjOPQQDAzCBhTEL
|
||||
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
|
||||
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
|
||||
IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTQwOTI1MDAw
|
||||
MDAwWhcNMjkwOTI0MjM1OTU5WjCBkjELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
|
||||
ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
|
||||
T0RPIENBIExpbWl0ZWQxODA2BgNVBAMTL0NPTU9ETyBFQ0MgRG9tYWluIFZhbGlk
|
||||
YXRpb24gU2VjdXJlIFNlcnZlciBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
|
||||
QgAEAjgZgTrJaYRwWQKOqIofMN+83gP8eR06JSxrQSEYgur5PkrkM8wSzypD/A7y
|
||||
ZADA4SVQgiTNtkk4DyVHkUikraOCAWYwggFiMB8GA1UdIwQYMBaAFHVxpxlIGbyd
|
||||
nepBR9+UxEh3mdN5MB0GA1UdDgQWBBRACWFn8LyDcU/eEggsb9TUK3Y9ljAOBgNV
|
||||
HQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHSUEFjAUBggrBgEF
|
||||
BQcDAQYIKwYBBQUHAwIwGwYDVR0gBBQwEjAGBgRVHSAAMAgGBmeBDAECATBMBgNV
|
||||
HR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9FQ0ND
|
||||
ZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDByBggrBgEFBQcBAQRmMGQwOwYIKwYB
|
||||
BQUHMAKGL2h0dHA6Ly9jcnQuY29tb2RvY2EuY29tL0NPTU9ET0VDQ0FkZFRydXN0
|
||||
Q0EuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC5jb21vZG9jYTQuY29tMAoG
|
||||
CCqGSM49BAMDA2gAMGUCMQCsaEclgBNPE1bAojcJl1pQxOfttGHLKIoKETKm4nHf
|
||||
EQGJbwd6IGZrGNC5LkP3Um8CMBKFfI4TZpIEuppFCZRKMGHRSdxv6+ctyYnPHmp8
|
||||
7IXOMCVZuoFwNLg0f+cB0eLLUg==
|
||||
-----END CERTIFICATE-----
|
@ -259,6 +259,13 @@ public final class OpenSsl {
|
||||
return version() >= 0x10002000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the used version of OpenSSL supports OCSP stapling.
|
||||
*/
|
||||
public static boolean isOcspSupported() {
|
||||
return version() >= 0x10002000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the used available OpenSSL library or {@code -1} if {@link #isAvailable()}
|
||||
* returns {@code false}.
|
||||
|
@ -175,17 +175,18 @@ public final class OpenSslClientContext extends OpenSslContext {
|
||||
throws SSLException {
|
||||
this(toX509CertificatesInternal(trustCertCollectionFile), trustManagerFactory,
|
||||
toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword),
|
||||
keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, null, sessionCacheSize, sessionTimeout);
|
||||
keyPassword, keyManagerFactory, ciphers, cipherFilter, apn, null, sessionCacheSize,
|
||||
sessionTimeout, false);
|
||||
}
|
||||
|
||||
OpenSslClientContext(X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
|
||||
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers,
|
||||
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
|
||||
long sessionCacheSize, long sessionTimeout)
|
||||
long sessionCacheSize, long sessionTimeout, boolean enableOcsp)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
|
||||
ClientAuth.NONE, protocols, false);
|
||||
ClientAuth.NONE, protocols, false, enableOcsp);
|
||||
boolean success = false;
|
||||
try {
|
||||
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
|
||||
|
@ -29,18 +29,19 @@ import javax.net.ssl.SSLException;
|
||||
public abstract class OpenSslContext extends ReferenceCountedOpenSslContext {
|
||||
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apnCfg,
|
||||
long sessionCacheSize, long sessionTimeout, int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls)
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp)
|
||||
throws SSLException {
|
||||
super(ciphers, cipherFilter, apnCfg, sessionCacheSize, sessionTimeout, mode, keyCertChain,
|
||||
clientAuth, protocols, startTls, false);
|
||||
clientAuth, protocols, startTls, enableOcsp, false);
|
||||
}
|
||||
|
||||
OpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
||||
OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
|
||||
long sessionTimeout, int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls) throws SSLException {
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, mode, keyCertChain, clientAuth, protocols,
|
||||
startTls, false);
|
||||
startTls, enableOcsp, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -323,17 +323,18 @@ public final class OpenSslServerContext extends OpenSslContext {
|
||||
this(toX509CertificatesInternal(trustCertCollectionFile), trustManagerFactory,
|
||||
toX509CertificatesInternal(keyCertChainFile), toPrivateKeyInternal(keyFile, keyPassword),
|
||||
keyPassword, keyManagerFactory, ciphers, cipherFilter,
|
||||
apn, sessionCacheSize, sessionTimeout, ClientAuth.NONE, null, false);
|
||||
apn, sessionCacheSize, sessionTimeout, ClientAuth.NONE, null, false, false);
|
||||
}
|
||||
|
||||
OpenSslServerContext(
|
||||
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls)
|
||||
throws SSLException {
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers,
|
||||
cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls);
|
||||
cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
|
||||
enableOcsp);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@ -341,10 +342,10 @@ public final class OpenSslServerContext extends OpenSslContext {
|
||||
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls)
|
||||
throws SSLException {
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
|
||||
clientAuth, protocols, startTls);
|
||||
clientAuth, protocols, startTls, enableOcsp);
|
||||
// Create a new SSL_CTX and configure it.
|
||||
boolean success = false;
|
||||
try {
|
||||
|
@ -54,10 +54,10 @@ public final class ReferenceCountedOpenSslClientContext extends ReferenceCounted
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword,
|
||||
KeyManagerFactory keyManagerFactory, Iterable<String> ciphers,
|
||||
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
|
||||
String[] protocols, long sessionCacheSize, long sessionTimeout)
|
||||
throws SSLException {
|
||||
String[] protocols, long sessionCacheSize, long sessionTimeout,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_CLIENT, keyCertChain,
|
||||
ClientAuth.NONE, protocols, false, true);
|
||||
ClientAuth.NONE, protocols, false, enableOcsp, true);
|
||||
boolean success = false;
|
||||
try {
|
||||
sessionContext = newSessionContext(this, ctx, engineMap, trustCertCollection, trustManagerFactory,
|
||||
|
@ -45,6 +45,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
@ -141,6 +142,7 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
final Certificate[] keyCertChain;
|
||||
final ClientAuth clientAuth;
|
||||
final String[] protocols;
|
||||
final boolean enableOcsp;
|
||||
final OpenSslEngineMap engineMap = new DefaultOpenSslEngineMap();
|
||||
private volatile boolean rejectRemoteInitiatedRenegotiation;
|
||||
private volatile int bioNonApplicationBufferSize = DEFAULT_BIO_NON_APPLICATION_BUFFER_SIZE;
|
||||
@ -213,20 +215,24 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
||||
ApplicationProtocolConfig apnCfg, long sessionCacheSize, long sessionTimeout,
|
||||
int mode, Certificate[] keyCertChain, ClientAuth clientAuth, String[] protocols,
|
||||
boolean startTls, boolean leakDetection) throws SSLException {
|
||||
boolean startTls, boolean enableOcsp, boolean leakDetection) throws SSLException {
|
||||
this(ciphers, cipherFilter, toNegotiator(apnCfg), sessionCacheSize, sessionTimeout, mode, keyCertChain,
|
||||
clientAuth, protocols, startTls, leakDetection);
|
||||
clientAuth, protocols, startTls, enableOcsp, leakDetection);
|
||||
}
|
||||
|
||||
ReferenceCountedOpenSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
|
||||
OpenSslApplicationProtocolNegotiator apn, long sessionCacheSize,
|
||||
long sessionTimeout, int mode, Certificate[] keyCertChain,
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean leakDetection)
|
||||
throws SSLException {
|
||||
ClientAuth clientAuth, String[] protocols, boolean startTls, boolean enableOcsp,
|
||||
boolean leakDetection) throws SSLException {
|
||||
super(startTls);
|
||||
|
||||
OpenSsl.ensureAvailability();
|
||||
|
||||
if (enableOcsp && !OpenSsl.isOcspSupported()) {
|
||||
throw new IllegalStateException("OCSP is not supported.");
|
||||
}
|
||||
|
||||
if (mode != SSL.SSL_MODE_SERVER && mode != SSL.SSL_MODE_CLIENT) {
|
||||
throw new IllegalArgumentException("mode most be either SSL.SSL_MODE_SERVER or SSL.SSL_MODE_CLIENT");
|
||||
}
|
||||
@ -234,6 +240,7 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
this.mode = mode;
|
||||
this.clientAuth = isServer() ? checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE;
|
||||
this.protocols = protocols;
|
||||
this.enableOcsp = enableOcsp;
|
||||
|
||||
if (mode == SSL.SSL_MODE_SERVER) {
|
||||
rejectRemoteInitiatedRenegotiation =
|
||||
@ -348,6 +355,10 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
// Revert the session timeout to the default value.
|
||||
SSLContext.setSessionCacheTimeout(ctx, sessionTimeout);
|
||||
}
|
||||
|
||||
if (enableOcsp) {
|
||||
SSLContext.enableOcsp(ctx, isClient());
|
||||
}
|
||||
}
|
||||
success = true;
|
||||
} finally {
|
||||
@ -493,6 +504,10 @@ public abstract class ReferenceCountedOpenSslContext extends SslContext implemen
|
||||
final void destroy() {
|
||||
synchronized (ReferenceCountedOpenSslContext.class) {
|
||||
if (ctx != 0) {
|
||||
if (enableOcsp) {
|
||||
SSLContext.disableOcsp(ctx);
|
||||
}
|
||||
|
||||
SSLContext.free(ctx);
|
||||
ctx = 0;
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
import javax.net.ssl.SNIMatcher;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLEngineResult;
|
||||
@ -203,6 +204,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1];
|
||||
private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1];
|
||||
private final OpenSslKeyMaterialManager keyMaterialManager;
|
||||
private final boolean enableOcsp;
|
||||
|
||||
// This is package-private as we set it from OpenSslContext if an exception is thrown during
|
||||
// the verification step.
|
||||
@ -229,6 +231,7 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
rejectRemoteInitiatedRenegation = context.getRejectRemoteInitiatedRenegotiation();
|
||||
localCerts = context.keyCertChain;
|
||||
keyMaterialManager = context.keyMaterialManager();
|
||||
enableOcsp = context.enableOcsp;
|
||||
ssl = SSL.newSSL(context.ctx, !context.isClient());
|
||||
try {
|
||||
networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize());
|
||||
@ -246,12 +249,52 @@ public class ReferenceCountedOpenSslEngine extends SSLEngine implements Referenc
|
||||
if (clientMode && peerHost != null) {
|
||||
SSL.setTlsExtHostName(ssl, peerHost);
|
||||
}
|
||||
|
||||
if (enableOcsp) {
|
||||
SSL.enableOcsp(ssl);
|
||||
}
|
||||
} catch (Throwable cause) {
|
||||
SSL.freeSSL(ssl);
|
||||
PlatformDependent.throwException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the OCSP response.
|
||||
*/
|
||||
@UnstableApi
|
||||
public void setOcspResponse(byte[] response) {
|
||||
if (!enableOcsp) {
|
||||
throw new IllegalStateException("OCSP stapling is not enabled");
|
||||
}
|
||||
|
||||
if (clientMode) {
|
||||
throw new IllegalStateException("Not a server SSLEngine");
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
SSL.setOcspResponse(ssl, response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OCSP response or {@code null} if the server didn't provide a stapled OCSP response.
|
||||
*/
|
||||
@UnstableApi
|
||||
public byte[] getOcspResponse() {
|
||||
if (!enableOcsp) {
|
||||
throw new IllegalStateException("OCSP stapling is not enabled");
|
||||
}
|
||||
|
||||
if (!clientMode) {
|
||||
throw new IllegalStateException("Not a client SSLEngine");
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
return SSL.getOcspResponse(ssl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int refCnt() {
|
||||
return refCnt.refCnt();
|
||||
|
@ -48,20 +48,21 @@ public final class ReferenceCountedOpenSslServerContext extends ReferenceCounted
|
||||
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls)
|
||||
throws SSLException {
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
this(trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory, ciphers,
|
||||
cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls);
|
||||
cipherFilter, toNegotiator(apn), sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
|
||||
enableOcsp);
|
||||
}
|
||||
|
||||
private ReferenceCountedOpenSslServerContext(
|
||||
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, OpenSslApplicationProtocolNegotiator apn,
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls)
|
||||
throws SSLException {
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
super(ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, SSL.SSL_MODE_SERVER, keyCertChain,
|
||||
clientAuth, protocols, startTls, true);
|
||||
clientAuth, protocols, startTls, enableOcsp, true);
|
||||
// Create a new SSL_CTX and configure it.
|
||||
boolean success = false;
|
||||
try {
|
||||
|
@ -385,7 +385,7 @@ public abstract class SslContext {
|
||||
trustManagerFactory, toX509Certificates(keyCertChainFile),
|
||||
toPrivateKey(keyFile, keyPassword),
|
||||
keyPassword, keyManagerFactory, ciphers, cipherFilter, apn,
|
||||
sessionCacheSize, sessionTimeout, ClientAuth.NONE, null, false);
|
||||
sessionCacheSize, sessionTimeout, ClientAuth.NONE, null, false, false);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof SSLException) {
|
||||
throw (SSLException) e;
|
||||
@ -400,8 +400,8 @@ public abstract class SslContext {
|
||||
X509Certificate[] trustCertCollection, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls)
|
||||
throws SSLException {
|
||||
long sessionCacheSize, long sessionTimeout, ClientAuth clientAuth, String[] protocols, boolean startTls,
|
||||
boolean enableOcsp) throws SSLException {
|
||||
|
||||
if (provider == null) {
|
||||
provider = defaultServerProvider();
|
||||
@ -409,6 +409,9 @@ public abstract class SslContext {
|
||||
|
||||
switch (provider) {
|
||||
case JDK:
|
||||
if (enableOcsp) {
|
||||
throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
|
||||
}
|
||||
return new JdkSslServerContext(sslContextProvider,
|
||||
trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
|
||||
@ -418,13 +421,13 @@ public abstract class SslContext {
|
||||
return new OpenSslServerContext(
|
||||
trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
|
||||
clientAuth, protocols, startTls);
|
||||
clientAuth, protocols, startTls, enableOcsp);
|
||||
case OPENSSL_REFCNT:
|
||||
verifyNullSslContextProvider(provider, sslContextProvider);
|
||||
return new ReferenceCountedOpenSslServerContext(
|
||||
trustCertCollection, trustManagerFactory, keyCertChain, key, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout,
|
||||
clientAuth, protocols, startTls);
|
||||
clientAuth, protocols, startTls, enableOcsp);
|
||||
default:
|
||||
throw new Error(provider.toString());
|
||||
}
|
||||
@ -740,7 +743,7 @@ public abstract class SslContext {
|
||||
toX509Certificates(trustCertCollectionFile), trustManagerFactory,
|
||||
toX509Certificates(keyCertChainFile), toPrivateKey(keyFile, keyPassword),
|
||||
keyPassword, keyManagerFactory, ciphers, cipherFilter,
|
||||
apn, null, sessionCacheSize, sessionTimeout);
|
||||
apn, null, sessionCacheSize, sessionTimeout, false);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof SSLException) {
|
||||
throw (SSLException) e;
|
||||
@ -755,12 +758,15 @@ public abstract class SslContext {
|
||||
X509Certificate[] trustCert, TrustManagerFactory trustManagerFactory,
|
||||
X509Certificate[] keyCertChain, PrivateKey key, String keyPassword, KeyManagerFactory keyManagerFactory,
|
||||
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, String[] protocols,
|
||||
long sessionCacheSize, long sessionTimeout) throws SSLException {
|
||||
long sessionCacheSize, long sessionTimeout, boolean enableOcsp) throws SSLException {
|
||||
if (provider == null) {
|
||||
provider = defaultClientProvider();
|
||||
}
|
||||
switch (provider) {
|
||||
case JDK:
|
||||
if (enableOcsp) {
|
||||
throw new IllegalArgumentException("OCSP is not supported with this SslProvider: " + provider);
|
||||
}
|
||||
return new JdkSslClientContext(sslContextProvider,
|
||||
trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout);
|
||||
@ -768,12 +774,14 @@ public abstract class SslContext {
|
||||
verifyNullSslContextProvider(provider, sslContextProvider);
|
||||
return new OpenSslClientContext(
|
||||
trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout);
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout,
|
||||
enableOcsp);
|
||||
case OPENSSL_REFCNT:
|
||||
verifyNullSslContextProvider(provider, sslContextProvider);
|
||||
return new ReferenceCountedOpenSslClientContext(
|
||||
trustCert, trustManagerFactory, keyCertChain, key, keyPassword,
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout);
|
||||
keyManagerFactory, ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout,
|
||||
enableOcsp);
|
||||
default:
|
||||
throw new Error(provider.toString());
|
||||
}
|
||||
|
@ -18,10 +18,13 @@ package io.netty.handler.ssl;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
|
||||
import io.netty.util.internal.UnstableApi;
|
||||
|
||||
import java.security.Provider;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.security.PrivateKey;
|
||||
@ -142,6 +145,7 @@ public final class SslContextBuilder {
|
||||
private ClientAuth clientAuth = ClientAuth.NONE;
|
||||
private String[] protocols;
|
||||
private boolean startTls;
|
||||
private boolean enableOcsp;
|
||||
|
||||
private SslContextBuilder(boolean forServer) {
|
||||
this.forServer = forServer;
|
||||
@ -415,6 +419,18 @@ public final class SslContextBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables OCSP stapling. Please note that not all {@link SslProvider} implementations support OCSP
|
||||
* stapling and an exception will be thrown upon {@link #build()}.
|
||||
*
|
||||
* @see OpenSsl#isOcspSupported()
|
||||
*/
|
||||
@UnstableApi
|
||||
public SslContextBuilder enableOcsp(boolean enableOcsp) {
|
||||
this.enableOcsp = enableOcsp;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@code SslContext} instance with configured settings.
|
||||
* <p>If {@link #sslProvider(SslProvider)} is set to {@link SslProvider#OPENSSL_REFCNT} then the caller is
|
||||
@ -424,11 +440,12 @@ public final class SslContextBuilder {
|
||||
if (forServer) {
|
||||
return SslContext.newServerContextInternal(provider, sslContextProvider, trustCertCollection,
|
||||
trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
|
||||
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls);
|
||||
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout, clientAuth, protocols, startTls,
|
||||
enableOcsp);
|
||||
} else {
|
||||
return SslContext.newClientContextInternal(provider, sslContextProvider, trustCertCollection,
|
||||
trustManagerFactory, keyCertChain, key, keyPassword, keyManagerFactory,
|
||||
ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout);
|
||||
ciphers, cipherFilter, apn, protocols, sessionCacheSize, sessionTimeout, enableOcsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package io.netty.handler.ssl.ocsp;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslContext;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
|
||||
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
|
||||
import io.netty.util.internal.ObjectUtil;
|
||||
import io.netty.util.internal.ThrowableUtil;
|
||||
import io.netty.util.internal.UnstableApi;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
/**
|
||||
* A handler for SSL clients to handle and act upon stapled OCSP responses.
|
||||
*
|
||||
* @see ReferenceCountedOpenSslContext#enableOcsp()
|
||||
* @see ReferenceCountedOpenSslEngine#getOcspResponse()
|
||||
*/
|
||||
@UnstableApi
|
||||
public abstract class OcspClientHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final SSLHandshakeException OCSP_VERIFICATION_EXCEPTION = ThrowableUtil.unknownStackTrace(
|
||||
new SSLHandshakeException("Bad OCSP response"), OcspClientHandler.class, "verify(...)");
|
||||
|
||||
private final ReferenceCountedOpenSslEngine engine;
|
||||
|
||||
protected OcspClientHandler(ReferenceCountedOpenSslEngine engine) {
|
||||
this.engine = ObjectUtil.checkNotNull(engine, "engine");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ReferenceCountedOpenSslEngine#getOcspResponse()
|
||||
*/
|
||||
protected abstract boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception;
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
if (evt instanceof SslHandshakeCompletionEvent) {
|
||||
ctx.pipeline().remove(this);
|
||||
|
||||
SslHandshakeCompletionEvent event = (SslHandshakeCompletionEvent) evt;
|
||||
if (event.isSuccess() && !verify(ctx, engine)) {
|
||||
throw OCSP_VERIFICATION_EXCEPTION;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fireUserEventTriggered(evt);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* <a href="https://en.wikipedia.org/wiki/OCSP_stapling">OCSP stapling</a>,
|
||||
* formally known as the TLS Certificate Status Request extension, is an
|
||||
* alternative approach to the Online Certificate Status Protocol (OCSP)
|
||||
* for checking the revocation status of X.509 digital certificates.
|
||||
*/
|
||||
package io.netty.handler.ssl.ocsp;
|
501
handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java
Normal file
501
handler/src/test/java/io/netty/handler/ssl/ocsp/OcspTest.java
Normal file
@ -0,0 +1,501 @@
|
||||
/*
|
||||
* Copyright 2017 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.ssl.ocsp;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.DefaultEventLoopGroup;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.local.LocalServerChannel;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
public class OcspTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void checkOcspSupported() {
|
||||
assumeTrue(OpenSsl.isOcspSupported());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testJdkClientEnableOcsp() throws Exception {
|
||||
SslContextBuilder.forClient()
|
||||
.sslProvider(SslProvider.JDK)
|
||||
.enableOcsp(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testJdkServerEnableOcsp() throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
try {
|
||||
SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(SslProvider.JDK)
|
||||
.enableOcsp(true)
|
||||
.build();
|
||||
} finally {
|
||||
ssc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testClientOcspNotEnabledOpenSsl() throws Exception {
|
||||
testClientOcspNotEnabled(SslProvider.OPENSSL);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testClientOcspNotEnabledOpenSslRefCnt() throws Exception {
|
||||
testClientOcspNotEnabled(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
|
||||
private void testClientOcspNotEnabled(SslProvider sslProvider) throws Exception {
|
||||
SslContext context = SslContextBuilder.forClient()
|
||||
.sslProvider(sslProvider)
|
||||
.build();
|
||||
try {
|
||||
SslHandler sslHandler = context.newHandler(ByteBufAllocator.DEFAULT);
|
||||
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
try {
|
||||
engine.getOcspResponse();
|
||||
} finally {
|
||||
engine.release();
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testServerOcspNotEnabledOpenSsl() throws Exception {
|
||||
testServerOcspNotEnabled(SslProvider.OPENSSL);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testServerOcspNotEnabledOpenSslRefCnt() throws Exception {
|
||||
testServerOcspNotEnabled(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
|
||||
private void testServerOcspNotEnabled(SslProvider sslProvider) throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
try {
|
||||
SslContext context = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(sslProvider)
|
||||
.build();
|
||||
try {
|
||||
SslHandler sslHandler = context.newHandler(ByteBufAllocator.DEFAULT);
|
||||
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
try {
|
||||
engine.setOcspResponse(new byte[] { 1, 2, 3 });
|
||||
} finally {
|
||||
engine.release();
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(context);
|
||||
}
|
||||
} finally {
|
||||
ssc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testClientAcceptingOcspStapleOpenSsl() throws Exception {
|
||||
testClientAcceptingOcspStaple(SslProvider.OPENSSL);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testClientAcceptingOcspStapleOpenSslRefCnt() throws Exception {
|
||||
testClientAcceptingOcspStaple(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Server provides an OCSP staple and the Client accepts it.
|
||||
*/
|
||||
private void testClientAcceptingOcspStaple(SslProvider sslProvider) throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ChannelInboundHandlerAdapter serverHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes()));
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
};
|
||||
|
||||
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
try {
|
||||
ReferenceCountUtil.release(msg);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
byte[] response = newOcspResponse();
|
||||
TestClientOcspContext callback = new TestClientOcspContext(true);
|
||||
|
||||
handshake(sslProvider, latch, serverHandler, response, clientHandler, callback);
|
||||
|
||||
byte[] actual = callback.response();
|
||||
|
||||
assertNotNull(actual);
|
||||
assertNotSame(response, actual);
|
||||
assertArrayEquals(response, actual);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testClientRejectingOcspStapleOpenSsl() throws Exception {
|
||||
testClientRejectingOcspStaple(SslProvider.OPENSSL);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testClientRejectingOcspStapleOpenSslRefCnt() throws Exception {
|
||||
testClientRejectingOcspStaple(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Server provides an OCSP staple and the Client rejects it.
|
||||
*/
|
||||
private void testClientRejectingOcspStaple(SslProvider sslProvider) throws Exception {
|
||||
final AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
try {
|
||||
causeRef.set(cause);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
byte[] response = newOcspResponse();
|
||||
TestClientOcspContext callback = new TestClientOcspContext(false);
|
||||
|
||||
handshake(sslProvider, latch, null, response, clientHandler, callback);
|
||||
|
||||
byte[] actual = callback.response();
|
||||
|
||||
assertNotNull(actual);
|
||||
assertNotSame(response, actual);
|
||||
assertArrayEquals(response, actual);
|
||||
|
||||
Throwable cause = causeRef.get();
|
||||
assertTrue("" + cause, cause instanceof SSLHandshakeException);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testServerHasNoStapleOpenSsl() throws Exception {
|
||||
testServerHasNoStaple(SslProvider.OPENSSL);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testServerHasNoStapleOpenSslRefCnt() throws Exception {
|
||||
testServerHasNoStaple(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* The server has OCSP stapling enabled but doesn't provide a staple.
|
||||
*/
|
||||
private void testServerHasNoStaple(SslProvider sslProvider) throws Exception {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ChannelInboundHandlerAdapter serverHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
ctx.writeAndFlush(Unpooled.wrappedBuffer("Hello, World!".getBytes()));
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
};
|
||||
|
||||
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
try {
|
||||
ReferenceCountUtil.release(msg);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
byte[] response = null;
|
||||
TestClientOcspContext callback = new TestClientOcspContext(true);
|
||||
|
||||
handshake(sslProvider, latch, serverHandler, response, clientHandler, callback);
|
||||
|
||||
byte[] actual = callback.response();
|
||||
|
||||
assertNull(response);
|
||||
assertNull(actual);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testClientExceptionOpenSsl() throws Exception {
|
||||
testClientException(SslProvider.OPENSSL);
|
||||
}
|
||||
|
||||
@Test(timeout = 10000L)
|
||||
public void testClientExceptionOpenSslRefCnt() throws Exception {
|
||||
testClientException(SslProvider.OPENSSL_REFCNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing what happens if the {@link OcspClientCallback} throws an {@link Exception}.
|
||||
*
|
||||
* The exception should bubble up on the client side and the connection should get closed.
|
||||
*/
|
||||
private void testClientException(SslProvider sslProvider) throws Exception {
|
||||
final AtomicReference<Throwable> causeRef = new AtomicReference<Throwable>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
ChannelInboundHandlerAdapter clientHandler = new ChannelInboundHandlerAdapter() {
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
try {
|
||||
causeRef.set(cause);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final OcspTestException clientException = new OcspTestException("testClientException");
|
||||
byte[] response = newOcspResponse();
|
||||
OcspClientCallback callback = new OcspClientCallback() {
|
||||
@Override
|
||||
public boolean verify(byte[] response) throws Exception {
|
||||
throw clientException;
|
||||
}
|
||||
};
|
||||
|
||||
handshake(sslProvider, latch, null, response, clientHandler, callback);
|
||||
|
||||
assertSame(clientException, causeRef.get());
|
||||
}
|
||||
|
||||
private static void handshake(SslProvider sslProvider, CountDownLatch latch, ChannelHandler serverHandler,
|
||||
byte[] response, ChannelHandler clientHandler, OcspClientCallback callback) throws Exception {
|
||||
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
try {
|
||||
SslContext serverSslContext = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey())
|
||||
.sslProvider(sslProvider)
|
||||
.enableOcsp(true)
|
||||
.build();
|
||||
|
||||
try {
|
||||
SslContext clientSslContext = SslContextBuilder.forClient()
|
||||
.sslProvider(sslProvider)
|
||||
.enableOcsp(true)
|
||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
||||
.build();
|
||||
|
||||
try {
|
||||
EventLoopGroup group = new DefaultEventLoopGroup();
|
||||
try {
|
||||
LocalAddress address = new LocalAddress("handshake-" + Math.random());
|
||||
Channel server = newServer(group, address, serverSslContext, response, serverHandler);
|
||||
Channel client = newClient(group, address, clientSslContext, callback, clientHandler);
|
||||
try {
|
||||
assertTrue("Something went wrong.", latch.await(10L, TimeUnit.SECONDS));
|
||||
} finally {
|
||||
client.close().syncUninterruptibly();
|
||||
server.close().syncUninterruptibly();
|
||||
}
|
||||
} finally {
|
||||
group.shutdownGracefully(1L, 1L, TimeUnit.SECONDS);
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(clientSslContext);
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(serverSslContext);
|
||||
}
|
||||
} finally {
|
||||
ssc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private static Channel newServer(EventLoopGroup group, SocketAddress address,
|
||||
SslContext context, byte[] response, ChannelHandler handler) {
|
||||
|
||||
ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(LocalServerChannel.class)
|
||||
.group(group)
|
||||
.childHandler(newServerHandler(context, response, handler));
|
||||
|
||||
return bootstrap.bind(address)
|
||||
.syncUninterruptibly()
|
||||
.channel();
|
||||
}
|
||||
|
||||
private static Channel newClient(EventLoopGroup group, SocketAddress address,
|
||||
SslContext context, OcspClientCallback callback, ChannelHandler handler) {
|
||||
|
||||
Bootstrap bootstrap = new Bootstrap()
|
||||
.channel(LocalChannel.class)
|
||||
.group(group)
|
||||
.handler(newClientHandler(context, callback, handler));
|
||||
|
||||
return bootstrap.connect(address)
|
||||
.syncUninterruptibly()
|
||||
.channel();
|
||||
}
|
||||
|
||||
private static ChannelHandler newServerHandler(final SslContext context,
|
||||
final byte[] response, final ChannelHandler handler) {
|
||||
return new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
SslHandler sslHandler = context.newHandler(ch.alloc());
|
||||
|
||||
if (response != null) {
|
||||
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
engine.setOcspResponse(response);
|
||||
}
|
||||
|
||||
pipeline.addLast(sslHandler);
|
||||
|
||||
if (handler != null) {
|
||||
pipeline.addLast(handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ChannelHandler newClientHandler(final SslContext context,
|
||||
final OcspClientCallback callback, final ChannelHandler handler) {
|
||||
return new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
SslHandler sslHandler = context.newHandler(ch.alloc());
|
||||
ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
|
||||
|
||||
pipeline.addLast(sslHandler);
|
||||
pipeline.addLast(new OcspClientCallbackHandler(engine, callback));
|
||||
|
||||
if (handler != null) {
|
||||
pipeline.addLast(handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] newOcspResponse() {
|
||||
// Assume we got the OCSP staple from somewhere. Using a bogus byte[]
|
||||
// in the test because getting a true staple from the CA is quite involved.
|
||||
// It requires HttpCodec and Bouncycastle and the test may be very unreliable
|
||||
// because the OCSP responder servers are basically being DDoS'd by the
|
||||
// Internet.
|
||||
|
||||
return "I am a bogus OCSP staple. OpenSSL does not care about the format of the byte[]!"
|
||||
.getBytes(CharsetUtil.US_ASCII);
|
||||
}
|
||||
|
||||
private interface OcspClientCallback {
|
||||
boolean verify(byte[] staple) throws Exception;
|
||||
}
|
||||
|
||||
private static final class TestClientOcspContext implements OcspClientCallback {
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private final boolean valid;
|
||||
|
||||
private volatile byte[] response;
|
||||
|
||||
public TestClientOcspContext(boolean valid) {
|
||||
this.valid = valid;
|
||||
}
|
||||
|
||||
public byte[] response() throws InterruptedException, TimeoutException {
|
||||
assertTrue(latch.await(10L, TimeUnit.SECONDS));
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] response) throws Exception {
|
||||
this.response = response;
|
||||
latch.countDown();
|
||||
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OcspClientCallbackHandler extends OcspClientHandler {
|
||||
|
||||
private final OcspClientCallback callback;
|
||||
|
||||
public OcspClientCallbackHandler(ReferenceCountedOpenSslEngine engine, OcspClientCallback callback) {
|
||||
super(engine);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verify(ChannelHandlerContext ctx, ReferenceCountedOpenSslEngine engine) throws Exception {
|
||||
byte[] response = engine.getOcspResponse();
|
||||
return callback.verify(response);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class OcspTestException extends IllegalStateException {
|
||||
public OcspTestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
12
pom.xml
12
pom.xml
@ -370,6 +370,18 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Completely optional and only needed for OCSP stapling to construct and
|
||||
parse OCSP requests and responses.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.54</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml</groupId>
|
||||
<artifactId>aalto-xml</artifactId>
|
||||
|
Loading…
Reference in New Issue
Block a user