26

I'm wanting to sign a jar using jarsigner, then verify it using a Java application which does not have the signed jar as part of it's classpath (i.e. just using a filesystem location of the jar)

Now my problem is getting the signature file out of the jar, is there a simple way to do this?

I've had a play with the Inflater and Jar InputStreams with no luck.

Or is this something that can be accomplished in a better way?

Thanks

James Carr
  • 797
  • 2
  • 10
  • 21

4 Answers4

17

You can simply open the JAR with java.util.jar.JarFile and tell it to verify the JAR file. If the JAR is signed, then JarFile has the option to verify it (which is on by default). However, JarFile will also open unsigned JARs happily, therefore you must also check, whether or not the file is signed. You can do so by checking the JAR's manifest for *-Digest attributes: Elements with such an attribute attribute are signed.

Example:

JarFile jar = new JarFile(new File("path/to/your/jar-file"));

// This call will throw a java.lang.SecurityException if someone has tampered
// with the signature of _any_ element of the JAR file.
// Alas, it will proceed without a problem if the JAR file is not signed at all
InputStream is = jar.getInputStream(jar.getEntry("META-INF/MANIFEST.MF"));
Manifest man = new Manifest(is);
is.close();

Set<String> signed = new HashSet();
for(Map.Entry<String, Attributes> entry: man.getEntries().entrySet()) {
    for(Object attrkey: entry.getValue().keySet()) {
        if (attrkey instanceof Attributes.Name && 
           ((Attributes.Name)attrkey).toString().indexOf("-Digest") != -1)
            signed.add(entry.getKey());
    }
}

Set<String> entries = new HashSet<String>();
for(Enumeration<JarEntry> entry = jar.entries(); entry.hasMoreElements(); ) {
    JarEntry je = entry.nextElement();
    if (!je.isDirectory())
        entries.add(je.getName());
}

// contains all entries in the Manifest that are not signed.
// Ususally, this contains:
//  * MANIFEST.MF itself
//  * *.SF files containing the signature of MANIFEST.MF
//  * *.DSA files containing public keys of the signer

Set<String> unsigned = new HashSet<String>(entries);
unsigned.removeAll(signed);

// contains all the entries with a signature that are not present in the JAR
Set<String> missing = new HashSet<String>(signed);
missing.removeAll(entries);
nd.
  • 8,699
  • 2
  • 32
  • 42
  • 7
    Opening a jar via JarFile(fileName) does not verify the classes in the JAR it only enables verification that happens on access. Therefore for verifying all entries of a Jar you have to call jar.getInputStream(..) for each entry - this triggers the verification. – Robert Dec 06 '10 at 12:23
  • see http://stackoverflow.com/a/5589898/3934807 for some code which implements Robert's suggestion – ingenue Mar 01 '16 at 14:31
  • 1
    there's no reference to which keystore to use for validating the signature (the one used to sign jars) – madduci Apr 17 '18 at 08:14
  • @madduci - I have the same question as you. Do you know how the validation can be done using the public key of the keystore that was used for signing? Any help is appreciated. – learner Nov 22 '20 at 01:37
  • @learner I've did a bug report to Oracle for an issue with signed jars. Here's the code https://github.com/madduci/ecdsa-bug-jlink-9151223/blob/master/signature-verifier/src/main/java/com/github/madduci/signature/verifier/SignatureVerifier.java – madduci Nov 23 '20 at 08:44
16

The security Provider implementation guide outlines the process of verifying JARs. Although these instructions are for a JCA cryptographic service provider to verify itself, they should be applicable to your problem.

Specifically, check out the verify(X509Certificate targetCert) method in the sample code, "MyJCE.java".

erickson
  • 265,237
  • 58
  • 395
  • 493
  • 3
    Instead of suggesting the code, they could have as well provided a verifyAllContent() method ;-) – lapo Sep 03 '09 at 16:07
3

You can use entry.getCodeSigners() to get the signers for a particular entry in the JAR.

Make sure to open the JarFile with verify=true and to fully read the JAR entry before calling entry.getCodeSigners().

Something like this could be used to verify each entry that is not a signature file:

boolean verify = true;
JarFile jar = new JarFile(signedFile, verify);

// Need each entry so that future calls to entry.getCodeSigners will return anything
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
   JarEntry entry = entries.nextElement();
   IOUtils.copy(jar.getInputStream(entry), new NullOutputStream());
}

// Now check each entry that is not a signature file
entries = jar.entries();
while (entries.hasMoreElements()) {
    JarEntry entry = entries.nextElement();
    String fileName = entry.getName().toUpperCase(Locale.ENGLISH);
    if (!fileName.endsWith(".SF")
       && !fileName.endsWith(".DSA")
       && !fileName.endsWith(".EC")
       && !fileName.endsWith(".RSA")) {

       // Now get code signers, inspect certificates etc here...
       // entry.getCodeSigners();
    }
 }
Markus
  • 196
  • 5
  • Here is a written out example https://stackoverflow.com/questions/1374170/how-to-verify-a-jar-signed-with-jarsigner-programmatically – Sam Ginrich Apr 06 '21 at 07:26
-3

You can use the jarsigner application to do this. In processbuilder (or Runtime.exec) you can run the command with these arguments

 ProcessBulider pb = new ProcessBuilder("/usr/bin/jarsigner", "-verify", "-certs", f.getAbsolutePath());

and if the output contians verified then the jar is signed

Process p = pb.start();
p.waitFor();
InputStream is = p.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null)
{
if(line.contains("verified");
...

THere are more complicated things you can do when you have the output of the jarsigner code.

Milhous
  • 14,473
  • 16
  • 63
  • 82
  • The example is platform specific and additionally the jarsigner tool usually only comes with the JDK. – Robert Dec 06 '10 at 10:12
  • 1
    jarsigner won't verify the signer certificate (so any untrusted signature would do), it doesn't check trusted timestamps (cannot handle valid signatures from signers whose certificates expired) and its output is useless ('verified' is returned even on errors, a bit more information can be obtained by parsing error and warning messages - but that is still not enough to decide if a certificate is valid). – Jacek Konieczny Jun 14 '12 at 06:30