summaryrefslogtreecommitdiff
path: root/libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java')
-rw-r--r--libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java619
1 files changed, 619 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java b/libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java
new file mode 100644
index 00000000000..dcfa9d4adc9
--- /dev/null
+++ b/libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java
@@ -0,0 +1,619 @@
+/* XMLSessionContext.java -- XML-encoded persistent SSL sessions.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is a part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at
+your option) any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.javax.net.ssl.provider;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import gnu.javax.crypto.mac.IMac;
+import gnu.javax.crypto.mac.MacFactory;
+import gnu.javax.crypto.mode.IMode;
+import gnu.javax.crypto.mode.ModeFactory;
+import gnu.javax.crypto.prng.IPBE;
+import gnu.java.security.prng.IRandom;
+import gnu.java.security.prng.PRNGFactory;
+
+import gnu.javax.net.ssl.Base64;
+
+/**
+ * An implementation of session contexts that stores session data on the
+ * filesystem in a simple XML-encoded file.
+ */
+class XMLSessionContext extends SessionContext
+{
+
+ // Fields.
+ // -------------------------------------------------------------------------
+
+ private final File file;
+ private final IRandom pbekdf;
+ private final boolean compress;
+ private final SecureRandom random;
+ private boolean encoding;
+
+ // Constructor.
+ // -------------------------------------------------------------------------
+
+ XMLSessionContext() throws IOException, SAXException
+ {
+ file = new File(Util.getSecurityProperty("jessie.SessionContext.xml.file"));
+ String password = Util.getSecurityProperty("jessie.SessionContext.xml.password");
+ compress = new Boolean(Util.getSecurityProperty("jessie.SessionContext.xml.compress")).booleanValue();
+ if (password == null)
+ {
+ password = "";
+ }
+ pbekdf = PRNGFactory.getInstance("PBKDF2-HMAC-SHA1");
+ HashMap kdfattr = new HashMap();
+ kdfattr.put(IPBE.PASSWORD, password.toCharArray());
+ // Dummy salt. This is replaced by a real salt when encoding.
+ kdfattr.put(IPBE.SALT, new byte[8]);
+ kdfattr.put(IPBE.ITERATION_COUNT, new Integer(1000));
+ pbekdf.init(kdfattr);
+ encoding = false;
+ if (file.exists())
+ {
+ decode();
+ }
+ encoding = true;
+ random = new SecureRandom ();
+ }
+
+ // Instance methods.
+ // -------------------------------------------------------------------------
+
+ synchronized boolean addSession(Session.ID sessionId, Session session)
+ {
+ boolean ret = super.addSession(sessionId, session);
+ if (ret && encoding)
+ {
+ try
+ {
+ encode();
+ }
+ catch (IOException ioe)
+ {
+ }
+ }
+ return ret;
+ }
+
+ synchronized void notifyAccess(Session session)
+ {
+ try
+ {
+ encode();
+ }
+ catch (IOException ioe)
+ {
+ }
+ }
+
+ synchronized boolean removeSession(Session.ID sessionId)
+ {
+ if (super.removeSession(sessionId))
+ {
+ try
+ {
+ encode();
+ }
+ catch (Exception x)
+ {
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void decode() throws IOException, SAXException
+ {
+ SAXParser parser = null;
+ try
+ {
+ parser = SAXParserFactory.newInstance().newSAXParser();
+ }
+ catch (Exception x)
+ {
+ throw new Error(x.toString());
+ }
+ SAXHandler handler = new SAXHandler(this, pbekdf);
+ InputStream in = null;
+ if (compress)
+ in = new GZIPInputStream(new FileInputStream(file));
+ else
+ in = new FileInputStream(file);
+ parser.parse(in, handler);
+ }
+
+ private void encode() throws IOException
+ {
+ IMode cipher = ModeFactory.getInstance("CBC", "AES", 16);
+ HashMap cipherAttr = new HashMap();
+ IMac mac = MacFactory.getInstance("HMAC-SHA1");
+ HashMap macAttr = new HashMap();
+ byte[] key = new byte[32];
+ byte[] iv = new byte[16];
+ byte[] mackey = new byte[20];
+ byte[] salt = new byte[8];
+ byte[] encryptedSecret = new byte[48];
+ cipherAttr.put(IMode.KEY_MATERIAL, key);
+ cipherAttr.put(IMode.IV, iv);
+ cipherAttr.put(IMode.STATE, new Integer(IMode.ENCRYPTION));
+ macAttr.put(IMac.MAC_KEY_MATERIAL, mackey);
+ PrintStream out = null;
+ if (compress)
+ {
+ out = new PrintStream(new GZIPOutputStream(new FileOutputStream(file)));
+ }
+ else
+ {
+ out = new PrintStream(new FileOutputStream(file));
+ }
+ out.println("<?xml version=\"1.0\"?>");
+ out.println("<!DOCTYPE sessions [");
+ out.println(" <!ELEMENT sessions (session*)>");
+ out.println(" <!ATTLIST sessions size CDATA \"0\">");
+ out.println(" <!ATTLIST sessions timeout CDATA \"86400\">");
+ out.println(" <!ELEMENT session (peer, certificates?, secret)>");
+ out.println(" <!ATTLIST session id CDATA #REQUIRED>");
+ out.println(" <!ATTLIST session protocol (SSLv3|TLSv1|TLSv1.1) #REQUIRED>");
+ out.println(" <!ATTLIST session suite CDATA #REQUIRED>");
+ out.println(" <!ATTLIST session created CDATA #REQUIRED>");
+ out.println(" <!ATTLIST session timestamp CDATA #REQUIRED>");
+ out.println(" <!ELEMENT peer (certificates?)>");
+ out.println(" <!ATTLIST peer host CDATA #REQUIRED>");
+ out.println(" <!ELEMENT certificates (#PCDATA)>");
+ out.println(" <!ATTLIST certificates type CDATA \"X.509\">");
+ out.println(" <!ELEMENT secret (#PCDATA)>");
+ out.println(" <!ATTLIST secret salt CDATA #REQUIRED>");
+ out.println("]>");
+ out.println();
+ out.print("<sessions size=\"");
+ out.print(cacheSize);
+ out.print("\" timeout=\"");
+ out.print(timeout);
+ out.println("\">");
+ for (Iterator it = sessions.entrySet().iterator(); it.hasNext(); )
+ {
+ Map.Entry entry = (Map.Entry) it.next();
+ Session.ID id = (Session.ID) entry.getKey();
+ Session session = (Session) entry.getValue();
+ if (!session.valid)
+ {
+ continue;
+ }
+ out.print("<session id=\"");
+ out.print(Base64.encode(id.getId(), 0));
+ out.print("\" suite=\"");
+ out.print(session.getCipherSuite());
+ out.print("\" protocol=\"");
+ out.print(session.getProtocol());
+ out.print("\" created=\"");
+ out.print(session.getCreationTime());
+ out.print("\" timestamp=\"");
+ out.print(session.getLastAccessedTime());
+ out.println("\">");
+ out.print("<peer host=\"");
+ out.print(session.getPeerHost());
+ out.println("\">");
+ Certificate[] certs = session.getPeerCertificates();
+ if (certs != null && certs.length > 0)
+ {
+ out.print("<certificates type=\"");
+ out.print(certs[0].getType());
+ out.println("\">");
+ for (int i = 0; i < certs.length; i++)
+ {
+ out.println("-----BEGIN CERTIFICATE-----");
+ try
+ {
+ out.print(Base64.encode(certs[i].getEncoded(), 70));
+ }
+ catch (CertificateEncodingException cee)
+ {
+ throw new IOException(cee.toString());
+ }
+ out.println("-----END CERTIFICATE-----");
+ }
+ out.println("</certificates>");
+ }
+ out.println("</peer>");
+ certs = session.getLocalCertificates();
+ if (certs != null && certs.length > 0)
+ {
+ out.print("<certificates type=\"");
+ out.print(certs[0].getType());
+ out.println("\">");
+ for (int i = 0; i < certs.length; i++)
+ {
+ out.println("-----BEGIN CERTIFICATE-----");
+ try
+ {
+ out.print(Base64.encode(certs[i].getEncoded(), 70));
+ }
+ catch (CertificateEncodingException cee)
+ {
+ throw new IOException(cee.toString());
+ }
+ out.println("-----END CERTIFICATE-----");
+ }
+ out.println("</certificates>");
+ }
+ random.nextBytes (salt);
+ pbekdf.init(Collections.singletonMap(IPBE.SALT, salt));
+ try
+ {
+ pbekdf.nextBytes(key, 0, key.length);
+ pbekdf.nextBytes(iv, 0, iv.length);
+ pbekdf.nextBytes(mackey, 0, mackey.length);
+ cipher.reset();
+ cipher.init(cipherAttr);
+ mac.init(macAttr);
+ }
+ catch (Exception ex)
+ {
+ throw new Error(ex.toString());
+ }
+ for (int i = 0; i < session.masterSecret.length; i += 16)
+ {
+ cipher.update(session.masterSecret, i, encryptedSecret, i);
+ }
+ mac.update(encryptedSecret, 0, encryptedSecret.length);
+ byte[] macValue = mac.digest();
+ out.print("<secret salt=\"");
+ out.print(Base64.encode(salt, 0));
+ out.println("\">");
+ out.print(Base64.encode(Util.concat(encryptedSecret, macValue), 70));
+ out.println("</secret>");
+ out.println("</session>");
+ }
+ out.println("</sessions>");
+ out.close();
+ }
+
+ // Inner class.
+ // -------------------------------------------------------------------------
+
+ private class SAXHandler extends DefaultHandler
+ {
+
+ // Field.
+ // -----------------------------------------------------------------------
+
+ private SessionContext context;
+ private Session current;
+ private IRandom pbekdf;
+ private StringBuffer buf;
+ private String certType;
+ private int state;
+ private IMode cipher;
+ private HashMap cipherAttr;
+ private IMac mac;
+ private HashMap macAttr;
+ private byte[] key;
+ private byte[] iv;
+ private byte[] mackey;
+
+ private static final int START = 0;
+ private static final int SESSIONS = 1;
+ private static final int SESSION = 2;
+ private static final int PEER = 3;
+ private static final int PEER_CERTS = 4;
+ private static final int CERTS = 5;
+ private static final int SECRET = 6;
+
+ // Constructor.
+ // -----------------------------------------------------------------------
+
+ SAXHandler(SessionContext context, IRandom pbekdf)
+ {
+ this.context = context;
+ this.pbekdf = pbekdf;
+ buf = new StringBuffer();
+ state = START;
+ cipher = ModeFactory.getInstance("CBC", "AES", 16);
+ cipherAttr = new HashMap();
+ mac = MacFactory.getInstance("HMAC-SHA1");
+ macAttr = new HashMap();
+ key = new byte[32];
+ iv = new byte[16];
+ mackey = new byte[20];
+ cipherAttr.put(IMode.KEY_MATERIAL, key);
+ cipherAttr.put(IMode.IV, iv);
+ cipherAttr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
+ macAttr.put(IMac.MAC_KEY_MATERIAL, mackey);
+ }
+
+ // Instance methods.
+ // -----------------------------------------------------------------------
+
+ public void startElement(String u, String n, String qname, Attributes attr)
+ throws SAXException
+ {
+ qname = qname.toLowerCase();
+ switch (state)
+ {
+ case START:
+ if (qname.equals("sessions"))
+ {
+ try
+ {
+ timeout = Integer.parseInt(attr.getValue("timeout"));
+ cacheSize = Integer.parseInt(attr.getValue("size"));
+ if (timeout <= 0 || cacheSize < 0)
+ throw new SAXException("timeout or cache size out of range");
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw new SAXException(nfe);
+ }
+ state = SESSIONS;
+ }
+ else
+ throw new SAXException("expecting sessions");
+ break;
+
+ case SESSIONS:
+ if (qname.equals("session"))
+ {
+ try
+ {
+ current = new Session(Long.parseLong(attr.getValue("created")));
+ current.enabledSuites = new ArrayList(SSLSocket.supportedSuites);
+ current.enabledProtocols = new TreeSet(SSLSocket.supportedProtocols);
+ current.context = context;
+ current.sessionId = new Session.ID(Base64.decode(attr.getValue("id")));
+ current.setLastAccessedTime(Long.parseLong(attr.getValue("timestamp")));
+ }
+ catch (Exception ex)
+ {
+ throw new SAXException(ex);
+ }
+ String prot = attr.getValue("protocol");
+ if (prot.equals("SSLv3"))
+ current.protocol = ProtocolVersion.SSL_3;
+ else if (prot.equals("TLSv1"))
+ current.protocol = ProtocolVersion.TLS_1;
+ else if (prot.equals("TLSv1.1"))
+ current.protocol = ProtocolVersion.TLS_1_1;
+ else
+ throw new SAXException("bad protocol: " + prot);
+ current.cipherSuite = CipherSuite.forName(attr.getValue("suite"));
+ state = SESSION;
+ }
+ else
+ throw new SAXException("expecting session");
+ break;
+
+ case SESSION:
+ if (qname.equals("peer"))
+ {
+ current.peerHost = attr.getValue("host");
+ state = PEER;
+ }
+ else if (qname.equals("certificates"))
+ {
+ certType = attr.getValue("type");
+ state = CERTS;
+ }
+ else if (qname.equals("secret"))
+ {
+ byte[] salt = null;
+ try
+ {
+ salt = Base64.decode(attr.getValue("salt"));
+ }
+ catch (IOException ioe)
+ {
+ throw new SAXException(ioe);
+ }
+ pbekdf.init(Collections.singletonMap(IPBE.SALT, salt));
+ state = SECRET;
+ }
+ else
+ throw new SAXException("bad element: " + qname);
+ break;
+
+ case PEER:
+ if (qname.equals("certificates"))
+ {
+ certType = attr.getValue("type");
+ state = PEER_CERTS;
+ }
+ else
+ throw new SAXException("bad element: " + qname);
+ break;
+
+ default:
+ throw new SAXException("bad element: " + qname);
+ }
+ }
+
+ public void endElement(String uri, String name, String qname)
+ throws SAXException
+ {
+ qname = qname.toLowerCase();
+ switch (state)
+ {
+ case SESSIONS:
+ if (qname.equals("sessions"))
+ state = START;
+ else
+ throw new SAXException("expecting sessions");
+ break;
+
+ case SESSION:
+ if (qname.equals("session"))
+ {
+ current.valid = true;
+ context.addSession(current.sessionId, current);
+ state = SESSIONS;
+ }
+ else
+ throw new SAXException("expecting session");
+ break;
+
+ case PEER:
+ if (qname.equals("peer"))
+ state = SESSION;
+ else
+ throw new SAXException("unexpected element: " + qname);
+ break;
+
+ case PEER_CERTS:
+ if (qname.equals("certificates"))
+ {
+ try
+ {
+ CertificateFactory fact = CertificateFactory.getInstance(certType);
+ current.peerCerts = (Certificate[])
+ fact.generateCertificates(new ByteArrayInputStream(
+ buf.toString().getBytes())).toArray(new Certificate[0]);
+ }
+ catch (Exception ex)
+ {
+ throw new SAXException(ex);
+ }
+ current.peerVerified = true;
+ state = PEER;
+ }
+ else
+ throw new SAXException("unexpected element: " + qname);
+ break;
+
+ case CERTS:
+ if (qname.equals("certificates"))
+ {
+ try
+ {
+ CertificateFactory fact = CertificateFactory.getInstance(certType);
+ current.localCerts = (Certificate[])
+ fact.generateCertificates(new ByteArrayInputStream(
+ buf.toString().getBytes())).toArray(new Certificate[0]);
+ }
+ catch (Exception ex)
+ {
+ throw new SAXException(ex);
+ }
+ state = SESSION;
+ }
+ else
+ throw new SAXException("unexpected element: " + qname);
+ break;
+
+ case SECRET:
+ if (qname.equals("secret"))
+ {
+ byte[] encrypted = null;
+ try
+ {
+ encrypted = Base64.decode(buf.toString());
+ if (encrypted.length != 68)
+ throw new IOException("encrypted secret not 68 bytes long");
+ pbekdf.nextBytes(key, 0, key.length);
+ pbekdf.nextBytes(iv, 0, iv.length);
+ pbekdf.nextBytes(mackey, 0, mackey.length);
+ cipher.reset();
+ cipher.init(cipherAttr);
+ mac.init(macAttr);
+ }
+ catch (Exception ex)
+ {
+ throw new SAXException(ex);
+ }
+ mac.update(encrypted, 0, 48);
+ byte[] macValue = mac.digest();
+ for (int i = 0; i < macValue.length; i++)
+ {
+ if (macValue[i] != encrypted[48+i])
+ throw new SAXException("MAC mismatch");
+ }
+ current.masterSecret = new byte[48];
+ for (int i = 0; i < current.masterSecret.length; i += 16)
+ {
+ cipher.update(encrypted, i, current.masterSecret, i);
+ }
+ state = SESSION;
+ }
+ else
+ throw new SAXException("unexpected element: " + qname);
+ break;
+
+ default:
+ throw new SAXException("unexpected element: " + qname);
+ }
+ buf.setLength(0);
+ }
+
+ public void characters(char[] ch, int off, int len) throws SAXException
+ {
+ if (state != CERTS && state != PEER_CERTS && state != SECRET)
+ {
+ throw new SAXException("illegal character data");
+ }
+ buf.append(ch, off, len);
+ }
+ }
+}