Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / gnu / javax / net / ssl / provider / XMLSessionContext.java
blobdcfa9d4adc968cd207b986cbfb29eb9b678ac386
1 /* XMLSessionContext.java -- XML-encoded persistent SSL sessions.
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is a part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 USA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.javax.net.ssl.provider;
41 import java.io.ByteArrayInputStream;
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileOutputStream;
45 import java.io.InputStream;
46 import java.io.IOException;
47 import java.io.PrintStream;
49 import java.security.SecureRandom;
50 import java.security.cert.Certificate;
51 import java.security.cert.CertificateEncodingException;
52 import java.security.cert.CertificateFactory;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.Iterator;
58 import java.util.Map;
59 import java.util.TreeSet;
60 import java.util.zip.GZIPInputStream;
61 import java.util.zip.GZIPOutputStream;
63 import javax.xml.parsers.SAXParser;
64 import javax.xml.parsers.SAXParserFactory;
66 import org.xml.sax.Attributes;
67 import org.xml.sax.SAXException;
68 import org.xml.sax.helpers.DefaultHandler;
70 import gnu.javax.crypto.mac.IMac;
71 import gnu.javax.crypto.mac.MacFactory;
72 import gnu.javax.crypto.mode.IMode;
73 import gnu.javax.crypto.mode.ModeFactory;
74 import gnu.javax.crypto.prng.IPBE;
75 import gnu.java.security.prng.IRandom;
76 import gnu.java.security.prng.PRNGFactory;
78 import gnu.javax.net.ssl.Base64;
80 /**
81 * An implementation of session contexts that stores session data on the
82 * filesystem in a simple XML-encoded file.
84 class XMLSessionContext extends SessionContext
87 // Fields.
88 // -------------------------------------------------------------------------
90 private final File file;
91 private final IRandom pbekdf;
92 private final boolean compress;
93 private final SecureRandom random;
94 private boolean encoding;
96 // Constructor.
97 // -------------------------------------------------------------------------
99 XMLSessionContext() throws IOException, SAXException
101 file = new File(Util.getSecurityProperty("jessie.SessionContext.xml.file"));
102 String password = Util.getSecurityProperty("jessie.SessionContext.xml.password");
103 compress = new Boolean(Util.getSecurityProperty("jessie.SessionContext.xml.compress")).booleanValue();
104 if (password == null)
106 password = "";
108 pbekdf = PRNGFactory.getInstance("PBKDF2-HMAC-SHA1");
109 HashMap kdfattr = new HashMap();
110 kdfattr.put(IPBE.PASSWORD, password.toCharArray());
111 // Dummy salt. This is replaced by a real salt when encoding.
112 kdfattr.put(IPBE.SALT, new byte[8]);
113 kdfattr.put(IPBE.ITERATION_COUNT, new Integer(1000));
114 pbekdf.init(kdfattr);
115 encoding = false;
116 if (file.exists())
118 decode();
120 encoding = true;
121 random = new SecureRandom ();
124 // Instance methods.
125 // -------------------------------------------------------------------------
127 synchronized boolean addSession(Session.ID sessionId, Session session)
129 boolean ret = super.addSession(sessionId, session);
130 if (ret && encoding)
134 encode();
136 catch (IOException ioe)
140 return ret;
143 synchronized void notifyAccess(Session session)
147 encode();
149 catch (IOException ioe)
154 synchronized boolean removeSession(Session.ID sessionId)
156 if (super.removeSession(sessionId))
160 encode();
162 catch (Exception x)
165 return true;
167 return false;
170 private void decode() throws IOException, SAXException
172 SAXParser parser = null;
175 parser = SAXParserFactory.newInstance().newSAXParser();
177 catch (Exception x)
179 throw new Error(x.toString());
181 SAXHandler handler = new SAXHandler(this, pbekdf);
182 InputStream in = null;
183 if (compress)
184 in = new GZIPInputStream(new FileInputStream(file));
185 else
186 in = new FileInputStream(file);
187 parser.parse(in, handler);
190 private void encode() throws IOException
192 IMode cipher = ModeFactory.getInstance("CBC", "AES", 16);
193 HashMap cipherAttr = new HashMap();
194 IMac mac = MacFactory.getInstance("HMAC-SHA1");
195 HashMap macAttr = new HashMap();
196 byte[] key = new byte[32];
197 byte[] iv = new byte[16];
198 byte[] mackey = new byte[20];
199 byte[] salt = new byte[8];
200 byte[] encryptedSecret = new byte[48];
201 cipherAttr.put(IMode.KEY_MATERIAL, key);
202 cipherAttr.put(IMode.IV, iv);
203 cipherAttr.put(IMode.STATE, new Integer(IMode.ENCRYPTION));
204 macAttr.put(IMac.MAC_KEY_MATERIAL, mackey);
205 PrintStream out = null;
206 if (compress)
208 out = new PrintStream(new GZIPOutputStream(new FileOutputStream(file)));
210 else
212 out = new PrintStream(new FileOutputStream(file));
214 out.println("<?xml version=\"1.0\"?>");
215 out.println("<!DOCTYPE sessions [");
216 out.println(" <!ELEMENT sessions (session*)>");
217 out.println(" <!ATTLIST sessions size CDATA \"0\">");
218 out.println(" <!ATTLIST sessions timeout CDATA \"86400\">");
219 out.println(" <!ELEMENT session (peer, certificates?, secret)>");
220 out.println(" <!ATTLIST session id CDATA #REQUIRED>");
221 out.println(" <!ATTLIST session protocol (SSLv3|TLSv1|TLSv1.1) #REQUIRED>");
222 out.println(" <!ATTLIST session suite CDATA #REQUIRED>");
223 out.println(" <!ATTLIST session created CDATA #REQUIRED>");
224 out.println(" <!ATTLIST session timestamp CDATA #REQUIRED>");
225 out.println(" <!ELEMENT peer (certificates?)>");
226 out.println(" <!ATTLIST peer host CDATA #REQUIRED>");
227 out.println(" <!ELEMENT certificates (#PCDATA)>");
228 out.println(" <!ATTLIST certificates type CDATA \"X.509\">");
229 out.println(" <!ELEMENT secret (#PCDATA)>");
230 out.println(" <!ATTLIST secret salt CDATA #REQUIRED>");
231 out.println("]>");
232 out.println();
233 out.print("<sessions size=\"");
234 out.print(cacheSize);
235 out.print("\" timeout=\"");
236 out.print(timeout);
237 out.println("\">");
238 for (Iterator it = sessions.entrySet().iterator(); it.hasNext(); )
240 Map.Entry entry = (Map.Entry) it.next();
241 Session.ID id = (Session.ID) entry.getKey();
242 Session session = (Session) entry.getValue();
243 if (!session.valid)
245 continue;
247 out.print("<session id=\"");
248 out.print(Base64.encode(id.getId(), 0));
249 out.print("\" suite=\"");
250 out.print(session.getCipherSuite());
251 out.print("\" protocol=\"");
252 out.print(session.getProtocol());
253 out.print("\" created=\"");
254 out.print(session.getCreationTime());
255 out.print("\" timestamp=\"");
256 out.print(session.getLastAccessedTime());
257 out.println("\">");
258 out.print("<peer host=\"");
259 out.print(session.getPeerHost());
260 out.println("\">");
261 Certificate[] certs = session.getPeerCertificates();
262 if (certs != null && certs.length > 0)
264 out.print("<certificates type=\"");
265 out.print(certs[0].getType());
266 out.println("\">");
267 for (int i = 0; i < certs.length; i++)
269 out.println("-----BEGIN CERTIFICATE-----");
272 out.print(Base64.encode(certs[i].getEncoded(), 70));
274 catch (CertificateEncodingException cee)
276 throw new IOException(cee.toString());
278 out.println("-----END CERTIFICATE-----");
280 out.println("</certificates>");
282 out.println("</peer>");
283 certs = session.getLocalCertificates();
284 if (certs != null && certs.length > 0)
286 out.print("<certificates type=\"");
287 out.print(certs[0].getType());
288 out.println("\">");
289 for (int i = 0; i < certs.length; i++)
291 out.println("-----BEGIN CERTIFICATE-----");
294 out.print(Base64.encode(certs[i].getEncoded(), 70));
296 catch (CertificateEncodingException cee)
298 throw new IOException(cee.toString());
300 out.println("-----END CERTIFICATE-----");
302 out.println("</certificates>");
304 random.nextBytes (salt);
305 pbekdf.init(Collections.singletonMap(IPBE.SALT, salt));
308 pbekdf.nextBytes(key, 0, key.length);
309 pbekdf.nextBytes(iv, 0, iv.length);
310 pbekdf.nextBytes(mackey, 0, mackey.length);
311 cipher.reset();
312 cipher.init(cipherAttr);
313 mac.init(macAttr);
315 catch (Exception ex)
317 throw new Error(ex.toString());
319 for (int i = 0; i < session.masterSecret.length; i += 16)
321 cipher.update(session.masterSecret, i, encryptedSecret, i);
323 mac.update(encryptedSecret, 0, encryptedSecret.length);
324 byte[] macValue = mac.digest();
325 out.print("<secret salt=\"");
326 out.print(Base64.encode(salt, 0));
327 out.println("\">");
328 out.print(Base64.encode(Util.concat(encryptedSecret, macValue), 70));
329 out.println("</secret>");
330 out.println("</session>");
332 out.println("</sessions>");
333 out.close();
336 // Inner class.
337 // -------------------------------------------------------------------------
339 private class SAXHandler extends DefaultHandler
342 // Field.
343 // -----------------------------------------------------------------------
345 private SessionContext context;
346 private Session current;
347 private IRandom pbekdf;
348 private StringBuffer buf;
349 private String certType;
350 private int state;
351 private IMode cipher;
352 private HashMap cipherAttr;
353 private IMac mac;
354 private HashMap macAttr;
355 private byte[] key;
356 private byte[] iv;
357 private byte[] mackey;
359 private static final int START = 0;
360 private static final int SESSIONS = 1;
361 private static final int SESSION = 2;
362 private static final int PEER = 3;
363 private static final int PEER_CERTS = 4;
364 private static final int CERTS = 5;
365 private static final int SECRET = 6;
367 // Constructor.
368 // -----------------------------------------------------------------------
370 SAXHandler(SessionContext context, IRandom pbekdf)
372 this.context = context;
373 this.pbekdf = pbekdf;
374 buf = new StringBuffer();
375 state = START;
376 cipher = ModeFactory.getInstance("CBC", "AES", 16);
377 cipherAttr = new HashMap();
378 mac = MacFactory.getInstance("HMAC-SHA1");
379 macAttr = new HashMap();
380 key = new byte[32];
381 iv = new byte[16];
382 mackey = new byte[20];
383 cipherAttr.put(IMode.KEY_MATERIAL, key);
384 cipherAttr.put(IMode.IV, iv);
385 cipherAttr.put(IMode.STATE, new Integer(IMode.DECRYPTION));
386 macAttr.put(IMac.MAC_KEY_MATERIAL, mackey);
389 // Instance methods.
390 // -----------------------------------------------------------------------
392 public void startElement(String u, String n, String qname, Attributes attr)
393 throws SAXException
395 qname = qname.toLowerCase();
396 switch (state)
398 case START:
399 if (qname.equals("sessions"))
403 timeout = Integer.parseInt(attr.getValue("timeout"));
404 cacheSize = Integer.parseInt(attr.getValue("size"));
405 if (timeout <= 0 || cacheSize < 0)
406 throw new SAXException("timeout or cache size out of range");
408 catch (NumberFormatException nfe)
410 throw new SAXException(nfe);
412 state = SESSIONS;
414 else
415 throw new SAXException("expecting sessions");
416 break;
418 case SESSIONS:
419 if (qname.equals("session"))
423 current = new Session(Long.parseLong(attr.getValue("created")));
424 current.enabledSuites = new ArrayList(SSLSocket.supportedSuites);
425 current.enabledProtocols = new TreeSet(SSLSocket.supportedProtocols);
426 current.context = context;
427 current.sessionId = new Session.ID(Base64.decode(attr.getValue("id")));
428 current.setLastAccessedTime(Long.parseLong(attr.getValue("timestamp")));
430 catch (Exception ex)
432 throw new SAXException(ex);
434 String prot = attr.getValue("protocol");
435 if (prot.equals("SSLv3"))
436 current.protocol = ProtocolVersion.SSL_3;
437 else if (prot.equals("TLSv1"))
438 current.protocol = ProtocolVersion.TLS_1;
439 else if (prot.equals("TLSv1.1"))
440 current.protocol = ProtocolVersion.TLS_1_1;
441 else
442 throw new SAXException("bad protocol: " + prot);
443 current.cipherSuite = CipherSuite.forName(attr.getValue("suite"));
444 state = SESSION;
446 else
447 throw new SAXException("expecting session");
448 break;
450 case SESSION:
451 if (qname.equals("peer"))
453 current.peerHost = attr.getValue("host");
454 state = PEER;
456 else if (qname.equals("certificates"))
458 certType = attr.getValue("type");
459 state = CERTS;
461 else if (qname.equals("secret"))
463 byte[] salt = null;
466 salt = Base64.decode(attr.getValue("salt"));
468 catch (IOException ioe)
470 throw new SAXException(ioe);
472 pbekdf.init(Collections.singletonMap(IPBE.SALT, salt));
473 state = SECRET;
475 else
476 throw new SAXException("bad element: " + qname);
477 break;
479 case PEER:
480 if (qname.equals("certificates"))
482 certType = attr.getValue("type");
483 state = PEER_CERTS;
485 else
486 throw new SAXException("bad element: " + qname);
487 break;
489 default:
490 throw new SAXException("bad element: " + qname);
494 public void endElement(String uri, String name, String qname)
495 throws SAXException
497 qname = qname.toLowerCase();
498 switch (state)
500 case SESSIONS:
501 if (qname.equals("sessions"))
502 state = START;
503 else
504 throw new SAXException("expecting sessions");
505 break;
507 case SESSION:
508 if (qname.equals("session"))
510 current.valid = true;
511 context.addSession(current.sessionId, current);
512 state = SESSIONS;
514 else
515 throw new SAXException("expecting session");
516 break;
518 case PEER:
519 if (qname.equals("peer"))
520 state = SESSION;
521 else
522 throw new SAXException("unexpected element: " + qname);
523 break;
525 case PEER_CERTS:
526 if (qname.equals("certificates"))
530 CertificateFactory fact = CertificateFactory.getInstance(certType);
531 current.peerCerts = (Certificate[])
532 fact.generateCertificates(new ByteArrayInputStream(
533 buf.toString().getBytes())).toArray(new Certificate[0]);
535 catch (Exception ex)
537 throw new SAXException(ex);
539 current.peerVerified = true;
540 state = PEER;
542 else
543 throw new SAXException("unexpected element: " + qname);
544 break;
546 case CERTS:
547 if (qname.equals("certificates"))
551 CertificateFactory fact = CertificateFactory.getInstance(certType);
552 current.localCerts = (Certificate[])
553 fact.generateCertificates(new ByteArrayInputStream(
554 buf.toString().getBytes())).toArray(new Certificate[0]);
556 catch (Exception ex)
558 throw new SAXException(ex);
560 state = SESSION;
562 else
563 throw new SAXException("unexpected element: " + qname);
564 break;
566 case SECRET:
567 if (qname.equals("secret"))
569 byte[] encrypted = null;
572 encrypted = Base64.decode(buf.toString());
573 if (encrypted.length != 68)
574 throw new IOException("encrypted secret not 68 bytes long");
575 pbekdf.nextBytes(key, 0, key.length);
576 pbekdf.nextBytes(iv, 0, iv.length);
577 pbekdf.nextBytes(mackey, 0, mackey.length);
578 cipher.reset();
579 cipher.init(cipherAttr);
580 mac.init(macAttr);
582 catch (Exception ex)
584 throw new SAXException(ex);
586 mac.update(encrypted, 0, 48);
587 byte[] macValue = mac.digest();
588 for (int i = 0; i < macValue.length; i++)
590 if (macValue[i] != encrypted[48+i])
591 throw new SAXException("MAC mismatch");
593 current.masterSecret = new byte[48];
594 for (int i = 0; i < current.masterSecret.length; i += 16)
596 cipher.update(encrypted, i, current.masterSecret, i);
598 state = SESSION;
600 else
601 throw new SAXException("unexpected element: " + qname);
602 break;
604 default:
605 throw new SAXException("unexpected element: " + qname);
607 buf.setLength(0);
610 public void characters(char[] ch, int off, int len) throws SAXException
612 if (state != CERTS && state != PEER_CERTS && state != SECRET)
614 throw new SAXException("illegal character data");
616 buf.append(ch, off, len);