import
authoradam <adam@megacz.com>
Tue, 16 Mar 2004 05:00:05 +0000 (05:00 +0000)
committeradam <adam@megacz.com>
Tue, 16 Mar 2004 05:00:05 +0000 (05:00 +0000)
darcs-hash:20040316050005-5007d-d3b0594e35ba6c6edf7b86abf5a5dcb5b471fa9c.gz

lib/netscape.jar [new file with mode: 0644]
src/org/xwt/plat/Win32-dll.cc [new file with mode: 0644]
src/org/xwt/plat/Win32.def [new file with mode: 0644]
src/org/xwt/plat/Win32.inf [new file with mode: 0644]
src/org/xwt/shoehorn/ShoeHorn.java [new file with mode: 0644]
src/org/xwt/shoehorn/ShoeHorn1.java [new file with mode: 0644]
src/org/xwt/shoehorn2/ShoeHorn.java [new file with mode: 0644]
src/org/xwt/shoehorn3/Crypto.java [new file with mode: 0644]
src/org/xwt/shoehorn3/ShoeHorn.java [new file with mode: 0644]

diff --git a/lib/netscape.jar b/lib/netscape.jar
new file mode 100644 (file)
index 0000000..b1a72b2
Binary files /dev/null and b/lib/netscape.jar differ
diff --git a/src/org/xwt/plat/Win32-dll.cc b/src/org/xwt/plat/Win32-dll.cc
new file mode 100644 (file)
index 0000000..ff823e5
--- /dev/null
@@ -0,0 +1,307 @@
+// Copyright 2002 Adam Megacz, ALL RIGHTS RESERVED
+
+//
+// A simple DLL to invoke xwt.exe and pass it any arguments found in the <object/> tag
+//
+
+#include <windows.h>
+#include <initguid.h>
+#include <objbase.h>
+#include <oaidl.h>
+#include <oleauto.h>
+#include <olectl.h>
+#include <unknwn.h>
+
+
+// Globals ////////////////////////////////////////////////////////////////////////
+
+using namespace std;
+
+#define CLSID_STRING_SIZE 39
+
+const char XWT_friendlyName[] = "XWT ActiveX Control (build " BUILDID ")";
+const char XWT_versionIndependantProgramID[] = "XWT.ActiveX";
+const char XWT_programID[] = "XWT.ActiveX (build " BUILDID ")";
+extern "C" const CLSID XWT_clsid = CLSID_STRUCT;
+static HMODULE g_hModule = NULL;    //DLL handle
+
+
+
+
+// Superclasses ////////////////////////////////////////////////////////////////////////
+
+// Option bit definitions for IObjectSafety:
+#define INTERFACESAFE_FOR_UNTRUSTED_CALLER  0x00000001  // Caller of interface may be untrusted
+#define INTERFACESAFE_FOR_UNTRUSTED_DATA    0x00000002  // Data passed into interface may be untrusted
+
+// {CB5BDC81-93C1-11cf-8F20-00805F2CD064}
+//DEFINE_GUID(IID_IObjectSafety, 0xcb5bdc81, 0x93c1, 0x11cf, 0x8f, 0x20, 0x0, 0x80, 0x5f, 0x2c, 0xd0, 0x64);
+extern "C" const CLSID IID_IObjectSafety;
+
+interface IObjectSafety : public IUnknown {
+ public:
+    virtual HRESULT __stdcall GetInterfaceSafetyOptions(REFIID riid, DWORD __RPC_FAR *pdwSupportedOptions, DWORD __RPC_FAR *pdwEnabledOptions) = 0;
+    virtual HRESULT __stdcall SetInterfaceSafetyOptions(REFIID riid, DWORD dwOptionSetMask, DWORD dwEnabledOptions) = 0;
+};
+
+interface IShoeHorn : IPersistPropertyBag, IObjectSafety { };
+
+
+
+// Entry Points ////////////////////////////////////////////////////////////////////////
+
+// to get mingw to stop nagging me
+int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { }
+
+// determines whether or not the DLL can be unloaded; always allow this since we don't do too much
+STDAPI __declspec(dllexport) DllCanUnloadNow(void) { return S_OK; }
+
+extern "C" __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID /*lpReserved*/) {
+    if (dwReason == DLL_PROCESS_ATTACH) g_hModule = (HINSTANCE)hModule;
+    return TRUE;
+}
+
+
+
+// Other ///////////////////////////////////////////////////////////////////////////////////
+
+// simple assert() replacement that pops open a message box if there are errors
+void check(int val, char* message) {
+    if (!val) {
+        MessageBox (NULL, message, "XWT Critical Abort", MB_OK | MB_ICONEXCLAMATION  | MB_TASKMODAL | MB_SETFOREGROUND);
+        exit(-1);
+    }
+}
+
+void clsidToString(const CLSID& clsid, char* str, int length) {
+    check(length >= CLSID_STRING_SIZE, "clsidToString(): string too short");
+       LPOLESTR wide_str = NULL;
+       HRESULT hr = StringFromCLSID(clsid, &wide_str);
+       check(SUCCEEDED(hr), "StringFromCLSID() failed in clsidToString()");
+       wcstombs(str, wide_str, length);
+       CoTaskMemFree(wide_str);
+}
+
+void setRegistryKey(const char* key, const char* subkey, const char* value) {
+       HKEY hKey;
+       char keyBuf[1024];
+       strcpy(keyBuf, key);
+       if (subkey != NULL) {
+               strcat(keyBuf, "\\");
+               strcat(keyBuf, subkey );
+       }
+       long lResult = RegCreateKeyEx(HKEY_CLASSES_ROOT, keyBuf, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL);
+       if (value != NULL)
+        check(RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)value, strlen(value) + 1) == ERROR_SUCCESS,
+              "RegSetValueEx() failed in setRegistryKey()");
+       RegCloseKey(hKey);
+}
+
+void deleteRegistryKey(HKEY parent, const char* target) {
+       HKEY hKeyChild;
+       RegOpenKeyEx(parent, target, 0, KEY_ALL_ACCESS, &hKeyChild);
+
+       // Iterate over children, deleting them
+       FILETIME time;
+       char szBuffer[256];
+       DWORD dwSize = 256;
+       while (RegEnumKeyEx(hKeyChild, 0, szBuffer, &dwSize, NULL, NULL, NULL, &time) == S_OK) {
+               deleteRegistryKey(hKeyChild, szBuffer);
+               dwSize = 256;
+       }
+
+       RegCloseKey(hKeyChild);
+       RegDeleteKey(parent, target);
+}
+
+STDAPI __declspec(dllexport) DllRegisterServer(void) {
+       char moduleName[512];
+    HRESULT result = GetModuleFileName(g_hModule, moduleName, sizeof(moduleName)/sizeof(char));
+       check(result, "GetModuleFileName() failed in RegisterServer()");
+
+       char clsidString[CLSID_STRING_SIZE];
+       clsidToString(XWT_clsid, clsidString, sizeof(clsidString));
+
+       // build the key
+       char key[64];
+       strcpy(key, "CLSID\\");
+       strcat(key, clsidString);
+  
+       setRegistryKey(key, NULL, XWT_friendlyName);
+       setRegistryKey(key, "InprocServer32", moduleName);
+       setRegistryKey(key, "ProgID", XWT_programID);
+       setRegistryKey(key, "VersionIndependentProgID", XWT_versionIndependantProgramID);
+       setRegistryKey(XWT_versionIndependantProgramID, NULL, XWT_friendlyName); 
+       setRegistryKey(XWT_versionIndependantProgramID, "CLSID", clsidString);
+       setRegistryKey(XWT_versionIndependantProgramID, "CurVer", XWT_programID);
+       setRegistryKey(XWT_programID, NULL, XWT_friendlyName); 
+       setRegistryKey(XWT_programID, "CLSID", clsidString);
+       return S_OK;
+}
+
+STDAPI __declspec(dllexport) DllUnregisterServer(void) {
+       char clsidString[CLSID_STRING_SIZE];
+       clsidToString(XWT_clsid, clsidString, sizeof(clsidString));
+
+       // build the key
+       char key[64];
+       strcpy(key, "CLSID\\");
+       strcat(key, clsidString);
+
+       deleteRegistryKey(HKEY_CLASSES_ROOT, key);
+       deleteRegistryKey(HKEY_CLASSES_ROOT, XWT_versionIndependantProgramID);
+       deleteRegistryKey(HKEY_CLASSES_ROOT, XWT_programID);
+       return S_OK;
+}
+
+
+
+// ShoeHorn //////////////////////////////////////////////////////////////////////////////////
+
+class ShoeHorn : public IShoeHorn {
+    public:
+    virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) {
+        if(iid == IID_IUnknown) *ppv = static_cast<IShoeHorn*>(this);
+        else if(iid == XWT_clsid) *ppv = static_cast<IShoeHorn*>(this);
+        else if(iid == IID_IPersistPropertyBag) *ppv = static_cast<IPersistPropertyBag*>(this);
+        else if(iid == IID_IObjectSafety) *ppv = static_cast<IObjectSafety*>(this);
+        else { *ppv = NULL; return E_NOINTERFACE; }
+        reinterpret_cast<IUnknown*>(*ppv)->AddRef();
+        return S_OK;
+    }
+    virtual ULONG __stdcall AddRef() { return InterlockedIncrement(&m_cRef); }
+    virtual ULONG __stdcall Release() {
+        if(InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; }
+        return m_cRef;
+    }
+    virtual HRESULT __stdcall GetClassID(CLSID*) { return S_OK; }
+    virtual HRESULT __stdcall InitNew() { return S_OK; }
+    virtual HRESULT __stdcall Save(IPropertyBag*, int, int) { return S_OK; }
+    virtual HRESULT __stdcall SetInterfaceSafetyOptions(REFIID riid, DWORD pdwSupportedOptions, DWORD pdwEnabledOptions) { return S_OK; }
+    virtual HRESULT __stdcall GetInterfaceSafetyOptions(REFIID riid, DWORD* pdwSupportedOptions, DWORD* pdwEnabledOptions) {
+        if (pdwSupportedOptions != NULL) *pdwSupportedOptions |= INTERFACESAFE_FOR_UNTRUSTED_DATA;
+        if (pdwEnabledOptions != NULL) *pdwSupportedOptions |= INTERFACESAFE_FOR_UNTRUSTED_DATA;
+        return S_OK;
+    }
+
+    virtual HRESULT __stdcall Load(IPropertyBag* pPropBag, IErrorLog* pErrorLog) {
+        VARIANT v;
+        v.vt = VT_BSTR;
+        HRESULT hrRead;
+        
+        WCHAR wc[100];
+        char url[100];
+        
+        MultiByteToWideChar(CP_ACP, 0, "xwar", -1, wc, 100);
+        pPropBag->Read(wc, &v, pErrorLog);
+        check(WideCharToMultiByte(CP_ACP, 0, v.bstrVal, -1, url, 100, NULL, NULL),
+              "WideCharToMultiByte() failed in ShoeHorn::Load()");
+        
+        HKEY hkey;
+        LONG result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\ActiveX Cache", &hkey);
+        check(result == ERROR_SUCCESS, "RegOpenKey() failed in ShoeHorn::Load()");
+        
+        // iterate over all the activex cache locations until we find ourselves
+        for(int i=0; i<9; i++) {
+            DWORD buflen = 999;
+            char buf[1000];
+            VALENT valents[20];
+            DWORD type;
+            char which[2];
+
+            which[0] = '0' + i;
+            which[1] = '\0';
+            result = RegQueryValueEx(hkey, which, NULL, &type, (BYTE*)buf, &buflen);
+            if (result != ERROR_SUCCESS)
+                if (i == 0) {
+                    check(0, "RegQueryValueEx() failed in ShoeHorn::Load()");
+                } else {
+                    break;
+                }
+            buf[buflen] = '\0';
+            
+            char cmdline[200];
+            for(int i=0; i<200; i++) cmdline[i] = '\0';
+            strncpy(cmdline, buf, 200);
+            strncpy(cmdline + strlen(cmdline), "\\xwt-" BUILDID ".exe", 200 - strlen(cmdline));
+            strncpy(cmdline + strlen(cmdline), " ", 200 - strlen(cmdline));
+            strncpy(cmdline + strlen(cmdline), url, 200 - strlen(cmdline));
+            
+            PROCESS_INFORMATION pInfo;
+            STARTUPINFO sInfo;
+            sInfo.cb = sizeof(STARTUPINFO);
+            sInfo.lpReserved = NULL;
+            sInfo.lpReserved2 = NULL;
+            sInfo.cbReserved2 = 0;
+            sInfo.lpDesktop = NULL;
+            sInfo.lpTitle = NULL;
+            sInfo.dwFlags = 0;
+            sInfo.dwX = 0;
+            sInfo.dwY = 0;
+            sInfo.dwFillAttribute = 0;
+            sInfo.wShowWindow = SW_SHOW;
+            BOOL b = CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &sInfo, &pInfo);
+            if (b) return S_OK;
+        }
+
+        check(0, "unable to locate xwt-" BUILDID ".exe in ActiveX cache folders");
+    }
+
+    ShoeHorn() : m_cRef(1) { };
+    ~ShoeHorn() { };
+    private: long m_cRef;
+};
+
+
+
+
+// ClassFactory //////////////////////////////////////////////////////////////////////////
+
+class ClassFactory : public IClassFactory {
+    public:
+    virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) {
+        if(iid == IID_IUnknown) *ppv = static_cast<IClassFactory*>(this);
+        else if(iid == IID_IClassFactory) *ppv = static_cast<IClassFactory*>(this);
+        else {
+            *ppv = NULL;
+            return E_NOINTERFACE;
+        }
+        reinterpret_cast<IUnknown*>(*ppv)->AddRef();
+        return S_OK;
+    }
+
+    ClassFactory() : m_cRef(1) { }
+    ~ClassFactory() { }
+    virtual HRESULT __stdcall LockServer(BOOL bLock) { return S_OK; }
+    virtual ULONG __stdcall AddRef() { return InterlockedIncrement(&m_cRef); }
+    virtual ULONG __stdcall Release() {
+        if(InterlockedDecrement(&m_cRef) == 0) {
+            delete this;
+            return 0;
+        }
+        return m_cRef;
+    }
+
+    virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) {
+        if(pUnknownOuter != NULL) return CLASS_E_NOAGGREGATION;
+        ShoeHorn* s = new ShoeHorn;
+        if(s == NULL) return E_OUTOFMEMORY;
+        HRESULT hr = s->QueryInterface(iid, ppv);
+        s->Release();
+        return hr;
+    }
+    
+    private: long m_cRef;
+};
+
+
+extern "C" __stdcall HRESULT DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) {
+    if(clsid != XWT_clsid) return CLASS_E_CLASSNOTAVAILABLE;
+    ClassFactory* pFactory = new ClassFactory; 
+    if(pFactory == NULL) return E_OUTOFMEMORY;
+    HRESULT hr = pFactory->QueryInterface(iid, ppv);
+    pFactory->Release();
+    return hr;
+}
+
+
diff --git a/src/org/xwt/plat/Win32.def b/src/org/xwt/plat/Win32.def
new file mode 100644 (file)
index 0000000..9a790cb
--- /dev/null
@@ -0,0 +1,5 @@
+EXPORTS
+        DllGetClassObject = DllGetClassObject@12
+        DllCanUnloadNow = DllCanUnloadNow@0
+        DllRegisterServer = DllRegisterServer@0
+        DllUnregisterServer = DllUnregisterServer@0
diff --git a/src/org/xwt/plat/Win32.inf b/src/org/xwt/plat/Win32.inf
new file mode 100644 (file)
index 0000000..ca2eb91
--- /dev/null
@@ -0,0 +1,19 @@
+;; This file will be copied to bin-Win32/cabsrc/xwt-__BUILD__.inf and then packed up
+;; into the .cab file for distribution
+
+[version]
+    signature="$CHICAGO$"
+    AdvancedINF=2.0
+[Add.Code]
+    xwt-__BUILD__.dll=xwt-__BUILD__.dll
+    xwt-__BUILD__.exe=xwt-__BUILD__.exe
+[xwt-__BUILD__.dll]
+    file-win32-x86=thiscab
+    clsid={D605__BUILD__-61B3-11d6-82FA-005056CA9250}
+    FileVersion=7,0,0,0
+    RegisterServer=yes
+[xwt-__BUILD__.exe]
+    file-win32-x86=thiscab
+    clsid={FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFE}
+    FileVersion=7,0,0,0
+    RegisterServer=no
diff --git a/src/org/xwt/shoehorn/ShoeHorn.java b/src/org/xwt/shoehorn/ShoeHorn.java
new file mode 100644 (file)
index 0000000..bf34c2f
--- /dev/null
@@ -0,0 +1,3 @@
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt.shoehorn;
+public class ShoeHorn extends org.xwt.shoehorn3.ShoeHorn { }
diff --git a/src/org/xwt/shoehorn/ShoeHorn1.java b/src/org/xwt/shoehorn/ShoeHorn1.java
new file mode 100644 (file)
index 0000000..0fdac2e
--- /dev/null
@@ -0,0 +1,3 @@
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt.shoehorn;
+public class ShoeHorn1 extends org.xwt.shoehorn3.ShoeHorn { }
diff --git a/src/org/xwt/shoehorn2/ShoeHorn.java b/src/org/xwt/shoehorn2/ShoeHorn.java
new file mode 100644 (file)
index 0000000..4e3edbf
--- /dev/null
@@ -0,0 +1,3 @@
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt.shoehorn2;
+public class ShoeHorn extends org.xwt.shoehorn3.ShoeHorn { }
diff --git a/src/org/xwt/shoehorn3/Crypto.java b/src/org/xwt/shoehorn3/Crypto.java
new file mode 100644 (file)
index 0000000..cf90501
--- /dev/null
@@ -0,0 +1,10138 @@
+// This is just a cut-and-paste of a bunch of files from Bouncycastle
+package org.xwt.shoehorn3;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.util.*;
+import java.text.*;
+
+abstract class ASN1OctetString
+    extends DERObject
+{
+    byte[]  string;
+
+    /**
+     * return an Octet String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want.
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *              be converted.
+     */
+    public static ASN1OctetString getInstance(
+        ASN1TaggedObject    obj,
+        boolean             explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+       
+    /**
+     * return an Octet String from the given object.
+     *
+     * @param obj the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static ASN1OctetString getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof ASN1OctetString)
+        {
+            return (ASN1OctetString)obj;
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        if (obj instanceof ASN1Sequence)
+        {
+            Vector      v = new Vector();
+            Enumeration e = ((ASN1Sequence)obj).getObjects();
+
+            while (e.hasMoreElements())
+            {
+                v.addElement(e.nextElement());
+            }
+
+            return new BERConstructedOctetString(v);
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * @param string the octets making up the octet string.
+     */
+    public ASN1OctetString(
+        byte[]  string)
+    {
+        this.string = string;
+    }
+
+    public ASN1OctetString(
+        DEREncodable obj)
+    {
+        try
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+
+            dOut.writeObject(obj);
+            dOut.close();
+
+            this.string = bOut.toByteArray();
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("Error processing object : " + e.toString());
+        }
+    }
+
+    public byte[] getOctets()
+    {
+        return string;
+    }
+
+    public int hashCode()
+    {
+        byte[]  b = this.getOctets();
+        int     value = 0;
+
+        for (int i = 0; i != b.length; i++)
+        {
+            value ^= (b[i] & 0xff) << (i % 4);
+        }
+
+        return value;
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof DEROctetString))
+        {
+            return false;
+        }
+
+        DEROctetString  other = (DEROctetString)o;
+
+        byte[] b1 = other.getOctets();
+        byte[] b2 = this.getOctets();
+
+        if (b1.length != b2.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != b1.length; i++)
+        {
+            if (b1[i] != b2[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    abstract void encode(DEROutputStream out)
+        throws IOException;
+}
+
+
+class ASN1OutputStream
+    extends DEROutputStream
+{
+    public ASN1OutputStream(
+        OutputStream    os)
+    {
+        super(os);
+    }
+
+    public void writeObject(
+        Object    obj)
+        throws IOException
+    {
+        if (obj == null)
+        {
+            writeNull();
+        }
+        else if (obj instanceof DERObject)
+        {
+            ((DERObject)obj).encode(this);
+        }
+        else if (obj instanceof DEREncodable)
+        {
+            ((DEREncodable)obj).getDERObject().encode(this);
+        }
+        else
+        {
+            throw new IOException("object not ASN1Encodable");
+        }
+    }
+}
+
+
+abstract class ASN1Sequence
+    extends DERObject
+{
+    private Vector seq = new Vector();
+
+    /**
+     * return an ASN1Sequence from the given object.
+     *
+     * @param obj the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static ASN1Sequence getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof ASN1Sequence)
+        {
+            return (ASN1Sequence)obj;
+        }
+
+        throw new IllegalArgumentException("unknown object in getInstance");
+    }
+
+    /**
+     * Return an ASN1 sequence from a tagged object. There is a special
+     * case here, if an object appears to have been explicitly tagged on 
+     * reading but we were expecting it to be implictly tagged in the 
+     * normal course of events it indicates that we lost the surrounding
+     * sequence - so we need to add it back (this will happen if the tagged
+     * object is a sequence that contains other sequences). If you are
+     * dealing with implicitly tagged sequences you really <b>should</b>
+     * be using this method.
+     *
+     * @param obj the tagged object.
+     * @param explicit true if the object is meant to be explicitly tagged,
+     *          false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *          be converted.
+     */
+    public static ASN1Sequence getInstance(
+        ASN1TaggedObject    obj,
+        boolean             explicit)
+    {
+        if (explicit)
+        {
+            if (!obj.isExplicit())
+            {
+                throw new IllegalArgumentException("object implicit - explicit expected.");
+            }
+
+            return (ASN1Sequence)obj.getObject();
+        }
+        else
+        {
+            //
+            // constructed object which appears to be explicitly tagged
+            // when it should be implicit means we have to add the
+            // surrounding sequence.
+            //
+            if (obj.isExplicit())
+            {
+                ASN1Sequence    seq;
+
+                if (obj instanceof BERTaggedObject)
+                {
+                    seq = new BERConstructedSequence();
+                }
+                else
+                {
+                    seq = new DERConstructedSequence();
+                }
+
+                seq.addObject(obj.getObject());
+
+                return seq;
+            }
+            else
+            {
+                ASN1Sequence    seq;
+
+                if (obj.getObject() instanceof ASN1Sequence)
+                {
+                    return (ASN1Sequence)obj.getObject();
+                }
+            }
+        }
+
+        throw new IllegalArgumentException(
+                "unknown object in getInstanceFromTagged");
+    }
+
+    public Enumeration getObjects()
+    {
+        return seq.elements();
+    }
+
+    /**
+     * return the object at the sequence postion indicated by index.
+     *
+     * @param the sequence number (starting at zero) of the object
+     * @return the object at the sequence postion indicated by index.
+     */
+    public DEREncodable getObjectAt(
+        int index)
+    {
+        return (DEREncodable)seq.elementAt(index);
+    }
+
+    /**
+     * return the number of objects in this sequence.
+     *
+     * @return the number of objects in this sequence.
+     */
+    public int size()
+    {
+        return seq.size();
+    }
+
+    public int hashCode()
+    {
+        Enumeration             e = this.getObjects();
+        int                     hashCode = 0;
+
+        while (e.hasMoreElements())
+        {
+            hashCode ^= e.nextElement().hashCode();
+        }
+
+        return hashCode;
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof ASN1Sequence))
+        {
+            return false;
+        }
+
+        ASN1Sequence   other = (ASN1Sequence)o;
+
+        if (this.size() != other.size())
+        {
+            return false;
+        }
+
+        Enumeration s1 = this.getObjects();
+        Enumeration s2 = other.getObjects();
+
+        while (s1.hasMoreElements())
+        {
+            if (!s1.nextElement().equals(s2.nextElement()))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected void addObject(
+        DEREncodable obj)
+    {
+        seq.addElement(obj);
+    }
+
+    abstract void encode(DEROutputStream out)
+        throws IOException;
+}
+
+
+abstract class ASN1Set
+    extends DERObject
+{
+    protected Vector set = new Vector();
+
+    /**
+     * return an ASN1Set from the given object.
+     *
+     * @param obj the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static ASN1Set getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof ASN1Set)
+        {
+            return (ASN1Set)obj;
+        }
+
+        throw new IllegalArgumentException("unknown object in getInstance");
+    }
+
+    /**
+     * Return an ASN1 set from a tagged object. There is a special
+     * case here, if an object appears to have been explicitly tagged on 
+     * reading but we were expecting it to be implictly tagged in the 
+     * normal course of events it indicates that we lost the surrounding
+     * set - so we need to add it back (this will happen if the tagged
+     * object is a sequence that contains other sequences). If you are
+     * dealing with implicitly tagged sets you really <b>should</b>
+     * be using this method.
+     *
+     * @param obj the tagged object.
+     * @param explicit true if the object is meant to be explicitly tagged
+     *          false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *          be converted.
+     */
+    public static ASN1Set getInstance(
+        ASN1TaggedObject    obj,
+        boolean             explicit)
+    {
+        if (explicit)
+        {
+            if (!obj.isExplicit())
+            {
+                throw new IllegalArgumentException("object implicit - explicit expected.");
+            }
+
+            return (ASN1Set)obj.getObject();
+        }
+        else
+        {
+            //
+            // constructed object which appears to be explicitly tagged
+            // and it's really implicit means we have to add the
+            // surrounding sequence.
+            //
+            if (obj.isExplicit())
+            {
+                ASN1Set    set = new DERSet(obj.getObject());
+
+                return set;
+            }
+            else
+            {
+                //
+                // in this case the parser returns a sequence, convert it
+                // into a set.
+                //
+                DEREncodableVector  v = new DEREncodableVector();
+
+                if (obj.getObject() instanceof ASN1Sequence)
+                {
+                    ASN1Sequence s = (ASN1Sequence)obj.getObject();
+                    Enumeration e = s.getObjects();
+
+                    while (e.hasMoreElements())
+                    {
+                        v.add((DEREncodable)e.nextElement());
+                    }
+
+                    return new DERSet(v);
+                }
+            }
+        }
+
+        throw new IllegalArgumentException(
+                    "unknown object in getInstanceFromTagged");
+    }
+
+    public ASN1Set()
+    {
+    }
+
+    public Enumeration getObjects()
+    {
+        return set.elements();
+    }
+
+    /**
+     * return the object at the set postion indicated by index.
+     *
+     * @param the set number (starting at zero) of the object
+     * @return the object at the set postion indicated by index.
+     */
+    public DEREncodable getObjectAt(
+        int index)
+    {
+        return (DEREncodable)set.elementAt(index);
+    }
+
+    /**
+     * return the number of objects in this set.
+     *
+     * @return the number of objects in this set.
+     */
+    public int size()
+    {
+        return set.size();
+    }
+
+    public int hashCode()
+    {
+        Enumeration             e = this.getObjects();
+        int                     hashCode = 0;
+
+        while (e.hasMoreElements())
+        {
+            hashCode ^= e.nextElement().hashCode();
+        }
+
+        return hashCode;
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof ASN1Set))
+        {
+            return false;
+        }
+
+        ASN1Set   other = (ASN1Set)o;
+
+        if (this.size() != other.size())
+        {
+            return false;
+        }
+
+        Enumeration s1 = this.getObjects();
+        Enumeration s2 = other.getObjects();
+
+        while (s1.hasMoreElements())
+        {
+            if (!s1.nextElement().equals(s2.nextElement()))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected void addObject(
+        DEREncodable obj)
+    {
+        set.addElement(obj);
+    }
+
+    abstract void encode(DEROutputStream out)
+            throws IOException;
+}
+
+
+/**
+ * ASN.1 TaggedObject - in ASN.1 nottation this is any object proceeded by
+ * a [n] where n is some number - these are assume to follow the construction
+ * rules (as with sequences).
+ */
+abstract class ASN1TaggedObject
+    extends DERObject
+{
+    int             tagNo;
+    boolean         empty = false;
+    boolean         explicit = true;
+    DEREncodable    obj = null;
+
+    /**
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public ASN1TaggedObject(
+        int             tagNo,
+        DEREncodable    obj)
+    {
+        this.explicit = true;
+        this.tagNo = tagNo;
+        this.obj = obj;
+    }
+
+    /**
+     * @param explicit true if the object is explicitly tagged.
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public ASN1TaggedObject(
+        boolean         explicit,
+        int             tagNo,
+        DEREncodable    obj)
+    {
+        this.explicit = explicit;
+        this.tagNo = tagNo;
+        this.obj = obj;
+    }
+       
+       public boolean equals(
+               Object o)
+       {
+        if (o == null || !(o instanceof ASN1TaggedObject))
+        {
+            return false;
+        }
+        
+        ASN1TaggedObject other = (ASN1TaggedObject)o;
+        
+        if(tagNo != other.tagNo || empty != other.empty || explicit != other.explicit)
+        {
+                       return false;
+               }
+               
+               if(obj == null)
+               {
+                       if(other.obj != null)
+                       {
+                               return false;
+                       }
+               }
+               else
+               {
+                       if(!(obj.equals(other.obj)))
+                       {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+       
+    public int getTagNo()
+    {
+        return tagNo;
+    }
+
+    /**
+     * return whether or not the object may be explicitly tagged. 
+     * <p>
+     * Note: if the object has been read from an input stream, the only
+     * time you can be sure if isExplicit is returning the true state of
+     * affairs is if it returns false. An implicitly tagged object may appear
+     * to be explicitly tagged, so you need to understand the context under
+     * which the reading was done as well, see getObject below.
+     */
+    public boolean isExplicit()
+    {
+        return explicit;
+    }
+
+    public boolean isEmpty()
+    {
+        return empty;
+    }
+
+    /**
+     * return whatever was following the tag.
+     * <p>
+     * Note: tagged objects are generally context dependent if you're
+     * trying to extract a tagged object you should be going via the
+     * appropriate getInstance method.
+     */
+    public DERObject getObject()
+    {
+        if (obj != null)
+        {
+            return obj.getDERObject();
+        }
+
+        return null;
+    }
+
+    abstract void encode(DEROutputStream  out)
+        throws IOException;
+}
+
+
+class BERConstructedOctetString
+    extends DEROctetString
+{
+    /**
+     * convert a vector of octet strings into a single byte string
+     */
+    static private byte[] toBytes(
+        Vector  octs)
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+
+        for (int i = 0; i != octs.size(); i++)
+        {
+            DEROctetString  o = (DEROctetString)octs.elementAt(i);
+
+            try
+            {
+                bOut.write(o.getOctets());
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException("exception converting octets " + e.toString());
+            }
+        }
+
+        return bOut.toByteArray();
+    }
+
+    private Vector  octs;
+
+    /**
+     * @param string the octets making up the octet string.
+     */
+    public BERConstructedOctetString(
+        byte[]  string)
+    {
+               super(string);
+    }
+
+    public BERConstructedOctetString(
+        Vector  octs)
+    {
+               super(toBytes(octs));
+
+        this.octs = octs;
+    }
+
+    public BERConstructedOctetString(
+        DERObject  obj)
+    {
+               super(obj);
+    }
+
+    public BERConstructedOctetString(
+        DEREncodable  obj)
+    {
+        super(obj.getDERObject());
+    }
+
+    public byte[] getOctets()
+    {
+        return string;
+    }
+
+    /**
+     * return the DER octets that make up this string.
+     */
+    public Enumeration getObjects()
+    {
+        if (octs == null)
+        {
+            octs = generateOcts();
+        }
+
+        return octs.elements();
+    }
+
+    private Vector generateOcts()
+    {
+        int     start = 0;
+        int     end = 0;
+        Vector  vec = new Vector();
+
+        while ((end + 1) < string.length)
+        {
+            if (string[end] == 0 && string[end + 1] == 0)
+            {
+                byte[]  nStr = new byte[end - start + 1];
+
+                for (int i = 0; i != nStr.length; i++)
+                {
+                    nStr[i] = string[start + i];
+                }
+
+                vec.addElement(new DEROctetString(nStr));
+                start = end + 1;
+            }
+            end++;
+        }
+
+        byte[]  nStr = new byte[string.length - start];
+        for (int i = 0; i != nStr.length; i++)
+        {
+            nStr[i] = string[start + i];
+        }
+
+        vec.addElement(new DEROctetString(nStr));
+
+        return vec;
+    }
+
+    public void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
+        {
+            out.write(CONSTRUCTED | OCTET_STRING);
+
+            out.write(0x80);
+
+            if (octs == null)
+            {
+                octs = generateOcts();
+            }
+
+            for (int i = 0; i != octs.size(); i++)
+            {
+                out.writeObject(octs.elementAt(i));
+            }
+
+            out.write(0x00);
+            out.write(0x00);
+        }
+        else
+        {
+            super.encode(out);
+        }
+    }
+}
+
+
+class BERConstructedSequence
+    extends DERConstructedSequence
+{
+    /*
+     */
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
+        {
+            out.write(SEQUENCE | CONSTRUCTED);
+            out.write(0x80);
+            
+            Enumeration e = getObjects();
+            while (e.hasMoreElements())
+            {
+                out.writeObject(e.nextElement());
+            }
+        
+            out.write(0x00);
+            out.write(0x00);
+        }
+        else
+        {
+            super.encode(out);
+        }
+    }
+}
+
+
+class BERInputStream
+    extends DERInputStream
+{
+       private DERObject END_OF_STREAM = new DERObject() {
+                                                                               void encode(
+                                                                                       DEROutputStream out)
+                                                                               throws IOException
+                                                                               {
+                                                                                       throw new IOException("Eeek!");
+                                                                               }
+
+                                                                       };
+    public BERInputStream(
+        InputStream is)
+    {
+        super(is);
+    }
+
+    /**
+     * read a string of bytes representing an indefinite length object.
+     */
+    private byte[] readIndefiniteLengthFully()
+        throws IOException
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        int                     b, b1;
+
+        b1 = read();
+
+        while ((b = read()) >= 0)
+        {
+                       if (b1 == 0 && b == 0)
+                       {
+                               break;
+                       }
+
+            bOut.write(b1);
+            b1 = b;
+        }
+
+        return bOut.toByteArray();
+    }
+
+       private BERConstructedOctetString buildConstructedOctetString()
+               throws IOException
+       {
+        Vector                  octs = new Vector();
+
+               for (;;)
+               {
+                       DERObject               o = readObject();
+
+                       if (o == END_OF_STREAM)
+                       {
+                               break;
+                       }
+
+            octs.addElement(o);
+               }
+
+               return new BERConstructedOctetString(octs);
+       }
+
+    public DERObject readObject()
+        throws IOException
+    {
+        int tag = read();
+        if (tag == -1)
+        {
+            throw new EOFException();
+        }
+    
+        int     length = readLength();
+
+        if (length < 0)    // indefinite length method
+        {
+            switch (tag)
+            {
+            case NULL:
+                return null;
+            case SEQUENCE | CONSTRUCTED:
+                BERConstructedSequence  seq = new BERConstructedSequence();
+    
+                               for (;;)
+                               {
+                                       DERObject   obj = readObject();
+
+                                       if (obj == END_OF_STREAM)
+                                       {
+                                               break;
+                                       }
+
+                                       seq.addObject(obj);
+                               }
+                               return seq;
+            case OCTET_STRING | CONSTRUCTED:
+                               return buildConstructedOctetString();
+            case SET | CONSTRUCTED:
+                DEREncodableVector  v = new DEREncodableVector();
+    
+                               for (;;)
+                               {
+                                       DERObject   obj = readObject();
+
+                                       if (obj == END_OF_STREAM)
+                                       {
+                                               break;
+                                       }
+
+                                       v.add(obj);
+                               }
+                               return new BERSet(v);
+            default:
+                //
+                // with tagged object tag number is bottom 5 bits
+                //
+                if ((tag & TAGGED) != 0)  
+                {
+                    if ((tag & 0x1f) == 0x1f)
+                    {
+                        throw new IOException("unsupported high tag encountered");
+                    }
+
+                    //
+                    // simple type - implicit... return an octet string
+                    //
+                    if ((tag & CONSTRUCTED) == 0)
+                    {
+                        byte[]  bytes = readIndefiniteLengthFully();
+
+                        return new BERTaggedObject(false, tag & 0x1f, new DEROctetString(bytes));
+                    }
+
+                    //
+                    // either constructed or explicitly tagged
+                    //
+                                       DERObject               dObj = readObject();
+
+                                       if (dObj == END_OF_STREAM)     // empty tag!
+                    {
+                        return new DERTaggedObject(tag & 0x1f);
+                    }
+
+                    DERObject       next = readObject();
+
+                    //
+                    // explicitly tagged (probably!) - if it isn't we'd have to
+                    // tell from the context
+                    //
+                    if (next == END_OF_STREAM)
+                    {
+                        return new BERTaggedObject(tag & 0x1f, dObj);
+                    }
+
+                    //
+                    // another implicit object, we'll create a sequence...
+                    //
+                    seq = new BERConstructedSequence();
+
+                    seq.addObject(dObj);
+
+                    do
+                    {
+                        seq.addObject(next);
+                        next = readObject();
+                    }
+                    while (next != END_OF_STREAM);
+
+                    return new BERTaggedObject(false, tag & 0x1f, seq);
+                }
+
+                throw new IOException("unknown BER object encountered");
+            }
+        }
+        else
+        {
+            if (tag == 0 && length == 0)    // end of contents marker.
+            {
+                return END_OF_STREAM;
+            }
+
+            byte[]  bytes = new byte[length];
+    
+            readFully(bytes);
+    
+                       return buildObject(tag, bytes);
+        }
+    }
+}
+
+
+class BEROutputStream
+    extends DEROutputStream
+{
+    public BEROutputStream(
+        OutputStream    os)
+    {
+        super(os);
+    }
+
+    public void writeObject(
+        Object    obj)
+        throws IOException
+    {
+        if (obj == null)
+        {
+            writeNull();
+        }
+        else if (obj instanceof DERObject)
+        {
+            ((DERObject)obj).encode(this);
+        }
+        else if (obj instanceof DEREncodable)
+        {
+            ((DEREncodable)obj).getDERObject().encode(this);
+        }
+        else
+        {
+            throw new IOException("object not BEREncodable");
+        }
+    }
+}
+
+
+class BERSet
+    extends DERSet
+{
+    /**
+     * create an empty sequence
+     */
+    public BERSet()
+    {
+    }
+
+    /**
+     * create a set containing one object
+     */
+    public BERSet(
+        DEREncodable    obj)
+    {
+        super(obj);
+    }
+
+    /**
+     * create a set containing a vector of objects.
+     */
+    public BERSet(
+        DEREncodableVector   v)
+    {
+        super(v);
+    }
+
+    /*
+     */
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
+        {
+            out.write(SET | CONSTRUCTED);
+            out.write(0x80);
+            
+            Enumeration e = getObjects();
+            while (e.hasMoreElements())
+            {
+                out.writeObject(e.nextElement());
+            }
+        
+            out.write(0x00);
+            out.write(0x00);
+        }
+        else
+        {
+            super.encode(out);
+        }
+    }
+}
+
+
+/**
+ * BER TaggedObject - in ASN.1 nottation this is any object proceeded by
+ * a [n] where n is some number - these are assume to follow the construction
+ * rules (as with sequences).
+ */
+class BERTaggedObject
+    extends DERTaggedObject
+{
+    /**
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public BERTaggedObject(
+        int             tagNo,
+        DEREncodable    obj)
+    {
+               super(tagNo, obj);
+    }
+
+    /**
+     * @param explicit true if an explicitly tagged object.
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public BERTaggedObject(
+        boolean         explicit,
+        int             tagNo,
+        DEREncodable    obj)
+    {
+               super(explicit, tagNo, obj);
+    }
+
+    /**
+     * create an implicitly tagged object that contains a zero
+     * length sequence.
+     */
+    public BERTaggedObject(
+        int             tagNo)
+    {
+        super(false, tagNo, new BERConstructedSequence());
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        if (out instanceof ASN1OutputStream || out instanceof BEROutputStream)
+        {
+            out.write(CONSTRUCTED | TAGGED | tagNo);
+            out.write(0x80);
+
+            if (!empty)
+            {
+                if (!explicit)
+                {
+                    if (obj instanceof BERConstructedOctetString)
+                    {
+                        Enumeration  e = ((BERConstructedOctetString)obj).getObjects();
+
+                        while (e.hasMoreElements())
+                        {
+                            out.writeObject(e.nextElement());
+                        }
+                    }
+                    else if (obj instanceof ASN1Sequence)
+                    {
+                        Enumeration  e = ((ASN1Sequence)obj).getObjects();
+
+                        while (e.hasMoreElements())
+                        {
+                            out.writeObject(e.nextElement());
+                        }
+                    }
+                    else if (obj instanceof ASN1Set)
+                    {
+                        Enumeration  e = ((ASN1Set)obj).getObjects();
+
+                        while (e.hasMoreElements())
+                        {
+                            out.writeObject(e.nextElement());
+                        }
+                    }
+                    else
+                    {
+                        throw new RuntimeException("not implemented: " + obj.getClass().getName());
+                    }
+                }
+                else
+                {
+                    out.writeObject(obj);
+                }
+            }
+
+            out.write(0x00);
+            out.write(0x00);
+        }
+        else
+        {
+            super.encode(out);
+        }
+    }
+}
+
+
+class Attribute
+    implements DEREncodable
+{
+       private DERObjectIdentifier attrType;
+       private ASN1Set             attrValues;
+
+    /**
+     * return an Attribute object from the given object.
+     *
+     * @param o the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+       public static Attribute getInstance(
+        Object o)
+    {
+               if (o == null || o instanceof Attribute)
+        {
+                       return (Attribute)o;
+               }
+               
+               if (o instanceof ASN1Sequence)
+        {
+                       return new Attribute((ASN1Sequence)o);
+               }
+
+        throw new IllegalArgumentException("unknown object in factory");
+       }
+       
+       public Attribute(
+        ASN1Sequence seq)
+    {
+               attrType = (DERObjectIdentifier)seq.getObjectAt(0);
+               attrValues = (ASN1Set)seq.getObjectAt(1);
+       }
+
+       public Attribute(
+           DERObjectIdentifier attrType,
+           ASN1Set             attrValues)
+    {
+               this.attrType = attrType;
+               this.attrValues = attrValues;
+       }
+
+       public DERObjectIdentifier getAttrType()
+    {
+               return attrType;
+       }
+       
+       public ASN1Set getAttrValues()
+    {
+               return attrValues;
+       }
+
+    /** 
+     * <pre>
+     * Attribute ::= SEQUENCE {
+     *         attrType OBJECT IDENTIFIER,
+     *         attrValues SET OF AttributeValue
+     * }
+     * </pre>
+     */
+       public DERObject getDERObject()
+    {
+               DEREncodableVector v = new DEREncodableVector();
+
+               v.add(attrType);
+               v.add(attrValues);
+
+               return new DERSequence(v);
+       }
+}
+// Decompiled by Jad v1.5.7f. Copyright 2000 Pavel Kouznetsov.
+// Jad home page: http://www.geocities.com/SiliconValley/Bridge/8617/jad.html
+// Decompiler options: packimports(3) 
+// Source File Name:   SignedAttributes.java
+
+
+
+// Referenced classes of package org.bouncycastle.asn1.cms:
+//            Attribute
+
+class SignedAttributes
+    implements DEREncodable
+{
+
+    public SignedAttributes(Vector vector)
+    {
+        setAttributes(vector);
+    }
+
+    public SignedAttributes(DERConstructedSet derconstructedset)
+    {
+        attributes = derconstructedset;
+    }
+
+    public SignedAttributes(SignedAttributes signedattributes)
+    {
+        attributes = signedattributes.attributes;
+    }
+
+    public static SignedAttributes getInstance(Object obj)
+    {
+        if(obj == null)
+            return null;
+        if(obj instanceof SignedAttributes)
+            return (SignedAttributes)obj;
+        if(obj instanceof DERConstructedSet)
+            return new SignedAttributes((DERConstructedSet)obj);
+        if(obj instanceof DERTaggedObject)
+            return getInstance(((DERTaggedObject)obj).getObject());
+        else
+            throw new IllegalArgumentException("Invalid SignedAttributes");
+    }
+
+    public static SignedAttributes newInstance(Object obj)
+    {
+        if(obj == null)
+            return null;
+        if(obj instanceof SignedAttributes)
+            return new SignedAttributes((SignedAttributes)obj);
+        if(obj instanceof DERConstructedSet)
+            return new SignedAttributes((DERConstructedSet)obj);
+        if(obj instanceof DERTaggedObject)
+            return getInstance(((DERTaggedObject)obj).getObject());
+        else
+            throw new IllegalArgumentException("Invalid SignedAttributes");
+    }
+
+    public Vector getAttributes()
+    {
+        int i = attributes.getSize();
+        Vector vector = new Vector();
+        for(int j = 0; j < i; j++)
+            vector.addElement(Attribute.getInstance(attributes.getObjectAt(j)));
+
+        return vector;
+    }
+
+    private void setAttributes(Vector vector)
+    {
+        int i = vector.size();
+        attributes = new DERConstructedSet();
+        for(int j = 0; j < i; j++)
+            attributes.addObject(Attribute.getInstance(vector.elementAt(j)));
+
+    }
+
+    public DERObject getDERObject()
+    {
+        return attributes;
+    }
+
+    private DERConstructedSet attributes;
+}
+
+
+class DERBitString
+    extends DERObject
+{
+    protected byte[]      data;
+    protected int         padBits;
+
+    /**
+     * return the correct number of pad bits for a bit string defined in
+     * a 16 bit constant
+     */
+    static protected int getPadBits(
+        int bitString)
+    {
+        int val;
+
+        if (bitString == 0)
+        {
+            return 7;
+        }
+
+        if (bitString > 255)
+        {
+            val = ((bitString >> 8) & 0xFF);
+        }
+        else
+        {
+            val = (bitString & 0xFF);
+        }
+
+        int bits = 1;
+
+        while (((val <<= 1) & 0xFF) != 0)
+        {
+            bits++;
+        }
+
+        return 8 - bits;
+    }
+
+    /**
+     * return the correct number of bytes for a bit string defined in
+     * a 16 bit constant
+     */
+    static protected byte[] getBytes(
+        int bitString)
+    {
+        if (bitString > 255)
+        {
+            byte[]  bytes = new byte[2];
+
+            bytes[0] = (byte)(bitString & 0xFF);
+            bytes[1] = (byte)((bitString >> 8) & 0xFF);
+
+            return bytes;
+        }
+        else
+        {
+            byte[]  bytes = new byte[1];
+
+            bytes[0] = (byte)(bitString & 0xFF);
+
+            return bytes;
+        }
+    }
+
+    /**
+     * return a Bit String from the passed in object
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERBitString getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERBitString)
+        {
+            return (DERBitString)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            byte[]  bytes = ((ASN1OctetString)obj).getOctets();
+            int     padBits = bytes[0];
+            byte[]  data = new byte[bytes.length - 1];
+
+            System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
+
+            return new DERBitString(data, padBits);
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a Bit String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERBitString getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+    
+    protected DERBitString(
+        byte    data,
+        int     padBits)
+    {
+        this.data = new byte[1];
+        this.data[0] = data;
+        this.padBits = padBits;
+    }
+
+    /**
+     * @param data the octets making up the bit string.
+     * @param padBits the number of extra bits at the end of the string.
+     */
+    public DERBitString(
+        byte[]  data,
+        int     padBits)
+    {
+        this.data = data;
+        this.padBits = padBits;
+    }
+
+    public DERBitString(
+        byte[]  data)
+    {
+        this(data, 0);
+    }
+
+    public DERBitString(
+        DEREncodable  obj)
+    {
+        try
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+
+            dOut.writeObject(obj);
+            dOut.close();
+
+            this.data = bOut.toByteArray();
+            this.padBits = 0;
+        }
+        catch (IOException e)
+        {
+            throw new IllegalArgumentException("Error processing object : " + e.toString());
+        }
+    }
+
+    public byte[] getBytes()
+    {
+        return data;
+    }
+
+    public int getPadBits()
+    {
+        return padBits;
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        byte[]  bytes = new byte[getBytes().length + 1];
+
+        bytes[0] = (byte)getPadBits();
+        System.arraycopy(getBytes(), 0, bytes, 1, bytes.length - 1);
+
+        out.writeEncoded(BIT_STRING, bytes);
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof DERBitString))
+        {
+            return false;
+        }
+
+        DERBitString  other = (DERBitString)o;
+
+        if (data.length != other.data.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != data.length; i++)
+        {
+            if (data[i] != other.data[i])
+            {
+                return false;
+            }
+        }
+
+        return (padBits == other.padBits);
+    }
+}
+
+
+/**
+ * DER BMPString object.
+ */
+class DERBMPString
+    extends DERObject
+    implements DERString
+{
+    String  string;
+
+    /**
+     * return a BMP String from the given object.
+     *
+     * @param obj the object we want converted.
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERBMPString getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERBMPString)
+        {
+            return (DERBMPString)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERBMPString(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a BMP String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *              be converted.
+     */
+    public static DERBMPString getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+    
+
+    /**
+     * basic constructor - byte encoded string.
+     */
+    public DERBMPString(
+        byte[]   string)
+    {
+        char[]  cs = new char[string.length / 2];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            cs[i] = (char)((string[2 * i] << 8) | (string[2 * i + 1] & 0xff));
+        }
+
+        this.string = new String(cs);
+    }
+
+    /**
+     * basic constructor
+     */
+    public DERBMPString(
+        String   string)
+    {
+        this.string = string;
+    }
+
+    public String getString()
+    {
+        return string;
+    }
+
+    public int hashCode()
+    {
+        return this.getString().hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof DERBMPString))
+        {
+            return false;
+        }
+
+        DERPrintableString  s = (DERPrintableString)o;
+
+        return this.getString().equals(s.getString());
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        char[]  c = string.toCharArray();
+        byte[]  b = new byte[c.length * 2];
+
+        for (int i = 0; i != c.length; i++)
+        {
+            b[2 * i] = (byte)(c[i] >> 8);
+            b[2 * i + 1] = (byte)c[i];
+        }
+
+        out.writeEncoded(BMP_STRING, b);
+    }
+}
+
+
+class DERBoolean
+    extends DERObject
+{
+    byte         value;
+
+       public static final DERBoolean FALSE = new DERBoolean(false);
+       public static final DERBoolean TRUE  = new DERBoolean(true);
+
+    /**
+     * return a boolean from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERBoolean getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERBoolean)
+        {
+            return (DERBoolean)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERBoolean(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a DERBoolean from the passed in boolean.
+     */
+    public static DERBoolean getInstance(
+        boolean  value)
+    {
+        return (value ? TRUE : FALSE);
+    }
+
+    /**
+     * return a Boolean from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERBoolean getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+    
+    public DERBoolean(
+        byte[]       value)
+    {
+        this.value = value[0];
+    }
+
+    public DERBoolean(
+        boolean     value)
+    {
+        this.value = (value) ? (byte)0xff : (byte)0;
+    }
+
+    public boolean isTrue()
+    {
+        return (value != 0);
+    }
+
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        byte[]  bytes = new byte[1];
+
+        bytes[0] = value;
+
+        out.writeEncoded(BOOLEAN, bytes);
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERBoolean))
+        {
+            return false;
+        }
+
+        return (value == ((DERBoolean)o).value);
+    }
+
+}
+
+
+class DERConstructedSequence
+    extends ASN1Sequence
+{
+    public void addObject(
+        DEREncodable obj)
+    {
+        super.addObject(obj);
+    }
+
+    public int getSize()
+    {
+        return size();
+    }
+
+    /*
+     * A note on the implementation:
+     * <p>
+     * As DER requires the constructed, definite-length model to
+     * be used for structured types, this varies slightly from the
+     * ASN.1 descriptions given. Rather than just outputing SEQUENCE,
+     * we also have to specify CONSTRUCTED, and the objects length.
+     */
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+        Enumeration             e = this.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            Object    obj = e.nextElement();
+
+            dOut.writeObject(obj);
+        }
+
+        dOut.close();
+
+        byte[]  bytes = bOut.toByteArray();
+
+        out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes);
+    }
+}
+
+
+class DERConstructedSet
+    extends ASN1Set
+{
+    public DERConstructedSet()
+    {
+    }
+
+    /**
+     * @param obj - a single object that makes up the set.
+     */
+    public DERConstructedSet(
+        DEREncodable   obj)
+    {
+        this.addObject(obj);
+    }
+
+    /**
+     * @param v - a vector of objects making up the set.
+     */
+    public DERConstructedSet(
+        DEREncodableVector   v)
+    {
+        for (int i = 0; i != v.size(); i++)
+        {
+            this.addObject(v.get(i));
+        }
+    }
+
+    public void addObject(
+        DEREncodable    obj)
+    {
+        super.addObject(obj);
+    }
+
+    public int getSize()
+    {
+        return size();
+    }
+
+    /*
+     * A note on the implementation:
+     * <p>
+     * As DER requires the constructed, definite-length model to
+     * be used for structured types, this varies slightly from the
+     * ASN.1 descriptions given. Rather than just outputing SET,
+     * we also have to specify CONSTRUCTED, and the objects length.
+     */
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+        Enumeration             e = this.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            Object    obj = e.nextElement();
+
+            dOut.writeObject(obj);
+        }
+
+        dOut.close();
+
+        byte[]  bytes = bOut.toByteArray();
+
+        out.writeEncoded(SET | CONSTRUCTED, bytes);
+    }
+}
+
+interface DEREncodable
+{
+    public DERObject getDERObject();
+}
+
+
+/**
+ * a general class for building up a vector of DER encodable objects
+ */
+class DEREncodableVector
+{
+    private Vector  v = new Vector();
+
+    public void add(
+        DEREncodable   obj)
+    {
+        v.addElement(obj);
+    }
+
+    public DEREncodable get(
+        int i)
+    {
+        return (DEREncodable)v.elementAt(i);
+    }
+
+    public int size()
+    {
+        return v.size();
+    }
+}
+
+
+class DEREnumerated
+    extends DERObject
+{
+    byte[]      bytes;
+
+    /**
+     * return an integer from the passed in object
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DEREnumerated getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DEREnumerated)
+        {
+            return (DEREnumerated)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DEREnumerated(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an Enumerated from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DEREnumerated getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    public DEREnumerated(
+        int         value)
+    {
+        bytes = BigInteger.valueOf(value).toByteArray();
+    }
+
+    public DEREnumerated(
+        BigInteger   value)
+    {
+        bytes = value.toByteArray();
+    }
+
+    public DEREnumerated(
+        byte[]   bytes)
+    {
+        this.bytes = bytes;
+    }
+
+    public BigInteger getValue()
+    {
+        return new BigInteger(bytes);
+    }
+
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        out.writeEncoded(ENUMERATED, bytes);
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof DEREnumerated))
+        {
+            return false;
+        }
+
+        DEREnumerated other = (DEREnumerated)o;
+
+        if (bytes.length != other.bytes.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != bytes.length; i++)
+        {
+            if (bytes[i] != other.bytes[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
+
+
+/**
+ * Generalized time object.
+ */
+class DERGeneralizedTime
+    extends DERObject
+{
+    String      time;
+
+    /**
+     * return a generalized time from the passed in object
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERGeneralizedTime getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERGeneralizedTime)
+        {
+            return (DERGeneralizedTime)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERGeneralizedTime(((ASN1OctetString)obj).getOctets());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a Generalized Time object from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERGeneralizedTime getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+    
+    /**
+     * The correct format for this is YYYYMMDDHHMMSSZ, or without the Z
+     * for local time, or Z+-HHMM on the end, for difference between local
+     * time and UTC time.
+     * <p>
+     *
+     * @param time the time string.
+     */
+    public DERGeneralizedTime(
+        String  time)
+    {
+        this.time = time;
+    }
+
+    /**
+     * base constructer from a java.util.date object
+     */
+    public DERGeneralizedTime(
+        Date time)
+    {
+        SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
+
+        dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
+
+        this.time = dateF.format(time);
+    }
+
+    DERGeneralizedTime(
+        byte[]  bytes)
+    {
+        //
+        // explicitly convert to characters
+        //
+        char[]  dateC = new char[bytes.length];
+
+        for (int i = 0; i != dateC.length; i++)
+        {
+            dateC[i] = (char)(bytes[i] & 0xff);
+        }
+
+        this.time = new String(dateC);
+    }
+
+    /**
+     * return the time - always in the form of 
+     *  YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
+     * <p>
+     * Normally in a certificate we would expect "Z" rather than "GMT",
+     * however adding the "GMT" means we can just use:
+     * <pre>
+     *     dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
+     * </pre>
+     * To read in the time and get a date which is compatible with our local
+     * time zone.
+     */
+    public String getTime()
+    {
+        //
+        // standardise the format.
+        //
+        if (time.length() == 15)
+        {
+            return time.substring(0, 14) + "GMT+00:00";
+        }
+        else if (time.length() == 17)
+        {
+            return time.substring(0, 14) + "GMT" + time.substring(15, 17) + ":" + time.substring(17, 19);
+        }
+
+        return time;
+    }
+
+    private byte[] getOctets()
+    {
+        char[]  cs = time.toCharArray();
+        byte[]  bs = new byte[cs.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            bs[i] = (byte)cs[i];
+        }
+
+        return bs;
+    }
+
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(GENERALIZED_TIME, this.getOctets());
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERGeneralizedTime))
+        {
+            return false;
+        }
+
+        return time.equals(((DERGeneralizedTime)o).time);
+    }
+}
+
+
+/**
+ * DER IA5String object - this is an ascii string.
+ */
+class DERIA5String
+    extends DERObject
+    implements DERString
+{
+    String  string;
+
+    /**
+     * return a IA5 string from the passed in object
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERIA5String getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERIA5String)
+        {
+            return (DERIA5String)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERIA5String(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an IA5 String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERIA5String getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    /**
+     * basic constructor - with bytes.
+     */
+    public DERIA5String(
+        byte[]   string)
+    {
+        char[]  cs = new char[string.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            cs[i] = (char)(string[i] & 0xff);
+        }
+
+        this.string = new String(cs);
+    }
+
+    /**
+     * basic constructor - with string.
+     */
+    public DERIA5String(
+        String   string)
+    {
+        this.string = string;
+    }
+
+    public String getString()
+    {
+        return string;
+    }
+
+    public byte[] getOctets()
+    {
+        char[]  cs = string.toCharArray();
+        byte[]  bs = new byte[cs.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            bs[i] = (byte)cs[i];
+        }
+
+        return bs; 
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(IA5_STRING, this.getOctets());
+    }
+
+    public int hashCode()
+    {
+        return this.getString().hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof DERIA5String))
+        {
+            return false;
+        }
+
+        DERIA5String  s = (DERIA5String)o;
+
+        return this.getString().equals(s.getString());
+    }
+}
+
+
+
+
+class DERInputStream
+    extends FilterInputStream implements DERTags
+{
+    public DERInputStream(
+        InputStream is)
+    {
+        super(is);
+    }
+
+    protected int readLength()
+        throws IOException
+    {
+        int length = read();
+        if (length < 0)
+        {
+            throw new IOException("EOF found when length expected");
+        }
+
+        if (length == 0x80)
+        {
+            return -1;      // indefinite-length encoding
+        }
+
+        if (length > 127)
+        {
+            int size = length & 0x7f;
+
+            length = 0;
+            for (int i = 0; i < size; i++)
+            {
+                int next = read();
+
+                if (next < 0)
+                {
+                    throw new IOException("EOF found reading length");
+                }
+
+                length = (length << 8) + next;
+            }
+        }
+
+        return length;
+    }
+
+    protected void readFully(
+        byte[]  bytes)
+        throws IOException
+    {
+        int     left = bytes.length;
+
+        if (left == 0)
+        {
+            return;
+        }
+
+        while ((left -= read(bytes, bytes.length - left, left)) != 0)
+        {
+            ;
+        }
+    }
+
+       /**
+        * build an object given its tag and a byte stream to construct it
+        * from.
+        */
+    protected DERObject buildObject(
+               int         tag,
+               byte[]  bytes)
+               throws IOException
+       {
+               switch (tag)
+        {
+        case NULL:
+            return null;   
+        case SEQUENCE | CONSTRUCTED:
+            ByteArrayInputStream    bIn = new ByteArrayInputStream(bytes);
+            BERInputStream          dIn = new BERInputStream(bIn);
+            DERConstructedSequence  seq = new DERConstructedSequence();
+
+            try
+            {
+                for (;;)
+                {
+                    DERObject   obj = dIn.readObject();
+
+                    seq.addObject(obj);
+                }
+            }
+            catch (EOFException ex)
+            {
+                return seq;
+            }
+        case SET | CONSTRUCTED:
+            bIn = new ByteArrayInputStream(bytes);
+            dIn = new BERInputStream(bIn);
+
+            DEREncodableVector    v = new DEREncodableVector();
+
+            try
+            {
+                for (;;)
+                {
+                    DERObject   obj = dIn.readObject();
+
+                    v.add(obj);
+                }
+            }
+            catch (EOFException ex)
+            {
+                return new DERConstructedSet(v);
+            }
+        case BOOLEAN:
+            return new DERBoolean(bytes);
+        case INTEGER:
+            return new DERInteger(bytes);
+        case ENUMERATED:
+            return new DEREnumerated(bytes);
+        case OBJECT_IDENTIFIER:
+            return new DERObjectIdentifier(bytes);
+        case BIT_STRING:
+            int     padBits = bytes[0];
+            byte[]  data = new byte[bytes.length - 1];
+
+            System.arraycopy(bytes, 1, data, 0, bytes.length - 1);
+
+            return new DERBitString(data, padBits);
+        case UTF8_STRING:
+            return new DERUTF8String(bytes);
+        case PRINTABLE_STRING:
+            return new DERPrintableString(bytes);
+        case IA5_STRING:
+            return new DERIA5String(bytes);
+        case T61_STRING:
+            return new DERT61String(bytes);
+        case VISIBLE_STRING:
+            return new DERVisibleString(bytes);
+        case UNIVERSAL_STRING:
+            return new DERUniversalString(bytes);
+        case BMP_STRING:
+            return new DERBMPString(bytes);
+        case OCTET_STRING:
+            return new DEROctetString(bytes);
+        case UTC_TIME:
+            return new DERUTCTime(bytes);
+        case GENERALIZED_TIME:
+            return new DERGeneralizedTime(bytes);
+        default:
+            //
+            // with tagged object tag number is bottom 5 bits
+            //
+            if ((tag & TAGGED) != 0)  
+            {
+                if ((tag & 0x1f) == 0x1f)
+                {
+                    throw new IOException("unsupported high tag encountered");
+                }
+
+                if (bytes.length == 0)        // empty tag!
+                {
+                    return new DERTaggedObject(false, tag & 0x1f, new DERConstructedSequence());
+                }
+
+                //
+                // simple type - implicit... return an octet string
+                //
+                if ((tag & CONSTRUCTED) == 0)
+                {
+                    return new DERTaggedObject(false, tag & 0x1f, new DEROctetString(bytes));
+                }
+
+                bIn = new ByteArrayInputStream(bytes);
+                dIn = new BERInputStream(bIn);
+
+                DEREncodable dObj = dIn.readObject();
+
+                //
+                // explicitly tagged (probably!) - if it isn't we'd have to
+                // tell from the context
+                //
+                if (dIn.available() == 0)
+                {
+                    return new DERTaggedObject(tag & 0x1f, dObj);
+                }
+
+                //
+                // another implicit object, we'll create a sequence...
+                //
+                seq = new DERConstructedSequence();
+
+                seq.addObject(dObj);
+
+                try
+                {
+                    for (;;)
+                    {
+                        dObj = dIn.readObject();
+
+                        seq.addObject(dObj);
+                    }
+                }
+                catch (EOFException ex)
+                {
+                    // ignore --
+                }
+
+                return new DERTaggedObject(false, tag & 0x1f, seq);
+            }
+
+            return new DERUnknownTag(tag, bytes);
+        }
+       }
+
+    public DERObject readObject()
+        throws IOException
+    {
+        int tag = read();
+        if (tag == -1)
+        {
+            throw new EOFException();
+        }
+
+        int     length = readLength();
+        byte[]  bytes = new byte[length];
+
+        readFully(bytes);
+
+               return buildObject(tag, bytes);
+       }
+}
+
+
+class DERInteger
+    extends DERObject
+{
+    byte[]      bytes;
+
+    /**
+     * return an integer from the passed in object
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERInteger getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERInteger)
+        {
+            return (DERInteger)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERInteger(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an Integer from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERInteger getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    public DERInteger(
+        int         value)
+    {
+        bytes = BigInteger.valueOf(value).toByteArray();
+    }
+
+    public DERInteger(
+        BigInteger   value)
+    {
+        bytes = value.toByteArray();
+    }
+
+    public DERInteger(
+        byte[]   bytes)
+    {
+        this.bytes = bytes;
+    }
+
+    public BigInteger getValue()
+    {
+        return new BigInteger(bytes);
+    }
+
+    /**
+     * in some cases positive values get crammed into a space,
+     * that's not quite big enough...
+     */
+    public BigInteger getPositiveValue()
+    {
+        return new BigInteger(1, bytes);
+    }
+
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        out.writeEncoded(INTEGER, bytes);
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof DERInteger))
+        {
+            return false;
+        }
+
+        DERInteger other = (DERInteger)o;
+
+        if (bytes.length != other.bytes.length)
+        {
+            return false;
+        }
+
+        for (int i = 0; i != bytes.length; i++)
+        {
+            if (bytes[i] != other.bytes[i])
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}
+
+
+abstract class DERObject
+    implements DERTags, DEREncodable
+{
+    public DERObject getDERObject()
+    {
+        return this;
+    }
+
+    abstract void encode(DEROutputStream out)
+        throws IOException;
+}
+
+
+class DERObjectIdentifier
+    extends DERObject
+{
+    String      identifier;
+
+    /**
+     * return an OID from the passed in object
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERObjectIdentifier getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERObjectIdentifier)
+        {
+            return (DERObjectIdentifier)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERObjectIdentifier(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an Object Identifier from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERObjectIdentifier getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+    
+
+    DERObjectIdentifier(
+        byte[]  bytes)
+    {
+        int             head = bytes[0] & 0xff;
+        StringBuffer    objId = new StringBuffer();
+        int             value = 0;
+        boolean         first = true;
+
+        for (int i = 0; i != bytes.length; i++)
+        {
+            int b = bytes[i] & 0xff;
+
+            value = value * 128 + (b & 0x7f);
+            if ((b & 0x80) == 0)             // end of number reached
+            {
+                if (first)
+                {
+                    switch (value / 40)
+                    {
+                    case 0:
+                        objId.append('0');
+                        break;
+                    case 1:
+                        objId.append('1');
+                        value -= 40;
+                        break;
+                    default:
+                        objId.append('2');
+                        value -= 80;
+                    }
+                    first = false;
+                }
+
+                objId.append('.');
+                objId.append(Integer.toString(value));
+                value = 0;
+            }
+        }
+
+        this.identifier = objId.toString();
+    }
+
+    public DERObjectIdentifier(
+        String  identifier)
+    {
+        this.identifier = identifier;
+    }
+
+    public String getId()
+    {
+        return identifier;
+    }
+
+    private void writeField(
+        OutputStream    out,
+        int             fieldValue)
+        throws IOException
+    {
+        if (fieldValue >= (1 << 7))
+        {
+            if (fieldValue >= (1 << 14))
+            {
+                if (fieldValue >= (1 << 21))
+                {
+                    if (fieldValue >= (1 << 28))
+                    {
+                        out.write((fieldValue >> 28) | 0x80);
+                    }
+                    out.write((fieldValue >> 21) | 0x80);
+                }
+                out.write((fieldValue >> 14) | 0x80);
+            }
+            out.write((fieldValue >> 7) | 0x80);
+        }
+        out.write(fieldValue & 0x7f);
+    }
+
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        OIDTokenizer            tok = new OIDTokenizer(identifier);
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+
+        writeField(bOut, 
+                    Integer.parseInt(tok.nextToken()) * 40
+                    + Integer.parseInt(tok.nextToken()));
+
+        while (tok.hasMoreTokens())
+        {
+            writeField(bOut, Integer.parseInt(tok.nextToken()));
+        }
+
+        dOut.close();
+
+        byte[]  bytes = bOut.toByteArray();
+
+        out.writeEncoded(OBJECT_IDENTIFIER, bytes);
+    }
+
+    public int hashCode()
+    {
+        return identifier.hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERObjectIdentifier))
+        {
+            return false;
+        }
+
+        return identifier.equals(((DERObjectIdentifier)o).identifier);
+    }
+}
+
+
+class DEROctetString
+    extends ASN1OctetString
+{
+    /**
+     * @param string the octets making up the octet string.
+     */
+    public DEROctetString(
+        byte[]  string)
+    {
+        super(string);
+    }
+
+    public DEROctetString(
+        DEREncodable  obj)
+    {
+        super(obj);
+    }
+
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        out.writeEncoded(OCTET_STRING, string);
+    }
+}
+
+
+
+class DEROutputStream
+    extends FilterOutputStream implements DERTags
+{
+    public DEROutputStream(
+        OutputStream    os)
+    {
+        super(os);
+    }
+
+    private void writeLength(
+        int length)
+        throws IOException
+    {
+        if (length > 127)
+        {
+            int size = 1;
+            int val = length;
+
+            while ((val >>>= 8) != 0)
+            {
+                size++;
+            }
+
+            write((byte)(size | 0x80));
+
+            for (int i = (size - 1) * 8; i >= 0; i -= 8)
+            {
+                write((byte)(length >> i));
+            }
+        }
+        else
+        {
+            write((byte)length);
+        }
+    }
+
+    void writeEncoded(
+        int     tag,
+        byte[]  bytes)
+        throws IOException
+    {
+        write(tag);
+        writeLength(bytes.length);
+        write(bytes);
+    }
+
+    protected void writeNull()
+        throws IOException
+    {
+        write(NULL);
+        write(0x00);
+    }
+
+    public void writeObject(
+        Object    obj)
+        throws IOException
+    {
+        if (obj == null)
+        {
+            writeNull();
+        }
+        else if (obj instanceof DERObject)
+        {
+            ((DERObject)obj).encode(this);
+        }
+        else if (obj instanceof DEREncodable)
+        {
+            ((DEREncodable)obj).getDERObject().encode(this);
+        }
+        else 
+        {
+            throw new IOException("object not DEREncodable");
+        }
+    }
+}
+
+
+/**
+ * DER PrintableString object.
+ */
+class DERPrintableString
+    extends DERObject
+    implements DERString
+{
+    String  string;
+
+    /**
+     * return a printable string from the passed in object.
+     * 
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERPrintableString getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERPrintableString)
+        {
+            return (DERPrintableString)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERPrintableString(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a Printable String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERPrintableString getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    /**
+     * basic constructor - byte encoded string.
+     */
+    public DERPrintableString(
+        byte[]   string)
+    {
+        char[]  cs = new char[string.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            cs[i] = (char)(string[i] & 0xff);
+        }
+
+        this.string = new String(cs);
+    }
+
+    /**
+     * basic constructor
+     */
+    public DERPrintableString(
+        String   string)
+    {
+        this.string = string;
+    }
+
+    public String getString()
+    {
+        return string;
+    }
+
+    public byte[] getOctets()
+    {
+        char[]  cs = string.toCharArray();
+        byte[]  bs = new byte[cs.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            bs[i] = (byte)cs[i];
+        }
+
+        return bs; 
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(PRINTABLE_STRING, this.getOctets());
+    }
+
+    public int hashCode()
+    {
+        return this.getString().hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof DERPrintableString))
+        {
+            return false;
+        }
+
+        DERPrintableString  s = (DERPrintableString)o;
+
+        return this.getString().equals(s.getString());
+    }
+}
+
+
+class DERSequence
+    extends ASN1Sequence
+{
+    /**
+     * create an empty sequence
+     */
+    public DERSequence()
+    {
+    }
+
+    /**
+     * create a sequence containing one object
+     */
+    public DERSequence(
+        DEREncodable    obj)
+    {
+        this.addObject(obj);
+    }
+
+    /**
+     * create a sequence containing a vector of objects.
+     */
+    public DERSequence(
+        DEREncodableVector   v)
+    {
+        for (int i = 0; i != v.size(); i++)
+        {
+            this.addObject(v.get(i));
+        }
+    }
+
+    /*
+     * A note on the implementation:
+     * <p>
+     * As DER requires the constructed, definite-length model to
+     * be used for structured types, this varies slightly from the
+     * ASN.1 descriptions given. Rather than just outputing SEQUENCE,
+     * we also have to specify CONSTRUCTED, and the objects length.
+     */
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+        Enumeration             e = this.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            Object    obj = e.nextElement();
+
+            dOut.writeObject(obj);
+        }
+
+        dOut.close();
+
+        byte[]  bytes = bOut.toByteArray();
+
+        out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes);
+    }
+}
+
+
+/**
+ * A DER encoded set object
+ */
+class DERSet
+    extends ASN1Set
+{
+    /**
+     * create an empty set
+     */
+    public DERSet()
+    {
+    }
+
+    /**
+     * @param obj - a single object that makes up the set.
+     */
+    public DERSet(
+        DEREncodable   obj)
+    {
+        this.addObject(obj);
+    }
+
+    /**
+     * @param v - a vector of objects making up the set.
+     */
+    public DERSet(
+        DEREncodableVector   v)
+    {
+        for (int i = 0; i != v.size(); i++)
+        {
+            this.addObject(v.get(i));
+        }
+    }
+
+    /*
+     * A note on the implementation:
+     * <p>
+     * As DER requires the constructed, definite-length model to
+     * be used for structured types, this varies slightly from the
+     * ASN.1 descriptions given. Rather than just outputing SET,
+     * we also have to specify CONSTRUCTED, and the objects length.
+     */
+    void encode(
+        DEROutputStream out)
+        throws IOException
+    {
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+        DEROutputStream         dOut = new DEROutputStream(bOut);
+        Enumeration             e = this.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            Object    obj = e.nextElement();
+
+            dOut.writeObject(obj);
+        }
+
+        dOut.close();
+
+        byte[]  bytes = bOut.toByteArray();
+
+        out.writeEncoded(SET | CONSTRUCTED, bytes);
+    }
+}
+
+/**
+ * basic interface for DER string objects.
+ */
+interface DERString
+{
+    public String getString();
+}
+
+
+/**
+ * DER T61String (also the teletex string)
+ */
+class DERT61String
+    extends DERObject
+    implements DERString
+{
+    String  string;
+
+    /**
+     * return a T61 string from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERT61String getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERT61String)
+        {
+            return (DERT61String)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERT61String(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an T61 String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERT61String getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    /**
+     * basic constructor - with bytes.
+     */
+    public DERT61String(
+        byte[]   string)
+    {
+        this.string = new String(string);
+    }
+
+    /**
+     * basic constructor - with string.
+     */
+    public DERT61String(
+        String   string)
+    {
+        this.string = string;
+    }
+
+    public String getString()
+    {
+        return string;
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(T61_STRING, string.getBytes());
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERT61String))
+        {
+            return false;
+        }
+
+        return this.getString().equals(((DERT61String)o).getString());
+    }
+}
+
+
+/**
+ * DER TaggedObject - in ASN.1 nottation this is any object proceeded by
+ * a [n] where n is some number - these are assume to follow the construction
+ * rules (as with sequences).
+ */
+class DERTaggedObject
+    extends ASN1TaggedObject
+{
+    /**
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public DERTaggedObject(
+        int             tagNo,
+        DEREncodable    obj)
+    {
+               super(tagNo, obj);
+    }
+
+    /**
+     * @param explicit true if an explicitly tagged object.
+     * @param tagNo the tag number for this object.
+     * @param obj the tagged object.
+     */
+    public DERTaggedObject(
+        boolean         explicit,
+        int             tagNo,
+        DEREncodable    obj)
+    {
+               super(explicit, tagNo, obj);
+    }
+
+    /**
+     * create an implicitly tagged object that contains a zero
+     * length sequence.
+     */
+    public DERTaggedObject(
+        int             tagNo)
+    {
+        super(false, tagNo, new DERSequence());
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        if (!empty)
+        {
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+
+            dOut.writeObject(obj);
+            dOut.close();
+
+            byte[]  bytes = bOut.toByteArray();
+
+            if (explicit)
+            {
+                out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, bytes);
+            }
+            else
+            {
+                //
+                // need to mark constructed types...
+                //
+                if ((bytes[0] & CONSTRUCTED) != 0)
+                {
+                    bytes[0] = (byte)(CONSTRUCTED | TAGGED | tagNo);
+                }
+                else
+                {
+                    bytes[0] = (byte)(TAGGED | tagNo);
+                }
+
+                out.write(bytes);
+            }
+        }
+        else
+        {
+            out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, new byte[0]);
+        }
+    }
+}
+
+interface DERTags
+{
+    public static final int BOOLEAN             = 0x01;
+    public static final int INTEGER             = 0x02;
+    public static final int BIT_STRING          = 0x03;
+    public static final int OCTET_STRING        = 0x04;
+    public static final int NULL                = 0x05;
+    public static final int OBJECT_IDENTIFIER   = 0x06;
+    public static final int EXTERNAL            = 0x08;
+    public static final int ENUMERATED          = 0x0a;
+    public static final int SEQUENCE            = 0x10;
+    public static final int SEQUENCE_OF         = 0x10; // for completeness
+    public static final int SET                 = 0x11;
+    public static final int SET_OF              = 0x11; // for completeness
+    public static final int CONSTRUCTED         = 0x20;
+    public static final int TAGGED              = 0x80;
+
+    public static final int NUMERIC_STRING      = 0x12;
+    public static final int PRINTABLE_STRING    = 0x13;
+    public static final int T61_STRING          = 0x14;
+    public static final int VIDEOTEX_STRING     = 0x15;
+    public static final int IA5_STRING          = 0x16;
+    public static final int UTC_TIME            = 0x17;
+    public static final int GENERALIZED_TIME    = 0x18;
+    public static final int GRAPHIC_STRING      = 0x19;
+    public static final int VISIBLE_STRING      = 0x1a;
+    public static final int GENERAL_STRING      = 0x1b;
+    public static final int UNIVERSAL_STRING    = 0x1c;
+    public static final int BMP_STRING          = 0x1e;
+    public static final int UTF8_STRING         = 0x0c;
+}
+
+
+/**
+ * DER UniversalString object.
+ */
+class DERUniversalString
+    extends DERObject
+    implements DERString
+{
+    byte[]  string;
+    char[]  table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+    /**
+     * return a Universal String from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERUniversalString getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERUniversalString)
+        {
+            return (DERUniversalString)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERUniversalString(((ASN1OctetString)obj).getOctets());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a Universal String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERUniversalString getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    /**
+     * basic constructor - byte encoded string.
+     */
+    public DERUniversalString(
+        byte[]   string)
+    {
+        this.string = string;
+    }
+
+    /**
+     * UniversalStrings have characters which are 4 bytes long - for the
+     * moment we just return them in Hex...
+     */
+    public String getString()
+    {
+        StringBuffer    buf = new StringBuffer();
+
+        for (int i = 0; i != string.length; i++)
+        {
+            buf.append(table[(string[i] >>> 4) % 0xf]);
+            buf.append(table[string[i] & 0xf]);
+        }
+
+        return buf.toString();
+    }
+
+    public byte[] getOctets()
+    {
+        return string;
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(UNIVERSAL_STRING, this.getOctets());
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERUniversalString))
+        {
+            return false;
+        }
+
+        return this.getString().equals(((DERUniversalString)o).getString());
+    }
+}
+
+
+/**
+ * We insert one of these when we find a tag we don't recognise.
+ */
+class DERUnknownTag
+    extends DERObject
+{
+    int         tag;
+    byte[]      data;
+
+    /**
+     * @param tag the tag value.
+     * @param data the octets making up the time.
+     */
+    public DERUnknownTag(
+        int     tag,
+        byte[]  data)
+    {
+        this.tag = tag;
+        this.data = data;
+    }
+
+    public int getTag()
+    {
+        return tag;
+    }
+
+    public byte[] getData()
+    {
+        return data;
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(tag, data);
+    }
+    
+       public boolean equals(
+               Object o)
+       {
+        if ((o == null) || !(o instanceof DERUnknownTag))
+        {
+            return false;
+        }
+        
+        DERUnknownTag other = (DERUnknownTag)o;
+        
+        if(tag != other.tag)
+        {
+                       return false;
+               }
+               
+               if(data.length != other.data.length)
+               {
+                       return false;
+               }
+               
+               for(int i = 0; i < data.length; i++) 
+               {
+                       if(data[i] != other.data[i])
+                       {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+}
+
+
+/**
+ * UTC time object.
+ */
+class DERUTCTime
+    extends DERObject
+{
+    String      time;
+
+    /**
+     * return an UTC Time from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERUTCTime getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERUTCTime)
+        {
+            return (DERUTCTime)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERUTCTime(((ASN1OctetString)obj).getOctets());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an UTC Time from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERUTCTime getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+    
+    /**
+     * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
+     * never encoded. When you're creating one of these objects from scratch, that's
+     * what you want to use, otherwise we'll try to deal with whatever gets read from
+     * the input stream... (this is why the input format is different from the getTime()
+     * method output).
+     * <p>
+     *
+     * @param time the time string.
+     */
+    public DERUTCTime(
+        String  time)
+    {
+        this.time = time;
+    }
+
+    /**
+     * base constructer from a java.util.date object
+     */
+    public DERUTCTime(
+       Date time)
+    {
+        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'");
+
+        dateF.setTimeZone(new SimpleTimeZone(0,"Z"));
+
+        this.time = dateF.format(time);
+    }
+
+    DERUTCTime(
+        byte[]  bytes)
+    {
+        //
+        // explicitly convert to characters
+        //
+        char[]  dateC = new char[bytes.length];
+
+        for (int i = 0; i != dateC.length; i++)
+        {
+            dateC[i] = (char)(bytes[i] & 0xff);
+        }
+
+        this.time = new String(dateC);
+    }
+
+    /**
+     * return the time - always in the form of 
+     *  YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
+     * <p>
+     * Normally in a certificate we would expect "Z" rather than "GMT",
+     * however adding the "GMT" means we can just use:
+     * <pre>
+     *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
+     * </pre>
+     * To read in the time and get a date which is compatible with our local
+     * time zone.
+     * <p>
+     * <b>Note:</b> In some cases, due to the local date processing, this
+     * may lead to unexpected results. If you want to stick the normal
+     * convention of 1950 to 2049 use the getAdjustedTime() method.
+     */
+    public String getTime()
+    {
+        //
+        // standardise the format.
+        //
+        if (time.length() == 11)
+        {
+            return time.substring(0, 10) + "00GMT+00:00";
+        }
+        else if (time.length() == 13)
+        {
+            return time.substring(0, 12) + "GMT+00:00";
+        }
+        else if (time.length() == 17)
+        {
+            return time.substring(0, 12) + "GMT" + time.substring(12, 15) + ":" + time.substring(15, 17);
+        }
+
+        return time;
+    }
+
+    /**
+     * return the time as an adjusted date with a 4 digit year. This goes
+     * in the range of 1950 - 2049.
+     */
+    public String getAdjustedTime()
+    {
+        String   d = this.getTime();
+
+        if (d.charAt(0) < '5')
+        {
+            return "20" + d;
+        }
+        else
+        {
+            return "19" + d;
+        }
+    }
+
+    private byte[] getOctets()
+    {
+        char[]  cs = time.toCharArray();
+        byte[]  bs = new byte[cs.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            bs[i] = (byte)cs[i];
+        }
+
+        return bs;
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(UTC_TIME, this.getOctets());
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERUTCTime))
+        {
+            return false;
+        }
+
+        return time.equals(((DERUTCTime)o).time);
+    }
+}
+
+
+/**
+ * DER UTF8String object.
+ */
+class DERUTF8String
+    extends DERObject
+    implements DERString
+{
+    String  string;
+
+    /**
+     * return an UTF8 string from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERUTF8String getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERUTF8String)
+        {
+            return (DERUTF8String)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERUTF8String(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return an UTF8 String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERUTF8String getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    /**
+     * basic constructor - byte encoded string.
+     */
+    DERUTF8String(
+        byte[]   string)
+    {
+        int i = 0;
+        int length = 0;
+
+        while (i < string.length)
+        {
+            length++;
+            if ((string[i] & 0xe0) == 0xe0)
+            {
+                i += 3;
+            }
+            else if ((string[i] & 0xc0) == 0xc0)
+            {
+                i += 2;
+            }
+            else
+            {
+                i += 1;
+            }
+        }
+
+        char[]  cs = new char[length];
+
+        i = 0;
+        length = 0;
+
+        while (i < string.length)
+        {
+            char    ch;
+
+            if ((string[i] & 0xe0) == 0xe0)
+            {
+                ch = (char)(((string[i] & 0x1f) << 12)
+                      | ((string[i + 1] & 0x3f) << 6) | (string[i + 2] & 0x3f));
+                i += 3;
+            }
+            else if ((string[i] & 0xc0) == 0xc0)
+            {
+                ch = (char)(((string[i] & 0x3f) << 6) | (string[i + 1] & 0x3f));
+                i += 2;
+            }
+            else
+            {
+                ch = (char)(string[i] & 0xff);
+                i += 1;
+            }
+
+            cs[length++] = ch;
+        }
+
+        this.string = new String(cs);
+    }
+
+    /**
+     * basic constructor
+     */
+    public DERUTF8String(
+        String   string)
+    {
+        this.string = string;
+    }
+
+    public String getString()
+    {
+        return string;
+    }
+
+    public int hashCode()
+    {
+        return this.getString().hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (!(o instanceof DERUTF8String))
+        {
+            return false;
+        }
+
+        DERUTF8String  s = (DERUTF8String)o;
+
+        return this.getString().equals(s.getString());
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        char[]                  c = string.toCharArray();
+        ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+
+        for (int i = 0; i != c.length; i++)
+        {
+            char    ch = c[i];
+
+            if (ch < 0x0080)
+            {
+                bOut.write(ch);
+            }
+            else if (ch < 0x0800)
+            {
+                bOut.write(0xc0 | (ch >> 6));
+                bOut.write(0x80 | (ch & 0x3f));
+            }
+            else
+            {
+                bOut.write(0xe0 | (ch >> 12));
+                bOut.write(0x80 | ((ch >> 6) & 0x3F));
+                bOut.write(0x80 | (ch & 0x3F));
+            }
+        }
+
+        out.writeEncoded(UTF8_STRING, bOut.toByteArray());
+    }
+}
+
+
+/**
+ * DER VisibleString object.
+ */
+class DERVisibleString
+    extends DERObject
+    implements DERString
+{
+    String  string;
+
+    /**
+     * return a Visible String from the passed in object.
+     *
+     * @exception IllegalArgumentException if the object cannot be converted.
+     */
+    public static DERVisibleString getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof DERVisibleString)
+        {
+            return (DERVisibleString)obj;
+        }
+
+        if (obj instanceof ASN1OctetString)
+        {
+            return new DERVisibleString(((ASN1OctetString)obj).getOctets());
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * return a Visible String from a tagged object.
+     *
+     * @param obj the tagged object holding the object we want
+     * @param explicit true if the object is meant to be explicitly
+     *              tagged false otherwise.
+     * @exception IllegalArgumentException if the tagged object cannot
+     *               be converted.
+     */
+    public static DERVisibleString getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    /**
+     * basic constructor - byte encoded string.
+     */
+    public DERVisibleString(
+        byte[]   string)
+    {
+        char[]  cs = new char[string.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            cs[i] = (char)(string[i] & 0xff);
+        }
+
+        this.string = new String(cs);
+    }
+
+    /**
+     * basic constructor
+     */
+    public DERVisibleString(
+        String   string)
+    {
+        this.string = string;
+    }
+
+    public String getString()
+    {
+        return string;
+    }
+
+    public byte[] getOctets()
+    {
+        char[]  cs = string.toCharArray();
+        byte[]  bs = new byte[cs.length];
+
+        for (int i = 0; i != cs.length; i++)
+        {
+            bs[i] = (byte)cs[i];
+        }
+
+        return bs;
+    }
+
+    void encode(
+        DEROutputStream  out)
+        throws IOException
+    {
+        out.writeEncoded(VISIBLE_STRING, this.getOctets());
+    }
+    
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof DERVisibleString))
+        {
+            return false;
+        }
+
+        return this.getString().equals(((DERVisibleString)o).getString());
+    }
+}
+
+/**
+ * class for breaking up an OID into it's component tokens, ala
+ * java.util.StringTokenizer. We need this class as some of the
+ * lightweight Java environment don't support classes like
+ * StringTokenizer.
+ */
+class OIDTokenizer
+{
+    private String  oid;
+    private int     index;
+
+    public OIDTokenizer(
+        String oid)
+    {
+        this.oid = oid;
+        this.index = 0;
+    }
+
+    public boolean hasMoreTokens()
+    {
+        return (index != -1);
+    }
+
+    public String nextToken()
+    {
+        if (index == -1)
+        {
+            return null;
+        }
+
+        String  token;
+        int     end = oid.indexOf('.', index);
+
+        if (end == -1)
+        {
+            token = oid.substring(index);
+            index = -1;
+            return token;
+        }
+
+        token = oid.substring(index, end);
+
+        index = end + 1;
+        return token;
+    }
+}
+
+
+interface PKCSObjectIdentifiers
+{
+    //
+    // pkcs-1 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 }
+    //
+    static final String                 pkcs_1                  = "1.2.840.113549.1.1";
+    static final DERObjectIdentifier    rsaEncryption           = new DERObjectIdentifier(pkcs_1 + ".1");
+    static final DERObjectIdentifier    md2WithRSAEncryption    = new DERObjectIdentifier(pkcs_1 + ".2");
+    static final DERObjectIdentifier    md4WithRSAEncryption    = new DERObjectIdentifier(pkcs_1 + ".3");
+    static final DERObjectIdentifier    md5WithRSAEncryption    = new DERObjectIdentifier(pkcs_1 + ".4");
+    static final DERObjectIdentifier    sha1WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".5");
+    static final DERObjectIdentifier    srsaOAEPEncryptionSET   = new DERObjectIdentifier(pkcs_1 + ".6");
+    static final DERObjectIdentifier    sha256WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".11");
+    static final DERObjectIdentifier    sha384WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".12");
+    static final DERObjectIdentifier    sha512WithRSAEncryption   = new DERObjectIdentifier(pkcs_1 + ".13");
+
+    //
+    // pkcs-3 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 3 }
+    //
+    static final String                 pkcs_3                  = "1.2.840.113549.1.3";
+    static final DERObjectIdentifier    dhKeyAgreement          = new DERObjectIdentifier(pkcs_3 + ".1");
+
+    //
+    // pkcs-5 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 5 }
+    //
+    static final String                 pkcs_5                  = "1.2.840.113549.1.5";
+
+    static final DERObjectIdentifier    id_PBES2                = new DERObjectIdentifier(pkcs_5 + ".13");
+
+    static final DERObjectIdentifier    id_PBKDF2               = new DERObjectIdentifier(pkcs_5 + ".12");
+
+    //
+    // encryptionAlgorithm OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) 3 }
+    //
+    static final String                 encryptionAlgorithm     = "1.2.840.113549.3";
+
+    static final DERObjectIdentifier    des_EDE3_CBC            = new DERObjectIdentifier(encryptionAlgorithm + ".7");
+    static final DERObjectIdentifier    RC2_CBC                 = new DERObjectIdentifier(encryptionAlgorithm + ".2");
+
+    //
+    // object identifiers for digests
+    //
+
+    //
+    // md2 OBJECT IDENTIFIER ::=
+    //      {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 2}
+    //
+    static final DERObjectIdentifier    md2                     = new DERObjectIdentifier("1.2.840.113549.2.2");
+
+    //
+    // md5 OBJECT IDENTIFIER ::=
+    //      {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 5}
+    //
+    static final DERObjectIdentifier    md5                     = new DERObjectIdentifier("1.2.840.113549.2.5");
+
+    //
+    // pkcs-7 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 }
+    //
+    static final String                 pkcs_7                  = "1.2.840.113549.1.7";
+    static final DERObjectIdentifier    data                    = new DERObjectIdentifier(pkcs_7 + ".1");
+    static final DERObjectIdentifier    signedData              = new DERObjectIdentifier(pkcs_7 + ".2");
+    static final DERObjectIdentifier    envelopedData           = new DERObjectIdentifier(pkcs_7 + ".3");
+    static final DERObjectIdentifier    signedAndEnvelopedData  = new DERObjectIdentifier(pkcs_7 + ".4");
+    static final DERObjectIdentifier    digestedData            = new DERObjectIdentifier(pkcs_7 + ".5");
+    static final DERObjectIdentifier    encryptedData           = new DERObjectIdentifier(pkcs_7 + ".6");
+
+    //
+    // pkcs-9 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
+    //
+    static final String                 pkcs_9                  = "1.2.840.113549.1.9";
+
+    static final DERObjectIdentifier    pkcs_9_at_emailAddress  = new DERObjectIdentifier(pkcs_9 + ".1");
+    static final DERObjectIdentifier    pkcs_9_at_unstructuredName = new DERObjectIdentifier(pkcs_9 + ".2");
+    static final DERObjectIdentifier    pkcs_9_at_contentType = new DERObjectIdentifier(pkcs_9 + ".3");
+    static final DERObjectIdentifier    pkcs_9_at_messageDigest = new DERObjectIdentifier(pkcs_9 + ".4");
+    static final DERObjectIdentifier    pkcs_9_at_signingTime = new DERObjectIdentifier(pkcs_9 + ".5");
+    static final DERObjectIdentifier    pkcs_9_at_counterSignature = new DERObjectIdentifier(pkcs_9 + ".6");
+    static final DERObjectIdentifier    pkcs_9_at_challengePassword = new DERObjectIdentifier(pkcs_9 + ".7");
+    static final DERObjectIdentifier    pkcs_9_at_unstructuredAddress = new DERObjectIdentifier(pkcs_9 + ".8");
+    static final DERObjectIdentifier    pkcs_9_at_extendedCertificateAttributes = new DERObjectIdentifier(pkcs_9 + ".9");
+
+    static final DERObjectIdentifier    pkcs_9_at_signingDescription = new DERObjectIdentifier(pkcs_9 + ".13");
+    static final DERObjectIdentifier    pkcs_9_at_extensionRequest = new DERObjectIdentifier(pkcs_9 + ".14");
+    static final DERObjectIdentifier    pkcs_9_at_smimeCapabilities = new DERObjectIdentifier(pkcs_9 + ".15");
+
+    static final DERObjectIdentifier    pkcs_9_at_friendlyName  = new DERObjectIdentifier(pkcs_9 + ".20");
+    static final DERObjectIdentifier    pkcs_9_at_localKeyId    = new DERObjectIdentifier(pkcs_9 + ".21");
+
+    static final DERObjectIdentifier    x509certType            = new DERObjectIdentifier(pkcs_9 + ".22.1");
+
+    //
+    // SMIME capability sub oids.
+    //
+    static final DERObjectIdentifier    preferSignedData        = new DERObjectIdentifier(pkcs_9 + ".15.1");
+    static final DERObjectIdentifier    canNotDecryptAny        = new DERObjectIdentifier(pkcs_9 + ".15.2");
+    static final DERObjectIdentifier    sMIMECapabilitiesVersions = new DERObjectIdentifier(pkcs_9 + ".15.3");
+
+    //
+    // other SMIME attributes
+    //
+
+       //
+       // id-aa OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840)
+       // rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) attributes(2)}
+       //
+       static String id_aa = "1.2.840.113549.1.9.16.2";
+       
+       /*
+        * id-aa-encrypKeyPref OBJECT IDENTIFIER ::= {id-aa 11}
+        * 
+        */
+       static DERObjectIdentifier id_aa_encrypKeyPref = new DERObjectIdentifier(id_aa + ".11");
+
+    //
+    // pkcs-12 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 12 }
+    //
+    static final String                 pkcs_12                  = "1.2.840.113549.1.12";
+    static final String                 bagtypes                 = pkcs_12 + ".10.1";
+
+    static final DERObjectIdentifier    keyBag                  = new DERObjectIdentifier(bagtypes + ".1");
+    static final DERObjectIdentifier    pkcs8ShroudedKeyBag     = new DERObjectIdentifier(bagtypes + ".2");
+    static final DERObjectIdentifier    certBag                 = new DERObjectIdentifier(bagtypes + ".3");
+    static final DERObjectIdentifier    crlBag                  = new DERObjectIdentifier(bagtypes + ".4");
+    static final DERObjectIdentifier    secretBag               = new DERObjectIdentifier(bagtypes + ".5");
+    static final DERObjectIdentifier    safeContentsBag         = new DERObjectIdentifier(bagtypes + ".6");
+}
+
+
+
+
+class AlgorithmIdentifier
+    implements DEREncodable
+{
+    private DERObjectIdentifier objectId;
+    private DEREncodable        parameters;
+    private boolean             parametersDefined = false;
+       
+    public static AlgorithmIdentifier getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+    
+    public static AlgorithmIdentifier getInstance(
+        Object  obj)
+    {
+        if (obj instanceof AlgorithmIdentifier)
+        {
+            return (AlgorithmIdentifier)obj;
+        }
+        
+        if (obj instanceof DERObjectIdentifier)
+        {
+            return new AlgorithmIdentifier((DERObjectIdentifier)obj);
+        }
+
+        if (obj instanceof String)
+        {
+            return new AlgorithmIdentifier((String)obj);
+        }
+
+        if (obj instanceof ASN1Sequence)
+        {
+            return new AlgorithmIdentifier((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public AlgorithmIdentifier(
+        DERObjectIdentifier     objectId)
+    {
+        this.objectId = objectId;
+    }
+
+    public AlgorithmIdentifier(
+        String     objectId)
+    {
+        this.objectId = new DERObjectIdentifier(objectId);
+    }
+
+    public AlgorithmIdentifier(
+        DERObjectIdentifier     objectId,
+        DEREncodable            parameters)
+    {
+        parametersDefined = true;
+        this.objectId = objectId;
+        this.parameters = parameters;
+    }
+
+    public AlgorithmIdentifier(
+        ASN1Sequence   seq)
+    {
+        objectId = (DERObjectIdentifier)seq.getObjectAt(0);
+
+        if (seq.size() == 2)
+        {
+            parametersDefined = true;
+            parameters = seq.getObjectAt(1);
+        }
+        else
+        {
+            parameters = null;
+        }
+    }
+
+    public DERObjectIdentifier getObjectId()
+    {
+        return objectId;
+    }
+
+    public DEREncodable getParameters()
+    {
+        return parameters;
+    }
+
+    /**
+     * <pre>
+     *      AlgorithmIdentifier ::= SEQUENCE {
+     *                            algorithm OBJECT IDENTIFIER,
+     *                            parameters ANY DEFINED BY algorithm OPTIONAL }
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(objectId);
+
+        if (parametersDefined)
+        {
+            seq.addObject(parameters);
+        }
+
+        return seq;
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if ((o == null) || !(o instanceof AlgorithmIdentifier))
+        {
+            return false;
+        }
+
+        AlgorithmIdentifier other = (AlgorithmIdentifier)o;
+
+        if (!this.getObjectId().equals(other.getObjectId()))
+        {
+            return false;
+        }
+
+        if (this.getParameters() == null && other.getParameters() == null)
+        {
+            return true;
+        }
+
+        if (this.getParameters() == null || other.getParameters() == null)
+        {
+            return false;
+        }
+
+        ByteArrayOutputStream   b1Out = new ByteArrayOutputStream();
+        ByteArrayOutputStream   b2Out = new ByteArrayOutputStream();
+        DEROutputStream         d1Out = new DEROutputStream(b1Out);
+        DEROutputStream         d2Out = new DEROutputStream(b2Out);
+
+        try
+        {
+            d1Out.writeObject(this.getParameters());
+            d2Out.writeObject(other.getParameters());
+
+            byte[]  b1 = b1Out.toByteArray();
+            byte[]  b2 = b2Out.toByteArray();
+
+            if (b1.length != b2.length)
+            {
+                return false;
+            }
+
+            for (int i = 0; i != b1.length; i++)
+            {
+                if (b1[i] != b2[i])
+                {
+                    return false;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            return false;
+        }
+
+        return true;
+    }
+}
+
+
+
+
+/**
+ * <pre>
+ * id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::=  { id-ce 35 }
+ *
+ *   AuthorityKeyIdentifier ::= SEQUENCE {
+ *      keyIdentifier             [0] IMPLICIT KeyIdentifier           OPTIONAL,
+ *      authorityCertIssuer       [1] IMPLICIT GeneralNames            OPTIONAL,
+ *      authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL  }
+ *
+ *   KeyIdentifier ::= OCTET STRING
+ * </pre>
+ *
+ */
+class AuthorityKeyIdentifier
+    implements DEREncodable, DERTags
+{
+    ASN1OctetString keyidentifier=null;
+    GeneralNames certissuer=null;
+    DERInteger certserno=null;
+
+    public static AuthorityKeyIdentifier getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static AuthorityKeyIdentifier getInstance(
+        Object  obj)
+    {
+        if (obj instanceof AuthorityKeyIdentifier)
+        {
+            return (AuthorityKeyIdentifier)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new AuthorityKeyIdentifier((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+       
+    public AuthorityKeyIdentifier(
+        ASN1Sequence   seq)
+    {
+        Enumeration     e = seq.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            DERTaggedObject o = (DERTaggedObject)e.nextElement();
+
+            switch (o.getTagNo())
+            {
+            case 0:
+                this.keyidentifier = ASN1OctetString.getInstance(o, false);
+                break;
+            case 1:
+                this.certissuer = GeneralNames.getInstance(o, false);
+                break;
+            case 2:
+                this.certserno = DERInteger.getInstance(o, false);
+                break;
+            default:
+                throw new IllegalArgumentException("illegal tag");
+            }
+        }
+    }
+
+    /**
+     *
+     * Calulates the keyidentifier using a SHA1 hash over the BIT STRING
+     * from SubjectPublicKeyInfo as defined in RFC2459.
+     *
+     * Example of making a AuthorityKeyIdentifier:
+     * <pre>
+     *   SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((DERConstructedSequence)new DERInputStream(
+     *       new ByteArrayInputStream(publicKey.getEncoded())).readObject());
+     *   AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);
+     * </pre>
+     *
+     **/
+    public AuthorityKeyIdentifier(
+        SubjectPublicKeyInfo    spki)
+    {
+        Digest  digest = new SHA1Digest();
+        byte[]  resBuf = new byte[digest.getDigestSize()];
+
+        byte[] bytes = spki.getPublicKeyData().getBytes();
+        digest.update(bytes, 0, bytes.length);
+        digest.doFinal(resBuf, 0);
+        this.keyidentifier = new DEROctetString(resBuf);
+    }
+
+    /**
+     * create an AuthorityKeyIdentifier with the GeneralNames tag and
+     * the serial number provided as well.
+     */
+    public AuthorityKeyIdentifier(
+        SubjectPublicKeyInfo    spki,
+        GeneralNames            name,
+        BigInteger              serialNumber)
+    {
+        Digest  digest = new SHA1Digest();
+        byte[]  resBuf = new byte[digest.getDigestSize()];
+
+        byte[] bytes = spki.getPublicKeyData().getBytes();
+        digest.update(bytes, 0, bytes.length);
+        digest.doFinal(resBuf, 0);
+
+        this.keyidentifier = new DEROctetString(resBuf);
+        this.certissuer = name;
+        this.certserno = new DERInteger(serialNumber);
+    }
+
+    public byte[] getKeyIdentifier()
+    {
+        if (keyidentifier != null)
+        {
+            return keyidentifier.getOctets();
+        }
+
+        return null;
+    }
+
+     /**
+     * <pre>
+     *   AuthorityKeyIdentifier ::= SEQUENCE {
+     *      keyIdentifier             [0] IMPLICIT KeyIdentifier           OPTIONAL,
+     *      authorityCertIssuer       [1] IMPLICIT GeneralNames            OPTIONAL,
+     *      authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL  }
+     *
+     *   KeyIdentifier ::= OCTET STRING
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        if (keyidentifier != null)
+        {
+            seq.addObject(new DERTaggedObject(false, 0, keyidentifier));
+        }
+
+        if (certissuer != null)
+        {
+            seq.addObject(new DERTaggedObject(false, 1, certissuer));
+        }
+
+        if (certserno != null)
+        {
+            seq.addObject(new DERTaggedObject(false, 2, certserno));
+        }
+
+
+        return seq;
+    }
+
+    public String toString()
+    {
+        return ("AuthorityKeyIdentifier: KeyID(" + this.keyidentifier.getOctets() + ")");
+    }
+}
+
+
+
+class BasicConstraints
+    implements DEREncodable
+{
+    DERBoolean  cA = new DERBoolean(false);
+    DERInteger  pathLenConstraint = null;
+
+    public static BasicConstraints getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static BasicConstraints getInstance(
+        Object  obj)
+    {
+        if (obj instanceof BasicConstraints)
+        {
+            return (BasicConstraints)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new BasicConstraints((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+       
+    public BasicConstraints(
+        ASN1Sequence   seq)
+    {
+        if (seq.size() != 0)
+        {
+            this.cA = (DERBoolean)seq.getObjectAt(0);
+            this.pathLenConstraint = (DERInteger)seq.getObjectAt(1);
+        }
+    }
+
+    public BasicConstraints(
+        boolean cA,
+        int     pathLenConstraint)
+    {
+        this.cA = new DERBoolean(cA);
+        this.pathLenConstraint = new DERInteger(pathLenConstraint);
+    }
+
+    public BasicConstraints(
+        boolean cA)
+    {
+        this.cA = new DERBoolean(cA);
+        this.pathLenConstraint = null;
+    }
+
+    public boolean isCA()
+    {
+        return cA.isTrue();
+    }
+
+    public BigInteger getPathLenConstraint()
+    {
+        if (pathLenConstraint != null)
+        {
+            return pathLenConstraint.getValue();
+        }
+
+        return null;
+    }
+
+    /**
+     * <pre>
+     * BasicConstraints := SEQUENCE {
+     *    cA                  BOOLEAN DEFAULT FALSE,
+     *    pathLenConstraint   INTEGER (0..MAX) OPTIONAL
+     * }
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(cA);
+
+        if (pathLenConstraint != null)
+        {
+            seq.addObject(pathLenConstraint);
+        }
+
+        return seq;
+    }
+
+    public String toString()
+    {
+        return "BasicConstraints: isCa(" + this.isCA() + "), pathLenConstraint = " + pathLenConstraint.getValue();
+    }
+}
+
+
+
+/**
+ * PKIX RFC-2459
+ *
+ * The X.509 v2 CRL syntax is as follows.  For signature calculation,
+ * the data that is to be signed is ASN.1 DER encoded.
+ *
+ * <pre>
+ * CertificateList  ::=  SEQUENCE  {
+ *      tbsCertList          TBSCertList,
+ *      signatureAlgorithm   AlgorithmIdentifier,
+ *      signatureValue       BIT STRING  }
+ * </pre>
+ */
+class CertificateList
+    implements DEREncodable
+{
+    TBSCertList            tbsCertList;
+    AlgorithmIdentifier    sigAlgId;
+    DERBitString           sig;
+
+    public static CertificateList getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static CertificateList getInstance(
+        Object  obj)
+    {
+        if (obj instanceof CertificateList)
+        {
+            return (CertificateList)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new CertificateList((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public CertificateList(
+        ASN1Sequence seq)
+    {
+        tbsCertList = TBSCertList.getInstance(seq.getObjectAt(0));
+        sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
+        sig = (DERBitString)seq.getObjectAt(2);
+    }
+
+    public TBSCertList getTBSCertList()
+    {
+        return tbsCertList;
+    }
+
+    public TBSCertList.CRLEntry[] getRevokedCertificates()
+    {
+        return tbsCertList.getRevokedCertificates();
+    }
+
+    public AlgorithmIdentifier getSignatureAlgorithm()
+    {
+        return sigAlgId;
+    }
+
+    public DERBitString getSignature()
+    {
+        return sig;
+    }
+
+    public int getVersion()
+    {
+        return tbsCertList.getVersion();
+    }
+
+    public X509Name getIssuer()
+    {
+        return tbsCertList.getIssuer();
+    }
+
+    public Time getThisUpdate()
+    {
+        return tbsCertList.getThisUpdate();
+    }
+
+    public Time getNextUpdate()
+    {
+        return tbsCertList.getNextUpdate();
+    }
+
+    public DERObject getDERObject()
+    {
+        DERConstructedSequence seq = new DERConstructedSequence();
+        seq.addObject(tbsCertList);
+        seq.addObject(sigAlgId);
+        seq.addObject(sig);
+        return seq;
+    }
+}
+
+
+class CRLDistPoint
+    implements DEREncodable
+{
+    ASN1Sequence  seq = null;
+
+    public static CRLDistPoint getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static CRLDistPoint getInstance(
+        Object  obj)
+    {
+        if (obj instanceof CRLDistPoint)
+        {
+            return (CRLDistPoint)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new CRLDistPoint((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+       
+    public CRLDistPoint(
+        ASN1Sequence seq)
+    {
+        this.seq = seq;
+    }
+    
+    public CRLDistPoint(
+        DistributionPoint[] points)
+    {
+        DEREncodableVector  v = new DEREncodableVector();
+
+        for (int i = 0; i != points.length; i++)
+        {
+            v.add(points[i]);
+        }
+
+        seq = new DERSequence(v);
+    }
+
+    /**
+     * <pre>
+     * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        return seq;
+    }
+}
+
+
+
+/**
+ * <pre>
+ * CRLNumber::= INTEGER(0..MAX)
+ * </pre>
+ */
+class CRLNumber
+    extends DERInteger
+{
+
+    public CRLNumber(
+        BigInteger number)
+    {
+        super(number);
+    }
+
+    public BigInteger getCRLNumber()
+    {
+        return getPositiveValue();
+    }
+}
+
+
+class CRLReason
+    extends DEREnumerated
+{
+    public static final int UNSPECIFIED = 0;
+    public static final int KEY_COMPROMISE = 1;
+    public static final int CA_COMPROMISE = 2;
+    public static final int AFFILIATION_CHANGED = 3;
+    public static final int SUPERSEDED = 4;
+    public static final int CESSATION_OF_OPERATION  = 5;
+    public static final int CERTIFICATE_HOLD = 6;
+    public static final int REMOVE_FROM_CRL = 8;
+    public static final int PRIVILEGE_WITHDRAWN = 9;
+    public static final int AA_COMPROMISE = 10;
+
+    /**
+     * <pre>
+     * CRLReason ::= ENUMERATED {
+     *  unspecified             (0),
+     *  keyCompromise           (1),
+     *  cACompromise            (2),
+     *  affiliationChanged      (3),
+     *  superseded              (4),
+     *  cessationOfOperation    (5),
+     *  certificateHold         (6),
+     *  removeFromCRL           (8),
+     *  privilegeWithdrawn      (9),
+     *  aACompromise           (10)
+     * }
+     * </pre>
+     */
+    public CRLReason(
+        int reason)
+    {
+        super(reason);
+    }
+}
+
+
+
+/**
+ * <pre>
+ * DigestInfo::=SEQUENCE{
+ *          digestAlgorithm  AlgorithmIdentifier,
+ *          digest OCTET STRING }
+ * </pre>
+ */
+class DigestInfo
+    implements DEREncodable
+{
+    private byte[]                  digest;
+    private AlgorithmIdentifier     algId;
+
+    public static DigestInfo getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static DigestInfo getInstance(
+        Object  obj)
+    {
+        if (obj instanceof DigestInfo)
+        {
+            return (DigestInfo)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new DigestInfo((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public DigestInfo(
+        AlgorithmIdentifier  algId,
+        byte[]               digest)
+    {
+        this.digest = digest;
+        this.algId = algId;
+    }
+
+    public DigestInfo(
+        ASN1Sequence  obj)
+    {
+        Enumeration             e = obj.getObjects();
+
+        algId = AlgorithmIdentifier.getInstance(e.nextElement());
+        digest = ((ASN1OctetString)e.nextElement()).getOctets();
+    }
+
+    public AlgorithmIdentifier getAlgorithmId()
+    {
+        return algId;
+    }
+
+    public byte[] getDigest()
+    {
+        return digest;
+    }
+
+    public DERObject getDERObject()
+    {
+        DEREncodableVector  v = new DEREncodableVector();
+
+        v.add(algId);
+        v.add(new DEROctetString(digest));
+
+        return new DERSequence(v);
+    }
+}
+
+
+class DistributionPoint
+    implements DEREncodable
+{
+    ASN1Sequence  seq = null;
+
+    public static DistributionPoint getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static DistributionPoint getInstance(
+        Object obj)
+    {
+        if(obj == null || obj instanceof DistributionPoint) 
+        {
+            return (DistributionPoint)obj;
+        }
+        
+        if(obj instanceof ASN1Sequence) 
+        {
+            return new DistributionPoint((ASN1Sequence)obj);
+        }
+        
+        throw new IllegalArgumentException("Invalid DistributionPoint: " + obj.getClass().getName());
+    }
+
+    public DistributionPoint(
+        ASN1Sequence seq)
+    {
+        this.seq = seq;
+    }
+    
+    public DistributionPoint(
+        DistributionPointName   distributionPoint,
+        ReasonFlags             reasons,
+        GeneralNames            cRLIssuer)
+    {
+        DEREncodableVector  v = new DEREncodableVector();
+
+        if (distributionPoint != null)
+        {
+            v.add(new DERTaggedObject(0, distributionPoint));
+        }
+
+        if (reasons != null)
+        {
+            v.add(new DERTaggedObject(1, reasons));
+        }
+
+        if (cRLIssuer != null)
+        {
+            v.add(new DERTaggedObject(2, cRLIssuer));
+        }
+
+        seq = new DERSequence(v);
+    }
+
+    /**
+     * <pre>
+     * DistributionPoint ::= SEQUENCE {
+     *      distributionPoint [0] DistributionPointName OPTIONAL,
+     *      reasons           [1] ReasonFlags OPTIONAL,
+     *      cRLIssuer         [2] GeneralNames OPTIONAL
+     * }
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        return seq;
+    }
+}
+
+
+class DistributionPointName
+    implements DEREncodable
+{
+    DEREncodable        name;
+    int                 type;
+
+    public static final int FULL_NAME = 0;
+    public static final int NAME_RELATIVE_TO_CRL_ISSUER = 1;
+
+    public DistributionPointName(
+        int             type,
+        DEREncodable    name)
+    {
+        this.type = type;
+        this.name = name;
+    }
+
+    /**
+     * <pre>
+     * DistributionPointName ::= CHOICE {
+     *     fullName                 [0] GeneralNames,
+     *     nameRelativeToCRLIssuer  [1] RelativeDistinguishedName
+     * }
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        return new DERTaggedObject(false, type, name);
+    }
+}
+
+
+
+class DSAParameter
+    implements DEREncodable
+{
+    DERInteger      p, q, g;
+
+    public static DSAParameter getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static DSAParameter getInstance(
+        Object obj)
+    {
+        if(obj == null || obj instanceof DSAParameter) 
+        {
+            return (DSAParameter)obj;
+        }
+        
+        if(obj instanceof ASN1Sequence) 
+        {
+            return new DSAParameter((ASN1Sequence)obj);
+        }
+        
+        throw new IllegalArgumentException("Invalid DSAParameter: " + obj.getClass().getName());
+    }
+
+    public DSAParameter(
+        BigInteger  p,
+        BigInteger  q,
+        BigInteger  g)
+    {
+        this.p = new DERInteger(p);
+        this.q = new DERInteger(q);
+        this.g = new DERInteger(g);
+    }
+
+    public DSAParameter(
+        ASN1Sequence  seq)
+    {
+        Enumeration     e = seq.getObjects();
+
+        p = (DERInteger)e.nextElement();
+        q = (DERInteger)e.nextElement();
+        g = (DERInteger)e.nextElement();
+    }
+
+    public BigInteger getP()
+    {
+        return p.getPositiveValue();
+    }
+
+    public BigInteger getQ()
+    {
+        return q.getPositiveValue();
+    }
+
+    public BigInteger getG()
+    {
+        return g.getPositiveValue();
+    }
+
+    public DERObject getDERObject()
+    {
+        DEREncodableVector  v = new DEREncodableVector();
+
+        v.add(p);
+        v.add(q);
+        v.add(g);
+
+        return new DERSequence(v);
+    }
+}
+
+
+/**
+ * <pre>
+ * GeneralName ::= CHOICE {
+ *      otherName                       [0]     OtherName,
+ *      rfc822Name                      [1]     IA5String,
+ *      dNSName                         [2]     IA5String,
+ *      x400Address                     [3]     ORAddress,
+ *      directoryName                   [4]     Name,
+ *      ediPartyName                    [5]     EDIPartyName,
+ *      uniformResourceIdentifier       [6]     IA5String,
+ *      iPAddress                       [7]     OCTET STRING,
+ *      registeredID                    [8]     OBJECT IDENTIFIER}
+ *
+ * OtherName ::= SEQUENCE {
+ *      type-id    OBJECT IDENTIFIER,
+ *      value      [0] EXPLICIT ANY DEFINED BY type-id }
+ *
+ * EDIPartyName ::= SEQUENCE {
+ *      nameAssigner            [0]     DirectoryString OPTIONAL,
+ *      partyName               [1]     DirectoryString }
+ * </pre>
+ */
+class GeneralName
+    implements DEREncodable
+{
+    DEREncodable       obj;
+    int                tag;
+       boolean                 isInsideImplicit = false;               // if we are in an implicitly tagged object
+
+    public GeneralName(
+        X509Name  directoryName)
+    {
+        this.obj = directoryName;
+        this.tag = 4;
+    }
+
+    /**
+     * When the subjectAltName extension contains an Internet mail address,
+     * the address MUST be included as an rfc822Name. The format of an
+     * rfc822Name is an "addr-spec" as defined in RFC 822 [RFC 822].
+     *
+     * When the subjectAltName extension contains a domain name service
+     * label, the domain name MUST be stored in the dNSName (an IA5String).
+     * The name MUST be in the "preferred name syntax," as specified by RFC
+     * 1034 [RFC 1034].
+     *
+     * When the subjectAltName extension contains a URI, the name MUST be
+     * stored in the uniformResourceIdentifier (an IA5String). The name MUST
+     * be a non-relative URL, and MUST follow the URL syntax and encoding
+     * rules specified in [RFC 1738].  The name must include both a scheme
+     * (e.g., "http" or "ftp") and a scheme-specific-part.  The scheme-
+     * specific-part must include a fully qualified domain name or IP
+     * address as the host.
+     *
+     * When the subjectAltName extension contains a iPAddress, the address
+     * MUST be stored in the octet string in "network byte order," as
+     * specified in RFC 791 [RFC 791]. The least significant bit (LSB) of
+     * each octet is the LSB of the corresponding byte in the network
+     * address. For IP Version 4, as specified in RFC 791, the octet string
+     * MUST contain exactly four octets.  For IP Version 6, as specified in
+     * RFC 1883, the octet string MUST contain exactly sixteen octets [RFC
+     * 1883].
+     */
+    public GeneralName(
+        DERObject name, int tag)
+    {
+        this.obj = name;
+        this.tag = tag;
+    }
+
+    /**
+     * mark whether or not we are contained inside an implicitly tagged
+     * object.
+     * @deprecated
+     */
+       public void markInsideImplicit(
+               boolean         isInsideImplicit)
+       {
+               this.isInsideImplicit = isInsideImplicit;
+       }
+
+    public DERObject getDERObject()
+    {
+        if (obj.getDERObject() instanceof ASN1Sequence)
+        {
+            return new DERTaggedObject(true, tag, obj);
+        }
+        else
+        {
+            return new DERTaggedObject(false, tag, obj);
+        }
+    }
+}
+
+
+
+class GeneralNames
+    implements DEREncodable
+{
+    ASN1Sequence            seq;
+    boolean                 isInsideImplicit = false;
+
+    public static GeneralNames getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof GeneralNames)
+        {
+            return (GeneralNames)obj;
+        }
+
+        if (obj instanceof ASN1Sequence)
+        {
+            return new GeneralNames((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    public static GeneralNames getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public GeneralNames(
+        ASN1Sequence  seq)
+    {
+        this.seq = seq;
+    }
+
+    /*
+     * this is a hack! But it will have to do until the ambiguity rules
+     * get sorted out for implicit/explicit tagging...
+     * @deprecated
+     */
+    public void markInsideImplicit(
+        boolean    isInsideImplicit)
+    {
+        this.isInsideImplicit = isInsideImplicit;
+    }
+
+    /**
+     * <pre>
+     * GeneralNames ::= SEQUENCE SIZE {1..MAX} OF GeneralName
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        return seq;
+    }
+}
+
+
+/**
+ * <pre>
+ *    id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
+ *
+ *    KeyUsage ::= BIT STRING {
+ *         digitalSignature        (0),
+ *         nonRepudiation          (1),
+ *         keyEncipherment         (2),
+ *         dataEncipherment        (3),
+ *         keyAgreement            (4),
+ *         keyCertSign             (5),
+ *         cRLSign                 (6),
+ *         encipherOnly            (7),
+ *         decipherOnly            (8) }
+ * </pre>
+ */
+class KeyUsage
+    extends DERBitString
+{
+    public static final int        digitalSignature = (1 << 7); 
+    public static final int        nonRepudiation   = (1 << 6);
+    public static final int        keyEncipherment  = (1 << 5);
+    public static final int        dataEncipherment = (1 << 4);
+    public static final int        keyAgreement     = (1 << 3);
+    public static final int        keyCertSign      = (1 << 2);
+    public static final int        cRLSign          = (1 << 1);
+    public static final int        encipherOnly     = (1 << 0);
+    public static final int        decipherOnly     = (1 << 15);
+
+    /**
+     * Basic constructor.
+     * 
+     * @param usage - the bitwise OR of the Key Usage flags giving the
+     * allowed uses for the key.
+     * e.g. (X509KeyUsage.keyEncipherment | X509KeyUsage.dataEncipherment)
+     */
+    public KeyUsage(
+        int usage)
+    {
+        super(getBytes(usage), getPadBits(usage));
+    }
+
+    public KeyUsage(
+        DERBitString usage)
+    {
+        super(usage.getBytes(), usage.getPadBits());
+    }
+
+    public String toString()
+    {
+        return "KeyUsage: 0x" + Integer.toHexString(data[0] & 0xff);
+    }
+}
+
+
+class ReasonFlags
+    extends DERBitString
+{
+    public static final int UNUSED                  = (1 << 7);
+    public static final int KEY_COMPROMISE          = (1 << 6);
+    public static final int CA_COMPROMISE           = (1 << 5);
+    public static final int AFFILIATION_CHANGED     = (1 << 4);
+    public static final int SUPERSEDED              = (1 << 3);
+    public static final int CESSATION_OF_OPERATION  = (1 << 2);
+    public static final int CERTIFICATE_HOLD        = (1 << 1);
+    public static final int PRIVILEGE_WITHDRAWN     = (1 << 0);
+    public static final int AA_COMPROMISE           = (1 << 15);
+
+    /**
+     * <pre>
+     * ReasonFlags ::= BIT STRING {
+     *    unused(0),
+     *    keyCompromise(1),
+     *    cACompromise(2),
+     *    affiliationChanged(3),
+     *    superseded(4),
+     *    cessationOfOperation(5),
+     *    certficateHold(6)
+     * }
+     * </pre>
+     * @param reasons - the bitwise OR of the Key Reason flags giving the
+     * allowed uses for the key.
+     */
+    public ReasonFlags(
+        int reasons)
+    {
+        super(getBytes(reasons), getPadBits(reasons));
+    }
+
+    public ReasonFlags(
+        DERBitString reasons)
+    {
+        super(reasons.getBytes(), reasons.getPadBits());
+    }
+}
+
+
+
+class RSAPublicKeyStructure
+    implements DEREncodable
+{
+    private BigInteger  modulus;
+    private BigInteger  publicExponent;
+
+    public static RSAPublicKeyStructure getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static RSAPublicKeyStructure getInstance(
+        Object obj)
+    {
+        if(obj == null || obj instanceof RSAPublicKeyStructure) 
+        {
+            return (RSAPublicKeyStructure)obj;
+        }
+        
+        if(obj instanceof ASN1Sequence) 
+        {
+            return new RSAPublicKeyStructure((ASN1Sequence)obj);
+        }
+        
+        throw new IllegalArgumentException("Invalid RSAPublicKeyStructure: " + obj.getClass().getName());
+    }
+    
+    public RSAPublicKeyStructure(
+        BigInteger  modulus,
+        BigInteger  publicExponent)
+    {
+        this.modulus = modulus;
+        this.publicExponent = publicExponent;
+    }
+
+    public RSAPublicKeyStructure(
+        ASN1Sequence  seq)
+    {
+        Enumeration e = seq.getObjects();
+
+        modulus = ((DERInteger)e.nextElement()).getValue();
+        publicExponent = ((DERInteger)e.nextElement()).getValue();
+    }
+
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    public BigInteger getPublicExponent()
+    {
+        return publicExponent;
+    }
+
+    /**
+     * This outputs the key in PKCS1v2 format.
+     * <pre>
+     *      RSAPublicKey ::= SEQUENCE {
+     *                          modulus INTEGER, -- n
+     *                          publicExponent INTEGER, -- e
+     *                      }
+     * </pre>
+     * <p>
+     */
+    public DERObject getDERObject()
+    {
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(new DERInteger(getModulus()));
+        seq.addObject(new DERInteger(getPublicExponent()));
+
+        return seq;
+    }
+}
+
+
+/**
+ * <pre>
+ * SubjectKeyIdentifier::= OCTET STRING
+ * </pre>
+ */
+class SubjectKeyIdentifier
+    implements DEREncodable
+{
+       private byte[] keyidentifier;
+
+    public static SubjectKeyIdentifier getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1OctetString.getInstance(obj, explicit));
+    }
+
+    public static SubjectKeyIdentifier getInstance(
+        Object obj)
+    {
+        if(obj == null || obj instanceof SubjectKeyIdentifier) 
+        {
+            return (SubjectKeyIdentifier)obj;
+        }
+        
+        if(obj instanceof SubjectPublicKeyInfo) 
+        {
+            return new SubjectKeyIdentifier((SubjectPublicKeyInfo)obj);
+        }
+        
+        if(obj instanceof ASN1OctetString) 
+        {
+            return new SubjectKeyIdentifier((ASN1OctetString)obj);
+        }
+        
+        throw new IllegalArgumentException("Invalid SubjectKeyIdentifier: " + obj.getClass().getName());
+    }
+       
+    public SubjectKeyIdentifier(
+        byte[] keyid)
+    {
+        this.keyidentifier=keyid;
+    }
+
+    public SubjectKeyIdentifier(
+        ASN1OctetString  keyid)
+    {
+               this.keyidentifier=keyid.getOctets();
+
+    }
+
+       /**
+        *
+        * Calulates the keyidentifier using a SHA1 hash over the BIT STRING
+        * from SubjectPublicKeyInfo as defined in RFC2459.
+        *
+        **/
+       public SubjectKeyIdentifier(
+               SubjectPublicKeyInfo    spki)
+       {
+               Digest  digest = new SHA1Digest();
+               byte[]  resBuf = new byte[digest.getDigestSize()];
+
+               byte[] bytes = spki.getPublicKeyData().getBytes();
+               digest.update(bytes, 0, bytes.length);
+               digest.doFinal(resBuf, 0);
+               this.keyidentifier=resBuf;
+       }
+
+    public byte[] getKeyIdentifier()
+    {
+        return keyidentifier;
+    }
+
+     /**
+     * <pre>
+     * SubjectKeyIdentifier := OCTET STRING
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        return new DEROctetString(keyidentifier);
+    }
+}
+
+
+
+/**
+ * The object that contains the public key stored in a certficate.
+ * <p>
+ * The getEncoded() method in the public keys in the JCE produces a DER
+ * encoded one of these.
+ */
+class SubjectPublicKeyInfo
+    implements DEREncodable
+{
+    private AlgorithmIdentifier     algId;
+    private DERBitString            keyData;
+
+    public static SubjectPublicKeyInfo getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static SubjectPublicKeyInfo getInstance(
+        Object  obj)
+    {
+        if (obj instanceof SubjectPublicKeyInfo)
+        {
+            return (SubjectPublicKeyInfo)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new SubjectPublicKeyInfo((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public SubjectPublicKeyInfo(
+        AlgorithmIdentifier algId,
+        DEREncodable        publicKey)
+    {
+        this.keyData = new DERBitString(publicKey);
+        this.algId = algId;
+    }
+
+    public SubjectPublicKeyInfo(
+        AlgorithmIdentifier algId,
+        byte[]              publicKey)
+    {
+        this.keyData = new DERBitString(publicKey);
+        this.algId = algId;
+    }
+
+    public SubjectPublicKeyInfo(
+        ASN1Sequence  seq)
+    {
+        Enumeration         e = seq.getObjects();
+
+        this.algId = AlgorithmIdentifier.getInstance(e.nextElement());
+        this.keyData = (DERBitString)e.nextElement();
+    }
+
+    public AlgorithmIdentifier getAlgorithmId()
+    {
+        return algId;
+    }
+
+    /**
+     * for when the public key is an encoded object - if the bitstring
+     * can't be decoded this routine throws an IOException.
+     *
+     * @exception IOException - if the bit string doesn't represent a DER
+     * encoded object.
+     */
+    public DERObject getPublicKey()
+        throws IOException
+    {
+        ByteArrayInputStream    bIn = new ByteArrayInputStream(keyData.getBytes());
+        DERInputStream          dIn = new DERInputStream(bIn);
+
+        return dIn.readObject();
+    }
+
+    /**
+     * for when the public key is raw bits...
+     */
+    public DERBitString getPublicKeyData()
+    {
+        return keyData;
+    }
+
+    /**
+     * <pre>
+     * SubjectPublicKeyInfo ::= SEQUENCE {
+     *                          algorithm AlgorithmIdentifier,
+     *                          publicKey BIT STRING }
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(algId);
+        seq.addObject(keyData);
+
+        return seq;
+    }
+}
+
+
+/**
+ * <pre>
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * </pre>
+ * <p>
+ * Note: issuerUniqueID and subjectUniqueID are both deprecated by the IETF. This class
+ * will parse them, but you really shouldn't be creating new ones.
+ */
+class TBSCertificateStructure
+    implements DEREncodable, X509ObjectIdentifiers, PKCSObjectIdentifiers
+{
+    ASN1Sequence            seq;
+
+    DERInteger              version;
+    DERInteger              serialNumber;
+    AlgorithmIdentifier     signature;
+    X509Name                issuer;
+    Time                    startDate, endDate;
+    X509Name                subject;
+    SubjectPublicKeyInfo    subjectPublicKeyInfo;
+    DERBitString            issuerUniqueId;
+    DERBitString            subjectUniqueId;
+    X509Extensions          extensions;
+
+    public static TBSCertificateStructure getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static TBSCertificateStructure getInstance(
+        Object  obj)
+    {
+        if (obj instanceof TBSCertificateStructure)
+        {
+            return (TBSCertificateStructure)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new TBSCertificateStructure((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public TBSCertificateStructure(
+        ASN1Sequence  seq)
+    {
+        int         seqStart = 0;
+
+        this.seq = seq;
+
+        //
+        // some certficates don't include a version number - we assume v1
+        //
+        if (seq.getObjectAt(0) instanceof DERTaggedObject)
+        {
+            version = DERInteger.getInstance(seq.getObjectAt(0));
+        }
+        else
+        {
+            seqStart = -1;          // field 0 is missing!
+            version = new DERInteger(0);
+        }
+
+        serialNumber = DERInteger.getInstance(seq.getObjectAt(seqStart + 1));
+
+        signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqStart + 2));
+        issuer = X509Name.getInstance(seq.getObjectAt(seqStart + 3));
+
+        //
+        // before and after dates
+        //
+        ASN1Sequence  dates = (ASN1Sequence)seq.getObjectAt(seqStart + 4);
+
+        startDate = Time.getInstance(dates.getObjectAt(0));
+        endDate = Time.getInstance(dates.getObjectAt(1));
+
+        subject = X509Name.getInstance(seq.getObjectAt(seqStart + 5));
+
+        //
+        // public key info.
+        //
+        subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(seq.getObjectAt(seqStart + 6));
+
+        for (int extras = seq.size() - (seqStart + 6) - 1; extras > 0; extras--)
+        {
+            DERTaggedObject extra = (DERTaggedObject)seq.getObjectAt(seqStart + 6 + extras);
+
+            switch (extra.getTagNo())
+            {
+            case 1:
+                issuerUniqueId = DERBitString.getInstance(extra);
+                break;
+            case 2:
+                subjectUniqueId = DERBitString.getInstance(extra);
+                break;
+            case 3:
+                extensions = X509Extensions.getInstance(extra);
+            }
+        }
+    }
+
+    public int getVersion()
+    {
+        return version.getValue().intValue() + 1;
+    }
+
+    public DERInteger getVersionNumber()
+    {
+        return version;
+    }
+
+    public DERInteger getSerialNumber()
+    {
+        return serialNumber;
+    }
+
+    public AlgorithmIdentifier getSignature()
+    {
+        return signature;
+    }
+
+    public X509Name getIssuer()
+    {
+        return issuer;
+    }
+
+    public Time getStartDate()
+    {
+        return startDate;
+    }
+
+    public Time getEndDate()
+    {
+        return endDate;
+    }
+
+    public X509Name getSubject()
+    {
+        return subject;
+    }
+
+    public SubjectPublicKeyInfo getSubjectPublicKeyInfo()
+    {
+        return subjectPublicKeyInfo;
+    }
+
+    public DERBitString getIssuerUniqueId()
+    {
+        return issuerUniqueId;
+    }
+
+    public DERBitString getSubjectUniqueId()
+    {
+        return subjectUniqueId;
+    }
+
+    public X509Extensions getExtensions()
+    {
+        return extensions;
+    }
+
+    public DERObject getDERObject()
+    {
+        return seq;
+    }
+}
+
+
+
+/**
+ * PKIX RFC-2459
+ *
+ * <pre>
+ * TBSCertList  ::=  SEQUENCE  {
+ *      version                 Version OPTIONAL,
+ *                                   -- if present, shall be v2
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      thisUpdate              Time,
+ *      nextUpdate              Time OPTIONAL,
+ *      revokedCertificates     SEQUENCE OF SEQUENCE  {
+ *           userCertificate         CertificateSerialNumber,
+ *           revocationDate          Time,
+ *           crlEntryExtensions      Extensions OPTIONAL
+ *                                         -- if present, shall be v2
+ *                                }  OPTIONAL,
+ *      crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
+ *                                         -- if present, shall be v2
+ *                                }
+ * </pre>
+ */
+
+class TBSCertList
+    implements DEREncodable
+{
+    class CRLEntry
+        implements DEREncodable
+    {
+        DERConstructedSequence  seq;
+
+        DERInteger          userCertificate;
+        Time                revocationDate;
+        X509Extensions      crlEntryExtensions;
+
+        public CRLEntry(
+            DERConstructedSequence  seq)
+        {
+            this.seq = seq;
+
+            userCertificate = (DERInteger)seq.getObjectAt(0);
+            revocationDate = Time.getInstance(seq.getObjectAt(1));
+            if (seq.getSize() == 3)
+            {
+                crlEntryExtensions = X509Extensions.getInstance(seq.getObjectAt(2));
+            }
+        }
+
+        public DERInteger getUserCertificate()
+        {
+            return userCertificate;
+        }
+
+        public Time getRevocationDate()
+        {
+            return revocationDate;
+        }
+
+        public X509Extensions getExtensions()
+        {
+            return crlEntryExtensions;
+        }
+
+        public DERObject getDERObject()
+        {
+            return seq;
+        }
+    }
+
+    ASN1Sequence     seq;
+
+    DERInteger              version;
+    AlgorithmIdentifier     signature;
+    X509Name                issuer;
+    Time                    thisUpdate;
+    Time                    nextUpdate;
+    CRLEntry[]              revokedCertificates;
+    X509Extensions          crlExtensions;
+
+    public static TBSCertList getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static TBSCertList getInstance(
+        Object  obj)
+    {
+        if (obj instanceof TBSCertList)
+        {
+            return (TBSCertList)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new TBSCertList((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public TBSCertList(
+        ASN1Sequence  seq)
+    {
+        int seqPos = 0;
+
+        this.seq = seq;
+
+        if (seq.getObjectAt(seqPos) instanceof DERInteger)
+        {
+            version = (DERInteger)seq.getObjectAt(seqPos++);
+        }
+        else
+        {
+            version = new DERInteger(0);
+        }
+
+        signature = AlgorithmIdentifier.getInstance(seq.getObjectAt(seqPos++));
+        issuer = X509Name.getInstance(seq.getObjectAt(seqPos++));
+        thisUpdate = Time.getInstance(seq.getObjectAt(seqPos++));
+
+        if (seqPos < seq.size()
+            && (seq.getObjectAt(seqPos) instanceof DERUTCTime
+               || seq.getObjectAt(seqPos) instanceof DERGeneralizedTime
+               || seq.getObjectAt(seqPos) instanceof Time))
+        {
+            nextUpdate = Time.getInstance(seq.getObjectAt(seqPos++));
+        }
+
+        if (seqPos < seq.size()
+            && !(seq.getObjectAt(seqPos) instanceof DERTaggedObject))
+        {
+            DERConstructedSequence certs = (DERConstructedSequence)seq.getObjectAt(seqPos++);
+            revokedCertificates = new CRLEntry[certs.getSize()];
+
+            for ( int i = 0; i < revokedCertificates.length; i++)
+            {
+                revokedCertificates[i] = new CRLEntry((DERConstructedSequence)certs.getObjectAt(i));
+            }
+        }
+
+        if (seqPos < seq.size()
+            && seq.getObjectAt(seqPos) instanceof DERTaggedObject)
+        {
+            crlExtensions = X509Extensions.getInstance(seq.getObjectAt(seqPos++));
+        }
+    }
+
+    public int getVersion()
+    {
+        return version.getValue().intValue() + 1;
+    }
+
+    public DERInteger getVersionNumber()
+    {
+        return version;
+    }
+
+    public AlgorithmIdentifier getSignature()
+    {
+        return signature;
+    }
+
+    public X509Name getIssuer()
+    {
+        return issuer;
+    }
+
+    public Time getThisUpdate()
+    {
+        return thisUpdate;
+    }
+
+    public Time getNextUpdate()
+    {
+        return nextUpdate;
+    }
+
+    public CRLEntry[] getRevokedCertificates()
+    {
+        return revokedCertificates;
+    }
+
+    public X509Extensions getExtensions()
+    {
+        return crlExtensions;
+    }
+
+    public DERObject getDERObject()
+    {
+        return seq;
+    }
+}
+
+
+
+class Time
+    implements DEREncodable
+{
+    DERObject   time;
+
+    public static Time getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(obj.getObject());
+    }
+
+    public Time(
+        DERObject   time)
+    {
+        if (!(time instanceof DERUTCTime)
+            && !(time instanceof DERGeneralizedTime))
+        {
+            throw new IllegalArgumentException("unknown object passed to Time");
+        }
+
+        this.time = time; 
+    }
+
+    /**
+     * creates a time object from a given date - if the date is between 1950
+     * and 2049 a UTCTime object is generated, otherwise a GeneralizedTime
+     * is used.
+     */
+    public Time(
+        Date    date)
+    {
+        SimpleTimeZone      tz = new SimpleTimeZone(0, "Z");
+        SimpleDateFormat    dateF = new SimpleDateFormat("yyyyMMddHHmmss");
+
+        dateF.setTimeZone(tz);
+
+        String  d = dateF.format(date) + "Z";
+        int     year = Integer.parseInt(d.substring(0, 4));
+
+        if (year < 1950 || year > 2049)
+        {
+            time = new DERGeneralizedTime(d);
+        }
+        else
+        {
+            time = new DERUTCTime(d.substring(2));
+        }
+    }
+
+    public static Time getInstance(
+        Object  obj)
+    {
+        if (obj instanceof Time)
+        {
+            return (Time)obj;
+        }
+        else if (obj instanceof DERUTCTime)
+        {
+            return new Time((DERUTCTime)obj);
+        }
+        else if (obj instanceof DERGeneralizedTime)
+        {
+            return new Time((DERGeneralizedTime)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public String getTime()
+    {
+        if (time instanceof DERUTCTime)
+        {
+            return ((DERUTCTime)time).getAdjustedTime();
+        }
+        else
+        {
+            return ((DERGeneralizedTime)time).getTime();
+        }
+    }
+
+    public Date getDate()
+    {
+        SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
+
+        return dateF.parse(this.getTime(), new ParsePosition(0));
+    }
+
+    /**
+     * <pre>
+     * Time ::= CHOICE {
+     *             utcTime        UTCTime,
+     *             generalTime    GeneralizedTime }
+     * </pre>
+     */
+    public DERObject getDERObject()
+    {
+        return time;
+    }
+}
+
+
+/**
+ * Generator for Version 1 TBSCertificateStructures.
+ * <pre>
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      }
+ * </pre>
+ *
+ */
+class V1TBSCertificateGenerator
+{
+    DERTaggedObject         version = new DERTaggedObject(0, new DERInteger(0));
+
+    DERInteger              serialNumber;
+    AlgorithmIdentifier     signature;
+    X509Name                issuer;
+    Time                    startDate, endDate;
+    X509Name                subject;
+    SubjectPublicKeyInfo    subjectPublicKeyInfo;
+
+    public V1TBSCertificateGenerator()
+    {
+    }
+
+    public void setSerialNumber(
+        DERInteger  serialNumber)
+    {
+        this.serialNumber = serialNumber;
+    }
+
+    public void setSignature(
+        AlgorithmIdentifier    signature)
+    {
+        this.signature = signature;
+    }
+
+    public void setIssuer(
+        X509Name    issuer)
+    {
+        this.issuer = issuer;
+    }
+
+    public void setStartDate(
+        Time startDate)
+    {
+        this.startDate = startDate;
+    }
+
+    public void setStartDate(
+        DERUTCTime startDate)
+    {
+        this.startDate = new Time(startDate);
+    }
+
+    public void setEndDate(
+        Time endDate)
+    {
+        this.endDate = endDate;
+    }
+
+    public void setEndDate(
+        DERUTCTime endDate)
+    {
+        this.endDate = new Time(endDate);
+    }
+
+    public void setSubject(
+        X509Name    subject)
+    {
+        this.subject = subject;
+    }
+
+    public void setSubjectPublicKeyInfo(
+        SubjectPublicKeyInfo    pubKeyInfo)
+    {
+        this.subjectPublicKeyInfo = pubKeyInfo;
+    }
+
+    public TBSCertificateStructure generateTBSCertificate()
+    {
+        if ((serialNumber == null) || (signature == null)
+            || (issuer == null) || (startDate == null) || (endDate == null)
+            || (subject == null) || (subjectPublicKeyInfo == null))
+        {
+            throw new IllegalStateException("not all mandatory fields set in V1 TBScertificate generator");
+        }
+
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(version);
+        seq.addObject(serialNumber);
+        seq.addObject(signature);
+        seq.addObject(issuer);
+
+        //
+        // before and after dates
+        //
+        DERConstructedSequence  validity = new DERConstructedSequence();
+
+        validity.addObject(startDate);
+        validity.addObject(endDate);
+
+        seq.addObject(validity);
+
+        seq.addObject(subject);
+
+        seq.addObject(subjectPublicKeyInfo);
+
+        return new TBSCertificateStructure(seq);
+    }
+}
+
+
+
+/**
+ * Generator for Version 2 TBSCertList structures.
+ * <pre>
+ *  TBSCertList  ::=  SEQUENCE  {
+ *       version                 Version OPTIONAL,
+ *                                    -- if present, shall be v2
+ *       signature               AlgorithmIdentifier,
+ *       issuer                  Name,
+ *       thisUpdate              Time,
+ *       nextUpdate              Time OPTIONAL,
+ *       revokedCertificates     SEQUENCE OF SEQUENCE  {
+ *            userCertificate         CertificateSerialNumber,
+ *            revocationDate          Time,
+ *            crlEntryExtensions      Extensions OPTIONAL
+ *                                          -- if present, shall be v2
+ *                                 }  OPTIONAL,
+ *       crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
+ *                                          -- if present, shall be v2
+ *                                 }
+ * </pre>
+ *
+ * <b>Note: This class may be subject to change</b>
+ */
+class V2TBSCertListGenerator
+{
+    DERInteger version = new DERInteger(1);
+
+    AlgorithmIdentifier     signature;
+    X509Name                issuer;
+    Time                    thisUpdate, nextUpdate=null;
+    X509Extensions          extensions=null;
+    private Vector          crlentries=null;
+
+    public V2TBSCertListGenerator()
+    {
+    }
+
+
+    public void setSignature(
+        AlgorithmIdentifier    signature)
+    {
+        this.signature = signature;
+    }
+
+    public void setIssuer(
+        X509Name    issuer)
+    {
+        this.issuer = issuer;
+    }
+
+    public void setThisUpdate(
+        DERUTCTime thisUpdate)
+    {
+        this.thisUpdate = new Time(thisUpdate);
+    }
+
+    public void setNextUpdate(
+        DERUTCTime nextUpdate)
+    {
+        this.nextUpdate = new Time(nextUpdate);
+    }
+
+    public void setThisUpdate(
+        Time thisUpdate)
+    {
+        this.thisUpdate = thisUpdate;
+    }
+
+    public void setNextUpdate(
+        Time nextUpdate)
+    {
+        this.nextUpdate = nextUpdate;
+    }
+
+    public void addCRLEntry(
+        DERConstructedSequence crlEntry)
+    {
+        if (crlentries == null)
+            crlentries = new Vector();
+        crlentries.addElement(crlEntry);
+    }
+
+    public void addCRLEntry(DERInteger userCertificate, DERUTCTime revocationDate, int reason)
+    {
+        addCRLEntry(userCertificate, new Time(revocationDate), reason);
+    }
+
+    public void addCRLEntry(DERInteger userCertificate, Time revocationDate, int reason)
+    {
+        DERConstructedSequence seq = new DERConstructedSequence();
+        seq.addObject(userCertificate);
+        seq.addObject(revocationDate);
+       
+        if (reason != 0)
+        {
+            CRLReason rf = new CRLReason(reason);
+            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
+            DEROutputStream         dOut = new DEROutputStream(bOut);
+            try
+            {
+                dOut.writeObject(rf);
+            }
+            catch (IOException e)
+            {
+                throw new IllegalArgumentException("error encoding value: " + e);
+            }
+           byte[] value = bOut.toByteArray();
+            DERConstructedSequence eseq = new DERConstructedSequence();
+            DERConstructedSequence eseq1 = new DERConstructedSequence();
+            eseq1.addObject(X509Extensions.ReasonCode);
+            eseq1.addObject(new DEROctetString(value));
+           eseq.addObject(eseq1);
+            X509Extensions ex = new X509Extensions(eseq);
+            seq.addObject(ex);
+        }
+        if (crlentries == null)
+            crlentries = new Vector();
+        crlentries.addElement(seq);
+    }
+
+    public void setExtensions(
+        X509Extensions    extensions)
+    {
+        this.extensions = extensions;
+    }
+
+    public TBSCertList generateTBSCertList()
+    {
+        if ((signature == null) || (issuer == null) || (thisUpdate == null))
+        {
+            throw new IllegalStateException("Not all mandatory fields set in V2 TBSCertList generator.");
+        }
+
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(version);
+        seq.addObject(signature);
+        seq.addObject(issuer);
+
+        seq.addObject(thisUpdate);
+        if (nextUpdate != null)
+            seq.addObject(nextUpdate);
+
+        // Add CRLEntries if they exist
+        if (crlentries != null) {
+            DERConstructedSequence certseq = new DERConstructedSequence();
+            Enumeration it = crlentries.elements();
+            while( it.hasMoreElements() ) {
+                certseq.addObject((DERConstructedSequence)it.nextElement());
+            }
+            seq.addObject(certseq);
+        }
+
+        if (extensions != null)
+        {
+            seq.addObject(new DERTaggedObject(0, extensions));
+        }
+
+        return new TBSCertList(seq);
+    }
+}
+
+
+/**
+ * Generator for Version 3 TBSCertificateStructures.
+ * <pre>
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * </pre>
+ *
+ */
+class V3TBSCertificateGenerator
+{
+    DERTaggedObject         version = new DERTaggedObject(0, new DERInteger(2));
+
+    DERInteger              serialNumber;
+    AlgorithmIdentifier     signature;
+    X509Name                issuer;
+    Time                    startDate, endDate;
+    X509Name                subject;
+    SubjectPublicKeyInfo    subjectPublicKeyInfo;
+    X509Extensions          extensions;
+
+    public V3TBSCertificateGenerator()
+    {
+    }
+
+    public void setSerialNumber(
+        DERInteger  serialNumber)
+    {
+        this.serialNumber = serialNumber;
+    }
+
+    public void setSignature(
+        AlgorithmIdentifier    signature)
+    {
+        this.signature = signature;
+    }
+
+    public void setIssuer(
+        X509Name    issuer)
+    {
+        this.issuer = issuer;
+    }
+
+    public void setStartDate(
+        DERUTCTime startDate)
+    {
+        this.startDate = new Time(startDate);
+    }
+
+    public void setStartDate(
+        Time startDate)
+    {
+        this.startDate = startDate;
+    }
+
+    public void setEndDate(
+        DERUTCTime endDate)
+    {
+        this.endDate = new Time(endDate);
+    }
+
+    public void setEndDate(
+        Time endDate)
+    {
+        this.endDate = endDate;
+    }
+
+    public void setSubject(
+        X509Name    subject)
+    {
+        this.subject = subject;
+    }
+
+    public void setSubjectPublicKeyInfo(
+        SubjectPublicKeyInfo    pubKeyInfo)
+    {
+        this.subjectPublicKeyInfo = pubKeyInfo;
+    }
+
+    public void setExtensions(
+        X509Extensions    extensions)
+    {
+        this.extensions = extensions;
+    }
+
+    public TBSCertificateStructure generateTBSCertificate()
+    {
+        if ((serialNumber == null) || (signature == null)
+            || (issuer == null) || (startDate == null) || (endDate == null)
+            || (subject == null) || (subjectPublicKeyInfo == null))
+        {
+            throw new IllegalStateException("not all mandatory fields set in V3 TBScertificate generator");
+        }
+
+        DERConstructedSequence  seq = new DERConstructedSequence();
+
+        seq.addObject(version);
+        seq.addObject(serialNumber);
+        seq.addObject(signature);
+        seq.addObject(issuer);
+
+        //
+        // before and after dates
+        //
+        DERConstructedSequence  validity = new DERConstructedSequence();
+
+        validity.addObject(startDate);
+        validity.addObject(endDate);
+
+        seq.addObject(validity);
+
+        seq.addObject(subject);
+
+        seq.addObject(subjectPublicKeyInfo);
+
+        if (extensions != null)
+        {
+            seq.addObject(new DERTaggedObject(3, extensions));
+        }
+
+        return new TBSCertificateStructure(seq);
+    }
+}
+
+
+/**
+ * an X509Certificate structure.
+ * <pre>
+ *  Certificate ::= SEQUENCE {
+ *      tbsCertificate          TBSCertificate,
+ *      signatureAlgorithm      AlgorithmIdentifier,
+ *      signature               BIT STRING
+ *  }
+ * </pre>
+ */
+class X509CertificateStructure
+    implements DEREncodable, X509ObjectIdentifiers, PKCSObjectIdentifiers
+{
+    ASN1Sequence  seq;
+    TBSCertificateStructure tbsCert;
+    AlgorithmIdentifier     sigAlgId;
+    DERBitString            sig;
+
+    public static X509CertificateStructure getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+       
+    public static X509CertificateStructure getInstance(
+        Object  obj)
+    {
+        if (obj instanceof X509CertificateStructure)
+        {
+            return (X509CertificateStructure)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new X509CertificateStructure((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    public X509CertificateStructure(
+        ASN1Sequence  seq)
+    {
+        this.seq = seq;
+
+        //
+        // correct x509 certficate
+        //
+        if (seq.size() == 3)
+        {
+            tbsCert = TBSCertificateStructure.getInstance(seq.getObjectAt(0));
+            sigAlgId = AlgorithmIdentifier.getInstance(seq.getObjectAt(1));
+
+            sig = (DERBitString)seq.getObjectAt(2);
+        }
+    }
+
+    public TBSCertificateStructure getTBSCertificate()
+    {
+        return tbsCert;
+    }
+
+    public int getVersion()
+    {
+        return tbsCert.getVersion();
+    }
+
+    public DERInteger getSerialNumber()
+    {
+        return tbsCert.getSerialNumber();
+    }
+
+    public X509Name getIssuer()
+    {
+        return tbsCert.getIssuer();
+    }
+
+    public Time getStartDate()
+    {
+        return tbsCert.getStartDate();
+    }
+
+    public Time getEndDate()
+    {
+        return tbsCert.getEndDate();
+    }
+
+    public X509Name getSubject()
+    {
+        return tbsCert.getSubject();
+    }
+
+    public SubjectPublicKeyInfo getSubjectPublicKeyInfo()
+    {
+        return tbsCert.getSubjectPublicKeyInfo();
+    }
+
+    public AlgorithmIdentifier getSignatureAlgorithm()
+    {
+        return sigAlgId;
+    }
+
+    public DERBitString getSignature()
+    {
+        return sig;
+    }
+
+    public DERObject getDERObject()
+    {
+        return seq;
+    }
+}
+
+
+/**
+ * an object for the elements in the X.509 V3 extension block.
+ */
+class X509Extension
+{
+    boolean             critical;
+    DEROctetString      value;
+
+    public X509Extension(
+        DERBoolean              critical,
+        DEROctetString          value)
+    {
+        this.critical = critical.isTrue();
+        this.value = value;
+    }
+
+    public X509Extension(
+        boolean                 critical,
+        DEROctetString          value)
+    {
+        this.critical = critical;
+        this.value = value;
+    }
+
+    public boolean isCritical()
+    {
+        return critical;
+    }
+
+    public DEROctetString getValue()
+    {
+        return value;
+    }
+
+    public int hashCode()
+    {
+        if (this.isCritical())
+        {
+            return this.getValue().hashCode();
+        }
+
+        
+        return ~this.getValue().hashCode();
+    }
+
+    public boolean equals(
+        Object  o)
+    {
+        if (o == null || !(o instanceof X509Extension))
+        {
+            return false;
+        }
+
+        X509Extension   other = (X509Extension)o;
+
+        return other.getValue().equals(this.getValue())
+            && (other.isCritical() == this.isCritical());
+    }
+}
+
+
+
+class X509Extensions
+    implements DEREncodable
+{
+    /**
+     * Subject Key Identifier 
+     */
+    public static final DERObjectIdentifier SubjectKeyIdentifier = new DERObjectIdentifier("2.5.29.14");
+
+    /**
+     * Key Usage 
+     */
+    public static final DERObjectIdentifier KeyUsage = new DERObjectIdentifier("2.5.29.15");
+
+    /**
+     * Private Key Usage Period 
+     */
+    public static final DERObjectIdentifier PrivateKeyUsagePeriod = new DERObjectIdentifier("2.5.29.16");
+
+    /**
+     * Subject Alternative Name 
+     */
+    public static final DERObjectIdentifier SubjectAlternativeName = new DERObjectIdentifier("2.5.29.17");
+
+    /**
+     * Issuer Alternative Name 
+     */
+    public static final DERObjectIdentifier IssuerAlternativeName = new DERObjectIdentifier("2.5.29.18");
+
+    /**
+     * Basic Constraints 
+     */
+    public static final DERObjectIdentifier BasicConstraints = new DERObjectIdentifier("2.5.29.19");
+
+    /**
+     * CRL Number 
+     */
+    public static final DERObjectIdentifier CRLNumber = new DERObjectIdentifier("2.5.29.20");
+
+    /**
+     * Reason code 
+     */
+    public static final DERObjectIdentifier ReasonCode = new DERObjectIdentifier("2.5.29.21");
+
+    /**
+     * Hold Instruction Code 
+     */
+    public static final DERObjectIdentifier InstructionCode = new DERObjectIdentifier("2.5.29.23");
+
+    /**
+     * Invalidity Date 
+     */
+    public static final DERObjectIdentifier InvalidityDate = new DERObjectIdentifier("2.5.29.24");
+
+    /**
+     * Delta CRL indicator 
+     */
+    public static final DERObjectIdentifier DeltaCRLIndicator = new DERObjectIdentifier("2.5.29.27");
+
+    /**
+     * Issuing Distribution Point 
+     */
+    public static final DERObjectIdentifier IssuingDistributionPoint = new DERObjectIdentifier("2.5.29.28");
+
+    /**
+     * Certificate Issuer 
+     */
+    public static final DERObjectIdentifier CertificateIssuer = new DERObjectIdentifier("2.5.29.29");
+
+    /**
+     * Name Constraints 
+     */
+    public static final DERObjectIdentifier NameConstraints = new DERObjectIdentifier("2.5.29.30");
+
+    /**
+     * CRL Distribution Points 
+     */
+    public static final DERObjectIdentifier CRLDistributionPoints = new DERObjectIdentifier("2.5.29.31");
+
+    /**
+     * Certificate Policies 
+     */
+    public static final DERObjectIdentifier CertificatePolicies = new DERObjectIdentifier("2.5.29.32");
+
+    /**
+     * Policy Mappings 
+     */
+    public static final DERObjectIdentifier PolicyMappings = new DERObjectIdentifier("2.5.29.33");
+
+    /**
+     * Authority Key Identifier 
+     */
+    public static final DERObjectIdentifier AuthorityKeyIdentifier = new DERObjectIdentifier("2.5.29.35");
+
+    /**
+     * Policy Constraints 
+     */
+    public static final DERObjectIdentifier PolicyConstraints = new DERObjectIdentifier("2.5.29.36");
+
+    /**
+     * Extended Key Usage 
+     */
+    public static final DERObjectIdentifier ExtendedKeyUsage = new DERObjectIdentifier("2.5.29.37");
+
+    private Hashtable               extensions = new Hashtable();
+    private Vector                  ordering = new Vector();
+
+    public static X509Extensions getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static X509Extensions getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof X509Extensions)
+        {
+            return (X509Extensions)obj;
+        }
+
+        if (obj instanceof ASN1Sequence)
+        {
+            return new X509Extensions((ASN1Sequence)obj);
+        }
+
+        if (obj instanceof ASN1TaggedObject)
+        {
+            return getInstance(((ASN1TaggedObject)obj).getObject());
+        }
+
+        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
+    }
+
+    /**
+     * Constructor from DERConstructedSequence.
+     *
+     * the extensions are a list of constructed sequences, either with (OID, OctetString) or (OID, Boolean, OctetString)
+     */
+    public X509Extensions(
+        ASN1Sequence  seq)
+    {
+        Enumeration e = seq.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            ASN1Sequence            s = (ASN1Sequence)e.nextElement();
+            Enumeration             e1 = s.getObjects();
+
+            if (s.size() == 3)
+            {
+                extensions.put(s.getObjectAt(0), new X509Extension((DERBoolean)s.getObjectAt(1), (DEROctetString)s.getObjectAt(2)));
+            }
+            else
+            {
+                extensions.put(s.getObjectAt(0), new X509Extension(false, (DEROctetString)s.getObjectAt(1)));
+            }
+
+            ordering.addElement(s.getObjectAt(0));
+        }
+    }
+
+    /**
+     * constructor from a table of extensions.
+     * <p>
+     * it's is assumed the table contains OID/String pairs.
+     */
+    public X509Extensions(
+        Hashtable  extensions)
+    {
+        this(null, extensions);
+    }
+
+    /**
+     * constructor from a table of extensions with ordering
+     * <p>
+     * it's is assumed the table contains OID/String pairs.
+     */
+    public X509Extensions(
+        Vector      ordering,
+        Hashtable   extensions)
+    {
+        Enumeration e;
+
+        if (ordering == null)
+        {
+            e = extensions.keys();
+        }
+        else
+        {
+            e = ordering.elements();
+        }
+
+        while (e.hasMoreElements())
+        {
+            this.ordering.addElement(e.nextElement()); 
+        }
+
+        e = this.ordering.elements();
+
+        while (e.hasMoreElements())
+        {
+            DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
+            X509Extension           ext = (X509Extension)extensions.get(oid);
+
+            this.extensions.put(oid, ext);
+        }
+    }
+
+    /**
+     * return an Enumeration of the extension field's object ids.
+     */
+    public Enumeration oids()
+    {
+        return ordering.elements();
+    }
+
+    /**
+     * return the extension represented by the object identifier
+     * passed in.
+     *
+     * @return the extension if it's present, null otherwise.
+     */
+    public X509Extension getExtension(
+        DERObjectIdentifier oid)
+    {
+        return (X509Extension)extensions.get(oid);
+    }
+
+    public DERObject getDERObject()
+    {
+        DEREncodableVector      vec = new DEREncodableVector();
+        Enumeration             e = ordering.elements();
+
+        while (e.hasMoreElements())
+        {
+            DERObjectIdentifier     oid = (DERObjectIdentifier)e.nextElement();
+            X509Extension           ext = (X509Extension)extensions.get(oid);
+            DEREncodableVector      v = new DEREncodableVector();
+
+            v.add(oid);
+
+            if (ext.isCritical())
+            {
+                v.add(new DERBoolean(true));
+            }
+
+            v.add(ext.getValue());
+
+            vec.add(new DERSequence(v));
+        }
+
+        return new DERSequence(vec);
+    }
+
+    public int hashCode()
+    {
+        Enumeration     e = extensions.keys();
+        int             hashCode = 0;
+
+        while (e.hasMoreElements())
+        {
+            Object  o = e.nextElement();
+
+            hashCode ^= o.hashCode();
+            hashCode ^= extensions.get(o).hashCode();
+        }
+
+        return hashCode;
+    }
+
+    public boolean equals(
+        Object o)
+    {
+        if (o == null || !(o instanceof X509Extensions))
+        {
+            return false;
+        }
+
+        X509Extensions  other = (X509Extensions)o;
+
+        Enumeration     e1 = extensions.keys();
+        Enumeration     e2 = other.extensions.keys();
+
+        while (e1.hasMoreElements() && e2.hasMoreElements())
+        {
+            Object  o1 = e1.nextElement();
+            Object  o2 = e2.nextElement();
+            
+            if (!o1.equals(o2))
+            {
+                return false;
+            }
+        }
+
+        if (e1.hasMoreElements() || e2.hasMoreElements())
+        {
+            return false;
+        }
+
+        return true;
+    }
+}
+
+
+
+class X509Name
+    implements DEREncodable
+{
+    /**
+     * country code - StringType(SIZE(2))
+     */
+    public static final DERObjectIdentifier C = new DERObjectIdentifier("2.5.4.6");
+
+    /**
+     * organization - StringType(SIZE(1..64))
+     */
+    public static final DERObjectIdentifier O = new DERObjectIdentifier("2.5.4.10");
+
+    /**
+     * organizational unit name - StringType(SIZE(1..64))
+     */
+    public static final DERObjectIdentifier OU = new DERObjectIdentifier("2.5.4.11");
+
+    /**
+     * Title
+     */
+    public static final DERObjectIdentifier T = new DERObjectIdentifier("2.5.4.12");
+
+    /**
+     * common name - StringType(SIZE(1..64))
+     */
+    public static final DERObjectIdentifier CN = new DERObjectIdentifier("2.5.4.3");
+
+    /**
+     * device serial number name - StringType(SIZE(1..64))
+     */
+    public static final DERObjectIdentifier SN = new DERObjectIdentifier("2.5.4.5");
+
+    /**
+     * locality name - StringType(SIZE(1..64))
+     */
+    public static final DERObjectIdentifier L = new DERObjectIdentifier("2.5.4.7");
+
+    /**
+     * state, or province name - StringType(SIZE(1..64))
+     */
+    public static final DERObjectIdentifier ST = new DERObjectIdentifier("2.5.4.8");
+
+
+    /**
+     * email address (RSA PKCS#9 extension) - IA5String
+     * <p>
+     * note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here.
+     */
+    public static final DERObjectIdentifier EmailAddress = new DERObjectIdentifier("1.2.840.113549.1.9.1");
+       
+       /**
+        * email address in Verisign certificates
+        */
+       public static final DERObjectIdentifier E = EmailAddress;
+       
+    /*
+     * others...
+     */
+    public static final DERObjectIdentifier DC = new DERObjectIdentifier("0.9.2342.19200300.100.1.25");
+
+    /**
+     * LDAP User id.
+     */
+    public static final DERObjectIdentifier UID = new DERObjectIdentifier("0.9.2342.19200300.100.1.1");
+
+    /**
+     * look up table translating OID values into their common symbols.
+     */
+    public static Hashtable OIDLookUp = new Hashtable();
+
+    /**
+     * look up table translating common symbols into their OIDS.
+     */
+    public static Hashtable SymbolLookUp = new Hashtable();
+
+    static
+    {
+        OIDLookUp.put(C, "C");
+        OIDLookUp.put(O, "O");
+        OIDLookUp.put(T, "T");
+        OIDLookUp.put(OU, "OU");
+        OIDLookUp.put(CN, "CN");
+        OIDLookUp.put(L, "L");
+        OIDLookUp.put(ST, "ST");
+        OIDLookUp.put(SN, "SN");
+        OIDLookUp.put(EmailAddress, "E");
+        OIDLookUp.put(DC, "DC");
+        OIDLookUp.put(UID, "UID");
+
+        SymbolLookUp.put("c", C);
+        SymbolLookUp.put("o", O);
+        SymbolLookUp.put("t", T);
+        SymbolLookUp.put("ou", OU);
+        SymbolLookUp.put("cn", CN);
+        SymbolLookUp.put("l", L);
+        SymbolLookUp.put("st", ST);
+        SymbolLookUp.put("sn", SN);
+        SymbolLookUp.put("emailaddress", E);
+        SymbolLookUp.put("dc", DC);
+        SymbolLookUp.put("e", E);
+        SymbolLookUp.put("uid", UID);
+    }
+
+    private Vector                  ordering = new Vector();
+    private Vector                  values = new Vector();
+    private ASN1Sequence            seq;
+
+    public static X509Name getInstance(
+        ASN1TaggedObject obj,
+        boolean          explicit)
+    {
+        return getInstance(ASN1Sequence.getInstance(obj, explicit));
+    }
+
+    public static X509Name getInstance(
+        Object  obj)
+    {
+        if (obj == null || obj instanceof X509Name)
+        {
+            return (X509Name)obj;
+        }
+        else if (obj instanceof ASN1Sequence)
+        {
+            return new X509Name((ASN1Sequence)obj);
+        }
+
+        throw new IllegalArgumentException("unknown object in factory");
+    }
+
+    /**
+     * Constructor from ASN1Sequence
+     *
+     * the principal will be a list of constructed sets, each containing an (OID, String) pair.
+     */
+    public X509Name(
+        ASN1Sequence  seq)
+    {
+        this.seq = seq;
+
+        Enumeration e = seq.getObjects();
+
+        while (e.hasMoreElements())
+        {
+            ASN1Set         set = (ASN1Set)e.nextElement();
+            ASN1Sequence    s = (ASN1Sequence)set.getObjectAt(0);
+
+            ordering.addElement(s.getObjectAt(0));
+            values.addElement(((DERString)s.getObjectAt(1)).getString());
+        }
+    }
+
+    /**
+     * constructor from a table of attributes.
+     * <p>
+     * it's is assumed the table contains OID/String pairs, and the contents
+     * of the table are copied into an internal table as part of the 
+     * construction process.
+     * <p>
+     * <b>Note:</b> if the name you are trying to generate should be
+     * following a specific ordering, you should use the constructor
+     * with the ordering specified below.
+     */
+    public X509Name(
+        Hashtable  attributes)
+    {
+        this(null, attributes);
+    }
+
+    /**
+     * constructor from a table of attributes with ordering.
+     * <p>
+     * it's is assumed the table contains OID/String pairs, and the contents
+     * of the table are copied into an internal table as part of the 
+     * construction process. The ordering vector should contain the OIDs
+     * in the order they are meant to be encoded or printed in toString.
+     */
+    public X509Name(
+        Vector      ordering,
+        Hashtable   attributes)
+    {
+        if (ordering != null)
+        {
+            for (int i = 0; i != ordering.size(); i++)
+            {
+                this.ordering.addElement(ordering.elementAt(i));
+            }
+        }
+        else
+        {
+            Enumeration     e = attributes.keys();
+
+            while (e.hasMoreElements())
+            {
+                this.ordering.addElement(e.nextElement());
+            }
+        }
+
+        for (int i = 0; i != this.ordering.size(); i++)
+        {
+            DERObjectIdentifier     oid = (DERObjectIdentifier)this.ordering.elementAt(i);
+
+            if (OIDLookUp.get(oid) == null)
+            {
+                throw new IllegalArgumentException("Unknown object id - " + oid.getId() + " - passed to distinguished name");
+            }
+
+            if (attributes.get(oid) == null)
+            {
+                throw new IllegalArgumentException("No attribute for object id - " + oid.getId() + " - passed to distinguished name");
+            }
+
+            this.values.addElement(attributes.get(oid)); // copy the hash table
+        }
+    }
+
+    /**
+     * takes two vectors one of the oids and the other of the values.
+     */
+    public X509Name(
+        Vector  ordering,
+        Vector  values)
+    {
+        if (ordering.size() != values.size())
+        {
+            throw new IllegalArgumentException("ordering vector must be same length as values.");
+        }
+
+        for (int i = 0; i < ordering.size(); i++)
+        {
+            this.ordering.addElement(ordering.elementAt(i));
+            this.values.addElement(values.elementAt(i));
+        }
+    }
+
+    /**
+     * takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or
+     * some such, converting it into an ordered set of name attributes.
+     */
+    public X509Name(
+        String  dirName)
+    {
+        X509NameTokenizer   nTok = new X509NameTokenizer(dirName);
+
+        while (nTok.hasMoreTokens())
+        {
+            String  token = nTok.nextToken();
+            int     index = token.indexOf('=');
+
+            if (index == -1)
+            {
+                throw new IllegalArgumentException("badly formated directory string");
+            }
+
+            String              name = token.substring(0, index);
+            String              value = token.substring(index + 1);
+            DERObjectIdentifier oid = null;
+
+            if (name.toUpperCase().startsWith("OID."))
+            {
+                oid = new DERObjectIdentifier(name.substring(4));
+            }
+            else if (name.charAt(0) >= '0' && name.charAt(0) <= '9')
+            {
+                oid = new DERObjectIdentifier(name);
+            }
+            else
+            {
+                oid = (DERObjectIdentifier)SymbolLookUp.get(name.toLowerCase());
+                if (oid == null)
+                {
+                    throw new IllegalArgumentException("Unknown object id - " + name + " - passed to distinguished name");
+                }
+            }
+
+            this.ordering.addElement(oid);
+            this.values.addElement(value);
+        }
+    }
+
+    /**
+     * return false if we have characters out of the range of a printable
+     * string, true otherwise.
+     */
+    private boolean canBePrintable(
+        String  str)
+    {
+        for (int i = str.length() - 1; i >= 0; i--)
+        {
+            if (str.charAt(i) > 0x007f)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public DERObject getDERObject()
+    {
+        if (seq == null)
+        {
+            DEREncodableVector  vec = new DEREncodableVector();
+
+            for (int i = 0; i != ordering.size(); i++)
+            {
+                DEREncodableVector      v = new DEREncodableVector();
+                DERObjectIdentifier     oid = (DERObjectIdentifier)ordering.elementAt(i);
+
+                v.add(oid);
+
+                String  str = (String)values.elementAt(i);
+
+                if (oid.equals(EmailAddress))
+                {
+                    v.add(new DERIA5String(str));
+                }
+                else
+                {
+                    if (canBePrintable(str))
+                    {
+                        v.add(new DERPrintableString(str));
+                    }
+                    else
+                    {
+                        v.add(new DERUTF8String(str));
+                    }
+                }
+
+                vec.add(new DERSet(new DERSequence(v)));
+            }
+
+            seq = new DERSequence(vec);
+        }
+
+        return seq;
+    }
+
+    /**
+     * test for equality - note: case is ignored.
+     */
+    public boolean equals(Object _obj) 
+    {
+        if (_obj == this)
+        {
+            return true;
+        }
+
+        if (_obj == null || !(_obj instanceof X509Name))
+        {
+            return false;
+        }
+        
+        X509Name _oxn          = (X509Name)_obj;
+        int      _orderingSize = ordering.size();
+
+        if (_orderingSize != _oxn.ordering.size()) 
+        {
+                       return false;
+               }
+               
+               boolean[] _indexes = new boolean[_orderingSize];
+
+               for(int i = 0; i < _orderingSize; i++) 
+               {
+                       boolean _found = false;
+                       String  _oid   = ((DERObjectIdentifier)ordering.elementAt(i)).getId();
+                       String  _val   = (String)values.elementAt(i);
+                       
+                       for(int j = 0; j < _orderingSize; j++) 
+                       {
+                               if(_indexes[j] == true)
+                               {
+                                       continue;
+                               }
+                               
+                               String _oOID = ((DERObjectIdentifier)_oxn.ordering.elementAt(j)).getId();
+                               String _oVal = (String)_oxn.values.elementAt(j);
+
+                // was equalsIgnoreCase but MIDP doesn't like that.
+                               if(_oid.equals(_oOID) && _val.toLowerCase().equals(_oVal.toLowerCase()))
+                               {
+                                       _indexes[j] = true;
+                                       _found      = true;
+                                       break;
+                               }
+
+                       }
+
+                       if(!_found)
+                       {
+                               return false;
+                       }
+               }
+               
+               return true;
+       }
+       
+    public int hashCode()
+    {
+        ASN1Sequence  seq = (ASN1Sequence)this.getDERObject();
+        Enumeration   e = seq.getObjects();
+        int           hashCode = 0;
+
+        while (e.hasMoreElements())
+        {
+            hashCode ^= e.nextElement().hashCode();
+        }
+
+        return hashCode;
+    }
+
+    public String toString()
+    {
+        StringBuffer            buf = new StringBuffer();
+        boolean                 first = true;
+        Enumeration             e1 = ordering.elements();
+        Enumeration             e2 = values.elements();
+
+        while (e1.hasMoreElements())
+        {
+            Object                  oid = e1.nextElement();
+            String                  sym = (String)OIDLookUp.get(oid);
+            
+            if (first)
+            {
+                first = false;
+            }
+            else
+            {
+                buf.append(",");
+            }
+
+            if (sym != null)
+            {
+                buf.append(sym);
+            }
+            else
+            {
+                buf.append(((DERObjectIdentifier)oid).getId());
+            }
+
+            buf.append("=");
+
+            int     index = buf.length();
+
+            buf.append((String)e2.nextElement());
+
+            int     end = buf.length();
+
+            while (index != end)
+            {
+                if ((buf.charAt(index) == ',')
+                   || (buf.charAt(index) == '"')
+                   || (buf.charAt(index) == '\\')
+                   || (buf.charAt(index) == '+')
+                   || (buf.charAt(index) == '<')
+                   || (buf.charAt(index) == '>')
+                   || (buf.charAt(index) == ';'))
+                {
+                    buf.insert(index, "\\");
+                    index++;
+                    end++;
+                }
+
+                index++;
+            }
+        }
+
+        return buf.toString();
+    }
+}
+
+/**
+ * class for breaking up an X500 Name into it's component tokens, ala
+ * java.util.StringTokenizer. We need this class as some of the
+ * lightweight Java environment don't support classes like
+ * StringTokenizer.
+ */
+class X509NameTokenizer
+{
+    private String          oid;
+    private int             index;
+    private StringBuffer    buf = new StringBuffer();
+
+    public X509NameTokenizer(
+        String oid)
+    {
+        this.oid = oid;
+        this.index = -1;
+    }
+
+    public boolean hasMoreTokens()
+    {
+        return (index != oid.length());
+    }
+
+    public String nextToken()
+    {
+        if (index == oid.length())
+        {
+            return null;
+        }
+
+        int     end = index + 1;
+        boolean quoted = false;
+        boolean escaped = false;
+
+        buf.setLength(0);
+
+        while (end != oid.length())
+        {
+            char    c = oid.charAt(end);
+
+            if (c == '"')
+            {
+                if (!escaped)
+                {
+                    quoted = !quoted;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+                escaped = false;
+            }
+            else
+            {
+                if (escaped || quoted)
+                {
+                    buf.append(c);
+                    escaped = false;
+                }
+                else if (c == '\\')
+                {
+                    escaped = true;
+                }
+                else if (c == ',')
+                {
+                    break;
+                }
+                else
+                {
+                    buf.append(c);
+                }
+            }
+            end++;
+        }
+
+        index = end;
+        return buf.toString().trim();
+    }
+}
+
+
+interface X509ObjectIdentifiers
+{
+    //
+    // base id
+    //
+    static final String                 id                      = "2.5.4";
+
+    static final DERObjectIdentifier    commonName              = new DERObjectIdentifier(id + ".3");
+    static final DERObjectIdentifier    countryName             = new DERObjectIdentifier(id + ".6");
+    static final DERObjectIdentifier    localityName            = new DERObjectIdentifier(id + ".7");
+    static final DERObjectIdentifier    stateOrProvinceName     = new DERObjectIdentifier(id + ".8");
+    static final DERObjectIdentifier    organization            = new DERObjectIdentifier(id + ".10");
+    static final DERObjectIdentifier    organizationalUnitName  = new DERObjectIdentifier(id + ".11");
+
+    // id-SHA1 OBJECT IDENTIFIER ::=    
+    //   {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 }    //
+    static final DERObjectIdentifier    id_SHA1                 = new DERObjectIdentifier("1.3.14.3.2.26");
+
+    //
+    // ripemd160 OBJECT IDENTIFIER ::=
+    //      {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) hashAlgorithm(2) RIPEMD-160(1)}
+    //
+    static final DERObjectIdentifier    ripemd160               = new DERObjectIdentifier("1.3.36.3.2.1");
+
+    //
+    // ripemd160WithRSAEncryption OBJECT IDENTIFIER ::=
+    //      {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) signatureAlgorithm(3) rsaSignature(1) rsaSignatureWithripemd160(2) }
+    //
+    static final DERObjectIdentifier    ripemd160WithRSAEncryption = new DERObjectIdentifier("1.3.36.3.3.1.2");
+
+
+       static final DERObjectIdentifier        id_ea_rsa = new DERObjectIdentifier("2.5.8.1.1");
+}
+
+
+
+/**
+ * base interface that a public/private key block cipher needs
+ * to conform to.
+ */
+interface AsymmetricBlockCipher
+{
+    /**
+     * initialise the cipher.
+     *
+     * @param forEncryption if true the cipher is initialised for 
+     *  encryption, if false for decryption.
+     * @param param the key and other data required by the cipher.
+     */
+    public void init(boolean forEncryption, CipherParameters param);
+
+    /**
+     * returns the largest size an input block can be.
+     *
+     * @return maximum size for an input block.
+     */
+    public int getInputBlockSize();
+
+    /**
+     * returns the maximum size of the block produced by this cipher.
+     *
+     * @return maximum size of the output block produced by the cipher.
+     */
+    public int getOutputBlockSize();
+
+    /**
+     * process the block of len bytes stored in in from offset inOff.
+     *
+     * @param in the input data
+     * @param inOff offset into the in array where the data starts
+     * @param len the length of the block to be processed.
+     * @return the resulting byte array of the encryption/decryption process.
+     * @exception InvalidCipherTextException data decrypts improperly.
+     * @exception DataLengthException the input data is too large for the cipher.
+     */
+    public byte[] processBlock(byte[] in, int inOff, int len)
+        throws InvalidCipherTextException;
+}
+
+/**
+ * a holding class for public/private parameter pairs.
+ */
+class AsymmetricCipherKeyPair
+{
+    private CipherParameters    publicParam;
+    private CipherParameters    privateParam;
+
+    /**
+     * basic constructor.
+     *
+     * @param publicParam a public key parameters object.
+     * @param privateParam the corresponding private key parameters.
+     */
+    public AsymmetricCipherKeyPair(
+        CipherParameters    publicParam,
+        CipherParameters    privateParam)
+    {
+        this.publicParam = publicParam;
+        this.privateParam = privateParam;
+    }
+
+    /**
+     * return the public key parameters.
+     *
+     * @return the public key parameters.
+     */
+    public CipherParameters getPublic()
+    {
+        return publicParam;
+    }
+
+    /**
+     * return the private key parameters.
+     *
+     * @return the private key parameters.
+     */
+    public CipherParameters getPrivate()
+    {
+        return privateParam;
+    }
+}
+
+/**
+ * interface that a public/private key pair generator should conform to.
+ */
+interface AsymmetricCipherKeyPairGenerator
+{
+    /**
+     * intialise the key pair generator.
+     *
+     * @param the parameters the key pair is to be initialised with.
+     */
+    public void init(KeyGenerationParameters param);
+
+    /**
+     * return an AsymmetricCipherKeyPair containing the generated keys.
+     *
+     * @return an AsymmetricCipherKeyPair containing the generated keys.
+     */
+    public AsymmetricCipherKeyPair generateKeyPair();
+}
+
+
+
+/**
+ * Block cipher engines are expected to conform to this interface.
+ */
+interface BlockCipher
+{
+    /**
+     * Initialise the cipher.
+     *
+     * @param forEncryption if true the cipher is initialised for
+     *  encryption, if false for decryption.
+     * @param param the key and other data required by the cipher.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm the cipher implements.
+     *
+     * @return the name of the algorithm the cipher implements.
+     */
+    public String getAlgorithmName();
+
+    /**
+     * Return the block size for this cipher (in bytes).
+     *
+     * @return the block size for this cipher in bytes.
+     */
+    public int getBlockSize();
+
+    /**
+     * Process one block of input from the array in and write it to
+     * the out array.
+     *
+     * @param in the array containing the input data.
+     * @param inOff offset into the in array the data starts at.
+     * @param out the array the output data will be copied into.
+     * @param outOff the offset into the out array the output will start at.
+     * @exception DataLengthException if there isn't enough data in in, or
+     * space in out.
+     * @exception IllegalStateException if the cipher isn't initialised.
+     * @return the number of bytes processed and produced.
+     */
+    public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
+        throws DataLengthException, IllegalStateException;
+
+    /**
+     * Reset the cipher. After resetting the cipher is in the same state
+     * as it was after the last init (if there was one).
+     */
+    public void reset();
+}
+
+
+/**
+ * The base class for symmetric, or secret, cipher key generators.
+ */
+class CipherKeyGenerator
+{
+    protected Random  random;
+    protected int           strength;
+
+    /**
+     * initialise the key generator.
+     *
+     * @param param the parameters to be used for key generation
+     */
+    public void init(
+        KeyGenerationParameters param)
+    {
+        this.random = param.getRandom();
+        this.strength = (param.getStrength() + 7) / 8;
+    }
+
+    /**
+     * generate a secret key.
+     *
+     * @return a byte array containing the key value.
+     */
+    public byte[] generateKey()
+    {
+        byte[]  key = new byte[strength];
+
+        random.nextBytes(key);
+
+        return key;
+    }
+}
+
+/**
+ * all parameter classes implement this.
+ */
+interface CipherParameters
+{
+}
+
+/**
+ * the foundation class for the hard exceptions thrown by the crypto packages.
+ */
+class CryptoException 
+    extends Exception
+{
+    /**
+     * base constructor.
+     */
+    public CryptoException()
+    {
+    }
+
+    /**
+     * create a CryptoException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public CryptoException(
+        String  message)
+    {
+        super(message);
+    }
+}
+
+/**
+ * this exception is thrown if a buffer that is meant to have output
+ * copied into it turns out to be too short, or if we've been given 
+ * insufficient input. In general this exception will get thrown rather
+ * than an ArrayOutOfBounds exception.
+ */
+class DataLengthException 
+    extends RuntimeCryptoException
+{
+    /**
+     * base constructor.
+     */
+    public DataLengthException()
+    {
+    }
+
+    /**
+     * create a DataLengthException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public DataLengthException(
+        String  message)
+    {
+        super(message);
+    }
+}
+
+/**
+ * interface that a message digest conforms to.
+ */
+interface Digest
+{
+    /**
+     * return the algorithm name
+     *
+     * @return the algorithm name
+     */
+    public String getAlgorithmName();
+
+    /**
+     * return the size, in bytes, of the digest produced by this message digest.
+     *
+     * @return the size, in bytes, of the digest produced by this message digest.
+     */
+       public int getDigestSize();
+
+    /**
+     * update the message digest with a single byte.
+     *
+     * @param in the input byte to be entered.
+     */
+       public void update(byte in);
+
+    /**
+     * update the message digest with a block of bytes.
+     *
+     * @param in the byte array containing the data.
+     * @param inOff the offset into the byte array where the data starts.
+     * @param len the length of the data.
+     */
+       public void update(byte[] in, int inOff, int len);
+
+    /**
+     * close the digest, producing the final digest value. The doFinal
+     * call leaves the digest reset.
+     *
+     * @param out the array the digest is to be copied into.
+     * @param outOff the offset into the out array the digest is to start at.
+     */
+       public int doFinal(byte[] out, int outOff);
+
+    /**
+     * reset the digest back to it's initial state.
+     */
+    public void reset();
+}
+
+
+/**
+ * base implementation of MD4 family style digest as outlined in
+ * "Handbook of Applied Cryptography", pages 344 - 347.
+ */
+abstract class GeneralDigest
+    implements Digest
+{
+    private byte[]  xBuf;
+    private int     xBufOff;
+
+    private long    byteCount;
+
+       /**
+        * Standard constructor
+        */
+       protected GeneralDigest()
+       {
+               xBuf = new byte[4];
+               xBufOff = 0;
+       }
+
+       /**
+        * Copy constructor.  We are using copy constructors in place
+        * of the Object.clone() interface as this interface is not
+        * supported by J2ME.
+        */
+       protected GeneralDigest(GeneralDigest t)
+       {
+        xBuf = new byte[t.xBuf.length];
+               System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
+
+               xBufOff = t.xBufOff;
+               byteCount = t.byteCount;
+       }
+
+    public void update(
+        byte in)
+    {
+        xBuf[xBufOff++] = in;
+
+        if (xBufOff == xBuf.length)
+        {
+            processWord(xBuf, 0);
+            xBufOff = 0;
+        }
+
+        byteCount++;
+    }
+
+    public void update(
+        byte[]  in,
+        int     inOff,
+        int     len)
+    {
+        //
+        // fill the current word
+        //
+        while ((xBufOff != 0) && (len > 0))
+        {
+            update(in[inOff]);
+
+            inOff++;
+            len--;
+        }
+
+        //
+        // process whole words.
+        //
+        while (len > xBuf.length)
+        {
+            processWord(in, inOff);
+
+            inOff += xBuf.length;
+            len -= xBuf.length;
+            byteCount += xBuf.length;
+        }
+
+        //
+        // load in the remainder.
+        //
+        while (len > 0)
+        {
+            update(in[inOff]);
+
+            inOff++;
+            len--;
+        }
+    }
+
+    public void finish()
+    {
+        long    bitLength = (byteCount << 3);
+
+        //
+        // add the pad bytes.
+        //
+        update((byte)128);
+
+        while (xBufOff != 0)
+        {
+            update((byte)0);
+        }
+
+        processLength(bitLength);
+
+        processBlock();
+    }
+
+    public void reset()
+    {
+        byteCount = 0;
+
+        xBufOff = 0;
+               for ( int i = 0; i < xBuf.length; i++ ) {
+                       xBuf[i] = 0;
+               }
+    }
+
+    protected abstract void processWord(byte[] in, int inOff);
+
+    protected abstract void processLength(long bitLength);
+
+    protected abstract void processBlock();
+}
+
+/**
+ * implementation of MD2
+ * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992
+ */
+class MD2Digest\r
+    implements Digest\r
+{\r
+    private static final int DIGEST_LENGTH = 16;\r
+\r
+    /* X buffer */\r
+    private byte[]   X = new byte[48];\r
+    private int     xOff;
+\r    /* M buffer */
+\r    private byte[]   M = new byte[16];\r
+    private int     mOff;
+\r    /* check sum */
+\r    private byte[]   C = new byte[16];
+    private int COff;
+
+    public MD2Digest()\r
+    {\r
+        reset();\r
+    }\r
+       public MD2Digest(MD2Digest t)\r
+       {
+               System.arraycopy(t.X, 0, X, 0, t.X.length);
+               xOff = t.xOff;
+               System.arraycopy(t.M, 0, M, 0, t.M.length);
+               mOff = t.mOff;
+               System.arraycopy(t.C, 0, C, 0, t.C.length);
+               COff = t.COff;
+       }
+    /**\r
+     * return the algorithm name
+     *
+     * @return the algorithm name
+     */
+    public String getAlgorithmName()
+    {
+        return "MD2";
+    }
+    /**
+     * return the size, in bytes, of the digest produced by this message digest.
+     *
+     * @return the size, in bytes, of the digest produced by this message digest.
+     */
+       public int getDigestSize()
+    {
+        return DIGEST_LENGTH;
+    }
+    /**
+     * close the digest, producing the final digest value. The doFinal
+     * call leaves the digest reset.
+     *
+     * @param out the array the digest is to be copied into.
+     * @param outOff the offset into the out array the digest is to start at.
+     */
+       public int doFinal(byte[] out, int outOff)
+    {
+        // add padding
+        byte paddingByte = (byte)(M.length-mOff);
+        for (int i=mOff;i<M.length;i++)
+        {
+            M[i] = paddingByte;
+        }
+        //do final check sum
+        processCheckSum(M);
+        // do final block process
+        processBlock(M);
+
+        processBlock(C);
+
+        System.arraycopy(X,xOff,out,outOff,16);
+
+        reset();
+
+        return DIGEST_LENGTH;
+    }
+    /**
+     * reset the digest back to it's initial state.
+     */
+    public void reset()\r
+    {\r
+        xOff = 0;\r
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+        mOff = 0;\r
+        for (int i = 0; i != M.length; i++)
+        {
+            M[i] = 0;
+        }
+        COff = 0;\r
+        for (int i = 0; i != C.length; i++)
+        {
+            C[i] = 0;
+        }
+    }\r
+    /**\r
+     * update the message digest with a single byte.
+     *
+     * @param in the input byte to be entered.
+     */
+       public void update(byte in)
+    {
+        M[mOff++] = in;
+
+        if (mOff == 16)
+        {
+            processCheckSum(M);
+            processBlock(M);
+            mOff = 0;
+        }
+    }
+
+    /**
+     * update the message digest with a block of bytes.
+     *
+     * @param in the byte array containing the data.
+     * @param inOff the offset into the byte array where the data starts.
+     * @param len the length of the data.
+     */
+       public void update(byte[] in, int inOff, int len)\r
+    {\r
+        //\r
+        // fill the current word
+        //
+        while ((mOff != 0) && (len > 0))
+        {
+            update(in[inOff]);
+            inOff++;
+            len--;
+        }
+
+        //
+        // process whole words.
+        //
+        while (len > 16)
+        {
+            System.arraycopy(in,inOff,M,0,16);
+            processCheckSum(M);
+            processBlock(M);
+            len -= 16;
+            inOff += 16;
+        }
+
+        //
+        // load in the remainder.
+        //
+        while (len > 0)
+        {
+            update(in[inOff]);
+            inOff++;
+            len--;
+        }\r
+    }\r
+    protected void processCheckSum(byte[] m)\r
+    {\r
+        int L = C[15];\r
+        for (int i=0;i<16;i++)\r
+        {\r
+            C[i] ^= S[(m[i] ^ L) & 0xff];\r
+            L = C[i];\r
+        }\r
+    }\r
+    protected void processBlock(byte[] m)\r
+    {
+        for (int i=0;i<16;i++)
+        {
+            X[i+16] = m[i];
+            X[i+32] = (byte)(m[i] ^ X[i]);
+        }
+        // encrypt block
+        int t = 0;
+
+        for (int j=0;j<18;j++)
+        {
+            for (int k=0;k<48;k++)
+            {
+                t = X[k] ^= S[t];
+                t = t & 0xff;
+            }
+            t = (t + j)%256;
+        }
+     }
+     // 256-byte random permutation constructed from the digits of PI
+    private static final byte[] S = {\r
+      (byte)41,(byte)46,(byte)67,(byte)201,(byte)162,(byte)216,(byte)124,\r
+      (byte)1,(byte)61,(byte)54,(byte)84,(byte)161,(byte)236,(byte)240,\r
+      (byte)6,(byte)19,(byte)98,(byte)167,(byte)5,(byte)243,(byte)192,\r
+      (byte)199,(byte)115,(byte)140,(byte)152,(byte)147,(byte)43,(byte)217,\r
+      (byte)188,(byte)76,(byte)130,(byte)202,(byte)30,(byte)155,(byte)87,\r
+      (byte)60,(byte)253,(byte)212,(byte)224,(byte)22,(byte)103,(byte)66,\r
+      (byte)111,(byte)24,(byte)138,(byte)23,(byte)229,(byte)18,(byte)190,\r
+      (byte)78,(byte)196,(byte)214,(byte)218,(byte)158,(byte)222,(byte)73,\r
+      (byte)160,(byte)251,(byte)245,(byte)142,(byte)187,(byte)47,(byte)238,\r
+      (byte)122,(byte)169,(byte)104,(byte)121,(byte)145,(byte)21,(byte)178,\r
+      (byte)7,(byte)63,(byte)148,(byte)194,(byte)16,(byte)137,(byte)11,\r
+      (byte)34,(byte)95,(byte)33,(byte)128,(byte)127,(byte)93,(byte)154,\r
+      (byte)90,(byte)144,(byte)50,(byte)39,(byte)53,(byte)62,(byte)204,\r
+      (byte)231,(byte)191,(byte)247,(byte)151,(byte)3,(byte)255,(byte)25,\r
+      (byte)48,(byte)179,(byte)72,(byte)165,(byte)181,(byte)209,(byte)215,\r
+      (byte)94,(byte)146,(byte)42,(byte)172,(byte)86,(byte)170,(byte)198,\r
+      (byte)79,(byte)184,(byte)56,(byte)210,(byte)150,(byte)164,(byte)125,\r
+      (byte)182,(byte)118,(byte)252,(byte)107,(byte)226,(byte)156,(byte)116,\r
+      (byte)4,(byte)241,(byte)69,(byte)157,(byte)112,(byte)89,(byte)100,\r
+      (byte)113,(byte)135,(byte)32,(byte)134,(byte)91,(byte)207,(byte)101,\r
+      (byte)230,(byte)45,(byte)168,(byte)2,(byte)27,(byte)96,(byte)37,\r
+      (byte)173,(byte)174,(byte)176,(byte)185,(byte)246,(byte)28,(byte)70,\r
+      (byte)97,(byte)105,(byte)52,(byte)64,(byte)126,(byte)15,(byte)85,\r
+      (byte)71,(byte)163,(byte)35,(byte)221,(byte)81,(byte)175,(byte)58,\r
+      (byte)195,(byte)92,(byte)249,(byte)206,(byte)186,(byte)197,(byte)234,\r
+      (byte)38,(byte)44,(byte)83,(byte)13,(byte)110,(byte)133,(byte)40,\r
+      (byte)132, 9,(byte)211,(byte)223,(byte)205,(byte)244,(byte)65,\r
+      (byte)129,(byte)77,(byte)82,(byte)106,(byte)220,(byte)55,(byte)200,\r
+      (byte)108,(byte)193,(byte)171,(byte)250,(byte)36,(byte)225,(byte)123,\r
+      (byte)8,(byte)12,(byte)189,(byte)177,(byte)74,(byte)120,(byte)136,\r
+      (byte)149,(byte)139,(byte)227,(byte)99,(byte)232,(byte)109,(byte)233,\r
+      (byte)203,(byte)213,(byte)254,(byte)59,(byte)0,(byte)29,(byte)57,\r
+      (byte)242,(byte)239,(byte)183,(byte)14,(byte)102,(byte)88,(byte)208,\r
+      (byte)228,(byte)166,(byte)119,(byte)114,(byte)248,(byte)235,(byte)117,\r
+      (byte)75,(byte)10,(byte)49,(byte)68,(byte)80,(byte)180,(byte)143,\r
+      (byte)237,(byte)31,(byte)26,(byte)219,(byte)153,(byte)141,(byte)51,\r
+      (byte)159,(byte)17,(byte)131,(byte)20\r
+    };\r
+}\r
+
+
+/**
+ * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347.
+ */
+class MD5Digest
+    extends GeneralDigest
+{
+    private static final int    DIGEST_LENGTH = 16;
+
+    private int     H1, H2, H3, H4;         // IV's
+
+    private int[]   X = new int[16];
+    private int     xOff;
+
+       /**
+        * Standard constructor
+        */
+    public MD5Digest()
+    {
+        reset();
+    }
+
+       /**
+        * Copy constructor.  This will copy the state of the provided
+        * message digest.
+        */
+       public MD5Digest(MD5Digest t)
+       {
+               super(t);
+
+               H1 = t.H1;
+               H2 = t.H2;
+               H3 = t.H3;
+               H4 = t.H4;
+
+               System.arraycopy(t.X, 0, X, 0, t.X.length);
+               xOff = t.xOff;
+       }
+
+    public String getAlgorithmName()
+    {
+        return "MD5";
+    }
+
+    public int getDigestSize()
+    {
+        return DIGEST_LENGTH;
+    }
+
+    protected void processWord(
+        byte[]  in,
+        int     inOff)
+    {
+        X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8)
+            | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); 
+
+        if (xOff == 16)
+        {
+            processBlock();
+        }
+    }
+
+    protected void processLength(
+        long    bitLength)
+    {
+        if (xOff > 14)
+        {
+            processBlock();
+        }
+
+        X[14] = (int)(bitLength & 0xffffffff);
+        X[15] = (int)(bitLength >>> 32);
+    }
+
+    private void unpackWord(
+        int     word,
+        byte[]  out,
+        int     outOff)
+    {
+        out[outOff]     = (byte)word;
+        out[outOff + 1] = (byte)(word >>> 8);
+        out[outOff + 2] = (byte)(word >>> 16);
+        out[outOff + 3] = (byte)(word >>> 24);
+    }
+
+    public int doFinal(
+        byte[]  out,
+        int     outOff)
+    {
+        finish();
+
+        unpackWord(H1, out, outOff);
+        unpackWord(H2, out, outOff + 4);
+        unpackWord(H3, out, outOff + 8);
+        unpackWord(H4, out, outOff + 12);
+
+        reset();
+
+        return DIGEST_LENGTH;
+    }
+
+    /**
+     * reset the chaining variables to the IV values.
+     */
+    public void reset()
+    {
+        super.reset();
+
+        H1 = 0x67452301;
+        H2 = 0xefcdab89;
+        H3 = 0x98badcfe;
+        H4 = 0x10325476;
+
+        xOff = 0;
+
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+
+    //
+    // round 1 left rotates
+    //
+    private static final int S11 = 7;
+    private static final int S12 = 12;
+    private static final int S13 = 17;
+    private static final int S14 = 22;
+
+    //
+    // round 2 left rotates
+    //
+    private static final int S21 = 5;
+    private static final int S22 = 9;
+    private static final int S23 = 14;
+    private static final int S24 = 20;
+
+    //
+    // round 3 left rotates
+    //
+    private static final int S31 = 4;
+    private static final int S32 = 11;
+    private static final int S33 = 16;
+    private static final int S34 = 23;
+
+    //
+    // round 4 left rotates
+    //
+    private static final int S41 = 6;
+    private static final int S42 = 10;
+    private static final int S43 = 15;
+    private static final int S44 = 21;
+
+    /*
+     * rotate int x left n bits.
+     */
+    private int rotateLeft(
+        int x,
+        int n)
+    {
+        return (x << n) | (x >>> (32 - n));
+    }
+
+    /*
+     * F, G, H and I are the basic MD5 functions.
+     */
+    private int F(
+        int u,
+        int v,
+        int w)
+    {
+        return (u & v) | (~u & w);
+    }
+
+    private int G(
+        int u,
+        int v,
+        int w)
+    {
+        return (u & w) | (v & ~w);
+    }
+
+    private int H(
+        int u,
+        int v,
+        int w)
+    {
+        return u ^ v ^ w;
+    }
+
+    private int K(
+        int u,
+        int v,
+        int w)
+    {
+        return v ^ (u | ~w);
+    }
+
+    protected void processBlock()
+    {
+        int a = H1;
+        int b = H2;
+        int c = H3;
+        int d = H4;
+
+        //
+        // Round 1 - F cycle, 16 times.
+        //
+        a = rotateLeft((a + F(b, c, d) + X[ 0] + 0xd76aa478), S11) + b;
+        d = rotateLeft((d + F(a, b, c) + X[ 1] + 0xe8c7b756), S12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[ 2] + 0x242070db), S13) + d;
+        b = rotateLeft((b + F(c, d, a) + X[ 3] + 0xc1bdceee), S14) + c;
+        a = rotateLeft((a + F(b, c, d) + X[ 4] + 0xf57c0faf), S11) + b;
+        d = rotateLeft((d + F(a, b, c) + X[ 5] + 0x4787c62a), S12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[ 6] + 0xa8304613), S13) + d;
+        b = rotateLeft((b + F(c, d, a) + X[ 7] + 0xfd469501), S14) + c;
+        a = rotateLeft((a + F(b, c, d) + X[ 8] + 0x698098d8), S11) + b;
+        d = rotateLeft((d + F(a, b, c) + X[ 9] + 0x8b44f7af), S12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[10] + 0xffff5bb1), S13) + d;
+        b = rotateLeft((b + F(c, d, a) + X[11] + 0x895cd7be), S14) + c;
+        a = rotateLeft((a + F(b, c, d) + X[12] + 0x6b901122), S11) + b;
+        d = rotateLeft((d + F(a, b, c) + X[13] + 0xfd987193), S12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[14] + 0xa679438e), S13) + d;
+        b = rotateLeft((b + F(c, d, a) + X[15] + 0x49b40821), S14) + c;
+
+        //
+        // Round 2 - G cycle, 16 times.
+        //
+        a = rotateLeft((a + G(b, c, d) + X[ 1] + 0xf61e2562), S21) + b;
+        d = rotateLeft((d + G(a, b, c) + X[ 6] + 0xc040b340), S22) + a;
+        c = rotateLeft((c + G(d, a, b) + X[11] + 0x265e5a51), S23) + d;
+        b = rotateLeft((b + G(c, d, a) + X[ 0] + 0xe9b6c7aa), S24) + c;
+        a = rotateLeft((a + G(b, c, d) + X[ 5] + 0xd62f105d), S21) + b;
+        d = rotateLeft((d + G(a, b, c) + X[10] + 0x02441453), S22) + a;
+        c = rotateLeft((c + G(d, a, b) + X[15] + 0xd8a1e681), S23) + d;
+        b = rotateLeft((b + G(c, d, a) + X[ 4] + 0xe7d3fbc8), S24) + c;
+        a = rotateLeft((a + G(b, c, d) + X[ 9] + 0x21e1cde6), S21) + b;
+        d = rotateLeft((d + G(a, b, c) + X[14] + 0xc33707d6), S22) + a;
+        c = rotateLeft((c + G(d, a, b) + X[ 3] + 0xf4d50d87), S23) + d;
+        b = rotateLeft((b + G(c, d, a) + X[ 8] + 0x455a14ed), S24) + c;
+        a = rotateLeft((a + G(b, c, d) + X[13] + 0xa9e3e905), S21) + b;
+        d = rotateLeft((d + G(a, b, c) + X[ 2] + 0xfcefa3f8), S22) + a;
+        c = rotateLeft((c + G(d, a, b) + X[ 7] + 0x676f02d9), S23) + d;
+        b = rotateLeft((b + G(c, d, a) + X[12] + 0x8d2a4c8a), S24) + c;
+
+        //
+        // Round 3 - H cycle, 16 times.
+        //
+        a = rotateLeft((a + H(b, c, d) + X[ 5] + 0xfffa3942), S31) + b;
+        d = rotateLeft((d + H(a, b, c) + X[ 8] + 0x8771f681), S32) + a;
+        c = rotateLeft((c + H(d, a, b) + X[11] + 0x6d9d6122), S33) + d;
+        b = rotateLeft((b + H(c, d, a) + X[14] + 0xfde5380c), S34) + c;
+        a = rotateLeft((a + H(b, c, d) + X[ 1] + 0xa4beea44), S31) + b;
+        d = rotateLeft((d + H(a, b, c) + X[ 4] + 0x4bdecfa9), S32) + a;
+        c = rotateLeft((c + H(d, a, b) + X[ 7] + 0xf6bb4b60), S33) + d;
+        b = rotateLeft((b + H(c, d, a) + X[10] + 0xbebfbc70), S34) + c;
+        a = rotateLeft((a + H(b, c, d) + X[13] + 0x289b7ec6), S31) + b;
+        d = rotateLeft((d + H(a, b, c) + X[ 0] + 0xeaa127fa), S32) + a;
+        c = rotateLeft((c + H(d, a, b) + X[ 3] + 0xd4ef3085), S33) + d;
+        b = rotateLeft((b + H(c, d, a) + X[ 6] + 0x04881d05), S34) + c;
+        a = rotateLeft((a + H(b, c, d) + X[ 9] + 0xd9d4d039), S31) + b;
+        d = rotateLeft((d + H(a, b, c) + X[12] + 0xe6db99e5), S32) + a;
+        c = rotateLeft((c + H(d, a, b) + X[15] + 0x1fa27cf8), S33) + d;
+        b = rotateLeft((b + H(c, d, a) + X[ 2] + 0xc4ac5665), S34) + c;
+
+        //
+        // Round 4 - K cycle, 16 times.
+        //
+        a = rotateLeft((a + K(b, c, d) + X[ 0] + 0xf4292244), S41) + b;
+        d = rotateLeft((d + K(a, b, c) + X[ 7] + 0x432aff97), S42) + a;
+        c = rotateLeft((c + K(d, a, b) + X[14] + 0xab9423a7), S43) + d;
+        b = rotateLeft((b + K(c, d, a) + X[ 5] + 0xfc93a039), S44) + c;
+        a = rotateLeft((a + K(b, c, d) + X[12] + 0x655b59c3), S41) + b;
+        d = rotateLeft((d + K(a, b, c) + X[ 3] + 0x8f0ccc92), S42) + a;
+        c = rotateLeft((c + K(d, a, b) + X[10] + 0xffeff47d), S43) + d;
+        b = rotateLeft((b + K(c, d, a) + X[ 1] + 0x85845dd1), S44) + c;
+        a = rotateLeft((a + K(b, c, d) + X[ 8] + 0x6fa87e4f), S41) + b;
+        d = rotateLeft((d + K(a, b, c) + X[15] + 0xfe2ce6e0), S42) + a;
+        c = rotateLeft((c + K(d, a, b) + X[ 6] + 0xa3014314), S43) + d;
+        b = rotateLeft((b + K(c, d, a) + X[13] + 0x4e0811a1), S44) + c;
+        a = rotateLeft((a + K(b, c, d) + X[ 4] + 0xf7537e82), S41) + b;
+        d = rotateLeft((d + K(a, b, c) + X[11] + 0xbd3af235), S42) + a;
+        c = rotateLeft((c + K(d, a, b) + X[ 2] + 0x2ad7d2bb), S43) + d;
+        b = rotateLeft((b + K(c, d, a) + X[ 9] + 0xeb86d391), S44) + c;
+
+        H1 += a;
+        H2 += b;
+        H3 += c;
+        H4 += d;
+
+        //
+        // reset the offset and clean out the word buffer.
+        //
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+}
+
+
+/**
+ * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349.
+ *
+ * It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5
+ * is the "endienness" of the word processing!
+ */
+class SHA1Digest
+    extends GeneralDigest
+{
+    private static final int    DIGEST_LENGTH = 20;
+
+    private int     H1, H2, H3, H4, H5;
+
+    private int[]   X = new int[80];
+    private int     xOff;
+
+       /**
+        * Standard constructor
+        */
+    public SHA1Digest()
+    {
+        reset();
+    }
+
+       /**
+        * Copy constructor.  This will copy the state of the provided
+        * message digest.
+        */
+       public SHA1Digest(SHA1Digest t)
+       {
+               super(t);
+
+               H1 = t.H1;
+               H2 = t.H2;
+               H3 = t.H3;
+               H4 = t.H4;
+               H5 = t.H5;
+
+               System.arraycopy(t.X, 0, X, 0, t.X.length);
+               xOff = t.xOff;
+       }
+
+    public String getAlgorithmName()
+    {
+        return "SHA-1";
+    }
+
+    public int getDigestSize()
+    {
+        return DIGEST_LENGTH;
+    }
+
+    protected void processWord(
+        byte[]  in,
+        int     inOff)
+    {
+        X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16)
+                    | ((in[inOff + 2] & 0xff) << 8) | ((in[inOff + 3] & 0xff)); 
+
+        if (xOff == 16)
+        {
+            processBlock();
+        }
+    }
+
+    private void unpackWord(
+        int     word,
+        byte[]  out,
+        int     outOff)
+    {
+        out[outOff]     = (byte)(word >>> 24);
+        out[outOff + 1] = (byte)(word >>> 16);
+        out[outOff + 2] = (byte)(word >>> 8);
+        out[outOff + 3] = (byte)word;
+    }
+
+    protected void processLength(
+        long    bitLength)
+    {
+        if (xOff > 14)
+        {
+            processBlock();
+        }
+
+        X[14] = (int)(bitLength >>> 32);
+        X[15] = (int)(bitLength & 0xffffffff);
+    }
+
+    public int doFinal(
+        byte[]  out,
+        int     outOff)
+    {
+        finish();
+
+        unpackWord(H1, out, outOff);
+        unpackWord(H2, out, outOff + 4);
+        unpackWord(H3, out, outOff + 8);
+        unpackWord(H4, out, outOff + 12);
+        unpackWord(H5, out, outOff + 16);
+
+        reset();
+
+        return DIGEST_LENGTH;
+    }
+
+    /**
+     * reset the chaining variables
+     */
+    public void reset()
+    {
+        super.reset();
+
+        H1 = 0x67452301;
+        H2 = 0xefcdab89;
+        H3 = 0x98badcfe;
+        H4 = 0x10325476;
+        H5 = 0xc3d2e1f0;
+
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+
+    //
+    // Additive constants
+    //
+    private static final int    Y1 = 0x5a827999;
+    private static final int    Y2 = 0x6ed9eba1;
+    private static final int    Y3 = 0x8f1bbcdc;
+    private static final int    Y4 = 0xca62c1d6;
+
+    private int f(
+        int    u,
+        int    v,
+        int    w)
+    {
+        return ((u & v) | ((~u) & w));
+    }
+
+    private int h(
+        int    u,
+        int    v,
+        int    w)
+    {
+        return (u ^ v ^ w);
+    }
+
+    private int g(
+        int    u,
+        int    v,
+        int    w)
+    {
+        return ((u & v) | (u & w) | (v & w));
+    }
+
+    private int rotateLeft(
+        int    x,
+        int    n)
+    {
+        return (x << n) | (x >>> (32 - n));
+    }
+
+    protected void processBlock()
+    {
+        //
+        // expand 16 word block into 80 word block.
+        //
+        for (int i = 16; i <= 79; i++)
+        {
+            X[i] = rotateLeft((X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]), 1);
+        }
+
+        //
+        // set up working variables.
+        //
+        int     A = H1;
+        int     B = H2;
+        int     C = H3;
+        int     D = H4;
+        int     E = H5;
+
+        //
+        // round 1
+        //
+        for (int j = 0; j <= 19; j++)
+        {
+            int     t = rotateLeft(A, 5) + f(B, C, D) + E + X[j] + Y1;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        //
+        // round 2
+        //
+        for (int j = 20; j <= 39; j++)
+        {
+            int     t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y2;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        //
+        // round 3
+        //
+        for (int j = 40; j <= 59; j++)
+        {
+            int     t = rotateLeft(A, 5) + g(B, C, D) + E + X[j] + Y3;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        //
+        // round 4
+        //
+        for (int j = 60; j <= 79; j++)
+        {
+            int     t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y4;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        H1 += A;
+        H2 += B;
+        H3 += C;
+        H4 += D;
+        H5 += E;
+
+        //
+        // reset the offset and clean out the word buffer.
+        //
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+}
+
+
+
+/**
+ * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this
+ * depends on your application - see PKCS1 Version 2 for details.
+ */
+class PKCS1Encoding
+    implements AsymmetricBlockCipher
+{
+    private static int      HEADER_LENGTH = 10;
+
+    private Random            random;
+    private AsymmetricBlockCipher   engine;
+    private boolean                 forEncryption;
+    private boolean                 forPrivateKey;
+
+    public PKCS1Encoding(
+        AsymmetricBlockCipher   cipher)
+    {
+        this.engine = cipher;
+    }   
+
+    public AsymmetricBlockCipher getUnderlyingCipher()
+    {
+        return engine;
+    }
+
+    public void init(
+        boolean             forEncryption,
+        CipherParameters    param)
+    {
+        AsymmetricKeyParameter  kParam;
+
+        if (param instanceof ParametersWithRandom)
+        {
+            ParametersWithRandom    rParam = (ParametersWithRandom)param;
+
+            this.random = rParam.getRandom();
+            kParam = (AsymmetricKeyParameter)rParam.getParameters();
+        }
+        else
+        {
+            this.random = new Random();
+            kParam = (AsymmetricKeyParameter)param;
+        }
+
+        engine.init(forEncryption, kParam);
+
+        this.forPrivateKey = kParam.isPrivate();
+        this.forEncryption = forEncryption;
+    }
+
+    public int getInputBlockSize()
+    {
+        int     baseBlockSize = engine.getInputBlockSize();
+
+        if (forEncryption)
+        {
+            return baseBlockSize - HEADER_LENGTH;
+        }
+        else
+        {
+            return baseBlockSize;
+        }
+    }
+
+    public int getOutputBlockSize()
+    {
+        int     baseBlockSize = engine.getOutputBlockSize();
+
+        if (forEncryption)
+        {
+            return baseBlockSize;
+        }
+        else
+        {
+            return baseBlockSize - HEADER_LENGTH;
+        }
+    }
+
+    public byte[] processBlock(
+        byte[]  in,
+        int     inOff,
+        int     inLen)
+        throws InvalidCipherTextException
+    {
+        if (forEncryption)
+        {
+            return encodeBlock(in, inOff, inLen);
+        }
+        else
+        {
+            return decodeBlock(in, inOff, inLen);
+        }
+    }
+
+    private byte[] encodeBlock(
+        byte[]  in,
+        int     inOff,
+        int     inLen)
+        throws InvalidCipherTextException
+    {
+        byte[]  block = new byte[engine.getInputBlockSize()];
+
+        if (forPrivateKey)
+        {
+            block[0] = 0x01;                        // type code 1
+
+            for (int i = 1; i != block.length - inLen - 1; i++)
+            {
+                block[i] = (byte)0xFF;
+            }
+        }
+        else
+        {
+            random.nextBytes(block);                // random fill
+
+            block[0] = 0x02;                        // type code 2
+
+            //
+            // a zero byte marks the end of the padding, so all
+            // the pad bytes must be non-zero.
+            //
+            for (int i = 1; i != block.length - inLen - 1; i++)
+            {
+                while (block[i] == 0)
+                {
+                    block[i] = (byte)random.nextInt();
+                }
+            }
+        }
+
+        block[block.length - inLen - 1] = 0x00;       // mark the end of the padding
+        System.arraycopy(in, inOff, block, block.length - inLen, inLen);
+
+        return engine.processBlock(block, 0, block.length);
+    }
+
+    /**
+     * @exception InvalidCipherTextException if the decrypted block is not in PKCS1 format.
+     */
+    private byte[] decodeBlock(
+        byte[]  in,
+        int     inOff,
+        int     inLen)
+        throws InvalidCipherTextException
+    {
+        byte[]  block = engine.processBlock(in, inOff, inLen);
+
+        if (block.length < getOutputBlockSize())
+        {
+            throw new InvalidCipherTextException("block truncated");
+        }
+
+        if (block[0] != 1 && block[0] != 2)
+        {
+            throw new InvalidCipherTextException("unknown block type");
+        }
+
+        //
+        // find and extract the message block.
+        //
+        int start;
+
+        for (start = 1; start != block.length; start++)
+        {
+            if (block[start] == 0)
+            {
+                break;
+            }
+        }
+
+        start++;           // data should start at the next byte
+
+        if (start >= block.length || start < HEADER_LENGTH)
+        {
+            throw new InvalidCipherTextException("no data in block");
+        }
+
+        byte[]  result = new byte[block.length - start];
+
+        System.arraycopy(block, start, result, 0, result.length);
+
+        return result;
+    }
+}
+\r
+\r
+class RC4Engine implements StreamCipher\r
+{\r
+    private final static int STATE_LENGTH = 256;\r
+\r
+    /*\r
+     * variables to hold the state of the RC4 engine\r
+     * during encryption and decryption\r
+     */\r
+\r
+    private byte[]      engineState = null;\r
+    private int         x = 0;\r
+    private int         y = 0;\r
+    private byte[]      workingKey = null;\r
+\r
+    /**\r
+     * initialise a RC4 cipher.\r
+     *\r
+     * @param forEncryption whether or not we are for encryption.\r
+     * @param params the parameters required to set up the cipher.\r
+     * @exception IllegalArgumentException if the params argument is\r
+     * inappropriate.\r
+     */\r
+    public void init(\r
+        boolean             forEncryption, \r
+        CipherParameters     params\r
+    )\r
+    {\r
+        if (params instanceof KeyParameter)\r
+        {\r
+            /* \r
+             * RC4 encryption and decryption is completely\r
+             * symmetrical, so the 'forEncryption' is \r
+             * irrelevant.\r
+             */\r
+            workingKey = ((KeyParameter)params).getKey();\r
+            setKey(workingKey);\r
+\r
+            return;\r
+        }\r
+\r
+        throw new IllegalArgumentException("invalid parameter passed to RC4 init - " + params.getClass().getName());\r
+    }\r
+\r
+    public String getAlgorithmName()\r
+    {\r
+        return "RC4";\r
+    }\r
+\r
+    public byte returnByte(byte in)\r
+    {\r
+        x = (x + 1) & 0xff;\r
+        y = (engineState[x] + y) & 0xff;\r
+\r
+        // swap\r
+        byte tmp = engineState[x];\r
+        engineState[x] = engineState[y];\r
+        engineState[y] = tmp;\r
+\r
+        // xor\r
+        return (byte)(in ^ engineState[(engineState[x] + engineState[y]) & 0xff]);\r
+    }\r
+\r
+    public void processBytes(\r
+        byte[]     in, \r
+        int     inOff, \r
+        int     len, \r
+        byte[]     out, \r
+        int     outOff\r
+    )\r
+    {\r
+        if ((inOff + len) > in.length)\r
+        {\r
+            throw new DataLengthException("input buffer too short");\r
+        }\r
+\r
+        if ((outOff + len) > out.length)\r
+        {\r
+            throw new DataLengthException("output buffer too short");\r
+        }\r
+\r
+        for (int i = 0; i < len ; i++)\r
+        {\r
+            x = (x + 1) & 0xff;\r
+            y = (engineState[x] + y) & 0xff;\r
+\r
+            // swap\r
+            byte tmp = engineState[x];\r
+            engineState[x] = engineState[y];\r
+            engineState[y] = tmp;\r
+\r
+            // xor\r
+            out[i+outOff] = (byte)(in[i + inOff]\r
+                    ^ engineState[(engineState[x] + engineState[y]) & 0xff]);\r
+        }\r
+    }\r
+\r
+    public void reset()\r
+    {\r
+        setKey(workingKey);\r
+    }\r
+\r
+    // Private implementation\r
+\r
+    private void setKey(byte[] keyBytes)\r
+    {\r
+        workingKey = keyBytes;\r
+\r
+        // System.out.println("the key length is ; "+ workingKey.length);\r
+\r
+        x = 0;\r
+        y = 0;\r
+\r
+        if (engineState == null)\r
+        {\r
+            engineState = new byte[STATE_LENGTH];\r
+        }\r
+\r
+        // reset the state of the engine\r
+        for (int i=0; i < STATE_LENGTH; i++)\r
+        {\r
+            engineState[i] = (byte)i;\r
+        }\r
+        \r
+        int i1 = 0;\r
+        int i2 = 0;\r
+\r
+        for (int i=0; i < STATE_LENGTH; i++)\r
+        {\r
+            i2 = ((keyBytes[i1] & 0xff) + engineState[i] + i2) & 0xff;\r
+            // do the byte-swap inline\r
+            byte tmp = engineState[i];\r
+            engineState[i] = engineState[i2];\r
+            engineState[i2] = tmp;\r
+            i1 = (i1+1) % keyBytes.length; \r
+        }\r
+    }\r
+}\r
+
+
+
+/**
+ * this does your basic RSA algorithm.
+ */
+class RSAEngine
+    implements AsymmetricBlockCipher
+{
+    private RSAKeyParameters        key;
+    private boolean                 forEncryption;
+
+    /**
+     * initialise the RSA engine.
+     *
+     * @param forEncryption true if we are encrypting, false otherwise.
+     * @param param the necessary RSA key parameters.
+     */
+    public void init(
+        boolean             forEncryption,
+        CipherParameters    param)
+    {
+        this.key = (RSAKeyParameters)param;
+        this.forEncryption = forEncryption;
+    }
+
+    /**
+     * Return the maximum size for an input block to this engine.
+     * For RSA this is always one byte less than the key size on
+     * encryption, and the same length as the key size on decryption.
+     *
+     * @return maximum size for an input block.
+     */
+    public int getInputBlockSize()
+    {
+        int     bitSize = key.getModulus().bitLength();
+
+        if (forEncryption)
+        {
+            return (bitSize + 7) / 8 - 1;
+        }
+        else
+        {
+            return (bitSize + 7) / 8;
+        }
+    }
+
+    /**
+     * Return the maximum size for an output block to this engine.
+     * For RSA this is always one byte less than the key size on
+     * decryption, and the same length as the key size on encryption.
+     *
+     * @return maximum size for an output block.
+     */
+    public int getOutputBlockSize()
+    {
+        int     bitSize = key.getModulus().bitLength();
+
+        if (forEncryption)
+        {
+            return (bitSize + 7) / 8;
+        }
+        else
+        {
+            return (bitSize + 7) / 8 - 1;
+        }
+    }
+
+    /**
+     * Process a single block using the basic RSA algorithm.
+     *
+     * @param in the input array.
+     * @param inOff the offset into the input buffer where the data starts.
+     * @param inLen the length of the data to be processed.
+     * @return the result of the RSA process.
+     * @exception DataLengthException the input block is too large.
+     */
+    public byte[] processBlock(
+        byte[]  in,
+        int     inOff,
+        int     inLen)
+    {
+        if (inLen > (getInputBlockSize() + 1))
+        {
+            throw new DataLengthException("input too large for RSA cipher.\n");
+        }
+        else if (inLen == (getInputBlockSize() + 1) && (in[inOff] & 0x80) != 0)
+        {
+            throw new DataLengthException("input too large for RSA cipher.\n");
+        }
+
+        byte[]  block;
+
+        if (inOff != 0 || inLen != in.length)
+        {
+            block = new byte[inLen];
+
+            System.arraycopy(in, inOff, block, 0, inLen);
+        }
+        else
+        {
+            block = in;
+        }
+
+        BigInteger  input = new BigInteger(1, block);
+        byte[]      output;
+
+        if (key instanceof RSAPrivateCrtKeyParameters)
+        {
+            //
+            // we have the extra factors, use the Chinese Remainder Theorem - the author
+            // wishes to express his thanks to Dirk Bonekaemper at rtsffm.com for 
+            // advice regarding the expression of this.
+            //
+            RSAPrivateCrtKeyParameters crtKey = (RSAPrivateCrtKeyParameters)key;
+
+            BigInteger d = crtKey.getExponent();
+            BigInteger p = crtKey.getP();
+            BigInteger q = crtKey.getQ();
+            BigInteger dP = crtKey.getDP();
+            BigInteger dQ = crtKey.getDQ();
+            BigInteger qInv = crtKey.getQInv();
+    
+            BigInteger mP, mQ, h, m;
+    
+            // mP = ((input mod p) ^ dP)) mod p
+            mP = (input.remainder(p)).modPow(dP, p);
+    
+            // mQ = ((input mod q) ^ dQ)) mod q
+            mQ = (input.remainder(q)).modPow(dQ, q);
+    
+            // h = qInv * (mP - mQ) mod p
+            h = mP.subtract(mQ);
+            h = h.multiply(qInv);
+            h = h.mod(p);               // mod (in Java) returns the positive residual
+    
+            // m = h * q + mQ
+            m = h.multiply(q);
+            m = m.add(mQ);
+    
+            output = m.toByteArray();
+        }
+        else
+        {
+            output = input.modPow(
+                        key.getExponent(), key.getModulus()).toByteArray();
+        }
+
+        if (forEncryption)
+        {
+            if (output[0] == 0 && output.length > getOutputBlockSize())        // have ended up with an extra zero byte, copy down.
+            {
+                byte[]  tmp = new byte[output.length - 1];
+
+                System.arraycopy(output, 1, tmp, 0, tmp.length);
+
+                return tmp;
+            }
+
+            if (output.length < getOutputBlockSize())     // have ended up with less bytes than normal, lengthen
+            {
+                byte[]  tmp = new byte[getOutputBlockSize()];
+
+                System.arraycopy(output, 0, tmp, tmp.length - output.length, output.length);
+
+                return tmp;
+            }
+        }
+        else
+        {
+            if (output[0] == 0)        // have ended up with an extra zero byte, copy down.
+            {
+                byte[]  tmp = new byte[output.length - 1];
+
+                System.arraycopy(output, 1, tmp, 0, tmp.length);
+
+                return tmp;
+            }
+        }
+        return output;
+    }
+}
+
+/**
+ * this exception is thrown whenever we find something we don't expect in a
+ * message.
+ */
+class InvalidCipherTextException 
+    extends CryptoException
+{
+    /**
+     * base constructor.
+     */
+    public InvalidCipherTextException()
+    {
+    }
+
+    /**
+     * create a InvalidCipherTextException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public InvalidCipherTextException(
+        String  message)
+    {
+        super(message);
+    }
+}
+
+
+
+class DigestInputStream
+    extends FilterInputStream
+{
+    protected Digest digest;
+
+    public DigestInputStream(
+        InputStream stream,
+        Digest      digest)
+    {
+        super(stream);
+        this.digest = digest;
+    }
+
+    public int read()
+        throws IOException
+    {
+        int b = in.read();
+
+        if (b >= 0)
+        {
+            digest.update((byte)b);
+        }
+        return b;
+    }
+
+    public int read(
+        byte[] b,
+        int off,
+        int len)
+        throws IOException
+    {
+        int n = in.read(b, off, len);
+        if (n > 0)
+        {
+            digest.update(b, off, n);
+        }
+        return n;
+    }
+
+    public Digest getDigest()
+    {
+        return digest;
+    }
+}
+
+
+
+class DigestOutputStream
+    extends FilterOutputStream
+{
+    protected Digest digest;
+
+    public DigestOutputStream(
+        OutputStream    stream,
+        Digest          digest)
+    {
+        super(stream);
+        this.digest = digest;
+    }
+
+    public void write(int b)
+        throws IOException
+    {
+        digest.update((byte)b);
+        out.write(b);
+    }
+
+    public void write(
+        byte[] b,
+        int off,
+        int len)
+        throws IOException
+    {
+        digest.update(b, off, len);
+        out.write(b, off, len);
+    }
+
+    public Digest getDigest()
+    {
+        return digest;
+    }
+}
+
+
+/**
+ * The base class for parameters to key generators.
+ */
+class KeyGenerationParameters
+{
+    private Random    random;
+    private int             strength;
+
+    /**
+     * initialise the generator with a source of randomness
+     * and a strength (in bits).
+     *
+     * @param random the random byte source.
+     * @param strength the size, in bits, of the keys we want to produce.
+     */
+    public KeyGenerationParameters(
+        Random    random,
+        int             strength)
+    {
+        this.random = random;
+        this.strength = strength;
+    }
+
+    /**
+     * return the random source associated with this
+     * generator.
+     *
+     * @return the generators random source.
+     */
+    public Random getRandom()
+    {
+        return random;
+    }
+
+    /**
+     * return the bit strength for keys produced by this generator,
+     *
+     * @return the strength of the keys this generator produces (in bits).
+     */
+    public int getStrength()
+    {
+        return strength;
+    }
+}
+
+
+class AsymmetricKeyParameter
+       implements CipherParameters
+{
+    boolean privateKey;
+
+    public AsymmetricKeyParameter(
+        boolean privateKey)
+    {
+        this.privateKey = privateKey;
+    }
+
+    public boolean isPrivate()
+    {
+        return privateKey;
+    }
+}
+
+
+class KeyParameter
+       implements CipherParameters
+{
+    private byte[]  key;
+
+    public KeyParameter(
+        byte[]  key)
+    {
+        this(key, 0, key.length);
+    }
+
+    public KeyParameter(
+        byte[]  key,
+        int     keyOff,
+        int     keyLen)
+    {
+        this.key = new byte[keyLen];
+
+        System.arraycopy(key, keyOff, this.key, 0, keyLen);
+    }
+
+    public byte[] getKey()
+    {
+        return key;
+    }
+}
+
+
+
+class ParametersWithRandom
+    implements CipherParameters
+{
+    private Random        random;
+    private CipherParameters    parameters;
+
+    public ParametersWithRandom(
+        CipherParameters    parameters,
+        Random        random)
+    {
+        this.random = random;
+        this.parameters = parameters;
+    }
+
+    public ParametersWithRandom(
+        CipherParameters    parameters)
+    {
+        this.random = null;
+        this.parameters = parameters;
+    }
+
+    public Random getRandom()
+    {
+        if (random == null)
+        {
+            random = new Random();
+        }
+        return random;
+    }
+
+    public CipherParameters getParameters()
+    {
+        return parameters;
+    }
+}
+
+
+
+class RSAKeyParameters
+    extends AsymmetricKeyParameter
+{
+    private BigInteger      modulus;
+    private BigInteger      exponent;
+
+    public RSAKeyParameters(
+        boolean     isPrivate,
+        BigInteger  modulus,
+        BigInteger  exponent)
+    {
+        super(isPrivate);
+
+        this.modulus = modulus;
+        this.exponent = exponent;
+    }   
+
+    public BigInteger getModulus()
+    {
+        return modulus;
+    }
+
+    public BigInteger getExponent()
+    {
+        return exponent;
+    }
+}
+
+
+class RSAPrivateCrtKeyParameters
+    extends RSAKeyParameters
+{
+    private BigInteger  e;
+    private BigInteger  p;
+    private BigInteger  q;
+    private BigInteger  dP;
+    private BigInteger  dQ;
+    private BigInteger  qInv;
+
+    /**
+     * 
+     */
+    public RSAPrivateCrtKeyParameters(
+        BigInteger  modulus,
+        BigInteger  publicExponent,
+        BigInteger  privateExponent,
+        BigInteger  p,
+        BigInteger  q,
+        BigInteger  dP,
+        BigInteger  dQ,
+        BigInteger  qInv)
+    {
+        super(true, modulus, privateExponent);
+
+        this.e = publicExponent;
+        this.p = p;
+        this.q = q;
+        this.dP = dP;
+        this.dQ = dQ;
+        this.qInv = qInv;
+    }
+
+    public BigInteger getPublicExponent()
+    {
+        return e;
+    }
+
+    public BigInteger getP()
+    {
+        return p;
+    }
+
+    public BigInteger getQ()
+    {
+        return q;
+    }
+
+    public BigInteger getDP()
+    {
+        return dP;
+    }
+
+    public BigInteger getDQ()
+    {
+        return dQ;
+    }
+
+    public BigInteger getQInv()
+    {
+        return qInv;
+    }
+}
+
+/**
+ * the foundation class for the exceptions thrown by the crypto packages.
+ */
+class RuntimeCryptoException 
+    extends RuntimeException
+{
+    /**
+     * base constructor.
+     */
+    public RuntimeCryptoException()
+    {
+    }
+
+    /**
+     * create a RuntimeCryptoException with the given message.
+     *
+     * @param message the message to be carried with the exception.
+     */
+    public RuntimeCryptoException(
+        String  message)
+    {
+        super(message);
+    }
+}
+
+/**
+ * a wrapper for block ciphers with a single byte block size, so that they
+ * can be treated like stream ciphers.
+ */
+class StreamBlockCipher
+    implements StreamCipher
+{
+    private BlockCipher  cipher;
+
+    private byte[]  oneByte = new byte[1];
+
+    /**
+     * basic constructor.
+     *
+     * @param cipher the block cipher to be wrapped.
+     * @exception IllegalArgumentException if the cipher has a block size other than
+     * one.
+     */
+    public StreamBlockCipher(
+        BlockCipher cipher)
+    {
+        if (cipher.getBlockSize() != 1)
+        {
+            throw new IllegalArgumentException("block cipher block size != 1.");
+        }
+
+        this.cipher = cipher;
+    }
+
+    /**
+     * initialise the underlying cipher.
+     *
+     * @param forEncryption true if we are setting up for encryption, false otherwise.
+     * @param param the necessary parameters for the underlying cipher to be initialised.
+     */
+    public void init(
+        boolean forEncryption,
+        CipherParameters params)
+    {
+        cipher.init(forEncryption, params);
+    }
+
+    /**
+     * return the name of the algorithm we are wrapping.
+     *
+     * @return the name of the algorithm we are wrapping.
+     */
+    public String getAlgorithmName()
+    {
+        return cipher.getAlgorithmName();
+    }
+
+    /**
+     * encrypt/decrypt a single byte returning the result.
+     *
+     * @param in the byte to be processed.
+     * @return the result of processing the input byte.
+     */
+    public byte returnByte(
+        byte    in)
+    {
+        oneByte[0] = in;
+
+        cipher.processBlock(oneByte, 0, oneByte, 0);
+
+        return oneByte[0];
+    }
+
+    /**
+     * process a block of bytes from in putting the result into out.
+     * 
+     * @param in the input byte array.
+     * @param inOff the offset into the in array where the data to be processed starts.
+     * @param len the number of bytes to be processed.
+     * @param out the output buffer the processed bytes go into.   
+     * @param outOff the offset into the output byte array the processed data stars at.
+     * @exception DataLengthException if the output buffer is too small.
+     */
+    public void processBytes(
+        byte[]  in,
+        int     inOff,
+        int     len,
+        byte[]  out,
+        int     outOff)
+        throws DataLengthException
+    {
+        if (outOff + len > out.length)
+        {
+            throw new DataLengthException("output buffer too small in processBytes()");
+        }
+
+        for (int i = 0; i != len; i++)
+        {
+                cipher.processBlock(in, inOff + i, out, outOff + i);
+        }
+    }
+
+    /**
+     * reset the underlying cipher. This leaves it in the same state
+     * it was at after the last init (if there was one).
+     */
+    public void reset()
+    {
+        cipher.reset();
+    }
+}
+
+/**
+ * the interface stream ciphers conform to.
+ */
+interface StreamCipher
+{
+    /**
+     * Initialise the cipher.
+     *
+     * @param forEncryption if true the cipher is initialised for
+     *  encryption, if false for decryption.
+     * @param param the key and other data required by the cipher.
+     * @exception IllegalArgumentException if the params argument is
+     * inappropriate.
+     */
+    public void init(boolean forEncryption, CipherParameters params)
+        throws IllegalArgumentException;
+
+    /**
+     * Return the name of the algorithm the cipher implements.
+     *
+     * @return the name of the algorithm the cipher implements.
+     */
+    public String getAlgorithmName();
+
+    /**
+     * encrypt/decrypt a single byte returning the result.
+     *
+     * @param in the byte to be processed.
+     * @return the result of processing the input byte.
+     */
+    public byte returnByte(byte in);
+
+    /**
+     * process a block of bytes from in putting the result into out.
+     *
+     * @param in the input byte array.
+     * @param inOff the offset into the in array where the data to be processed starts.
+     * @param len the number of bytes to be processed.
+     * @param out the output buffer the processed bytes go into.
+     * @param outOff the offset into the output byte array the processed data stars at.
+     * @exception DataLengthException if the output buffer is too small.
+     */
+    public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+        throws DataLengthException;
+
+    /**
+     * reset the cipher. This leaves it in the same state
+     * it was at after the last init (if there was one).
+     */
+    public void reset();
+}
+
+class Base64
+{
+       private static final byte[] encodingTable =
+               {
+                   (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+            (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+            (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+            (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+                   (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+            (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+            (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+            (byte)'v',
+                   (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+                   (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6',
+            (byte)'7', (byte)'8', (byte)'9',
+                   (byte)'+', (byte)'/'
+               };
+
+       /**
+        * encode the input data producong a base 64 encoded byte array.
+        *
+        * @return a byte array containing the base 64 encoded data.
+        */
+       public static byte[] encode(
+               byte[]  data)
+       {
+               byte[]  bytes;
+               
+               int modulus = data.length % 3;
+               if (modulus == 0)
+               {
+                       bytes = new byte[4 * data.length / 3];
+               }
+               else
+               {
+                       bytes = new byte[4 * ((data.length / 3) + 1)];
+               }
+
+        int dataLength = (data.length - modulus);
+               int a1, a2, a3;
+               for (int i = 0, j = 0; i < dataLength; i += 3, j += 4)
+               {
+                       a1 = data[i] & 0xff;
+                       a2 = data[i + 1] & 0xff;
+                       a3 = data[i + 2] & 0xff;
+
+                       bytes[j] = encodingTable[(a1 >>> 2) & 0x3f];
+                       bytes[j + 1] = encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f];
+                       bytes[j + 2] = encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f];
+                       bytes[j + 3] = encodingTable[a3 & 0x3f];
+               }
+
+               /*
+                * process the tail end.
+                */
+               int     b1, b2, b3;
+               int     d1, d2;
+
+               switch (modulus)
+               {
+               case 0:         /* nothing left to do */
+                       break;
+               case 1:
+                       d1 = data[data.length - 1] & 0xff;
+                       b1 = (d1 >>> 2) & 0x3f;
+                       b2 = (d1 << 4) & 0x3f;
+
+                       bytes[bytes.length - 4] = encodingTable[b1];
+                       bytes[bytes.length - 3] = encodingTable[b2];
+                       bytes[bytes.length - 2] = (byte)'=';
+                       bytes[bytes.length - 1] = (byte)'=';
+                       break;
+               case 2:
+                       d1 = data[data.length - 2] & 0xff;
+                       d2 = data[data.length - 1] & 0xff;
+
+                       b1 = (d1 >>> 2) & 0x3f;
+                       b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
+                       b3 = (d2 << 2) & 0x3f;
+
+                       bytes[bytes.length - 4] = encodingTable[b1];
+                       bytes[bytes.length - 3] = encodingTable[b2];
+                       bytes[bytes.length - 2] = encodingTable[b3];
+                       bytes[bytes.length - 1] = (byte)'=';
+                       break;
+               }
+
+               return bytes;
+       }
+
+       /*
+        * set up the decoding table.
+        */
+       private static final byte[] decodingTable;
+
+       static
+       {
+               decodingTable = new byte[128];
+
+               for (int i = 'A'; i <= 'Z'; i++)
+               {
+                       decodingTable[i] = (byte)(i - 'A');
+               }
+
+               for (int i = 'a'; i <= 'z'; i++)
+               {
+                       decodingTable[i] = (byte)(i - 'a' + 26);
+               }
+
+               for (int i = '0'; i <= '9'; i++)
+               {
+                       decodingTable[i] = (byte)(i - '0' + 52);
+               }
+
+               decodingTable['+'] = 62;
+               decodingTable['/'] = 63;
+       }
+
+       /**
+        * decode the base 64 encoded input data.
+        *
+        * @return a byte array representing the decoded data.
+        */
+       public static byte[] decode(
+               byte[]  data)
+       {
+               byte[]  bytes;
+               byte    b1, b2, b3, b4;
+
+               if (data[data.length - 2] == '=')
+               {
+                       bytes = new byte[(((data.length / 4) - 1) * 3) + 1];
+               }
+               else if (data[data.length - 1] == '=')
+               {
+                       bytes = new byte[(((data.length / 4) - 1) * 3) + 2];
+               }
+               else
+               {
+                       bytes = new byte[((data.length / 4) * 3)];
+               }
+
+               for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3)
+               {
+                       b1 = decodingTable[data[i]];
+                       b2 = decodingTable[data[i + 1]];
+                       b3 = decodingTable[data[i + 2]];
+                       b4 = decodingTable[data[i + 3]];
+
+                       bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
+                       bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
+                       bytes[j + 2] = (byte)((b3 << 6) | b4);
+               }
+
+               if (data[data.length - 2] == '=')
+               {
+                       b1 = decodingTable[data[data.length - 4]];
+                       b2 = decodingTable[data[data.length - 3]];
+
+                       bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
+               }
+               else if (data[data.length - 1] == '=')
+               {
+                       b1 = decodingTable[data[data.length - 4]];
+                       b2 = decodingTable[data[data.length - 3]];
+                       b3 = decodingTable[data[data.length - 2]];
+
+                       bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
+                       bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
+               }
+               else
+               {
+                       b1 = decodingTable[data[data.length - 4]];
+                       b2 = decodingTable[data[data.length - 3]];
+                       b3 = decodingTable[data[data.length - 2]];
+                       b4 = decodingTable[data[data.length - 1]];
+
+                       bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
+                       bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
+                       bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
+               }
+
+               return bytes;
+       }
+
+       /**
+        * decode the base 64 encoded String data.
+        *
+        * @return a byte array representing the decoded data.
+        */
+       public static byte[] decode(
+               String  data)
+       {
+               byte[]  bytes;
+               byte    b1, b2, b3, b4;
+
+               if (data.charAt(data.length() - 2) == '=')
+               {
+                       bytes = new byte[(((data.length() / 4) - 1) * 3) + 1];
+               }
+               else if (data.charAt(data.length() - 1) == '=')
+               {
+                       bytes = new byte[(((data.length() / 4) - 1) * 3) + 2];
+               }
+               else
+               {
+                       bytes = new byte[((data.length() / 4) * 3)];
+               }
+
+               for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3)
+               {
+                       b1 = decodingTable[data.charAt(i)];
+                       b2 = decodingTable[data.charAt(i + 1)];
+                       b3 = decodingTable[data.charAt(i + 2)];
+                       b4 = decodingTable[data.charAt(i + 3)];
+
+                       bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
+                       bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
+                       bytes[j + 2] = (byte)((b3 << 6) | b4);
+               }
+
+               if (data.charAt(data.length() - 2) == '=')
+               {
+                       b1 = decodingTable[data.charAt(data.length() - 4)];
+                       b2 = decodingTable[data.charAt(data.length() - 3)];
+
+                       bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
+               }
+               else if (data.charAt(data.length() - 1) == '=')
+               {
+                       b1 = decodingTable[data.charAt(data.length() - 4)];
+                       b2 = decodingTable[data.charAt(data.length() - 3)];
+                       b3 = decodingTable[data.charAt(data.length() - 2)];
+
+                       bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
+                       bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
+               }
+               else
+               {
+                       b1 = decodingTable[data.charAt(data.length() - 4)];
+                       b2 = decodingTable[data.charAt(data.length() - 3)];
+                       b3 = decodingTable[data.charAt(data.length() - 2)];
+                       b4 = decodingTable[data.charAt(data.length() - 1)];
+
+                       bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
+                       bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
+                       bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
+               }
+
+               return bytes;
+       }
+}
diff --git a/src/org/xwt/shoehorn3/ShoeHorn.java b/src/org/xwt/shoehorn3/ShoeHorn.java
new file mode 100644 (file)
index 0000000..f7bc319
--- /dev/null
@@ -0,0 +1,628 @@
+// Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
+package org.xwt.shoehorn3;
+
+import java.applet.*;
+import java.util.*;
+import java.lang.reflect.*;
+import java.io.*;
+import java.net.*;
+import java.util.zip.*;
+import java.awt.*;
+import java.math.*;
+
+/** This class is XWT's presence on the user's computer; it must be run as trusted code */
+public class ShoeHorn extends Applet {
+
+    // Startup Phase ////////////////////////////////////////////////////////////////////
+    private String build = null;
+    public ShoeHorn() { log("*** constructor invoked for " + this.getClass().getName()); }
+
+    public final String getParameter(String arg) { return super.getParameter(arg); }
+    public final void main(String[] s) { new ShoeHorn().start(); }
+    private void log(String s) { System.out.println(s); }
+
+    /** this just ensures that we are running with full privileges */
+    public final void start() {
+        build = getParameter("build");
+        new Thread() { public void run() { 
+            log("ShoeHorn thread spawned");
+            try {
+
+                if (System.getProperty("java.vendor", "").startsWith("Netscape")) {
+                    log("Detected Navigator 4.x");
+                    Method m = Class.forName("netscape.security.PrivilegeManager").getMethod("enablePrivilege", new Class[] { String.class });
+                    m.invoke(null, new Object[] { "MarimbaInternalTarget" });
+                    m.invoke(null, new Object[] { "UniversalExecAccess" });
+                    m.invoke(null, new Object[] { "UniversalPropertyRead" });
+                    go();
+
+                } else if (System.getProperty("java.vendor", "").startsWith("Microsoft")) {
+                    //com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.SYSTEM);
+                    Class permissionIdClass = Class.forName("com.ms.security.PermissionID");
+                    Object o = permissionIdClass.getField("SYSTEM").get(null);
+                    Method m = Class.forName("com.ms.security.PolicyEngine").getMethod("assertPermission", new Class[] { permissionIdClass });
+                    m.invoke(null, new Object[] { o });
+                    go();
+
+                } else {
+                    log("Detected non-Navigator JVM");
+                    Method m = Class.forName("org.xwt.shoehorn3.ShoeHorn$Java12").getMethod("run", new Class[] { Object.class });
+                    m.invoke(null, new Object[] { ShoeHorn.this });
+                }
+            } catch (Throwable e) {
+                if (e instanceof InvocationTargetException) e = ((InvocationTargetException)e).getTargetException();
+                e.printStackTrace();
+                update(-1.0, "Error; please check the Java console");
+            }
+        } }.start();
+    }
+
+    /** ask Java Plugin for privs */
+    private static class Java12 {
+        public static void run(final Object a) {
+            java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() {
+                    public Object run() {
+                        ((ShoeHorn)a).go();
+                        return null;
+                    }
+                });
+        }
+    }
+
+
+    // Implantation ////////////////////////////////////////////////////////////////////
+
+    /** inserts the required entries into the user's ~/.java.policy */
+    private void modifyPolicyFile() throws IOException {
+        log("Adjusting ~/.java.policy");
+        File policy = new File(System.getProperty("user.home") + File.separatorChar + ".java.policy");
+        if (policy.exists()) {
+            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(policy)));
+            String s = null;
+            while((s = br.readLine()) != null)
+                if (s.startsWith("// XWT_MARKER:")) {
+                    log("Java policy file has already been adjusted");
+                    return;
+                }
+        }
+        FileOutputStream fos = new FileOutputStream(policy.getAbsolutePath(), true);
+        PrintWriter pw = new PrintWriter(new OutputStreamWriter(fos));
+        pw.println("");
+        pw.println("// XWT_MARKER: this line and the following two grant blocks are required for XWT; DO NOT REMOVE THEM.");
+        pw.println("grant {");
+        pw.println("    permission java.io.FilePermission \"${user.home}${/}.xwt${/}shoehorn.jar\", \"read\";");
+        pw.println("};");
+        pw.println("grant codebase \"file:${user.home}${/}.xwt${/}shoehorn.jar\" {");
+        pw.println("    permission java.security.AllPermission;");
+        pw.println("};");
+        pw.println("// END_XWT_MARKER");
+        pw.println("");
+        pw.flush();
+        pw.close();
+    }
+
+    /** read ourselves out of the resources and write a jarfile to some trusted place */
+    private void implantSelf() throws IOException {
+        InputStream manifest = getClass().getClassLoader().getResourceAsStream("META-INF/manifest.mf");
+        log("my classloader is " + getClass().getClassLoader().getClass().getName());
+        ClassLoader loader = getClass().getClassLoader();
+        if (manifest == null || loader == null ||
+            (loader.getClass().getName().indexOf("Applet") == -1 && loader.getClass().getName().indexOf("Plugin") == -1))
+            return;
+        BufferedReader br = new BufferedReader(new InputStreamReader(manifest));
+        Vector entries = new Vector();
+        String s = null;
+        while((s = br.readLine()) != null)
+            if (s.startsWith("Name: "))
+                entries.addElement(s.substring(6));
+
+        String ext_dirs = System.getProperty("java.ext.dirs");
+        log("java.ext.dirs = " + ext_dirs);
+        ext_dirs = ext_dirs + File.pathSeparatorChar + System.getProperty("user.home") + File.separatorChar + ".xwt";
+        StringTokenizer st = new StringTokenizer(ext_dirs, File.pathSeparatorChar + "");
+        while(st.hasMoreTokens()) {
+            String dir = st.nextToken();
+            new File(dir).mkdirs();
+            try {
+                // we have to modify the policy file BEFORE implanting in ~/.xwt to ensure that the policy mods work
+                if (!st.hasMoreTokens()) modifyPolicyFile();
+                implantInDirectory(dir, entries);
+                return;
+            } catch (IOException e) {
+                log("Failed to implant in " + dir + " due to " + e);
+            }
+        }
+        log("Failed to implant self!");
+    }
+
+    private void implantInDirectory(String dir, Vector entries) throws IOException {
+        File f = new File(dir + File.separatorChar + "shoehorn.tmp");
+        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(f));
+        for(int i=0; i<entries.size(); i++) {
+            zos.putNextEntry(new ZipEntry(entries.elementAt(i).toString()));
+            InputStream is = this.getClass().getClassLoader().getResourceAsStream(entries.elementAt(i).toString());
+            byte[] buf = new byte[1024];
+            while(true) {
+                int read = is.read(buf, 0, buf.length);
+                if (read == -1) break;
+                zos.write(buf, 0, read);
+            }
+            is.close();
+        }
+        zos.close();
+        f.renameTo(new File(dir + File.separatorChar + "shoehorn.jar"));
+        log("Succeeded in implanting in " + dir);
+    }
+
+
+    // Startup Phase ////////////////////////////////////////////////////////////////////
+
+    public final void go() {
+        try {
+            update(0.0, "");
+            implantSelf();
+            
+            File file;
+            String os_name = System.getProperty("os.name", "");
+            log("os.name == " + os_name);
+            Vector command = new Vector();
+            
+            String arch = null;
+            if (os_name.indexOf("Linux") != -1) {
+                arch = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("/bin/uname -m").getInputStream())).readLine();
+                log("arch is " + arch);
+            }
+            
+            if (os_name.indexOf("Linux") != -1 && arch.charAt(0) == 'i' && arch.substring(2, 4).equals("86")) {
+                file = fetch("xwt-" + build + ".linux.gz");
+                safeWaitFor(Runtime.getRuntime().exec("/bin/chmod +x " + file.getAbsolutePath()));
+                command.addElement(file.getAbsolutePath());
+                
+            } else if (os_name.indexOf("Windows") != -1) {
+                file = fetch("xwt-" + build + ".cab");
+                command.addElement(file.getAbsolutePath());
+                
+            } else {
+                file = fetch("xwt-" + build + ".jar");
+                command.addElement(findJvmBinary());
+                command.addElement("-jar");
+                command.addElement(file.getAbsolutePath());
+            }
+            
+            if ("true".equals(getParameter("showrenders"))) command.addElement("-s");
+            if ("true".equals(getParameter("verbose"))) command.addElement("-v");
+            if (getParameter("log") != null) {
+                command.addElement("-l");
+                command.addElement(getParameter("log"));
+            }
+            command.addElement(getParameter("xwar"));
+            spawn(command);
+        } catch (Exception e) {
+            update(-1.0, "Error; please check the Java console");
+            e.printStackTrace();
+        }
+    }
+
+    /** searches for the JVM binary in the usual places */
+    private String findJvmBinary() throws IOException {
+        String jvmBinary = null;
+
+        // HACK: prefer jdk1.3 on OSX; it has better fonts
+        if (new File("/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Commands/java").exists())
+            jvmBinary = "/System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Commands/java";
+
+        // check JAVA_HOME
+        String javaHome = getEnv("JAVA_HOME");
+        if (jvmBinary == null && javaHome != null && !javaHome.equals("")) {
+            jvmBinary = javaHome + File.separatorChar + "bin" + File.separatorChar + "java";
+            if (!new File(jvmBinary).exists()) jvmBinary = null;
+        }
+        
+        // check common locations
+        if (jvmBinary == null)
+            for(int i=0; i<commonJavaLocations.length; i++)
+                if (new File(commonJavaLocations[i]).exists()) {
+                    jvmBinary = commonJavaLocations[i];
+                    break;
+                }
+        
+        // check PATH
+        if (jvmBinary == null) {
+            String path = getEnv("PATH");
+            StringTokenizer st = new StringTokenizer(path, File.pathSeparatorChar + "");
+            while(st.hasMoreTokens()) {
+                String s = st.nextToken();
+                if (new File(s + File.separatorChar + "java").exists()) {
+                    jvmBinary = s + File.separatorChar + "java";
+                    break;
+                }
+            }
+        }
+        
+        // check ${java.home}
+        javaHome = System.getProperty("java.home");
+        if (jvmBinary == null && javaHome != null && !javaHome.equals("")) {
+            jvmBinary = javaHome + File.separatorChar + "bin" + File.separatorChar + "java";
+            if (!new File(jvmBinary).exists()) jvmBinary = null;
+        }
+        
+        if (jvmBinary == null)
+            throw new Error("couldn't find JVM binary! JAVA_HOME=" + getEnv("JAVA_HOME") + " PATH=" + getEnv("PATH"));
+        else
+            return jvmBinary;
+    }
+
+    private void spawn(Vector command) throws IOException {
+        String proxy = detectProxy();
+        log("proxy settings: " + proxy);
+
+        String[] command_vec;
+        command.copyInto(command_vec = new String[command.size()]);
+        log("executing:");
+        for(int i=0; i<command_vec.length; i++) log("    \"" + command_vec[i] + "\"");
+
+        Process p;
+        if (proxy == null) {
+            p = Runtime.getRuntime().exec(command_vec);
+        } else if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
+            // hrm, win32 doesn't like it when we probe the environment; that doesn't matter, since the
+            // win32 environ vars are pretty much useless anyways.
+            p = Runtime.getRuntime().exec(command_vec, new String[] { proxy });
+        } else {
+            p = Runtime.getRuntime().exec(command_vec, dumpEnv(proxy));
+        }
+        BufferedReader stderr = new BufferedReader(new InputStreamReader(p.getErrorStream()));
+        
+        if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) {
+            update(1.0, "XWT Loaded");
+        } else {
+            String s = stderr.readLine();
+            update(1.0, "XWT Loaded");
+            while(s != null) {
+                log(s);
+                s = stderr.readLine();
+            }
+        }
+        log("exiting...");
+    }
+
+
+    // Utility Functions /////////////////////////////////////////////////////////////////////
+    
+    /** Voodoo to extract the proxy settings from Sun's Java Plugin */
+    private String detectProxy() {
+        try {
+            Vector ret = new Vector();
+            
+            Class PluginProxyHandler = Class.forName("sun.plugin.protocol.PluginProxyHandler");
+            Method getDefaultProxyHandler = PluginProxyHandler.getMethod("getDefaultProxyHandler", new Class[] { });
+            Object proxyHandler = getDefaultProxyHandler.invoke(null, new Object[] { });
+            
+            Class ProxyHandler = Class.forName("sun.plugin.protocol.ProxyHandler");
+            Method getProxyInfo = ProxyHandler.getMethod("getProxyInfo", new Class[] { URL.class });
+            Object proxyInfo = getProxyInfo.invoke(proxyHandler, new Object[] { new URL("http://www.xwt.org") });
+            
+            Class ProxyInfo = Class.forName("sun.plugin.protocol.ProxyInfo");
+            
+            if (((Boolean)ProxyInfo.getMethod("isProxyUsed", new Class[] { }).invoke(proxyInfo, new Object[] { })).booleanValue())
+                return "http_proxy=" +
+                    (String)ProxyInfo.getMethod("getProxy", new Class[] { }).invoke(proxyInfo, new Object[] { }) + ":" +
+                    ((Integer)ProxyInfo.getMethod("getPort", new Class[] { }).invoke(proxyInfo, new Object[] { })).intValue();
+
+            if (((Boolean)ProxyInfo.getMethod("isSocksUsed", new Class[] { }).invoke(proxyInfo, new Object[] { })).booleanValue())
+                return "socks_proxy=" +
+                    (String)ProxyInfo.getMethod("getSocksProxy", new Class[] { }).invoke(proxyInfo, new Object[] { }) + ":" +
+                    ((Integer)ProxyInfo.getMethod("getSocksPort", new Class[] { }).invoke(proxyInfo, new Object[] { })).intValue();
+
+            return null;
+            
+        } catch (Throwable e) {
+            log("exception while querying sun.plugin.protocol.PluginProxyHandler: " + e);
+            return null;
+        }
+    }
+
+    // The Netscape JVM has a bug which causes waitFor() to lock up, so we wait no more than 2000ms
+    private void safeWaitFor(final Process p) {
+        final Object o = new Object();
+        new Thread() {
+                public void run() {
+                    try {
+                        p.waitFor();
+                    } catch (InterruptedException e) { }
+                    synchronized(o) { o.notify(); }
+                }
+            }.start();
+        try {
+            synchronized(o) { o.wait(2000); }
+        } catch (InterruptedException e) { }
+    }
+
+    /** fetches a file from the distribution site, writing it to the appropriate place */
+    private File fetch(String filename) throws IOException {
+        String tmpdir = System.getProperty("user.home") + File.separatorChar + ".xwt";
+        new File(tmpdir).mkdirs();
+        URL u = new URL("http://dist.xwt.org/" + filename);
+        if (filename.endsWith(".gz")) filename = filename.substring(0, filename.length() - 3);
+        if (filename.endsWith(".cab")) filename = filename.substring(0, filename.length() - 4) + ".exe";
+        File target = new File(tmpdir + File.separatorChar + filename);
+        if (target.exists()) return target;
+        loadFromURL(u, target, filename);
+        return target;
+    }
+
+    /** loads a file from a url, verifying that it was properly signed */
+    private void loadFromURL(URL u, File target, String filename) throws IOException {
+
+        final URLConnection uc = u.openConnection();
+        final int signatureLength = 128;
+        final byte[] signature = new byte[signatureLength];
+
+        InputStream is = uc.getInputStream();
+        final int contentLength = uc.getContentLength();
+
+        // display the progress indicator as we go
+        InputStream dis = new FilterInputStream(is) {
+                int total = 0;
+                public int read() throws IOException {
+                    int ret = super.read();
+                    if (ret != -1) total++;
+                    double loaded = ((double)total) / ((double)uc.getContentLength());
+                    update(loaded, "Loading XWT: " + ((int)Math.ceil(loaded * 100)) + "%");
+                    return ret;
+                }
+                public int read(byte[] buf, int off, int len) throws IOException {
+                    int ret = super.read(buf, off, len);
+                    if (ret != -1) total += ret;
+                    double loaded = ((double)total) / ((double)uc.getContentLength());
+                    update(loaded, "Loading XWT: " + ((int)Math.ceil(loaded * 100)) + "%");
+                    return ret;
+                }
+            };
+
+        // decompress if needed
+        if (u.toString().endsWith(".cab")) dis = CAB.getFileInputStream(dis, "xwt-" + build + ".exe");
+        else if (u.toString().endsWith(".gz")) dis = new GZIPInputStream(dis);
+
+        // digest and copy the file to target.tmp, omitting the last signatureLength bytes
+        SHA1Digest sha1 = new SHA1Digest();
+        OutputStream fos = new DigestOutputStream(new FileOutputStream(target + ".tmp"), sha1);
+        byte[] buf = new byte[1024 * 128];
+        int numread = 0;
+        while(true) {
+            while(numread < buf.length) {
+                int read = dis.read(buf, numread, buf.length - numread);
+                if (read == -1) break;
+                numread += read;
+            }
+            if (numread < buf.length) {
+                System.arraycopy(buf, numread - signatureLength, signature, 0, signatureLength);
+                fos.write(buf, 0, numread - signatureLength);
+                break;
+            } else {
+                fos.write(buf, 0, buf.length - signatureLength);
+                System.arraycopy(buf, buf.length - signatureLength, buf, 0, signatureLength);
+                numread = signatureLength;
+            }
+        }
+        fos.close();
+
+        // construct our hash
+        byte[] hash = new byte[sha1.getDigestSize()];
+        sha1.doFinal(hash, 0);
+
+        // decrypt the signature hash
+        DERInputStream deris = new DERInputStream(new ByteArrayInputStream(Base64.decode(XWT_foundation_public_key)));
+        SubjectPublicKeyInfo pki = new SubjectPublicKeyInfo((DERConstructedSequence)deris.readObject());
+        RSAPublicKeyStructure rsa_pks = new RSAPublicKeyStructure((DERConstructedSequence)pki.getPublicKey());
+        BigInteger modulus = rsa_pks.getModulus();
+        BigInteger exponent = rsa_pks.getPublicExponent();
+        AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine());
+        rsa.init(false, new RSAKeyParameters(false, modulus, exponent));
+
+        // compare
+        try {
+            byte[] decryptedHash = rsa.processBlock(signature, 0, signature.length);
+            for(int i=0; i<hash.length; i++)
+                if (decryptedHash[i] != hash[i]) {
+                    new File(target + ".tmp").delete();
+                    throw new Error("invalid signature on " + filename);
+                }
+        } catch (InvalidCipherTextException e) {
+            new File(target + ".tmp").delete();
+            throw new Error(e);
+        }
+
+        // good to go; rename the file
+        new File(target + ".tmp").renameTo(target);
+    }
+
+    /** retrieves an environment variable */
+    private static String getEnv(String key) throws IOException {
+        Process p = Runtime.getRuntime().exec(new String[] { "/bin/sh", "-c", "echo $" + key });
+        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        return br.readLine();
+    }
+
+    /** dumps the environment plus an additional variable */
+    private static String[] dumpEnv(String newvar) throws IOException {
+        Vector v = new Vector();
+        String os = System.getProperty("os.name").toLowerCase();
+        Process p;
+        if (os.indexOf("windows 9") > -1) {
+            p = Runtime.getRuntime().exec("command.com /c set");
+        } else if ((os.indexOf("nt") > -1) || (os.indexOf("windows 2000") > -1) ) {
+            p = Runtime.getRuntime().exec("cmd.exe /c set");
+        } else {  
+            p = Runtime.getRuntime().exec("env");
+        }
+        BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        String s;
+        while ((s = br.readLine()) != null) v.addElement(s);
+        v.addElement(newvar);
+        String[] ret;
+        v.copyInto(ret = new String[v.size()]);
+        return ret;
+    }
+
+
+
+    // Applet Painting Functions ////////////////////////////////////////////////////////
+
+    private Image backbuffer = null;
+    public final void paint(Graphics g) { if (backbuffer != null) g.drawImage(backbuffer, 0, 0, null); }
+
+    public final Graphics getGraphics() { return super.getGraphics(); }
+    public final Dimension getSize() { return super.getSize(); }
+
+    private void update(double loaded, String text) {
+        // indicates we should paint ourselves
+        Graphics g2 = getGraphics();
+        String s = text;
+        if (backbuffer == null || backbuffer.getWidth(null) != getSize().width || backbuffer.getHeight(null) != getSize().height)
+            backbuffer = createImage(getSize().width, getSize().height);
+        if (backbuffer == null) return;
+        Graphics g = backbuffer.getGraphics();
+
+        g.setColor(loaded < 0 ? Color.red : Color.blue);
+        loaded = Math.abs(loaded);
+
+        int w = (int)((double)getSize().width * loaded);
+        g.fillRect(0, 0, w, getSize().height);
+        g.setColor(Color.darkGray);
+        g.fillRect(w, 0, getSize().width - w, getSize().height);
+
+        Font f = new Font("Sans-serif", Font.BOLD, 12);
+        FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(f);
+        g.setFont(f);
+
+        int x = (getSize().width - fm.stringWidth(s)) / 2;
+        int y = ((getSize().height - fm.getMaxAscent() - fm.getMaxDescent()) / 2) + fm.getMaxAscent();
+        g.setColor(Color.white);
+        g.drawString(s, x, y);
+
+        if (g2 != null) {
+            g2.setClip(0, 0, getSize().width, getSize().height);
+            g2.drawImage(backbuffer, 0, 0, null);
+        }
+    }
+
+
+    // Misc Constants ///////////////////////////////////////////////////////////
+
+    private static final String XWT_foundation_public_key =
+        "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoVweDZ+h3jcN2Qz1YwWJR8gVF0m/IE" +
+        "BasLIcvey9y1VkyN9jY6b9qm/Z2UoVtvAlgezd4CsJedUCIMe7uURyHGnqI4MLrozxLz3" +
+        "zqx5EYChsJt+Ju/f44KYnMx7upUQ4irfxOj6RpHy3E5GbW4XO96WwFlOuaR8+HRwKCXGP" +
+        "QvQIDAQAB";
+
+    private static final String[] commonJavaLocations = new String[] {
+        "/usr/bin/java",
+        "/usr/java/bin/java",
+        "/usr/local/bin/java",
+        "/usr/local/java/bin/java",
+        "/usr/lib/j2sdk1.4/bin/java",
+        "/usr/lib/j2sdk1.3/bin/java",
+        "/usr/lib/j2sdk1.2/bin/java"
+    };
+
+}
+
+
+
+
+/*
+
+/// NSJVM Notes ///////////////////////////////////////////////////////////////////
+
+  PrivilegeManager.enablePrivilege("MarimbaInternalTarget");
+  PrivilegeManager.enablePrivilege("UniversalSystemClipboardAccess");
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalConnect"); (sockets)
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalSystemClipboardAccess");
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalTopLevelWindow");
+
+  - Netscape's ClassLoader.getResource() is broken, see http://developer.netscape.com/docs/technote/java/getresource/getresource.html
+  - this will fix it: netscape.security.PrivilegeManager.enablePrivilege("UniversalPropertyRead");
+
+  - If you create a classloader, include
+        public boolean checkMatchPrincipalAlways(int i) {
+            return ((AppletClassLoader)this.getClass().getClassLoader()).checkMatchPrincipalAlways(i);
+        }
+        
+  - protected int _modifiersToButtonNumber(int modifiers) {
+        if ((modifiers & (InputEvent.BUTTON1_MASK & 15)) == (InputEvent.BUTTON1_MASK & 15)) return 1;
+        if ((modifiers & (InputEvent.BUTTON2_MASK & 15)) == (InputEvent.BUTTON2_MASK & 15)) return 3;
+        if ((modifiers & (InputEvent.BUTTON3_MASK & 15)) == (InputEvent.BUTTON3_MASK & 15)) return 2;
+
+
+/// MSJVM Notes ///////////////////////////////////////////////////////////////////
+
+- To sniff the JVM, check if (window.clientInformation.javaEnabled()), or if
+  (navigator.javaEnabled()).  See also http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndetect/html/detectvm.asp
+
+- MSJVM note: you have to write to the getGraphics() of a Frame before you can setIconImage() it.
+
+- How to create trusted classes within an already-running MSJVM:
+
+    class MicrosoftClassLoader extends SecurityClassLoader {
+        static PermissionSet ps;  // a PermissionSet that allows <i>everything</i>
+        static {
+                PermissionDataSet pds = new PermissionDataSet();
+                    IPermission perm = new IPermission() {
+                    public void check(Object request) { }
+                    public IPermission combine(IPermission other) { return this; }
+                    public IPermission copy() { return this; }
+                    public int compareSet(Object o) { return SetComparison.DISJOINT; }
+                };
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ClientStoragePermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ExecutionPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.FileIOPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.MultimediaPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.NetIOPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.PrintingPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.PropertyPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ReflectionPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.SecurityPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.SystemStreamsPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.ThreadPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.UIPermission"), perm);
+            pds.add(PolicyEngine.permissionNameToID("com.ms.security.permissions.UserFileIOPermission"), perm);
+            ps = new PermissionSet(pds);
+        }
+        public Class defineClass(String name, byte[] b) {
+            Class c = super.defineClass(name, b, 0, b.length, ps, com.ms.security.PolicyEngine.getPrincipalOfClass(Resources.class));
+            if (name.startsWith("net")) System.out.println("defineClass " + name + " yielded " + c);
+            // for some reason, the MSJVM doesn't request resolves properly, so we have to do it manually on every class-load
+            resolveClass(c);
+            return c;
+        }
+
+- How to inhibit background-clearing on the MSJVM
+
+        // Used to ensure that getPeer() doesn't go off in an infinite loop
+        boolean ok = false;
+
+        // The MSJVM needs us to occasionally falsify getPeer() in order to thwart unneeded background-clearing
+        public ComponentPeer getPeer() {
+            if (Thread.currentThread() == Platform.fastPathThread) return super.getPeer();
+            if (ok) return super.getPeer();
+
+            // to prevent recursive stack-dumping... =)
+            ok = true;
+            Dimension d = getSize();
+            ok = false;
+            if (last != null && last.width == d.width && last.height == d.height)
+                return super.getPeer();
+
+            String s = com.ms.wfc.util.Debug.getStackTraceText();
+            for(int i = s.indexOf('d'); i != -1; i = s.indexOf('d', i + 1)) {
+                if (s.regionMatches(i, "doClearAndPaint", 0, 15)) {
+                    last = getSize(); 
+                    update(null);
+                    return null;
+                }
+            }
+            return super.getPeer();
+        }
+
+*/