8244683: A TSA server used by tests

Reviewed-by: weijun
This commit is contained in:
John Jiang 2020-06-05 23:36:01 +08:00
parent ec4240b342
commit 13d30235e1
8 changed files with 1084 additions and 218 deletions

View File

@ -21,49 +21,41 @@
* questions.
*/
import com.sun.net.httpserver.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.sun.net.httpserver.HttpExchange;
import jdk.test.lib.SecurityTools;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.lib.security.timestamp.*;
import jdk.test.lib.util.JarUtils;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.SignerInfo;
import sun.security.timestamp.TimestampToken;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;
/*
* @test
* @bug 6543842 6543440 6939248 8009636 8024302 8163304 8169911 8180289 8172404
* 8242151
* @summary checking response of timestamp
* @modules java.base/sun.security.pkcs
* java.base/sun.security.timestamp
@ -85,241 +77,112 @@ import sun.security.x509.X500Name;
*/
public class TimestampCheck {
static final String defaultPolicyId = "2.3.4";
static String host = null;
private static final String PASSWORD = "changeit";
private static final String defaultPolicyId = "2.3.4";
private static String host = null;
static class Handler implements HttpHandler, AutoCloseable {
private static class Interceptor implements RespInterceptor {
private final HttpServer httpServer;
private final String keystore;
private final String path;
@Override
public void handle(HttpExchange t) throws IOException {
int len = 0;
for (String h: t.getRequestHeaders().keySet()) {
if (h.equalsIgnoreCase("Content-length")) {
len = Integer.valueOf(t.getRequestHeaders().get(h).get(0));
}
}
byte[] input = new byte[len];
t.getRequestBody().read(input);
try {
String path = t.getRequestURI().getPath().substring(1);
byte[] output = sign(input, path);
Headers out = t.getResponseHeaders();
out.set("Content-Type", "application/timestamp-reply");
t.sendResponseHeaders(200, output.length);
OutputStream os = t.getResponseBody();
os.write(output);
} catch (Exception e) {
e.printStackTrace();
t.sendResponseHeaders(500, 0);
}
t.close();
Interceptor(String path) {
this.path = path;
}
/**
* @param input The data to sign
* @param path different cases to simulate, impl on URL path
* @returns the signed
*/
byte[] sign(byte[] input, String path) throws Exception {
DerValue value = new DerValue(input);
System.out.println("#\n# Incoming Request\n===================");
System.out.println("# Version: " + value.data.getInteger());
DerValue messageImprint = value.data.getDerValue();
AlgorithmId aid = AlgorithmId.parse(
messageImprint.data.getDerValue());
System.out.println("# AlgorithmId: " + aid);
ObjectIdentifier policyId = ObjectIdentifier.of(defaultPolicyId);
BigInteger nonce = null;
while (value.data.available() > 0) {
DerValue v = value.data.getDerValue();
if (v.tag == DerValue.tag_Integer) {
nonce = v.getBigInteger();
System.out.println("# nonce: " + nonce);
} else if (v.tag == DerValue.tag_Boolean) {
System.out.println("# certReq: " + v.getBoolean());
} else if (v.tag == DerValue.tag_ObjectId) {
policyId = v.getOID();
System.out.println("# PolicyID: " + policyId);
}
}
System.out.println("#\n# Response\n===================");
KeyStore ks = KeyStore.getInstance(
new File(keystore), "changeit".toCharArray());
// If path starts with "ts", use the TSA it points to.
// Otherwise, always use "ts".
String alias = path.startsWith("ts") ? path : "ts";
if (path.equals("diffpolicy")) {
policyId = ObjectIdentifier.of(defaultPolicyId);
}
DerOutputStream statusInfo = new DerOutputStream();
statusInfo.putInteger(0);
AlgorithmId[] algorithms = {aid};
Certificate[] chain = ks.getCertificateChain(alias);
X509Certificate[] signerCertificateChain;
X509Certificate signer = (X509Certificate)chain[0];
if (path.equals("fullchain")) { // Only case 5 uses full chain
signerCertificateChain = new X509Certificate[chain.length];
for (int i=0; i<chain.length; i++) {
signerCertificateChain[i] = (X509Certificate)chain[i];
}
@Override
public X509Certificate[] getSignerCertChain(
X509Certificate[] signerCertChain, boolean certReq)
throws Exception {
if (path.equals("fullchain")) { // Only case 5 uses full chain
return signerCertChain;
} else if (path.equals("nocert")) {
signerCertificateChain = new X509Certificate[0];
return new X509Certificate[0];
} else {
signerCertificateChain = new X509Certificate[1];
signerCertificateChain[0] = (X509Certificate)chain[0];
return new X509Certificate[] { signerCertChain[0] };
}
}
DerOutputStream tst = new DerOutputStream();
@Override
public String getSigAlgo(String sigAlgo) throws Exception {
return "SHA256withRSA";
}
tst.putInteger(1);
tst.putOID(policyId);
@Override
public TsaParam getRespParam(TsaParam reqParam) {
TsaParam respParam = RespInterceptor.super.getRespParam(reqParam);
if (!path.equals("baddigest") && !path.equals("diffalg")) {
tst.putDerValue(messageImprint);
} else {
byte[] data = messageImprint.toByteArray();
if (path.equals("diffalg")) {
data[6] = (byte)0x01;
} else {
data[data.length-1] = (byte)0x01;
data[data.length-2] = (byte)0x02;
data[data.length-3] = (byte)0x03;
}
tst.write(data);
String policyId
= respParam.policyId() == null || path.equals("diffpolicy")
? TimestampCheck.defaultPolicyId
: respParam.policyId();
respParam.policyId(policyId);
String digestAlgo = respParam.digestAlgo();
if (path.equals("diffalg")) {
digestAlgo = digestAlgo.contains("256")
? "SHA-1" : "SHA-256";
}
respParam.digestAlgo(digestAlgo);
tst.putInteger(1);
byte[] hashedMessage = respParam.hashedMessage();
if (path.equals("baddigest")) {
hashedMessage[hashedMessage.length - 1] = (byte) 0x01;
hashedMessage[hashedMessage.length - 2] = (byte) 0x02;
hashedMessage[hashedMessage.length - 3] = (byte) 0x03;
}
respParam.hashedMessage(hashedMessage);
Instant instant = Instant.now();
if (path.equals("tsold")) {
instant = instant.minus(20, ChronoUnit.DAYS);
}
tst.putGeneralizedTime(Date.from(instant));
respParam.genTime(Date.from(instant));
BigInteger nonce = respParam.nonce();
if (path.equals("diffnonce")) {
tst.putInteger(1234);
nonce = BigInteger.valueOf(1234);
} else if (path.equals("nononce")) {
// no noce
} else {
tst.putInteger(nonce);
nonce = null;
}
respParam.nonce(nonce);
DerOutputStream tstInfo = new DerOutputStream();
tstInfo.write(DerValue.tag_Sequence, tst);
DerOutputStream tstInfo2 = new DerOutputStream();
tstInfo2.putOctetString(tstInfo.toByteArray());
// Always use the same algorithm at timestamp signing
// so it is different from the hash algorithm.
String sigAlg = "SHA256withRSA";
Signature sig = Signature.getInstance(sigAlg);
sig.initSign((PrivateKey)(ks.getKey(
alias, "changeit".toCharArray())));
sig.update(tstInfo.toByteArray());
ContentInfo contentInfo = new ContentInfo(ObjectIdentifier.of(
"1.2.840.113549.1.9.16.1.4"),
new DerValue(tstInfo2.toByteArray()));
System.out.println("# Signing...");
System.out.println("# " + new X500Name(signer
.getIssuerX500Principal().getName()));
System.out.println("# " + signer.getSerialNumber());
SignerInfo signerInfo = new SignerInfo(
new X500Name(signer.getIssuerX500Principal().getName()),
signer.getSerialNumber(),
AlgorithmId.get(AlgorithmId.getDigAlgFromSigAlg(sigAlg)),
AlgorithmId.get(AlgorithmId.getEncAlgFromSigAlg(sigAlg)),
sig.sign());
SignerInfo[] signerInfos = {signerInfo};
PKCS7 p7 = new PKCS7(algorithms, contentInfo,
signerCertificateChain, signerInfos);
ByteArrayOutputStream p7out = new ByteArrayOutputStream();
p7.encodeSignedData(p7out);
DerOutputStream response = new DerOutputStream();
response.write(DerValue.tag_Sequence, statusInfo);
response.putDerValue(new DerValue(p7out.toByteArray()));
DerOutputStream out = new DerOutputStream();
out.write(DerValue.tag_Sequence, response);
return out.toByteArray();
}
private Handler(HttpServer httpServer, String keystore) {
this.httpServer = httpServer;
this.keystore = keystore;
}
/**
* Initialize TSA instance.
*
* Extended Key Info extension of certificate that is used for
* signing TSA responses should contain timeStamping value.
*/
static Handler init(int port, String keystore) throws IOException {
HttpServer httpServer = HttpServer.create(
new InetSocketAddress(port), 0);
Handler tsa = new Handler(httpServer, keystore);
httpServer.createContext("/", tsa);
return tsa;
}
/**
* Start TSA service.
*/
void start() {
httpServer.start();
}
/**
* Stop TSA service.
*/
void stop() {
httpServer.stop(0);
}
/**
* Return server port number.
*/
int getPort() {
return httpServer.getAddress().getPort();
}
@Override
public void close() throws Exception {
stop();
return respParam;
}
}
public static void main(String[] args) throws Throwable {
private static class Handler extends TsaHandler {
try (Handler tsa = Handler.init(0, "ks");) {
Handler(String keystore) throws Exception {
super(KeyStore.getInstance(new File(keystore),
PASSWORD.toCharArray()), PASSWORD);
}
public TsaSigner createSigner(HttpExchange exchange)
throws Exception {
String path = exchange.getRequestURI().getPath().substring(1);
SignerEntry signerEntry = createSignerEntry(
path.startsWith("ts") ? path : "ts");
byte[] requestData = exchange.getRequestBody().readAllBytes();
RespInterceptor interceptor = new Interceptor(path);
return new TsaSigner(signerEntry, requestData, interceptor);
}
}
private static TsaServer initServer(String keystore) throws Exception {
return new TsaServer(new Handler(keystore));
}
public static void main(String[] args) throws Throwable {
prepare();
try (TsaServer tsa = initServer("ks");) {
tsa.start();
int port = tsa.getPort();
host = "http://localhost:" + port + "/";
if (args.length == 0) { // Run this test
prepare();
sign("normal")
.shouldNotContain("Warning")
.shouldContain("The signer certificate will expire on")

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Date;
import java.util.Objects;
/**
* A default implementation for {@link RespInterceptor}.
*/
public class DefaultRespInterceptor<T extends TsaParam>
implements RespInterceptor {
private final T param;
public DefaultRespInterceptor(T param) {
Objects.requireNonNull(param);
this.param = param;
}
@Override
public X509Certificate[] getSignerCertChain(
X509Certificate[] signerCertChain, boolean certReq)
throws Exception {
X509Certificate[] certChain = RespInterceptor.super.getSignerCertChain(
signerCertChain, certReq);
if (param.certReq() == null) {
return certChain;
} else if (param.certReq()) {
return signerCertChain;
} else {
return new X509Certificate[0];
}
}
@Override
public String getSigAlgo(String sigAlgo) throws Exception {
return param.sigAlgo() == null ? sigAlgo : param.sigAlgo();
}
@Override
public TsaParam getRespParam(TsaParam reqParam) {
TsaParam respParam = RespInterceptor.super.getRespParam(reqParam);
respParam.version(param.version() == null
? respParam.version() : param.version());
respParam.status(param.status() == null
? respParam.status() : param.status());
respParam.policyId(param.policyId() == null
? respParam.policyId() : param.policyId());
respParam.digestAlgo(param.digestAlgo() == null
? respParam.digestAlgo() : param.digestAlgo());
respParam.hashedMessage(param.hashedMessage() == null
? respParam.hashedMessage() : param.hashedMessage());
respParam.genTime(param.genTime() == null
? respParam.genTime() : param.genTime());
return respParam;
}
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.Date;
/**
* This interceptor defines some extensions for generating time-stamping
* response.
*/
public interface RespInterceptor {
/**
* Return an alternative signer certificate chain if necessary.
* In default case, the returned certificate chain is empty if the server
* certificate chain is not required.
*
* @param signerCertChain the original signer certificate chain
* @param certReq indicate if require {@code TSA} server certificate
* @return the signer certificate chain
* @throws Exception the exception
*/
default X509Certificate[] getSignerCertChain(
X509Certificate[] signerCertChain, boolean certReq)
throws Exception {
return certReq ? signerCertChain : new X509Certificate[0];
}
/**
* Return an alternative signature algorithm if necessary.
*
* @param sigAlgo the original signature algorithm
* @return the signature algorithm
* @throws Exception the exception
*/
default public String getSigAlgo(String sigAlgo) throws Exception {
return sigAlgo;
}
/**
* Return the TSA response parameters.
*
* @param reqParam the TSA request parameters.
* @return the TSA response parameters.
*/
default public TsaParam getRespParam(TsaParam reqParam) {
TsaParam respParam = TsaParam.newInstance();
respParam.version(1);
respParam.status(0);
respParam.policyId(reqParam.policyId() == null
? "2.3.4" : reqParam.policyId());
respParam.digestAlgo(reqParam.digestAlgo());
respParam.hashedMessage(reqParam.hashedMessage());
respParam.serialNumber(BigInteger.ONE);
respParam.genTime(Date.from(Instant.now()));
respParam.nonce(reqParam.nonce());
return respParam;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Objects;
/**
* A carrier for the TSA signer private key and certificate chain.
*/
public class SignerEntry {
public final PrivateKey privateKey;
public final X509Certificate[] certChain;
public final X509Certificate cert;
public SignerEntry(PrivateKey privateKey, X509Certificate[] certChain) {
Objects.requireNonNull(privateKey);
Objects.requireNonNull(certChain);
this.privateKey = privateKey;
this.certChain = certChain;
this.cert = certChain[0];
}
}

View File

@ -0,0 +1,232 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import sun.security.util.KnownOIDs;
import sun.security.x509.AlgorithmId;
/**
* A {@link HttpHandler} receiving time-stamping request and returning response.
*
* <p> It can be initialized by the given key store and key passphrase if any.
* At runtime, the handler can accept the below parameters via {@code HTTP}
* query string.
*
* <ul>
* <li><b>alias</b>: Certificate alias. The default value is the alias of the
* first found time stamping certificate in the key store.</li>
* <li><b>sigAlgo</b>: Signature algorithm. The default value is determined by
* the selected certificate.</li>
* <li><b>version</b>: The time-stamping request/token version.</li>
* <li><b>status</b>: The time-stamping status.</li>
* <li><b>policyId</b>: The policy ID.</li>
* <li><b>digestAlgo</b>: The digest algorithm used by the hashed message.</li>
* <li><b>hashedMessage</b>: The hashed message.</li>
* <li><b>serialNumber</b>: The serial number.</li>
* <li><b>genTime</b>: The time at which the time-stamp token has been created.
* The format is {@code yyyy-MM-dd}.</li>
* <li><b>nonce</b>: The nonce.</li>
* <li><b>certReq</b>: Indicate if require {@code TSA} server certificate.</li>
* </ul>
*
* <p> All the parameters are optional.
*
* <p> The {@code HTTP} query string format looks like: <br>
* {@literal alias=<certificate alias>&sigAlgo=<signature algorithm>&date=<yyyy-MM-dd>&status=<status number>}
*/
public class TsaHandler implements HttpHandler {
private static final DateTimeFormatter FORMATTER
= DateTimeFormatter.ofPattern("yyyy-MM-dd");
public final KeyStore keyStore;
public final char[] passphrase;
/**
* Create a handler with a given key store and private key passphrase.
*
* @param keyStore a key store
* @param passphrase the private key passphrase
*/
public TsaHandler(KeyStore keyStore, String passphrase) {
this.keyStore = keyStore;
this.passphrase = passphrase == null
? null
: passphrase.toCharArray();
}
@Override
public void handle(HttpExchange exchange) throws IOException {
try {
byte[] responseData = createSigner(exchange).sign();
exchange.getResponseHeaders().set(
"Content-Type", "application/timestamp-reply");
exchange.sendResponseHeaders(200, responseData.length);
exchange.getResponseBody().write(responseData);
} catch (Exception e) {
e.printStackTrace();
exchange.sendResponseHeaders(500, 0);
} finally {
exchange.close();
}
}
/**
* Create a {@link TsaSigner} instance with the time-stamping request and
* application parameters from a given {@link HttpExchange} instance.
*
* @param exchange a {@link HttpExchange} instance
* @return a {@link TsaSigner} instance
* @throws Exception the exception
*/
protected TsaSigner createSigner(HttpExchange exchange) throws Exception {
TsaParam param = getParam(exchange.getRequestURI());
String alias = getSignerAlias(param.alias(), param.sigAlgo());
SignerEntry signerEntry = createSignerEntry(alias);
byte[] requestData = exchange.getRequestBody().readAllBytes();
return new TsaSigner(signerEntry, requestData, param);
}
/**
* Create a {@link SignerEntry} from a given key store alias.
*
* @param alias the keys tore alias
* @return the {@link SignerEntry} instance
* @throws Exception the exception
*/
protected SignerEntry createSignerEntry(String alias) throws Exception {
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, passphrase);
Certificate[] certChain = keyStore.getCertificateChain(alias);
X509Certificate[] x509CertChain = Arrays.copyOf(
certChain, certChain.length, X509Certificate[].class);
SignerEntry signerEntry = new SignerEntry(privateKey, x509CertChain);
return signerEntry;
}
/**
* Parse parameters from {@link URI}.
*
* @param uri the {@link URI} from {@code HTTP} request
* @return a {@link TsaParam} instance wrapping application parameters
*/
protected TsaParam getParam(URI uri) {
String query = uri.getQuery();
TsaParam param = TsaParam.newInstance();
if (query != null) {
for (String bufParam : query.split("&")) {
String[] pair = bufParam.split("=");
if ("alias".equalsIgnoreCase(pair[0])) {
param.alias(pair[1]);
System.out.println("alias: " + param.alias());
} else if ("sigAlgo".equalsIgnoreCase(pair[0])) {
param.sigAlgo(pair[1]);
System.out.println("sigAlgo: " + param.sigAlgo());
} else if ("version".equalsIgnoreCase(pair[0])) {
param.version(Integer.valueOf(pair[1]));
System.out.println("version: " + param.version());
} else if ("status".equalsIgnoreCase(pair[0])) {
param.status(Integer.valueOf(pair[1]));
System.out.println("status: " + param.status());
} else if ("policyId".equalsIgnoreCase(pair[0])) {
param.policyId(pair[1]);
System.out.println("policyId: " + param.policyId());
} else if ("digestAlgo".equalsIgnoreCase(pair[0])) {
param.digestAlgo(pair[1]);
System.out.println("digestAlgo: " + param.digestAlgo());
} else if ("serialNumber".equalsIgnoreCase(pair[0])) {
param.serialNumber(new BigInteger(pair[1]));
System.out.println("serialNumber: " + param.serialNumber());
} else if ("nonce".equalsIgnoreCase(pair[0])) {
param.nonce(new BigInteger(pair[1]));
System.out.println("nonce: " + param.nonce());
} else if ("date".equalsIgnoreCase(pair[0])) {
Date genTime = Date.from(LocalDate.parse(pair[1], FORMATTER)
.atStartOfDay().atZone(ZoneId.systemDefault())
.toInstant());
param.genTime(genTime);
System.out.println("genTime: " + param.genTime());
} else if ("certReq".equalsIgnoreCase(pair[0])) {
param.certReq(Boolean.valueOf(pair[1]));
System.out.println("certReq: " + param.certReq());
}
}
}
return param;
}
/**
* Determine the signer certificate alias.
*
* @param alias the specified alias
* @param sigAlgo the specified signature algorithm
* @return the signer alias
* @throws Exception the exception
*/
protected String getSignerAlias(String alias, String sigAlgo)
throws Exception {
if (alias == null) {
String keyAlgo = sigAlgo == null
? null
: AlgorithmId.getEncAlgFromSigAlg(sigAlgo);
Enumeration<String> aliases = keyStore.aliases();
while(aliases.hasMoreElements()) {
String bufAlias = aliases.nextElement();
X509Certificate bufCert
= (X509Certificate) keyStore.getCertificate(bufAlias);
// The certificate must have critical extended key usage time
// stamping, and must match the key algorithm if any.
List<String> eku = bufCert.getExtendedKeyUsage();
if (eku != null && eku.contains(KnownOIDs.KP_TimeStamping.value())
&& (keyAlgo == null || keyAlgo.equalsIgnoreCase(
bufCert.getPublicKey().getAlgorithm()))) {
return bufAlias;
}
}
}
return alias;
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import jdk.test.lib.Utils;
import java.math.BigInteger;
import java.util.Date;
/**
* The application parameters. They are used for generating {@code TSA}
* response.
*/
public class TsaParam {
/********** Signing internals **********/
// The signer certificate alias in the key store
private String alias;
// The signature algorithm used by time-stamping
private String sigAlgo;
/********** TSA fields **********/
// The time-stamping token version
private Integer version;
// The time-stamping response status
private Integer status;
// The policy ID
private String policyId;
// The digest algorithm in messageImprint
private String digestAlgo;
// The hashed message in messageImprint
private byte[] hashedMessage;
// The serial number
private BigInteger serialNumber;
// The time-stamping token generation time
private Date genTime;
// The nonce
private BigInteger nonce;
// Indicate if request TSA server certificate
private Boolean certReq;
public static TsaParam newInstance() {
return new TsaParam();
}
public String alias() {
return alias;
}
public TsaParam alias(String alias) {
this.alias = alias;
return this;
}
public String sigAlgo() {
return sigAlgo;
}
public TsaParam sigAlgo(String sigAlgo) {
this.sigAlgo = sigAlgo;
return this;
}
public Integer version() {
return version;
}
public TsaParam version(Integer version) {
this.version = version;
return this;
}
public Integer status() {
return status;
}
public TsaParam status(Integer status) {
this.status = status;
return this;
}
public String policyId() {
return policyId;
}
public TsaParam policyId(String policyId) {
this.policyId = policyId;
return this;
}
public String digestAlgo() {
return digestAlgo;
}
public TsaParam digestAlgo(String digestAlgo) {
this.digestAlgo = digestAlgo;
return this;
}
public Date genTime() {
return genTime;
}
public TsaParam genTime(Date genTime) {
this.genTime = genTime;
return this;
}
public byte[] hashedMessage() {
return hashedMessage;
}
public TsaParam hashedMessage(byte[] hashedMessage) {
this.hashedMessage = hashedMessage;
return this;
}
public TsaParam hashedMessage(String hashedMessageHex) {
return hashedMessage(Utils.toByteArray(hashedMessageHex));
}
public BigInteger serialNumber() {
return serialNumber;
}
public TsaParam serialNumber(BigInteger serialNumber) {
this.serialNumber = serialNumber;
return this;
}
public BigInteger nonce() {
return nonce;
}
public TsaParam nonce(BigInteger nonce) {
this.nonce = nonce;
return this;
}
public Boolean certReq() {
return certReq;
}
public TsaParam certReq(Boolean certReq) {
this.certReq = certReq;
return this;
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.util.Objects;
import com.sun.net.httpserver.HttpServer;
/**
* A {@link HttpServer}-based {@code TSA} server working with/without a
* time-stamping request. With a set of specific parameters defined in
* {@link TsaParam} and delivered by {@code HTTP} query (see {@link TsaHandler}),
* an application could affect the time-stamping response generating process
* directly. The application parameters can override the similar fields in the
* time-stamping request. Furthermore an application can provide an alternative
* {@link RespInterceptor} or even extend {@link TsaSigner} for generating custom
* time-stamping response.
*/
public class TsaServer implements AutoCloseable {
private final HttpServer server;
/**
* Create {@code TSA} server with the given port, key store and the
* associated passphrase if any. A default {@link TsaHandler} is initialized
* along with this server.
*
* @param port port
* @param keyStore a key store
* @param passphrase the private key passphrase
* @throws IOException the I/O exception
*/
public TsaServer(int port, KeyStore keyStore, String passphrase)
throws IOException {
Objects.requireNonNull(keyStore, "Key store cannot be null");
server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new TsaHandler(keyStore, passphrase));
}
public TsaServer(KeyStore keyStore, String passphrase) throws IOException {
this(0, keyStore, passphrase);
}
public TsaServer(int port, KeyStore keyStore) throws IOException {
this(port, keyStore, null);
}
public TsaServer(KeyStore keyStore) throws IOException {
this(0, keyStore, null);
}
/**
* Create {@code TSA} server with the given port and {@link TsaHandler}.
*
* @param port the port number
* @param handler a {@link TsaHandler} instance
* @throws IOException the I/O exception
*/
public TsaServer(int port, TsaHandler handler) throws IOException {
server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", handler);
}
public TsaServer(TsaHandler handler) throws IOException {
this(0, handler);
}
/**
* Start {@code TSA} server.
*/
public void start() {
server.start();
}
/**
* Stop {@code TSA} server.
*/
public void stop() {
server.stop(0);
}
/**
* Return the server port.
*/
public int getPort() {
return server.getAddress().getPort();
}
@Override
public void close() throws Exception {
stop();
}
}

View File

@ -0,0 +1,260 @@
/*
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.test.lib.security.timestamp;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Objects;
import jdk.test.lib.hexdump.HexPrinter;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.SignerInfo;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
import sun.security.util.KnownOIDs;
import sun.security.util.ObjectIdentifier;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;
/**
* Process time-stamping request and generate signed data.
*/
public class TsaSigner {
private static final boolean DEBUG = Boolean.getBoolean("test.debug");
private static final HexPrinter HEX_PRINTER = HexPrinter.simple();
protected final SignerEntry signerEntry;
protected final byte[] requestData;
private final RespInterceptor interceptor;
/**
* Initialization.
*
* @param signerEntry a {@link SignerEntry} instance
* @param requestData the time-stamping request data
* @param interceptor the interceptor for customizing signing
*/
public TsaSigner(SignerEntry signerEntry,
byte[] requestData, RespInterceptor interceptor) {
Objects.requireNonNull(signerEntry);
Objects.requireNonNull(interceptor);
this.signerEntry = signerEntry;
this.requestData = requestData;
this.interceptor = interceptor;
}
/**
* Initialization.
*
* @param signerEntry a {@link SignerEntry} instance
* @param requestData the time-stamping request
* @param param the application parameters
*/
public TsaSigner(SignerEntry signerEntry, byte[] requestData,
TsaParam param) {
this(signerEntry, requestData,
new DefaultRespInterceptor<TsaParam>(param));
}
/**
* Sign data.
*
* @returns the time-stamping response data
*/
public byte[] sign() throws Exception {
TsaParam requestParam = parseRequestParam();
byte[] responseSeqData = createResponse(requestParam);
return responseSeqData;
}
// Parse the parameters from the time-stamping request data.
private TsaParam parseRequestParam() throws Exception {
TsaParam param = TsaParam.newInstance();
if (requestData == null) {
return param;
}
System.out.println("===== Request ====");
debug("Request", requestData);
DerValue request = new DerValue(requestData);
param.version(request.data.getInteger());
print("reqVersion", param.version());
DerValue messageImprintValue = request.data.getDerValue();
debug("messageImprintValue", messageImprintValue.toByteArray());
DerValue digestAlgoValue = messageImprintValue.data.getDerValue();
debug("digestAlgoValue", digestAlgoValue.toByteArray());
param.digestAlgo(AlgorithmId.parse(digestAlgoValue).getName());
print("reqDigestAlgo", param.digestAlgo());
param.hashedMessage(messageImprintValue.data.getOctetString());
debug("reqHashedMessage", param.hashedMessage());
while (request.data.available() > 0) {
DerValue value = request.data.getDerValue();
if (value.tag == DerValue.tag_Integer) {
param.nonce(value.getBigInteger());
print("reqNonce", param.nonce());
} else if (value.tag == DerValue.tag_Boolean) {
param.certReq(value.getBoolean());
print("certReq", param.certReq());
} else if (value.tag == DerValue.tag_ObjectId) {
param.policyId(value.getOID().toString());
print("reqPolicyId", param.policyId());
}
}
return param;
}
// Create the time-stamping response data with the given the time-stamping
// request parameters.
private byte[] createResponse(TsaParam requestParam) throws Exception {
System.out.println("===== Response ====");
TsaParam respParam = interceptor.getRespParam(requestParam);
DerOutputStream statusInfoOut = new DerOutputStream();
int status = respParam.status();
print("Status", status);
statusInfoOut.putInteger(status);
DerOutputStream responseOut = new DerOutputStream();
responseOut.write(DerValue.tag_Sequence, statusInfoOut);
debug("Status info", statusInfoOut.toByteArray());
System.out.println("Generated status info");
// Here, when the status is either 0 or 1, the response will contains
// the signed data. Note that even though the signed data is not
// generated, no failure info will be sent.
if (status == 0 || status == 1) {
System.out.println("Signer: "
+ signerEntry.cert.getSubjectX500Principal().getName());
String issuerName = signerEntry.cert.getIssuerX500Principal().getName();
print("Issuer", issuerName);
DerOutputStream tstInfoOut = new DerOutputStream();
int version = respParam.version();
print("version", version);
tstInfoOut.putInteger(version);
String policyId = respParam.policyId();
print("policyId", policyId);
tstInfoOut.putOID(ObjectIdentifier.of(policyId));
String digestAlgo = respParam.digestAlgo();
print("digestAlgo", digestAlgo);
DerOutputStream digestAlgoOut = new DerOutputStream();
AlgorithmId digestAlgoId = AlgorithmId.get(digestAlgo);
digestAlgoId.encode(digestAlgoOut);
byte[] hashedMessage = respParam.hashedMessage();
debug("hashedMessage", hashedMessage);
digestAlgoOut.putOctetString(hashedMessage);
tstInfoOut.write(DerValue.tag_Sequence, digestAlgoOut);
BigInteger serialNumber = respParam.serialNumber();
print("serialNumber", serialNumber);
tstInfoOut.putInteger(serialNumber);
Date genTime = respParam.genTime();
print("genTime", genTime);
tstInfoOut.putGeneralizedTime(genTime);
BigInteger nonce = respParam.nonce();
if (nonce != null) {
tstInfoOut.putInteger(nonce);
}
DerOutputStream tstInfoSeqOut = new DerOutputStream();
tstInfoSeqOut.write(DerValue.tag_Sequence, tstInfoOut);
byte[] tstInfoSeqData = tstInfoSeqOut.toByteArray();
debug("TST Info", tstInfoSeqData);
DerOutputStream eContentOut = new DerOutputStream();
eContentOut.putOctetString(tstInfoSeqData);
ContentInfo eContentInfo = new ContentInfo(
ObjectIdentifier.of(KnownOIDs.TimeStampTokenInfo),
new DerValue(eContentOut.toByteArray()));
String defaultSigAlgo = AlgorithmId.getDefaultSigAlgForKey(
signerEntry.privateKey);
String sigAlgo = interceptor.getSigAlgo(defaultSigAlgo);
Signature signature = Signature.getInstance(sigAlgo);
System.out.println(
"Signature algorithm: " + signature.getAlgorithm());
signature.initSign(signerEntry.privateKey);
signature.update(tstInfoSeqData);
SignerInfo signerInfo = new SignerInfo(
new X500Name(issuerName),
signerEntry.cert.getSerialNumber(),
AlgorithmId.get(
AlgorithmId.getDigAlgFromSigAlg(sigAlgo)),
AlgorithmId.get(sigAlgo),
signature.sign());
X509Certificate[] signerCertChain = interceptor.getSignerCertChain(
signerEntry.certChain, requestParam.certReq());
PKCS7 p7 = new PKCS7(new AlgorithmId[] { digestAlgoId },
eContentInfo, signerCertChain,
new SignerInfo[] { signerInfo });
ByteArrayOutputStream signedDataOut = new ByteArrayOutputStream();
p7.encodeSignedData(signedDataOut);
byte[] signedData = signedDataOut.toByteArray();
debug("Signed data", signedData);
responseOut.putDerValue(new DerValue(signedData));
System.out.println("Generated signed data");
}
DerOutputStream responseSeqOut = new DerOutputStream();
responseSeqOut.write(DerValue.tag_Sequence, responseOut);
byte[] responseSeqData = responseSeqOut.toByteArray();
debug("Response", responseSeqData);
System.out.println("Generated response");
return responseSeqData;
}
private static void print(String name, Object value) {
System.out.println(name + ": " + value);
}
private static void debug(String name, byte[] bytes) {
if (DEBUG) {
System.out.println(name + ":");
HEX_PRINTER.format(bytes);
}
}
}