Index: java/net/deterlab/abac/Context.java
===================================================================
--- java/net/deterlab/abac/Context.java	(revision c1736fe3113c21b39b3ffa81901a4118bcfdaa01)
+++ java/net/deterlab/abac/Context.java	(revision 4fd94364f0b545485dcc6ccb0dba5b0cf2c7a5eb)
@@ -48,8 +48,6 @@
     protected CredentialFactory credentialFactory;
 
-    /** Translation from issuer CN to issuer pubkey identifier */
-    protected Map<String, String> nicknames;
-    /** Translation from issuer pubkey identifier to issuer CN */
-    protected Map<String, String> keys;
+    /** Translation of nicknames to keyids */
+    protected KeyIDMap keyMap;
 
     /** True once BouncyCastle has been loaded. */
@@ -132,6 +130,5 @@
 	dirty = false;
 	m_identities = new TreeSet<Identity>();
-	nicknames = new TreeMap<String, String>();
-	keys = new TreeMap<String, String>();
+	keyMap = new KeyIDMap();
 	try {
 	    credentialFactory = new CredentialFactory();
@@ -528,40 +525,6 @@
 	    return;
 	m_identities.add(id);
-	if (id.getName() != null && id.getKeyID() != null) {
-	    if ( !keys.containsKey(id.getKeyID()) ) {
-		String name = id.getName();
-		int n= 1;
-
-		while (nicknames.containsKey(name)) {
-		    name = id.getName() + n++;
-		}
-		nicknames.put(name, id.getKeyID());
-		keys.put(id.getKeyID(), name);
-	    }
-	}
-    }
-    /**
-     * Translate either keys to nicknames or vice versa.  Break the string into
-     * space separated tokens and then each of them into period separated
-     * strings.  If any of the smallest strings is in the map, replace it with
-     * the value.
-     * @param is the string to manipulate
-     * @param m the Map containing translations
-     * @return the string after modification
-     */
-    protected String replace(String is, Map<String, String> m) {
-	String rv = "";
-	for (String tok: is.split(" ")) {
-	    String term = "";
-	    for (String s: tok.split("\\.")) {
-		String next = m.containsKey(s) ? m.get(s) : s;
-
-		if (term.isEmpty()) term = next;
-		else term += "." + next;
-	    }
-	    if (rv.isEmpty()) rv = term;
-	    else rv += " " + term;
-	}
-	return rv;
+	if (id.getName() != null && id.getKeyID() != null) 
+	    keyMap.addNickname(id.getKeyID(), id.getName());
     }
 
@@ -573,5 +536,6 @@
      * @return the String after expansion.
      */
-    String expandKeyID(String s) { return replace(s, nicknames); }
+    String expandKeyID(String s) { return keyMap.expandKeyID(s); }
+
     /**
      * Convert key IDs to  menmonic names in a Role string.  The inverse of
@@ -580,5 +544,5 @@
      * @return the String after expansion.
      */
-    String expandNickname(String s) { return replace(s, keys); }
+    String expandNickname(String s) { return keyMap.expandNickname(s); }
 
     /**
Index: java/net/deterlab/abac/KeyIDMap.java
===================================================================
--- java/net/deterlab/abac/KeyIDMap.java	(revision 4fd94364f0b545485dcc6ccb0dba5b0cf2c7a5eb)
+++ java/net/deterlab/abac/KeyIDMap.java	(revision 4fd94364f0b545485dcc6ccb0dba5b0cf2c7a5eb)
@@ -0,0 +1,184 @@
+package net.deterlab.abac;
+
+import edu.uci.ics.jung.graph.*;
+import edu.uci.ics.jung.graph.util.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.zip.*;
+import java.security.*;
+import java.security.cert.*;
+
+import org.bouncycastle.asn1.*;
+import org.bouncycastle.asn1.x509.*;
+import org.bouncycastle.x509.*;
+import org.bouncycastle.openssl.*;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * Represents a global graph of credentials in the form of principals and
+ * attributes.  Contains the identities and credentials that can be used in a
+ * proof.
+ * @author <a href="http://abac.deterlab.net">ISI ABAC team</a>
+ * @version 1.4
+ */
+public class KeyIDMap {
+    /** Translation from nickname to issuer pubkey identifier */
+    protected Map<String, String> nicknames;
+    /** Translation from issuer pubkey identifier to nickname */
+    protected Map<String, String> keys;
+
+    /**
+     * Create an empty Context.
+     */
+    public KeyIDMap() {
+	nicknames = new TreeMap<String, String>();
+	keys = new TreeMap<String, String>();
+    }
+
+    /**
+     * Create a context from another context.
+     * @param c the Context to copy
+     */
+    public KeyIDMap(KeyIDMap k) {
+	nicknames = new TreeMap<String, String>(k.nicknames);
+	keys = new TreeMap<String, String>(k.keys);
+    }
+
+    /**
+     * Add a mapping from keyid to nickname and back. If the keyid is already
+     * assigned a nickname, this fails.  If the nickname is already assigned to
+     * another key, it is disambiguated from other known nicknames.  The
+     * nickname that is assigned is returned, or null on failure.
+     * @param keyid a String, the keyid to map
+     * @param nick a String, the nickname to assign
+     * @return a String, the nickname actually assigned
+     */
+    public String addNickname(String keyid, String nick) { 
+	String name = nick;
+	int n= 1;
+
+	if ( keyid == null || nick == null) return null;
+	if ( keys.containsKey(keyid) ) return null;
+
+	while (nicknames.containsKey(name)) {
+	    name = nick + n++;
+	}
+	nicknames.put(name, keyid);
+	keys.put(keyid, name);
+	return name;
+    }
+
+    /**
+     * Return the nickname of this keyid, if any.
+     * @return the nickname of this keyid, or null if it is unknown.
+     */
+    public String keyToNickname(String keyid) {
+	return keys.get(keyid);
+    }
+
+    /**
+     * Return the keyis of this nickname, if any.
+     * @return the keyid of this nickname, or null if it is unknown.
+     */
+    public String nicknameToKey(String nick) {
+	return nicknames.get(nick);
+    }
+
+    /**
+     * Return all the keyIDs this map knows.
+     * @return a Set of Strings containing all the keyIDs this map knows.
+     */
+    public Set<String> getKeys() {
+	return keys.keySet();
+    }
+
+    /**
+     * Remove the mapping associated with this keyid.
+     * @param keyid a String to remove from both maps as a keyis
+     * @return a boolean, true if the keyid was found and removed
+     */
+    public boolean clearKey(String keyid) {
+	String nick = keys.get(keyid);
+
+	if ( nick == null ) return false;
+
+	nicknames.remove(nick);
+	keys.remove(keyid);
+	return true;
+    }
+
+    /**
+     * Remove the mapping associated with this nickname.
+     * @param nick a String to remove from both maps as a nickname
+     * @return a boolean, true if the nickname was found and removed
+     */
+    public boolean clearNickname(String nick) {
+	String keyid = nicknames.get(nick);
+
+	if ( keyid == null ) return false;
+
+	nicknames.remove(nick);
+	keys.remove(keyid);
+	return true;
+    }
+
+    /**
+     * Merge the mapping into this one.  If overwrite is true, entries in km
+     * overwrite entries in this.
+     * @param km the KeyIDMap to merge
+     * @param overwrite a boolean, true if km entries overwrite this map
+     */
+    public void merge(KeyIDMap km, boolean overwrite) {
+	for (String k : km.getKeys()) {
+	    if (keyToNickname(k) != null) {
+		if ( overwrite) clearKey(k);
+		else continue;
+	    }
+	    addNickname(k, km.keyToNickname(k));
+	}
+    }
+
+    /**
+     * Translate either keys to nicknames or vice versa.  Break the string into
+     * space separated tokens and then each of them into period separated
+     * strings.  If any of the smallest strings is in the map, replace it with
+     * the value.
+     * @param is the string to manipulate
+     * @param m the Map containing translations
+     * @return the string after modification
+     */
+    protected String replace(String is, Map<String, String> m) {
+	String rv = "";
+	for (String tok: is.split(" ")) {
+	    String term = "";
+	    for (String s: tok.split("\\.")) {
+		String next = m.containsKey(s) ? m.get(s) : s;
+
+		if (term.isEmpty()) term = next;
+		else term += "." + next;
+	    }
+	    if (rv.isEmpty()) rv = term;
+	    else rv += " " + term;
+	}
+	return rv;
+    }
+
+    /**
+     * Expand menmonic names in a Role string, e.g. the CN of the issuer
+     * certificate, into the full key ID.  Used internally by Roles to provide
+     * transparent use of mnemonics
+     * @param s the string to expand
+     * @return the String after expansion.
+     */
+    public String expandKeyID(String s) { return replace(s, nicknames); }
+    /**
+     * Convert key IDs to  menmonic names in a Role string.  The inverse of
+     * expandKeyID.
+     * @param s the string to expand
+     * @return the String after expansion.
+     */
+    public String expandNickname(String s) { return replace(s, keys); }
+
+}
