< prev index next >
src/share/classes/sun/security/pkcs/PKCS7.java
Print this page
rev 1518 : 7102686: Restructure timestamp code so that jars and modules can more easily share the same code
Reviewed-by: mchung
rev 1522 : 8009636: JARSigner including TimeStamp PolicyID (TSAPolicyID) as defined in RFC3161
Reviewed-by: mullan
rev 1523 : 8038837: Add support to jarsigner for specifying timestamp hash algorithm
Reviewed-by: weijun
rev 1524 : 7142339: PKCS7.java is needlessly creating SHA1PRNG SecureRandom instances when timestamping is not done
Reviewed-by: xuelei, wetmore
@@ -1,7 +1,7 @@
/*
- * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2012, 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. Oracle designates this
@@ -25,19 +25,21 @@
package sun.security.pkcs;
import java.io.*;
import java.math.BigInteger;
+import java.net.URI;
import java.util.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.CRLException;
import java.security.cert.CertificateFactory;
import java.security.*;
+import sun.security.timestamp.*;
import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateIssuerName;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
@@ -67,10 +69,37 @@
private boolean oldStyle = false; // Is this JDK1.1.x-style?
private Principal[] certIssuerNames;
+ /*
+ * Random number generator for creating nonce values
+ * (Lazy initialization)
+ */
+ private static class SecureRandomHolder {
+ static final SecureRandom RANDOM;
+ static {
+ SecureRandom tmp = null;
+ try {
+ tmp = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ // should not happen
+ }
+ RANDOM = tmp;
+ }
+ }
+
+ /*
+ * Object identifier for the timestamping key purpose.
+ */
+ private static final String KP_TIMESTAMPING_OID = "1.3.6.1.5.5.7.3.8";
+
+ /*
+ * Object identifier for extendedKeyUsage extension
+ */
+ private static final String EXTENDED_KEY_USAGE_OID = "2.5.29.37";
+
/**
* Unmarshals a PKCS7 block from its encoded form, parsing the
* encoded bytes from the InputStream.
*
* @param in an input stream holding at least one PKCS7 block.
@@ -701,6 +730,188 @@
* otherwise.
*/
public boolean isOldStyle() {
return this.oldStyle;
}
+
+ /**
+ * Assembles a PKCS #7 signed data message that optionally includes a
+ * signature timestamp.
+ *
+ * @param signature the signature bytes
+ * @param signerChain the signer's X.509 certificate chain
+ * @param content the content that is signed; specify null to not include
+ * it in the PKCS7 data
+ * @param signatureAlgorithm the name of the signature algorithm
+ * @param tsaURI the URI of the Timestamping Authority; or null if no
+ * timestamp is requested
+ * @param tSAPolicyID the TSAPolicyID of the Timestamping Authority as a
+ * numerical object identifier; or null if we leave the TSA server
+ * to choose one. This argument is only used when tsaURI is provided
+ * @return the bytes of the encoded PKCS #7 signed data message
+ * @throws NoSuchAlgorithmException The exception is thrown if the signature
+ * algorithm is unrecognised.
+ * @throws CertificateException The exception is thrown if an error occurs
+ * while processing the signer's certificate or the TSA's
+ * certificate.
+ * @throws IOException The exception is thrown if an error occurs while
+ * generating the signature timestamp or while generating the signed
+ * data message.
+ */
+ public static byte[] generateSignedData(byte[] signature,
+ X509Certificate[] signerChain,
+ byte[] content,
+ String signatureAlgorithm,
+ URI tsaURI,
+ String tSAPolicyID,
+ String tSADigestAlg)
+ throws CertificateException, IOException, NoSuchAlgorithmException
+ {
+
+ // Generate the timestamp token
+ PKCS9Attributes unauthAttrs = null;
+ if (tsaURI != null) {
+ // Timestamp the signature
+ HttpTimestamper tsa = new HttpTimestamper(tsaURI);
+ byte[] tsToken = generateTimestampToken(
+ tsa, tSAPolicyID, tSADigestAlg, signature);
+
+ // Insert the timestamp token into the PKCS #7 signer info element
+ // (as an unsigned attribute)
+ unauthAttrs =
+ new PKCS9Attributes(new PKCS9Attribute[]{
+ new PKCS9Attribute(
+ PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_STR,
+ tsToken)});
+ }
+
+ // Create the SignerInfo
+ X500Name issuerName =
+ X500Name.asX500Name(signerChain[0].getIssuerX500Principal());
+ BigInteger serialNumber = signerChain[0].getSerialNumber();
+ String encAlg = AlgorithmId.getEncAlgFromSigAlg(signatureAlgorithm);
+ String digAlg = AlgorithmId.getDigAlgFromSigAlg(signatureAlgorithm);
+ SignerInfo signerInfo = new SignerInfo(issuerName, serialNumber,
+ AlgorithmId.get(digAlg), null,
+ AlgorithmId.get(encAlg),
+ signature, unauthAttrs);
+
+ // Create the PKCS #7 signed data message
+ SignerInfo[] signerInfos = {signerInfo};
+ AlgorithmId[] algorithms = {signerInfo.getDigestAlgorithmId()};
+ // Include or exclude content
+ ContentInfo contentInfo = (content == null)
+ ? new ContentInfo(ContentInfo.DATA_OID, null)
+ : new ContentInfo(content);
+ PKCS7 pkcs7 = new PKCS7(algorithms, contentInfo,
+ signerChain, signerInfos);
+ ByteArrayOutputStream p7out = new ByteArrayOutputStream();
+ pkcs7.encodeSignedData(p7out);
+
+ return p7out.toByteArray();
+ }
+
+ /**
+ * Requests, processes and validates a timestamp token from a TSA using
+ * common defaults. Uses the following defaults in the timestamp request:
+ * SHA-1 for the hash algorithm, a 64-bit nonce, and request certificate
+ * set to true.
+ *
+ * @param tsa the timestamping authority to use
+ * @param tSAPolicyID the TSAPolicyID of the Timestamping Authority as a
+ * numerical object identifier; or null if we leave the TSA server
+ * to choose one
+ * @param toBeTimestamped the token that is to be timestamped
+ * @return the encoded timestamp token
+ * @throws IOException The exception is thrown if an error occurs while
+ * communicating with the TSA, or a non-null
+ * TSAPolicyID is specified in the request but it
+ * does not match the one in the reply
+ * @throws CertificateException The exception is thrown if the TSA's
+ * certificate is not permitted for timestamping.
+ */
+ private static byte[] generateTimestampToken(Timestamper tsa,
+ String tSAPolicyID,
+ String tSADigestAlg,
+ byte[] toBeTimestamped)
+ throws IOException, CertificateException
+ {
+ // Generate a timestamp
+ MessageDigest messageDigest = null;
+ TSRequest tsQuery = null;
+ try {
+ messageDigest = MessageDigest.getInstance(tSADigestAlg);
+ tsQuery = new TSRequest(tSAPolicyID, toBeTimestamped, messageDigest);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ // Generate a nonce
+ BigInteger nonce = null;
+ if (SecureRandomHolder.RANDOM != null) {
+ nonce = new BigInteger(64, SecureRandomHolder.RANDOM);
+ tsQuery.setNonce(nonce);
+ }
+ tsQuery.requestCertificate(true);
+
+ TSResponse tsReply = tsa.generateTimestamp(tsQuery);
+ int status = tsReply.getStatusCode();
+ // Handle TSP error
+ if (status != 0 && status != 1) {
+ throw new IOException("Error generating timestamp: " +
+ tsReply.getStatusCodeAsText() + " " +
+ tsReply.getFailureCodeAsText());
+ }
+
+ if (tSAPolicyID != null &&
+ !tSAPolicyID.equals(tsReply.getTimestampToken().getPolicyID())) {
+ throw new IOException("TSAPolicyID changed in "
+ + "timestamp token");
+ }
+ PKCS7 tsToken = tsReply.getToken();
+
+ TimestampToken tst = tsReply.getTimestampToken();
+ try {
+ if (!tst.getHashAlgorithm().equals(AlgorithmId.get(tSADigestAlg))) {
+ throw new IOException("Digest algorithm not " + tSADigestAlg + " in "
+ + "timestamp token");
+ }
+ } catch (NoSuchAlgorithmException nase) {
+ throw new IllegalArgumentException(); // should have been caught before
+ }
+ if (!MessageDigest.isEqual(tst.getHashedMessage(),
+ tsQuery.getHashedMessage())) {
+ throw new IOException("Digest octets changed in timestamp token");
+ }
+
+ BigInteger replyNonce = tst.getNonce();
+ if (replyNonce == null && nonce != null) {
+ throw new IOException("Nonce missing in timestamp token");
+ }
+ if (replyNonce != null && !replyNonce.equals(nonce)) {
+ throw new IOException("Nonce changed in timestamp token");
+ }
+
+ // Examine the TSA's certificate (if present)
+ for (SignerInfo si: tsToken.getSignerInfos()) {
+ X509Certificate cert = si.getCertificate(tsToken);
+ if (cert == null) {
+ // Error, we've already set tsRequestCertificate = true
+ throw new CertificateException(
+ "Certificate not included in timestamp token");
+ } else {
+ if (!cert.getCriticalExtensionOIDs().contains(
+ EXTENDED_KEY_USAGE_OID)) {
+ throw new CertificateException(
+ "Certificate is not valid for timestamping");
+ }
+ List<String> keyPurposes = cert.getExtendedKeyUsage();
+ if (keyPurposes == null ||
+ !keyPurposes.contains(KP_TIMESTAMPING_OID)) {
+ throw new CertificateException(
+ "Certificate is not valid for timestamping");
+ }
+ }
+ }
+ return tsReply.getEncodedToken();
+ }
}
< prev index next >