The database that an identity is held in is an identity scope. There can be multiple identity scopes in a Java program, though typically there is only a system identity scope. By default, the system identity scope for all Java programs is read from a file; this file is the database that javakey operates on. But the architecture of an identity scope can be more complex than a single scope.
As Figure B-1 shows, multiple identity scopes can be nested, or they can be disjoint. This is because an identity scope may itself be scoped--that is, just like an identity can belong to a particular scope, an identity scope can belong to another scope.
This architecture is not as useful as it might seem, since the identity scope class does not give any particular semantics to the notion of a nested identity scope. If you search the system scope in the figure for sdo's identity, you may or may not find it, depending on how the system identity scope is implemented. That's because there's no requirement that an identity scope recursively search its enclosed scopes for any information. And the default identity scope does not do such a recursive search.
This is not to prevent you from writing identity scope classes that use such semantics--indeed, writing such a scope is the goal of this appendix.
The idea of an identity scope, of course, is to hold one or more unique identities. However, possible implementations of an IdentityScope class (java.security.IdentityScope) are conceivably more complicated than that because of the definition of this class:
Implementations of this class are responsible for storing a set of identities and for performing certain operations on those identities.
Hence, an identity scope is also an identity. That means that an identity scope might have a name and a public key, which gives you the ability to model an identity database in very different ways. Conceivably, you might want an identity scope for an organization that contains all the identities of individuals within that organization. Rather than having a separate identity for the organization itself, the organization's identity can be subsumed by the identity scope. Since the organization itself also needs a name and a public key, this type of model might offer some flexibility over the alternative: a model that just has a list of identities, some of which are individuals and one of which is the organization.
However, we'll ignore that possibility for now, and just explore the identity scope class with a view to its simplest use: as a holder of one or more identities.
The IdentityScope class is an abstract class, and there are no classes in the core JDK that extend the IdentityScope class. Like other classes in the security package, instances of it may be retrieved by a static method (albeit with a different name than we've been led to expect):
Return the default identity scope provided by the virtual machine. For javakey, this is the identity scope held in the identitydb.obj file in the user's home directory (or an alternate file specified in the java.security property file).
Once you have retrieved the system's default scope (or any other identity scope), you can operate on it with the following methods:
Return the number of identities that are held in this scope. By default, this does not include the number of nested identities in other scopes that are held in this scope.
Return the identity object associated with the corresponding name.
Using the principal's name, return the identity object associated with the corresponding principal.
Return the identity object associated with the corresponding public key.
Add the given identity to this identity scope. A KeyManagementException is thrown if the identity has the same name or public key as another identity in this scope.
Remove the given identity from this identity scope. A KeyManagementException is thrown if the identity is not present in this scope.
Return an enumeration of all the identities in this scope.
For the most part, using these methods is straightforward. For example, to list all the identities in the default identity database, we need only find the system identity scope and enumerate it:
public class Test { public static void main(String args[]) { try { IdentityScope is = IdentityScope.getSystemScope(); System.out.println(is); Enumeration e = is.identities(); while (e.hasMoreElements()) { Identity id = (Identity) e.nextElement(); System.out.println(id); } } catch (Exception ex) {} } }
There is one exception to this idea of simplicity, however. An identity scope is typically persistent--the javakey database is in a local persistent file, and you could write your own scope that was saved in a file, a database, or some other storage. However, you'll notice that there are no methods in the IdentityScope class that allow you to save the database for a particular scope. Hence, we could add a new identity to the system identity scope like this:
IdentityScope is = IdentityScope.getSystemScope(); Identity me = somehowCreateIdentity("sdo"); try { is.addIdentity(me); } catch (KeyManagementException kme) {}
That adds an sdo identity to the system identity scope for the current execution of the virtual machine, but unless we can somehow save that scope to the identitydb.obj file, the sdo identity will be lost when we exit the virtual machine. Unfortunately, there are no public methods to save the identity scope.
As an aside, we'll note that the identitydb.obj file just happens to be the serialized version of an IdentityScope object--to save the database, we need only open an ObjectOutputStream and write the is instance variable to that output stream.
There's another point here that we must mention: the JDK's notion of the system identity scope expects to hold identity objects that are instances of a particular class that exists only in the sun package. This means that we can't actually write a fully correct somehowCreateIdentity() method--we can create identities, but they will not be of the exact class that the system identity scope expects. This can affect some of the operations of the javakey database, since some of those operations are dependent on properties of the Sun implementation of an identity that are not in the generic idea of an identity. When we write our own identity-based database at the end of this appendix, that will no longer be a problem (but we won't be able to use the javakey utility on that database, either).
We'll now implement our own identity scope, which will be one of the classes that we'll use at the end of this appendix to put together an identity-based key management database. We'll write a generic identity scope that implements the notion that its identities are held in a file:
public class XYZFileScope extends IdentityScope { private Hashtable ids; private static String fname; public XYZFileScope(String fname) throws KeyManagementException { super("XYZFileScope"); this.fname = fname; try { FileInputStream fis = new FileInputStream(fname); ObjectInputStream ois = new ObjectInputStream(fis); ids = (Hashtable) ois.readObject(); } catch (FileNotFoundException fnfe) { ids = new Hashtable(); } catch (Exception e) { throw new KeyManagementException( "Can't load identity database " + fname); } } public int size() { return ids.size(); } public Identity getIdentity(String name) { Identity id; id = (Identity) ids.get(name); return id; } public Identity getIdentity(PublicKey key) { if (key == null) return null; Identity id; for (Enumeration e = ids.elements(); e.hasMoreElements(); ) { id = (Identity) e.nextElement(); PublicKey k = id.getPublicKey(); if (k != null && k.equals(key)) return id; } return null; } public void addIdentity(Identity identity) throws KeyManagementException { String name = identity.getName(); if (getIdentity(name) != null) throw new KeyManagementException( name + " already in identity scope"); PublicKey k = identity.getPublicKey(); if (getIdentity(k) != null) throw new KeyManagementException( name + " already in identity scope"); ids.put(name, identity); } public void removeIdentity(Identity identity) throws KeyManagementException { String name = identity.getName(); if (ids.get(name) == null) throw new KeyManagementException( name + " isn't in the identity scope"); ids.remove(name); } public Enumeration identities() { return ids.elements(); } public void save() { try { FileOutputStream fos = new FileOutputStream(fname); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(ids); } catch (Exception e) { System.out.println(e); throw new RuntimeException("Can't save id database"); } } }
Let's delve into the implementation of this class. First, there are two instance variables. The ids variable will hold the identities themselves; we've decided to hold the identities in a hashtable so that we can easily search them based on a key. That key will be their name, which makes locating identities in this scope by name very easy (but notice that locating them by public key is harder). The second variable, fname, is the name of the file that will hold the persistent copy of this identity scope.
There are three constructors in the IdentityScope class that are available to us:
Construct an unnamed identity scope. This constructor is not designed to be used by programmers; it is provided only so that an identity scope may be subject to object serialization.
Construct an identity scope with the given name. If an identity scope is specified, the new identity scope will be scoped within the specified scope; otherwise, the new identity scope will have no scope associated with it (like Private Scope #2 in figure Figure B-1). A KeyManagementException will be thrown if an identity or identity scope with the desired name already exists in the given scope.
In our case, we've chosen only to provide our identity scope with a name. After calling the appropriate superclass constructor, our class opens up the stored version of the identity database and reads it in. Like the default javakey implementation, we've chosen the simple expedient of object serialization to a persistent file to provide our storage. If the file isn't found, we create an empty identity scope.
We've provided a simple save() method that serializes the private database out to the same file that we read it in from; this method has a package protection so that it will only be accessible by the code we develop. The remaining methods in our class are all methods we are required to implement, because they are methods that are abstract in our superclass. Because we're storing identities in a hashtable, their implementations are usually simple:
The size() method can simply return the size of the hashtable.
The getIdentity(name) method can simply use the name as the lookup key into the hashtable.
The getIdentity(key) method is the most complex method, although only slightly: it merely needs to enumerate the identities and test each one individually to see if the keys match.
The addIdentity() method can search to make sure that the name and public key of the new identity are unique and then simply store the identity into the hashtable with the name as its key.
The removeIdentity() method can just tell the hashtable to remove the identity with the appropriate key.
The identities() method can just return the hashtable enumeration.
There is one remaining protected method of the IdentityScope class:
Set the system identity scope to be the given scope.
We haven't used this method in this example, but it is one that we'll rely on later when we extend this example. This method replaces the system identity database. Replacing the system database makes things easier for developers. When developers need to operate on identities, they expect to access those identities through the system database. Now that our class is the system database, we can return identities whether they exist in the user's private key database or in the shared public key database.
Like the Identity class, the IdentityScope class uses the checkSecurityAccess() method of the security manager to protect many of its operations from being performed by untrusted classes. This method is called by the setSystemScope() method (with an argument of "set.system.scope"); no other methods of the IdentityScope class call this method by default.
However, in the default identity scope implemented in the sun package, in the following situations, these methods call the checkSecurityAccess() method with the given string:
When the getIdentity() method would return a signer--that is, an identity that has a private key ("get.signer")
When the addIdentity() and removeIdentity() methods are called ("add.identity" and "remove.identity", respectively)
When the database is written to a file via object serialization ("serialize.identity.database")
When we implemented the abstract methods of our IdentityScope class, we probably should have made the decision to let the security manager override the ability of an untrusted (or other) class to perform these operations. Hence, a better implementation of the getIdentity() method would be:
public Identity getIdentity(String name) { Identity id; id = (Identity) ids.get(name); if (id instanceof Signer) { SecurityManager sec = System.getSecurityManager(); if (sec != null) sec.checkSecurityAccess("get.signer"); } return id; }
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |