home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book Home Java Security Search this book

B.3. Key Management in an Identity Scope

We're now going to put together the identity scope with the information about the identity class to produce another key management system. One of the primary limitations of the default identity scope is that it's based upon a single file. If you're in a corporation, you may want to have an identity scope that encompasses the public keys of every employee in the corporation--but you can't afford to put the private keys of the employees in that database. Every employee needs read access to the database to obtain his or her own key; there's no practical way with a single identity scope to prevent these users from reading each other's private keys.

Hence, in this example, we're going to develop an identity scope that provides for the architecture shown in Figure B-2.

figure

Figure B-2. A key management architecture

There are two simple goals to this example:

  • There should be a central database (identity scope) managed by the system administrators of the XYZ Corporation. This database will hold the public keys of all identities that are used in the system, along with a security level that is assigned to each identity.

  • Each user should have a private database that holds the user's private key. The user's private key will be certified by the XYZ Corporation itself, so this private database will need to have the public key of the XYZ Corporation. We'll make this scope the system scope so that it can encapsulate the knowledge that there are two scopes in use; to a program, it will appear as only a single scope.

This architecture allows a program to access the user's private key, but not anyone else's private key; it also allows the corporation to set security policies for classes that are signed by particular entities.

There's a certain schizophrenic approach that a system administrator must take in order to use a system like the one we're describing here. Many of the operations that are provided by javakey cannot be duplicated by a standard Java program. Hence, we must always rely on javakey to perform certain operations (like importing a 1.1-based certificate), and then we need to convert from the javakey database to our own database.

We must implement three classes for this example: an identity class, a signer class, and a shared identity scope class (which will be based upon the XYZFileScope class that we showed above).

B.3.1. Implementing an Identity Class

First, let's look at an implementation of the identity class:

Class Definition

public class XYZIdentity extends Identity {
	private int trustLevel;

	protected XYZIdentity() {
	}

	public XYZIdentity(String name, IdentityScope scope)
                                  throws KeyManagementException {
		super(name, scope);
		scope.addIdentity(this);
		trustLevel = 0;
	}

	public void setPublicKey(PublicKey key)
                                  throws KeyManagementException {
		IdentityScope is = getScope();
		Identity i = is.getIdentity(key);
		if (i != null && !equals(i))
			throw new KeyManagementException("Duplicate public key");
		super.setPublicKey(key);
	}

	public void addCertificate(Certificate cert)
                                      throws KeyManagementException {
		Identity i = getScope().getIdentity(cert.getPublicKey());
		if (i != null && !equals(i))
			throw new KeyManagementException("Duplicate public key");
		super.addCertificate(cert);
	}

	public int getTrust() {
		return trustLevel;
	}

	void setTrust(int x) {
		if (x < 0 || x > 10)
			throw new IllegalArgumentException("Invalid trust level");
		trustLevel = x;
	}

	public String toString() {
		return super.toString() + " trust level: " + trustLevel;
	}
}

We've chosen in this class to ensure that an identity always belongs to a scope and so we only provided one constructor. There's a somewhat confusing point here, however. Constructing an identity as part of a scope does not automatically add that identity to the scope. That logic is required either in the constructor (as we've done), or the design of the class will require that the developer using the class explicitly assigns the identity to the scope later. The former case is probably more useful; make sure to assign your identities inside their constructors.

Other than the constructor, we're not required to implement any other methods in our identity class. However, we've chosen to override the setPublicKey() and addCertificate() methods so that those methods throw an exception when an identity is to be assigned a public key that already exists in the identity scope. You'll recall that when we first introduced the Identity class, we mentioned that this logic was not present. Adding that logic is a simple matter of checking to see if the public key in question is already in the identity scope.

Finally, we've introduced a variable in our identity to determine the level of trust that we place in this identity. This is similar to the binary option that javakey gives us as to whether an identity is trusted or not; in our version, we allow the identity to have a level of trust. A trust level of 3 might indicate that the identity is fully trusted and hence should have access to all files; a level of 2 might indicate that the identity should be allowed access only to files in the user's temporary directory; a level of 1 might indicate that the identity should never be allowed to access a local file. The point is, the notion of trust associated with an identity is completely up to the programmer to decide--you're free to assign whatever semantics you like for this (or any other value), or to dispense with such an idea altogether. The idea behind this variable is that the security manager might use it (or other such information) to determine an appropriate security policy.

B.3.2. Implementing a Signer Class

Implementing the Signer class that we require follows virtually the same process:

Class Definition

public class XYZSigner extends Signer {
	private int trustLevel;

	public XYZSigner(String name, IdentityScope scope)
                                      throws KeyManagementException {
		super(name, scope);
		scope.addIdentity(this);
	}

	public void setPublicKey(PublicKey key)
                                      throws KeyManagementException {
		IdentityScope scope = getScope();
		if (scope != null) {
			Identity i = getScope().getIdentity(key);
			if (i != null && !equals(i))
				throw new KeyManagementException(
                                                   "Duplicate public key");
		}
		super.setPublicKey(key);
	}
 
	public void addCertificate(Certificate cert)
                                      throws KeyManagementException {
		IdentityScope scope = getScope();
		if (scope != null) {
			Identity i = getScope().getIdentity(cert.getPublicKey());
			if (i != null && !equals(i))
				throw new KeyManagementException(
                                                   "Duplicate public key");
		}
		super.addCertificate(cert);
	}	

	public int getTrust() {
		return trustLevel;
	}

	void setTrust(int x) {
		if (x < 0 || x > 10)
			throw new IllegalArgumentException("Invalid trust level");
		trustLevel = x;
	}

	public String toString() {
		return super.toString() + " trust level: " + trustLevel;
	}
}

We do not need to provide an overridden method for the setKeyPair() method of the Signer class to ensure that a duplicate private key is not inserted into the identity scope. Since we can only insert a private key with a public key, and since there is a one-to-one correspondence between such keys, we know that if the public keys are unique, the private keys are unique as well.

B.3.3. A Shared System Identity Scope

In the architecture we're examining, there are two identity scopes:

  • The private scope. This scope will hold one and only one instance of XYZSigner. This signer will represent the user who owns that particular database.

  • The public scope. This scope will hold several instances of XYZIdentity, but no signers--since it is to be shared, we don't want it to contain any private keys.

Each of these scopes will be an instance of the XYZFileScope that we showed earlier. To combine them, we'll create another identity scope that holds a reference to both scopes:

Class Definition

public class XYZIdentityScope extends IdentityScope {
	private transient IdentityScope publicScope;
	private transient IdentityScope privateScope;

	public XYZIdentityScope() throws KeyManagementException {
		super("XYZIdentityScope");
		privateScope = new XYZFileScope("/floppy/floppy0/private");
		publicScope = new XYZFileScope("/auto/shared/sharedScope");
		setSystemScope(this);
	}
	
	public int size() {
		return publicScope.size() + privateScope.size();
	}

	public Identity getIdentity(String name) {
		Identity id;
		id = privateScope.getIdentity(name);
		if (id == null)
			id = publicScope.getIdentity(name);
		return id;
	}

	public Identity getIdentity(PublicKey key) {
		Identity id;
		id = privateScope.getIdentity(key);
		if (id == null)
			id = publicScope.getIdentity(key);
		return id;
	}

	public void addIdentity(Identity identity)
                                      throws KeyManagementException {
		throw new KeyManagementException(
                           "This scope does not support adding identities");
	}

	public void removeIdentity(Identity identity)
                                      throws KeyManagementException {
		throw new KeyManagementException(
                           "This scope does not support removing identities");
	}

	class XYZIdentityScopeEnumerator implements Enumeration {
		private boolean donePrivate = false;
		Enumeration pubEnum = null, privEnum = null;

		XYZIdentityScopeEnumerator() {
			pubEnum = publicScope.identities();
			privEnum = privateScope.identities();
			if (!privEnum.hasMoreElements())
				donePrivate = true;
		}

		public boolean hasMoreElements() {
			return pubEnum.hasMoreElements() ||
				   privEnum.hasMoreElements();
		}

		public Object nextElement() {
			Object o = null;
			if (!donePrivate) {
				o = privEnum.nextElement();
				if (!privEnum.hasMoreElements())
					donePrivate = true;
			}
			else o = pubEnum.nextElement();
			if (o == null)
				throw new NoSuchElementException(
                                          "XYZIdentityScopeEnumerator");
			return o;
		}
	}

	public Enumeration identities() {
		return new XYZIdentityScopeEnumerator();
	}
}

The idea behind this class is that it is going to hold identities containing private keys, and that those private keys should be held somewhere safe. For this example, we're assuming that the private identity scope database will be stored on a floppy disk somewhere--that way, a user can move the identity scope around with her, and the private key won't be left on a disk where some malicious person might attempt to retrieve it.

This class is completely tailored to a Solaris machine, since we've hardwired the name of the private file to a file on the default floppy drive of a Solaris machine, and we've hardwired the name of the public file to a file that can be automounted on the user's machine. On other machines, the name of the floppy drive will vary, and a complete implementation of this class would really require that filename to be a property. The property can be set to the appropriate value for the hardware on which the Java virtual machine is running. The public database probably shouldn't even be a file; it should be held on a remote machine somewhere and accessed via RMI or another technique. We'll leave those enhancements as an exercise for the reader.

Now that we have the two scopes we're interested in, completing the implementation is a simple matter of:

  • Setting this identity scope to be the system identity scope. This allows the developer to use the standard methods we've already seen to extract information from this scope.

  • Overriding the getIdentity() and identities() methods so that they operate on both included identity scopes. Remember that often identity scopes are disjoint; in this case, however, it makes sense for there to be a single interface to the two identity scopes.

  • Overriding the addIdentity() and removeIdentity() methods to prevent them from changing the underlying identity databases. We'll see how to manipulate the individual database in the next section.

B.3.4. Creating Identities

The XYZ Corporation is concerned about two sorts of identities: identities from corporations and individuals outside the corporation, and identities of employees. The latter must all have private keys in order for the employees to be able to sign documents and will be instances of the XYZSigner class; the former need only public keys and will be instances of the XYZIdentity class.

In order to create these identities, we're going to rely on the facilities provided by javakey to do the bulk of the work for us, then we're going to read the generic entity out of the javakey database and turn it into an XYZ-based entity. This allows us to import or create certificates for these identities, which is something that only javakey can do in Java 1.1.

When a new employee comes to the XYZ Corporation, we must generate a private identity database for that employee on a floppy that can be given to the employee. As a first step, however, we must create the employee in a standard javakey database so that the employee can be given a certificate to accompany her identity. Once we've got the employee into the javakey database, here's the code we use to convert the javakey entry into the XYZIdentityScope we just examined:

Class Definition

public class NewEmployee {
	public static void main(String args[]) {
		try {
			IdentityScope is = IdentityScope.getSystemScope();
			Signer origSigner = (Signer) is.getIdentity(args[0]);

			System.out.println(
						"Please insert the floppy for " + args[0]);
			System.out.print("Press enter when ready:  ");
			System.in.read();
			XYZFileScope privateScope =
						new XYZFileScope("/floppy/floppy0/private");
			XYZSigner newSigner = new XYZSigner(args[0], privateScope);
			KeyPair kp = new KeyPair(origSigner.getPublicKey(),
									  origSigner.getPrivateKey());
			newSigner.setKeyPair(kp);
			newSigner.setInfo(origSigner.getInfo());
			Certificate certs[] = origSigner.certificates();
			for (int i = 0; i < certs.length; i++)
				newSigner.addCertificate(certs[i]);
			newSigner.setTrust(Integer.parseInt(args[1]));
			privateScope.save();

			XYZFileScope sharedScope =
						new XYZFileScope("/auto/shared/sharedScope");
			XYZIdentity newId = new XYZIdentity(args[0], sharedScope);
			newId.setPublicKey(origSigner.getPublicKey());
			newId.setInfo(origSigner.getInfo());
			certs = origSigner.certificates();
			for (int i = 0; i < certs.length; i++)
				newId.addCertificate(certs[i]);
			newId.setTrust(Integer.parseInt(args[1]));
			sharedScope.save();
		} catch (Exception e) {
			System.out.println(e);
		}
	}
}

This program is then run with the name of the employee as an argument. When the program is run, two things happen:

  1. The correct private key database is created and written to the floppy. The private key database has the signing identity of the new employee loaded into it.

  2. The shared public database is opened, and the identity of the new employee is added to it.

In both cases, it was necessary to read the existing data out of the entity read from the javakey database and convert that data into an XYZ-based class. We could have used the existing object (a subclass of the Identity or Signer class), but that would not have allowed us to associate a level of trust with these entities in our database. After the program has run, both databases have the desired entity, with the desired set of keys.

When the system administrator for the XYZ Corporation receives a public key (and a certificate) for an entity that is not going to be a signer within the XYZ Corporation, a similar procedure would need to be followed to enter the certificate into the javakey database, and then extract out the new identity and update only the shared identity scope. Code to do that would be very similar to the code shown above.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.