The Berkeley Service Discovery Service
Tutorial for writing SDS services
[Homepage] [System overview] [Clients] [Services] [Servers] [Config options]

Background

This section will describe the simplest way to make your service SDS savy. If you follow these steps, then a description of your service will be automatically advertised to the nearest SDS server, and clients will be able to discover your service by making SDS queries.

There are two main components of making your service SDS savy: first, implementing the ninja.sds.SDSserviceIF interface, and second, running the ninja.sds.iSpaceForwarder service in your service's ispace.

The ninja.sds.iSpaceForwarder service is actually a helper service that takes care of the most of the grunt work of dealing with the SDS. It handles the periodic advertisement of all appropriate services in its iSpace, and also handles listening to the SDS server announcements.

The ninja.sds.SDSserviceIF interface provides a way for the iSpaceForwarder to retreive information about the service so that it can advertise it to the SDS appropriately. In fact, the iSpaceForwarder uses this interface to determine which services wish to be advertised to the SDS system: if a service implements the interface, then it wants to be advertised.

The SDSserverIF interface

The routines that compose the ninja.sds.SDSserviceIF allow the iSpaceForwarder to gather all of the information needed in order to advertise the service. In essence, this means that the interface provides the signed XML description of the service, the certificate name of the principal running it, the name of the capability required to discover it, and also a list of default principals that should receive this capability. With that in mind, let's examine the ninja.sds.SDSserviceIF interface. Below is a list of the routines and what they basically do:

Sample service implementation

To show how to implement an SDS savy service, here's some sample code showing how to implement each of these routines. This code is taken from the ninja.sds.examples.SampleService class. See that class for the complete implementation and most up-to-date information. (In the code below, we left out some of the details such as how to read in the certificate and get the authenticator associated with the certificate). To start things off, let's just list the variables that we are using in the class.
     
     public class SampleService extends iSpaceService implements 
            SampleServiceIF, SDSserviceIF {
       private Authenticator myAuthenticator;  // principal's authenticator
       private NinjaCert myCert;               // principal's certificate
       /* the capability name is choosen by the service... it can be anything
          the signing principal wants it to be.. each principal's 
          capability namespace is independent of other principal's. */
       private final String myCapabilityName = "SampleService"; 
       private ServiceDescription myDescription = null;  // the description
       
       /* the following are some variables indicating which sds server we
          are sending to... */
       private NinjaCert theSDSserver = null; 
       private EKeyCert theServersEKey = null;   // server's encryption key
       private PacketCipher serverCipher = null;
The first two routines getCertificate and getCapabilityName are very straight forward. The main difficulty with them usually is configuring and retreiving the correct certificate for the principal and choosing a capability name.
       
       public NinjaCert getCertificate() {
         return myCert;
       }

       public String getCapabilityName() {
         return myCapabilityName;
       }

The getDefaultPermissions is a little trickier to do. The point of the method is to return a listing of the principals/certificates that should receive this service's capability automatically. However, it's more difficult than that because we somehow have to be able to prove that this service really wants those people to get the capability. That basically means that the service has to signed the permission list to prove to that it indeed wants these principals to be able to discover the principal. To this end, the getDefaultPermissions does not actually return just a list of permissions, but rather a list of the permissions and a signature in a byte array.

In order to make things easier for individual services, there is a helper routine in the ninja.sds.Permission class that aids in this. The SignPermissions method takes in a permission list, a timestamp, and an authenticator, and produces a byte array that contains both the permission list and a signature in the format needed.

The sample code below shows how you can do this. In this example, we create a permission list with just one permission, showing that the principal "czerwin@cs.berkeley.edu" should have ACCESS permission to this service's capability. It then signs the permission list using the helper routine.

       public byte[] getDefaultPermissions() {

         Permission[] perms = new Permission[1];
         
         perms[0] = new Permission("czerwin@cs.berkeley.edu.",
                                   myCert.uniqid, myCapabilityName,
                                   Permission.ACCESS);
         try {
           byte[] signed = null;
           synchronized (myAuthenticator) {
                signed = Permission.
                   SignPermissions(perms, System.currentTimeMillis(), myAuthenticator);
           }
           return signed;
         } catch (Exception e) {
             System.err.println("Warning, failed signing default capabilites");
             return null;
         }
       }

The getSignedDescription is even trickier to implement correctly. Not only does the routine have to return the XML description signed with by the service's principal, but it also has to encrypt it such that only the intended SDS server can decrypt it. We do this encryption in this routine so that the service can be assured that a malicious iSpaceForwarder cannot read the service's description.

One additional trickier part of this routine is that the SDS server that is being used can change (in the case of one SDS server going down, another one might come up and take its place). Therefore, we have to be careful of this.

With this said, the routine is fairly easy to understand. The certificate of the SDS server and its encryption key certificate are passed in. Using this information, the service can decide whether or not it really trusts the principal running the SDS server, and can elect not to advertise to it. In the sample code below, the service always trusts the service, and just prepares its description for it. A more careful service might wish to verify the certificate and the encryption key certificate through the verification routines.

In the routine, the first thing that happens is to see if the SDS server or encryption key has change, because if it has, we need to generate a new cipher object that is configured for the correct server and encryption key. To do the encryption, we use the helper routines in ninja.sds.PacketCipher, which represents the encryption protocol between services and SDS servers. It ensures efficient encrypted messages going from the services to the SDS servers.

To create the service description, we use another helper class, ServiceDescription. This can be bypassed, and we can manually input our own XML description, but for most services, the helper class is sufficient and gets by some grunt work.

Finally, to send the description, we just encode it using the ninja.sds.PacketCipher class. The resulting byte array is then ready to go, protected by encryption.

                      
       
       public byte [] getSignedDescription(NinjaCert sdsServer, EKeyCert serverEKey) {
     
         /** See if the sds server or its encryption key has changed.. */
         if ((theSDSserver == null) ||
             (!sdsServer.uniqid.equals(theSDSserver.uniqid)) ||
             (serverEKey.sameEKey(theServersEKey))) {

            theSDSserver = sdsServer;
            theServersEKey = serverEKey;
            serverCipher = new PacketCipher(myCert, myResourceName,
                                            theSDSserver.uniqid, "sds-server",
                                            theServersEKey.getEncryptionKey(),
                                            myAuthenticator);
         }
     
         if (myDescription == null) {
            String className = "ninja.sds.examples.SampleService";

            synchronized (myAuthenticator) {
                myDescription = new ServiceDescription(className, myCert.uniqid,
                                                   myResourceName, className,
                                                   null, myAthenticator);
            }
         }
         
         byte[] result = null;
     
         try { 
           result = serverCipher.EncodePacket(myDescription.toByteArray()); 
         } catch (Exception e) { result = null; }
     
         return result;
       }
     }

And that's all there is to it. As you can see, most of the security and XML grunt work is accomplished through helper classes, so service developers shouldn't have to fool around with them too much. But, as always, the helper classes can be overriden if the developers want to have more control over things like their XML descriptions.

Now you're ready to run your service and have it work with the SDS.

Running your service

You can run your iSpace service exactly how you used to, with the caveat that you must be sure that you also run the ninja.sds.iSpaceForwarder service and the ninja.services.auth.CertificateService. To do this, you simply modify the list of services that are run when you start up the iSpace. Just add the lines ninja.sds.iSpaceForwarder and ninja.services.auth.CertificateService to your list of services. For example, if you just wanted to run the SampleService, iSpaceForwarder and ninja.services.auth.CertificateService. your list would be:

    ninja.services.auth.CertificateService
    ninja.sds.examples.SampleService
    ninja.sds.iSpaceForwarder

Note, you really shouldn't have to run a certificate service in your iSpace, but because of some bad interactions with the CertificateRegistry, for now you should use it.

In terms of configuration of the iSpaceForwarder, there's only one option that might matter to you, and that's the global SDS server announcement channel. This is the channel that the iSpaceForwarder will listen to for available SDS servers. If you are running your own experimental SDS server, you might want to change this so that you can operate your SDS server and services independent of the production SDS servers.

You can see where to change this parameter in the SDS configuration options page. You should look over some of the other options too, in case you do want to change others, but mosto of the parameters only affect how a SDS server is run.

Insecure service

The SDS is also capable of advertising services that do not wish to be secure. There is no authentication or protection of the service's description in this case, but it does allow for easier implementation of services, and also descreases the computation load at the servers. Of course, you really shouldn't use this for production services, but if you are testing/debugging your own service, you might want to use this.

To advertise a service insecurely to the SDS, you simply have to implement the ninja.sds.SDSinsecureServiceIF interface instead of the ninja.sds.SDSserviceIF interface. Implementing this interface is very easy since you don't have to worry about permissions or certificates. In fact, you just have to implement one routine, which returns the service's description. The sample code given below shows how you can implement this routine. The full code for this class is given in ninja.sds.examples.SampleInsecureService. See that for the gory details.

     
     public class SampleInsecureService extends iSpaceService
       implements SampleInsecureServiceIF, SDSinsecureServiceIF {

       private ServiceDescription myDescription = null;
       

       public byte [] getDescription()
       {
         if (myDescription == null) {
            myDescription = new ServiceDescription("Public Printer", "none",
                                                  "insecure", className,
                                                  "yes", null);
         }
         return myDescription.toByteArray();
       }
       
     }