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.