// DecryptRun.java

import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class DecryptRun extends ClassLoader
{
  // These are set up in the constructor, and later
  // used in the loadClass() method for decrypting the classes
  private SecretKey key;
  private Cipher cipher;

  // Constructor: set up the objects we need for
  // decryption
  public DecryptRun( SecretKey key ) throws GeneralSecurityException,
      IOException {
    this.key = key;

    String algorithm = "DES";

    SecureRandom sr = new SecureRandom();

    System.err.println( "[DecryptRun: creating cipher]" );
    cipher = Cipher.getInstance( algorithm );
    cipher.init( Cipher.DECRYPT_MODE, key, sr );
  }

  // Main routine: here, we read in the key, and create
  // an instance of DecryptRun, which is our custom ClassLoader.
  // After we've set up the ClassLoader, we use it to
  // load up an instance of the main class of the application.
  // Finally, we call the main method of this class via
  // the Java Reflection API
  static public void main( String args[] ) throws Exception {
    String keyFilename = args[0];
    String appName = args[1];

    // These are the arguments to the application itself
    String realArgs[] = new String[args.length-2];
    System.arraycopy( args, 2, realArgs, 0, args.length-2 );

    // Read in the key
    System.err.println( "[DecryptRun: reading key]" );
    byte rawKey[] = Util.readFile( keyFilename );
    DESKeySpec dks = new DESKeySpec( rawKey );
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // Create a decrypting ClassLoader
    DecryptRun dr = new DecryptRun( key );

    // Create an instance of the application's main class,
    // loading it through the ClassLoader
    System.err.println( "[DecryptRun: loading "+appName+"]" );
    Class clasz = dr.loadClass( appName );

    // Finally, call the main() routine of this instance
    // using the Reflection API

    // Grab a reference to main()
    String proto[] = new String[1];
    Class mainArgs[] = { (new String[1]).getClass() };
    Method main = clasz.getMethod( "main", mainArgs );

    // Create an array containing the arguments to main()
    Object argsArray[] = { realArgs };
    System.err.println( "[DecryptRun: running "+appName+".main()]" );

    // Call main().  We've handed execution off to the
    // application, and we're done!
    main.invoke( null, argsArray );
  }

  public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {
    try {
      // This will be the Class object that we create.
      // Note that we call it "clasz" instead of "class"
      // because "class" is a reserved word in Java
      Class clasz = null;

      // Obligatory step 1: if the class is already in the
      // system cache, we don't need to load it again
      clasz = findLoadedClass( name );

      if (clasz != null)
        return clasz;

      // Now we get to the custom part
      try {
        // Read the encrypted class file
        byte classData[] = Util.readFile( name+".class" );

        if (classData != null) {
          // decrypt it ...
          byte decryptedClassData[] = cipher.doFinal( classData );

          // ... and turn it into a class
          clasz = defineClass( name, decryptedClassData,
            0, decryptedClassData.length );
          System.err.println( "[DecryptRun: decrypting class "+name+"]" );
        }
      } catch( FileNotFoundException fnfe ) {
        // It's probably a system file, so this isn't an error
      }

      // Obligatory step 2: if our decryption didn't work,
      // maybe the class is to be found on the filesystem, so we
      // try to load it using the default ClassLoader
      if (clasz == null)
        clasz = findSystemClass( name );

      // Obligatory step 3: if we've been asked to,
      // resolve the class
      if (resolve && clasz != null)
        resolveClass( clasz );

      // Return the class to the caller
      return clasz;
    } catch( IOException ie ) {
      throw new ClassNotFoundException( ie.toString()
);
    } catch( GeneralSecurityException gse ) {
      throw new ClassNotFoundException( gse.toString()
);
    }
  }
}