diff options
Diffstat (limited to 'libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java')
-rw-r--r-- | libjava/classpath/gnu/javax/net/ssl/provider/XMLSessionContext.java | 619 |
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); + } + } +} |