Index: java/net/deterlab/abac/Credential.java
===================================================================
--- java/net/deterlab/abac/Credential.java	(revision be05757f1e4d406274cd7d5a6a42e81181b0c726)
+++ java/net/deterlab/abac/Credential.java	(revision 5cf72ccdd982c58a67aed1341c8007e5279554ed)
@@ -5,4 +5,5 @@
 
 import java.util.*;
+import java.util.zip.*;
 import java.security.*;
 import java.security.cert.*;
@@ -206,4 +207,77 @@
     public boolean hasCertificate() { return m_ac != null; }
 
+    public Identity getID() { return m_id; }
+
+    /**
+     * Import a zip file.  First import all the identities
+     * (pem), then the credentials (der) into the credential graph then any
+     * alias files into the two maps.  If keys is not null, any key pairs in
+     * PEM files are put in there.  If errors is not null, errors reading files
+     * are added indexed by filename.
+     */
+    static public Collection<Credential> readZipFile(ZipFile z, 
+	    Collection<KeyPair> keys, Map<String, Exception> errors) {
+	Vector<Credential> creds = new Vector<Credential>();
+	Vector<ZipEntry> derFiles = new Vector<ZipEntry>();
+
+	for (Enumeration<? extends ZipEntry> ze = z.entries(); 
+		ze.hasMoreElements();) {
+	    ZipEntry  f = ze.nextElement();
+	    Object o = null;
+	    try {
+		o = new PEMReader(new InputStreamReader(
+			    z.getInputStream(f))).readObject();
+	    }
+	    catch (IOException e) { 
+		// PEMReader couldn't deal, so we assume it's a DER
+		derFiles.add(f);
+		continue;
+	    }
+	    try {
+		if ( o == null ) {
+		    // This shouldn't be, but assume it's a DER
+		    derFiles.add(f);
+		}
+		else if (o instanceof X509CertificateObject) {
+		    Credential.addIdentity(
+			    new Identity((X509CertificateObject)o));
+		}
+		else if (o instanceof KeyPair ) {
+		    if ( keys != null ) keys.add((KeyPair) o);
+		}
+		else {
+		    throw new IOException("Unexpected PEM object: " + 
+			    o.getClass().getName());
+		}
+	    }
+	    catch (Exception e ) {
+		if (errors != null ) errors.put(f.getName(), e);
+	    }
+	}
+
+	for ( ZipEntry f : derFiles ) {
+	    try {
+		creds.add(new Credential(z.getInputStream(f)));
+	    }
+	    catch (Exception e ) {
+		if (errors != null ) errors.put(f.getName(), e);
+	    }
+	}
+	return creds;
+    }
+
+    static public Collection<Credential> readZipFile(ZipFile d) {
+	return readZipFile(d, null, null);
+    }
+    static public Collection<Credential> readZipFile(ZipFile d, 
+	    Map<String, Exception> errors) {
+	return readZipFile(d, null, errors);
+    }
+    static public Collection<Credential> readZipFile(ZipFile d, 
+	    Collection<KeyPair> keys) {
+	return readZipFile(d, keys, null);
+    }
+
+
     /**
      * Import a directory full of files.  First import all the identities
@@ -214,5 +288,5 @@
      */
     static public Collection<Credential> readDirectory(File d, 
-	    Collection<KeyPair> keys, Map<File, Exception> errors) {
+	    Collection<KeyPair> keys, Map<String, Exception> errors) {
 	Vector<Credential> creds = new Vector<Credential>();
 	Vector<File> derFiles = new Vector<File>();
@@ -246,5 +320,5 @@
 	    }
 	    catch (Exception e ) {
-		if (errors != null ) errors.put(f, e);
+		if (errors != null ) errors.put(f.getName(), e);
 	    }
 	}
@@ -255,5 +329,5 @@
 	    }
 	    catch (Exception e ) {
-		if (errors != null ) errors.put(f, e);
+		if (errors != null ) errors.put(f.getName(), e);
 	    }
 	}
@@ -265,5 +339,5 @@
     }
     static public Collection<Credential> readDirectory(File d, 
-	    Map<File, Exception> errors) {
+	    Map<String, Exception> errors) {
 	return readDirectory(d, null, errors);
     }
@@ -272,4 +346,27 @@
 	return readDirectory(d, keys, null);
     }
+
+    static public void writeZipFile(Collection<Credential> creds, File f,
+	    boolean allIDs) 
+	    throws IOException {
+	ZipOutputStream z = new ZipOutputStream(new FileOutputStream(f));
+	Set<Identity> ids = allIDs ? 
+	    new TreeSet(s_ids) : new TreeSet<Identity>();
+
+	int n = 0;
+	for (Credential c: creds) {
+	    z.putNextEntry(new ZipEntry("attr" + n++  + ".der"));
+	    c.write(z);
+	    z.closeEntry();
+	    if ( c.getID() != null && !allIDs) ids.add(c.getID());
+	}
+	for (Identity i: ids) {
+	    z.putNextEntry(new ZipEntry(i.getName() + ".pem"));
+	    i.write(z);
+	    z.closeEntry();
+	}
+	z.close();
+    }
+
 
 private Role m_head, m_tail;
Index: java/net/deterlab/abac/Identity.java
===================================================================
--- java/net/deterlab/abac/Identity.java	(revision be05757f1e4d406274cd7d5a6a42e81181b0c726)
+++ java/net/deterlab/abac/Identity.java	(revision 5cf72ccdd982c58a67aed1341c8007e5279554ed)
@@ -17,5 +17,5 @@
 import org.bouncycastle.openssl.PEMWriter;
 
-public class Identity {
+public class Identity implements Comparable {
     private X509CertificateObject m_cert;
     private String m_keyid;
@@ -107,5 +107,5 @@
 
 	pw.writeObject(m_cert);
-	pw.close();
+	pw.flush();
     }
 
@@ -160,4 +160,13 @@
     public String getName() { return m_cn; }
     public String toString() { return m_keyid + " (" + m_cn + ")"; }
+    public boolean equals(Object o) { 
+	if ( o == null ) return false;
+	else if ( ! (o instanceof Identity) ) return false;
+	else return getKeyID().equals(((Identity)o).getKeyID());
+    }
+    public int compareTo(Object o) { 
+	if ( ! (o instanceof Identity) ) return 1;
+	else return getKeyID().compareTo(((Identity)o).getKeyID());
+    }
     public X509CertificateObject getCertificate() { return m_cert; }
 
