Skip to main content

skip to main content

developerWorks  >  Java technology | Security  >

The practice of peer-to-peer computing: P2P meets SSL

Securing communication between peers

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Todd Sundsted (todd-p2p@etcee.com), Chief Architect, PointFire

01 Oct 2001

A core requirement of any non-trivial P2P application is secure communication between peers. While the details of the security depend on how the application will be used and on what it will protect, it's often possible to implement strong, general-purpose security using off-the-shelf technology such as SSL. This month, Todd Sundsted demonstrates how to use SSL (via JSSE) in P2P security.

Last month we looked at the role of trust in P2P applications. Level of trust measures how confident we are that we are communicating with whom we think we are and are accessing the resources we think we are. We also examined the three building blocks used to establish trust in all distributed applications, including P2P applications: authentication, authorization, and encryption.

Now we'll put last month's lesson into practice by modifying our simple P2P application. Specifically, we'll extend the application to support P2P authentication and encryption using X.509 certificates. We'll tackle authorization in a future article.

Secure authentication

An application's security requirements depend heavily on how the application will be used and on what it will protect. Nevertheless, it's often possible to implement strong, general purpose security using off-the-shelf technology. Authentication is an excellent example.

When a customer attempts to purchase a product from a Web site, both the customer and the Web site perform authentication. The customer typically authenticates himself by supplying a name and a password. The Web site, on the other hand, authenticates itself by exchanging a block of signed data and a valid X.509 certificate as part of an SSL handshake. The customer's browser verifies the certificate and uses the enclosed public key to verify the signed data. Once both sides are authenticated, commerce can begin.

SSL can handle both server authentication (as in the example above) and client authentication using the same mechanism. Web sites typically don't rely on SSL for client authentication -- it's easier to require the user to supply a password. However, SSL client and server authentication is perfect for the transparent authentication that must happen between peers -- like those in P2P applications.

Secure Sockets Layer (SSL)
SSL is a security protocol that provides privacy for communications over networks like the Internet. SSL allows applications to communicate without fear of eavesdropping or tampering.

SSL is actually two protocols that work together: the SSL Record Protocol and the SSL Handshake Protocol. The SSL Record Protocol, the lower level of the two protocols, encrypts and decrypts variable length records of data for higher level protocols, such as the SSL Handshake Protocol. The SSL Handshake Protocol handles the exchange and verification of application credentials.

When an application (the client) wants to communicate with another application (the server) the client opens a socket connection with the server. The client and the server then negotiate a secure connection. As part of this negotiation, the server authenticates itself to the client. The client can optionally authenticate itself to the server. Once authentication is complete and the secure connection established, the two applications can communicate securely. See Resources for additional information on SSL.

By convention, I'll consider the peer who initiated the communication to be the client and the other the server, regardless of the role they assume after the connection.



Back to top


How to get SSL in Java applications

SSL for Java applications is provided by Java Secure Socket Extension (JSSE). JSSE is a standard part of the recently released JDK 1.4 Beta, but is available as an extension to earlier versions of the Java platform.

JSSE uses SSL as the underlying mechanism for its secure sockets. JSSE secure sockets work like regular sockets except they support transparent authentication and encryption. Because they also look like ordinary sockets (they are subclasses of class java.net.Socket and class java.net.ServerSocket) most of the code that uses JSSE is oblivious to the change. The code that's affected the most is the code that handles the creation and initialization of the secure socket factories.

If you intend to use JSSE with a Java platform prior to version 1.4, you'll have to download and install the JSSE extension yourself (see Resources). The installation instructions are quite straightforward, so I won't repeat them here.



Back to top


The model

Figure 1 illustrates the role SSL will play in the communication between peers.


Figure 1. SSL in action
Figure 1. SSL in action

Two peers, known as A and B, desire to communicate securely. In the context of our simple P2P application, peer A wants to query a resource on peer B.

Each peer has a database (known as a keystore) containing its private key and a certificate containing its public key. A password protects the contents of the database. The database also contains one or more self-signed certificates from trusted peers.

Peer A initiates the transaction, each peer authenticates the other, both peers negotiate the cipher to use and its strength and establish a secure channel. After these operations are complete, each peer knows whom they are talking to and that the channel is secure.



Back to top


Initialization

Because the introduction of JSSE and SSL most significantly affects the initialization code, let's take a look at the code in peer A that's responsible for initialization.


Listing 1. Security initialization code


   // Each peer has an identity that must be locally (but not globally)
   // unique.  This identity and its associated public and private keys
   // are stored in a keystore and protected by a password.  Each
   // peer also has a name that must be globally unique.
   String stringIdentity = null;
   String stringPassword = null;
   String stringName = null;

   // The code that prompts the user for his/her identity
   // and password goes here.  the user's name is
   // generated (if necessary) later.

   // Create home directory.  This is a very portable way
   // to create a home directory, but it has its problems --
   // the various flavors of Microsoft Windows put the directory
   // in widely different locations in the directory hierarchy.
   String stringHome = System.getProperty("user.home") + File.separator + "p2p";
   File fileHome = new File(stringHome);
   if (fileHome.exists() == false)
     fileHome.mkdirs();

   // Create keystore.  We must run an external process to create the
   // keystore, because the security APIs don't expose enough
   // functionality to do this inline.  I haven't tested this widely enough
   // to know how portable this code is, but it works on everything I
   // tried it on.
   String stringKeyStore = stringHome + File.separator + "keystore";
   File fileKeyStore = new File(stringKeyStore);
   if (fileKeyStore.exists() == false)
   {
     System.out.println("Creating keystore...");
     byte [] arb = new byte [16];
     SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
     securerandom.nextBytes(arb);
     stringName = new String(Base64.encode(arb));
     String [] arstringCommand = new String []
     {
       System.getProperty("java.home") + File.separator + "bin" + File.separator + "keytool",
       "-genkey",
       "-alias", stringIdentity,
       "-keyalg", "RSA",
       "-keysize", "1024",
       "-dname", "CN=" + stringName,
       "-keystore", stringHome + File.separator + "keystore",
       "-keypass", stringPassword,
       "-storetype", "JCEKS",
       "-storepass", stringPassword
     };
     Process process = Runtime.getRuntime().exec(arstringCommand);
     process.waitFor();
     InputStream inputstream2 = process.getInputStream();
     IOUtils.copy(inputstream2, System.out);
     InputStream inputstream3 = process.getErrorStream();
     IOUtils.copy(inputstream3, System.out);
     if (process.exitValue() != 0)
       System.exit(-1);
   }

   // Once the application has created/located the keystore, it
   // opens it and creates a KeyStore instance from the data
   // in it.
   char [] archPassword = stringPassword.toCharArray();
   FileInputStream fileinputstream = new FileInputStream(stringHome + File.separator +

     "keystore");
   KeyStore keystore = KeyStore.getInstance("JCEKS");
   try
   {
     keystore.load(fileinputstream, archPassword);
   }
   catch (IOException ioexception)
   {
     System.out.println("Cannot load keystore.  Password may be wrong.");
     System.exit(-3);
   }
   if (keystore.containsAlias(stringIdentity) == false)
   {
     System.out.println("Cannot locate identity.");
     System.exit(-2);
   }

   // Create key manager.  The key manager holds this peer's
   // private key.
   KeyManagerFactory keymanagerfactory = KeyManagerFactory.getInstance("SunX509");
   keymanagerfactory.init(keystore, archPassword);
   KeyManager [] arkeymanager = keymanagerfactory.getKeyManagers();

   // Create trust manager.  The trust manager hold other peers'
   // certificates.
   TrustManagerFactory trustmanagerfactory = TrustManagerFactory.getInstance("SunX509");
   trustmanagerfactory.init(keystore);
   TrustManager [] artrustmanager = trustmanagerfactory.getTrustManagers();

   // Create SSL context.
   SSLContext sslcontext = SSLContext.getInstance("SSL");
   SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
   sslcontext.init(arkeymanager, artrustmanager, securerandom);

   // Create factories.
   m_socketfactory = sslcontext.getSocketFactory();
   m_serversocketfactory = sslcontext.getServerSocketFactory();
   m_keystore = keystore;

When a user first launches the application, the application prompts the user for an identity (a nickname) and a password. The identity is only used to identify peers locally -- it has no global significance. The application generates a random 128-bit (16 byte) string that it uses to globally identify peers and converts it to an alphanumeric string. It uses the identity, the password, and the name to create a keystore and a public/private key pair, which it stores in the keystore. The password protects the information in the keystore.

I'm sure you noticed the approach I took to create the initial keystore. The application launches keytool as an external process and keytool creates the keystore. I had to do it this way because the public Java security APIs don't provide tools for creating certificates -- that functionality is hidden within JSSE and its APIs aren't published. The biggest drawback associated with launching the keytool to do the work is the risk that the user-supplied password, which is passed in as part of the parameter list, may be exposed.

After the application creates the keystore, it opens the keystore and loads it into memory (if we had been able to create the keystore directly, we could have avoided this step). It creates a key manager and a trust manager from the keystore. Key managers manage the keys that are used to authenticate the application to its peer across a secure socket. Trust managers manage the certificates that are used to authenticate the peer sitting on the other side of the secure socket.

Finally, the application creates an SSLContext instance, which acts as a factory for secure socket factories. It creates secure versions of the SocketFactory and ServerSocketFactory classes and uses them for later communication.



Back to top


User interface

Most of the rest of the code is the same as our previous release. (See "The P2P application framework" for details. Download the complete source below.) That's one of the advantages JSSE brings to the table -- secure sockets created with JSSE look and work just like regular sockets. None of the supporting code (beyond creation and initialization has to change). There are, however, changes related to the user interface. Most of these changes were inspired by my desire to make the application easier to use for those readers who simply want to quickly set up the application so that they can try it out.

In response to reader feedback, I improved the command line interface. Peers and their resources exist in a hierarchical structure that users explore with the commands ls and cd. The ls command lists the contents of a particular point on the hierarchy (like the ls command does in UNIX or the dir command does in DOS) and the cd command changes the current location in the hierarchy (once again, like the commands of the same name do in UNIX and DOS). cd .. backs down the hierarchy.

To make the application easier to use, I've created two zip files, each containing a properly configured instance of the application. One instance communicates on port 7776, the other on port 7777. They have also been configured to authenticate each other so that a secure channel can be established. The zip files are named peerA.zip and peerB.zip (see Resources). More detailed usage instructions are provided in the README.



Back to top


Conclusion

The addition of SSL to a P2P application is an excellent way to provide simple yet strong security. Next month, we'll continue our P2P travels by looking at more sophisticated methods of peer discovery.




Back to top


Download

DescriptionNameSizeDownload method
Sample codej-p2pssl.zip120 KBHTTP
Information about download methods


Resources

  • You can use JSSE as part of Java 1.4 beta or as a separate extension in previous versions.



  • The IBM Research Division pursues myriad projects on the topic of security. Read more about IBM Research's efforts in Security Research in Java and Distributed Object Systems.



  • Never used sockets before? Take this hands-on Java sockets tutorial (developerWorks, August 2001), which will teach you how to use sockets to handle typical scenarios that crop up in the real world.



  • Joseph Sinclair wrote an interesting series for developerWorks on security in high-stakes systems. Part 1 (March 2001) examines how Java technology can be used to secure systems in which the consequences of mistaken identity can be particularly destructive. Part 2 (June 2001) discusses three familiar approaches for identifying users, highlighting their strengths and weaknesses.



  • developerWorks hosts an entire topic area devoted to the practice of security.



  • Find more Java resources on the developerWorks Java technology zone.


  • Read all of the articles in Todd Sundsted's The practice of peer-to-peer computing column on developerWorks.




About the author

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd is co-founder and chief architect of PointFire, Inc. Contact Todd at todd-p2p@etcee.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top