Path: blob/master/test/jdk/java/security/testlibrary/SimpleOCSPServer.java
66644 views
/*1* Copyright (c) 2015, 2021, 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}176sigAlgId = AlgorithmId.get(SignatureUtil.getDefaultSigAlgForKey(signerKey));177respId = new ResponderId(signerCert.getSubjectX500Principal());178listenAddress = addr;179listenPort = port;180}181182/**183* Start the server. The server will bind to the specified network184* address and begin listening for incoming connections.185*186* @throws IOException if any number of things go wonky.187*/188public synchronized void start() throws IOException {189// You cannot start the server twice.190if (started) {191log("Server has already been started");192return;193} else {194started = true;195}196197// Create and start the thread pool198threadPool = Executors.newFixedThreadPool(32, new ThreadFactory() {199@Override200public Thread newThread(Runnable r) {201Thread t = Executors.defaultThreadFactory().newThread(r);202t.setDaemon(true);203return t;204}205});206207threadPool.submit(new Runnable() {208@Override209public void run() {210try (ServerSocket sSock = new ServerSocket()) {211servSocket = sSock;212servSocket.setReuseAddress(true);213servSocket.setSoTimeout(500);214servSocket.bind(new InetSocketAddress(listenAddress,215listenPort), 128);216log("Listening on " + servSocket.getLocalSocketAddress());217218// Singal ready219serverReady = true;220221// Update the listenPort with the new port number. If222// the server is restarted, it will bind to the same223// port rather than picking a new one.224listenPort = servSocket.getLocalPort();225226// Main dispatch loop227while (!receivedShutdown) {228try {229Socket newConnection = servSocket.accept();230if (!acceptConnections) {231try {232log("Reject connection");233newConnection.close();234} catch (IOException e) {235// ignore236}237continue;238}239threadPool.submit(new OcspHandler(newConnection));240} catch (SocketTimeoutException timeout) {241// Nothing to do here. If receivedShutdown242// has changed to true then the loop will243// exit on its own.244} catch (IOException ioe) {245// Something bad happened, log and force a shutdown246log("Unexpected Exception: " + ioe);247stop();248}249}250251log("Shutting down...");252threadPool.shutdown();253} catch (IOException ioe) {254err(ioe);255} finally {256// Reset state variables so the server can be restarted257receivedShutdown = false;258started = false;259serverReady = false;260}261}262});263}264265/**266* Make the OCSP server reject incoming connections.267*/268public synchronized void rejectConnections() {269log("Reject OCSP connections");270acceptConnections = false;271}272273/**274* Make the OCSP server accept incoming connections.275*/276public synchronized void acceptConnections() {277log("Accept OCSP connections");278acceptConnections = true;279}280281282/**283* Stop the OCSP server.284*/285public synchronized void stop() {286if (started) {287receivedShutdown = true;288log("Received shutdown notification");289}290}291292/**293* Print {@code SimpleOCSPServer} operating parameters.294*295* @return the {@code SimpleOCSPServer} operating parameters in296* {@code String} form.297*/298@Override299public String toString() {300StringBuilder sb = new StringBuilder();301sb.append("OCSP Server:\n");302sb.append("----------------------------------------------\n");303sb.append("issuer: ").append(issuerCert.getSubjectX500Principal()).304append("\n");305sb.append("signer: ").append(signerCert.getSubjectX500Principal()).306append("\n");307sb.append("ResponderId: ").append(respId).append("\n");308sb.append("----------------------------------------------");309310return sb.toString();311}312313/**314* Helpful debug routine to hex dump byte arrays.315*316* @param data the array of bytes to dump to stdout.317*318* @return the hexdump of the byte array319*/320private static String dumpHexBytes(byte[] data) {321return dumpHexBytes(data, 16, "\n", " ");322}323324/**325*326* @param data the array of bytes to dump to stdout.327* @param itemsPerLine the number of bytes to display per line328* if the {@code lineDelim} character is blank then all bytes will be329* printed on a single line.330* @param lineDelim the delimiter between lines331* @param itemDelim the delimiter between bytes332*333* @return The hexdump of the byte array334*/335private static String dumpHexBytes(byte[] data, int itemsPerLine,336String lineDelim, String itemDelim) {337StringBuilder sb = new StringBuilder();338if (data != null) {339for (int i = 0; i < data.length; i++) {340if (i % itemsPerLine == 0 && i != 0) {341sb.append(lineDelim);342}343sb.append(String.format("%02X", data[i])).append(itemDelim);344}345}346347return sb.toString();348}349350/**351* Enable or disable the logging feature.352*353* @param enable {@code true} to enable logging, {@code false} to354* disable it. The setting must be activated before the server calls355* its start method. Any calls after that have no effect.356*/357public void enableLog(boolean enable) {358if (!started) {359logEnabled = enable;360}361}362363/**364* Sets the nextUpdate interval. Intervals will be calculated relative365* to the server startup time. When first set, the nextUpdate date is366* calculated based on the current time plus the interval. After that,367* calls to getNextUpdate() will return this date if it is still368* later than current time. If not, the Date will be updated to the369* next interval that is later than current time. This value must be set370* before the server has had its start method called. Calls made after371* the server has been started have no effect.372*373* @param interval the recurring time interval in seconds used to374* calculate nextUpdate times. A value less than or equal to 0 will375* disable the nextUpdate feature.376*/377public synchronized void setNextUpdateInterval(long interval) {378if (!started) {379if (interval <= 0) {380nextUpdateInterval = -1;381nextUpdate = null;382log("nexUpdate support has been disabled");383} else {384nextUpdateInterval = interval * 1000;385nextUpdate = new Date(System.currentTimeMillis() +386nextUpdateInterval);387log("nextUpdate set to " + nextUpdate);388}389}390}391392/**393* Return the nextUpdate {@code Date} object for this server. If the394* nextUpdate date has already passed, set a new nextUpdate based on395* the nextUpdate interval and return that date.396*397* @return a {@code Date} object set to the nextUpdate field for OCSP398* responses.399*/400private synchronized Date getNextUpdate() {401if (nextUpdate != null && nextUpdate.before(new Date())) {402long nuEpochTime = nextUpdate.getTime();403long currentTime = System.currentTimeMillis();404405// Keep adding nextUpdate intervals until you reach a date406// that is later than current time.407while (currentTime >= nuEpochTime) {408nuEpochTime += nextUpdateInterval;409}410411// Set the nextUpdate for future threads412nextUpdate = new Date(nuEpochTime);413log("nextUpdate updated to new value: " + nextUpdate);414}415return nextUpdate;416}417418/**419* Add entries into the responder's status database.420*421* @param newEntries a map of {@code CertStatusInfo} objects, keyed on422* their serial number (as a {@code BigInteger}). All serial numbers423* are assumed to have come from this responder's issuer certificate.424*425* @throws IOException if a CertId cannot be generated.426*/427public void updateStatusDb(Map<BigInteger, CertStatusInfo> newEntries)428throws IOException {429if (newEntries != null) {430for (BigInteger serial : newEntries.keySet()) {431CertStatusInfo info = newEntries.get(serial);432if (info != null) {433CertId cid = new CertId(issuerCert,434new SerialNumber(serial));435statusDb.put(cid, info);436log("Added entry for serial " + serial + "(" +437info.getType() + ")");438}439}440}441}442443/**444* Check the status database for revocation information one one or more445* certificates.446*447* @param reqList the list of {@code LocalSingleRequest} objects taken448* from the incoming OCSP request.449*450* @return a {@code Map} of {@code CertStatusInfo} objects keyed by their451* {@code CertId} values, for each single request passed in. Those452* CertIds not found in the statusDb will have returned List members with453* a status of UNKNOWN.454*/455private Map<CertId, CertStatusInfo> checkStatusDb(456List<LocalOcspRequest.LocalSingleRequest> reqList) {457// TODO figure out what, if anything to do with request extensions458Map<CertId, CertStatusInfo> returnMap = new HashMap<>();459460for (LocalOcspRequest.LocalSingleRequest req : reqList) {461CertId cid = req.getCertId();462CertStatusInfo info = statusDb.get(cid);463if (info != null) {464log("Status for SN " + cid.getSerialNumber() + ": " +465info.getType());466returnMap.put(cid, info);467} else {468log("Status for SN " + cid.getSerialNumber() +469" not found, using CERT_STATUS_UNKNOWN");470returnMap.put(cid,471new CertStatusInfo(CertStatus.CERT_STATUS_UNKNOWN));472}473}474475return Collections.unmodifiableMap(returnMap);476}477478/**479* Set the digital signature algorithm used to sign OCSP responses.480*481* @param algName The algorithm name482*483* @throws NoSuchAlgorithmException if the algorithm name is invalid.484*/485public void setSignatureAlgorithm(String algName)486throws NoSuchAlgorithmException {487if (!started) {488sigAlgId = AlgorithmId.get(algName);489}490}491492/**493* Get the port the OCSP server is running on.494*495* @return the port that the OCSP server is running on, or -1 if the496* server has not yet been bound to a port.497*/498public int getPort() {499if (serverReady) {500InetSocketAddress inetSock =501(InetSocketAddress)servSocket.getLocalSocketAddress();502return inetSock.getPort();503} else {504return -1;505}506}507508/**509* Use to check if OCSP server is ready to accept connection.510*511* @return true if server ready, false otherwise512*/513public boolean isServerReady() {514return serverReady;515}516517/**518* Set a delay between the reception of the request and production of519* the response.520*521* @param delayMillis the number of milliseconds to wait before acting522* on the incoming request.523*/524public void setDelay(long delayMillis) {525delayMsec = delayMillis > 0 ? delayMillis : 0;526if (delayMsec > 0) {527log("OCSP latency set to " + delayMsec + " milliseconds.");528} else {529log("OCSP latency disabled");530}531}532533/**534* Log a message to stdout.535*536* @param message the message to log537*/538private synchronized void log(String message) {539if (logEnabled || debug != null) {540System.out.println("[" + Thread.currentThread().getName() + "]: " +541message);542}543}544545/**546* Log an error message on the stderr stream.547*548* @param message the message to log549*/550private static synchronized void err(String message) {551System.out.println("[" + Thread.currentThread().getName() + "]: " +552message);553}554555/**556* Log exception information on the stderr stream.557*558* @param exc the exception to dump information about559*/560private static synchronized void err(Throwable exc) {561System.out.print("[" + Thread.currentThread().getName() +562"]: Exception: ");563exc.printStackTrace(System.out);564}565566/**567* The {@code CertStatusInfo} class defines an object used to return568* information from the internal status database. The data in this569* object may be used to construct OCSP responses.570*/571public static class CertStatusInfo {572private CertStatus certStatusType;573private CRLReason reason;574private Date revocationTime;575576/**577* Create a Certificate status object by providing the status only.578* If the status is {@code REVOKED} then current time is assumed579* for the revocation time.580*581* @param statType the status for this entry.582*/583public CertStatusInfo(CertStatus statType) {584this(statType, null, null);585}586587/**588* Create a CertStatusInfo providing both type and revocation date589* (if applicable).590*591* @param statType the status for this entry.592* @param revDate if applicable, the date that revocation took place.593* A value of {@code null} indicates that current time should be used.594* If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},595* then the {@code revDate} parameter is ignored.596*/597public CertStatusInfo(CertStatus statType, Date revDate) {598this(statType, revDate, null);599}600601/**602* Create a CertStatusInfo providing type, revocation date603* (if applicable) and revocation reason.604*605* @param statType the status for this entry.606* @param revDate if applicable, the date that revocation took place.607* A value of {@code null} indicates that current time should be used.608* If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},609* then the {@code revDate} parameter is ignored.610* @param revReason the reason the certificate was revoked. A value of611* {@code null} means that no reason was provided.612*/613public CertStatusInfo(CertStatus statType, Date revDate,614CRLReason revReason) {615Objects.requireNonNull(statType, "Cert Status must be non-null");616certStatusType = statType;617switch (statType) {618case CERT_STATUS_GOOD:619case CERT_STATUS_UNKNOWN:620revocationTime = null;621break;622case CERT_STATUS_REVOKED:623revocationTime = revDate != null ? (Date)revDate.clone() :624new Date();625break;626default:627throw new IllegalArgumentException("Unknown status type: " +628statType);629}630}631632/**633* Get the cert status type634*635* @return the status applied to this object (e.g.636* {@code CERT_STATUS_GOOD}, {@code CERT_STATUS_UNKNOWN}, etc.)637*/638public CertStatus getType() {639return certStatusType;640}641642/**643* Get the revocation time (if applicable).644*645* @return the revocation time as a {@code Date} object, or646* {@code null} if not applicable (i.e. if the certificate hasn't been647* revoked).648*/649public Date getRevocationTime() {650return (revocationTime != null ? (Date)revocationTime.clone() :651null);652}653654/**655* Get the revocation reason.656*657* @return the revocation reason, or {@code null} if one was not658* provided.659*/660public CRLReason getRevocationReason() {661return reason;662}663}664665/**666* Runnable task that handles incoming OCSP Requests and returns667* responses.668*/669private class OcspHandler implements Runnable {670private final Socket sock;671InetSocketAddress peerSockAddr;672673/**674* Construct an {@code OcspHandler}.675*676* @param incomingSocket the socket the server created on accept()677*/678private OcspHandler(Socket incomingSocket) {679sock = incomingSocket;680}681682/**683* Run the OCSP Request parser and construct a response to be sent684* back to the client.685*/686@Override687public void run() {688// If we have implemented a delay to simulate network latency689// wait out the delay here before any other processing.690try {691if (delayMsec > 0) {692Thread.sleep(delayMsec);693}694} catch (InterruptedException ie) {695// Just log the interrupted sleep696log("Delay of " + delayMsec + " milliseconds was interrupted");697}698699try (Socket ocspSocket = sock;700InputStream in = ocspSocket.getInputStream();701OutputStream out = ocspSocket.getOutputStream()) {702peerSockAddr =703(InetSocketAddress)ocspSocket.getRemoteSocketAddress();704String[] headerTokens = readLine(in).split(" ");705LocalOcspRequest ocspReq = null;706LocalOcspResponse ocspResp = null;707ResponseStatus respStat = ResponseStatus.INTERNAL_ERROR;708try {709if (headerTokens[0] != null) {710log("Received incoming HTTP " + headerTokens[0] +711" from " + peerSockAddr);712switch (headerTokens[0]) {713case "POST":714ocspReq = parseHttpOcspPost(in);715break;716case "GET":717ocspReq = parseHttpOcspGet(headerTokens);718break;719default:720respStat = ResponseStatus.MALFORMED_REQUEST;721throw new IOException("Not a GET or POST");722}723} else {724respStat = ResponseStatus.MALFORMED_REQUEST;725throw new IOException("Unable to get HTTP method");726}727728if (ocspReq != null) {729log(ocspReq.toString());730// Get responses for all CertIds in the request731Map<CertId, CertStatusInfo> statusMap =732checkStatusDb(ocspReq.getRequests());733if (statusMap.isEmpty()) {734respStat = ResponseStatus.UNAUTHORIZED;735} else {736ocspResp = new LocalOcspResponse(737ResponseStatus.SUCCESSFUL, statusMap,738ocspReq.getExtensions());739}740} else {741respStat = ResponseStatus.MALFORMED_REQUEST;742throw new IOException("Found null request");743}744} catch (IOException | RuntimeException exc) {745err(exc);746}747if (ocspResp == null) {748ocspResp = new LocalOcspResponse(respStat);749}750sendResponse(out, ocspResp);751} catch (IOException | CertificateException exc) {752err(exc);753}754}755756/**757* Send an OCSP response on an {@code OutputStream}.758*759* @param out the {@code OutputStream} on which to send the response.760* @param resp the OCSP response to send.761*762* @throws IOException if an encoding error occurs.763*/764public void sendResponse(OutputStream out, LocalOcspResponse resp)765throws IOException {766StringBuilder sb = new StringBuilder();767768byte[] respBytes;769try {770respBytes = resp.getBytes();771} catch (RuntimeException re) {772err(re);773return;774}775776sb.append("HTTP/1.0 200 OK\r\n");777sb.append("Content-Type: application/ocsp-response\r\n");778sb.append("Content-Length: ").append(respBytes.length);779sb.append("\r\n\r\n");780781out.write(sb.toString().getBytes("UTF-8"));782out.write(respBytes);783log(resp.toString());784}785786/**787* Parse the incoming HTTP POST of an OCSP Request.788*789* @param inStream the input stream from the socket bound to this790* {@code OcspHandler}.791*792* @return the OCSP Request as a {@code LocalOcspRequest}793*794* @throws IOException if there are network related issues or problems795* occur during parsing of the OCSP request.796* @throws CertificateException if one or more of the certificates in797* the OCSP request cannot be read/parsed.798*/799private LocalOcspRequest parseHttpOcspPost(InputStream inStream)800throws IOException, CertificateException {801boolean endOfHeader = false;802boolean properContentType = false;803int length = -1;804805while (!endOfHeader) {806String[] lineTokens = readLine(inStream).split(" ");807if (lineTokens[0].isEmpty()) {808endOfHeader = true;809} else if (lineTokens[0].equalsIgnoreCase("Content-Type:")) {810if (lineTokens[1] == null ||811!lineTokens[1].equals(812"application/ocsp-request")) {813log("Unknown Content-Type: " +814(lineTokens[1] != null ?815lineTokens[1] : "<NULL>"));816return null;817} else {818properContentType = true;819log("Content-Type = " + lineTokens[1]);820}821} else if (lineTokens[0].equalsIgnoreCase("Content-Length:")) {822if (lineTokens[1] != null) {823length = Integer.parseInt(lineTokens[1]);824log("Content-Length = " + length);825}826}827}828829// Okay, make sure we got what we needed from the header, then830// read the remaining OCSP Request bytes831if (properContentType && length >= 0) {832byte[] ocspBytes = new byte[length];833inStream.read(ocspBytes);834return new LocalOcspRequest(ocspBytes);835} else {836return null;837}838}839840/**841* Parse the incoming HTTP GET of an OCSP Request.842*843* @param headerTokens the individual String tokens from the first844* line of the HTTP GET.845*846* @return the OCSP Request as a {@code LocalOcspRequest}847*848* @throws IOException if there are network related issues or problems849* occur during parsing of the OCSP request.850* @throws CertificateException if one or more of the certificates in851* the OCSP request cannot be read/parsed.852*/853private LocalOcspRequest parseHttpOcspGet(String[] headerTokens)854throws IOException, CertificateException {855// We have already established headerTokens[0] to be "GET".856// We should have the URL-encoded base64 representation of the857// OCSP request in headerTokens[1]. We need to strip any leading858// "/" off before decoding.859return new LocalOcspRequest(Base64.getMimeDecoder().decode(860URLDecoder.decode(headerTokens[1].replaceAll("/", ""),861"UTF-8")));862}863864/**865* Read a line of text that is CRLF-delimited.866*867* @param is the {@code InputStream} tied to the socket868* for this {@code OcspHandler}869*870* @return a {@code String} consisting of the line of text871* read from the stream with the CRLF stripped.872*873* @throws IOException if any I/O error occurs.874*/875private String readLine(InputStream is) throws IOException {876PushbackInputStream pbis = new PushbackInputStream(is);877ByteArrayOutputStream bos = new ByteArrayOutputStream();878boolean done = false;879while (!done) {880byte b = (byte)pbis.read();881if (b == '\r') {882byte bNext = (byte)pbis.read();883if (bNext == '\n' || bNext == -1) {884done = true;885} else {886pbis.unread(bNext);887bos.write(b);888}889} else if (b == -1) {890done = true;891} else {892bos.write(b);893}894}895896return new String(bos.toByteArray(), "UTF-8");897}898}899900901/**902* Simple nested class to handle OCSP requests without making903* changes to sun.security.provider.certpath.OCSPRequest904*/905public class LocalOcspRequest {906907private byte[] nonce;908private byte[] signature = null;909private AlgorithmId algId = null;910private int version = 0;911private GeneralName requestorName = null;912private Map<String, Extension> extensions = Collections.emptyMap();913private final List<LocalSingleRequest> requestList = new ArrayList<>();914private final List<X509Certificate> certificates = new ArrayList<>();915916/**917* Construct a {@code LocalOcspRequest} from its DER encoding.918*919* @param requestBytes the DER-encoded bytes920*921* @throws IOException if decoding errors occur922* @throws CertificateException if certificates are found in the923* OCSP request and they do not parse correctly.924*/925private LocalOcspRequest(byte[] requestBytes) throws IOException,926CertificateException {927Objects.requireNonNull(requestBytes, "Received null input");928929DerInputStream dis = new DerInputStream(requestBytes);930931// Parse the top-level structure, it should have no more than932// two elements.933DerValue[] topStructs = dis.getSequence(2);934for (DerValue dv : topStructs) {935if (dv.tag == DerValue.tag_Sequence) {936parseTbsRequest(dv);937} else if (dv.isContextSpecific((byte)0)) {938parseSignature(dv);939} else {940throw new IOException("Unknown tag at top level: " +941dv.tag);942}943}944}945946/**947* Parse the signature block from an OCSP request948*949* @param sigSequence a {@code DerValue} containing the signature950* block at the outer sequence datum.951*952* @throws IOException if any non-certificate-based parsing errors occur953* @throws CertificateException if certificates are found in the954* OCSP request and they do not parse correctly.955*/956private void parseSignature(DerValue sigSequence)957throws IOException, CertificateException {958DerValue[] sigItems = sigSequence.data.getSequence(3);959if (sigItems.length != 3) {960throw new IOException("Invalid number of signature items: " +961"expected 3, got " + sigItems.length);962}963964algId = AlgorithmId.parse(sigItems[0]);965signature = sigItems[1].getBitString();966967if (sigItems[2].isContextSpecific((byte)0)) {968DerValue[] certDerItems = sigItems[2].data.getSequence(4);969int i = 0;970for (DerValue dv : certDerItems) {971X509Certificate xc = new X509CertImpl(dv);972certificates.add(xc);973}974} else {975throw new IOException("Invalid tag in signature block: " +976sigItems[2].tag);977}978}979980/**981* Parse the to-be-signed request data982*983* @param tbsReqSeq a {@code DerValue} object containing the to-be-984* signed OCSP request at the outermost SEQUENCE tag.985* @throws IOException if any parsing errors occur986*/987private void parseTbsRequest(DerValue tbsReqSeq) throws IOException {988while (tbsReqSeq.data.available() > 0) {989DerValue dv = tbsReqSeq.data.getDerValue();990if (dv.isContextSpecific((byte)0)) {991// The version was explicitly called out992version = dv.data.getInteger();993} else if (dv.isContextSpecific((byte)1)) {994// A GeneralName was provided995requestorName = new GeneralName(dv.data.getDerValue());996} else if (dv.isContextSpecific((byte)2)) {997// Parse the extensions998DerValue[] extItems = dv.data.getSequence(2);999extensions = parseExtensions(extItems);1000} else if (dv.tag == DerValue.tag_Sequence) {1001while (dv.data.available() > 0) {1002requestList.add(new LocalSingleRequest(dv.data));1003}1004}1005}1006}10071008/**1009* Parse a SEQUENCE of extensions. This routine is used both1010* at the overall request level and down at the singleRequest layer.1011*1012* @param extDerItems an array of {@code DerValue} items, each one1013* consisting of a DER-encoded extension.1014*1015* @return a {@code Map} of zero or more extensions,1016* keyed by its object identifier in {@code String} form.1017*1018* @throws IOException if any parsing errors occur.1019*/1020private Map<String, Extension> parseExtensions(DerValue[] extDerItems)1021throws IOException {1022Map<String, Extension> extMap = new HashMap<>();10231024if (extDerItems != null && extDerItems.length != 0) {1025for (DerValue extDerVal : extDerItems) {1026sun.security.x509.Extension ext =1027new sun.security.x509.Extension(extDerVal);1028extMap.put(ext.getId(), ext);1029}1030}10311032return extMap;1033}10341035/**1036* Return the list of single request objects in this OCSP request.1037*1038* @return an unmodifiable {@code List} of zero or more requests.1039*/1040private List<LocalSingleRequest> getRequests() {1041return Collections.unmodifiableList(requestList);1042}10431044/**1045* Return the list of X.509 Certificates in this OCSP request.1046*1047* @return an unmodifiable {@code List} of zero or more1048* {@cpde X509Certificate} objects.1049*/1050private List<X509Certificate> getCertificates() {1051return Collections.unmodifiableList(certificates);1052}10531054/**1055* Return the map of OCSP request extensions.1056*1057* @return an unmodifiable {@code Map} of zero or more1058* {@code Extension} objects, keyed by their object identifiers1059* in {@code String} form.1060*/1061private Map<String, Extension> getExtensions() {1062return Collections.unmodifiableMap(extensions);1063}10641065/**1066* Display the {@code LocalOcspRequest} in human readable form.1067*1068* @return a {@code String} representation of the1069* {@code LocalOcspRequest}1070*/1071@Override1072public String toString() {1073StringBuilder sb = new StringBuilder();10741075sb.append(String.format("OCSP Request: Version %d (0x%X)",1076version + 1, version)).append("\n");1077if (requestorName != null) {1078sb.append("Requestor Name: ").append(requestorName).1079append("\n");1080}10811082int requestCtr = 0;1083for (LocalSingleRequest lsr : requestList) {1084sb.append("Request [").append(requestCtr++).append("]\n");1085sb.append(lsr).append("\n");1086}1087if (!extensions.isEmpty()) {1088sb.append("Extensions (").append(extensions.size()).1089append(")\n");1090for (Extension ext : extensions.values()) {1091sb.append("\t").append(ext).append("\n");1092}1093}1094if (signature != null) {1095sb.append("Signature: ").append(algId).append("\n");1096sb.append(dumpHexBytes(signature)).append("\n");1097int certCtr = 0;1098for (X509Certificate cert : certificates) {1099sb.append("Certificate [").append(certCtr++).append("]").1100append("\n");1101sb.append("\tSubject: ");1102sb.append(cert.getSubjectX500Principal()).append("\n");1103sb.append("\tIssuer: ");1104sb.append(cert.getIssuerX500Principal()).append("\n");1105sb.append("\tSerial: ").append(cert.getSerialNumber());1106}1107}11081109return sb.toString();1110}11111112/**1113* Inner class designed to handle the decoding/representation of1114* single requests within a {@code LocalOcspRequest} object.1115*/1116public class LocalSingleRequest {1117private final CertId cid;1118private Map<String, Extension> extensions = Collections.emptyMap();11191120private LocalSingleRequest(DerInputStream dis)1121throws IOException {1122DerValue[] srItems = dis.getSequence(2);11231124// There should be 1, possibly 2 DerValue items1125if (srItems.length == 1 || srItems.length == 2) {1126// The first parsable item should be the mandatory CertId1127cid = new CertId(srItems[0].data);1128if (srItems.length == 2) {1129if (srItems[1].isContextSpecific((byte)0)) {1130DerValue[] extDerItems = srItems[1].data.getSequence(2);1131extensions = parseExtensions(extDerItems);1132} else {1133throw new IOException("Illegal tag in Request " +1134"extensions: " + srItems[1].tag);1135}1136}1137} else {1138throw new IOException("Invalid number of items in " +1139"Request (" + srItems.length + ")");1140}1141}11421143/**1144* Get the {@code CertId} for this single request.1145*1146* @return the {@code CertId} for this single request.1147*/1148private CertId getCertId() {1149return cid;1150}11511152/**1153* Return the map of single request extensions.1154*1155* @return an unmodifiable {@code Map} of zero or more1156* {@code Extension} objects, keyed by their object identifiers1157* in {@code String} form.1158*/1159private Map<String, Extension> getExtensions() {1160return Collections.unmodifiableMap(extensions);1161}11621163/**1164* Display the {@code LocalSingleRequest} in human readable form.1165*1166* @return a {@code String} representation of the1167* {@code LocalSingleRequest}1168*/1169@Override1170public String toString() {1171StringBuilder sb = new StringBuilder();1172sb.append("CertId, Algorithm = ");1173sb.append(cid.getHashAlgorithm()).append("\n");1174sb.append("\tIssuer Name Hash: ");1175sb.append(dumpHexBytes(cid.getIssuerNameHash(), 256, "", ""));1176sb.append("\n");1177sb.append("\tIssuer Key Hash: ");1178sb.append(dumpHexBytes(cid.getIssuerKeyHash(), 256, "", ""));1179sb.append("\n");1180sb.append("\tSerial Number: ").append(cid.getSerialNumber());1181if (!extensions.isEmpty()) {1182sb.append("Extensions (").append(extensions.size()).1183append(")\n");1184for (Extension ext : extensions.values()) {1185sb.append("\t").append(ext).append("\n");1186}1187}11881189return sb.toString();1190}1191}1192}11931194/**1195* Simple nested class to handle OCSP requests without making1196* changes to sun.security.provider.certpath.OCSPResponse1197*/1198public class LocalOcspResponse {1199private final int version = 0;1200private final OCSPResponse.ResponseStatus responseStatus;1201private final Map<CertId, CertStatusInfo> respItemMap;1202private final Date producedAtDate;1203private final List<LocalSingleResponse> singleResponseList =1204new ArrayList<>();1205private final Map<String, Extension> responseExtensions;1206private byte[] signature;1207private final List<X509Certificate> certificates;1208private final byte[] encodedResponse;12091210/**1211* Constructor for the generation of non-successful responses1212*1213* @param respStat the OCSP response status.1214*1215* @throws IOException if an error happens during encoding1216* @throws NullPointerException if {@code respStat} is {@code null}1217* or {@code respStat} is successful.1218*/1219public LocalOcspResponse(OCSPResponse.ResponseStatus respStat)1220throws IOException {1221this(respStat, null, null);1222}12231224/**1225* Construct a response from a list of certificate1226* status objects and extensions.1227*1228* @param respStat the status of the entire response1229* @param itemMap a {@code Map} of {@code CertId} objects and their1230* respective revocation statuses from the server's response DB.1231* @param reqExtensions a {@code Map} of request extensions1232*1233* @throws IOException if an error happens during encoding1234* @throws NullPointerException if {@code respStat} is {@code null}1235* or {@code respStat} is successful, and a {@code null} {@code itemMap}1236* has been provided.1237*/1238public LocalOcspResponse(OCSPResponse.ResponseStatus respStat,1239Map<CertId, CertStatusInfo> itemMap,1240Map<String, Extension> reqExtensions) throws IOException {1241responseStatus = Objects.requireNonNull(respStat,1242"Illegal null response status");1243if (responseStatus == ResponseStatus.SUCCESSFUL) {1244respItemMap = Objects.requireNonNull(itemMap,1245"SUCCESSFUL responses must have a response map");1246producedAtDate = new Date();12471248// Turn the answerd from the response DB query into a list1249// of single responses.1250for (CertId id : itemMap.keySet()) {1251singleResponseList.add(1252new LocalSingleResponse(id, itemMap.get(id)));1253}12541255responseExtensions = setResponseExtensions(reqExtensions);1256certificates = new ArrayList<>();1257if (signerCert != issuerCert) {1258certificates.add(signerCert);1259}1260certificates.add(issuerCert);1261} else {1262respItemMap = null;1263producedAtDate = null;1264responseExtensions = null;1265certificates = null;1266}1267encodedResponse = this.getBytes();1268}12691270/**1271* Set the response extensions based on the request extensions1272* that were received. Right now, this is limited to the1273* OCSP nonce extension.1274*1275* @param reqExts a {@code Map} of zero or more request extensions1276*1277* @return a {@code Map} of zero or more response extensions, keyed1278* by the extension object identifier in {@code String} form.1279*/1280private Map<String, Extension> setResponseExtensions(1281Map<String, Extension> reqExts) {1282Map<String, Extension> respExts = new HashMap<>();1283String ocspNonceStr = PKIXExtensions.OCSPNonce_Id.toString();12841285if (reqExts != null) {1286for (String id : reqExts.keySet()) {1287if (id.equals(ocspNonceStr)) {1288// We found a nonce, add it into the response extensions1289Extension ext = reqExts.get(id);1290if (ext != null) {1291respExts.put(id, ext);1292log("Added OCSP Nonce to response");1293} else {1294log("Error: Found nonce entry, but found null " +1295"value. Skipping");1296}1297}1298}1299}13001301return respExts;1302}13031304/**1305* Get the DER-encoded response bytes for this response1306*1307* @return a byte array containing the DER-encoded bytes for1308* the response1309*1310* @throws IOException if any encoding errors occur1311*/1312private byte[] getBytes() throws IOException {1313DerOutputStream outerSeq = new DerOutputStream();1314DerOutputStream responseStream = new DerOutputStream();1315responseStream.putEnumerated(responseStatus.ordinal());1316if (responseStatus == ResponseStatus.SUCCESSFUL &&1317respItemMap != null) {1318encodeResponseBytes(responseStream);1319}13201321// Commit the outermost sequence bytes1322outerSeq.write(DerValue.tag_Sequence, responseStream);1323return outerSeq.toByteArray();1324}13251326private void encodeResponseBytes(DerOutputStream responseStream)1327throws IOException {1328DerOutputStream explicitZero = new DerOutputStream();1329DerOutputStream respItemStream = new DerOutputStream();13301331respItemStream.putOID(OCSP_BASIC_RESPONSE_OID);13321333byte[] basicOcspBytes = encodeBasicOcspResponse();1334respItemStream.putOctetString(basicOcspBytes);1335explicitZero.write(DerValue.tag_Sequence, respItemStream);1336responseStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1337true, (byte)0), explicitZero);1338}13391340private byte[] encodeBasicOcspResponse() throws IOException {1341DerOutputStream outerSeq = new DerOutputStream();1342DerOutputStream basicORItemStream = new DerOutputStream();13431344// Encode the tbsResponse1345byte[] tbsResponseBytes = encodeTbsResponse();1346basicORItemStream.write(tbsResponseBytes);13471348try {1349// Create the signature1350Signature sig = SignatureUtil.fromKey(1351sigAlgId.getName(), signerKey, (Provider)null);1352sig.update(tbsResponseBytes);1353signature = sig.sign();1354// Rewrite signAlg, RSASSA-PSS needs some parameters.1355sigAlgId = SignatureUtil.fromSignature(sig, signerKey);1356sigAlgId.derEncode(basicORItemStream);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