Reading Signed Jar Files

protected abstract byte[] engineSign protected int engineSignbyte[] outbuf, int offset, int len Create the signature based on the accumulated data. If there is an error in generating the signature, a SignatureException is thrown. protected abstract boolean engineVerifybyte b[] Return an indication of whether or not the given signature matches the expected signature of the accumulated data. If there is an error in validating the signature, a SignatureException is thrown. protected abstract void engineSetParameterString p, Object o [deprecated] protected abstract void engineSetParameterAlgorithmParameterSpec p Set the given parameters, which may be algorithm−specific. If this parameter does not apply to this algorithm, this method should throw an InvalidParameterException . protected abstract Object engineGetParameterString p [deprecated] Return the desired parameter, which is algorithm−specific. If the given parameter does not apply to this algorithm, this method should throw an InvalidParameterException . In addition to those methods, there are a few protected instance variables that keep track of the state of the signature object −− whether it has been initialized, whether it can be used to sign or to verify, and so on: protected final static int UNINITIALIZED protected final static int SIGN protected final static int VERIFY protected int state These variables control the internal state of signature object. The state is initially UNINITIALIZED ; it is set to SIGN by the initSign method and to VERIFY by the initVerify method. These variables are not normally used by the subclasses of Signature since the logic to maintain them is already implemented in the Signature class itself. Here is an implementation of a signature class. Note that the XYZSign class depends on other aspects of the security architecture −− in this example, the message digest engine to create an SHA message digest and the DSA key interfaces to handle the public and private keys. This is very typical of signature algorithms −− even to the point where the default name of the algorithm reflects the underlying components. The actual encryption of the message digest will use a simple XOR−based algorithm so that we can, as usual, avoid the mathematics involved with a secure example: package javasec.samples.ch12; import java.security.; import java.security.interfaces.; import java.security.spec.; public class XYZSignature extends Signature implements Cloneable { private DSAPublicKey pub; private DSAPrivateKey priv; private MessageDigest md; public XYZSignature throws NoSuchAlgorithmException { superXYZSignature; md = MessageDigest.getInstanceSHA; } public void engineInitVerifyPublicKey publicKey throws InvalidKeyException { try { pub = DSAPublicKey publicKey; } catch ClassCastException cce { throw new InvalidKeyExceptionWrong public key type; } } public void engineInitSignPrivateKey privateKey throws InvalidKeyException { try { priv = DSAPrivateKey privateKey; } catch ClassCastException cce { throw new InvalidKeyExceptionWrong private key type; } } public void engineUpdatebyte b throws SignatureException { try { md.updateb; } catch NullPointerException npe { throw new SignatureExceptionNo SHA digest found; } } public void engineUpdatebyte b[], int offset, int length throws SignatureException { try { md.updateb, offset, length; } catch NullPointerException npe { throw new SignatureExceptionNo SHA digest found; } } public byte[] engineSign throws SignatureException { byte b[] = null; try { b = md.digest ; } catch NullPointerException npe { throw new SignatureExceptionNo SHA digest found; } return cryptb, priv; } public boolean engineVerifybyte[] sigBytes throws SignatureException { byte b[] = null; try { b = md.digest ; } catch NullPointerException npe { throw new SignatureExceptionNo SHA digest found; } byte sig[] = cryptsigBytes, pub; return MessageDigest.isEqualsig, b; } public void engineSetParameterString param, Object value { throw new InvalidParameterExceptionNo parameters; } public void engineSetParameterAlgorithmParameterSpec aps { throw new InvalidParameterExceptionNo parameters; } public Object engineGetParameterString param { throw new InvalidParameterExceptionNo parameters; } public void engineReset { } private byte[] cryptbyte s[], DSAKey key { DSAParams p = key.getParams ; int rotValue = p.getP.intValue ; byte d[] = rots, byte rotValue; return d; } private byte[] rotbyte in[], byte rotValue { byte out[] = new byte[in.length]; for int i = 0; i in.length; i++ { out[i] = byte in[i] rotValue; } return out; } } Like all implementations of engines in the security architecture, this class must have a constructor that takes no arguments, but it must call its superclass with its name. The constructor also is responsible for creating the instance of the underlying message digest using whatever algorithm this class feels is important. It is interesting to note that this requires the constructor to specify that it can throw a NoSuchAlgorithmException in case the SHA algorithm cant be found. The keys for this test algorithm are required to be DSA public and private keys. In general, the correspondence between an algorithm and the type of key it requires is very strong, so this is a typical usage. Hence, the two engine initialization methods cast the key to make sure that the key has the correct format. The engine initialization methods are not required to keep track of the state of the signature object −− that is, whether the object has been initialized for signing or for verifying. That logic, since it is common to all signature objects, is present in the generic initialization methods of the Signature class itself. The methods that update the engine can simply pass their data to the message digest since the message digest is responsible for providing the fingerprint of the data that this object is going to sign or verify. Hence, the only interesting logic in this class is that employed by the signing and verification methods. Each method uses the message digest to create the digital fingerprint of the data. Then, to sign the data, the digest must be encrypted or otherwise operated upon with the previously defined private key −− this produces a unique digest that could only have been produced by the given data and the given private key. Conversely, to verify the data, the digest must be decrypted or otherwise operated upon with the previously defined public key; the resulting digest can then be compared to the expected digest to test for verification. Clearly, the security of this algorithm depends on a strong implementation of the signing operations. Our example here does not meet that definition −− were simply XORing every byte of the digest with a byte obtained from the parameters used to generate the keys. This XOR−encryption provides a good example since its both simple and symmetric; a real digital signature implementation is much more complex. These engine signing and verification methods are also responsible for setting the internal state of the engine back to an initialization state so that the same object can be used to sign or verify multiple signatures. In this case, no other work needs to be done for that; the message digest object itself is already reset once it creates its digest, and there is no other internal state inside the algorithm that needs to be reset. But if there were other state information, it would need to be reset in those methods.

12.4 Comparison with Previous Releases

There are few changes to the Signature class itself between Java 1.1 and Java 2. In Java 1.1, there is no SignatureSpi class and the Signature class extends the Object class instead; the setParameter method that requires an algorithm parameter spec does not exist in 1.1. In 1.1 and Java 2, version 1.2, the default security provider supports only DSA signatures; to get RSA signatures you must either install a third−party security provider or upgrade to 1.3. The SignedObject class is only available in Java 2. There are significant changes to the way in which signed classes are handled between Java 1.1. and Java 2. In Java 1.1, there is no jarsigner tool; the equivalent tool is called javakey , and it creates signatures using the 1.1 identity scope rather than a keystore. We will discuss this in Appendix C. Since Java 1.1 does not have code sources, reading a signed jar file is also different. In fact, since the java.util.jar package does not exist in that release, the classes required to read a standard PKCS7 signature block are unavailable to us. More important, the security manager must handle signed classes differently: the class loader we presented here must be modified to associate the certificates with the class using the setSigners method of the Class class, and the security manager must retrieve those certificates with the getSigners method. In general, the security manager and the class loader must be more tightly−coupled in order for this all to work; thats a technique well show in Appendix D.

12.5 Summary

The digital signatures weve examined in this chapter form a key piece of the Java security architecture since they are the mechanism by which the parameters of the Java security sandbox can be extended: a digital signature gives the user the assurance that particular Java classes were provided by known entities. The user is then free to adopt a security policy for those classes based on the users assessment of the trustworthiness of the entity that provided the classes. Digital signatures have many other uses, of course, and in conjunction with the SignedObject class they allow you to send and verify arbitrary pieces of data. The digital signature engine is interesting also because it requires the use of the other engines weve looked at in earlier chapters −− the message digest engine to generate the fingerprint of the data that the digital signature will sign and the key pair engine and its related classes to provide the necessary keys to feed into this engine.

Chapter 13. Cipher−Based Encryption

In this chapter, well look at how to encrypt data using ciphers. We usually think of encryption as a means of protecting data sent over an insecure network, although it may also be used to protect data stored in a file, on a Java smart card, or in a number of other applications. With ciphers, the encryption of data is separate from its transmission. This is in sharp contrast to SSL, which can encrypt only data that is sent over sockets. Cipher−based encryption is part of the JCE, which contains an engine the cipher engine that performs encryption as well as several classes that support data encryption. All the classes in this chapter are available only with the security provider that comes with JCE.

13.1 The Cipher Engine

First, well look at the engine that performs encryption within JCE. This engine is called the Cipher class javax.crypto.Cipher ; it provides an interface to encrypt and decrypt data either in arrays within the program or as that data is read or written through Javas stream interfaces: public class Cipher implements Cloneable Perform encryption and decryption of arbitrary data, using potentially a wide array of encryption algorithms. Like all security engines, the cipher engine implements named algorithms. However, the naming convention for the cipher engine is different in that cipher algorithms are compound names that can include the name of the algorithm along with the name of a padding scheme and the name of a mode. Padding schemes and modes are specified by names −− just like algorithms. In theory, just as you may pick a new name for an algorithm, you may specify new names for a padding scheme or a mode, although the SunJCE security provider specifies several standard ones. Modes and padding schemes are present in the Cipher class because that class implements what is known as a block cipher; that is, it expects to operate on data one block e.g., 8 bytes at a time. Padding schemes are required in order to ensure that the length of the data is an integral number of blocks. Modes are provided to further alter the encrypted data in an attempt to make it harder to break the encryption. For example, if the data to be encrypted contains a number of similar patterns −− repeated names or headerfooter information, for example −− any patterns in the resulting data may aid in breaking the encryption. Different modes of encrypting data help prevent these sorts of attacks. Depending upon the mode used by a cipher, it may need to be initialized in a special manner when the cipher is used for decryption. Some modes require initialization via an initialization vector. Modes also enable a block cipher to behave as a stream cipher; that is, instead of requiring a large, 8−byte chunk of data to operate upon, a mode may allow data to be processed in smaller quantities. So modes are very important in stream−based operations where data may need to be transmitted one or two characters at a time. The algorithms specified by the SunJCE security provider are: DES DES is the Data Encryption Standard algorithm, a standard that has been adopted by various organizations, including the U.S. government. There are known ways to attack this encryption, though they require a lot of computing power to do so; despite widespread predictions about the demise of