mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
305 lines
10 KiB
Java
305 lines
10 KiB
Java
/*
|
|
* Copyright (c) 1997, 2022, 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.util;
|
|
|
|
import java.io.IOException;
|
|
import java.security.*;
|
|
import java.util.*;
|
|
import java.util.jar.*;
|
|
|
|
import sun.security.jca.Providers;
|
|
|
|
/**
|
|
* This class is used to verify each entry in a jar file with its
|
|
* manifest value.
|
|
*/
|
|
|
|
public class ManifestEntryVerifier {
|
|
|
|
private static final Debug debug = Debug.getInstance("jar");
|
|
|
|
/**
|
|
* Holder class to lazily load Sun provider. NOTE: if
|
|
* Providers.getSunProvider returned a cached provider, we could avoid the
|
|
* need for caching the provider with this holder class; we should try to
|
|
* revisit this in JDK 8.
|
|
*/
|
|
private static class SunProviderHolder {
|
|
private static final Provider instance = Providers.getSunProvider();
|
|
}
|
|
|
|
/** the created digest objects */
|
|
HashMap<String, MessageDigest> createdDigests;
|
|
|
|
/** the digests in use for a given entry*/
|
|
ArrayList<MessageDigest> digests;
|
|
|
|
/** the manifest hashes for the digests in use */
|
|
ArrayList<byte[]> manifestHashes;
|
|
|
|
private String name = null;
|
|
|
|
private final String manifestFileName; // never null
|
|
private final Manifest man;
|
|
|
|
private boolean skip = true;
|
|
|
|
private JarEntry entry;
|
|
|
|
private CodeSigner[] signers = null;
|
|
|
|
/**
|
|
* Create a new ManifestEntryVerifier object.
|
|
*/
|
|
public ManifestEntryVerifier(Manifest man, String manifestFileName)
|
|
{
|
|
createdDigests = new HashMap<>(11);
|
|
digests = new ArrayList<>();
|
|
manifestHashes = new ArrayList<>();
|
|
this.manifestFileName = manifestFileName;
|
|
this.man = man;
|
|
}
|
|
|
|
/**
|
|
* Find the hashes in the
|
|
* manifest for this entry, save them, and set the MessageDigest
|
|
* objects to calculate the hashes on the fly. If name is
|
|
* null it signifies that update/verify should ignore this entry.
|
|
*/
|
|
public void setEntry(String name, JarEntry entry)
|
|
throws IOException
|
|
{
|
|
digests.clear();
|
|
manifestHashes.clear();
|
|
this.name = name;
|
|
this.entry = entry;
|
|
|
|
skip = true;
|
|
signers = null;
|
|
|
|
if (man == null || name == null) {
|
|
return;
|
|
}
|
|
|
|
/* get the headers from the manifest for this entry */
|
|
/* if there aren't any, we can't verify any digests for this entry */
|
|
|
|
skip = false;
|
|
|
|
Attributes attr = man.getAttributes(name);
|
|
if (attr == null) {
|
|
// ugh. we should be able to remove this at some point.
|
|
// there are broken jars floating around with ./name and /name
|
|
// in the manifest, and "name" in the zip/jar file.
|
|
attr = man.getAttributes("./"+name);
|
|
if (attr == null) {
|
|
attr = man.getAttributes("/"+name);
|
|
if (attr == null)
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (Map.Entry<Object,Object> se : attr.entrySet()) {
|
|
String key = se.getKey().toString();
|
|
|
|
if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
|
|
// 7 is length of "-Digest"
|
|
String algorithm = key.substring(0, key.length()-7);
|
|
|
|
MessageDigest digest = createdDigests.get(algorithm);
|
|
|
|
if (digest == null) {
|
|
try {
|
|
|
|
digest = MessageDigest.getInstance
|
|
(algorithm, SunProviderHolder.instance);
|
|
createdDigests.put(algorithm, digest);
|
|
} catch (NoSuchAlgorithmException nsae) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
if (digest != null) {
|
|
digest.reset();
|
|
digests.add(digest);
|
|
manifestHashes.add(
|
|
Base64.getMimeDecoder().decode((String)se.getValue()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* update the digests for the digests we are interested in
|
|
*/
|
|
public void update(byte buffer) {
|
|
if (skip) return;
|
|
|
|
for (int i=0; i < digests.size(); i++) {
|
|
digests.get(i).update(buffer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* update the digests for the digests we are interested in
|
|
*/
|
|
public void update(byte[] buffer, int off, int len) {
|
|
if (skip) return;
|
|
|
|
for (int i=0; i < digests.size(); i++) {
|
|
digests.get(i).update(buffer, off, len);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the JarEntry for this object
|
|
*/
|
|
public JarEntry getEntry()
|
|
{
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* go through all the digests, calculating the final digest
|
|
* and comparing it to the one in the manifest. If this is
|
|
* the first time we have verified this object, remove its
|
|
* code signers from sigFileSigners and place in verifiedSigners.
|
|
*
|
|
*/
|
|
public CodeSigner[] verify(Hashtable<String, CodeSigner[]> verifiedSigners,
|
|
Hashtable<String, CodeSigner[]> sigFileSigners,
|
|
Map<CodeSigner[], Map<String, Boolean>> signersToAlgs)
|
|
throws JarException
|
|
{
|
|
if (skip) {
|
|
return null;
|
|
}
|
|
|
|
if (digests.isEmpty()) {
|
|
throw new SecurityException("digest missing for " + name);
|
|
}
|
|
|
|
if (signers != null) {
|
|
return signers;
|
|
}
|
|
|
|
CodeSigner[] entrySigners = sigFileSigners.get(name);
|
|
Map<String, Boolean> algsPermittedStatus =
|
|
algsPermittedStatusForSigners(signersToAlgs, entrySigners);
|
|
|
|
// Flag that indicates if only disabled algorithms are used and jar
|
|
// entry should be treated as unsigned.
|
|
boolean disabledAlgs = true;
|
|
JarConstraintsParameters params = null;
|
|
for (int i=0; i < digests.size(); i++) {
|
|
MessageDigest digest = digests.get(i);
|
|
String digestAlg = digest.getAlgorithm();
|
|
|
|
// Check if this algorithm is permitted, skip if false.
|
|
if (algsPermittedStatus != null) {
|
|
Boolean permitted = algsPermittedStatus.get(digestAlg);
|
|
if (permitted == null) {
|
|
if (params == null) {
|
|
params = new JarConstraintsParameters(entrySigners);
|
|
}
|
|
if (!checkConstraints(digestAlg, params)) {
|
|
algsPermittedStatus.put(digestAlg, Boolean.FALSE);
|
|
continue;
|
|
} else {
|
|
algsPermittedStatus.put(digestAlg, Boolean.TRUE);
|
|
}
|
|
} else if (!permitted) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// A non-disabled algorithm was used.
|
|
disabledAlgs = false;
|
|
|
|
byte [] manHash = manifestHashes.get(i);
|
|
byte [] theHash = digest.digest();
|
|
|
|
if (debug != null) {
|
|
debug.println("Manifest Entry: " +
|
|
name + " digest=" + digestAlg);
|
|
debug.println(" manifest " + HexFormat.of().formatHex(manHash));
|
|
debug.println(" computed " + HexFormat.of().formatHex(theHash));
|
|
debug.println();
|
|
}
|
|
|
|
if (!MessageDigest.isEqual(theHash, manHash)) {
|
|
throw new SecurityException(digestAlg +
|
|
" digest error for "+name);
|
|
}
|
|
}
|
|
|
|
// If there were only disabled algorithms used, return null and jar
|
|
// entry will be treated as unsigned.
|
|
if (disabledAlgs) {
|
|
return null;
|
|
}
|
|
|
|
// take it out of sigFileSigners and put it in verifiedSigners...
|
|
signers = sigFileSigners.remove(name);
|
|
if (signers != null) {
|
|
verifiedSigners.put(name, signers);
|
|
}
|
|
return signers;
|
|
}
|
|
|
|
// Gets the algorithms permitted status for the signers of this entry.
|
|
private static Map<String, Boolean> algsPermittedStatusForSigners(
|
|
Map<CodeSigner[], Map<String, Boolean>> signersToAlgs,
|
|
CodeSigner[] signers) {
|
|
if (signers != null) {
|
|
Map<String, Boolean> algs = signersToAlgs.get(signers);
|
|
// create new HashMap if absent
|
|
if (algs == null) {
|
|
algs = new HashMap<>();
|
|
signersToAlgs.put(signers, algs);
|
|
}
|
|
return algs;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Checks the algorithm constraints against the signers of this entry.
|
|
private boolean checkConstraints(String algorithm,
|
|
JarConstraintsParameters params) {
|
|
try {
|
|
params.setExtendedExceptionMsg(JarFile.MANIFEST_NAME,
|
|
name + " entry");
|
|
DisabledAlgorithmConstraints.jarConstraints()
|
|
.permits(algorithm, params, false);
|
|
return true;
|
|
} catch (GeneralSecurityException e) {
|
|
if (debug != null) {
|
|
debug.println("Digest algorithm is restricted: " + e);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|