JarVerifierpublic class JarVerifier extends Object Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
objects are expected to have a {@code JarVerifier} instance member which
can be used to carry out the tasks associated with verifying a signed JAR.
These tasks would typically include:
- verification of all signed signature files
- confirmation that all signed data was signed only by the party or parties
specified in the signature block data
- verification that the contents of all signature files (i.e. {@code .SF}
files) agree with the JAR entries information found in the JAR manifest.
|
Fields Summary |
---|
private final String | jarName | private Manifest | man | private HashMap | metaEntries | private final Hashtable | signatures | private final Hashtable | certificates | private final Hashtable | verifiedEntries | byte[] | mainAttributesChunk | private static long | measureCount | private static long | averageTime |
Constructors Summary |
---|
JarVerifier(String name)Constructs and returns a new instance of {@code JarVerifier}.
jarName = name;
|
Methods Summary |
---|
void | addMetaEntry(java.lang.String name, byte[] buf)Add a new meta entry to the internal collection of data held on each JAR
entry in the {@code META-INF} directory including the manifest
file itself. Files associated with the signing of a JAR would also be
added to this collection.
metaEntries.put(Util.toASCIIUpperCase(name), buf);
| java.security.cert.Certificate[] | getCertificates(java.lang.String name)Returns all of the {@link java.security.cert.Certificate} instances that
were used to verify the signature on the JAR entry called
{@code name}.
Certificate[] verifiedCerts = verifiedEntries.get(name);
if (verifiedCerts == null) {
return null;
}
return verifiedCerts.clone();
| public static java.util.Vector | getSignerCertificates(java.lang.String signatureFileName, java.util.Map certificates)Returns a {@code Vector} of all of the
{@link java.security.cert.Certificate}s that are associated with the
signing of the named signature file.
Vector<Certificate> result = new Vector<Certificate>();
Certificate[] certChain = certificates.get(signatureFileName);
if (certChain != null) {
for (Certificate element : certChain) {
result.add(element);
}
}
return result;
| java.util.jar.JarVerifier$VerifierEntry | initEntry(java.lang.String name)Invoked for each new JAR entry read operation from the input
stream. This method constructs and returns a new {@link VerifierEntry}
which contains the certificates used to sign the entry and its hash value
as specified in the JAR MANIFEST format.
// If no manifest is present by the time an entry is found,
// verification cannot occur. If no signature files have
// been found, do not verify.
if (man == null || signatures.size() == 0) {
return null;
}
Attributes attributes = man.getAttributes(name);
// entry has no digest
if (attributes == null) {
return null;
}
Vector<Certificate> certs = new Vector<Certificate>();
Iterator<Map.Entry<String, HashMap<String, Attributes>>> it =
signatures.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
HashMap<String, Attributes> hm = entry.getValue();
if (hm.get(name) != null) {
// Found an entry for entry name in .SF file
String signatureFile = entry.getKey();
Vector<Certificate> newCerts = getSignerCertificates(
signatureFile, certificates);
Iterator<Certificate> iter = newCerts.iterator();
while (iter.hasNext()) {
certs.add(iter.next());
}
}
}
// entry is not signed
if (certs.size() == 0) {
return null;
}
Certificate[] certificatesArray = new Certificate[certs.size()];
certs.toArray(certificatesArray);
String algorithms = attributes.getValue("Digest-Algorithms"); //$NON-NLS-1$
if (algorithms == null) {
algorithms = "SHA SHA1"; //$NON-NLS-1$
}
StringTokenizer tokens = new StringTokenizer(algorithms);
while (tokens.hasMoreTokens()) {
String algorithm = tokens.nextToken();
String hash = attributes.getValue(algorithm + "-Digest"); //$NON-NLS-1$
if (hash == null) {
continue;
}
byte[] hashBytes;
try {
hashBytes = hash.getBytes("ISO8859_1"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.toString());
}
try {
// BEGIN android-changed
return new VerifierEntry(OpenSSLMessageDigestJDK.getInstance(algorithm),
hashBytes, certificatesArray);
// END android-changed
} catch (NoSuchAlgorithmException e) {
// Ignored
}
}
return null;
| boolean | isSignedJar()Returns a {@code boolean} indication of whether or not the
associated JAR file is signed.
return certificates.size() > 0;
| synchronized boolean | readCertificates()If the associated JAR file is signed, check on the validity of all of the
known signatures.
if (metaEntries == null) {
return false;
}
Iterator<String> it = metaEntries.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.endsWith(".DSA") || key.endsWith(".RSA")) { //$NON-NLS-1$ //$NON-NLS-2$
verifyCertificate(key);
// Check for recursive class load
if (metaEntries == null) {
return false;
}
it.remove();
}
}
return true;
| void | removeMetaEntries()Remove all entries from the internal collection of data held about each
JAR entry in the {@code META-INF} directory.
metaEntries = null;
| void | setManifest(java.util.jar.Manifest mf)Associate this verifier with the specified {@link Manifest} object.
man = mf;
| private boolean | verify(java.util.jar.Attributes attributes, java.lang.String entry, byte[] data, boolean ignoreSecondEndline, boolean ignorable)
String algorithms = attributes.getValue("Digest-Algorithms"); //$NON-NLS-1$
if (algorithms == null) {
algorithms = "SHA SHA1"; //$NON-NLS-1$
}
StringTokenizer tokens = new StringTokenizer(algorithms);
while (tokens.hasMoreTokens()) {
String algorithm = tokens.nextToken();
String hash = attributes.getValue(algorithm + entry);
if (hash == null) {
continue;
}
MessageDigest md;
try {
// BEGIN android-changed
md = OpenSSLMessageDigestJDK.getInstance(algorithm);
// END android-changed
} catch (NoSuchAlgorithmException e) {
continue;
}
if (ignoreSecondEndline && data[data.length - 1] == '\n"
&& data[data.length - 2] == '\n") {
md.update(data, 0, data.length - 1);
} else {
md.update(data, 0, data.length);
}
byte[] b = md.digest();
byte[] hashBytes;
try {
hashBytes = hash.getBytes("ISO8859_1"); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.toString());
}
return MessageDigest.isEqual(b, Base64.decode(hashBytes));
}
return ignorable;
| private void | verifyCertificate(java.lang.String certFile)
// Found Digital Sig, .SF should already have been read
String signatureFile = certFile.substring(0, certFile.lastIndexOf('."))
+ ".SF"; //$NON-NLS-1$
byte[] sfBytes = metaEntries.get(signatureFile);
if (sfBytes == null) {
return;
}
byte[] sBlockBytes = metaEntries.get(certFile);
try {
Certificate[] signerCertChain = JarUtils.verifySignature(
new ByteArrayInputStream(sfBytes),
new ByteArrayInputStream(sBlockBytes));
/*
* Recursive call in loading security provider related class which
* is in a signed JAR.
*/
if (null == metaEntries) {
return;
}
if (signerCertChain != null) {
certificates.put(signatureFile, signerCertChain);
}
} catch (IOException e) {
return;
} catch (GeneralSecurityException e) {
/* [MSG "archive.30", "{0} failed verification of {1}"] */
throw new SecurityException(
Messages.getString("archive.30", jarName, signatureFile)); //$NON-NLS-1$
}
// Verify manifest hash in .sf file
Attributes attributes = new Attributes();
HashMap<String, Attributes> hm = new HashMap<String, Attributes>();
try {
new InitManifest(new ByteArrayInputStream(sfBytes), attributes, hm,
null, "Signature-Version"); //$NON-NLS-1$
} catch (IOException e) {
return;
}
boolean createdBySigntool = false;
String createdByValue = attributes.getValue("Created-By"); //$NON-NLS-1$
if (createdByValue != null) {
createdBySigntool = createdByValue.indexOf("signtool") != -1; //$NON-NLS-1$
}
// Use .SF to verify the mainAttributes of the manifest
// If there is no -Digest-Manifest-Main-Attributes entry in .SF
// file, such as those created before java 1.5, then we ignore
// such verification.
// FIXME: The meaning of createdBySigntool
if (mainAttributesChunk != null && !createdBySigntool) {
String digestAttribute = "-Digest-Manifest-Main-Attributes"; //$NON-NLS-1$
if (!verify(attributes, digestAttribute, mainAttributesChunk,
false, true)) {
/* [MSG "archive.30", "{0} failed verification of {1}"] */
throw new SecurityException(
Messages.getString("archive.30", jarName, signatureFile)); //$NON-NLS-1$
}
}
byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME);
if (manifest == null) {
return;
}
// Use .SF to verify the whole manifest
String digestAttribute = createdBySigntool ? "-Digest" //$NON-NLS-1$
: "-Digest-Manifest"; //$NON-NLS-1$
if (!verify(attributes, digestAttribute, manifest, false, false)) {
Iterator<Map.Entry<String, Attributes>> it = hm.entrySet()
.iterator();
while (it.hasNext()) {
Map.Entry<String, Attributes> entry = it.next();
byte[] chunk = man.getChunk(entry.getKey());
if (chunk == null) {
return;
}
if (!verify(entry.getValue(), "-Digest", chunk, //$NON-NLS-1$
createdBySigntool, false)) {
/* [MSG "archive.31", "{0} has invalid digest for {1} in {2}"] */
throw new SecurityException(
Messages.getString("archive.31", //$NON-NLS-1$
new Object[] { signatureFile, entry.getKey(), jarName }));
}
}
}
metaEntries.put(signatureFile, null);
signatures.put(signatureFile, hm);
| void | verifySignatures(java.util.jar.JarVerifier$VerifierEntry entry, java.util.zip.ZipEntry zipEntry)Verifies that the digests stored in the manifest match the decrypted
digests from the .SF file. This indicates the validity of the signing,
not the integrity of the file, as it's digest must be calculated and
verified when its contents are read.
byte[] digest = entry.digest.digest();
if (!MessageDigest.isEqual(digest, Base64.decode(entry.hash))) {
/* [MSG "archive.31", "{0} has invalid digest for {1} in {2}"] */
throw new SecurityException(Messages.getString("archive.31", new Object[] { //$NON-NLS-1$
JarFile.MANIFEST_NAME, zipEntry.getName(), jarName }));
}
verifiedEntries.put(zipEntry.getName(), entry.certificates);
|
|