Path: blob/master/test/jdk/java/security/testlibrary/SimpleOCSPServer.java
41149 views
/*1* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.testlibrary;2627import java.io.*;28import java.net.*;29import java.security.*;30import java.security.cert.CRLReason;31import java.security.cert.X509Certificate;32import java.security.cert.Extension;33import java.security.cert.CertificateException;34import java.security.cert.CertificateEncodingException;35import java.security.Signature;36import java.util.*;37import java.util.concurrent.*;38import java.text.SimpleDateFormat;39import java.math.BigInteger;4041import sun.security.x509.*;42import sun.security.x509.PKIXExtensions;43import sun.security.provider.certpath.ResponderId;44import sun.security.provider.certpath.CertId;45import sun.security.provider.certpath.OCSPResponse;46import sun.security.provider.certpath.OCSPResponse.ResponseStatus;47import sun.security.util.*;484950/**51* This is a simple OCSP server designed to listen and respond to incoming52* requests.53*/54public class SimpleOCSPServer {55private final Debug debug = Debug.getInstance("oserv");56private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID =57ObjectIdentifier.of(KnownOIDs.OCSPBasicResponse);5859private static final SimpleDateFormat utcDateFmt =60new SimpleDateFormat("MMM dd yyyy, HH:mm:ss z");6162static final int FREE_PORT = 0;6364// CertStatus values65public static enum CertStatus {66CERT_STATUS_GOOD,67CERT_STATUS_REVOKED,68CERT_STATUS_UNKNOWN,69}7071// Fields used for the networking portion of the responder72private ServerSocket servSocket;73private InetAddress listenAddress;74private int listenPort;7576// Keystore information (certs, keys, etc.)77private KeyStore keystore;78private X509Certificate issuerCert;79private X509Certificate signerCert;80private PrivateKey signerKey;8182// Fields used for the operational portions of the server83private boolean logEnabled = false;84private ExecutorService threadPool;85private volatile boolean started = false;86private volatile boolean serverReady = false;87private volatile boolean receivedShutdown = false;88private volatile boolean acceptConnections = true;89private volatile long delayMsec = 0;9091// Fields used in the generation of responses92private long nextUpdateInterval = -1;93private Date nextUpdate = null;94private ResponderId respId;95private AlgorithmId sigAlgId;96private Map<CertId, CertStatusInfo> statusDb =97Collections.synchronizedMap(new HashMap<>());9899/**100* Construct a SimpleOCSPServer using keystore, password, and alias101* parameters.102*103* @param ks the keystore to be used104* @param password the password to access key material in the keystore105* @param issuerAlias the alias of the issuer certificate106* @param signerAlias the alias of the signer certificate and key. A107* value of {@code null} means that the {@code issuerAlias} will be used108* to look up the signer key.109*110* @throws GeneralSecurityException if there are problems accessing the111* keystore or finding objects within the keystore.112* @throws IOException if a {@code ResponderId} cannot be generated from113* the signer certificate.114*/115public SimpleOCSPServer(KeyStore ks, String password, String issuerAlias,116String signerAlias) throws GeneralSecurityException, IOException {117this(null, FREE_PORT, ks, password, issuerAlias, signerAlias);118}119120/**121* Construct a SimpleOCSPServer using specific network parameters,122* keystore, password, and alias.123*124* @param addr the address to bind the server to. A value of {@code null}125* means the server will bind to all interfaces.126* @param port the port to listen on. A value of {@code 0} will mean that127* the server will randomly pick an open ephemeral port to bind to.128* @param ks the keystore to be used129* @param password the password to access key material in the keystore130* @param issuerAlias the alias of the issuer certificate131* @param signerAlias the alias of the signer certificate and key. A132* value of {@code null} means that the {@code issuerAlias} will be used133* to look up the signer key.134*135* @throws GeneralSecurityException if there are problems accessing the136* keystore or finding objects within the keystore.137* @throws IOException if a {@code ResponderId} cannot be generated from138* the signer certificate.139*/140public SimpleOCSPServer(InetAddress addr, int port, KeyStore ks,141String password, String issuerAlias, String signerAlias)142throws GeneralSecurityException, IOException {143Objects.requireNonNull(ks, "Null keystore provided");144Objects.requireNonNull(issuerAlias, "Null issuerName provided");145146utcDateFmt.setTimeZone(TimeZone.getTimeZone("GMT"));147148keystore = ks;149issuerCert = (X509Certificate)ks.getCertificate(issuerAlias);150if (issuerCert == null) {151throw new IllegalArgumentException("Certificate for alias " +152issuerAlias + " not found");153}154155if (signerAlias != null) {156signerCert = (X509Certificate)ks.getCertificate(signerAlias);157if (signerCert == null) {158throw new IllegalArgumentException("Certificate for alias " +159signerAlias + " not found");160}161signerKey = (PrivateKey)ks.getKey(signerAlias,162password.toCharArray());163if (signerKey == null) {164throw new IllegalArgumentException("PrivateKey for alias " +165signerAlias + " not found");166}167} else {168signerCert = issuerCert;169signerKey = (PrivateKey)ks.getKey(issuerAlias,170password.toCharArray());171if (signerKey == null) {172throw new IllegalArgumentException("PrivateKey for alias " +173issuerAlias + " not found");174}175}176177sigAlgId = AlgorithmId.get("Sha256withRSA");178respId = new ResponderId(signerCert.getSubjectX500Principal());179listenAddress = addr;180listenPort = port;181}182183/**184* Start the server. The server will bind to the specified network185* address and begin listening for incoming connections.186*187* @throws IOException if any number of things go wonky.188*/189public synchronized void start() throws IOException {190// You cannot start the server twice.191if (started) {192log("Server has already been started");193return;194} else {195started = true;196}197198// Create and start the thread pool199threadPool = Executors.newFixedThreadPool(32, new ThreadFactory() {200@Override201public Thread newThread(Runnable r) {202Thread t = Executors.defaultThreadFactory().newThread(r);203t.setDaemon(true);204return t;205}206});207208threadPool.submit(new Runnable() {209@Override210public void run() {211try (ServerSocket sSock = new ServerSocket()) {212servSocket = sSock;213servSocket.setReuseAddress(true);214servSocket.setSoTimeout(500);215servSocket.bind(new InetSocketAddress(listenAddress,216listenPort), 128);217log("Listening on " + servSocket.getLocalSocketAddress());218219// Singal ready220serverReady = true;221222// Update the listenPort with the new port number. If223// the server is restarted, it will bind to the same224// port rather than picking a new one.225listenPort = servSocket.getLocalPort();226227// Main dispatch loop228while (!receivedShutdown) {229try {230Socket newConnection = servSocket.accept();231if (!acceptConnections) {232try {233log("Reject connection");234newConnection.close();235} catch (IOException e) {236// ignore237}238continue;239}240threadPool.submit(new OcspHandler(newConnection));241} catch (SocketTimeoutException timeout) {242// Nothing to do here. If receivedShutdown243// has changed to true then the loop will244// exit on its own.245} catch (IOException ioe) {246// Something bad happened, log and force a shutdown247log("Unexpected Exception: " + ioe);248stop();249}250}251252log("Shutting down...");253threadPool.shutdown();254} catch (IOException ioe) {255err(ioe);256} finally {257// Reset state variables so the server can be restarted258receivedShutdown = false;259started = false;260serverReady = false;261}262}263});264}265266/**267* Make the OCSP server reject incoming connections.268*/269public synchronized void rejectConnections() {270log("Reject OCSP connections");271acceptConnections = false;272}273274/**275* Make the OCSP server accept incoming connections.276*/277public synchronized void acceptConnections() {278log("Accept OCSP connections");279acceptConnections = true;280}281282283/**284* Stop the OCSP server.285*/286public synchronized void stop() {287if (started) {288receivedShutdown = true;289log("Received shutdown notification");290}291}292293/**294* Print {@code SimpleOCSPServer} operating parameters.295*296* @return the {@code SimpleOCSPServer} operating parameters in297* {@code String} form.298*/299@Override300public String toString() {301StringBuilder sb = new StringBuilder();302sb.append("OCSP Server:\n");303sb.append("----------------------------------------------\n");304sb.append("issuer: ").append(issuerCert.getSubjectX500Principal()).305append("\n");306sb.append("signer: ").append(signerCert.getSubjectX500Principal()).307append("\n");308sb.append("ResponderId: ").append(respId).append("\n");309sb.append("----------------------------------------------");310311return sb.toString();312}313314/**315* Helpful debug routine to hex dump byte arrays.316*317* @param data the array of bytes to dump to stdout.318*319* @return the hexdump of the byte array320*/321private static String dumpHexBytes(byte[] data) {322return dumpHexBytes(data, 16, "\n", " ");323}324325/**326*327* @param data the array of bytes to dump to stdout.328* @param itemsPerLine the number of bytes to display per line329* if the {@code lineDelim} character is blank then all bytes will be330* printed on a single line.331* @param lineDelim the delimiter between lines332* @param itemDelim the delimiter between bytes333*334* @return The hexdump of the byte array335*/336private static String dumpHexBytes(byte[] data, int itemsPerLine,337String lineDelim, String itemDelim) {338StringBuilder sb = new StringBuilder();339if (data != null) {340for (int i = 0; i < data.length; i++) {341if (i % itemsPerLine == 0 && i != 0) {342sb.append(lineDelim);343}344sb.append(String.format("%02X", data[i])).append(itemDelim);345}346}347348return sb.toString();349}350351/**352* Enable or disable the logging feature.353*354* @param enable {@code true} to enable logging, {@code false} to355* disable it. The setting must be activated before the server calls356* its start method. Any calls after that have no effect.357*/358public void enableLog(boolean enable) {359if (!started) {360logEnabled = enable;361}362}363364/**365* Sets the nextUpdate interval. Intervals will be calculated relative366* to the server startup time. When first set, the nextUpdate date is367* calculated based on the current time plus the interval. After that,368* calls to getNextUpdate() will return this date if it is still369* later than current time. If not, the Date will be updated to the370* next interval that is later than current time. This value must be set371* before the server has had its start method called. Calls made after372* the server has been started have no effect.373*374* @param interval the recurring time interval in seconds used to375* calculate nextUpdate times. A value less than or equal to 0 will376* disable the nextUpdate feature.377*/378public synchronized void setNextUpdateInterval(long interval) {379if (!started) {380if (interval <= 0) {381nextUpdateInterval = -1;382nextUpdate = null;383log("nexUpdate support has been disabled");384} else {385nextUpdateInterval = interval * 1000;386nextUpdate = new Date(System.currentTimeMillis() +387nextUpdateInterval);388log("nextUpdate set to " + nextUpdate);389}390}391}392393/**394* Return the nextUpdate {@code Date} object for this server. If the395* nextUpdate date has already passed, set a new nextUpdate based on396* the nextUpdate interval and return that date.397*398* @return a {@code Date} object set to the nextUpdate field for OCSP399* responses.400*/401private synchronized Date getNextUpdate() {402if (nextUpdate != null && nextUpdate.before(new Date())) {403long nuEpochTime = nextUpdate.getTime();404long currentTime = System.currentTimeMillis();405406// Keep adding nextUpdate intervals until you reach a date407// that is later than current time.408while (currentTime >= nuEpochTime) {409nuEpochTime += nextUpdateInterval;410}411412// Set the nextUpdate for future threads413nextUpdate = new Date(nuEpochTime);414log("nextUpdate updated to new value: " + nextUpdate);415}416return nextUpdate;417}418419/**420* Add entries into the responder's status database.421*422* @param newEntries a map of {@code CertStatusInfo} objects, keyed on423* their serial number (as a {@code BigInteger}). All serial numbers424* are assumed to have come from this responder's issuer certificate.425*426* @throws IOException if a CertId cannot be generated.427*/428public void updateStatusDb(Map<BigInteger, CertStatusInfo> newEntries)429throws IOException {430if (newEntries != null) {431for (BigInteger serial : newEntries.keySet()) {432CertStatusInfo info = newEntries.get(serial);433if (info != null) {434CertId cid = new CertId(issuerCert,435new SerialNumber(serial));436statusDb.put(cid, info);437log("Added entry for serial " + serial + "(" +438info.getType() + ")");439}440}441}442}443444/**445* Check the status database for revocation information one one or more446* certificates.447*448* @param reqList the list of {@code LocalSingleRequest} objects taken449* from the incoming OCSP request.450*451* @return a {@code Map} of {@code CertStatusInfo} objects keyed by their452* {@code CertId} values, for each single request passed in. Those453* CertIds not found in the statusDb will have returned List members with454* a status of UNKNOWN.455*/456private Map<CertId, CertStatusInfo> checkStatusDb(457List<LocalOcspRequest.LocalSingleRequest> reqList) {458// TODO figure out what, if anything to do with request extensions459Map<CertId, CertStatusInfo> returnMap = new HashMap<>();460461for (LocalOcspRequest.LocalSingleRequest req : reqList) {462CertId cid = req.getCertId();463CertStatusInfo info = statusDb.get(cid);464if (info != null) {465log("Status for SN " + cid.getSerialNumber() + ": " +466info.getType());467returnMap.put(cid, info);468} else {469log("Status for SN " + cid.getSerialNumber() +470" not found, using CERT_STATUS_UNKNOWN");471returnMap.put(cid,472new CertStatusInfo(CertStatus.CERT_STATUS_UNKNOWN));473}474}475476return Collections.unmodifiableMap(returnMap);477}478479/**480* Set the digital signature algorithm used to sign OCSP responses.481*482* @param algName The algorithm name483*484* @throws NoSuchAlgorithmException if the algorithm name is invalid.485*/486public void setSignatureAlgorithm(String algName)487throws NoSuchAlgorithmException {488if (!started) {489sigAlgId = AlgorithmId.get(algName);490}491}492493/**494* Get the port the OCSP server is running on.495*496* @return the port that the OCSP server is running on, or -1 if the497* server has not yet been bound to a port.498*/499public int getPort() {500if (serverReady) {501InetSocketAddress inetSock =502(InetSocketAddress)servSocket.getLocalSocketAddress();503return inetSock.getPort();504} else {505return -1;506}507}508509/**510* Use to check if OCSP server is ready to accept connection.511*512* @return true if server ready, false otherwise513*/514public boolean isServerReady() {515return serverReady;516}517518/**519* Set a delay between the reception of the request and production of520* the response.521*522* @param delayMillis the number of milliseconds to wait before acting523* on the incoming request.524*/525public void setDelay(long delayMillis) {526delayMsec = delayMillis > 0 ? delayMillis : 0;527if (delayMsec > 0) {528log("OCSP latency set to " + delayMsec + " milliseconds.");529} else {530log("OCSP latency disabled");531}532}533534/**535* Log a message to stdout.536*537* @param message the message to log538*/539private synchronized void log(String message) {540if (logEnabled || debug != null) {541System.out.println("[" + Thread.currentThread().getName() + "]: " +542message);543}544}545546/**547* Log an error message on the stderr stream.548*549* @param message the message to log550*/551private static synchronized void err(String message) {552System.out.println("[" + Thread.currentThread().getName() + "]: " +553message);554}555556/**557* Log exception information on the stderr stream.558*559* @param exc the exception to dump information about560*/561private static synchronized void err(Throwable exc) {562System.out.print("[" + Thread.currentThread().getName() +563"]: Exception: ");564exc.printStackTrace(System.out);565}566567/**568* The {@code CertStatusInfo} class defines an object used to return569* information from the internal status database. The data in this570* object may be used to construct OCSP responses.571*/572public static class CertStatusInfo {573private CertStatus certStatusType;574private CRLReason reason;575private Date revocationTime;576577/**578* Create a Certificate status object by providing the status only.579* If the status is {@code REVOKED} then current time is assumed580* for the revocation time.581*582* @param statType the status for this entry.583*/584public CertStatusInfo(CertStatus statType) {585this(statType, null, null);586}587588/**589* Create a CertStatusInfo providing both type and revocation date590* (if applicable).591*592* @param statType the status for this entry.593* @param revDate if applicable, the date that revocation took place.594* A value of {@code null} indicates that current time should be used.595* If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},596* then the {@code revDate} parameter is ignored.597*/598public CertStatusInfo(CertStatus statType, Date revDate) {599this(statType, revDate, null);600}601602/**603* Create a CertStatusInfo providing type, revocation date604* (if applicable) and revocation reason.605*606* @param statType the status for this entry.607* @param revDate if applicable, the date that revocation took place.608* A value of {@code null} indicates that current time should be used.609* If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},610* then the {@code revDate} parameter is ignored.611* @param revReason the reason the certificate was revoked. A value of612* {@code null} means that no reason was provided.613*/614public CertStatusInfo(CertStatus statType, Date revDate,615CRLReason revReason) {616Objects.requireNonNull(statType, "Cert Status must be non-null");617certStatusType = statType;618switch (statType) {619case CERT_STATUS_GOOD:620case CERT_STATUS_UNKNOWN:621revocationTime = null;622break;623case CERT_STATUS_REVOKED:624revocationTime = revDate != null ? (Date)revDate.clone() :625new Date();626break;627default:628throw new IllegalArgumentException("Unknown status type: " +629statType);630}631}632633/**634* Get the cert status type635*636* @return the status applied to this object (e.g.637* {@code CERT_STATUS_GOOD}, {@code CERT_STATUS_UNKNOWN}, etc.)638*/639public CertStatus getType() {640return certStatusType;641}642643/**644* Get the revocation time (if applicable).645*646* @return the revocation time as a {@code Date} object, or647* {@code null} if not applicable (i.e. if the certificate hasn't been648* revoked).649*/650public Date getRevocationTime() {651return (revocationTime != null ? (Date)revocationTime.clone() :652null);653}654655/**656* Get the revocation reason.657*658* @return the revocation reason, or {@code null} if one was not659* provided.660*/661public CRLReason getRevocationReason() {662return reason;663}664}665666/**667* Runnable task that handles incoming OCSP Requests and returns668* responses.669*/670private class OcspHandler implements Runnable {671private final Socket sock;672InetSocketAddress peerSockAddr;673674/**675* Construct an {@code OcspHandler}.676*677* @param incomingSocket the socket the server created on accept()678*/679private OcspHandler(Socket incomingSocket) {680sock = incomingSocket;681}682683/**684* Run the OCSP Request parser and construct a response to be sent685* back to the client.686*/687@Override688public void run() {689// If we have implemented a delay to simulate network latency690// wait out the delay here before any other processing.691try {692if (delayMsec > 0) {693Thread.sleep(delayMsec);694}695} catch (InterruptedException ie) {696// Just log the interrupted sleep697log("Delay of " + delayMsec + " milliseconds was interrupted");698}699700try (Socket ocspSocket = sock;701InputStream in = ocspSocket.getInputStream();702OutputStream out = ocspSocket.getOutputStream()) {703peerSockAddr =704(InetSocketAddress)ocspSocket.getRemoteSocketAddress();705String[] headerTokens = readLine(in).split(" ");706LocalOcspRequest ocspReq = null;707LocalOcspResponse ocspResp = null;708ResponseStatus respStat = ResponseStatus.INTERNAL_ERROR;709try {710if (headerTokens[0] != null) {711log("Received incoming HTTP " + headerTokens[0] +712" from " + peerSockAddr);713switch (headerTokens[0]) {714case "POST":715ocspReq = parseHttpOcspPost(in);716break;717case "GET":718ocspReq = parseHttpOcspGet(headerTokens);719break;720default:721respStat = ResponseStatus.MALFORMED_REQUEST;722throw new IOException("Not a GET or POST");723}724} else {725respStat = ResponseStatus.MALFORMED_REQUEST;726throw new IOException("Unable to get HTTP method");727}728729if (ocspReq != null) {730log(ocspReq.toString());731// Get responses for all CertIds in the request732Map<CertId, CertStatusInfo> statusMap =733checkStatusDb(ocspReq.getRequests());734if (statusMap.isEmpty()) {735respStat = ResponseStatus.UNAUTHORIZED;736} else {737ocspResp = new LocalOcspResponse(738ResponseStatus.SUCCESSFUL, statusMap,739ocspReq.getExtensions());740}741} else {742respStat = ResponseStatus.MALFORMED_REQUEST;743throw new IOException("Found null request");744}745} catch (IOException | RuntimeException exc) {746err(exc);747}748if (ocspResp == null) {749ocspResp = new LocalOcspResponse(respStat);750}751sendResponse(out, ocspResp);752} catch (IOException | CertificateException exc) {753err(exc);754}755}756757/**758* Send an OCSP response on an {@code OutputStream}.759*760* @param out the {@code OutputStream} on which to send the response.761* @param resp the OCSP response to send.762*763* @throws IOException if an encoding error occurs.764*/765public void sendResponse(OutputStream out, LocalOcspResponse resp)766throws IOException {767StringBuilder sb = new StringBuilder();768769byte[] respBytes;770try {771respBytes = resp.getBytes();772} catch (RuntimeException re) {773err(re);774return;775}776777sb.append("HTTP/1.0 200 OK\r\n");778sb.append("Content-Type: application/ocsp-response\r\n");779sb.append("Content-Length: ").append(respBytes.length);780sb.append("\r\n\r\n");781782out.write(sb.toString().getBytes("UTF-8"));783out.write(respBytes);784log(resp.toString());785}786787/**788* Parse the incoming HTTP POST of an OCSP Request.789*790* @param inStream the input stream from the socket bound to this791* {@code OcspHandler}.792*793* @return the OCSP Request as a {@code LocalOcspRequest}794*795* @throws IOException if there are network related issues or problems796* occur during parsing of the OCSP request.797* @throws CertificateException if one or more of the certificates in798* the OCSP request cannot be read/parsed.799*/800private LocalOcspRequest parseHttpOcspPost(InputStream inStream)801throws IOException, CertificateException {802boolean endOfHeader = false;803boolean properContentType = false;804int length = -1;805806while (!endOfHeader) {807String[] lineTokens = readLine(inStream).split(" ");808if (lineTokens[0].isEmpty()) {809endOfHeader = true;810} else if (lineTokens[0].equalsIgnoreCase("Content-Type:")) {811if (lineTokens[1] == null ||812!lineTokens[1].equals(813"application/ocsp-request")) {814log("Unknown Content-Type: " +815(lineTokens[1] != null ?816lineTokens[1] : "<NULL>"));817return null;818} else {819properContentType = true;820log("Content-Type = " + lineTokens[1]);821}822} else if (lineTokens[0].equalsIgnoreCase("Content-Length:")) {823if (lineTokens[1] != null) {824length = Integer.parseInt(lineTokens[1]);825log("Content-Length = " + length);826}827}828}829830// Okay, make sure we got what we needed from the header, then831// read the remaining OCSP Request bytes832if (properContentType && length >= 0) {833byte[] ocspBytes = new byte[length];834inStream.read(ocspBytes);835return new LocalOcspRequest(ocspBytes);836} else {837return null;838}839}840841/**842* Parse the incoming HTTP GET of an OCSP Request.843*844* @param headerTokens the individual String tokens from the first845* line of the HTTP GET.846*847* @return the OCSP Request as a {@code LocalOcspRequest}848*849* @throws IOException if there are network related issues or problems850* occur during parsing of the OCSP request.851* @throws CertificateException if one or more of the certificates in852* the OCSP request cannot be read/parsed.853*/854private LocalOcspRequest parseHttpOcspGet(String[] headerTokens)855throws IOException, CertificateException {856// We have already established headerTokens[0] to be "GET".857// We should have the URL-encoded base64 representation of the858// OCSP request in headerTokens[1]. We need to strip any leading859// "/" off before decoding.860return new LocalOcspRequest(Base64.getMimeDecoder().decode(861URLDecoder.decode(headerTokens[1].replaceAll("/", ""),862"UTF-8")));863}864865/**866* Read a line of text that is CRLF-delimited.867*868* @param is the {@code InputStream} tied to the socket869* for this {@code OcspHandler}870*871* @return a {@code String} consisting of the line of text872* read from the stream with the CRLF stripped.873*874* @throws IOException if any I/O error occurs.875*/876private String readLine(InputStream is) throws IOException {877PushbackInputStream pbis = new PushbackInputStream(is);878ByteArrayOutputStream bos = new ByteArrayOutputStream();879boolean done = false;880while (!done) {881byte b = (byte)pbis.read();882if (b == '\r') {883byte bNext = (byte)pbis.read();884if (bNext == '\n' || bNext == -1) {885done = true;886} else {887pbis.unread(bNext);888bos.write(b);889}890} else if (b == -1) {891done = true;892} else {893bos.write(b);894}895}896897return new String(bos.toByteArray(), "UTF-8");898}899}900901902/**903* Simple nested class to handle OCSP requests without making904* changes to sun.security.provider.certpath.OCSPRequest905*/906public class LocalOcspRequest {907908private byte[] nonce;909private byte[] signature = null;910private AlgorithmId algId = null;911private int version = 0;912private GeneralName requestorName = null;913private Map<String, Extension> extensions = Collections.emptyMap();914private final List<LocalSingleRequest> requestList = new ArrayList<>();915private final List<X509Certificate> certificates = new ArrayList<>();916917/**918* Construct a {@code LocalOcspRequest} from its DER encoding.919*920* @param requestBytes the DER-encoded bytes921*922* @throws IOException if decoding errors occur923* @throws CertificateException if certificates are found in the924* OCSP request and they do not parse correctly.925*/926private LocalOcspRequest(byte[] requestBytes) throws IOException,927CertificateException {928Objects.requireNonNull(requestBytes, "Received null input");929930DerInputStream dis = new DerInputStream(requestBytes);931932// Parse the top-level structure, it should have no more than933// two elements.934DerValue[] topStructs = dis.getSequence(2);935for (DerValue dv : topStructs) {936if (dv.tag == DerValue.tag_Sequence) {937parseTbsRequest(dv);938} else if (dv.isContextSpecific((byte)0)) {939parseSignature(dv);940} else {941throw new IOException("Unknown tag at top level: " +942dv.tag);943}944}945}946947/**948* Parse the signature block from an OCSP request949*950* @param sigSequence a {@code DerValue} containing the signature951* block at the outer sequence datum.952*953* @throws IOException if any non-certificate-based parsing errors occur954* @throws CertificateException if certificates are found in the955* OCSP request and they do not parse correctly.956*/957private void parseSignature(DerValue sigSequence)958throws IOException, CertificateException {959DerValue[] sigItems = sigSequence.data.getSequence(3);960if (sigItems.length != 3) {961throw new IOException("Invalid number of signature items: " +962"expected 3, got " + sigItems.length);963}964965algId = AlgorithmId.parse(sigItems[0]);966signature = sigItems[1].getBitString();967968if (sigItems[2].isContextSpecific((byte)0)) {969DerValue[] certDerItems = sigItems[2].data.getSequence(4);970int i = 0;971for (DerValue dv : certDerItems) {972X509Certificate xc = new X509CertImpl(dv);973certificates.add(xc);974}975} else {976throw new IOException("Invalid tag in signature block: " +977sigItems[2].tag);978}979}980981/**982* Parse the to-be-signed request data983*984* @param tbsReqSeq a {@code DerValue} object containing the to-be-985* signed OCSP request at the outermost SEQUENCE tag.986* @throws IOException if any parsing errors occur987*/988private void parseTbsRequest(DerValue tbsReqSeq) throws IOException {989while (tbsReqSeq.data.available() > 0) {990DerValue dv = tbsReqSeq.data.getDerValue();991if (dv.isContextSpecific((byte)0)) {992// The version was explicitly called out993version = dv.data.getInteger();994} else if (dv.isContextSpecific((byte)1)) {995// A GeneralName was provided996requestorName = new GeneralName(dv.data.getDerValue());997} else if (dv.isContextSpecific((byte)2)) {998// Parse the extensions999DerValue[] extItems = dv.data.getSequence(2);1000extensions = parseExtensions(extItems);1001} else if (dv.tag == DerValue.tag_Sequence) {1002while (dv.data.available() > 0) {1003requestList.add(new LocalSingleRequest(dv.data));1004}1005}1006}1007}10081009/**1010* Parse a SEQUENCE of extensions. This routine is used both1011* at the overall request level and down at the singleRequest layer.1012*1013* @param extDerItems an array of {@code DerValue} items, each one1014* consisting of a DER-encoded extension.1015*1016* @return a {@code Map} of zero or more extensions,1017* keyed by its object identifier in {@code String} form.1018*1019* @throws IOException if any parsing errors occur.1020*/1021private Map<String, Extension> parseExtensions(DerValue[] extDerItems)1022throws IOException {1023Map<String, Extension> extMap = new HashMap<>();10241025if (extDerItems != null && extDerItems.length != 0) {1026for (DerValue extDerVal : extDerItems) {1027sun.security.x509.Extension ext =1028new sun.security.x509.Extension(extDerVal);1029extMap.put(ext.getId(), ext);1030}1031}10321033return extMap;1034}10351036/**1037* Return the list of single request objects in this OCSP request.1038*1039* @return an unmodifiable {@code List} of zero or more requests.1040*/1041private List<LocalSingleRequest> getRequests() {1042return Collections.unmodifiableList(requestList);1043}10441045/**1046* Return the list of X.509 Certificates in this OCSP request.1047*1048* @return an unmodifiable {@code List} of zero or more1049* {@cpde X509Certificate} objects.1050*/1051private List<X509Certificate> getCertificates() {1052return Collections.unmodifiableList(certificates);1053}10541055/**1056* Return the map of OCSP request extensions.1057*1058* @return an unmodifiable {@code Map} of zero or more1059* {@code Extension} objects, keyed by their object identifiers1060* in {@code String} form.1061*/1062private Map<String, Extension> getExtensions() {1063return Collections.unmodifiableMap(extensions);1064}10651066/**1067* Display the {@code LocalOcspRequest} in human readable form.1068*1069* @return a {@code String} representation of the1070* {@code LocalOcspRequest}1071*/1072@Override1073public String toString() {1074StringBuilder sb = new StringBuilder();10751076sb.append(String.format("OCSP Request: Version %d (0x%X)",1077version + 1, version)).append("\n");1078if (requestorName != null) {1079sb.append("Requestor Name: ").append(requestorName).1080append("\n");1081}10821083int requestCtr = 0;1084for (LocalSingleRequest lsr : requestList) {1085sb.append("Request [").append(requestCtr++).append("]\n");1086sb.append(lsr).append("\n");1087}1088if (!extensions.isEmpty()) {1089sb.append("Extensions (").append(extensions.size()).1090append(")\n");1091for (Extension ext : extensions.values()) {1092sb.append("\t").append(ext).append("\n");1093}1094}1095if (signature != null) {1096sb.append("Signature: ").append(algId).append("\n");1097sb.append(dumpHexBytes(signature)).append("\n");1098int certCtr = 0;1099for (X509Certificate cert : certificates) {1100sb.append("Certificate [").append(certCtr++).append("]").1101append("\n");1102sb.append("\tSubject: ");1103sb.append(cert.getSubjectX500Principal()).append("\n");1104sb.append("\tIssuer: ");1105sb.append(cert.getIssuerX500Principal()).append("\n");1106sb.append("\tSerial: ").append(cert.getSerialNumber());1107}1108}11091110return sb.toString();1111}11121113/**1114* Inner class designed to handle the decoding/representation of1115* single requests within a {@code LocalOcspRequest} object.1116*/1117public class LocalSingleRequest {1118private final CertId cid;1119private Map<String, Extension> extensions = Collections.emptyMap();11201121private LocalSingleRequest(DerInputStream dis)1122throws IOException {1123DerValue[] srItems = dis.getSequence(2);11241125// There should be 1, possibly 2 DerValue items1126if (srItems.length == 1 || srItems.length == 2) {1127// The first parsable item should be the mandatory CertId1128cid = new CertId(srItems[0].data);1129if (srItems.length == 2) {1130if (srItems[1].isContextSpecific((byte)0)) {1131DerValue[] extDerItems = srItems[1].data.getSequence(2);1132extensions = parseExtensions(extDerItems);1133} else {1134throw new IOException("Illegal tag in Request " +1135"extensions: " + srItems[1].tag);1136}1137}1138} else {1139throw new IOException("Invalid number of items in " +1140"Request (" + srItems.length + ")");1141}1142}11431144/**1145* Get the {@code CertId} for this single request.1146*1147* @return the {@code CertId} for this single request.1148*/1149private CertId getCertId() {1150return cid;1151}11521153/**1154* Return the map of single request extensions.1155*1156* @return an unmodifiable {@code Map} of zero or more1157* {@code Extension} objects, keyed by their object identifiers1158* in {@code String} form.1159*/1160private Map<String, Extension> getExtensions() {1161return Collections.unmodifiableMap(extensions);1162}11631164/**1165* Display the {@code LocalSingleRequest} in human readable form.1166*1167* @return a {@code String} representation of the1168* {@code LocalSingleRequest}1169*/1170@Override1171public String toString() {1172StringBuilder sb = new StringBuilder();1173sb.append("CertId, Algorithm = ");1174sb.append(cid.getHashAlgorithm()).append("\n");1175sb.append("\tIssuer Name Hash: ");1176sb.append(dumpHexBytes(cid.getIssuerNameHash(), 256, "", ""));1177sb.append("\n");1178sb.append("\tIssuer Key Hash: ");1179sb.append(dumpHexBytes(cid.getIssuerKeyHash(), 256, "", ""));1180sb.append("\n");1181sb.append("\tSerial Number: ").append(cid.getSerialNumber());1182if (!extensions.isEmpty()) {1183sb.append("Extensions (").append(extensions.size()).1184append(")\n");1185for (Extension ext : extensions.values()) {1186sb.append("\t").append(ext).append("\n");1187}1188}11891190return sb.toString();1191}1192}1193}11941195/**1196* Simple nested class to handle OCSP requests without making1197* changes to sun.security.provider.certpath.OCSPResponse1198*/1199public class LocalOcspResponse {1200private final int version = 0;1201private final OCSPResponse.ResponseStatus responseStatus;1202private final Map<CertId, CertStatusInfo> respItemMap;1203private final Date producedAtDate;1204private final List<LocalSingleResponse> singleResponseList =1205new ArrayList<>();1206private final Map<String, Extension> responseExtensions;1207private byte[] signature;1208private final List<X509Certificate> certificates;1209private final byte[] encodedResponse;12101211/**1212* Constructor for the generation of non-successful responses1213*1214* @param respStat the OCSP response status.1215*1216* @throws IOException if an error happens during encoding1217* @throws NullPointerException if {@code respStat} is {@code null}1218* or {@code respStat} is successful.1219*/1220public LocalOcspResponse(OCSPResponse.ResponseStatus respStat)1221throws IOException {1222this(respStat, null, null);1223}12241225/**1226* Construct a response from a list of certificate1227* status objects and extensions.1228*1229* @param respStat the status of the entire response1230* @param itemMap a {@code Map} of {@code CertId} objects and their1231* respective revocation statuses from the server's response DB.1232* @param reqExtensions a {@code Map} of request extensions1233*1234* @throws IOException if an error happens during encoding1235* @throws NullPointerException if {@code respStat} is {@code null}1236* or {@code respStat} is successful, and a {@code null} {@code itemMap}1237* has been provided.1238*/1239public LocalOcspResponse(OCSPResponse.ResponseStatus respStat,1240Map<CertId, CertStatusInfo> itemMap,1241Map<String, Extension> reqExtensions) throws IOException {1242responseStatus = Objects.requireNonNull(respStat,1243"Illegal null response status");1244if (responseStatus == ResponseStatus.SUCCESSFUL) {1245respItemMap = Objects.requireNonNull(itemMap,1246"SUCCESSFUL responses must have a response map");1247producedAtDate = new Date();12481249// Turn the answerd from the response DB query into a list1250// of single responses.1251for (CertId id : itemMap.keySet()) {1252singleResponseList.add(1253new LocalSingleResponse(id, itemMap.get(id)));1254}12551256responseExtensions = setResponseExtensions(reqExtensions);1257certificates = new ArrayList<>();1258if (signerCert != issuerCert) {1259certificates.add(signerCert);1260}1261certificates.add(issuerCert);1262} else {1263respItemMap = null;1264producedAtDate = null;1265responseExtensions = null;1266certificates = null;1267}1268encodedResponse = this.getBytes();1269}12701271/**1272* Set the response extensions based on the request extensions1273* that were received. Right now, this is limited to the1274* OCSP nonce extension.1275*1276* @param reqExts a {@code Map} of zero or more request extensions1277*1278* @return a {@code Map} of zero or more response extensions, keyed1279* by the extension object identifier in {@code String} form.1280*/1281private Map<String, Extension> setResponseExtensions(1282Map<String, Extension> reqExts) {1283Map<String, Extension> respExts = new HashMap<>();1284String ocspNonceStr = PKIXExtensions.OCSPNonce_Id.toString();12851286if (reqExts != null) {1287for (String id : reqExts.keySet()) {1288if (id.equals(ocspNonceStr)) {1289// We found a nonce, add it into the response extensions1290Extension ext = reqExts.get(id);1291if (ext != null) {1292respExts.put(id, ext);1293log("Added OCSP Nonce to response");1294} else {1295log("Error: Found nonce entry, but found null " +1296"value. Skipping");1297}1298}1299}1300}13011302return respExts;1303}13041305/**1306* Get the DER-encoded response bytes for this response1307*1308* @return a byte array containing the DER-encoded bytes for1309* the response1310*1311* @throws IOException if any encoding errors occur1312*/1313private byte[] getBytes() throws IOException {1314DerOutputStream outerSeq = new DerOutputStream();1315DerOutputStream responseStream = new DerOutputStream();1316responseStream.putEnumerated(responseStatus.ordinal());1317if (responseStatus == ResponseStatus.SUCCESSFUL &&1318respItemMap != null) {1319encodeResponseBytes(responseStream);1320}13211322// Commit the outermost sequence bytes1323outerSeq.write(DerValue.tag_Sequence, responseStream);1324return outerSeq.toByteArray();1325}13261327private void encodeResponseBytes(DerOutputStream responseStream)1328throws IOException {1329DerOutputStream explicitZero = new DerOutputStream();1330DerOutputStream respItemStream = new DerOutputStream();13311332respItemStream.putOID(OCSP_BASIC_RESPONSE_OID);13331334byte[] basicOcspBytes = encodeBasicOcspResponse();1335respItemStream.putOctetString(basicOcspBytes);1336explicitZero.write(DerValue.tag_Sequence, respItemStream);1337responseStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1338true, (byte)0), explicitZero);1339}13401341private byte[] encodeBasicOcspResponse() throws IOException {1342DerOutputStream outerSeq = new DerOutputStream();1343DerOutputStream basicORItemStream = new DerOutputStream();13441345// Encode the tbsResponse1346byte[] tbsResponseBytes = encodeTbsResponse();1347basicORItemStream.write(tbsResponseBytes);13481349try {1350sigAlgId.derEncode(basicORItemStream);13511352// Create the signature1353Signature sig = Signature.getInstance(sigAlgId.getName());1354sig.initSign(signerKey);1355sig.update(tbsResponseBytes);1356signature = sig.sign();1357basicORItemStream.putBitString(signature);1358} catch (GeneralSecurityException exc) {1359err(exc);1360throw new IOException(exc);1361}13621363// Add certificates1364try {1365DerOutputStream certStream = new DerOutputStream();1366ArrayList<DerValue> certList = new ArrayList<>();1367if (signerCert != issuerCert) {1368certList.add(new DerValue(signerCert.getEncoded()));1369}1370certList.add(new DerValue(issuerCert.getEncoded()));1371DerValue[] dvals = new DerValue[certList.size()];1372certStream.putSequence(certList.toArray(dvals));1373basicORItemStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1374true, (byte)0), certStream);1375} catch (CertificateEncodingException cex) {1376err(cex);1377throw new IOException(cex);1378}13791380// Commit the outermost sequence bytes1381outerSeq.write(DerValue.tag_Sequence, basicORItemStream);1382return outerSeq.toByteArray();1383}13841385private byte[] encodeTbsResponse() throws IOException {1386DerOutputStream outerSeq = new DerOutputStream();1387DerOutputStream tbsStream = new DerOutputStream();13881389// Note: We're not going explicitly assert the version1390tbsStream.write(respId.getEncoded());1391tbsStream.putGeneralizedTime(producedAtDate);13921393// Sequence of responses1394encodeSingleResponses(tbsStream);13951396// TODO: add response extension support1397encodeExtensions(tbsStream);13981399outerSeq.write(DerValue.tag_Sequence, tbsStream);1400return outerSeq.toByteArray();1401}14021403private void encodeSingleResponses(DerOutputStream tbsStream)1404throws IOException {1405DerValue[] srDerVals = new DerValue[singleResponseList.size()];1406int srDvCtr = 0;14071408for (LocalSingleResponse lsr : singleResponseList) {1409srDerVals[srDvCtr++] = new DerValue(lsr.getBytes());1410}14111412tbsStream.putSequence(srDerVals);1413}14141415private void encodeExtensions(DerOutputStream tbsStream)1416throws IOException {1417DerOutputStream extSequence = new DerOutputStream();1418DerOutputStream extItems = new DerOutputStream();14191420for (Extension ext : responseExtensions.values()) {1421ext.encode(extItems);1422}1423extSequence.write(DerValue.tag_Sequence, extItems);1424tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,1425(byte)1), extSequence);1426}14271428@Override1429public String toString() {1430StringBuilder sb = new StringBuilder();14311432sb.append("OCSP Response: ").append(responseStatus).append("\n");1433if (responseStatus == ResponseStatus.SUCCESSFUL) {1434sb.append("Response Type: ").1435append(OCSP_BASIC_RESPONSE_OID.toString()).append("\n");1436sb.append(String.format("Version: %d (0x%X)", version + 1,1437version)).append("\n");1438sb.append("Responder Id: ").append(respId.toString()).1439append("\n");1440sb.append("Produced At: ").1441append(utcDateFmt.format(producedAtDate)).append("\n");14421443int srCtr = 0;1444for (LocalSingleResponse lsr : singleResponseList) {1445sb.append("SingleResponse [").append(srCtr++).append("]\n");1446sb.append(lsr);1447}14481449if (!responseExtensions.isEmpty()) {1450sb.append("Extensions (").append(responseExtensions.size()).1451append(")\n");1452for (Extension ext : responseExtensions.values()) {1453sb.append("\t").append(ext).append("\n");1454}1455} else {1456sb.append("\n");1457}14581459if (signature != null) {1460sb.append("Signature: ").append(sigAlgId).append("\n");1461sb.append(dumpHexBytes(signature)).append("\n");1462int certCtr = 0;1463for (X509Certificate cert : certificates) {1464sb.append("Certificate [").append(certCtr++).append("]").1465append("\n");1466sb.append("\tSubject: ");1467sb.append(cert.getSubjectX500Principal()).append("\n");1468sb.append("\tIssuer: ");1469sb.append(cert.getIssuerX500Principal()).append("\n");1470sb.append("\tSerial: ").append(cert.getSerialNumber());1471sb.append("\n");1472}1473}1474}14751476return sb.toString();1477}14781479private class LocalSingleResponse {1480private final CertId certId;1481private final CertStatusInfo csInfo;1482private final Date thisUpdate;1483private final Date lsrNextUpdate;1484private final Map<String, Extension> singleExtensions;14851486public LocalSingleResponse(CertId cid, CertStatusInfo info) {1487certId = Objects.requireNonNull(cid, "CertId must be non-null");1488csInfo = Objects.requireNonNull(info,1489"CertStatusInfo must be non-null");14901491// For now, we'll keep things simple and make the thisUpdate1492// field the same as the producedAt date.1493thisUpdate = producedAtDate;1494lsrNextUpdate = getNextUpdate();14951496// TODO Add extensions support1497singleExtensions = Collections.emptyMap();1498}14991500@Override1501public String toString() {1502StringBuilder sb = new StringBuilder();1503sb.append("Certificate Status: ").append(csInfo.getType());1504sb.append("\n");1505if (csInfo.getType() == CertStatus.CERT_STATUS_REVOKED) {1506sb.append("Revocation Time: ");1507sb.append(utcDateFmt.format(csInfo.getRevocationTime()));1508sb.append("\n");1509if (csInfo.getRevocationReason() != null) {1510sb.append("Revocation Reason: ");1511sb.append(csInfo.getRevocationReason()).append("\n");1512}1513}15141515sb.append("CertId, Algorithm = ");1516sb.append(certId.getHashAlgorithm()).append("\n");1517sb.append("\tIssuer Name Hash: ");1518sb.append(dumpHexBytes(certId.getIssuerNameHash(), 256, "", ""));1519sb.append("\n");1520sb.append("\tIssuer Key Hash: ");1521sb.append(dumpHexBytes(certId.getIssuerKeyHash(), 256, "", ""));1522sb.append("\n");1523sb.append("\tSerial Number: ").append(certId.getSerialNumber());1524sb.append("\n");1525sb.append("This Update: ");1526sb.append(utcDateFmt.format(thisUpdate)).append("\n");1527if (lsrNextUpdate != null) {1528sb.append("Next Update: ");1529sb.append(utcDateFmt.format(lsrNextUpdate)).append("\n");1530}15311532if (!singleExtensions.isEmpty()) {1533sb.append("Extensions (").append(singleExtensions.size()).1534append(")\n");1535for (Extension ext : singleExtensions.values()) {1536sb.append("\t").append(ext).append("\n");1537}1538}15391540return sb.toString();1541}15421543public byte[] getBytes() throws IOException {1544byte[] nullData = { };1545DerOutputStream responseSeq = new DerOutputStream();1546DerOutputStream srStream = new DerOutputStream();15471548// Encode the CertId1549certId.encode(srStream);15501551// Next, encode the CertStatus field1552CertStatus csiType = csInfo.getType();1553switch (csiType) {1554case CERT_STATUS_GOOD:1555srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1556false, (byte)0), nullData);1557break;1558case CERT_STATUS_REVOKED:1559DerOutputStream revInfo = new DerOutputStream();1560revInfo.putGeneralizedTime(csInfo.getRevocationTime());1561CRLReason revReason = csInfo.getRevocationReason();1562if (revReason != null) {1563byte[] revDer = new byte[3];1564revDer[0] = DerValue.tag_Enumerated;1565revDer[1] = 1;1566revDer[2] = (byte)revReason.ordinal();1567revInfo.write(DerValue.createTag(1568DerValue.TAG_CONTEXT, true, (byte)0),1569revDer);1570}1571srStream.write(DerValue.createTag(1572DerValue.TAG_CONTEXT, true, (byte)1),1573revInfo);1574break;1575case CERT_STATUS_UNKNOWN:1576srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1577false, (byte)2), nullData);1578break;1579default:1580throw new IOException("Unknown CertStatus: " + csiType);1581}15821583// Add the necessary dates1584srStream.putGeneralizedTime(thisUpdate);1585if (lsrNextUpdate != null) {1586DerOutputStream nuStream = new DerOutputStream();1587nuStream.putGeneralizedTime(lsrNextUpdate);1588srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1589true, (byte)0), nuStream);1590}15911592// TODO add singleResponse Extension support15931594// Add the single response to the response output stream1595responseSeq.write(DerValue.tag_Sequence, srStream);1596return responseSeq.toByteArray();1597}1598}1599}1600}160116021603