0

I have an application that needs to run plug-ins written by the same company, but discovered at run-time.

For security, I want the application to authenticate that each plug-in was written by us. No third-party code needs to perform the authentication.

What is an easy way to perform this authentication?

Is it reasonable to get by with challenge-response, or do I need to sign the plug-in jar?

If I need to sign the plug-in jar, what APIs would I use to authenticate?

Andy Thomas
  • 84,978
  • 11
  • 107
  • 151
  • Do you control the plug-in mechanism? – Thorbjørn Ravn Andersen Jan 30 '17 at 22:33
  • Since it is dealing with Jars, would something like Maven or Gradle for your build dependencies/jar management suffice as you can specify the name, author, etc. – SomeStudent Jan 30 '17 at 22:52
  • @ThorbjørnRavnAndersen - Yes, totally. Ideally, it would be one with zero or few dependencies besides the Java SE standard library. – Andy Thomas Jan 30 '17 at 23:00
  • @SomeStudent - I'm discovering the plug-ins at runtime, not buildtime. – Andy Thomas Jan 30 '17 at 23:01
  • 1
    I would personally look into signed jars as they are designed for this particular purpose but I do not have personal experience with using them like this. You might find http://stackoverflow.com/questions/502218/sandbox-against-malicious-code-in-a-java-application inspirational. – Thorbjørn Ravn Andersen Jan 30 '17 at 23:07

1 Answers1

0

This is the answer I came to after reading and experimentation.

In this case, a self-signed certificate may be sufficient, since no third-party code needs to authenticate. The hosting code can use the public key to verify the plug-in.

Details

The examples below use the default key algorithm. You may wish to specify a more secure algorithm with -keyalg.

Make the keystore, a public/private key pair, and a self-signed certificate containing the public key

keytool -genkeypair -alias myalias -validity 365 -keystore mykeystore

Validity is measured in days.

Export the certificate containing the public key

keytool -export -keystore mykeystore -alias myalias -file mycertificate.cer

At build time, sign the plug-in jar

jarsigner -keystore mykeystore -signedjar my_signed.jar my_unsigned.jar myalias

At run time, authenticate the plug-in jar contents

This test harness can be used to test the code that follows.

public class CEVerify {
  public static void main(String[] args) throws IOException, CertificateException {
    File jarFile = new File( "C:\\myplugins\\my_signed.jar" );
    String certificatePath = "C:\\mycertificates\\mycertificate.cer";

    File certificateFile = new File( certificatePath );

    PublicKey publicKey = getPublicKeyFromCertificate( certificateFile );

    JarFile jar = new JarFile( jarFile );
    boolean isVerified = verify( jar, publicKey );
    if ( isVerified ) {
        System.out.println( "Verified!" );
    }
    else {
        System.err.println( "NOT verified!" );
    }

  }

You can extract the public key from the certificate like this:

  private static PublicKey 
  getPublicKeyFromCertificate( File certificateFile ) 
  throws CertificateException, FileNotFoundException 
  {
      CertificateFactory certificateFactory = CertificateFactory.getInstance( "X.509" );

      FileInputStream inCertificate = new FileInputStream( certificateFile );
      Certificate certificate = certificateFactory.generateCertificate( inCertificate );
      return certificate.getPublicKey();
  }

Given a jar file and a public key, you can verify appropriate entries in the jar. You may need to exclude other files if you used a different -keyalg, like RSA.

  private static boolean 
  verify( JarFile jar, PublicKey publicKey ) throws IOException {
      Enumeration<JarEntry> jarEntries = jar.entries();
      while ( jarEntries.hasMoreElements()) {
        JarEntry jarEntry = jarEntries.nextElement();

        if ( jarEntry.isDirectory()) {
            continue;
        }
        else {
            String entryName = jarEntry.getName();
            if ( entryName.endsWith( ".SF" ) || entryName.endsWith( ".DSA" )) {
              continue;
            }
            else if ( ! verifyJarEntry( jar, publicKey, jarEntry )) {
              return false;
            }
         }
      }

      return true;
  }

And this authenticates a particular file in a jar. Note the need to read all the bytes in the jar entry before its certificates can be obtained.

    private static boolean 
    verifyJarEntry( JarFile jar, PublicKey publicKey, JarEntry jarEntry)
    throws IOException 
    {
        try {
            InputStream in = jar.getInputStream( jarEntry );
            readAllOf( in );

            // public Certificate[] getCertificates()
            // ... This method can only be called once the JarEntry has been
            // completely verified by reading from the entry input stream
            // until the end of the stream has been reached. Otherwise, this
            // method will return null.

            Certificate[] certificates = jarEntry.getCertificates();
            if ((null == certificates) || (0 == certificates.length)) {
                return false;
            } else {
                for (int i = 0; i < certificates.length; ++i) {
                    Certificate certificate = certificates[i];
                    try {
                        certificate.verify( publicKey );
                        return true;
                    } catch (Exception e) {
                        continue;
                    }
                }
                return false;
            }
        } catch (SecurityException e) {
            return false;
        }
    }

Finally, this is the method called above to read all the bytes in a jar entry.

    private static void readAllOf(InputStream in) throws IOException {
        byte[] buffer = new byte[4096];
        while ( 0 < in.read( buffer )) {
            continue;
        }
    }
Andy Thomas
  • 84,978
  • 11
  • 107
  • 151