0N/A/*
6336N/A * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.security.util;
0N/A
0N/Aimport java.security.cert.CertPath;
0N/Aimport java.security.cert.X509Certificate;
0N/Aimport java.security.cert.CertificateException;
0N/Aimport java.security.cert.CertificateFactory;
0N/Aimport java.security.*;
0N/Aimport java.io.*;
0N/Aimport java.util.*;
0N/Aimport java.util.jar.*;
0N/A
0N/Aimport sun.security.pkcs.*;
0N/Aimport sun.security.timestamp.TimestampToken;
0N/Aimport sun.misc.BASE64Decoder;
0N/A
0N/Aimport sun.security.jca.Providers;
0N/A
0N/Apublic class SignatureFileVerifier {
0N/A
0N/A /* Are we debugging ? */
0N/A private static final Debug debug = Debug.getInstance("jar");
0N/A
0N/A /* cache of CodeSigner objects */
0N/A private ArrayList<CodeSigner[]> signerCache;
0N/A
0N/A private static final String ATTR_DIGEST =
0N/A ("-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS).toUpperCase
0N/A (Locale.ENGLISH);
0N/A
1786N/A /** the PKCS7 block for this .DSA/.RSA/.EC file */
0N/A private PKCS7 block;
0N/A
4046N/A /** the raw bytes of the .SF file */
4046N/A private byte sfBytes[];
0N/A
0N/A /** the name of the signature block file, uppercased and without
1786N/A * the extension (.DSA/.RSA/.EC)
0N/A */
0N/A private String name;
0N/A
0N/A /** the ManifestDigester */
0N/A private ManifestDigester md;
0N/A
0N/A /** cache of created MessageDigest objects */
0N/A private HashMap<String, MessageDigest> createdDigests;
0N/A
0N/A /* workaround for parsing Netscape jars */
0N/A private boolean workaround = false;
0N/A
0N/A /* for generating certpath objects */
0N/A private CertificateFactory certificateFactory = null;
0N/A
0N/A /**
0N/A * Create the named SignatureFileVerifier.
0N/A *
1786N/A * @param name the name of the signature block file (.DSA/.RSA/.EC)
0N/A *
0N/A * @param rawBytes the raw bytes of the signature block file
0N/A */
0N/A public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache,
0N/A ManifestDigester md,
0N/A String name,
0N/A byte rawBytes[])
0N/A throws IOException, CertificateException
0N/A {
0N/A // new PKCS7() calls CertificateFactory.getInstance()
0N/A // need to use local providers here, see Providers class
0N/A Object obj = null;
0N/A try {
0N/A obj = Providers.startJarVerification();
0N/A block = new PKCS7(rawBytes);
4046N/A sfBytes = block.getContentInfo().getData();
0N/A certificateFactory = CertificateFactory.getInstance("X509");
0N/A } finally {
0N/A Providers.stopJarVerification(obj);
0N/A }
0N/A this.name = name.substring(0, name.lastIndexOf("."))
0N/A .toUpperCase(Locale.ENGLISH);
0N/A this.md = md;
0N/A this.signerCache = signerCache;
0N/A }
0N/A
0N/A /**
0N/A * returns true if we need the .SF file
0N/A */
4046N/A public boolean needSignatureFileBytes()
0N/A {
4046N/A
4046N/A return sfBytes == null;
0N/A }
0N/A
4046N/A
4046N/A /**
4046N/A * returns true if we need this .SF file.
4046N/A *
4046N/A * @param name the name of the .SF file without the extension
4046N/A *
4046N/A */
4046N/A public boolean needSignatureFile(String name)
4046N/A {
4046N/A return this.name.equalsIgnoreCase(name);
4046N/A }
4046N/A
4046N/A /**
4046N/A * used to set the raw bytes of the .SF file when it
4046N/A * is external to the signature block file.
4046N/A */
4046N/A public void setSignatureFile(byte sfBytes[])
4046N/A {
4046N/A this.sfBytes = sfBytes;
0N/A }
0N/A
0N/A /**
0N/A * Utility method used by JarVerifier and JarSigner
0N/A * to determine the signature file names and PKCS7 block
0N/A * files names that are supported
0N/A *
0N/A * @param s file name
0N/A * @return true if the input file name is a supported
0N/A * Signature File or PKCS7 block file name
0N/A */
0N/A public static boolean isBlockOrSF(String s) {
4046N/A // we currently only support DSA and RSA PKCS7 blocks
4046N/A if (s.endsWith(".SF") || s.endsWith(".DSA") ||
4046N/A s.endsWith(".RSA") || s.endsWith(".EC")) {
4046N/A return true;
4046N/A }
4046N/A return false;
0N/A }
0N/A
0N/A /** get digest from cache */
0N/A
0N/A private MessageDigest getDigest(String algorithm)
0N/A {
0N/A if (createdDigests == null)
0N/A createdDigests = new HashMap<String, MessageDigest>();
0N/A
0N/A MessageDigest digest = createdDigests.get(algorithm);
0N/A
0N/A if (digest == null) {
0N/A try {
0N/A digest = MessageDigest.getInstance(algorithm);
0N/A createdDigests.put(algorithm, digest);
0N/A } catch (NoSuchAlgorithmException nsae) {
0N/A // ignore
0N/A }
0N/A }
0N/A return digest;
0N/A }
0N/A
0N/A /**
0N/A * process the signature block file. Goes through the .SF file
0N/A * and adds code signers for each section where the .SF section
0N/A * hash was verified against the Manifest section.
0N/A *
0N/A *
0N/A */
4046N/A public void process(Hashtable<String, CodeSigner[]> signers,
3570N/A List manifestDigests)
0N/A throws IOException, SignatureException, NoSuchAlgorithmException,
0N/A JarException, CertificateException
0N/A {
0N/A // calls Signature.getInstance() and MessageDigest.getInstance()
0N/A // need to use local providers here, see Providers class
0N/A Object obj = null;
0N/A try {
0N/A obj = Providers.startJarVerification();
3570N/A processImpl(signers, manifestDigests);
0N/A } finally {
0N/A Providers.stopJarVerification(obj);
0N/A }
0N/A
0N/A }
0N/A
4046N/A private void processImpl(Hashtable<String, CodeSigner[]> signers,
3570N/A List manifestDigests)
0N/A throws IOException, SignatureException, NoSuchAlgorithmException,
0N/A JarException, CertificateException
0N/A {
4046N/A Manifest sf = new Manifest();
4046N/A sf.read(new ByteArrayInputStream(sfBytes));
0N/A
4046N/A String version =
4046N/A sf.getMainAttributes().getValue(Attributes.Name.SIGNATURE_VERSION);
0N/A
4046N/A if ((version == null) || !(version.equalsIgnoreCase("1.0"))) {
4046N/A // XXX: should this be an exception?
4046N/A // for now we just ignore this signature file
4046N/A return;
0N/A }
0N/A
4046N/A SignerInfo[] infos = block.verify(sfBytes);
0N/A
4046N/A if (infos == null) {
0N/A throw new SecurityException("cannot verify signature block file " +
0N/A name);
0N/A }
0N/A
4046N/A BASE64Decoder decoder = new BASE64Decoder();
0N/A
0N/A CodeSigner[] newSigners = getSigners(infos, block);
0N/A
0N/A // make sure we have something to do all this work for...
0N/A if (newSigners == null)
0N/A return;
0N/A
4046N/A Iterator<Map.Entry<String,Attributes>> entries =
4046N/A sf.getEntries().entrySet().iterator();
4046N/A
4046N/A // see if we can verify the whole manifest first
4046N/A boolean manifestSigned = verifyManifestHash(sf, md, decoder, manifestDigests);
4046N/A
0N/A // verify manifest main attributes
0N/A if (!manifestSigned && !verifyManifestMainAttrs(sf, md, decoder)) {
0N/A throw new SecurityException
0N/A ("Invalid signature file digest for Manifest main attributes");
0N/A }
0N/A
4046N/A // go through each section in the signature file
0N/A while(entries.hasNext()) {
0N/A
0N/A Map.Entry<String,Attributes> e = entries.next();
0N/A String name = e.getKey();
0N/A
0N/A if (manifestSigned ||
4046N/A (verifySection(e.getValue(), name, md, decoder))) {
0N/A
0N/A if (name.startsWith("./"))
0N/A name = name.substring(2);
0N/A
0N/A if (name.startsWith("/"))
0N/A name = name.substring(1);
0N/A
0N/A updateSigners(newSigners, signers, name);
0N/A
0N/A if (debug != null) {
0N/A debug.println("processSignature signed name = "+name);
0N/A }
0N/A
0N/A } else if (debug != null) {
0N/A debug.println("processSignature unsigned name = "+name);
0N/A }
0N/A }
3209N/A
3209N/A // MANIFEST.MF is always regarded as signed
3209N/A updateSigners(newSigners, signers, JarFile.MANIFEST_NAME);
0N/A }
0N/A
0N/A /**
0N/A * See if the whole manifest was signed.
0N/A */
0N/A private boolean verifyManifestHash(Manifest sf,
0N/A ManifestDigester md,
3570N/A BASE64Decoder decoder,
3570N/A List manifestDigests)
0N/A throws IOException
0N/A {
0N/A Attributes mattr = sf.getMainAttributes();
0N/A boolean manifestSigned = false;
0N/A
0N/A // go through all the attributes and process *-Digest-Manifest entries
0N/A for (Map.Entry<Object,Object> se : mattr.entrySet()) {
0N/A
0N/A String key = se.getKey().toString();
0N/A
0N/A if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) {
0N/A // 16 is length of "-Digest-Manifest"
0N/A String algorithm = key.substring(0, key.length()-16);
0N/A
3570N/A manifestDigests.add(key);
3570N/A manifestDigests.add(se.getValue());
0N/A MessageDigest digest = getDigest(algorithm);
0N/A if (digest != null) {
0N/A byte[] computedHash = md.manifestDigest(digest);
0N/A byte[] expectedHash =
0N/A decoder.decodeBuffer((String)se.getValue());
0N/A
0N/A if (debug != null) {
0N/A debug.println("Signature File: Manifest digest " +
0N/A digest.getAlgorithm());
0N/A debug.println( " sigfile " + toHex(expectedHash));
0N/A debug.println( " computed " + toHex(computedHash));
0N/A debug.println();
0N/A }
0N/A
0N/A if (MessageDigest.isEqual(computedHash,
0N/A expectedHash)) {
0N/A manifestSigned = true;
0N/A } else {
0N/A //XXX: we will continue and verify each section
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return manifestSigned;
0N/A }
0N/A
0N/A private boolean verifyManifestMainAttrs(Manifest sf,
0N/A ManifestDigester md,
0N/A BASE64Decoder decoder)
0N/A throws IOException
0N/A {
0N/A Attributes mattr = sf.getMainAttributes();
0N/A boolean attrsVerified = true;
0N/A
0N/A // go through all the attributes and process
0N/A // digest entries for the manifest main attributes
0N/A for (Map.Entry<Object,Object> se : mattr.entrySet()) {
0N/A String key = se.getKey().toString();
0N/A
0N/A if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) {
0N/A String algorithm =
0N/A key.substring(0, key.length() - ATTR_DIGEST.length());
0N/A
0N/A MessageDigest digest = getDigest(algorithm);
0N/A if (digest != null) {
0N/A ManifestDigester.Entry mde =
0N/A md.get(ManifestDigester.MF_MAIN_ATTRS, false);
0N/A byte[] computedHash = mde.digest(digest);
0N/A byte[] expectedHash =
0N/A decoder.decodeBuffer((String)se.getValue());
0N/A
0N/A if (debug != null) {
0N/A debug.println("Signature File: " +
0N/A "Manifest Main Attributes digest " +
0N/A digest.getAlgorithm());
0N/A debug.println( " sigfile " + toHex(expectedHash));
0N/A debug.println( " computed " + toHex(computedHash));
0N/A debug.println();
0N/A }
0N/A
0N/A if (MessageDigest.isEqual(computedHash,
0N/A expectedHash)) {
0N/A // good
0N/A } else {
0N/A // we will *not* continue and verify each section
0N/A attrsVerified = false;
0N/A if (debug != null) {
0N/A debug.println("Verification of " +
0N/A "Manifest main attributes failed");
0N/A debug.println();
0N/A }
0N/A break;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A // this method returns 'true' if either:
0N/A // . manifest main attributes were not signed, or
0N/A // . manifest main attributes were signed and verified
0N/A return attrsVerified;
0N/A }
0N/A
0N/A /**
0N/A * given the .SF digest header, and the data from the
0N/A * section in the manifest, see if the hashes match.
0N/A * if not, throw a SecurityException.
0N/A *
0N/A * @return true if all the -Digest headers verified
0N/A * @exception SecurityException if the hash was not equal
0N/A */
0N/A
0N/A private boolean verifySection(Attributes sfAttr,
0N/A String name,
0N/A ManifestDigester md,
0N/A BASE64Decoder decoder)
0N/A throws IOException
0N/A {
0N/A boolean oneDigestVerified = false;
0N/A ManifestDigester.Entry mde = md.get(name,block.isOldStyle());
0N/A
0N/A if (mde == null) {
0N/A throw new SecurityException(
0N/A "no manifiest section for signature file entry "+name);
0N/A }
0N/A
0N/A if (sfAttr != null) {
0N/A
0N/A //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder();
0N/A //hex.encodeBuffer(data, System.out);
0N/A
0N/A // go through all the attributes and process *-Digest entries
0N/A for (Map.Entry<Object,Object> se : sfAttr.entrySet()) {
0N/A String key = se.getKey().toString();
0N/A
0N/A if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
0N/A // 7 is length of "-Digest"
0N/A String algorithm = key.substring(0, key.length()-7);
0N/A
0N/A MessageDigest digest = getDigest(algorithm);
0N/A
0N/A if (digest != null) {
0N/A boolean ok = false;
0N/A
0N/A byte[] expected =
0N/A decoder.decodeBuffer((String)se.getValue());
0N/A byte[] computed;
0N/A if (workaround) {
0N/A computed = mde.digestWorkaround(digest);
0N/A } else {
0N/A computed = mde.digest(digest);
0N/A }
0N/A
0N/A if (debug != null) {
0N/A debug.println("Signature Block File: " +
0N/A name + " digest=" + digest.getAlgorithm());
0N/A debug.println(" expected " + toHex(expected));
0N/A debug.println(" computed " + toHex(computed));
0N/A debug.println();
0N/A }
0N/A
0N/A if (MessageDigest.isEqual(computed, expected)) {
0N/A oneDigestVerified = true;
0N/A ok = true;
0N/A } else {
0N/A // attempt to fallback to the workaround
0N/A if (!workaround) {
0N/A computed = mde.digestWorkaround(digest);
0N/A if (MessageDigest.isEqual(computed, expected)) {
0N/A if (debug != null) {
0N/A debug.println(" re-computed " + toHex(computed));
0N/A debug.println();
0N/A }
0N/A workaround = true;
0N/A oneDigestVerified = true;
0N/A ok = true;
0N/A }
0N/A }
0N/A }
0N/A if (!ok){
0N/A throw new SecurityException("invalid " +
0N/A digest.getAlgorithm() +
0N/A " signature file digest for " + name);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return oneDigestVerified;
0N/A }
0N/A
0N/A /**
0N/A * Given the PKCS7 block and SignerInfo[], create an array of
0N/A * CodeSigner objects. We do this only *once* for a given
0N/A * signature block file.
0N/A */
0N/A private CodeSigner[] getSigners(SignerInfo infos[], PKCS7 block)
0N/A throws IOException, NoSuchAlgorithmException, SignatureException,
0N/A CertificateException {
0N/A
0N/A ArrayList<CodeSigner> signers = null;
0N/A
0N/A for (int i = 0; i < infos.length; i++) {
0N/A
0N/A SignerInfo info = infos[i];
0N/A ArrayList<X509Certificate> chain = info.getCertificateChain(block);
0N/A CertPath certChain = certificateFactory.generateCertPath(chain);
0N/A if (signers == null) {
0N/A signers = new ArrayList<CodeSigner>();
0N/A }
0N/A // Append the new code signer
3659N/A signers.add(new CodeSigner(certChain, getTimestamp(info)));
0N/A
0N/A if (debug != null) {
0N/A debug.println("Signature Block Certificate: " +
0N/A chain.get(0));
0N/A }
0N/A }
0N/A
0N/A if (signers != null) {
0N/A return signers.toArray(new CodeSigner[signers.size()]);
0N/A } else {
0N/A return null;
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * Examines a signature timestamp token to generate a timestamp object.
0N/A *
0N/A * Examines the signer's unsigned attributes for a
0N/A * <tt>signatureTimestampToken</tt> attribute. If present,
0N/A * then it is parsed to extract the date and time at which the
0N/A * timestamp was generated.
0N/A *
0N/A * @param info A signer information element of a PKCS 7 block.
0N/A *
0N/A * @return A timestamp token or null if none is present.
0N/A * @throws IOException if an error is encountered while parsing the
0N/A * PKCS7 data.
0N/A * @throws NoSuchAlgorithmException if an error is encountered while
0N/A * verifying the PKCS7 object.
0N/A * @throws SignatureException if an error is encountered while
0N/A * verifying the PKCS7 object.
0N/A * @throws CertificateException if an error is encountered while generating
0N/A * the TSA's certpath.
0N/A */
0N/A private Timestamp getTimestamp(SignerInfo info)
0N/A throws IOException, NoSuchAlgorithmException, SignatureException,
0N/A CertificateException {
0N/A
0N/A Timestamp timestamp = null;
0N/A
0N/A // Extract the signer's unsigned attributes
0N/A PKCS9Attributes unsignedAttrs = info.getUnauthenticatedAttributes();
0N/A if (unsignedAttrs != null) {
0N/A PKCS9Attribute timestampTokenAttr =
0N/A unsignedAttrs.getAttribute("signatureTimestampToken");
0N/A if (timestampTokenAttr != null) {
0N/A PKCS7 timestampToken =
0N/A new PKCS7((byte[])timestampTokenAttr.getValue());
0N/A // Extract the content (an encoded timestamp token info)
0N/A byte[] encodedTimestampTokenInfo =
0N/A timestampToken.getContentInfo().getData();
0N/A // Extract the signer (the Timestamping Authority)
0N/A // while verifying the content
0N/A SignerInfo[] tsa =
0N/A timestampToken.verify(encodedTimestampTokenInfo);
0N/A // Expect only one signer
0N/A ArrayList<X509Certificate> chain =
0N/A tsa[0].getCertificateChain(timestampToken);
0N/A CertPath tsaChain = certificateFactory.generateCertPath(chain);
0N/A // Create a timestamp token info object
0N/A TimestampToken timestampTokenInfo =
0N/A new TimestampToken(encodedTimestampTokenInfo);
6336N/A // Check that the signature timestamp applies to this signature
6336N/A verifyTimestamp(timestampTokenInfo, info.getEncryptedDigest());
0N/A // Create a timestamp object
0N/A timestamp =
0N/A new Timestamp(timestampTokenInfo.getDate(), tsaChain);
0N/A }
0N/A }
0N/A return timestamp;
0N/A }
0N/A
6336N/A /*
6336N/A * Check that the signature timestamp applies to this signature.
6336N/A * Match the hash present in the signature timestamp token against the hash
6336N/A * of this signature.
6336N/A */
6336N/A private void verifyTimestamp(TimestampToken token, byte[] signature)
6336N/A throws NoSuchAlgorithmException, SignatureException {
6336N/A
6336N/A MessageDigest md =
6336N/A MessageDigest.getInstance(token.getHashAlgorithm().getName());
6336N/A
6336N/A if (!Arrays.equals(token.getHashedMessage(), md.digest(signature))) {
6336N/A throw new SignatureException("Signature timestamp (#" +
6336N/A token.getSerialNumber() + ") generated on " + token.getDate() +
6336N/A " is inapplicable");
6336N/A }
6336N/A
6336N/A if (debug != null) {
6336N/A debug.println();
6336N/A debug.println("Detected signature timestamp (#" +
6336N/A token.getSerialNumber() + ") generated on " + token.getDate());
6336N/A debug.println();
6336N/A }
6336N/A }
6336N/A
0N/A // for the toHex function
0N/A private static final char[] hexc =
0N/A {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
0N/A /**
0N/A * convert a byte array to a hex string for debugging purposes
0N/A * @param data the binary data to be converted to a hex string
0N/A * @return an ASCII hex string
0N/A */
0N/A
0N/A static String toHex(byte[] data) {
0N/A
0N/A StringBuffer sb = new StringBuffer(data.length*2);
0N/A
0N/A for (int i=0; i<data.length; i++) {
0N/A sb.append(hexc[(data[i] >>4) & 0x0f]);
0N/A sb.append(hexc[data[i] & 0x0f]);
0N/A }
0N/A return sb.toString();
0N/A }
0N/A
0N/A // returns true if set contains signer
0N/A static boolean contains(CodeSigner[] set, CodeSigner signer)
0N/A {
0N/A for (int i = 0; i < set.length; i++) {
0N/A if (set[i].equals(signer))
0N/A return true;
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A // returns true if subset is a subset of set
0N/A static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set)
0N/A {
0N/A // check for the same object
0N/A if (set == subset)
0N/A return true;
0N/A
4046N/A boolean match;
0N/A for (int i = 0; i < subset.length; i++) {
0N/A if (!contains(set, subset[i]))
0N/A return false;
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A /**
0N/A * returns true if signer contains exactly the same code signers as
0N/A * oldSigner and newSigner, false otherwise. oldSigner
0N/A * is allowed to be null.
0N/A */
0N/A static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners,
0N/A CodeSigner[] newSigners) {
0N/A
0N/A // special case
0N/A if ((oldSigners == null) && (signers == newSigners))
0N/A return true;
0N/A
4046N/A boolean match;
4046N/A
0N/A // make sure all oldSigners are in signers
0N/A if ((oldSigners != null) && !isSubSet(oldSigners, signers))
0N/A return false;
0N/A
0N/A // make sure all newSigners are in signers
0N/A if (!isSubSet(newSigners, signers)) {
0N/A return false;
0N/A }
0N/A
0N/A // now make sure all the code signers in signers are
0N/A // also in oldSigners or newSigners
0N/A
0N/A for (int i = 0; i < signers.length; i++) {
0N/A boolean found =
0N/A ((oldSigners != null) && contains(oldSigners, signers[i])) ||
0N/A contains(newSigners, signers[i]);
0N/A if (!found)
0N/A return false;
0N/A }
0N/A return true;
0N/A }
0N/A
0N/A void updateSigners(CodeSigner[] newSigners,
4046N/A Hashtable<String, CodeSigner[]> signers, String name) {
0N/A
0N/A CodeSigner[] oldSigners = signers.get(name);
0N/A
0N/A // search through the cache for a match, go in reverse order
0N/A // as we are more likely to find a match with the last one
0N/A // added to the cache
0N/A
0N/A CodeSigner[] cachedSigners;
0N/A for (int i = signerCache.size() - 1; i != -1; i--) {
0N/A cachedSigners = signerCache.get(i);
0N/A if (matches(cachedSigners, oldSigners, newSigners)) {
0N/A signers.put(name, cachedSigners);
0N/A return;
0N/A }
0N/A }
0N/A
0N/A if (oldSigners == null) {
0N/A cachedSigners = newSigners;
0N/A } else {
0N/A cachedSigners =
0N/A new CodeSigner[oldSigners.length + newSigners.length];
0N/A System.arraycopy(oldSigners, 0, cachedSigners, 0,
0N/A oldSigners.length);
0N/A System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length,
0N/A newSigners.length);
0N/A }
0N/A signerCache.add(cachedSigners);
0N/A signers.put(name, cachedSigners);
0N/A }
0N/A}