| /* |
| * 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.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.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.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<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</code> 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<X509Certificate>(); |
| 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(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 |
| * <tt>signatureTimestampToken</tt> 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<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; |
| } |
| } |