The Java Security API is a framework for implementing and using security measures in the Java environment. The Java Security API is included in the core Java API, in the form of the java.security package.
The security package really provides two APIs: one for users of security algorithms, and another for the implementors or providers of these algorithms.
The user API is designed to let you use different cryptographic algorithms in your application without having to know how they are implemented by providers. All you need to know is an algorithm's name. As an example, you can create a new key-pair generator using the Digital Signature Algorithm (DSA) with the following call:
KeyPairGenerator myGen = KeyPairGenerator.getInstance("DSA");
You can take this new object and ask it for key pairs to be used to sign messages, without knowing how the key pairs are being generated. If you wanted to use a different algorithm to implement your key-pair generator, you would just change the algorithm name in the preceding line of code. The rest of your code that uses the object can usually remain unchanged.
In the same way that cryptographic algorithms are specified by name, providers of these algorithms are also specified by name. If you wanted to use an implementation of DSA from a specific provider, then you could ask for it by name when you create an object:
KeyPairGenerator myGen = KeyPairGenerator.getInstance("DSA", "MY_PROVIDER");
Although the Security API lets you hide from the details of cryptographic algorithms if you want to, it also lets you use those details if you need to. Underneath the generic, algorithm-independent interfaces provided by the Security API, like the KeyPairGenerator in our example, implementations of these interfaces will use algorithm-specific subclasses. If you need to give details about the algorithm and its specific parameters, then you can access these algorithm-specific interfaces for the objects you create by casting:
DSAKeyPairGenerator myDSAGen = (DSAKeyPairGenerator)KeyPairGenerator.getInstance("DSA"); DSAParams myParams = new DSAParams(myP, myQ, myG); myDSAGen.initialize(myParams, new SecureRandom(mySeed));
In this case, since we asked for a key-pair generator for the DSA algorithm, we know that the returned object will implement the DSAKeyPairGenerator interface. So we can cast the object to this type, and call algorithm-specific methods on the interface.
Companies or individuals that provide implementations of security algorithms can add their own implementations to the java.security API using the Provider interface. The provider creates implementations of the relevant interfaces in the API (Signature, KeyPairGenerator, etc.), then creates a subclass of the Provider interface. The Provider subclass will register the algorithms with the Security API, so that users can ask for their implementation of algorithms by name. So if you had implemented a better, faster version of the MD5 message digest format you would create a subclass of the java.security.MessageDigest class that used your implementation, and then create a subclass of Provider that would register your MD5 implementation under your chosen provider name ("Jim's Security," for example). Then a user of the Security API would use your MD5 implementation through the Security API by just asking for it by name:
MessageDigest digest = MessageDigest.getInstance("MD5", "Jim's Security");
The high-level facilities provided by the Security API cover the identification of agents and the encoding or decoding of information passed between agents. These are the same issues we identified as critical security issues in an earlier section, since indentifying agents involves the certification and authentication of the agents, while data encoding and decoding requires some form of encryption.
The initial public release of the Java Security API in the JDK 1.1 included APIs for identifying and verifying agents, and using message digests and digital signatures. At the time of this writing, there is also an extension package, the Java Cryptography Extension (JCE), that adds encryption interfaces to the Security API. These extensions are separated from the main Security API because the encryption code used in the JCE is not exportable outside the United States. In this chapter we'll discuss both elements of the overall Security API, but be warned that to use the encryption classes, such as Ciphers, you'll need to be a U.S. or Canadian citizen and download the JCE package from the Javasoft site.
The overall Java Security API includes interfaces for the following:
These interfaces let you represent the identities of agents and create access control lists (ACLs) for resources that reference these identities. These interfaces include Principal, Identity, Signer, and the java.secu-rity.acl package.
Digital signatures are used to sign messages and data, so that the receiver can verify the identity of the sender. Signatures are often implemented using public/private key pairs--a message is signed using the sender's private key, and the signature can be verified on the other end using the corresponding public key. The interfaces provided for generating and using digital signatures include Key, KeyGenerator, KeyPairGenerator, Signature, and MessageDigest.
Encryption algorithms are used to encode and decode data for secure transmission between agents. The key interface here is the Cipher, which is a general representation of an encryption algorithm.
In the rest of this chapter, we'll be looking at adding security to distributed applications using the Java Security API. To fuel the discussion, we'll be extending the simple agent shown in Example 5-1 to include user authentication and data encryption. The SimpleAgent is a basic network client that opens up a socket to a remote agent at a particular host and port number, and starts exchanging messages with it. The SimpleAgent keeps a queue of outgoing messages in its msgQueue data member: messages are added to the queue with the addMsg() method, and the nextMsg() method takes the first message off the queue and returns it. The SimpleAgent constructor takes a host name and port number as arguments, and opens a socket connection to the process listening to that port on the remote host. If the connection is made, it retrieves the input and output streams from the socket. The SimpleAgent also extends Thread, and its run() method is a loop in which it sends the next message in its queue to the remote process, reads a response message from the remote process, and handles the message by calling its processMsg() method. In this example, the processMsg() method does nothing, but subclasses of SimpleAgent could implement this method to interpret the message and act on it.
package dcj.examples.security; import java.lang.*; import java.net.*; import java.io.*; import java.util.Vector; public class SimpleAgent extends Thread { // Our socket connection to the server protected Socket serverConn; // The input/output streams from the other agent protected InputStream inStream; protected OutputStream outStream; // Message queue Vector msgQueue; public SimpleAgent(String host, int port) throws IllegalArgumentException { try { serverConn = new Socket(host, port); } catch (UnknownHostException e) { throw new IllegalArgumentException("Bad host name given."); } catch (IOException e) { System.out.println("SimpleAgent: " + e); System.exit(1); } try { inStream = new DataInputStream(serverConn.getInputStream()); outStream = new DataOutputStream(serverConn.getOutputStream()); } catch (Exception e) { inStream = null; outStream = null; } } public synchronized void addMsg(String msg) { msgQueue.addElement(msg); } protected synchronized String nextMsg() { String msg = null; if (msgQueue.size() > 0) { msg = (String)msgQueue.elementAt(0); msgQueue.removeElementAt(0); } return msg; } // Close the connection to the server when finished. protected void closeConnection() { try { serverConn.close(); } catch (Exception e) {} inStream = null; outStream = null; } public void run() { // Go into infinite loop, sending messages, receiving responses, and // processing them... DataInputStream din = (DataInputStream)inStream; DataOutputStream dout = (DataOutputStream)outStream; while (true) { String msg = nextMsg(); if (msg != null) { String inMsg = "", inToken = ""; try { dout.writeChars(msg); while (inToken.compareTo("END") != 0) { inToken = din.readUTF(); inMsg = inMsg + " " + inToken; } processMsg(inMsg); } catch (Exception e) {} } } } protected void processMsg(String msg) {} }
To use the SimpleAgent class, you would create one first, using the host and port number of the remote agent with which you want it to communicate. Then you would call its run() method to start the message-passing process:
SimpleAgent myAgent = new SimpleAgent("remote.host.org", 1234); myAgent.run();
In the examples in the remainder of this chapter, we'll add identity authentication and data encryption to this simple agent, to make sure that the information passed in its messages is secure. Although we'll be using a credit agent as our example application, we won't go into any details about the message protocol being used. These details aren't critical in a discussion about security, since our security measures will apply to any message protocol we decide to use.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |