--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/pkcs/SignerInfo.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,639 @@
+/*
+ * Copyright (c) 1996, 2017, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.security.pkcs;
+
+import java.io.OutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.CryptoPrimitive;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.Timestamp;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CertPath;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import sun.security.timestamp.TimestampToken;
+import sun.security.util.ConstraintsParameters;
+import sun.security.util.Debug;
+import sun.security.util.DerEncoder;
+import sun.security.util.DerInputStream;
+import sun.security.util.DerOutputStream;
+import sun.security.util.DerValue;
+import sun.security.util.DisabledAlgorithmConstraints;
+import sun.security.util.HexDumpEncoder;
+import sun.security.util.KeyUtil;
+import sun.security.util.ObjectIdentifier;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.X500Name;
+import sun.security.x509.KeyUsageExtension;
+
+/**
+ * A SignerInfo, as defined in PKCS#7's signedData type.
+ *
+ * @author Benjamin Renaud
+ */
+public class SignerInfo implements DerEncoder {
+
+ // Digest and Signature restrictions
+ private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET =
+ Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST));
+
+ private static final Set<CryptoPrimitive> SIG_PRIMITIVE_SET =
+ Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE));
+
+ private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =
+ new DisabledAlgorithmConstraints(
+ DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS);
+
+ BigInteger version;
+ X500Name issuerName;
+ BigInteger certificateSerialNumber;
+ AlgorithmId digestAlgorithmId;
+ AlgorithmId digestEncryptionAlgorithmId;
+ byte[] encryptedDigest;
+ Timestamp timestamp;
+ private boolean hasTimestamp = true;
+ private static final Debug debug = Debug.getInstance("jar");
+
+ PKCS9Attributes authenticatedAttributes;
+ PKCS9Attributes unauthenticatedAttributes;
+
+ public SignerInfo(X500Name issuerName,
+ BigInteger serial,
+ AlgorithmId digestAlgorithmId,
+ AlgorithmId digestEncryptionAlgorithmId,
+ byte[] encryptedDigest) {
+ this.version = BigInteger.ONE;
+ this.issuerName = issuerName;
+ this.certificateSerialNumber = serial;
+ this.digestAlgorithmId = digestAlgorithmId;
+ this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
+ this.encryptedDigest = encryptedDigest;
+ }
+
+ public SignerInfo(X500Name issuerName,
+ BigInteger serial,
+ AlgorithmId digestAlgorithmId,
+ PKCS9Attributes authenticatedAttributes,
+ AlgorithmId digestEncryptionAlgorithmId,
+ byte[] encryptedDigest,
+ PKCS9Attributes unauthenticatedAttributes) {
+ this.version = BigInteger.ONE;
+ this.issuerName = issuerName;
+ this.certificateSerialNumber = serial;
+ this.digestAlgorithmId = digestAlgorithmId;
+ this.authenticatedAttributes = authenticatedAttributes;
+ this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
+ this.encryptedDigest = encryptedDigest;
+ this.unauthenticatedAttributes = unauthenticatedAttributes;
+ }
+
+ /**
+ * Parses a PKCS#7 signer info.
+ */
+ public SignerInfo(DerInputStream derin)
+ throws IOException, ParsingException
+ {
+ this(derin, false);
+ }
+
+ /**
+ * Parses a PKCS#7 signer info.
+ *
+ * <p>This constructor is used only for backwards compatibility with
+ * PKCS#7 blocks that were generated using JDK1.1.x.
+ *
+ * @param derin the ASN.1 encoding of the signer info.
+ * @param oldStyle flag indicating whether or not the given signer info
+ * is encoded according to JDK1.1.x.
+ */
+ public SignerInfo(DerInputStream derin, boolean oldStyle)
+ throws IOException, ParsingException
+ {
+ // version
+ version = derin.getBigInteger();
+
+ // issuerAndSerialNumber
+ DerValue[] issuerAndSerialNumber = derin.getSequence(2);
+ byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
+ issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
+ issuerBytes));
+ certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();
+
+ // digestAlgorithmId
+ DerValue tmp = derin.getDerValue();
+
+ digestAlgorithmId = AlgorithmId.parse(tmp);
+
+ // authenticatedAttributes
+ if (oldStyle) {
+ // In JDK1.1.x, the authenticatedAttributes are always present,
+ // encoded as an empty Set (Set of length zero)
+ derin.getSet(0);
+ } else {
+ // check if set of auth attributes (implicit tag) is provided
+ // (auth attributes are OPTIONAL)
+ if ((byte)(derin.peekByte()) == (byte)0xA0) {
+ authenticatedAttributes = new PKCS9Attributes(derin);
+ }
+ }
+
+ // digestEncryptionAlgorithmId - little RSA naming scheme -
+ // signature == encryption...
+ tmp = derin.getDerValue();
+
+ digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);
+
+ // encryptedDigest
+ encryptedDigest = derin.getOctetString();
+
+ // unauthenticatedAttributes
+ if (oldStyle) {
+ // In JDK1.1.x, the unauthenticatedAttributes are always present,
+ // encoded as an empty Set (Set of length zero)
+ derin.getSet(0);
+ } else {
+ // check if set of unauth attributes (implicit tag) is provided
+ // (unauth attributes are OPTIONAL)
+ if (derin.available() != 0
+ && (byte)(derin.peekByte()) == (byte)0xA1) {
+ unauthenticatedAttributes =
+ new PKCS9Attributes(derin, true);// ignore unsupported attrs
+ }
+ }
+
+ // all done
+ if (derin.available() != 0) {
+ throw new ParsingException("extra data at the end");
+ }
+ }
+
+ public void encode(DerOutputStream out) throws IOException {
+
+ derEncode(out);
+ }
+
+ /**
+ * DER encode this object onto an output stream.
+ * Implements the {@code DerEncoder} interface.
+ *
+ * @param out
+ * the output stream on which to write the DER encoding.
+ *
+ * @exception IOException on encoding error.
+ */
+ public void derEncode(OutputStream out) throws IOException {
+ DerOutputStream seq = new DerOutputStream();
+ seq.putInteger(version);
+ DerOutputStream issuerAndSerialNumber = new DerOutputStream();
+ issuerName.encode(issuerAndSerialNumber);
+ issuerAndSerialNumber.putInteger(certificateSerialNumber);
+ seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);
+
+ digestAlgorithmId.encode(seq);
+
+ // encode authenticated attributes if there are any
+ if (authenticatedAttributes != null)
+ authenticatedAttributes.encode((byte)0xA0, seq);
+
+ digestEncryptionAlgorithmId.encode(seq);
+
+ seq.putOctetString(encryptedDigest);
+
+ // encode unauthenticated attributes if there are any
+ if (unauthenticatedAttributes != null)
+ unauthenticatedAttributes.encode((byte)0xA1, seq);
+
+ DerOutputStream tmp = new DerOutputStream();
+ tmp.write(DerValue.tag_Sequence, seq);
+
+ out.write(tmp.toByteArray());
+ }
+
+
+
+ /*
+ * Returns the (user) certificate pertaining to this SignerInfo.
+ */
+ public X509Certificate getCertificate(PKCS7 block)
+ throws IOException
+ {
+ return block.getCertificate(certificateSerialNumber, issuerName);
+ }
+
+ /*
+ * Returns the certificate chain pertaining to this SignerInfo.
+ */
+ public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
+ throws IOException
+ {
+ X509Certificate userCert;
+ userCert = block.getCertificate(certificateSerialNumber, issuerName);
+ if (userCert == null)
+ return null;
+
+ ArrayList<X509Certificate> certList = new ArrayList<>();
+ certList.add(userCert);
+
+ X509Certificate[] pkcsCerts = block.getCertificates();
+ if (pkcsCerts == null
+ || userCert.getSubjectDN().equals(userCert.getIssuerDN())) {
+ return certList;
+ }
+
+ Principal issuer = userCert.getIssuerDN();
+ int start = 0;
+ while (true) {
+ boolean match = false;
+ int i = start;
+ while (i < pkcsCerts.length) {
+ if (issuer.equals(pkcsCerts[i].getSubjectDN())) {
+ // next cert in chain found
+ certList.add(pkcsCerts[i]);
+ // if selected cert is self-signed, we're done
+ // constructing the chain
+ if (pkcsCerts[i].getSubjectDN().equals(
+ pkcsCerts[i].getIssuerDN())) {
+ start = pkcsCerts.length;
+ } else {
+ issuer = pkcsCerts[i].getIssuerDN();
+ X509Certificate tmpCert = pkcsCerts[start];
+ pkcsCerts[start] = pkcsCerts[i];
+ pkcsCerts[i] = tmpCert;
+ start++;
+ }
+ match = true;
+ break;
+ } else {
+ i++;
+ }
+ }
+ if (!match)
+ break;
+ }
+
+ return certList;
+ }
+
+ /* Returns null if verify fails, this signerInfo if
+ verify succeeds. */
+ SignerInfo verify(PKCS7 block, byte[] data)
+ throws NoSuchAlgorithmException, SignatureException {
+
+ try {
+
+ ContentInfo content = block.getContentInfo();
+ if (data == null) {
+ data = content.getContentBytes();
+ }
+
+ Timestamp timestamp = null;
+ try {
+ timestamp = getTimestamp();
+ } catch (Exception ignore) {
+ }
+
+ ConstraintsParameters cparams =
+ new ConstraintsParameters(timestamp);
+ String digestAlgname = getDigestAlgorithmId().getName();
+
+ byte[] dataSigned;
+
+ // if there are authenticate attributes, get the message
+ // digest and compare it with the digest of data
+ if (authenticatedAttributes == null) {
+ dataSigned = data;
+ } else {
+
+ // first, check content type
+ ObjectIdentifier contentType = (ObjectIdentifier)
+ authenticatedAttributes.getAttributeValue(
+ PKCS9Attribute.CONTENT_TYPE_OID);
+ if (contentType == null ||
+ !contentType.equals(content.contentType))
+ return null; // contentType does not match, bad SignerInfo
+
+ // now, check message digest
+ byte[] messageDigest = (byte[])
+ authenticatedAttributes.getAttributeValue(
+ PKCS9Attribute.MESSAGE_DIGEST_OID);
+
+ if (messageDigest == null) // fail if there is no message digest
+ return null;
+
+ // check that digest algorithm is not restricted
+ try {
+ JAR_DISABLED_CHECK.permits(digestAlgname, cparams);
+ } catch (CertPathValidatorException e) {
+ throw new SignatureException(e.getMessage(), e);
+ }
+
+ MessageDigest md = MessageDigest.getInstance(digestAlgname);
+ byte[] computedMessageDigest = md.digest(data);
+
+ if (messageDigest.length != computedMessageDigest.length)
+ return null;
+ for (int i = 0; i < messageDigest.length; i++) {
+ if (messageDigest[i] != computedMessageDigest[i])
+ return null;
+ }
+
+ // message digest attribute matched
+ // digest of original data
+
+ // the data actually signed is the DER encoding of
+ // the authenticated attributes (tagged with
+ // the "SET OF" tag, not 0xA0).
+ dataSigned = authenticatedAttributes.getDerEncoding();
+ }
+
+ // put together digest algorithm and encryption algorithm
+ // to form signing algorithm
+ String encryptionAlgname =
+ getDigestEncryptionAlgorithmId().getName();
+
+ // Workaround: sometimes the encryptionAlgname is actually
+ // a signature name
+ String tmp = AlgorithmId.getEncAlgFromSigAlg(encryptionAlgname);
+ if (tmp != null) encryptionAlgname = tmp;
+ String algname = AlgorithmId.makeSigAlg(
+ digestAlgname, encryptionAlgname);
+
+ // check that jar signature algorithm is not restricted
+ try {
+ JAR_DISABLED_CHECK.permits(algname, cparams);
+ } catch (CertPathValidatorException e) {
+ throw new SignatureException(e.getMessage(), e);
+ }
+
+ X509Certificate cert = getCertificate(block);
+ if (cert == null) {
+ return null;
+ }
+ PublicKey key = cert.getPublicKey();
+
+ // check if the public key is restricted
+ if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, key)) {
+ throw new SignatureException("Public key check failed. " +
+ "Disabled key used: " +
+ KeyUtil.getKeySize(key) + " bit " +
+ key.getAlgorithm());
+ }
+
+ if (cert.hasUnsupportedCriticalExtension()) {
+ throw new SignatureException("Certificate has unsupported "
+ + "critical extension(s)");
+ }
+
+ // Make sure that if the usage of the key in the certificate is
+ // restricted, it can be used for digital signatures.
+ // XXX We may want to check for additional extensions in the
+ // future.
+ boolean[] keyUsageBits = cert.getKeyUsage();
+ if (keyUsageBits != null) {
+ KeyUsageExtension keyUsage;
+ try {
+ // We don't care whether or not this extension was marked
+ // critical in the certificate.
+ // We're interested only in its value (i.e., the bits set)
+ // and treat the extension as critical.
+ keyUsage = new KeyUsageExtension(keyUsageBits);
+ } catch (IOException ioe) {
+ throw new SignatureException("Failed to parse keyUsage "
+ + "extension");
+ }
+
+ boolean digSigAllowed = keyUsage.get(
+ KeyUsageExtension.DIGITAL_SIGNATURE).booleanValue();
+
+ boolean nonRepuAllowed = keyUsage.get(
+ KeyUsageExtension.NON_REPUDIATION).booleanValue();
+
+ if (!digSigAllowed && !nonRepuAllowed) {
+ throw new SignatureException("Key usage restricted: "
+ + "cannot be used for "
+ + "digital signatures");
+ }
+ }
+
+ Signature sig = Signature.getInstance(algname);
+ sig.initVerify(key);
+ sig.update(dataSigned);
+ if (sig.verify(encryptedDigest)) {
+ return this;
+ }
+
+ } catch (IOException e) {
+ throw new SignatureException("IO error verifying signature:\n" +
+ e.getMessage());
+
+ } catch (InvalidKeyException e) {
+ throw new SignatureException("InvalidKey: " + e.getMessage());
+
+ }
+ return null;
+ }
+
+ /* Verify the content of the pkcs7 block. */
+ SignerInfo verify(PKCS7 block)
+ throws NoSuchAlgorithmException, SignatureException {
+ return verify(block, null);
+ }
+
+
+ public BigInteger getVersion() {
+ return version;
+ }
+
+ public X500Name getIssuerName() {
+ return issuerName;
+ }
+
+ public BigInteger getCertificateSerialNumber() {
+ return certificateSerialNumber;
+ }
+
+ public AlgorithmId getDigestAlgorithmId() {
+ return digestAlgorithmId;
+ }
+
+ public PKCS9Attributes getAuthenticatedAttributes() {
+ return authenticatedAttributes;
+ }
+
+ public AlgorithmId getDigestEncryptionAlgorithmId() {
+ return digestEncryptionAlgorithmId;
+ }
+
+ public byte[] getEncryptedDigest() {
+ return encryptedDigest;
+ }
+
+ public PKCS9Attributes getUnauthenticatedAttributes() {
+ return unauthenticatedAttributes;
+ }
+
+ /**
+ * Returns the timestamp PKCS7 data unverified.
+ * @return a PKCS7 object
+ */
+ public PKCS7 getTsToken() throws IOException {
+ if (unauthenticatedAttributes == null) {
+ return null;
+ }
+ PKCS9Attribute tsTokenAttr =
+ unauthenticatedAttributes.getAttribute(
+ PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID);
+ if (tsTokenAttr == null) {
+ return null;
+ }
+ return new PKCS7((byte[])tsTokenAttr.getValue());
+ }
+
+ /*
+ * Extracts a timestamp from a PKCS7 SignerInfo.
+ *
+ * Examines the signer's unsigned attributes for a
+ * {@code signatureTimestampToken} attribute. If present,
+ * then it is parsed to extract the date and time at which the
+ * timestamp was generated.
+ *
+ * @param info A signer information element of a PKCS 7 block.
+ *
+ * @return A timestamp token or null if none is present.
+ * @throws IOException if an error is encountered while parsing the
+ * PKCS7 data.
+ * @throws NoSuchAlgorithmException if an error is encountered while
+ * verifying the PKCS7 object.
+ * @throws SignatureException if an error is encountered while
+ * verifying the PKCS7 object.
+ * @throws CertificateException if an error is encountered while generating
+ * the TSA's certpath.
+ */
+ public Timestamp getTimestamp()
+ throws IOException, NoSuchAlgorithmException, SignatureException,
+ CertificateException
+ {
+ if (timestamp != null || !hasTimestamp)
+ return timestamp;
+
+ PKCS7 tsToken = getTsToken();
+ if (tsToken == null) {
+ hasTimestamp = false;
+ return null;
+ }
+
+ // Extract the content (an encoded timestamp token info)
+ byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
+ // Extract the signer (the Timestamping Authority)
+ // while verifying the content
+ SignerInfo[] tsa = tsToken.verify(encTsTokenInfo);
+ // Expect only one signer
+ ArrayList<X509Certificate> chain = tsa[0].getCertificateChain(tsToken);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ CertPath tsaChain = cf.generateCertPath(chain);
+ // Create a timestamp token info object
+ TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo);
+ // Check that the signature timestamp applies to this signature
+ verifyTimestamp(tsTokenInfo);
+ // Create a timestamp object
+ timestamp = new Timestamp(tsTokenInfo.getDate(), tsaChain);
+ return timestamp;
+ }
+
+ /*
+ * Check that the signature timestamp applies to this signature.
+ * Match the hash present in the signature timestamp token against the hash
+ * of this signature.
+ */
+ private void verifyTimestamp(TimestampToken token)
+ throws NoSuchAlgorithmException, SignatureException {
+ String digestAlgname = token.getHashAlgorithm().getName();
+ // check that algorithm is not restricted
+ if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, digestAlgname,
+ null)) {
+ throw new SignatureException("Timestamp token digest check failed. " +
+ "Disabled algorithm used: " + digestAlgname);
+ }
+
+ MessageDigest md =
+ MessageDigest.getInstance(digestAlgname);
+
+ if (!Arrays.equals(token.getHashedMessage(),
+ md.digest(encryptedDigest))) {
+
+ throw new SignatureException("Signature timestamp (#" +
+ token.getSerialNumber() + ") generated on " + token.getDate() +
+ " is inapplicable");
+ }
+
+ if (debug != null) {
+ debug.println();
+ debug.println("Detected signature timestamp (#" +
+ token.getSerialNumber() + ") generated on " + token.getDate());
+ debug.println();
+ }
+ }
+
+ public String toString() {
+ HexDumpEncoder hexDump = new HexDumpEncoder();
+
+ String out = "";
+
+ out += "Signer Info for (issuer): " + issuerName + "\n";
+ out += "\tversion: " + Debug.toHexString(version) + "\n";
+ out += "\tcertificateSerialNumber: " +
+ Debug.toHexString(certificateSerialNumber) + "\n";
+ out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
+ if (authenticatedAttributes != null) {
+ out += "\tauthenticatedAttributes: " + authenticatedAttributes +
+ "\n";
+ }
+ out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
+ "\n";
+
+ out += "\tencryptedDigest: " + "\n" +
+ hexDump.encodeBuffer(encryptedDigest) + "\n";
+ if (unauthenticatedAttributes != null) {
+ out += "\tunauthenticatedAttributes: " +
+ unauthenticatedAttributes + "\n";
+ }
+ return out;
+ }
+}