// This example is from _Java Examples in a Nutshell_. (http://www.oreilly.com) // Copyright (c) 1997 by David Flanagan // This example is provided WITHOUT ANY WARRANTY either expressed or implied. // You may study, use, modify, and distribute it for non-commercial purposes. // For any commercial use, see http://www.davidflanagan.com/javaexamples import java.io.*; import java.security.*; import java.rmi.*; // Used for the test programs only import java.rmi.server.*; /** * A SignedObject is exactly that--an object that bears a digital signature. * Subclass this class to add a digital signature to any class you want. * Note, however, that the signature is computed using serialization, so only * the serializable, non-transient fields of a subclass are included in * the signature computation. **/ public class SignedObject implements Serializable { protected String signername; // Who is doing the signing protected String algorithm; // What algorithm to use. private byte[] signature; // The bytes of the signature transient private boolean signing = false; // A flag used below. /** * This method computes a digital signature for the current state of the * object, excluding the signature-related state of this class. * That is, the signature is based only on the state of the subclasses. * The arguments specify who is signing the object, and what digital * signature algorithm to use. * * Note that no other threads should be modifying the object while * this computation is being performed. If a subclass will be used in a * multi-threaded environment, this means that methods of the subclass * that modify its state should be synchronized like this one is. **/ public synchronized void sign(String signername, String algorithm) throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { // Save the arguments for use by verify() this.signername = signername; this.algorithm = algorithm; // Get a Signature object to compute the signature with Signature s = Signature.getInstance(algorithm); // Get a Signer object representing the signer Signer signer = (Signer)IdentityScope.getSystemScope().getIdentity(signername); // Initialize the Signature object using the PrivateKey of the Signer s.initSign(signer.getPrivateKey()); // Create an ObjectOutputStream that writes its output to a // ByteArrayStream. This is how we capture the state of the object // so that it can be signed. ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); // Now serialize the object, capturing its state. We have to set a flag // before we do this, so that the signer name, algorithm, and signature // itself are not included in this serialized state. See writeObject() // below to see how this works. signing = true; out.writeObject(this); signing = false; // Now tell the Signature object about the bytes of serialized state // that were stored by the ByteArrayOutputStream s.update(bout.toByteArray()); // And finally, compute the signature this.signature = s.sign(); } /** A simpler version of sign(), that defaults to using the DSA algorithm */ public synchronized void sign(String signername) throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { sign(signername, "DSA"); } /** * This method verifies the signature of any SignedObject subclass. * It works much like the sign() method, and is also synchronized. **/ public synchronized boolean verify() throws IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException { // Make sure the object is actually signed. if (signature == null) throw new SignatureException("Object is not signed"); // Get the signature, signer and public key, and initialize, like above, // except that this time use a PublicKey instead of a PrivateKey Signature s = Signature.getInstance(algorithm); Identity signer = (Identity)IdentityScope.getSystemScope().getIdentity(signername); s.initVerify(signer.getPublicKey()); // Create streams and capture the current state of the object // (excluding the signature bytes themselves) in a byte array ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); signing = true; out.writeObject(this); signing = false; // Pass state of the object to the Signature, and verify the stored // signature bytes against that state. Return the result of verification. s.update(bout.toByteArray()); return s.verify(this.signature); } /** * When the contents of the object change, the signature becomes invalid. * When this happens, the signature should be erased, because validation * is guaranteed to fail. **/ public void removeSignature() { signature = null; signername = null; algorithm = null; } /** * This method is invoked to allow custom serialization. * We only write out our signature-related state when we are not computing * or verifying a signature. When we are computing or verifying, only our * subclass state gets written out. If we don't do this, verification will * fail because the signature[] array will obviously be different on * verification than it is when the signature is generated. **/ private void writeObject(ObjectOutputStream out) throws IOException { if (!signing) out.defaultWriteObject(); } } /** * This class is a simple SignedObject subclass. * This and the following interface and classes are used to test SignedObject. **/ class SignedString extends SignedObject { public String s; public SignedString(String s) { this.s = s; } } /** * This interface extends Remote. It is part of an RMI example using our * SignedString class above. **/ interface RemoteSignedString extends Remote { public SignedString getString() throws RemoteException; } /** * This is a simple RMI server class and a program to start and register the * server. The server creates a SignedString object, signs it, and then * exports it to clients through the RemoteSignedString getString() method. **/ class SignedStringServer extends UnicastRemoteObject implements RemoteSignedString { SignedString s; // The state of the server object /** The constructor. Initialize the SignedString */ public SignedStringServer(SignedString s) throws RemoteException {this.s=s;} /** This is the remote method exported by the server */ public SignedString getString() throws RemoteException { return s; } /** The main program that creates and registers the server object */ public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, IOException, SignatureException { SignedString ss = new SignedString(args[0]); // create object ss.sign(args[1]); // sign it SignedStringServer sss = new SignedStringServer(ss); // start server Naming.rebind("SignedStringServer", sss); // register server System.out.println("Ready for clients"); // up and running! } } /** * This class is a client that connects to a RemoteSignedString server and * calls the getString() method to obtain a SignedString. It then verifies * the signature of the object that it has just downloaded over a network * from an entirely different Java VM. The client takes an optional hostname * argument on the command line. **/ class SignedStringClient { public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, IOException, SignatureException, NotBoundException { // Look up the server RemoteSignedString rss = (RemoteSignedString) Naming.lookup("rmi://" + ((args.length>0)?args[0]:"") + "/SignedStringServer"); // Invoke a remote method of the server to get a SignedString SignedString ss = rss.getString(); // Now verify the signature on that SignedString. if (ss.verify()) System.out.println("Verified SignedString: " + ss.s); else System.out.println("SignedString failed verification"); } }