930 lines
31 KiB
Java
930 lines
31 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2014 The Android Open Source Project
|
||
|
* Copyright (c) 1997, 2013, 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 java.util.jar;
|
||
|
|
||
|
import java.io.*;
|
||
|
import java.net.URL;
|
||
|
import java.util.*;
|
||
|
import java.security.*;
|
||
|
import java.security.cert.CertificateException;
|
||
|
import java.util.zip.ZipEntry;
|
||
|
|
||
|
import jdk.internal.util.jar.JarIndex;
|
||
|
import sun.security.util.ManifestDigester;
|
||
|
import sun.security.util.ManifestEntryVerifier;
|
||
|
import sun.security.util.SignatureFileVerifier;
|
||
|
import sun.security.util.Debug;
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @author Roland Schemers
|
||
|
*/
|
||
|
class JarVerifier {
|
||
|
|
||
|
/* Are we debugging ? */
|
||
|
static final Debug debug = Debug.getInstance("jar");
|
||
|
|
||
|
/* a table mapping names to code signers, for jar entries that have
|
||
|
had their actual hashes verified */
|
||
|
private Hashtable<String, CodeSigner[]> verifiedSigners;
|
||
|
|
||
|
/* a table mapping names to code signers, for jar entries that have
|
||
|
passed the .SF/.DSA/.EC -> MANIFEST check */
|
||
|
private Hashtable<String, CodeSigner[]> sigFileSigners;
|
||
|
|
||
|
/* a hash table to hold .SF bytes */
|
||
|
private Hashtable<String, byte[]> sigFileData;
|
||
|
|
||
|
/** "queue" of pending PKCS7 blocks that we couldn't parse
|
||
|
* until we parsed the .SF file */
|
||
|
private ArrayList<SignatureFileVerifier> pendingBlocks;
|
||
|
|
||
|
/* cache of CodeSigner objects */
|
||
|
private ArrayList<CodeSigner[]> signerCache;
|
||
|
|
||
|
/* Are we parsing a block? */
|
||
|
private boolean parsingBlockOrSF = false;
|
||
|
|
||
|
/* Are we done parsing META-INF entries? */
|
||
|
private boolean parsingMeta = true;
|
||
|
|
||
|
/* Are there are files to verify? */
|
||
|
private boolean anyToVerify = true;
|
||
|
|
||
|
/* The output stream to use when keeping track of files we are interested
|
||
|
in */
|
||
|
private ByteArrayOutputStream baos;
|
||
|
|
||
|
/** The ManifestDigester object */
|
||
|
private volatile ManifestDigester manDig;
|
||
|
|
||
|
/** the bytes for the manDig object */
|
||
|
byte manifestRawBytes[] = null;
|
||
|
|
||
|
/** the manifest name this JarVerifier is created upon */
|
||
|
final String manifestName;
|
||
|
|
||
|
/** controls eager signature validation */
|
||
|
boolean eagerValidation;
|
||
|
|
||
|
/** makes code source singleton instances unique to us */
|
||
|
private Object csdomain = new Object();
|
||
|
|
||
|
/** collect -DIGEST-MANIFEST values for blacklist */
|
||
|
private List<Object> manifestDigests;
|
||
|
|
||
|
public JarVerifier(String name, byte rawBytes[]) {
|
||
|
manifestName = name;
|
||
|
manifestRawBytes = rawBytes;
|
||
|
sigFileSigners = new Hashtable<>();
|
||
|
verifiedSigners = new Hashtable<>();
|
||
|
sigFileData = new Hashtable<>(11);
|
||
|
pendingBlocks = new ArrayList<>();
|
||
|
baos = new ByteArrayOutputStream();
|
||
|
manifestDigests = new ArrayList<>();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method scans to see which entry we're parsing and
|
||
|
* keeps various state information depending on what type of
|
||
|
* file is being parsed.
|
||
|
*/
|
||
|
public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
|
||
|
throws IOException
|
||
|
{
|
||
|
if (je == null)
|
||
|
return;
|
||
|
|
||
|
if (debug != null) {
|
||
|
debug.println("beginEntry "+je.getName());
|
||
|
}
|
||
|
|
||
|
String name = je.getName();
|
||
|
|
||
|
/*
|
||
|
* Assumptions:
|
||
|
* 1. The manifest should be the first entry in the META-INF directory.
|
||
|
* 2. The .SF/.DSA/.EC files follow the manifest, before any normal entries
|
||
|
* 3. Any of the following will throw a SecurityException:
|
||
|
* a. digest mismatch between a manifest section and
|
||
|
* the SF section.
|
||
|
* b. digest mismatch between the actual jar entry and the manifest
|
||
|
*/
|
||
|
|
||
|
if (parsingMeta) {
|
||
|
String uname = name.toUpperCase(Locale.ENGLISH);
|
||
|
if ((uname.startsWith("META-INF/") ||
|
||
|
uname.startsWith("/META-INF/"))) {
|
||
|
|
||
|
if (je.isDirectory()) {
|
||
|
mev.setEntry(null, je);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (uname.equals(JarFile.MANIFEST_NAME) ||
|
||
|
uname.equals(JarIndex.INDEX_NAME)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (SignatureFileVerifier.isBlockOrSF(uname)) {
|
||
|
/* We parse only DSA, RSA or EC PKCS7 blocks. */
|
||
|
parsingBlockOrSF = true;
|
||
|
baos.reset();
|
||
|
mev.setEntry(null, je);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If a META-INF entry is not MF or block or SF, they should
|
||
|
// be normal entries. According to 2 above, no more block or
|
||
|
// SF will appear. Let's doneWithMeta.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (parsingMeta) {
|
||
|
doneWithMeta();
|
||
|
}
|
||
|
|
||
|
if (je.isDirectory()) {
|
||
|
mev.setEntry(null, je);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// be liberal in what you accept. If the name starts with ./, remove
|
||
|
// it as we internally canonicalize it with out the ./.
|
||
|
if (name.startsWith("./"))
|
||
|
name = name.substring(2);
|
||
|
|
||
|
// be liberal in what you accept. If the name starts with /, remove
|
||
|
// it as we internally canonicalize it with out the /.
|
||
|
if (name.startsWith("/"))
|
||
|
name = name.substring(1);
|
||
|
|
||
|
// only set the jev object for entries that have a signature
|
||
|
// (either verified or not)
|
||
|
if (sigFileSigners.get(name) != null ||
|
||
|
verifiedSigners.get(name) != null) {
|
||
|
mev.setEntry(name, je);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// don't compute the digest for this entry
|
||
|
mev.setEntry(null, je);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* update a single byte.
|
||
|
*/
|
||
|
|
||
|
public void update(int b, ManifestEntryVerifier mev)
|
||
|
throws IOException
|
||
|
{
|
||
|
if (b != -1) {
|
||
|
if (parsingBlockOrSF) {
|
||
|
baos.write(b);
|
||
|
} else {
|
||
|
mev.update((byte)b);
|
||
|
}
|
||
|
} else {
|
||
|
processEntry(mev);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* update an array of bytes.
|
||
|
*/
|
||
|
|
||
|
public void update(int n, byte[] b, int off, int len,
|
||
|
ManifestEntryVerifier mev)
|
||
|
throws IOException
|
||
|
{
|
||
|
if (n != -1) {
|
||
|
if (parsingBlockOrSF) {
|
||
|
baos.write(b, off, n);
|
||
|
} else {
|
||
|
mev.update(b, off, n);
|
||
|
}
|
||
|
} else {
|
||
|
processEntry(mev);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* called when we reach the end of entry in one of the read() methods.
|
||
|
*/
|
||
|
private void processEntry(ManifestEntryVerifier mev)
|
||
|
throws IOException
|
||
|
{
|
||
|
if (!parsingBlockOrSF) {
|
||
|
JarEntry je = mev.getEntry();
|
||
|
if ((je != null) && (je.signers == null)) {
|
||
|
je.signers = mev.verify(verifiedSigners, sigFileSigners);
|
||
|
je.certs = mapSignersToCertArray(je.signers);
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
try {
|
||
|
parsingBlockOrSF = false;
|
||
|
|
||
|
if (debug != null) {
|
||
|
debug.println("processEntry: processing block");
|
||
|
}
|
||
|
|
||
|
String uname = mev.getEntry().getName()
|
||
|
.toUpperCase(Locale.ENGLISH);
|
||
|
|
||
|
if (uname.endsWith(".SF")) {
|
||
|
String key = uname.substring(0, uname.length()-3);
|
||
|
byte bytes[] = baos.toByteArray();
|
||
|
// add to sigFileData in case future blocks need it
|
||
|
sigFileData.put(key, bytes);
|
||
|
// check pending blocks, we can now process
|
||
|
// anyone waiting for this .SF file
|
||
|
Iterator<SignatureFileVerifier> it = pendingBlocks.iterator();
|
||
|
while (it.hasNext()) {
|
||
|
SignatureFileVerifier sfv = it.next();
|
||
|
if (sfv.needSignatureFile(key)) {
|
||
|
if (debug != null) {
|
||
|
debug.println(
|
||
|
"processEntry: processing pending block");
|
||
|
}
|
||
|
|
||
|
sfv.setSignatureFile(bytes);
|
||
|
sfv.process(sigFileSigners, manifestDigests);
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// now we are parsing a signature block file
|
||
|
|
||
|
String key = uname.substring(0, uname.lastIndexOf("."));
|
||
|
|
||
|
if (signerCache == null)
|
||
|
signerCache = new ArrayList<>();
|
||
|
|
||
|
if (manDig == null) {
|
||
|
synchronized(manifestRawBytes) {
|
||
|
if (manDig == null) {
|
||
|
manDig = new ManifestDigester(manifestRawBytes);
|
||
|
manifestRawBytes = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SignatureFileVerifier sfv =
|
||
|
new SignatureFileVerifier(signerCache,
|
||
|
manDig, uname, baos.toByteArray());
|
||
|
|
||
|
if (sfv.needSignatureFileBytes()) {
|
||
|
// see if we have already parsed an external .SF file
|
||
|
byte[] bytes = sigFileData.get(key);
|
||
|
|
||
|
if (bytes == null) {
|
||
|
// put this block on queue for later processing
|
||
|
// since we don't have the .SF bytes yet
|
||
|
// (uname, block);
|
||
|
if (debug != null) {
|
||
|
debug.println("adding pending block");
|
||
|
}
|
||
|
pendingBlocks.add(sfv);
|
||
|
return;
|
||
|
} else {
|
||
|
sfv.setSignatureFile(bytes);
|
||
|
}
|
||
|
}
|
||
|
sfv.process(sigFileSigners, manifestDigests);
|
||
|
|
||
|
} catch (IOException ioe) {
|
||
|
// e.g. sun.security.pkcs.ParsingException
|
||
|
if (debug != null) debug.println("processEntry caught: "+ioe);
|
||
|
// ignore and treat as unsigned
|
||
|
} catch (SignatureException se) {
|
||
|
if (debug != null) debug.println("processEntry caught: "+se);
|
||
|
// ignore and treat as unsigned
|
||
|
} catch (NoSuchAlgorithmException nsae) {
|
||
|
if (debug != null) debug.println("processEntry caught: "+nsae);
|
||
|
// ignore and treat as unsigned
|
||
|
} catch (CertificateException ce) {
|
||
|
if (debug != null) debug.println("processEntry caught: "+ce);
|
||
|
// ignore and treat as unsigned
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Android-changed: @deprecated tag needs a description. http://b/110781661
|
||
|
/**
|
||
|
* Return an array of java.security.cert.Certificate objects for
|
||
|
* the given file in the jar.
|
||
|
* @deprecated Deprecated.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public java.security.cert.Certificate[] getCerts(String name)
|
||
|
{
|
||
|
return mapSignersToCertArray(getCodeSigners(name));
|
||
|
}
|
||
|
|
||
|
public java.security.cert.Certificate[] getCerts(JarFile jar, JarEntry entry)
|
||
|
{
|
||
|
return mapSignersToCertArray(getCodeSigners(jar, entry));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* return an array of CodeSigner objects for
|
||
|
* the given file in the jar. this array is not cloned.
|
||
|
*
|
||
|
*/
|
||
|
public CodeSigner[] getCodeSigners(String name)
|
||
|
{
|
||
|
return verifiedSigners.get(name);
|
||
|
}
|
||
|
|
||
|
public CodeSigner[] getCodeSigners(JarFile jar, JarEntry entry)
|
||
|
{
|
||
|
String name = entry.getName();
|
||
|
if (eagerValidation && sigFileSigners.get(name) != null) {
|
||
|
/*
|
||
|
* Force a read of the entry data to generate the
|
||
|
* verification hash.
|
||
|
*/
|
||
|
try {
|
||
|
InputStream s = jar.getInputStream(entry);
|
||
|
byte[] buffer = new byte[1024];
|
||
|
int n = buffer.length;
|
||
|
while (n != -1) {
|
||
|
n = s.read(buffer, 0, buffer.length);
|
||
|
}
|
||
|
s.close();
|
||
|
} catch (IOException e) {
|
||
|
}
|
||
|
}
|
||
|
return getCodeSigners(name);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Convert an array of signers into an array of concatenated certificate
|
||
|
* arrays.
|
||
|
*/
|
||
|
private static java.security.cert.Certificate[] mapSignersToCertArray(
|
||
|
CodeSigner[] signers) {
|
||
|
|
||
|
if (signers != null) {
|
||
|
ArrayList<java.security.cert.Certificate> certChains = new ArrayList<>();
|
||
|
for (int i = 0; i < signers.length; i++) {
|
||
|
certChains.addAll(
|
||
|
signers[i].getSignerCertPath().getCertificates());
|
||
|
}
|
||
|
|
||
|
// Convert into a Certificate[]
|
||
|
return certChains.toArray(
|
||
|
new java.security.cert.Certificate[certChains.size()]);
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* returns true if there no files to verify.
|
||
|
* should only be called after all the META-INF entries
|
||
|
* have been processed.
|
||
|
*/
|
||
|
boolean nothingToVerify()
|
||
|
{
|
||
|
return (anyToVerify == false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* called to let us know we have processed all the
|
||
|
* META-INF entries, and if we re-read one of them, don't
|
||
|
* re-process it. Also gets rid of any data structures
|
||
|
* we needed when parsing META-INF entries.
|
||
|
*/
|
||
|
void doneWithMeta()
|
||
|
{
|
||
|
parsingMeta = false;
|
||
|
anyToVerify = !sigFileSigners.isEmpty();
|
||
|
baos = null;
|
||
|
sigFileData = null;
|
||
|
pendingBlocks = null;
|
||
|
signerCache = null;
|
||
|
manDig = null;
|
||
|
// MANIFEST.MF is always treated as signed and verified,
|
||
|
// move its signers from sigFileSigners to verifiedSigners.
|
||
|
if (sigFileSigners.containsKey(JarFile.MANIFEST_NAME)) {
|
||
|
CodeSigner[] codeSigners = sigFileSigners.remove(JarFile.MANIFEST_NAME);
|
||
|
verifiedSigners.put(JarFile.MANIFEST_NAME, codeSigners);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static class VerifierStream extends java.io.InputStream {
|
||
|
|
||
|
private InputStream is;
|
||
|
private JarVerifier jv;
|
||
|
private ManifestEntryVerifier mev;
|
||
|
private long numLeft;
|
||
|
|
||
|
VerifierStream(Manifest man,
|
||
|
JarEntry je,
|
||
|
InputStream is,
|
||
|
JarVerifier jv) throws IOException
|
||
|
{
|
||
|
// BEGIN Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
// To know that null signals that the stream has been closed, we disallow
|
||
|
// it in the constructor. There's no need for anyone to pass null into this
|
||
|
// constructor, anyway.
|
||
|
if (is == null) {
|
||
|
throw new NullPointerException("is == null");
|
||
|
}
|
||
|
// END Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
this.is = is;
|
||
|
this.jv = jv;
|
||
|
this.mev = new ManifestEntryVerifier(man);
|
||
|
this.jv.beginEntry(je, mev);
|
||
|
this.numLeft = je.getSize();
|
||
|
if (this.numLeft == 0)
|
||
|
this.jv.update(-1, this.mev);
|
||
|
}
|
||
|
|
||
|
public int read() throws IOException
|
||
|
{
|
||
|
// BEGIN Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
if (is == null) {
|
||
|
throw new IOException("stream closed");
|
||
|
}
|
||
|
// END Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
if (numLeft > 0) {
|
||
|
int b = is.read();
|
||
|
jv.update(b, mev);
|
||
|
numLeft--;
|
||
|
if (numLeft == 0)
|
||
|
jv.update(-1, mev);
|
||
|
return b;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int read(byte b[], int off, int len) throws IOException {
|
||
|
// BEGIN Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
if (is == null) {
|
||
|
throw new IOException("stream closed");
|
||
|
}
|
||
|
// END Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
if ((numLeft > 0) && (numLeft < len)) {
|
||
|
len = (int)numLeft;
|
||
|
}
|
||
|
|
||
|
if (numLeft > 0) {
|
||
|
int n = is.read(b, off, len);
|
||
|
jv.update(n, b, off, len, mev);
|
||
|
numLeft -= n;
|
||
|
if (numLeft == 0)
|
||
|
jv.update(-1, b, off, len, mev);
|
||
|
return n;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void close()
|
||
|
throws IOException
|
||
|
{
|
||
|
if (is != null)
|
||
|
is.close();
|
||
|
is = null;
|
||
|
mev = null;
|
||
|
jv = null;
|
||
|
}
|
||
|
|
||
|
public int available() throws IOException {
|
||
|
// BEGIN Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
if (is == null) {
|
||
|
throw new IOException("stream closed");
|
||
|
}
|
||
|
// END Android-added: Throw IOE, not NPE, if stream is closed. http://b/110695212
|
||
|
return is.available();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Extended JavaUtilJarAccess CodeSource API Support
|
||
|
|
||
|
private Map<URL, Map<CodeSigner[], CodeSource>> urlToCodeSourceMap = new HashMap<>();
|
||
|
private Map<CodeSigner[], CodeSource> signerToCodeSource = new HashMap<>();
|
||
|
private URL lastURL;
|
||
|
private Map<CodeSigner[], CodeSource> lastURLMap;
|
||
|
|
||
|
/*
|
||
|
* Create a unique mapping from codeSigner cache entries to CodeSource.
|
||
|
* In theory, multiple URLs origins could map to a single locally cached
|
||
|
* and shared JAR file although in practice there will be a single URL in use.
|
||
|
*/
|
||
|
private synchronized CodeSource mapSignersToCodeSource(URL url, CodeSigner[] signers) {
|
||
|
Map<CodeSigner[], CodeSource> map;
|
||
|
if (url == lastURL) {
|
||
|
map = lastURLMap;
|
||
|
} else {
|
||
|
map = urlToCodeSourceMap.get(url);
|
||
|
if (map == null) {
|
||
|
map = new HashMap<>();
|
||
|
urlToCodeSourceMap.put(url, map);
|
||
|
}
|
||
|
lastURLMap = map;
|
||
|
lastURL = url;
|
||
|
}
|
||
|
CodeSource cs = map.get(signers);
|
||
|
if (cs == null) {
|
||
|
cs = new VerifierCodeSource(csdomain, url, signers);
|
||
|
signerToCodeSource.put(signers, cs);
|
||
|
}
|
||
|
return cs;
|
||
|
}
|
||
|
|
||
|
private CodeSource[] mapSignersToCodeSources(URL url, List<CodeSigner[]> signers, boolean unsigned) {
|
||
|
List<CodeSource> sources = new ArrayList<>();
|
||
|
|
||
|
for (int i = 0; i < signers.size(); i++) {
|
||
|
sources.add(mapSignersToCodeSource(url, signers.get(i)));
|
||
|
}
|
||
|
if (unsigned) {
|
||
|
sources.add(mapSignersToCodeSource(url, null));
|
||
|
}
|
||
|
return sources.toArray(new CodeSource[sources.size()]);
|
||
|
}
|
||
|
private CodeSigner[] emptySigner = new CodeSigner[0];
|
||
|
|
||
|
/*
|
||
|
* Match CodeSource to a CodeSigner[] in the signer cache.
|
||
|
*/
|
||
|
private CodeSigner[] findMatchingSigners(CodeSource cs) {
|
||
|
if (cs instanceof VerifierCodeSource) {
|
||
|
VerifierCodeSource vcs = (VerifierCodeSource) cs;
|
||
|
if (vcs.isSameDomain(csdomain)) {
|
||
|
return ((VerifierCodeSource) cs).getPrivateSigners();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* In practice signers should always be optimized above
|
||
|
* but this handles a CodeSource of any type, just in case.
|
||
|
*/
|
||
|
CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
|
||
|
List<CodeSource> sourceList = new ArrayList<>();
|
||
|
for (int i = 0; i < sources.length; i++) {
|
||
|
sourceList.add(sources[i]);
|
||
|
}
|
||
|
int j = sourceList.indexOf(cs);
|
||
|
if (j != -1) {
|
||
|
CodeSigner[] match;
|
||
|
match = ((VerifierCodeSource) sourceList.get(j)).getPrivateSigners();
|
||
|
if (match == null) {
|
||
|
match = emptySigner;
|
||
|
}
|
||
|
return match;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Instances of this class hold uncopied references to internal
|
||
|
* signing data that can be compared by object reference identity.
|
||
|
*/
|
||
|
private static class VerifierCodeSource extends CodeSource {
|
||
|
private static final long serialVersionUID = -9047366145967768825L;
|
||
|
|
||
|
URL vlocation;
|
||
|
CodeSigner[] vsigners;
|
||
|
java.security.cert.Certificate[] vcerts;
|
||
|
Object csdomain;
|
||
|
|
||
|
VerifierCodeSource(Object csdomain, URL location, CodeSigner[] signers) {
|
||
|
super(location, signers);
|
||
|
this.csdomain = csdomain;
|
||
|
vlocation = location;
|
||
|
vsigners = signers; // from signerCache
|
||
|
}
|
||
|
|
||
|
VerifierCodeSource(Object csdomain, URL location, java.security.cert.Certificate[] certs) {
|
||
|
super(location, certs);
|
||
|
this.csdomain = csdomain;
|
||
|
vlocation = location;
|
||
|
vcerts = certs; // from signerCache
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* All VerifierCodeSource instances are constructed based on
|
||
|
* singleton signerCache or signerCacheCert entries for each unique signer.
|
||
|
* No CodeSigner<->Certificate[] conversion is required.
|
||
|
* We use these assumptions to optimize equality comparisons.
|
||
|
*/
|
||
|
public boolean equals(Object obj) {
|
||
|
if (obj == this) {
|
||
|
return true;
|
||
|
}
|
||
|
if (obj instanceof VerifierCodeSource) {
|
||
|
VerifierCodeSource that = (VerifierCodeSource) obj;
|
||
|
|
||
|
/*
|
||
|
* Only compare against other per-signer singletons constructed
|
||
|
* on behalf of the same JarFile instance. Otherwise, compare
|
||
|
* things the slower way.
|
||
|
*/
|
||
|
if (isSameDomain(that.csdomain)) {
|
||
|
if (that.vsigners != this.vsigners
|
||
|
|| that.vcerts != this.vcerts) {
|
||
|
return false;
|
||
|
}
|
||
|
if (that.vlocation != null) {
|
||
|
return that.vlocation.equals(this.vlocation);
|
||
|
} else if (this.vlocation != null) {
|
||
|
return this.vlocation.equals(that.vlocation);
|
||
|
} else { // both null
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return super.equals(obj);
|
||
|
}
|
||
|
|
||
|
boolean isSameDomain(Object csdomain) {
|
||
|
return this.csdomain == csdomain;
|
||
|
}
|
||
|
|
||
|
private CodeSigner[] getPrivateSigners() {
|
||
|
return vsigners;
|
||
|
}
|
||
|
|
||
|
private java.security.cert.Certificate[] getPrivateCertificates() {
|
||
|
return vcerts;
|
||
|
}
|
||
|
}
|
||
|
private Map<String, CodeSigner[]> signerMap;
|
||
|
|
||
|
private synchronized Map<String, CodeSigner[]> signerMap() {
|
||
|
if (signerMap == null) {
|
||
|
/*
|
||
|
* Snapshot signer state so it doesn't change on us. We care
|
||
|
* only about the asserted signatures. Verification of
|
||
|
* signature validity happens via the JarEntry apis.
|
||
|
*/
|
||
|
signerMap = new HashMap<>(verifiedSigners.size() + sigFileSigners.size());
|
||
|
signerMap.putAll(verifiedSigners);
|
||
|
signerMap.putAll(sigFileSigners);
|
||
|
}
|
||
|
return signerMap;
|
||
|
}
|
||
|
|
||
|
public synchronized Enumeration<String> entryNames(JarFile jar, final CodeSource[] cs) {
|
||
|
final Map<String, CodeSigner[]> map = signerMap();
|
||
|
final Iterator<Map.Entry<String, CodeSigner[]>> itor = map.entrySet().iterator();
|
||
|
boolean matchUnsigned = false;
|
||
|
|
||
|
/*
|
||
|
* Grab a single copy of the CodeSigner arrays. Check
|
||
|
* to see if we can optimize CodeSigner equality test.
|
||
|
*/
|
||
|
List<CodeSigner[]> req = new ArrayList<>(cs.length);
|
||
|
for (int i = 0; i < cs.length; i++) {
|
||
|
CodeSigner[] match = findMatchingSigners(cs[i]);
|
||
|
if (match != null) {
|
||
|
if (match.length > 0) {
|
||
|
req.add(match);
|
||
|
} else {
|
||
|
matchUnsigned = true;
|
||
|
}
|
||
|
} else {
|
||
|
matchUnsigned = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
final List<CodeSigner[]> signersReq = req;
|
||
|
final Enumeration<String> enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration;
|
||
|
|
||
|
return new Enumeration<String>() {
|
||
|
|
||
|
String name;
|
||
|
|
||
|
public boolean hasMoreElements() {
|
||
|
if (name != null) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
while (itor.hasNext()) {
|
||
|
Map.Entry<String, CodeSigner[]> e = itor.next();
|
||
|
if (signersReq.contains(e.getValue())) {
|
||
|
name = e.getKey();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
while (enum2.hasMoreElements()) {
|
||
|
name = enum2.nextElement();
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public String nextElement() {
|
||
|
if (hasMoreElements()) {
|
||
|
String value = name;
|
||
|
name = null;
|
||
|
return value;
|
||
|
}
|
||
|
throw new NoSuchElementException();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Like entries() but screens out internal JAR mechanism entries
|
||
|
* and includes signed entries with no ZIP data.
|
||
|
*/
|
||
|
public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration<? extends ZipEntry> e) {
|
||
|
final Map<String, CodeSigner[]> map = new HashMap<>();
|
||
|
map.putAll(signerMap());
|
||
|
final Enumeration<? extends ZipEntry> enum_ = e;
|
||
|
return new Enumeration<JarEntry>() {
|
||
|
|
||
|
Enumeration<String> signers = null;
|
||
|
JarEntry entry;
|
||
|
|
||
|
public boolean hasMoreElements() {
|
||
|
if (entry != null) {
|
||
|
return true;
|
||
|
}
|
||
|
while (enum_.hasMoreElements()) {
|
||
|
ZipEntry ze = enum_.nextElement();
|
||
|
if (JarVerifier.isSigningRelated(ze.getName())) {
|
||
|
continue;
|
||
|
}
|
||
|
entry = jar.newEntry(ze);
|
||
|
return true;
|
||
|
}
|
||
|
if (signers == null) {
|
||
|
signers = Collections.enumeration(map.keySet());
|
||
|
}
|
||
|
while (signers.hasMoreElements()) {
|
||
|
String name = signers.nextElement();
|
||
|
entry = jar.newEntry(new ZipEntry(name));
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Any map entries left?
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public JarEntry nextElement() {
|
||
|
if (hasMoreElements()) {
|
||
|
JarEntry je = entry;
|
||
|
map.remove(je.getName());
|
||
|
entry = null;
|
||
|
return je;
|
||
|
}
|
||
|
throw new NoSuchElementException();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
private Enumeration<String> emptyEnumeration = new Enumeration<String>() {
|
||
|
|
||
|
public boolean hasMoreElements() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public String nextElement() {
|
||
|
throw new NoSuchElementException();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// true if file is part of the signature mechanism itself
|
||
|
static boolean isSigningRelated(String name) {
|
||
|
return SignatureFileVerifier.isSigningRelated(name);
|
||
|
}
|
||
|
|
||
|
private Enumeration<String> unsignedEntryNames(JarFile jar) {
|
||
|
final Map<String, CodeSigner[]> map = signerMap();
|
||
|
final Enumeration<JarEntry> entries = jar.entries();
|
||
|
return new Enumeration<String>() {
|
||
|
|
||
|
String name;
|
||
|
|
||
|
/*
|
||
|
* Grab entries from ZIP directory but screen out
|
||
|
* metadata.
|
||
|
*/
|
||
|
public boolean hasMoreElements() {
|
||
|
if (name != null) {
|
||
|
return true;
|
||
|
}
|
||
|
while (entries.hasMoreElements()) {
|
||
|
String value;
|
||
|
ZipEntry e = entries.nextElement();
|
||
|
value = e.getName();
|
||
|
if (e.isDirectory() || isSigningRelated(value)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (map.get(value) == null) {
|
||
|
name = value;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public String nextElement() {
|
||
|
if (hasMoreElements()) {
|
||
|
String value = name;
|
||
|
name = null;
|
||
|
return value;
|
||
|
}
|
||
|
throw new NoSuchElementException();
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
private List<CodeSigner[]> jarCodeSigners;
|
||
|
|
||
|
private synchronized List<CodeSigner[]> getJarCodeSigners() {
|
||
|
CodeSigner[] signers;
|
||
|
if (jarCodeSigners == null) {
|
||
|
HashSet<CodeSigner[]> set = new HashSet<>();
|
||
|
set.addAll(signerMap().values());
|
||
|
jarCodeSigners = new ArrayList<>();
|
||
|
jarCodeSigners.addAll(set);
|
||
|
}
|
||
|
return jarCodeSigners;
|
||
|
}
|
||
|
|
||
|
public synchronized CodeSource[] getCodeSources(JarFile jar, URL url) {
|
||
|
boolean hasUnsigned = unsignedEntryNames(jar).hasMoreElements();
|
||
|
|
||
|
return mapSignersToCodeSources(url, getJarCodeSigners(), hasUnsigned);
|
||
|
}
|
||
|
|
||
|
public CodeSource getCodeSource(URL url, String name) {
|
||
|
CodeSigner[] signers;
|
||
|
|
||
|
signers = signerMap().get(name);
|
||
|
return mapSignersToCodeSource(url, signers);
|
||
|
}
|
||
|
|
||
|
public CodeSource getCodeSource(URL url, JarFile jar, JarEntry je) {
|
||
|
CodeSigner[] signers;
|
||
|
|
||
|
return mapSignersToCodeSource(url, getCodeSigners(jar, je));
|
||
|
}
|
||
|
|
||
|
public void setEagerValidation(boolean eager) {
|
||
|
eagerValidation = eager;
|
||
|
}
|
||
|
|
||
|
public synchronized List<Object> getManifestDigests() {
|
||
|
return Collections.unmodifiableList(manifestDigests);
|
||
|
}
|
||
|
|
||
|
static CodeSource getUnsignedCS(URL url) {
|
||
|
return new VerifierCodeSource(null, url, (java.security.cert.Certificate[]) null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns whether the name is trusted. Used by
|
||
|
* {@link Manifest#getTrustedAttributes(String)}.
|
||
|
*/
|
||
|
boolean isTrustedManifestEntry(String name) {
|
||
|
// How many signers? MANIFEST.MF is always verified
|
||
|
CodeSigner[] forMan = verifiedSigners.get(manifestName);
|
||
|
if (forMan == null) {
|
||
|
return true;
|
||
|
}
|
||
|
// Check sigFileSigners first, because we are mainly dealing with
|
||
|
// non-file entries which will stay in sigFileSigners forever.
|
||
|
CodeSigner[] forName = sigFileSigners.get(name);
|
||
|
if (forName == null) {
|
||
|
forName = verifiedSigners.get(name);
|
||
|
}
|
||
|
// Returns trusted if all signers sign the entry
|
||
|
return forName != null && forName.length == forMan.length;
|
||
|
}
|
||
|
}
|