ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Programmatically Signing JAR Files

by Raffi Krikorian
04/12/2001

The jarsigner tool provides a way for developers to sign their JAR files with a given private key, so that others may verify the classes provided inside the archive. Unfortunately, neither this tool, nor the underlying sun.tools.jar.Main, is appropriate for embedding in other applications; in order to allow programmatic JAR signing, jarsigner needs to be retooled.

It is not an accident that programmatic JAR signing isn't supported by the core Java library. JAR signing gives users the ability to verify the integrity of the classes and other resources contained within the archive. Most JAR files are signed with the private key of the archive author, which is the end of a certificate chain that begins at a well-known certificate authority. Standard private key usage dictates that the password protecting a private key be kept only by the code author; storing the password on a computer makes the password inherently insecure. Thus, in most cases, programmatically signing JAR files is a frowned upon; however, there are a few cases when it is necessary.

Brief Primer on the Signed JAR Format

Signed JAR files are simply JAR files with a few additional files that Java uses for verification. Using public-private encryption, Java appends each file in the JAR to a buffer, and signs each file individually. Java then records these entries into the Manifest file. This Manifest is converted into a signature file which contains a digest of the entire manifest, preventing any files from being added or deleted from the JAR. The public key used for verification is provided along with the JAR file.

These additional files are inserted into the META-INF JAR entry directory of the signed JAR file. The signature block file has a SF extension and is named according to the key used to sign. (The jarsigner converts the proposed name for the key alias to be the name of the signing key; all alphanumeric characters in the name become uppercase; all other characters are replaced with the _ character.) The public key is also inserted with a DSA extension. The manifest file is then inserted as MANIFEST.MF.

The signed JAR format verifies that the user of the JAR file has received the file in the state that the signer intended. The JAR user can also decide whether he or she trusts the JAR signer or any other entity along the certificate chain. Signature blocks are computed by incrementally updating the signing buffer. This prevents files in the JAR file from being altered; a single modification would effect a change in the signature blocks for that file and for every file following it. This also ensures that files are not inserted "between" any of the files in the Manifest. Since the signature of the entire Manifest is also provided, the recipient can be sure that no files have been added to the end of the JAR file.

During verification, the public key is first extracted from the signed JAR file. Next, the Manifest signature is checked to ensure that no entity has tampered with the Manifest. The contents of the JAR are then compared to the contents and the attributes in the Manifest; all the files in the JAR must be in the Manifest (and all entries in the Manifest must be present in the JAR), and the signatures blocks provided in the Manifest must verify. If any of these steps fail, then the JAR isn't verified correctly.

jarsigner

The JDK provides jarsigner as the tool to sign and verify JAR files. When jarsigner is given an existing JAR file and a private key, it signs each file in the archive, records all the necessary information into the files detailed above, and outputs a signed JAR. When used in verification mode, jarsigner does the reverse, verifying the signatures provided in the signature block file and in the manifest against the public key provided in the META-INF directory of the JAR.

The drawback to jarsigner is that was created for use by a person; it was not designed to be embedded in any other application. Simply forking off the jarsigner executable from within another process requires that the contents of the process's System.out and System.err stream be parsed to account for all errors. Because many different errors are wrapped in the same error message, not all errors are reported distinctly via the error stream. Making use of the classes in the sun.tools.jar package, which contains all classes comprising the jarsigner, doesn't help, as the sun.tools.jar.Main class reports its error via two java.io.PrintStreams and a boolean return value on the only extra-package accessible method.

The ideal JAR signing solution would allow for all the different errors to be distinguished in a programmatic way and not by parsing strings off a PrintStream. To provide this, the JAR signing utility class must not simply return true or false on whether the method completes properly, but, instead, it should propagate the different exceptions which may occur when signing the JAR file. This allows other authors to catch the different exceptions and handle them as desired.

Pages: 1, 2, 3

Next Pagearrow