/* * Copyright (c) 1996, 2016, 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.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.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.misc.HexDumpEncoder; import sun.security.timestamp.TimestampToken; import sun.security.util.CryptoPrimitive; 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.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 DIGEST_PRIMITIVE_SET = Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST)); private static final Set 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. * *

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 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 getCertificateChain(PKCS7 block) throws IOException { X509Certificate userCert; userCert = block.getCertificate(certificateSerialNumber, issuerName); if (userCert == null) return null; ArrayList 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(); } 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((Object)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 algorithm is not restricted if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, digestAlgname, null)) { throw new SignatureException("Digest check failed. " + "Disabled algorithm used: " + digestAlgname); } MessageDigest md = MessageDigest.getInstance( AlgorithmId.getStandardDigestName(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 algorithm is not restricted if (!JAR_DISABLED_CHECK.permits(SIG_PRIMITIVE_SET, algname, null)) { throw new SignatureException("Signature check failed. " + "Disabled algorithm used: " + algname); } X509Certificate cert = getCertificate(block); PublicKey key = cert.getPublicKey(); if (cert == null) { return null; } // 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; } /* * Extracts a timestamp from a PKCS7 SignerInfo. * * Examines the signer's unsigned attributes for a * 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; if (unauthenticatedAttributes == null) { hasTimestamp = false; return null; } PKCS9Attribute tsTokenAttr = unauthenticatedAttributes.getAttribute( PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID); if (tsTokenAttr == null) { hasTimestamp = false; return null; } PKCS7 tsToken = new PKCS7((byte[])tsTokenAttr.getValue()); // 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 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 = AlgorithmId.getStandardDigestName(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; } }