8015081: javax.security.auth.Subject.toString() throws NPE
authorascarpino
Tue, 01 Jul 2014 09:46:20 -0700
changeset 25188 e680ab7f208e
parent 25187 08aff438def8
child 25211 2476da7d73bf
8015081: javax.security.auth.Subject.toString() throws NPE Reviewed-by: xuelei, weijun Contributed-by: Jamil Nimeh <jamil.j.nimeh@oracle.com>
jdk/src/share/classes/javax/security/auth/Subject.java
jdk/test/javax/security/auth/Subject/Generic.java
jdk/test/javax/security/auth/Subject/Serial.java
jdk/test/javax/security/auth/Subject/Subject.java
jdk/test/javax/security/auth/Subject/SubjectNullTests.java
--- a/jdk/src/share/classes/javax/security/auth/Subject.java	Tue Jul 01 14:44:37 2014 +0100
+++ b/jdk/src/share/classes/javax/security/auth/Subject.java	Tue Jul 01 09:46:20 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -142,7 +142,9 @@
      * <p> The newly constructed Sets check whether this {@code Subject}
      * has been set read-only before permitting subsequent modifications.
      * The newly created Sets also prevent illegal modifications
-     * by ensuring that callers have sufficient permissions.
+     * by ensuring that callers have sufficient permissions.  These Sets
+     * also prohibit null elements, and attempts to add or query a null
+     * element will result in a {@code NullPointerException}.
      *
      * <p> To modify the Principals Set, the caller must have
      * {@code AuthPermission("modifyPrincipals")}.
@@ -170,7 +172,9 @@
      * These newly created Sets check whether this {@code Subject}
      * has been set read-only before permitting subsequent modifications.
      * The newly created Sets also prevent illegal modifications
-     * by ensuring that callers have sufficient permissions.
+     * by ensuring that callers have sufficient permissions.  These Sets
+     * also prohibit null elements, and attempts to add or query a null
+     * element will result in a {@code NullPointerException}.
      *
      * <p> To modify the Principals Set, the caller must have
      * {@code AuthPermission("modifyPrincipals")}.
@@ -194,17 +198,16 @@
      *
      * @exception NullPointerException if the specified
      *          {@code principals}, {@code pubCredentials},
-     *          or {@code privCredentials} are {@code null}.
+     *          or {@code privCredentials} are {@code null},
+     *          or a null value exists within any of these three
+     *          Sets.
      */
     public Subject(boolean readOnly, Set<? extends Principal> principals,
                    Set<?> pubCredentials, Set<?> privCredentials)
     {
-
-        if (principals == null ||
-            pubCredentials == null ||
-            privCredentials == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.input.s."));
+        collectionNullClean(principals);
+        collectionNullClean(pubCredentials);
+        collectionNullClean(privCredentials);
 
         this.principals = Collections.synchronizedSet(new SecureSet<Principal>
                                 (this, PRINCIPAL_SET, principals));
@@ -287,18 +290,17 @@
             sm.checkPermission(AuthPermissionHolder.GET_SUBJECT_PERMISSION);
         }
 
-        if (acc == null) {
-            throw new NullPointerException(ResourcesMgr.getString
+        Objects.requireNonNull(acc, ResourcesMgr.getString
                 ("invalid.null.AccessControlContext.provided"));
-        }
 
         // return the Subject from the DomainCombiner of the provided context
         return AccessController.doPrivileged
             (new java.security.PrivilegedAction<Subject>() {
             public Subject run() {
                 DomainCombiner dc = acc.getDomainCombiner();
-                if (!(dc instanceof SubjectDomainCombiner))
+                if (!(dc instanceof SubjectDomainCombiner)) {
                     return null;
+                }
                 SubjectDomainCombiner sdc = (SubjectDomainCombiner)dc;
                 return sdc.getSubject();
             }
@@ -347,9 +349,9 @@
         if (sm != null) {
             sm.checkPermission(AuthPermissionHolder.DO_AS_PERMISSION);
         }
-        if (action == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.action.provided"));
+
+        Objects.requireNonNull(action,
+                ResourcesMgr.getString("invalid.null.action.provided"));
 
         // set up the new Subject-based AccessControlContext
         // for doPrivileged
@@ -410,9 +412,8 @@
             sm.checkPermission(AuthPermissionHolder.DO_AS_PERMISSION);
         }
 
-        if (action == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.action.provided"));
+        Objects.requireNonNull(action,
+                ResourcesMgr.getString("invalid.null.action.provided"));
 
         // set up the new Subject-based AccessControlContext for doPrivileged
         final AccessControlContext currentAcc = AccessController.getContext();
@@ -467,9 +468,8 @@
             sm.checkPermission(AuthPermissionHolder.DO_AS_PRIVILEGED_PERMISSION);
         }
 
-        if (action == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.action.provided"));
+        Objects.requireNonNull(action,
+                ResourcesMgr.getString("invalid.null.action.provided"));
 
         // set up the new Subject-based AccessControlContext
         // for doPrivileged
@@ -534,9 +534,8 @@
             sm.checkPermission(AuthPermissionHolder.DO_AS_PRIVILEGED_PERMISSION);
         }
 
-        if (action == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.action.provided"));
+        Objects.requireNonNull(action,
+                ResourcesMgr.getString("invalid.null.action.provided"));
 
         // set up the new Subject-based AccessControlContext for doPrivileged
         final AccessControlContext callerAcc =
@@ -557,13 +556,14 @@
         return java.security.AccessController.doPrivileged
             (new java.security.PrivilegedAction<AccessControlContext>() {
             public AccessControlContext run() {
-                if (subject == null)
+                if (subject == null) {
                     return new AccessControlContext(acc, null);
-                else
+                } else {
                     return new AccessControlContext
                                         (acc,
                                         new SubjectDomainCombiner(subject));
             }
+            }
         });
     }
 
@@ -615,9 +615,8 @@
      */
     public <T extends Principal> Set<T> getPrincipals(Class<T> c) {
 
-        if (c == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.Class.provided"));
+        Objects.requireNonNull(c,
+                ResourcesMgr.getString("invalid.null.Class.provided"));
 
         // always return an empty Set instead of null
         // so LoginModules can add to the Set if necessary
@@ -711,9 +710,8 @@
      */
     public <T> Set<T> getPublicCredentials(Class<T> c) {
 
-        if (c == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.Class.provided"));
+        Objects.requireNonNull(c,
+                ResourcesMgr.getString("invalid.null.Class.provided"));
 
         // always return an empty Set instead of null
         // so LoginModules can add to the Set if necessary
@@ -758,9 +756,8 @@
         // would do is protect the set operations themselves
         // (like size()), which don't seem security-sensitive.
 
-        if (c == null)
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.Class.provided"));
+        Objects.requireNonNull(c,
+                ResourcesMgr.getString("invalid.null.Class.provided"));
 
         // always return an empty Set instead of null
         // so LoginModules can add to the Set if necessary
@@ -790,11 +787,13 @@
      */
     public boolean equals(Object o) {
 
-        if (o == null)
+        if (o == null) {
             return false;
+        }
 
-        if (this == o)
+        if (this == o) {
             return true;
+        }
 
         if (o instanceof Subject) {
 
@@ -969,11 +968,10 @@
 
         Set<Principal> inputPrincs = (Set<Principal>)gf.get("principals", null);
 
+        Objects.requireNonNull(inputPrincs,
+                ResourcesMgr.getString("invalid.null.input.s."));
+
         // Rewrap the principals into a SecureSet
-        if (inputPrincs == null) {
-            throw new NullPointerException
-                (ResourcesMgr.getString("invalid.null.input.s."));
-        }
         try {
             principals = Collections.synchronizedSet(new SecureSet<Principal>
                                 (this, PRINCIPAL_SET, inputPrincs));
@@ -993,13 +991,43 @@
     }
 
     /**
+     * Tests for null-clean collections (both non-null reference and
+     * no null elements)
+     *
+     * @param coll A {@code Collection} to be tested for null references
+     *
+     * @exception NullPointerException if the specified collection is either
+     *            {@code null} or contains a {@code null} element
+     */
+    private static void collectionNullClean(Collection<?> coll) {
+        boolean hasNullElements = false;
+
+        Objects.requireNonNull(coll,
+                ResourcesMgr.getString("invalid.null.input.s."));
+
+        try {
+            hasNullElements = coll.contains(null);
+        } catch (NullPointerException npe) {
+            // A null-hostile collection may choose to throw
+            // NullPointerException if contains(null) is called on it
+            // rather than returning false.
+            // If this happens we know the collection is null-clean.
+            hasNullElements = false;
+        } finally {
+            if (hasNullElements) {
+                throw new NullPointerException
+                    (ResourcesMgr.getString("invalid.null.input.s."));
+            }
+        }
+    }
+
+    /**
      * Prevent modifications unless caller has permission.
      *
      * @serial include
      */
     private static class SecureSet<E>
-        extends AbstractSet<E>
-        implements java.io.Serializable {
+        implements Set<E>, java.io.Serializable {
 
         private static final long serialVersionUID = 7911754171111800359L;
 
@@ -1098,6 +1126,9 @@
 
         public boolean add(E o) {
 
+            Objects.requireNonNull(o,
+                    ResourcesMgr.getString("invalid.null.input.s."));
+
             if (subject.isReadOnly()) {
                 throw new IllegalStateException
                         (ResourcesMgr.getString("Subject.is.read.only"));
@@ -1133,12 +1164,16 @@
             // check for duplicates
             if (!elements.contains(o))
                 return elements.add(o);
-            else
+            else {
                 return false;
         }
+        }
 
         public boolean remove(Object o) {
 
+            Objects.requireNonNull(o,
+                    ResourcesMgr.getString("invalid.null.input.s."));
+
             final Iterator<E> e = iterator();
             while (e.hasNext()) {
                 E next;
@@ -1153,12 +1188,7 @@
                     });
                 }
 
-                if (next == null) {
-                    if (o == null) {
-                        e.remove();
-                        return true;
-                    }
-                } else if (next.equals(o)) {
+                if (next.equals(o)) {
                     e.remove();
                     return true;
                 }
@@ -1167,6 +1197,10 @@
         }
 
         public boolean contains(Object o) {
+
+            Objects.requireNonNull(o,
+                    ResourcesMgr.getString("invalid.null.input.s."));
+
             final Iterator<E> e = iterator();
             while (e.hasNext()) {
                 E next;
@@ -1194,19 +1228,28 @@
                     });
                 }
 
-                if (next == null) {
-                    if (o == null) {
-                        return true;
-                    }
-                } else if (next.equals(o)) {
+                if (next.equals(o)) {
                     return true;
                 }
             }
             return false;
         }
 
+        public boolean addAll(Collection<? extends E> c) {
+            boolean result = false;
+
+            collectionNullClean(c);
+
+            for (E item : c) {
+                result |= this.add(item);
+            }
+
+            return result;
+        }
+
         public boolean removeAll(Collection<?> c) {
-            Objects.requireNonNull(c);
+            collectionNullClean(c);
+
             boolean modified = false;
             final Iterator<E> e = iterator();
             while (e.hasNext()) {
@@ -1224,30 +1267,34 @@
 
                 Iterator<?> ce = c.iterator();
                 while (ce.hasNext()) {
-                    Object o = ce.next();
-                    if (next == null) {
-                        if (o == null) {
+                    if (next.equals(ce.next())) {
                             e.remove();
                             modified = true;
                             break;
                         }
-                    } else if (next.equals(o)) {
-                        e.remove();
-                        modified = true;
-                        break;
-                    }
                 }
             }
             return modified;
         }
 
+        public boolean containsAll(Collection<?> c) {
+            collectionNullClean(c);
+
+            for (Object item : c) {
+                if (this.contains(item) == false) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
         public boolean retainAll(Collection<?> c) {
-            Objects.requireNonNull(c);
+            collectionNullClean(c);
+
             boolean modified = false;
-            boolean retain = false;
             final Iterator<E> e = iterator();
             while (e.hasNext()) {
-                retain = false;
                 E next;
                 if (which != Subject.PRIV_CREDENTIAL_SET) {
                     next = e.next();
@@ -1260,26 +1307,12 @@
                     });
                 }
 
-                Iterator<?> ce = c.iterator();
-                while (ce.hasNext()) {
-                    Object o = ce.next();
-                    if (next == null) {
-                        if (o == null) {
-                            retain = true;
-                            break;
-                        }
-                    } else if (next.equals(o)) {
-                        retain = true;
-                        break;
+                if (c.contains(next) == false) {
+                    e.remove();
+                    modified = true;
                     }
                 }
 
-                if (!retain) {
-                    e.remove();
-                    retain = false;
-                    modified = true;
-                }
-            }
             return modified;
         }
 
@@ -1301,6 +1334,73 @@
             }
         }
 
+        public boolean isEmpty() {
+            return elements.isEmpty();
+        }
+
+        public Object[] toArray() {
+            final Iterator<E> e = iterator();
+            while (e.hasNext()) {
+                // The next() method performs a security manager check
+                // on each element in the SecureSet.  If we make it all
+                // the way through we should be able to simply return
+                // element's toArray results.  Otherwise we'll let
+                // the SecurityException pass up the call stack.
+                e.next();
+            }
+
+            return elements.toArray();
+        }
+
+        public <T> T[] toArray(T[] a) {
+            final Iterator<E> e = iterator();
+            while (e.hasNext()) {
+                // The next() method performs a security manager check
+                // on each element in the SecureSet.  If we make it all
+                // the way through we should be able to simply return
+                // element's toArray results.  Otherwise we'll let
+                // the SecurityException pass up the call stack.
+                e.next();
+            }
+
+            return elements.toArray(a);
+        }
+
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+
+            if (!(o instanceof Set)) {
+                return false;
+            }
+
+            Collection<?> c = (Collection<?>) o;
+            if (c.size() != size()) {
+                return false;
+            }
+
+            try {
+                return containsAll(c);
+            } catch (ClassCastException unused)   {
+                return false;
+            } catch (NullPointerException unused) {
+                return false;
+            }
+        }
+
+        public int hashCode() {
+            int h = 0;
+            Iterator<E> i = iterator();
+            while (i.hasNext()) {
+                E obj = i.next();
+                if (obj != null) {
+                    h += obj.hashCode();
+                }
+            }
+            return h;
+        }
+
         /**
          * Writes this object out to a stream (i.e., serializes it).
          *
@@ -1338,12 +1438,16 @@
             which = fields.get("which", 0);
 
             LinkedList<E> tmp = (LinkedList<E>) fields.get("elements", null);
+
+            Subject.collectionNullClean(tmp);
+
             if (tmp.getClass() != LinkedList.class) {
                 elements = new LinkedList<E>(tmp);
             } else {
                 elements = tmp;
             }
         }
+
     }
 
     /**
--- a/jdk/test/javax/security/auth/Subject/Generic.java	Tue Jul 01 14:44:37 2014 +0100
+++ b/jdk/test/javax/security/auth/Subject/Generic.java	Tue Jul 01 09:46:20 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2014 Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,7 +28,7 @@
  */
 
 import java.security.*;
-import javax.security.auth.*;
+import javax.security.auth.Subject;
 
 public class Generic {
     public static void main(String[] args) throws Exception {
--- a/jdk/test/javax/security/auth/Subject/Serial.java	Tue Jul 01 14:44:37 2014 +0100
+++ b/jdk/test/javax/security/auth/Subject/Serial.java	Tue Jul 01 09:46:20 2014 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2002, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,7 +29,7 @@
  * @run main/othervm/policy=Serial.policy Serial
  */
 
-import javax.security.auth.*;
+import javax.security.auth.Subject;
 import java.io.*;
 import java.util.*;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/Subject/Subject.java	Tue Jul 01 09:46:20 2014 -0700
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * An implementation of the Subject class that provides basic functionality
+ * for the construction of Subject objects with null Principal elements.
+ * This is a helper class for serialization tests tied to bug 8015081
+ * (see SubjectNullTests.java).
+ */
+package jjjjj.security.auth;
+
+import sun.misc.HexDumpEncoder;
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.x500.X500Principal;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamField;
+import java.lang.Exception;
+import java.lang.RuntimeException;
+import java.security.Principal;
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+import java.io.FileOutputStream;
+
+public class Subject implements java.io.Serializable {
+
+    private static final long serialVersionUID = -8308522755600156056L;
+
+    Set<Principal> principals;
+    private volatile boolean readOnly = false;
+    private static final int PRINCIPAL_SET = 1;
+
+    public Subject(Set<? extends Principal> principals) {
+        this.principals = Collections.synchronizedSet(new SecureSet<Principal>
+                (this, PRINCIPAL_SET, principals));
+    }
+
+    public Set<Principal> getPrincipals() {
+        return principals;
+    }
+
+    private static class SecureSet<E>
+            extends AbstractSet<E>
+            implements java.io.Serializable {
+
+        private static final long serialVersionUID = 7911754171111800359L;
+        private static final ObjectStreamField[] serialPersistentFields = {
+                new ObjectStreamField("this$0", Subject.class),
+                new ObjectStreamField("elements", LinkedList.class),
+                new ObjectStreamField("which", int.class)
+        };
+
+        Subject subject;
+        LinkedList<E> elements;
+        private int which;
+
+        SecureSet(Subject subject, int which, Set<? extends E> set) {
+            this.subject = subject;
+            this.which = which;
+            this.elements = new LinkedList<E>(set);
+        }
+
+        public Iterator<E> iterator() {
+            return elements.iterator();
+        }
+
+        public int size() {
+            return elements.size();
+        }
+
+        private void writeObject(java.io.ObjectOutputStream oos)
+                throws java.io.IOException {
+
+            ObjectOutputStream.PutField fields = oos.putFields();
+            fields.put("this$0", subject);
+            fields.put("elements", elements);
+            fields.put("which", which);
+            oos.writeFields();
+        }
+    }
+
+    public static byte[] enc(Object obj) {
+        try {
+            HexDumpEncoder hex = new HexDumpEncoder();
+            ByteArrayOutputStream bout;
+            bout = new ByteArrayOutputStream();
+            new ObjectOutputStream(bout).writeObject(obj);
+            byte[] data = bout.toByteArray();
+            for (int i = 0; i < data.length - 5; i++) {
+                if (data[i] == 'j' && data[i + 1] == 'j' && data[i + 2] == 'j'
+                        && data[i + 3] == 'j' && data[i + 4] == 'j') {
+                    System.arraycopy("javax".getBytes(), 0, data, i, 5);
+                }
+            }
+            return data;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/security/auth/Subject/SubjectNullTests.java	Tue Jul 01 09:46:20 2014 -0700
@@ -0,0 +1,852 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8015081
+ * @compile Subject.java
+ * @compile SubjectNullTests.java
+ * @build SubjectNullTests
+ * @run main SubjectNullTests
+ * @summary javax.security.auth.Subject.toString() throws NPE
+ */
+
+import java.io.File;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.ObjectInputStream;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+public class SubjectNullTests {
+
+    // Value templates for the constructor
+    private static Principal[] princVals = {
+        new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US"),
+        new JMXPrincipal("Huckleberry Finn"),
+        new KerberosPrincipal("mtwain/author@LITERATURE.US")
+    };
+    private static String[] pubVals = {"tsawyer", "hfinn", "mtwain"};
+    private static String[] privVals = {"th3R!v3r", "oNth3R4ft", "5Cl3M3nz"};
+
+    // Templates for collection-based modifiers for the Subject
+    private static Principal[] tmplAddPrincs = {
+        new X500Principal("CN=John Doe, O=Bogus Corp."),
+        new KerberosPrincipal("jdoe/admin@BOGUSCORP.COM")
+    };
+    private static String[] tmplAddPubVals = {"jdoe", "djoe"};
+    private static String[] tmplAddPrvVals = {"b4dpa55w0rd", "pass123"};
+
+    /**
+     * Byte arrays used for deserialization:
+     * These byte arrays contain serialized Subjects and SecureSets,
+     * either with or without nulls.  These use
+     * jjjjj.security.auth.Subject, which is a modified Subject
+     * implementation that allows the addition of null elements
+     */
+    private static final byte[] SUBJ_NO_NULL =
+        jjjjj.security.auth.Subject.enc(makeSubjWithNull(false));
+    private static final byte[] SUBJ_WITH_NULL =
+        jjjjj.security.auth.Subject.enc(makeSubjWithNull(true));
+    private static final byte[] PRIN_NO_NULL =
+        jjjjj.security.auth.Subject.enc(makeSecSetWithNull(false));
+    private static final byte[] PRIN_WITH_NULL =
+        jjjjj.security.auth.Subject.enc(makeSecSetWithNull(true));
+
+    /**
+     * Method to allow creation of a subject that can optionally
+     * insert a null reference into the principals Set.
+     */
+    private static jjjjj.security.auth.Subject makeSubjWithNull(
+            boolean nullPrinc) {
+        Set<Principal> setPrinc = new HashSet<>(Arrays.asList(princVals));
+        if (nullPrinc) {
+            setPrinc.add(null);
+        }
+
+        return (new jjjjj.security.auth.Subject(setPrinc));
+    }
+
+    /**
+     * Method to allow creation of a SecureSet that can optionally
+     * insert a null reference.
+     */
+    private static Set<Principal> makeSecSetWithNull(boolean nullPrinc) {
+        Set<Principal> setPrinc = new HashSet<>(Arrays.asList(princVals));
+        if (nullPrinc) {
+            setPrinc.add(null);
+        }
+
+        jjjjj.security.auth.Subject subj =
+            new jjjjj.security.auth.Subject(setPrinc);
+
+        return subj.getPrincipals();
+    }
+
+    /**
+     * Construct a subject, and optionally place a null in any one
+     * of the three Sets used to initialize a Subject's values
+     */
+    private static Subject makeSubj(boolean nullPrinc, boolean nullPub,
+                             boolean nullPriv) {
+        Set<Principal> setPrinc = new HashSet<>(Arrays.asList(princVals));
+        Set<String> setPubCreds = new HashSet<>(Arrays.asList(pubVals));
+        Set<String> setPrvCreds = new HashSet<>(Arrays.asList(privVals));
+
+        if (nullPrinc) {
+            setPrinc.add(null);
+        }
+
+        if (nullPub) {
+            setPubCreds.add(null);
+        }
+
+        if (nullPriv) {
+            setPrvCreds.add(null);
+        }
+
+        return (new Subject(false, setPrinc, setPubCreds, setPrvCreds));
+    }
+
+    /**
+     * Provide a simple interface for abstracting collection-on-collection
+     * functions
+     */
+    public interface Function {
+        boolean execCollection(Set<?> subjSet, Collection<?> actorData);
+    }
+
+    public static final Function methAdd = new Function() {
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean execCollection(Set<?> subjSet, Collection<?> actorData) {
+            return subjSet.addAll((Collection)actorData);
+        }
+    };
+
+    public static final Function methContains = new Function() {
+        @Override
+        public boolean execCollection(Set<?> subjSet, Collection<?> actorData) {
+            return subjSet.containsAll(actorData);
+        }
+    };
+
+    public static final Function methRemove = new Function() {
+        @Override
+        public boolean execCollection(Set<?> subjSet, Collection<?> actorData) {
+            return subjSet.removeAll(actorData);
+        }
+    };
+
+    public static final Function methRetain = new Function() {
+        @Override
+        public boolean execCollection(Set<?> subjSet, Collection<?> actorData) {
+            return subjSet.retainAll(actorData);
+        }
+    };
+
+    /**
+     * Run a test using a specified Collection method upon a Subject's
+     * SecureSet fields. This method expects NullPointerExceptions
+     * to be thrown, and throws RuntimeException when the operation
+     * succeeds
+     */
+    private static void nullTestCollection(Function meth, Set<?> subjSet,
+           Collection<?> actorData) {
+        try {
+            meth.execCollection(subjSet, actorData);
+            throw new RuntimeException("Failed to throw NullPointerException");
+        } catch (NullPointerException npe) {
+            System.out.println("Caught expected NullPointerException [PASS]");
+        }
+    }
+
+    /**
+     * Run a test using a specified Collection method upon a Subject's
+     * SecureSet fields. This method expects the function and arguments
+     * passed in to complete without exception.  It returns false
+     * if either an exception occurs or the result of the operation is
+     * false.
+     */
+    private static boolean validTestCollection(Function meth, Set<?> subjSet,
+           Collection<?> actorData) {
+        boolean result = false;
+
+        try {
+            result = meth.execCollection(subjSet, actorData);
+        } catch (Exception exc) {
+            System.out.println("Caught exception " + exc);
+        }
+
+        return result;
+    }
+
+    /**
+     * Deserialize an object from a byte array.
+     *
+     * @param type The {@code Class} that the serialized file is supposed
+     *             to contain.
+     * @param serBuffer The byte array containing the serialized object data
+     *
+     * @return An object of the type specified in the {@code type} parameter
+     */
+    private static <T> T deserializeBuffer(Class<T> type, byte[] serBuffer)
+            throws IOException, ClassNotFoundException {
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(serBuffer);
+        ObjectInputStream ois = new ObjectInputStream(bis);
+
+        T newObj = type.cast(ois.readObject());
+        ois.close();
+        bis.close();
+
+        return newObj;
+    }
+
+    private static void testCTOR() {
+        System.out.println("------ constructor ------");
+
+        try {
+            // Case 1: Create a subject with a null principal
+            // Expected result: NullPointerException
+            Subject mtSubj = makeSubj(true, false, false);
+            throw new RuntimeException(
+                    "constructor [principal w/ null]: Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println("constructor [principal w/ null]: " +
+                    "NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 2: Create a subject with a null public credential element
+            // Expected result: NullPointerException
+            Subject mtSubj = makeSubj(false, true, false);
+            throw new RuntimeException(
+                    "constructor [pub cred w/ null]: Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println("constructor [pub cred w/ null]: " +
+                    "NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 3: Create a subject with a null private credential element
+            // Expected result: NullPointerException
+            Subject mtSubj = makeSubj(false, false, true);
+            throw new RuntimeException(
+                    "constructor [priv cred w/ null]: Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println("constructor [priv cred w/ null]: " +
+                    "NullPointerException [PASS]");
+        }
+
+        // Case 4: Create a new subject using the principals, public
+        // and private credentials from another well-formed subject
+        // Expected result: Successful construction
+        Subject srcSubj = makeSubj(false, false, false);
+        Subject mtSubj = new Subject(false, srcSubj.getPrincipals(),
+                srcSubj.getPublicCredentials(),
+                srcSubj.getPrivateCredentials());
+        System.out.println("Construction from another well-formed Subject's " +
+                "principals/creds [PASS]");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static void testDeserialize() throws Exception {
+        System.out.println("------ deserialize -----");
+
+        Subject subj = null;
+        Set<Principal> prin = null;
+
+        // Case 1: positive deserialization test of a Subject
+        // Expected result: well-formed Subject
+        subj = deserializeBuffer(Subject.class, SUBJ_NO_NULL);
+        System.out.println("Positive deserialization test (Subject) passed");
+
+        // Case 2: positive deserialization test of a SecureSet
+        // Expected result: well-formed Set
+        prin = deserializeBuffer(Set.class, PRIN_NO_NULL);
+        System.out.println("Positive deserialization test (SecureSet) passed");
+
+        System.out.println(
+                "* Testing deserialization with null-poisoned objects");
+        // Case 3: deserialization test of a null-poisoned Subject
+        // Expected result: NullPointerException
+        try {
+            subj = deserializeBuffer(Subject.class, SUBJ_WITH_NULL);
+            throw new RuntimeException("Failed to throw NullPointerException");
+        } catch (NullPointerException npe) {
+            System.out.println("Caught expected NullPointerException [PASS]");
+        }
+
+        // Case 4: deserialization test of a null-poisoned SecureSet
+        // Expected result: NullPointerException
+        try {
+            prin = deserializeBuffer(Set.class, PRIN_WITH_NULL);
+            throw new RuntimeException("Failed to throw NullPointerException");
+        } catch (NullPointerException npe) {
+            System.out.println("Caught expected NullPointerException [PASS]");
+        }
+    }
+
+    private static void testAdd() {
+        System.out.println("------ add() ------");
+        // Create a well formed subject
+        Subject mtSubj = makeSubj(false, false, false);
+
+        try {
+            // Case 1: Attempt to add null values to principal
+            // Expected result: NullPointerException
+            mtSubj.getPrincipals().add(null);
+            throw new RuntimeException(
+                    "PRINCIPAL add(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PRINCIPAL add(null): NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 2: Attempt to add null into the public creds
+            // Expected result: NullPointerException
+            mtSubj.getPublicCredentials().add(null);
+            throw new RuntimeException(
+                    "PUB CRED add(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PUB CRED add(null): NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 3: Attempt to add null into the private creds
+            // Expected result: NullPointerException
+            mtSubj.getPrivateCredentials().add(null);
+            throw new RuntimeException(
+                    "PRIV CRED add(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PRIV CRED add(null): NullPointerException [PASS]");
+        }
+    }
+
+    private static void testRemove() {
+        System.out.println("------ remove() ------");
+        // Create a well formed subject
+        Subject mtSubj = makeSubj(false, false, false);
+
+        try {
+            // Case 1: Attempt to remove null values from principal
+            // Expected result: NullPointerException
+            mtSubj.getPrincipals().remove(null);
+            throw new RuntimeException(
+                    "PRINCIPAL remove(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PRINCIPAL remove(null): NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 2: Attempt to remove null from the public creds
+            // Expected result: NullPointerException
+            mtSubj.getPublicCredentials().remove(null);
+            throw new RuntimeException(
+                    "PUB CRED remove(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PUB CRED remove(null): NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 3: Attempt to remove null from the private creds
+            // Expected result: NullPointerException
+            mtSubj.getPrivateCredentials().remove(null);
+            throw new RuntimeException(
+                    "PRIV CRED remove(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PRIV CRED remove(null): NullPointerException [PASS]");
+        }
+    }
+
+    private static void testContains() {
+        System.out.println("------ contains() ------");
+        // Create a well formed subject
+        Subject mtSubj = makeSubj(false, false, false);
+
+        try {
+            // Case 1: Attempt to check for null values in principals
+            // Expected result: NullPointerException
+            mtSubj.getPrincipals().contains(null);
+            throw new RuntimeException(
+                    "PRINCIPAL contains(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PRINCIPAL contains(null): NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 2: Attempt to check for null in public creds
+            // Expected result: NullPointerException
+            mtSubj.getPublicCredentials().contains(null);
+            throw new RuntimeException(
+                    "PUB CRED contains(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PUB CRED contains(null): NullPointerException [PASS]");
+        }
+
+        try {
+            // Case 3: Attempt to check for null in private creds
+            // Expected result: NullPointerException
+            mtSubj.getPrivateCredentials().contains(null);
+            throw new RuntimeException(
+                    "PRIV CRED contains(null): Failed to throw NPE");
+        } catch (NullPointerException npe) {
+            System.out.println(
+                    "PRIV CRED contains(null): NullPointerException [PASS]");
+        }
+    }
+
+    private static void testAddAll() {
+        // Create a well formed subject and additional collections
+        Subject mtSubj = makeSubj(false, false, false);
+        Set<Principal> morePrincs = new HashSet<>(Arrays.asList(tmplAddPrincs));
+        Set<Object> morePubVals = new HashSet<>(Arrays.asList(tmplAddPubVals));
+        Set<Object> morePrvVals = new HashSet<>(Arrays.asList(tmplAddPrvVals));
+
+        // Run one success test for each Subject family to verify the
+        // overloaded method works as intended.
+        Set<Principal> setPrin = mtSubj.getPrincipals();
+        Set<Object> setPubCreds = mtSubj.getPublicCredentials();
+        Set<Object> setPrvCreds = mtSubj.getPrivateCredentials();
+        int prinOrigSize = setPrin.size();
+        int pubOrigSize = setPubCreds.size();
+        int prvOrigSize = setPrvCreds.size();
+
+        System.out.println("------ addAll() -----");
+
+        // Add the new members, then check the resulting size of the
+        // Subject attributes to verify they've increased by the proper
+        // amounts.
+        if ((validTestCollection(methAdd, setPrin, morePrincs) != true) ||
+            (setPrin.size() != prinOrigSize + morePrincs.size()))
+        {
+            throw new RuntimeException("Failed addAll() on principals");
+        }
+        if ((validTestCollection(methAdd, setPubCreds,
+                morePubVals) != true) ||
+            (setPubCreds.size() != pubOrigSize + morePubVals.size()))
+        {
+            throw new RuntimeException("Failed addAll() on public creds");
+        }
+        if ((validTestCollection(methAdd, setPrvCreds,
+                morePrvVals) != true) ||
+            (setPrvCreds.size() != prvOrigSize + morePrvVals.size()))
+        {
+            throw new RuntimeException("Failed addAll() on private creds");
+        }
+        System.out.println("Positive addAll() test passed");
+
+        // Now add null elements into each container, then retest
+        morePrincs.add(null);
+        morePubVals.add(null);
+        morePrvVals.add(null);
+
+        System.out.println("* Testing addAll w/ null values on Principals");
+        nullTestCollection(methAdd, mtSubj.getPrincipals(), null);
+        nullTestCollection(methAdd, mtSubj.getPrincipals(), morePrincs);
+
+        System.out.println("* Testing addAll w/ null values on Public Creds");
+        nullTestCollection(methAdd, mtSubj.getPublicCredentials(), null);
+        nullTestCollection(methAdd, mtSubj.getPublicCredentials(),
+                morePubVals);
+
+        System.out.println("* Testing addAll w/ null values on Private Creds");
+        nullTestCollection(methAdd, mtSubj.getPrivateCredentials(), null);
+        nullTestCollection(methAdd, mtSubj.getPrivateCredentials(),
+                morePrvVals);
+    }
+
+    private static void testRemoveAll() {
+        // Create a well formed subject and additional collections
+        Subject mtSubj = makeSubj(false, false, false);
+        Set<Principal> remPrincs = new HashSet<>();
+        Set<Object> remPubVals = new HashSet<>();
+        Set<Object> remPrvVals = new HashSet<>();
+
+        remPrincs.add(new KerberosPrincipal("mtwain/author@LITERATURE.US"));
+        remPubVals.add("mtwain");
+        remPrvVals.add("5Cl3M3nz");
+
+        // Run one success test for each Subject family to verify the
+        // overloaded method works as intended.
+        Set<Principal> setPrin = mtSubj.getPrincipals();
+        Set<Object> setPubCreds = mtSubj.getPublicCredentials();
+        Set<Object> setPrvCreds = mtSubj.getPrivateCredentials();
+        int prinOrigSize = setPrin.size();
+        int pubOrigSize = setPubCreds.size();
+        int prvOrigSize = setPrvCreds.size();
+
+        System.out.println("------ removeAll() -----");
+
+        // Remove the specified members, then check the resulting size of the
+        // Subject attributes to verify they've decreased by the proper
+        // amounts.
+        if ((validTestCollection(methRemove, setPrin, remPrincs) != true) ||
+            (setPrin.size() != prinOrigSize - remPrincs.size()))
+        {
+            throw new RuntimeException("Failed removeAll() on principals");
+        }
+        if ((validTestCollection(methRemove, setPubCreds,
+                remPubVals) != true) ||
+            (setPubCreds.size() != pubOrigSize - remPubVals.size()))
+        {
+            throw new RuntimeException("Failed removeAll() on public creds");
+        }
+        if ((validTestCollection(methRemove, setPrvCreds,
+                remPrvVals) != true) ||
+            (setPrvCreds.size() != prvOrigSize - remPrvVals.size()))
+        {
+            throw new RuntimeException("Failed removeAll() on private creds");
+        }
+        System.out.println("Positive removeAll() test passed");
+
+        // Now add null elements into each container, then retest
+        remPrincs.add(null);
+        remPubVals.add(null);
+        remPrvVals.add(null);
+
+        System.out.println("* Testing removeAll w/ null values on Principals");
+        nullTestCollection(methRemove, mtSubj.getPrincipals(), null);
+        nullTestCollection(methRemove, mtSubj.getPrincipals(), remPrincs);
+
+        System.out.println(
+                "* Testing removeAll w/ null values on Public Creds");
+        nullTestCollection(methRemove, mtSubj.getPublicCredentials(), null);
+        nullTestCollection(methRemove, mtSubj.getPublicCredentials(),
+                remPubVals);
+
+        System.out.println(
+                "* Testing removeAll w/ null values on Private Creds");
+        nullTestCollection(methRemove, mtSubj.getPrivateCredentials(), null);
+        nullTestCollection(methRemove, mtSubj.getPrivateCredentials(),
+                remPrvVals);
+    }
+
+    private static void testContainsAll() {
+        // Create a well formed subject and additional collections
+        Subject mtSubj = makeSubj(false, false, false);
+        Set<Principal> testPrincs = new HashSet<>(Arrays.asList(princVals));
+        Set<Object> testPubVals = new HashSet<>(Arrays.asList(pubVals));
+        Set<Object> testPrvVals = new HashSet<>(Arrays.asList(privVals));
+
+        System.out.println("------ containsAll() -----");
+
+        // Run one success test for each Subject family to verify the
+        // overloaded method works as intended.
+        if ((validTestCollection(methContains, mtSubj.getPrincipals(),
+                 testPrincs) == false) &&
+            (validTestCollection(methContains, mtSubj.getPublicCredentials(),
+                 testPubVals) == false) &&
+            (validTestCollection(methContains,
+                 mtSubj.getPrivateCredentials(), testPrvVals) == false)) {
+            throw new RuntimeException("Valid containsAll() check failed");
+        }
+        System.out.println("Positive containsAll() test passed");
+
+        // Now let's add a null into each collection and watch the fireworks.
+        testPrincs.add(null);
+        testPubVals.add(null);
+        testPrvVals.add(null);
+
+        System.out.println(
+                "* Testing containsAll w/ null values on Principals");
+        nullTestCollection(methContains, mtSubj.getPrincipals(), null);
+        nullTestCollection(methContains, mtSubj.getPrincipals(), testPrincs);
+
+        System.out.println(
+                "* Testing containsAll w/ null values on Public Creds");
+        nullTestCollection(methContains, mtSubj.getPublicCredentials(),
+                null);
+        nullTestCollection(methContains, mtSubj.getPublicCredentials(),
+                testPubVals);
+
+        System.out.println(
+                "* Testing containsAll w/ null values on Private Creds");
+        nullTestCollection(methContains, mtSubj.getPrivateCredentials(),
+                null);
+        nullTestCollection(methContains, mtSubj.getPrivateCredentials(),
+                testPrvVals);
+    }
+
+    private static void testRetainAll() {
+        // Create a well formed subject and additional collections
+        Subject mtSubj = makeSubj(false, false, false);
+        Set<Principal> remPrincs = new HashSet<>(Arrays.asList(tmplAddPrincs));
+        Set<Object> remPubVals = new HashSet<>(Arrays.asList(tmplAddPubVals));
+        Set<Object> remPrvVals = new HashSet<>(Arrays.asList(tmplAddPrvVals));
+
+        // Add in values that exist within the Subject
+        remPrincs.add(princVals[2]);
+        remPubVals.add(pubVals[2]);
+        remPrvVals.add(privVals[2]);
+
+        // Run one success test for each Subject family to verify the
+        // overloaded method works as intended.
+        Set<Principal> setPrin = mtSubj.getPrincipals();
+        Set<Object> setPubCreds = mtSubj.getPublicCredentials();
+        Set<Object> setPrvCreds = mtSubj.getPrivateCredentials();
+        int prinOrigSize = setPrin.size();
+        int pubOrigSize = setPubCreds.size();
+        int prvOrigSize = setPrvCreds.size();
+
+        System.out.println("------ retainAll() -----");
+
+        // Retain the specified members (those that exist in the Subject)
+        // and validate the results.
+        if (validTestCollection(methRetain, setPrin, remPrincs) == false ||
+            setPrin.size() != 1 || setPrin.contains(princVals[2]) == false)
+        {
+            throw new RuntimeException("Failed retainAll() on principals");
+        }
+
+        if (validTestCollection(methRetain, setPubCreds,
+                remPubVals) == false ||
+            setPubCreds.size() != 1 ||
+            setPubCreds.contains(pubVals[2]) == false)
+        {
+            throw new RuntimeException("Failed retainAll() on public creds");
+        }
+        if (validTestCollection(methRetain, setPrvCreds,
+                remPrvVals) == false ||
+            setPrvCreds.size() != 1 ||
+            setPrvCreds.contains(privVals[2]) == false)
+        {
+            throw new RuntimeException("Failed retainAll() on private creds");
+        }
+        System.out.println("Positive retainAll() test passed");
+
+        // Now add null elements into each container, then retest
+        remPrincs.add(null);
+        remPubVals.add(null);
+        remPrvVals.add(null);
+
+        System.out.println("* Testing retainAll w/ null values on Principals");
+        nullTestCollection(methRetain, mtSubj.getPrincipals(), null);
+        nullTestCollection(methRetain, mtSubj.getPrincipals(), remPrincs);
+
+        System.out.println(
+                "* Testing retainAll w/ null values on Public Creds");
+        nullTestCollection(methRetain, mtSubj.getPublicCredentials(), null);
+        nullTestCollection(methRetain, mtSubj.getPublicCredentials(),
+                remPubVals);
+
+        System.out.println(
+                "* Testing retainAll w/ null values on Private Creds");
+        nullTestCollection(methRetain, mtSubj.getPrivateCredentials(), null);
+        nullTestCollection(methRetain, mtSubj.getPrivateCredentials(),
+                remPrvVals);
+    }
+
+    private static void testIsEmpty() {
+        Subject populatedSubj = makeSubj(false, false, false);
+        Subject emptySubj = new Subject();
+
+        System.out.println("------ isEmpty() -----");
+
+        if (populatedSubj.getPrincipals().isEmpty()) {
+            throw new RuntimeException(
+                    "Populated Subject Principals incorrectly returned empty");
+        }
+        if (emptySubj.getPrincipals().isEmpty() == false) {
+            throw new RuntimeException(
+                    "Empty Subject Principals incorrectly returned non-empty");
+        }
+        System.out.println("isEmpty() test passed");
+    }
+
+    private static void testSecureSetEquals() {
+        System.out.println("------ SecureSet.equals() -----");
+
+        Subject subj = makeSubj(false, false, false);
+        Subject subjComp = makeSubj(false, false, false);
+
+        // Case 1: null comparison [expect false]
+        if (subj.getPublicCredentials().equals(null) != false) {
+            throw new RuntimeException(
+                    "equals(null) incorrectly returned true");
+        }
+
+        // Case 2: Self-comparison [expect true]
+        Set<Principal> princs = subj.getPrincipals();
+        princs.equals(subj.getPrincipals());
+
+        // Case 3: Comparison with non-Set type [expect false]
+        List<Principal> listPrinc = new LinkedList<>(Arrays.asList(princVals));
+        if (subj.getPublicCredentials().equals(listPrinc) != false) {
+            throw new RuntimeException(
+                    "equals([Non-Set]) incorrectly returned true");
+        }
+
+        // Case 4: SecureSets of differing sizes [expect false]
+        Subject subj1princ = new Subject();
+        Subject subj2princ = new Subject();
+        subj1princ.getPrincipals().add(
+                new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US"));
+        subj1princ.getPrincipals().add(
+                new X500Principal("CN=John Doe, O=Bogus Corp."));
+        subj2princ.getPrincipals().add(
+                new X500Principal("CN=Tom Sawyer, ST=Missouri, C=US"));
+        if (subj1princ.getPrincipals().equals(
+                    subj2princ.getPrincipals()) != false) {
+            throw new RuntimeException(
+                    "equals([differing sizes]) incorrectly returned true");
+        }
+
+        // Case 5: Content equality test [expect true]
+        Set<Principal> equalSet = new HashSet<>(Arrays.asList(princVals));
+        if (subj.getPrincipals().equals(equalSet) != true) {
+            throw new RuntimeException(
+                    "equals([equivalent set]) incorrectly returned false");
+        }
+
+        // Case 5: Content inequality test [expect false]
+        // Note: to not fall into the size inequality check the two
+        // sets need to have the same number of elements.
+        Set<Principal> inequalSet =
+            new HashSet<Principal>(Arrays.asList(tmplAddPrincs));
+        inequalSet.add(new JMXPrincipal("Samuel Clemens"));
+
+        if (subj.getPrincipals().equals(inequalSet) != false) {
+            throw new RuntimeException(
+                    "equals([equivalent set]) incorrectly returned false");
+        }
+        System.out.println("SecureSet.equals() tests passed");
+    }
+
+    private static void testSecureSetHashCode() {
+        System.out.println("------ SecureSet.hashCode() -----");
+
+        Subject subj = makeSubj(false, false, false);
+
+        // Make sure two other Set types that we know are equal per
+        // SecureSet.equals() and verify their hashCodes are also the same
+        Set<Principal> equalHashSet = new HashSet<>(Arrays.asList(princVals));
+
+        if (subj.getPrincipals().hashCode() != equalHashSet.hashCode()) {
+            throw new RuntimeException(
+                    "SecureSet and HashSet hashCodes() differ");
+        }
+        System.out.println("SecureSet.hashCode() tests passed");
+    }
+
+    private static void testToArray() {
+        System.out.println("------ toArray() -----");
+
+        Subject subj = makeSubj(false, false, false);
+
+        // Case 1: no-parameter toArray with equality comparison
+        // Expected result: true
+        List<Object> alSubj = Arrays.asList(subj.getPrincipals().toArray());
+        List<Principal> alPrincs = Arrays.asList(princVals);
+
+        if (alSubj.size() != alPrincs.size() ||
+                alSubj.containsAll(alPrincs) != true) {
+            throw new RuntimeException(
+                    "Unexpected inequality on returned toArray()");
+        }
+
+        // Case 2: generic-type toArray where passed array is of sufficient
+        //         size.
+        // Expected result: returned Array is reference-equal to input param
+        // and content equal to data used to construct the originating Subject.
+        Principal[] pBlock = new Principal[3];
+        Principal[] pBlockRef = subj.getPrincipals().toArray(pBlock);
+        alSubj = Arrays.asList((Object[])pBlockRef);
+
+        if (pBlockRef != pBlock) {
+            throw new RuntimeException(
+                    "Unexpected reference-inequality on returned toArray(T[])");
+        } else if (alSubj.size() != alPrincs.size() ||
+                alSubj.containsAll(alPrincs) != true) {
+            throw new RuntimeException(
+                    "Unexpected content-inequality on returned toArray(T[])");
+        }
+
+        // Case 3: generic-type toArray where passed array is of
+        //         insufficient size.
+        // Expected result: returned Array is not reference-equal to
+        // input param but is content equal to data used to construct the
+        // originating Subject.
+        pBlock = new Principal[1];
+        pBlockRef = subj.getPrincipals().toArray(pBlock);
+        alSubj = Arrays.asList((Object[])pBlockRef);
+
+        if (pBlockRef == pBlock) {
+            throw new RuntimeException(
+                    "Unexpected reference-equality on returned toArray(T[])");
+        } else if (alSubj.size() != alPrincs.size() ||
+                alSubj.containsAll(alPrincs) != true) {
+            throw new RuntimeException(
+                    "Unexpected content-inequality on returned toArray(T[])");
+        }
+        System.out.println("toArray() tests passed");
+    }
+
+    public static void main(String[] args) throws Exception {
+
+        testCTOR();
+
+        testDeserialize();
+
+        testAdd();
+
+        testRemove();
+
+        testContains();
+
+        testAddAll();
+
+        testRemoveAll();
+
+        testContainsAll();
+
+        testRetainAll();
+
+        testIsEmpty();
+
+        testSecureSetEquals();
+
+        testSecureSetHashCode();
+
+        testToArray();
+    }
+}