Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/java/security/testlibrary/SimpleOCSPServer.java
38811 views
/*1* Copyright (c) 2015, 2016, 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.Debug;48import sun.security.util.DerInputStream;49import sun.security.util.DerOutputStream;50import sun.security.util.DerValue;51import sun.security.util.ObjectIdentifier;525354/**55* This is a simple OCSP server designed to listen and respond to incoming56* requests.57*/58public class SimpleOCSPServer {59private final Debug debug = Debug.getInstance("oserv");60private static final ObjectIdentifier OCSP_BASIC_RESPONSE_OID =61ObjectIdentifier.newInternal(62new int[] { 1, 3, 6, 1, 5, 5, 7, 48, 1, 1});63private static final SimpleDateFormat utcDateFmt =64new SimpleDateFormat("MMM dd yyyy, HH:mm:ss z");6566static final int FREE_PORT = 0;6768// CertStatus values69public static enum CertStatus {70CERT_STATUS_GOOD,71CERT_STATUS_REVOKED,72CERT_STATUS_UNKNOWN,73}7475// Fields used for the networking portion of the responder76private ServerSocket servSocket;77private InetAddress listenAddress;78private int listenPort;7980// Keystore information (certs, keys, etc.)81private KeyStore keystore;82private X509Certificate issuerCert;83private X509Certificate signerCert;84private PrivateKey signerKey;8586// Fields used for the operational portions of the server87private boolean logEnabled = false;88private ExecutorService threadPool;89private volatile boolean started = false;90private volatile boolean serverReady = false;91private volatile boolean receivedShutdown = false;92private volatile boolean acceptConnections = true;93private volatile long delayMsec = 0;9495// Fields used in the generation of responses96private long nextUpdateInterval = -1;97private Date nextUpdate = null;98private ResponderId respId;99private AlgorithmId sigAlgId;100private Map<CertId, CertStatusInfo> statusDb =101Collections.synchronizedMap(new HashMap<>());102103/**104* Construct a SimpleOCSPServer using keystore, password, and alias105* parameters.106*107* @param ks the keystore to be used108* @param password the password to access key material in the keystore109* @param issuerAlias the alias of the issuer certificate110* @param signerAlias the alias of the signer certificate and key. A111* value of {@code null} means that the {@code issuerAlias} will be used112* to look up the signer key.113*114* @throws GeneralSecurityException if there are problems accessing the115* keystore or finding objects within the keystore.116* @throws IOException if a {@code ResponderId} cannot be generated from117* the signer certificate.118*/119public SimpleOCSPServer(KeyStore ks, String password, String issuerAlias,120String signerAlias) throws GeneralSecurityException, IOException {121this(null, FREE_PORT, ks, password, issuerAlias, signerAlias);122}123124/**125* Construct a SimpleOCSPServer using specific network parameters,126* keystore, password, and alias.127*128* @param addr the address to bind the server to. A value of {@code null}129* means the server will bind to all interfaces.130* @param port the port to listen on. A value of {@code 0} will mean that131* the server will randomly pick an open ephemeral port to bind to.132* @param ks the keystore to be used133* @param password the password to access key material in the keystore134* @param issuerAlias the alias of the issuer certificate135* @param signerAlias the alias of the signer certificate and key. A136* value of {@code null} means that the {@code issuerAlias} will be used137* to look up the signer key.138*139* @throws GeneralSecurityException if there are problems accessing the140* keystore or finding objects within the keystore.141* @throws IOException if a {@code ResponderId} cannot be generated from142* the signer certificate.143*/144public SimpleOCSPServer(InetAddress addr, int port, KeyStore ks,145String password, String issuerAlias, String signerAlias)146throws GeneralSecurityException, IOException {147Objects.requireNonNull(ks, "Null keystore provided");148Objects.requireNonNull(issuerAlias, "Null issuerName provided");149150utcDateFmt.setTimeZone(TimeZone.getTimeZone("GMT"));151152keystore = ks;153issuerCert = (X509Certificate)ks.getCertificate(issuerAlias);154if (issuerCert == null) {155throw new IllegalArgumentException("Certificate for alias " +156issuerAlias + " not found");157}158159if (signerAlias != null) {160signerCert = (X509Certificate)ks.getCertificate(signerAlias);161if (signerCert == null) {162throw new IllegalArgumentException("Certificate for alias " +163signerAlias + " not found");164}165signerKey = (PrivateKey)ks.getKey(signerAlias,166password.toCharArray());167if (signerKey == null) {168throw new IllegalArgumentException("PrivateKey for alias " +169signerAlias + " not found");170}171} else {172signerCert = issuerCert;173signerKey = (PrivateKey)ks.getKey(issuerAlias,174password.toCharArray());175if (signerKey == null) {176throw new IllegalArgumentException("PrivateKey for alias " +177issuerAlias + " not found");178}179}180181sigAlgId = AlgorithmId.get("Sha256withRSA");182respId = new ResponderId(signerCert.getSubjectX500Principal());183listenAddress = addr;184listenPort = port;185}186187/**188* Start the server. The server will bind to the specified network189* address and begin listening for incoming connections.190*191* @throws IOException if any number of things go wonky.192*/193public synchronized void start() throws IOException {194// You cannot start the server twice.195if (started) {196log("Server has already been started");197return;198} else {199started = true;200}201202// Create and start the thread pool203threadPool = Executors.newFixedThreadPool(32, new ThreadFactory() {204@Override205public Thread newThread(Runnable r) {206Thread t = Executors.defaultThreadFactory().newThread(r);207t.setDaemon(true);208return t;209}210});211212threadPool.submit(new Runnable() {213@Override214public void run() {215try (ServerSocket sSock = new ServerSocket()) {216servSocket = sSock;217servSocket.setReuseAddress(true);218servSocket.setSoTimeout(500);219servSocket.bind(new InetSocketAddress(listenAddress,220listenPort), 128);221log("Listening on " + servSocket.getLocalSocketAddress());222223// Singal ready224serverReady = true;225226// Update the listenPort with the new port number. If227// the server is restarted, it will bind to the same228// port rather than picking a new one.229listenPort = servSocket.getLocalPort();230231// Main dispatch loop232while (!receivedShutdown) {233try {234Socket newConnection = servSocket.accept();235if (!acceptConnections) {236try {237log("Reject connection");238newConnection.close();239} catch (IOException e) {240// ignore241}242continue;243}244threadPool.submit(new OcspHandler(newConnection));245} catch (SocketTimeoutException timeout) {246// Nothing to do here. If receivedShutdown247// has changed to true then the loop will248// exit on its own.249} catch (IOException ioe) {250// Something bad happened, log and force a shutdown251log("Unexpected Exception: " + ioe);252stop();253}254}255256log("Shutting down...");257threadPool.shutdown();258} catch (IOException ioe) {259err(ioe);260} finally {261// Reset state variables so the server can be restarted262receivedShutdown = false;263started = false;264serverReady = false;265}266}267});268}269270/**271* Make the OCSP server reject incoming connections.272*/273public synchronized void rejectConnections() {274log("Reject OCSP connections");275acceptConnections = false;276}277278/**279* Make the OCSP server accept incoming connections.280*/281public synchronized void acceptConnections() {282log("Accept OCSP connections");283acceptConnections = true;284}285286287/**288* Stop the OCSP server.289*/290public synchronized void stop() {291if (started) {292receivedShutdown = true;293log("Received shutdown notification");294}295}296297/**298* Print {@code SimpleOCSPServer} operating parameters.299*300* @return the {@code SimpleOCSPServer} operating parameters in301* {@code String} form.302*/303@Override304public String toString() {305StringBuilder sb = new StringBuilder();306sb.append("OCSP Server:\n");307sb.append("----------------------------------------------\n");308sb.append("issuer: ").append(issuerCert.getSubjectX500Principal()).309append("\n");310sb.append("signer: ").append(signerCert.getSubjectX500Principal()).311append("\n");312sb.append("ResponderId: ").append(respId).append("\n");313sb.append("----------------------------------------------");314315return sb.toString();316}317318/**319* Helpful debug routine to hex dump byte arrays.320*321* @param data the array of bytes to dump to stdout.322*323* @return the hexdump of the byte array324*/325private static String dumpHexBytes(byte[] data) {326return dumpHexBytes(data, 16, "\n", " ");327}328329/**330*331* @param data the array of bytes to dump to stdout.332* @param itemsPerLine the number of bytes to display per line333* if the {@code lineDelim} character is blank then all bytes will be334* printed on a single line.335* @param lineDelim the delimiter between lines336* @param itemDelim the delimiter between bytes337*338* @return The hexdump of the byte array339*/340private static String dumpHexBytes(byte[] data, int itemsPerLine,341String lineDelim, String itemDelim) {342StringBuilder sb = new StringBuilder();343if (data != null) {344for (int i = 0; i < data.length; i++) {345if (i % itemsPerLine == 0 && i != 0) {346sb.append(lineDelim);347}348sb.append(String.format("%02X", data[i])).append(itemDelim);349}350}351352return sb.toString();353}354355/**356* Enable or disable the logging feature.357*358* @param enable {@code true} to enable logging, {@code false} to359* disable it. The setting must be activated before the server calls360* its start method. Any calls after that have no effect.361*/362public void enableLog(boolean enable) {363if (!started) {364logEnabled = enable;365}366}367368/**369* Sets the nextUpdate interval. Intervals will be calculated relative370* to the server startup time. When first set, the nextUpdate date is371* calculated based on the current time plus the interval. After that,372* calls to getNextUpdate() will return this date if it is still373* later than current time. If not, the Date will be updated to the374* next interval that is later than current time. This value must be set375* before the server has had its start method called. Calls made after376* the server has been started have no effect.377*378* @param interval the recurring time interval in seconds used to379* calculate nextUpdate times. A value less than or equal to 0 will380* disable the nextUpdate feature.381*/382public synchronized void setNextUpdateInterval(long interval) {383if (!started) {384if (interval <= 0) {385nextUpdateInterval = -1;386nextUpdate = null;387log("nexUpdate support has been disabled");388} else {389nextUpdateInterval = interval * 1000;390nextUpdate = new Date(System.currentTimeMillis() +391nextUpdateInterval);392log("nextUpdate set to " + nextUpdate);393}394}395}396397/**398* Return the nextUpdate {@code Date} object for this server. If the399* nextUpdate date has already passed, set a new nextUpdate based on400* the nextUpdate interval and return that date.401*402* @return a {@code Date} object set to the nextUpdate field for OCSP403* responses.404*/405private synchronized Date getNextUpdate() {406if (nextUpdate != null && nextUpdate.before(new Date())) {407long nuEpochTime = nextUpdate.getTime();408long currentTime = System.currentTimeMillis();409410// Keep adding nextUpdate intervals until you reach a date411// that is later than current time.412while (currentTime >= nuEpochTime) {413nuEpochTime += nextUpdateInterval;414}415416// Set the nextUpdate for future threads417nextUpdate = new Date(nuEpochTime);418log("nextUpdate updated to new value: " + nextUpdate);419}420return nextUpdate;421}422423/**424* Add entries into the responder's status database.425*426* @param newEntries a map of {@code CertStatusInfo} objects, keyed on427* their serial number (as a {@code BigInteger}). All serial numbers428* are assumed to have come from this responder's issuer certificate.429*430* @throws IOException if a CertId cannot be generated.431*/432public void updateStatusDb(Map<BigInteger, CertStatusInfo> newEntries)433throws IOException {434if (newEntries != null) {435for (BigInteger serial : newEntries.keySet()) {436CertStatusInfo info = newEntries.get(serial);437if (info != null) {438CertId cid = new CertId(issuerCert,439new SerialNumber(serial));440statusDb.put(cid, info);441log("Added entry for serial " + serial + "(" +442info.getType() + ")");443}444}445}446}447448/**449* Check the status database for revocation information one one or more450* certificates.451*452* @param reqList the list of {@code LocalSingleRequest} objects taken453* from the incoming OCSP request.454*455* @return a {@code Map} of {@code CertStatusInfo} objects keyed by their456* {@code CertId} values, for each single request passed in. Those457* CertIds not found in the statusDb will have returned List members with458* a status of UNKNOWN.459*/460private Map<CertId, CertStatusInfo> checkStatusDb(461List<LocalOcspRequest.LocalSingleRequest> reqList) {462// TODO figure out what, if anything to do with request extensions463Map<CertId, CertStatusInfo> returnMap = new HashMap<>();464465for (LocalOcspRequest.LocalSingleRequest req : reqList) {466CertId cid = req.getCertId();467CertStatusInfo info = statusDb.get(cid);468if (info != null) {469log("Status for SN " + cid.getSerialNumber() + ": " +470info.getType());471returnMap.put(cid, info);472} else {473log("Status for SN " + cid.getSerialNumber() +474" not found, using CERT_STATUS_UNKNOWN");475returnMap.put(cid,476new CertStatusInfo(CertStatus.CERT_STATUS_UNKNOWN));477}478}479480return Collections.unmodifiableMap(returnMap);481}482483/**484* Set the digital signature algorithm used to sign OCSP responses.485*486* @param algName The algorithm name487*488* @throws NoSuchAlgorithmException if the algorithm name is invalid.489*/490public void setSignatureAlgorithm(String algName)491throws NoSuchAlgorithmException {492if (!started) {493sigAlgId = AlgorithmId.get(algName);494}495}496497/**498* Get the port the OCSP server is running on.499*500* @return the port that the OCSP server is running on, or -1 if the501* server has not yet been bound to a port.502*/503public int getPort() {504if (serverReady) {505InetSocketAddress inetSock =506(InetSocketAddress)servSocket.getLocalSocketAddress();507return inetSock.getPort();508} else {509return -1;510}511}512513/**514* Use to check if OCSP server is ready to accept connection.515*516* @return true if server ready, false otherwise517*/518public boolean isServerReady() {519return serverReady;520}521522/**523* Set a delay between the reception of the request and production of524* the response.525*526* @param delayMillis the number of milliseconds to wait before acting527* on the incoming request.528*/529public void setDelay(long delayMillis) {530delayMsec = delayMillis > 0 ? delayMillis : 0;531if (delayMsec > 0) {532log("OCSP latency set to " + delayMsec + " milliseconds.");533} else {534log("OCSP latency disabled");535}536}537538/**539* Log a message to stdout.540*541* @param message the message to log542*/543private synchronized void log(String message) {544if (logEnabled || debug != null) {545System.out.println("[" + Thread.currentThread().getName() + "]: " +546message);547}548}549550/**551* Log an error message on the stderr stream.552*553* @param message the message to log554*/555private static synchronized void err(String message) {556System.out.println("[" + Thread.currentThread().getName() + "]: " +557message);558}559560/**561* Log exception information on the stderr stream.562*563* @param exc the exception to dump information about564*/565private static synchronized void err(Throwable exc) {566System.out.print("[" + Thread.currentThread().getName() +567"]: Exception: ");568exc.printStackTrace(System.out);569}570571/**572* The {@code CertStatusInfo} class defines an object used to return573* information from the internal status database. The data in this574* object may be used to construct OCSP responses.575*/576public static class CertStatusInfo {577private CertStatus certStatusType;578private CRLReason reason;579private Date revocationTime;580581/**582* Create a Certificate status object by providing the status only.583* If the status is {@code REVOKED} then current time is assumed584* for the revocation time.585*586* @param statType the status for this entry.587*/588public CertStatusInfo(CertStatus statType) {589this(statType, null, null);590}591592/**593* Create a CertStatusInfo providing both type and revocation date594* (if applicable).595*596* @param statType the status for this entry.597* @param revDate if applicable, the date that revocation took place.598* A value of {@code null} indicates that current time should be used.599* If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},600* then the {@code revDate} parameter is ignored.601*/602public CertStatusInfo(CertStatus statType, Date revDate) {603this(statType, revDate, null);604}605606/**607* Create a CertStatusInfo providing type, revocation date608* (if applicable) and revocation reason.609*610* @param statType the status for this entry.611* @param revDate if applicable, the date that revocation took place.612* A value of {@code null} indicates that current time should be used.613* If the value of {@code statType} is not {@code CERT_STATUS_REVOKED},614* then the {@code revDate} parameter is ignored.615* @param revReason the reason the certificate was revoked. A value of616* {@code null} means that no reason was provided.617*/618public CertStatusInfo(CertStatus statType, Date revDate,619CRLReason revReason) {620Objects.requireNonNull(statType, "Cert Status must be non-null");621certStatusType = statType;622switch (statType) {623case CERT_STATUS_GOOD:624case CERT_STATUS_UNKNOWN:625revocationTime = null;626break;627case CERT_STATUS_REVOKED:628revocationTime = revDate != null ? (Date)revDate.clone() :629new Date();630break;631default:632throw new IllegalArgumentException("Unknown status type: " +633statType);634}635}636637/**638* Get the cert status type639*640* @return the status applied to this object (e.g.641* {@code CERT_STATUS_GOOD}, {@code CERT_STATUS_UNKNOWN}, etc.)642*/643public CertStatus getType() {644return certStatusType;645}646647/**648* Get the revocation time (if applicable).649*650* @return the revocation time as a {@code Date} object, or651* {@code null} if not applicable (i.e. if the certificate hasn't been652* revoked).653*/654public Date getRevocationTime() {655return (revocationTime != null ? (Date)revocationTime.clone() :656null);657}658659/**660* Get the revocation reason.661*662* @return the revocation reason, or {@code null} if one was not663* provided.664*/665public CRLReason getRevocationReason() {666return reason;667}668}669670/**671* Runnable task that handles incoming OCSP Requests and returns672* responses.673*/674private class OcspHandler implements Runnable {675private final Socket sock;676InetSocketAddress peerSockAddr;677678/**679* Construct an {@code OcspHandler}.680*681* @param incomingSocket the socket the server created on accept()682*/683private OcspHandler(Socket incomingSocket) {684sock = incomingSocket;685}686687/**688* Run the OCSP Request parser and construct a response to be sent689* back to the client.690*/691@Override692public void run() {693// If we have implemented a delay to simulate network latency694// wait out the delay here before any other processing.695try {696if (delayMsec > 0) {697Thread.sleep(delayMsec);698}699} catch (InterruptedException ie) {700// Just log the interrupted sleep701log("Delay of " + delayMsec + " milliseconds was interrupted");702}703704try (Socket ocspSocket = sock;705InputStream in = ocspSocket.getInputStream();706OutputStream out = ocspSocket.getOutputStream()) {707peerSockAddr =708(InetSocketAddress)ocspSocket.getRemoteSocketAddress();709log("Received incoming connection from " + peerSockAddr);710String[] headerTokens = readLine(in).split(" ");711LocalOcspRequest ocspReq = null;712LocalOcspResponse ocspResp = null;713ResponseStatus respStat = ResponseStatus.INTERNAL_ERROR;714try {715if (headerTokens[0] != null) {716switch (headerTokens[0]) {717case "POST":718ocspReq = parseHttpOcspPost(in);719break;720case "GET":721// req = parseHttpOcspGet(in);722// TODO implement the GET parsing723throw new IOException("GET method unsupported");724default:725respStat = ResponseStatus.MALFORMED_REQUEST;726throw new IOException("Not a GET or POST");727}728} else {729respStat = ResponseStatus.MALFORMED_REQUEST;730throw new IOException("Unable to get HTTP method");731}732733if (ocspReq != null) {734log(ocspReq.toString());735// Get responses for all CertIds in the request736Map<CertId, CertStatusInfo> statusMap =737checkStatusDb(ocspReq.getRequests());738if (statusMap.isEmpty()) {739respStat = ResponseStatus.UNAUTHORIZED;740} else {741ocspResp = new LocalOcspResponse(742ResponseStatus.SUCCESSFUL, statusMap,743ocspReq.getExtensions());744}745} else {746respStat = ResponseStatus.MALFORMED_REQUEST;747throw new IOException("Found null request");748}749} catch (IOException | RuntimeException exc) {750err(exc);751}752if (ocspResp == null) {753ocspResp = new LocalOcspResponse(respStat);754}755sendResponse(out, ocspResp);756} catch (IOException | CertificateException exc) {757err(exc);758}759}760761/**762* Send an OCSP response on an {@code OutputStream}.763*764* @param out the {@code OutputStream} on which to send the response.765* @param resp the OCSP response to send.766*767* @throws IOException if an encoding error occurs.768*/769public void sendResponse(OutputStream out, LocalOcspResponse resp)770throws IOException {771StringBuilder sb = new StringBuilder();772773byte[] respBytes;774try {775respBytes = resp.getBytes();776} catch (RuntimeException re) {777err(re);778return;779}780781sb.append("HTTP/1.0 200 OK\r\n");782sb.append("Content-Type: application/ocsp-response\r\n");783sb.append("Content-Length: ").append(respBytes.length);784sb.append("\r\n\r\n");785786out.write(sb.toString().getBytes("UTF-8"));787out.write(respBytes);788log(resp.toString());789}790791/**792* Parse the incoming HTTP POST of an OCSP Request.793*794* @param inStream the input stream from the socket bound to this795* {@code OcspHandler}.796*797* @return the OCSP Request as a {@code LocalOcspRequest}798*799* @throws IOException if there are network related issues or problems800* occur during parsing of the OCSP request.801* @throws CertificateException if one or more of the certificates in802* the OCSP request cannot be read/parsed.803*/804private LocalOcspRequest parseHttpOcspPost(InputStream inStream)805throws IOException, CertificateException {806boolean endOfHeader = false;807boolean properContentType = false;808int length = -1;809810while (!endOfHeader) {811String[] lineTokens = readLine(inStream).split(" ");812if (lineTokens[0].isEmpty()) {813endOfHeader = true;814} else if (lineTokens[0].equalsIgnoreCase("Content-Type:")) {815if (lineTokens[1] == null ||816!lineTokens[1].equals(817"application/ocsp-request")) {818log("Unknown Content-Type: " +819(lineTokens[1] != null ?820lineTokens[1] : "<NULL>"));821return null;822} else {823properContentType = true;824log("Content-Type = " + lineTokens[1]);825}826} else if (lineTokens[0].equalsIgnoreCase("Content-Length:")) {827if (lineTokens[1] != null) {828length = Integer.parseInt(lineTokens[1]);829log("Content-Length = " + length);830}831}832}833834// Okay, make sure we got what we needed from the header, then835// read the remaining OCSP Request bytes836if (properContentType && length >= 0) {837byte[] ocspBytes = new byte[length];838inStream.read(ocspBytes);839return new LocalOcspRequest(ocspBytes);840} else {841return null;842}843}844845/**846* Read a line of text that is CRLF-delimited.847*848* @param is the {@code InputStream} tied to the socket849* for this {@code OcspHandler}850*851* @return a {@code String} consisting of the line of text852* read from the stream with the CRLF stripped.853*854* @throws IOException if any I/O error occurs.855*/856private String readLine(InputStream is) throws IOException {857PushbackInputStream pbis = new PushbackInputStream(is);858ByteArrayOutputStream bos = new ByteArrayOutputStream();859boolean done = false;860while (!done) {861byte b = (byte)pbis.read();862if (b == '\r') {863byte bNext = (byte)pbis.read();864if (bNext == '\n' || bNext == -1) {865done = true;866} else {867pbis.unread(bNext);868bos.write(b);869}870} else if (b == -1) {871done = true;872} else {873bos.write(b);874}875}876877return new String(bos.toByteArray(), "UTF-8");878}879}880881882/**883* Simple nested class to handle OCSP requests without making884* changes to sun.security.provider.certpath.OCSPRequest885*/886public class LocalOcspRequest {887888private byte[] nonce;889private byte[] signature = null;890private AlgorithmId algId = null;891private int version = 0;892private GeneralName requestorName = null;893private Map<String, Extension> extensions = Collections.emptyMap();894private final List<LocalSingleRequest> requestList = new ArrayList<>();895private final List<X509Certificate> certificates = new ArrayList<>();896897/**898* Construct a {@code LocalOcspRequest} from its DER encoding.899*900* @param requestBytes the DER-encoded bytes901*902* @throws IOException if decoding errors occur903* @throws CertificateException if certificates are found in the904* OCSP request and they do not parse correctly.905*/906private LocalOcspRequest(byte[] requestBytes) throws IOException,907CertificateException {908Objects.requireNonNull(requestBytes, "Received null input");909910DerInputStream dis = new DerInputStream(requestBytes);911912// Parse the top-level structure, it should have no more than913// two elements.914DerValue[] topStructs = dis.getSequence(2);915for (DerValue dv : topStructs) {916if (dv.tag == DerValue.tag_Sequence) {917parseTbsRequest(dv);918} else if (dv.isContextSpecific((byte)0)) {919parseSignature(dv);920} else {921throw new IOException("Unknown tag at top level: " +922dv.tag);923}924}925}926927/**928* Parse the signature block from an OCSP request929*930* @param sigSequence a {@code DerValue} containing the signature931* block at the outer sequence datum.932*933* @throws IOException if any non-certificate-based parsing errors occur934* @throws CertificateException if certificates are found in the935* OCSP request and they do not parse correctly.936*/937private void parseSignature(DerValue sigSequence)938throws IOException, CertificateException {939DerValue[] sigItems = sigSequence.data.getSequence(3);940if (sigItems.length != 3) {941throw new IOException("Invalid number of signature items: " +942"expected 3, got " + sigItems.length);943}944945algId = AlgorithmId.parse(sigItems[0]);946signature = sigItems[1].getBitString();947948if (sigItems[2].isContextSpecific((byte)0)) {949DerValue[] certDerItems = sigItems[2].data.getSequence(4);950int i = 0;951for (DerValue dv : certDerItems) {952X509Certificate xc = new X509CertImpl(dv);953certificates.add(xc);954}955} else {956throw new IOException("Invalid tag in signature block: " +957sigItems[2].tag);958}959}960961/**962* Parse the to-be-signed request data963*964* @param tbsReqSeq a {@code DerValue} object containing the to-be-965* signed OCSP request at the outermost SEQUENCE tag.966* @throws IOException if any parsing errors occur967*/968private void parseTbsRequest(DerValue tbsReqSeq) throws IOException {969while (tbsReqSeq.data.available() > 0) {970DerValue dv = tbsReqSeq.data.getDerValue();971if (dv.isContextSpecific((byte)0)) {972// The version was explicitly called out973version = dv.data.getInteger();974} else if (dv.isContextSpecific((byte)1)) {975// A GeneralName was provided976requestorName = new GeneralName(dv.data.getDerValue());977} else if (dv.isContextSpecific((byte)2)) {978// Parse the extensions979DerValue[] extItems = dv.data.getSequence(2);980extensions = parseExtensions(extItems);981} else if (dv.tag == DerValue.tag_Sequence) {982while (dv.data.available() > 0) {983requestList.add(new LocalSingleRequest(dv.data));984}985}986}987}988989/**990* Parse a SEQUENCE of extensions. This routine is used both991* at the overall request level and down at the singleRequest layer.992*993* @param extDerItems an array of {@code DerValue} items, each one994* consisting of a DER-encoded extension.995*996* @return a {@code Map} of zero or more extensions,997* keyed by its object identifier in {@code String} form.998*999* @throws IOException if any parsing errors occur.1000*/1001private Map<String, Extension> parseExtensions(DerValue[] extDerItems)1002throws IOException {1003Map<String, Extension> extMap = new HashMap<>();10041005if (extDerItems != null && extDerItems.length != 0) {1006for (DerValue extDerVal : extDerItems) {1007sun.security.x509.Extension ext =1008new sun.security.x509.Extension(extDerVal);1009extMap.put(ext.getId(), ext);1010}1011}10121013return extMap;1014}10151016/**1017* Return the list of single request objects in this OCSP request.1018*1019* @return an unmodifiable {@code List} of zero or more requests.1020*/1021private List<LocalSingleRequest> getRequests() {1022return Collections.unmodifiableList(requestList);1023}10241025/**1026* Return the list of X.509 Certificates in this OCSP request.1027*1028* @return an unmodifiable {@code List} of zero or more1029* {@cpde X509Certificate} objects.1030*/1031private List<X509Certificate> getCertificates() {1032return Collections.unmodifiableList(certificates);1033}10341035/**1036* Return the map of OCSP request extensions.1037*1038* @return an unmodifiable {@code Map} of zero or more1039* {@code Extension} objects, keyed by their object identifiers1040* in {@code String} form.1041*/1042private Map<String, Extension> getExtensions() {1043return Collections.unmodifiableMap(extensions);1044}10451046/**1047* Display the {@code LocalOcspRequest} in human readable form.1048*1049* @return a {@code String} representation of the1050* {@code LocalOcspRequest}1051*/1052@Override1053public String toString() {1054StringBuilder sb = new StringBuilder();10551056sb.append(String.format("OCSP Request: Version %d (0x%X)",1057version + 1, version)).append("\n");1058if (requestorName != null) {1059sb.append("Requestor Name: ").append(requestorName).1060append("\n");1061}10621063int requestCtr = 0;1064for (LocalSingleRequest lsr : requestList) {1065sb.append("Request [").append(requestCtr++).append("]\n");1066sb.append(lsr).append("\n");1067}1068if (!extensions.isEmpty()) {1069sb.append("Extensions (").append(extensions.size()).1070append(")\n");1071for (Extension ext : extensions.values()) {1072sb.append("\t").append(ext).append("\n");1073}1074}1075if (signature != null) {1076sb.append("Signature: ").append(algId).append("\n");1077sb.append(dumpHexBytes(signature)).append("\n");1078int certCtr = 0;1079for (X509Certificate cert : certificates) {1080sb.append("Certificate [").append(certCtr++).append("]").1081append("\n");1082sb.append("\tSubject: ");1083sb.append(cert.getSubjectX500Principal()).append("\n");1084sb.append("\tIssuer: ");1085sb.append(cert.getIssuerX500Principal()).append("\n");1086sb.append("\tSerial: ").append(cert.getSerialNumber());1087}1088}10891090return sb.toString();1091}10921093/**1094* Inner class designed to handle the decoding/representation of1095* single requests within a {@code LocalOcspRequest} object.1096*/1097public class LocalSingleRequest {1098private final CertId cid;1099private Map<String, Extension> extensions = Collections.emptyMap();11001101private LocalSingleRequest(DerInputStream dis)1102throws IOException {1103DerValue[] srItems = dis.getSequence(2);11041105// There should be 1, possibly 2 DerValue items1106if (srItems.length == 1 || srItems.length == 2) {1107// The first parsable item should be the mandatory CertId1108cid = new CertId(srItems[0].data);1109if (srItems.length == 2) {1110if (srItems[1].isContextSpecific((byte)0)) {1111DerValue[] extDerItems = srItems[1].data.getSequence(2);1112extensions = parseExtensions(extDerItems);1113} else {1114throw new IOException("Illegal tag in Request " +1115"extensions: " + srItems[1].tag);1116}1117}1118} else {1119throw new IOException("Invalid number of items in " +1120"Request (" + srItems.length + ")");1121}1122}11231124/**1125* Get the {@code CertId} for this single request.1126*1127* @return the {@code CertId} for this single request.1128*/1129private CertId getCertId() {1130return cid;1131}11321133/**1134* Return the map of single request extensions.1135*1136* @return an unmodifiable {@code Map} of zero or more1137* {@code Extension} objects, keyed by their object identifiers1138* in {@code String} form.1139*/1140private Map<String, Extension> getExtensions() {1141return Collections.unmodifiableMap(extensions);1142}11431144/**1145* Display the {@code LocalSingleRequest} in human readable form.1146*1147* @return a {@code String} representation of the1148* {@code LocalSingleRequest}1149*/1150@Override1151public String toString() {1152StringBuilder sb = new StringBuilder();1153sb.append("CertId, Algorithm = ");1154sb.append(cid.getHashAlgorithm()).append("\n");1155sb.append("\tIssuer Name Hash: ");1156sb.append(dumpHexBytes(cid.getIssuerNameHash(), 256, "", ""));1157sb.append("\n");1158sb.append("\tIssuer Key Hash: ");1159sb.append(dumpHexBytes(cid.getIssuerKeyHash(), 256, "", ""));1160sb.append("\n");1161sb.append("\tSerial Number: ").append(cid.getSerialNumber());1162if (!extensions.isEmpty()) {1163sb.append("Extensions (").append(extensions.size()).1164append(")\n");1165for (Extension ext : extensions.values()) {1166sb.append("\t").append(ext).append("\n");1167}1168}11691170return sb.toString();1171}1172}1173}11741175/**1176* Simple nested class to handle OCSP requests without making1177* changes to sun.security.provider.certpath.OCSPResponse1178*/1179public class LocalOcspResponse {1180private final int version = 0;1181private final OCSPResponse.ResponseStatus responseStatus;1182private final Map<CertId, CertStatusInfo> respItemMap;1183private final Date producedAtDate;1184private final List<LocalSingleResponse> singleResponseList =1185new ArrayList<>();1186private final Map<String, Extension> responseExtensions;1187private byte[] signature;1188private final List<X509Certificate> certificates;1189private final byte[] encodedResponse;11901191/**1192* Constructor for the generation of non-successful responses1193*1194* @param respStat the OCSP response status.1195*1196* @throws IOException if an error happens during encoding1197* @throws NullPointerException if {@code respStat} is {@code null}1198* or {@code respStat} is successful.1199*/1200public LocalOcspResponse(OCSPResponse.ResponseStatus respStat)1201throws IOException {1202this(respStat, null, null);1203}12041205/**1206* Construct a response from a list of certificate1207* status objects and extensions.1208*1209* @param respStat the status of the entire response1210* @param itemMap a {@code Map} of {@code CertId} objects and their1211* respective revocation statuses from the server's response DB.1212* @param reqExtensions a {@code Map} of request extensions1213*1214* @throws IOException if an error happens during encoding1215* @throws NullPointerException if {@code respStat} is {@code null}1216* or {@code respStat} is successful, and a {@code null} {@code itemMap}1217* has been provided.1218*/1219public LocalOcspResponse(OCSPResponse.ResponseStatus respStat,1220Map<CertId, CertStatusInfo> itemMap,1221Map<String, Extension> reqExtensions) throws IOException {1222responseStatus = Objects.requireNonNull(respStat,1223"Illegal null response status");1224if (responseStatus == ResponseStatus.SUCCESSFUL) {1225respItemMap = Objects.requireNonNull(itemMap,1226"SUCCESSFUL responses must have a response map");1227producedAtDate = new Date();12281229// Turn the answerd from the response DB query into a list1230// of single responses.1231for (CertId id : itemMap.keySet()) {1232singleResponseList.add(1233new LocalSingleResponse(id, itemMap.get(id)));1234}12351236responseExtensions = setResponseExtensions(reqExtensions);1237certificates = new ArrayList<>();1238if (signerCert != issuerCert) {1239certificates.add(signerCert);1240}1241certificates.add(issuerCert);1242} else {1243respItemMap = null;1244producedAtDate = null;1245responseExtensions = null;1246certificates = null;1247}1248encodedResponse = this.getBytes();1249}12501251/**1252* Set the response extensions based on the request extensions1253* that were received. Right now, this is limited to the1254* OCSP nonce extension.1255*1256* @param reqExts a {@code Map} of zero or more request extensions1257*1258* @return a {@code Map} of zero or more response extensions, keyed1259* by the extension object identifier in {@code String} form.1260*/1261private Map<String, Extension> setResponseExtensions(1262Map<String, Extension> reqExts) {1263Map<String, Extension> respExts = new HashMap<>();1264String ocspNonceStr = PKIXExtensions.OCSPNonce_Id.toString();12651266if (reqExts != null) {1267for (String id : reqExts.keySet()) {1268if (id.equals(ocspNonceStr)) {1269// We found a nonce, add it into the response extensions1270Extension ext = reqExts.get(id);1271if (ext != null) {1272respExts.put(id, ext);1273log("Added OCSP Nonce to response");1274} else {1275log("Error: Found nonce entry, but found null " +1276"value. Skipping");1277}1278}1279}1280}12811282return respExts;1283}12841285/**1286* Get the DER-encoded response bytes for this response1287*1288* @return a byte array containing the DER-encoded bytes for1289* the response1290*1291* @throws IOException if any encoding errors occur1292*/1293private byte[] getBytes() throws IOException {1294DerOutputStream outerSeq = new DerOutputStream();1295DerOutputStream responseStream = new DerOutputStream();1296responseStream.putEnumerated(responseStatus.ordinal());1297if (responseStatus == ResponseStatus.SUCCESSFUL &&1298respItemMap != null) {1299encodeResponseBytes(responseStream);1300}13011302// Commit the outermost sequence bytes1303outerSeq.write(DerValue.tag_Sequence, responseStream);1304return outerSeq.toByteArray();1305}13061307private void encodeResponseBytes(DerOutputStream responseStream)1308throws IOException {1309DerOutputStream explicitZero = new DerOutputStream();1310DerOutputStream respItemStream = new DerOutputStream();13111312respItemStream.putOID(OCSP_BASIC_RESPONSE_OID);13131314byte[] basicOcspBytes = encodeBasicOcspResponse();1315respItemStream.putOctetString(basicOcspBytes);1316explicitZero.write(DerValue.tag_Sequence, respItemStream);1317responseStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1318true, (byte)0), explicitZero);1319}13201321private byte[] encodeBasicOcspResponse() throws IOException {1322DerOutputStream outerSeq = new DerOutputStream();1323DerOutputStream basicORItemStream = new DerOutputStream();13241325// Encode the tbsResponse1326byte[] tbsResponseBytes = encodeTbsResponse();1327basicORItemStream.write(tbsResponseBytes);13281329try {1330sigAlgId.derEncode(basicORItemStream);13311332// Create the signature1333Signature sig = Signature.getInstance(sigAlgId.getName());1334sig.initSign(signerKey);1335sig.update(tbsResponseBytes);1336signature = sig.sign();1337basicORItemStream.putBitString(signature);1338} catch (GeneralSecurityException exc) {1339err(exc);1340throw new IOException(exc);1341}13421343// Add certificates1344try {1345DerOutputStream certStream = new DerOutputStream();1346ArrayList<DerValue> certList = new ArrayList<>();1347if (signerCert != issuerCert) {1348certList.add(new DerValue(signerCert.getEncoded()));1349}1350certList.add(new DerValue(issuerCert.getEncoded()));1351DerValue[] dvals = new DerValue[certList.size()];1352certStream.putSequence(certList.toArray(dvals));1353basicORItemStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1354true, (byte)0), certStream);1355} catch (CertificateEncodingException cex) {1356err(cex);1357throw new IOException(cex);1358}13591360// Commit the outermost sequence bytes1361outerSeq.write(DerValue.tag_Sequence, basicORItemStream);1362return outerSeq.toByteArray();1363}13641365private byte[] encodeTbsResponse() throws IOException {1366DerOutputStream outerSeq = new DerOutputStream();1367DerOutputStream tbsStream = new DerOutputStream();13681369// Note: We're not going explicitly assert the version1370tbsStream.write(respId.getEncoded());1371tbsStream.putGeneralizedTime(producedAtDate);13721373// Sequence of responses1374encodeSingleResponses(tbsStream);13751376// TODO: add response extension support1377encodeExtensions(tbsStream);13781379outerSeq.write(DerValue.tag_Sequence, tbsStream);1380return outerSeq.toByteArray();1381}13821383private void encodeSingleResponses(DerOutputStream tbsStream)1384throws IOException {1385DerValue[] srDerVals = new DerValue[singleResponseList.size()];1386int srDvCtr = 0;13871388for (LocalSingleResponse lsr : singleResponseList) {1389srDerVals[srDvCtr++] = new DerValue(lsr.getBytes());1390}13911392tbsStream.putSequence(srDerVals);1393}13941395private void encodeExtensions(DerOutputStream tbsStream)1396throws IOException {1397DerOutputStream extSequence = new DerOutputStream();1398DerOutputStream extItems = new DerOutputStream();13991400for (Extension ext : responseExtensions.values()) {1401ext.encode(extItems);1402}1403extSequence.write(DerValue.tag_Sequence, extItems);1404tbsStream.write(DerValue.createTag(DerValue.TAG_CONTEXT, true,1405(byte)1), extSequence);1406}14071408@Override1409public String toString() {1410StringBuilder sb = new StringBuilder();14111412sb.append("OCSP Response: ").append(responseStatus).append("\n");1413if (responseStatus == ResponseStatus.SUCCESSFUL) {1414sb.append("Response Type: ").1415append(OCSP_BASIC_RESPONSE_OID.toString()).append("\n");1416sb.append(String.format("Version: %d (0x%X)", version + 1,1417version)).append("\n");1418sb.append("Responder Id: ").append(respId.toString()).1419append("\n");1420sb.append("Produced At: ").1421append(utcDateFmt.format(producedAtDate)).append("\n");14221423int srCtr = 0;1424for (LocalSingleResponse lsr : singleResponseList) {1425sb.append("SingleResponse [").append(srCtr++).append("]\n");1426sb.append(lsr);1427}14281429if (!responseExtensions.isEmpty()) {1430sb.append("Extensions (").append(responseExtensions.size()).1431append(")\n");1432for (Extension ext : responseExtensions.values()) {1433sb.append("\t").append(ext).append("\n");1434}1435} else {1436sb.append("\n");1437}14381439if (signature != null) {1440sb.append("Signature: ").append(sigAlgId).append("\n");1441sb.append(dumpHexBytes(signature)).append("\n");1442int certCtr = 0;1443for (X509Certificate cert : certificates) {1444sb.append("Certificate [").append(certCtr++).append("]").1445append("\n");1446sb.append("\tSubject: ");1447sb.append(cert.getSubjectX500Principal()).append("\n");1448sb.append("\tIssuer: ");1449sb.append(cert.getIssuerX500Principal()).append("\n");1450sb.append("\tSerial: ").append(cert.getSerialNumber());1451sb.append("\n");1452}1453}1454}14551456return sb.toString();1457}14581459private class LocalSingleResponse {1460private final CertId certId;1461private final CertStatusInfo csInfo;1462private final Date thisUpdate;1463private final Date lsrNextUpdate;1464private final Map<String, Extension> singleExtensions;14651466public LocalSingleResponse(CertId cid, CertStatusInfo info) {1467certId = Objects.requireNonNull(cid, "CertId must be non-null");1468csInfo = Objects.requireNonNull(info,1469"CertStatusInfo must be non-null");14701471// For now, we'll keep things simple and make the thisUpdate1472// field the same as the producedAt date.1473thisUpdate = producedAtDate;1474lsrNextUpdate = getNextUpdate();14751476// TODO Add extensions support1477singleExtensions = Collections.emptyMap();1478}14791480@Override1481public String toString() {1482StringBuilder sb = new StringBuilder();1483sb.append("Certificate Status: ").append(csInfo.getType());1484sb.append("\n");1485if (csInfo.getType() == CertStatus.CERT_STATUS_REVOKED) {1486sb.append("Revocation Time: ");1487sb.append(utcDateFmt.format(csInfo.getRevocationTime()));1488sb.append("\n");1489if (csInfo.getRevocationReason() != null) {1490sb.append("Revocation Reason: ");1491sb.append(csInfo.getRevocationReason()).append("\n");1492}1493}14941495sb.append("CertId, Algorithm = ");1496sb.append(certId.getHashAlgorithm()).append("\n");1497sb.append("\tIssuer Name Hash: ");1498sb.append(dumpHexBytes(certId.getIssuerNameHash(), 256, "", ""));1499sb.append("\n");1500sb.append("\tIssuer Key Hash: ");1501sb.append(dumpHexBytes(certId.getIssuerKeyHash(), 256, "", ""));1502sb.append("\n");1503sb.append("\tSerial Number: ").append(certId.getSerialNumber());1504sb.append("\n");1505sb.append("This Update: ");1506sb.append(utcDateFmt.format(thisUpdate)).append("\n");1507if (lsrNextUpdate != null) {1508sb.append("Next Update: ");1509sb.append(utcDateFmt.format(lsrNextUpdate)).append("\n");1510}15111512if (!singleExtensions.isEmpty()) {1513sb.append("Extensions (").append(singleExtensions.size()).1514append(")\n");1515for (Extension ext : singleExtensions.values()) {1516sb.append("\t").append(ext).append("\n");1517}1518}15191520return sb.toString();1521}15221523public byte[] getBytes() throws IOException {1524byte[] nullData = { };1525DerOutputStream responseSeq = new DerOutputStream();1526DerOutputStream srStream = new DerOutputStream();15271528// Encode the CertId1529certId.encode(srStream);15301531// Next, encode the CertStatus field1532CertStatus csiType = csInfo.getType();1533switch (csiType) {1534case CERT_STATUS_GOOD:1535srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1536false, (byte)0), nullData);1537break;1538case CERT_STATUS_REVOKED:1539DerOutputStream revInfo = new DerOutputStream();1540revInfo.putGeneralizedTime(csInfo.getRevocationTime());1541CRLReason revReason = csInfo.getRevocationReason();1542if (revReason != null) {1543byte[] revDer = new byte[3];1544revDer[0] = DerValue.tag_Enumerated;1545revDer[1] = 1;1546revDer[2] = (byte)revReason.ordinal();1547revInfo.write(DerValue.createTag(1548DerValue.TAG_CONTEXT, true, (byte)0),1549revDer);1550}1551srStream.write(DerValue.createTag(1552DerValue.TAG_CONTEXT, true, (byte)1),1553revInfo);1554break;1555case CERT_STATUS_UNKNOWN:1556srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1557false, (byte)2), nullData);1558break;1559default:1560throw new IOException("Unknown CertStatus: " + csiType);1561}15621563// Add the necessary dates1564srStream.putGeneralizedTime(thisUpdate);1565if (lsrNextUpdate != null) {1566DerOutputStream nuStream = new DerOutputStream();1567nuStream.putGeneralizedTime(lsrNextUpdate);1568srStream.write(DerValue.createTag(DerValue.TAG_CONTEXT,1569true, (byte)0), nuStream);1570}15711572// TODO add singleResponse Extension support15731574// Add the single response to the response output stream1575responseSeq.write(DerValue.tag_Sequence, srStream);1576return responseSeq.toByteArray();1577}1578}1579}1580}158115821583