|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.util; |
|
|
|
import java.security.*; |
|
import java.io.*; |
|
import java.util.*; |
|
import java.util.jar.*; |
|
|
|
import sun.security.jca.Providers; |
|
import sun.security.util.DisabledAlgorithmConstraints; |
|
import sun.security.util.JarConstraintsParameters; |
|
|
|
/** |
|
* 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"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static class SunProviderHolder { |
|
private static final Provider instance = Providers.getSunProvider(); |
|
} |
|
|
|
|
|
HashMap<String, MessageDigest> createdDigests; |
|
|
|
|
|
ArrayList<MessageDigest> digests; |
|
|
|
|
|
ArrayList<byte[]> manifestHashes; |
|
|
|
private String name = null; |
|
private Manifest man; |
|
|
|
private boolean skip = true; |
|
|
|
private JarEntry entry; |
|
|
|
private CodeSigner[] signers = null; |
|
|
|
|
|
|
|
*/ |
|
public ManifestEntryVerifier(Manifest man) |
|
{ |
|
createdDigests = new HashMap<>(11); |
|
digests = new ArrayList<>(); |
|
manifestHashes = new ArrayList<>(); |
|
this.man = man; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
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")) { |
|
|
|
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())); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void update(byte buffer) { |
|
if (skip) return; |
|
|
|
for (int i=0; i < digests.size(); i++) { |
|
digests.get(i).update(buffer); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
public JarEntry getEntry() |
|
{ |
|
return entry; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public CodeSigner[] verify(Hashtable<String, CodeSigner[]> verifiedSigners, |
|
Hashtable<String, CodeSigner[]> sigFileSigners) |
|
throws JarException |
|
{ |
|
if (skip) { |
|
return null; |
|
} |
|
|
|
if (digests.isEmpty()) { |
|
throw new SecurityException("digest missing for " + name); |
|
} |
|
|
|
if (signers != null) { |
|
return signers; |
|
} |
|
|
|
JarConstraintsParameters params = |
|
getParams(verifiedSigners, sigFileSigners); |
|
|
|
for (int i=0; i < digests.size(); i++) { |
|
|
|
MessageDigest digest = digests.get(i); |
|
if (params != null) { |
|
try { |
|
params.setExtendedExceptionMsg(JarFile.MANIFEST_NAME, |
|
name + " entry"); |
|
DisabledAlgorithmConstraints.jarConstraints() |
|
.permits(digest.getAlgorithm(), params); |
|
} catch (GeneralSecurityException e) { |
|
if (debug != null) { |
|
debug.println("Digest algorithm is restricted: " + e); |
|
} |
|
return null; |
|
} |
|
} |
|
byte [] manHash = manifestHashes.get(i); |
|
byte [] theHash = digest.digest(); |
|
|
|
if (debug != null) { |
|
debug.println("Manifest Entry: " + |
|
name + " digest=" + digest.getAlgorithm()); |
|
debug.println(" manifest " + HexFormat.of().formatHex(manHash)); |
|
debug.println(" computed " + HexFormat.of().formatHex(theHash)); |
|
debug.println(); |
|
} |
|
|
|
if (!MessageDigest.isEqual(theHash, manHash)) |
|
throw new SecurityException(digest.getAlgorithm()+ |
|
" digest error for "+name); |
|
} |
|
|
|
|
|
signers = sigFileSigners.remove(name); |
|
if (signers != null) { |
|
verifiedSigners.put(name, signers); |
|
} |
|
return signers; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private JarConstraintsParameters getParams( |
|
Map<String, CodeSigner[]> verifiedSigners, |
|
Map<String, CodeSigner[]> sigFileSigners) { |
|
|
|
// verifiedSigners is usually preloaded with the Manifest's signers. |
|
// If verifiedSigners contains the Manifest, then it will have all of |
|
// the signers of the JAR. But if it doesn't then we need to fallback |
|
// and check verifiedSigners to see if the signers of this entry have |
|
|
|
if (verifiedSigners.containsKey(JarFile.MANIFEST_NAME)) { |
|
if (verifiedSigners.size() > 1) { |
|
|
|
return null; |
|
} else { |
|
return new JarConstraintsParameters( |
|
verifiedSigners.get(JarFile.MANIFEST_NAME)); |
|
} |
|
} else { |
|
CodeSigner[] signers = sigFileSigners.get(name); |
|
if (verifiedSigners.containsValue(signers)) { |
|
return null; |
|
} else { |
|
return new JarConstraintsParameters(signers); |
|
} |
|
} |
|
} |
|
} |
|
|