OCSPHandler.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.common.ssl;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Map;
import java.util.stream.Collectors;
import org.bouncycastle.asn1.ocsp.OCSPResponse;
import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
import org.bouncycastle.asn1.ocsp.RevokedInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
import org.bouncycastle.cert.ocsp.Req;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.jcajce.JcaBasicOCSPRespBuilder;
import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class OCSPHandler implements HttpHandler {
    private static final Logger LOG = LoggerFactory.getLogger(OCSPHandler.class);

    private final Ca ca;

    public OCSPHandler(Ca ca) {
        this.ca = ca;
    }

    @Override
    public void handle(com.sun.net.httpserver.HttpExchange httpExchange) throws IOException {
        byte[] responseBytes;
        try {
            String uri = httpExchange.getRequestURI().toString();
            LOG.info("OCSP request: {} {}", httpExchange.getRequestMethod(), uri);
            httpExchange.getRequestHeaders().entrySet().forEach((e) -> {
                LOG.info("OCSP request header: {} {}", e.getKey(), e.getValue());
            });
            InputStream request = httpExchange.getRequestBody();
            byte[] requestBytes = new byte[10000];
            int len = request.read(requestBytes);
            LOG.info("OCSP request size {}", len);

            if (len < 0) {
                String removedUriEncoding = URLDecoder.decode(uri.substring(1), "utf-8");
                LOG.info("OCSP request from URI no encoding {}", removedUriEncoding);
                requestBytes = Base64.getDecoder().decode(removedUriEncoding);
            }
            OCSPReq ocspRequest = new OCSPReq(requestBytes);
            Req[] requestList = ocspRequest.getRequestList();
            LOG.info("requestList {}", Arrays.toString(requestList));

            DigestCalculator digestCalculator = new JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1);

            Map<CertificateID, RevokedInfo> revokedCerts = ca.ocspRevokedCerts.entrySet().stream().collect(Collectors.toMap(entry -> {
                try {
                    return new JcaCertificateID(digestCalculator, ca.cert, entry.getKey().getSerialNumber());
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }, Map.Entry::getValue));

            BasicOCSPRespBuilder responseBuilder = new JcaBasicOCSPRespBuilder(ca.key.getPublic(), digestCalculator);
            for (Req req : requestList) {
                CertificateID certId = req.getCertID();
                CertificateStatus certificateStatus = CertificateStatus.GOOD;
                RevokedInfo revokedInfo = revokedCerts.get(certId);
                if (revokedInfo != null) {
                    certificateStatus = new RevokedStatus(revokedInfo);
                }
                responseBuilder.addResponse(certId, certificateStatus, null);
            }

            X509CertificateHolder[] chain = new X509CertificateHolder[]{new JcaX509CertificateHolder(ca.cert)};
            ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(ca.key.getPrivate());
            BasicOCSPResp ocspResponse = responseBuilder.build(signer, chain, Calendar.getInstance().getTime());
            LOG.info("response {}", ocspResponse);
            responseBytes = new OCSPRespBuilder().build(OCSPRespBuilder.SUCCESSFUL, ocspResponse).getEncoded();
            LOG.error("OCSP server response OK");
        } catch (Throwable exception) {
            LOG.error("Internal OCSP server error", exception);
            responseBytes = new OCSPResp(new OCSPResponse(new OCSPResponseStatus(OCSPRespBuilder.INTERNAL_ERROR), null)).getEncoded();
        }

        Headers rh = httpExchange.getResponseHeaders();
        rh.set("Content-Type", "application/ocsp-response");
        httpExchange.sendResponseHeaders(200, responseBytes.length);

        OutputStream os = httpExchange.getResponseBody();
        os.write(responseBytes);
        os.close();
    }

}