< 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 >