io.netty.example.ocsp.OcspUtils Maven / Gradle / Ivy
/*
* 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 findObject(DLSequence sequence, ASN1ObjectIdentifier oid, Class 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();
}
}
}