Path: blob/master/src/java.base/share/classes/sun/security/util/ManifestEntryVerifier.java
41159 views
/*1* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.util;2627import java.security.*;28import java.io.*;29import java.util.*;30import java.util.jar.*;3132import sun.security.jca.Providers;33import sun.security.util.DisabledAlgorithmConstraints;34import sun.security.util.JarConstraintsParameters;3536/**37* This class is used to verify each entry in a jar file with its38* manifest value.39*/4041public class ManifestEntryVerifier {4243private static final Debug debug = Debug.getInstance("jar");4445/**46* Holder class to lazily load Sun provider. NOTE: if47* Providers.getSunProvider returned a cached provider, we could avoid the48* need for caching the provider with this holder class; we should try to49* revisit this in JDK 8.50*/51private static class SunProviderHolder {52private static final Provider instance = Providers.getSunProvider();53}5455/** the created digest objects */56HashMap<String, MessageDigest> createdDigests;5758/** the digests in use for a given entry*/59ArrayList<MessageDigest> digests;6061/** the manifest hashes for the digests in use */62ArrayList<byte[]> manifestHashes;6364private String name = null;65private Manifest man;6667private boolean skip = true;6869private JarEntry entry;7071private CodeSigner[] signers = null;7273/**74* Create a new ManifestEntryVerifier object.75*/76public ManifestEntryVerifier(Manifest man)77{78createdDigests = new HashMap<>(11);79digests = new ArrayList<>();80manifestHashes = new ArrayList<>();81this.man = man;82}8384/**85* Find the hashes in the86* manifest for this entry, save them, and set the MessageDigest87* objects to calculate the hashes on the fly. If name is88* null it signifies that update/verify should ignore this entry.89*/90public void setEntry(String name, JarEntry entry)91throws IOException92{93digests.clear();94manifestHashes.clear();95this.name = name;96this.entry = entry;9798skip = true;99signers = null;100101if (man == null || name == null) {102return;103}104105/* get the headers from the manifest for this entry */106/* if there aren't any, we can't verify any digests for this entry */107108skip = false;109110Attributes attr = man.getAttributes(name);111if (attr == null) {112// ugh. we should be able to remove this at some point.113// there are broken jars floating around with ./name and /name114// in the manifest, and "name" in the zip/jar file.115attr = man.getAttributes("./"+name);116if (attr == null) {117attr = man.getAttributes("/"+name);118if (attr == null)119return;120}121}122123for (Map.Entry<Object,Object> se : attr.entrySet()) {124String key = se.getKey().toString();125126if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {127// 7 is length of "-Digest"128String algorithm = key.substring(0, key.length()-7);129130MessageDigest digest = createdDigests.get(algorithm);131132if (digest == null) {133try {134135digest = MessageDigest.getInstance136(algorithm, SunProviderHolder.instance);137createdDigests.put(algorithm, digest);138} catch (NoSuchAlgorithmException nsae) {139// ignore140}141}142143if (digest != null) {144digest.reset();145digests.add(digest);146manifestHashes.add(147Base64.getMimeDecoder().decode((String)se.getValue()));148}149}150}151}152153/**154* update the digests for the digests we are interested in155*/156public void update(byte buffer) {157if (skip) return;158159for (int i=0; i < digests.size(); i++) {160digests.get(i).update(buffer);161}162}163164/**165* update the digests for the digests we are interested in166*/167public void update(byte[] buffer, int off, int len) {168if (skip) return;169170for (int i=0; i < digests.size(); i++) {171digests.get(i).update(buffer, off, len);172}173}174175/**176* get the JarEntry for this object177*/178public JarEntry getEntry()179{180return entry;181}182183/**184* go through all the digests, calculating the final digest185* and comparing it to the one in the manifest. If this is186* the first time we have verified this object, remove its187* code signers from sigFileSigners and place in verifiedSigners.188*189*190*/191public CodeSigner[] verify(Hashtable<String, CodeSigner[]> verifiedSigners,192Hashtable<String, CodeSigner[]> sigFileSigners)193throws JarException194{195if (skip) {196return null;197}198199if (digests.isEmpty()) {200throw new SecurityException("digest missing for " + name);201}202203if (signers != null) {204return signers;205}206207JarConstraintsParameters params =208getParams(verifiedSigners, sigFileSigners);209210for (int i=0; i < digests.size(); i++) {211212MessageDigest digest = digests.get(i);213if (params != null) {214try {215params.setExtendedExceptionMsg(JarFile.MANIFEST_NAME,216name + " entry");217DisabledAlgorithmConstraints.jarConstraints()218.permits(digest.getAlgorithm(), params);219} catch (GeneralSecurityException e) {220if (debug != null) {221debug.println("Digest algorithm is restricted: " + e);222}223return null;224}225}226byte [] manHash = manifestHashes.get(i);227byte [] theHash = digest.digest();228229if (debug != null) {230debug.println("Manifest Entry: " +231name + " digest=" + digest.getAlgorithm());232debug.println(" manifest " + HexFormat.of().formatHex(manHash));233debug.println(" computed " + HexFormat.of().formatHex(theHash));234debug.println();235}236237if (!MessageDigest.isEqual(theHash, manHash))238throw new SecurityException(digest.getAlgorithm()+239" digest error for "+name);240}241242// take it out of sigFileSigners and put it in verifiedSigners...243signers = sigFileSigners.remove(name);244if (signers != null) {245verifiedSigners.put(name, signers);246}247return signers;248}249250/**251* Get constraints parameters for JAR. The constraints should be252* checked against all code signers. Returns the parameters,253* or null if the signers for this entry have already been checked.254*/255private JarConstraintsParameters getParams(256Map<String, CodeSigner[]> verifiedSigners,257Map<String, CodeSigner[]> sigFileSigners) {258259// verifiedSigners is usually preloaded with the Manifest's signers.260// If verifiedSigners contains the Manifest, then it will have all of261// the signers of the JAR. But if it doesn't then we need to fallback262// and check verifiedSigners to see if the signers of this entry have263// been checked already.264if (verifiedSigners.containsKey(JarFile.MANIFEST_NAME)) {265if (verifiedSigners.size() > 1) {266// this means we already checked it previously267return null;268} else {269return new JarConstraintsParameters(270verifiedSigners.get(JarFile.MANIFEST_NAME));271}272} else {273CodeSigner[] signers = sigFileSigners.get(name);274if (verifiedSigners.containsValue(signers)) {275return null;276} else {277return new JarConstraintsParameters(signers);278}279}280}281}282283284285