From: adam Date: Tue, 13 Jul 2004 07:07:55 +0000 (+0000) Subject: added SSL X-Git-Url: http://git.megacz.com/?p=org.ibex.net.git;a=commitdiff_plain;h=2d5539a40aebddc8b35c830dd4077a2b16bd5d17 added SSL darcs-hash:20040713070755-5007d-2d080b9e0d1702968919b344bc432561648e8769.gz --- diff --git a/src/org/ibex/net/SSL.java b/src/org/ibex/net/SSL.java new file mode 100644 index 0000000..04519cd --- /dev/null +++ b/src/org/ibex/net/SSL.java @@ -0,0 +1,1010 @@ +/* + * org.ibex.net.SSL - By Brian Alliet + * Copyright (C) 2004 Brian Alliet + * + * Based on TinySSL by Adam Megacz + * Copyright (C) 2003 Adam Megacz all rights reserved. + * + * You may modify, copy, and redistribute this code under the terms of + * the GNU Lesser General Public License version 2.1, with the exception + * of the portion of clause 6a after the semicolon (aka the "obnoxious + * relink clause") + */ + +package org.ibex.net; + +import org.ibex.crypto.*; +import java.security.SecureRandom; + +import java.net.Socket; +import java.net.SocketException; + +import java.io.*; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Random; +import java.util.Vector; + +// FEATURE: Server socket + +public class SSL extends Socket { + private String hostname; + + private int negotiated; + + private boolean tls = true; + private boolean sha; + + private final DataInputStream rawIS; + private final DataOutputStream rawOS; + + private final InputStream sslIS; + private final OutputStream sslOS; + + private byte[] sessionID; + + private Digest clientWriteMACDigest; + private Digest serverWriteMACDigest; + private byte[] masterSecret; + + private RC4 writeRC4; + private RC4 readRC4; + + private long serverSequenceNumber; + private long clientSequenceNumber; + + private int warnings; + private boolean closed; + + // These are only used during negotiation + private byte[] serverRandom; + private byte[] clientRandom; + private byte[] preMasterSecret; + + // Buffers + private byte[] mac; + + private byte[] pending = new byte[16384]; + private int pendingStart; + private int pendingLength; + + private byte[] sendRecordBuf = new byte[16384]; + + private int handshakeDataStart; + private int handshakeDataLength; + private byte[] readRecordBuf = new byte[16384+20]; // 20 == sizeof(sha1 hash) + private byte[] readRecordScratch = new byte[16384+20]; + + private ByteArrayOutputStream handshakesBuffer; + + // End Buffers + + // Static variables + private final static byte[] pad1 = new byte[48]; + private final static byte[] pad2 = new byte[48]; + private final static byte[] pad1_sha = new byte[40]; + private final static byte[] pad2_sha = new byte[40]; + + static { + for(int i=0; i 256) throw new IllegalArgumentException("sessionID"); + // 2 = version, 32 = randomvalue, 1 = sessionID size, 2 = cipher list size, 4 = the two ciphers, + // 2 = compression length/no compression + int p = 0; + byte[] buf = new byte[2+32+1+(sessionID == null ? 0 : sessionID.length)+2+2+4]; + buf[p++] = 0x03; // major version + buf[p++] = tls ? (byte)0x01 : (byte)0x00; + + clientRandom = new byte[32]; + int now = (int)(System.currentTimeMillis() / 1000L); + new Random().nextBytes(clientRandom); + clientRandom[0] = (byte)(now>>>24); + clientRandom[1] = (byte)(now>>>16); + clientRandom[2] = (byte)(now>>>8); + clientRandom[3] = (byte)(now>>>0); + System.arraycopy(clientRandom,0,buf,p,32); + p += 32; + + buf[p++] = sessionID != null ? (byte)sessionID.length : 0; + if(sessionID != null && sessionID.length != 0) System.arraycopy(sessionID,0,buf,p,sessionID.length); + p += sessionID != null ? sessionID.length : 0; + buf[p++] = 0x00; // 4 bytes of ciphers + buf[p++] = 0x04; + buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_SHA + buf[p++] = 0x05; + buf[p++] = 0x00; // SSL_RSA_WITH_RC4_128_MD5 + buf[p++] = 0x04; + + buf[p++] = 0x01; + buf[p++] = 0x00; + + sendHandshake((byte)1,buf); + flush(); + } + + private void receiveServerHello() throws IOException { + // ServerHello + byte[] buf = readHandshake(); + if(buf[0] != 2) throw new Exn("expected a ServerHello message"); + + if(buf.length < 6 + 32 + 1) throw new Exn("ServerHello too small"); + if(buf.length < 6 + 32 + 1 + buf[6+32] + 3) throw new Exn("ServerHello too small " + buf.length+" "+buf[6+32]); + + if(buf[4] != 0x03 || !(buf[5]==0x00 || buf[5]==0x01)) throw new Exn("server wants to use version " + buf[4] + "." + buf[5]); + tls = buf[5] == 0x01; + int p = 6; + serverRandom = new byte[32]; + System.arraycopy(buf,p,serverRandom,0,32); + p += 32; + sessionID = new byte[buf[p++]&0xff]; + if(sessionID.length != 0) System.arraycopy(buf,p,sessionID,0,sessionID.length); + p += sessionID.length; + int cipher = ((buf[p]&0xff)<<8) | (buf[p+1]&0xff); + p += 2; + switch(cipher) { + case 0x0004: sha = false; debug("Using SSL_RSA_WITH_RC4_128_MD5"); break; + case 0x0005: sha = true; debug("Using SSL_RSA_WITH_RC4_128_SHA"); break; + default: throw new Exn("Unsupported cipher " + cipher); + } + mac = new byte[sha ? 20 : 16]; + if(buf[p++] != 0x0) throw new Exn("unsupported compression " + buf[p-1]); + } + + private X509.Certificate[] receiveServerCertificates() throws IOException { + byte[] buf = readHandshake(); + if(buf[0] != 11) throw new Exn("expected a Certificate message"); + if((((buf[4]&0xff)<<16)|((buf[5]&0xff)<<8)|((buf[6]&0xff)<<0)) != buf.length-7) throw new Exn("size mismatch in Certificate message"); + int p = 7; + int count = 0; + + for(int i=p;i buf.length) throw new Exn("Certificate message cut short"); + certs[count++] = new X509.Certificate(new ByteArrayInputStream(buf,p,len)); + p += len; + } + return certs; + } + + private void sendClientKeyExchange(X509.Certificate serverCert) throws IOException { + byte[] encryptedPreMasterSecret; + RSA.PublicKey pks = serverCert.getRSAPublicKey(); + PKCS1 pkcs1 = new PKCS1(new RSA(pks.modulus,pks.exponent,false),random); + encryptedPreMasterSecret = pkcs1.encode(preMasterSecret); + byte[] buf; + if(tls) { + buf = new byte[encryptedPreMasterSecret.length+2]; + buf[0] = (byte) (encryptedPreMasterSecret.length>>>8); + buf[1] = (byte) (encryptedPreMasterSecret.length>>>0); + System.arraycopy(encryptedPreMasterSecret,0,buf,2,encryptedPreMasterSecret.length); + } else { + // ugh... netscape didn't send the length bytes and now every SSLv3 implementation + // must implement this bug + buf = encryptedPreMasterSecret; + } + sendHandshake((byte)16,buf); + } + + private void sendChangeCipherSpec() throws IOException { + sendRecord((byte)20,new byte[] { 0x01 }); + } + + private void computeMasterSecret() { + preMasterSecret = new byte[48]; + preMasterSecret[0] = 0x03; // version_high + preMasterSecret[1] = tls ? (byte) 0x01 : (byte) 0x00; // version_low + randomBytes(preMasterSecret,2,46); + + if(tls) { + masterSecret = tlsPRF(48,preMasterSecret,getBytes("master secret"),concat(clientRandom,serverRandom)); + } else { + masterSecret = concat(new byte[][] { + md5(new byte[][] { preMasterSecret, + sha1(new byte[][] { new byte[] { 0x41 }, preMasterSecret, clientRandom, serverRandom })}), + md5(new byte[][] { preMasterSecret, + sha1(new byte[][] { new byte[] { 0x42, 0x42 }, preMasterSecret, clientRandom, serverRandom })}), + md5(new byte[][] { preMasterSecret, + sha1(new byte[][] { new byte[] { 0x43, 0x43, 0x43 }, preMasterSecret, clientRandom, serverRandom })}) + } ); + } + } + + public void initCrypto() { + byte[] keyMaterial; + + if(tls) { + keyMaterial = tlsPRF( + (mac.length + 16 + 0)*2, // MAC len + key len + iv len + masterSecret, + getBytes("key expansion"), + concat(serverRandom,clientRandom) + ); + } else { + keyMaterial = new byte[] { }; + for(int i=0; keyMaterial.length < 72; i++) { + byte[] crap = new byte[i + 1]; + for(int j=0; j (1<<24)) throw new IllegalArgumentException("payload.length"); + byte[] buf = new byte[4+payload.length]; + buf[0] = type; + buf[1] = (byte)(payload.length>>>16); + buf[2] = (byte)(payload.length>>>8); + buf[3] = (byte)(payload.length>>>0); + System.arraycopy(payload,0,buf,4,payload.length); + handshakesBuffer.write(buf); + sendRecord((byte)22,buf); + } + + private void sendRecord(byte proto, byte[] buf) throws IOException { sendRecord(proto,buf,0,buf.length); } + private void sendRecord(byte proto, byte[] payload, int off, int totalLen) throws IOException { + int macLength = (negotiated & 1) != 0 ? mac.length : 0; + while(totalLen > 0) { + int len = min(totalLen,16384-macLength); + rawOS.writeByte(proto); + rawOS.writeShort(tls ? 0x0301 : 0x0300); + if((negotiated & 1) != 0) { + computeMAC(proto,payload,off,len,clientWriteMACDigest,clientSequenceNumber); + // FEATURE: Encode in place + writeRC4.process(payload,off,sendRecordBuf,0,len); + writeRC4.process(mac,0,sendRecordBuf,len,macLength); + rawOS.writeShort(len + macLength); + rawOS.write(sendRecordBuf,0, len +macLength); + clientSequenceNumber++; + } else { + rawOS.writeShort(len); + rawOS.write(payload,off,len); + } + totalLen -= len; + off += len; + } + } + + private byte[] readHandshake() throws IOException { + if(handshakeDataLength == 0) { + handshakeDataStart = 0; + handshakeDataLength = readRecord((byte)22); + if(handshakeDataLength == -1) throw new Exn("got eof when expecting a handshake packet"); + } + byte[] buf = readRecordBuf; + int len = ((buf[handshakeDataStart+1]&0xff)<<16)|((buf[handshakeDataStart+2]&0xff)<<8)|((buf[handshakeDataStart+3]&0xff)<<0); + // Handshake messages can theoretically span multiple records, but in practice this does not occur + if(len > handshakeDataLength) { + sendAlert(true,10); // 10 == unexpected message + throw new Exn("handshake message size too large " + len + " vs " + (handshakeDataLength-handshakeDataStart)); + } + byte[] ret = new byte[4+len]; + System.arraycopy(buf,handshakeDataStart,ret,0,ret.length); + handshakeDataLength -= ret.length; + handshakeDataStart += ret.length; + handshakesBuffer.write(ret); + return ret; + } + + private int readRecord(byte reqProto) throws IOException { + int macLength = (negotiated & 2) != 0 ? mac.length : 0; + for(;;) { + byte proto; + int version, len; + + try { + proto = rawIS.readByte(); + } catch(EOFException e) { + // this may or may not be an error. it is up to the application protocol + closed = true; + super.close(); + throw new PrematureCloseExn(); + } + try { + version = rawIS.readShort(); + if(version != 0x0300 && version != 0x0301) throw new Exn("invalid version "); + len = rawIS.readShort(); + if(len <= 0 || len > 16384+((negotiated&2)!=0 ? macLength : 0)) throw new Exn("invalid length " + len); + rawIS.readFully((negotiated&2)!=0 ? readRecordScratch : readRecordBuf,0,len); + } catch(EOFException e) { + // an EOF here is always an error (we don't pass the EOF back on to the app + // because it isn't a "legitimate" eof) + throw new Exn("Hit EOF too early"); + } + + if((negotiated & 2) != 0) { + if(len < macLength) throw new Exn("packet size < macLength"); + // FEATURE: Decode in place + readRC4.process(readRecordScratch,0,readRecordBuf,0,len); + computeMAC(proto,readRecordBuf,0,len-macLength,serverWriteMACDigest,serverSequenceNumber); + for(int i=0;i len - 4) throw new Exn("Multiple sequential handshake messages received after negotiation"); + if(type == 0) { // HellloRequest + if(tls) sendAlert(false,100); // politely refuse, 100 == NoRegnegotiation + } else { + throw new Exn("Unexpected Handshake type: " + type); + } + } + default: throw new Exn("Unexpected protocol: " + proto); + } + } + } + + private static void longToBytes(long l, byte[] buf, int off) { + for(int i=0;i<8;i++) buf[off+i] = (byte)(l>>>(8*(7-i))); + } + private void computeMAC(byte proto, byte[] payload, int off, int len, Digest digest, long sequenceNumber) { + if(tls) { + longToBytes(sequenceNumber,mac,0); + mac[8] = proto; + mac[9] = 0x03; // version + mac[10] = 0x01; + mac[11] = (byte)(len>>>8); + mac[12] = (byte)(len>>>0); + + digest.update(mac,0,13); + digest.update(payload,off,len); + digest.doFinal(mac,0); + } else { + longToBytes(sequenceNumber, mac, 0); + mac[8] = proto; + mac[9] = (byte)(len>>>8); + mac[10] = (byte)(len>>>0); + + digest.update(mac, 0, 11); + digest.update(payload, off, len); + digest.doFinal(mac, 0); + } + } + + private void sendCloseNotify() throws IOException { sendRecord((byte)21, new byte[] { 0x01, 0x00 }); } + private void sendAlert(boolean fatal, int message) throws IOException { + byte[] buf = new byte[] { fatal ? (byte)2 :(byte)1, (byte)message }; + sendRecord((byte)21,buf); + flush(); + } + + // + // Hash functions + // + + // Shared digest objects + private MD5 masterMD5 = new MD5(); + private SHA1 masterSHA1 = new SHA1(); + + private byte[] md5(byte[] in) { return md5( new byte[][] { in }); } + private byte[] md5(byte[][] inputs) { + masterMD5.reset(); + for(int i=0; i 112) throw new IllegalArgumentException("size > 112"); + seed = concat(label,seed); + + int half_length = (secret.length + 1) / 2; + byte[] s1 = new byte[half_length]; + System.arraycopy(secret,0,s1,0,half_length); + byte[] s2 = new byte[half_length]; + System.arraycopy(secret,secret.length - half_length, s2, 0, half_length); + + Digest hmac_md5 = new HMAC(new MD5(),s1); + Digest hmac_sha = new HMAC(new SHA1(),s2); + + byte[] md5out = new byte[112]; + byte[] shaout = new byte[120]; + byte[] digest = new byte[20]; + int n; + + n = 0; + hmac_md5.update(seed,0,seed.length); + hmac_md5.doFinal(digest,0); + + // digest == md5_a_1 + while(n < size) { + hmac_md5.update(digest,0,16); + hmac_md5.update(seed,0,seed.length); + hmac_md5.doFinal(md5out,n); + hmac_md5.update(digest,0,16); + hmac_md5.doFinal(digest,0); + n += 16; + } + + n = 0; + hmac_sha.update(seed,0,seed.length); + hmac_sha.doFinal(digest,0); + + while(n < size) { + hmac_sha.update(digest,0,20); + hmac_sha.update(seed,0,seed.length); + hmac_sha.doFinal(shaout,n); + hmac_sha.update(digest,0,20); + hmac_sha.doFinal(digest,0); + n += 20; + } + + byte[] ret = new byte[size]; + for(int i=0;i len) System.arraycopy(readRecordBuf,len,pending,0,readLen-len); + pendingStart = 0; + pendingLength = readLen - len; + return len; + } else { + len = min(len,pendingLength); + System.arraycopy(pending,pendingStart,buf,off,len); + pendingLength -= len; + pendingStart += len; + return len; + } + } + + private void write(byte[] buf, int off, int len) throws IOException { + if(closed) throw new SocketException("Socket closed"); + sendRecord((byte)23,buf,off,len); + flush(); + } + + private class SSLInputStream extends InputStream { + public int available() throws IOException { + synchronized(SSL.this) { + return negotiated != 0 ? pendingLength : rawIS.available(); + } + } + public int read() throws IOException { + synchronized(SSL.this) { + if(negotiated==0) return rawIS.read(); + if(pendingLength > 0) { + pendingLength--; + return pending[pendingStart++]; + } else { + byte[] buf = new byte[1]; + int n = read(buf); + return n == -1 ? -1 : buf[0]&0xff; + } + } + } + public int read(byte[] buf, int off, int len) throws IOException { + synchronized(SSL.this) { + return negotiated!=0 ? SSL.this.read(buf,off,len) : rawIS.read(buf,off,len); + } + } + public long skip(long n) throws IOException { + synchronized(SSL.this) { + if(negotiated==0) return rawIS.skip(n); + if(pendingLength > 0) { + n = min((int)n,pendingLength); + pendingLength -= n; + pendingStart += n; + return n; + } + return super.skip(n); + } + } + } + + private class SSLOutputStream extends OutputStream { + public void flush() throws IOException { rawOS.flush(); } + public void write(int b) throws IOException { write(new byte[] { (byte)b }); } + public void write(byte[] buf, int off, int len) throws IOException { + synchronized(SSL.this) { + if(negotiated!=0) + SSL.this.write(buf,off,len); + else + rawOS.write(buf,off,len); + } + } + } + + public static class Exn extends IOException { public Exn(String s) { super(s); } } + public static class PrematureCloseExn extends Exn { + public PrematureCloseExn() { super("Connection was closed by the remote WITHOUT a close_noify"); } + } + + public static boolean debugOn = false; + private static void debug(Object o) { if(debugOn) System.err.println("[BriSSL-Debug] " + o.toString()); } + private static void log(Object o) { System.err.println("[BriSSL] " + o.toString()); } + + private static void verifyCerts(X509.Certificate[] certs) throws DER.Exception, Exn { + try { + verifyCerts_(certs); + } catch(RuntimeException e) { + e.printStackTrace(); + throw new Exn("Error while verifying certificates: " + e); + } + } + + private static void verifyCerts_(X509.Certificate[] certs) throws DER.Exception, Exn { + boolean ignoreLast = false; + for(int i=0;i which'll load the certs + Class.forName("org.ibex.net.ssl.RootCerts"); + log("Loaded root keys from org.ibex.net.ssl.RootCerts"); + } catch(ClassNotFoundException e) { + InputStream is = SSL.class.getClassLoader().getResourceAsStream("org.ibex/net/ssl/rootcerts.dat"); + if(is != null) { + try { + addCompactCAKeys(is); + log("Loaded root certs from rootcerts.dat"); + } catch(IOException e2) { + log("Error loading certs from rootcerts.dat: " + e2.getMessage()); + } + } + } + } + + public static int addCompactCAKeys(InputStream is) throws IOException { + synchronized(caKeys) { + try { + Vector seq = (Vector) new DER.InputStream(is).readObject(); + for(Enumeration e = seq.elements(); e.hasMoreElements();) { + Vector seq2 = (Vector) e.nextElement(); + X509.Name subject = new X509.Name(seq2.elementAt(0)); + RSA.PublicKey pks = new RSA.PublicKey(seq2.elementAt(1)); + addCAKey(subject,pks); + } + return seq.size(); + } catch(RuntimeException e) { + e.printStackTrace(); + throw new IOException("error while reading stream: " + e); + } + } + } + + public static synchronized void setVerifyCallback(VerifyCallback cb) { verifyCallback = cb; } + + // State Info + public static class State { + byte[] sessionID; + byte[] masterSecret; + State(byte[] sessionID, byte[] masterSecret) { + this.sessionID = sessionID; + this.masterSecret = masterSecret; + } + } + + public interface VerifyCallback { + public boolean checkCerts(X509.Certificate[] certs, String hostname, Exn exn); + } + + // Helper methods + private static final int min(int a, int b) { return a < b ? a : b; } +} diff --git a/src/org/ibex/net/ssl/GenCompactCAList.java b/src/org/ibex/net/ssl/GenCompactCAList.java new file mode 100644 index 0000000..e15a194 --- /dev/null +++ b/src/org/ibex/net/ssl/GenCompactCAList.java @@ -0,0 +1,98 @@ +package org.ibex.net.ssl; + +import java.io.*; +//import org.bouncycastle.asn1.*; +//import org.bouncycastle.asn1.x509.*; + +public class GenCompactCAList { + /* + public static void main(String[] args) throws Exception { + if(args.length < 2) throw new Exception("Usage: GenCAList format file(s)"); + String format = args[0]; + DER.EncodableVector vec = new DEREncodableVector(); + for(int i=1;i>>(7*(7-j)))&0x7f); + if(c=='\n') sb.append("\\n"); + else if(c=='\r') sb.append("\\r"); + else if(c=='\\') sb.append("\\\\"); + else if(c=='"') sb.append("\\\""); + else if(c >= 32 && c <= 126) sb.append(c); + else sb.append("\\" + toOctal3(c)); + } + } + System.out.println("package org.ibex.net.ssl;"); + System.out.println("public final class RootCerts {"); + System.out.println(" private final static String DATA = \"" + sb.toString() + "\";"); + System.out.print( + " static {\n" + + " try {\n" + + " org.ibex.net.SSL.addCompactCAKeys(new java.io.ByteArrayInputStream(unpack(DATA)));\n" + + " } catch(Exception e) {\n" + + " System.err.println(\"Error loading root CA keys: \" + e.getMessage());\n" + + " }\n" + + " }\n"); + System.out.println(" public static void load() { }"); // force clinit + System.out.print( + " private static byte[] unpack(String s) {\n" + + " int len = s.length();\n" + + " if(len % 8 != 0) throw new IllegalArgumentException(\"not a multiple of 8\");\n" + + " byte[] ret = new byte[(len / 8) * 7];\n" + + " for(int i=0; i=0; j--) {\n" + + " ret[base + j] = (byte)(l & 0xff);\n" + + " l >>>= 8;\n" + + " }\n" + + " }\n" + + " return ret;\n" + + " }"); + System.out.println("}"); + } else { + throw new Error("unknown format"); + } + } + + private final static String toOctal3(int n) { + char[] buf = new char[3]; + for(int i=2;i>=0;i--) { + buf[i] = (char) ('0' + (n & 7)); + n >>= 3; + } + return new String(buf); + } + */ +} diff --git a/src/org/ibex/net/ssl/RootCerts.java b/src/org/ibex/net/ssl/RootCerts.java new file mode 100644 index 0000000..18438a0 --- /dev/null +++ b/src/org/ibex/net/ssl/RootCerts.java @@ -0,0 +1,29 @@ +package org.ibex.net.ssl; +public final class RootCerts { + private final static String DATA = "\030 Oi\031B\004\001M\014\020\030ID\0260\004A@5(\020\014\023\001\025*3\010,`\011\003\000jP &\002\"\020f\021\031@\"\006\001U @8L\024W0\\m\006K9Nt7[F\0219@*\006\001U @PL\034A!\020%d*\r\036M\026\010\011\024r\014\\1\014L\002p0\r*\004\001Db\004\n\011\002.\"Piti\001$o7]\004\004\032\004b$\030\010@`I*\014HC=aP\010$\002\026\nX,Fk%\\@2\032,w\033%Nt9\035.7!9Fo6L\020 \010(\005\002\000@ \013\016L#`4\nHEq?\037L\014\027H{ajbF\036G^\013I0h_F\037\011@A&:@*zgGl\017=5x=q(\030:BLZ\016T[\032}4\"K{\003\031^\004\nh~yVzLF(,r\036\011\002 hJ\031\035:rYad\025\ngPV/\000s*_}Z}VBYv9\026\034G\013\007:')i^nsg-b\003TN\011!32\022*e6F1\033g\004UF;3K_K\026[\010>\005f\011W\014)&\nZ\002S\017\"e,m\037\014ogh\000B-Jt\024\026:2T>%%'o~x o\027W\011#i;H/\037y1P\011c\"gT\0241!\001\001\001\000`\020\000\004a\002\000_f\006yD\0260\004A@5(\020\014\023\001\024hS\010P`\022\003\000jP (&\013 Y\014E#Ijs:\010\010\024\021DL0\022\001@5(\020\026\023\016P,F\"Qdu9]\004\004+ahe9\033L\026a\001(T(\010\011f+Qno9\032f\022\021@@\006\001U @\030L2A2\031\nG\023Uft\020\021/\007#\025dn0[\004\004\032\004@R7[nC\004\010\002\n\001 @\020\010\002ow\r\014|o\020\000\010-\034x\011e_4?<6\003y[/hG6g7S1\011N/$\024S\017Y\034~zJ^$}P\030kG\017gy-U\002k-\027\024e\013(\005A\022zmKR_mc\025?\033N\005Qqz\"AV\\\nl?A\036?t\014Sg&+\021\020*:8ex=q\035M\020\007@u1C*/\005\021yt\036Bj(7\032yR:^\005YiN\031$qc3(\030\001,8N:8\003a@*G&Pi\023\006\022\nZ2`O\021jg$|\0231_g'|\026K>wW!5m4'\023\017-w\033(\025{I4q\004\rv7K\026HYP%\006\1778\000^\ra$N-V@prmE\025\004.\021:*>mvM6\023],fDhK>$W]!@\004_S\006ri5\026\0041\r^N3GUNUOW\032)kQVpQ-K6iPMq-Jp{}{}\025jO*4-\011` \030\004\000\001\030 @\027)AJ1\005L\001\0200\r*\004\003\004`%\032\024b\024\030\004@`\032T\010\n\011Bh\026#\021(r:\\nB\002\005\0041\016L\00300\r*\004\005DbD\013\021HT9\035.7!\001(T(\010\011f+Qno9\032f\022\011@>\006\001U @\030L0A2\031\nG\023Uft\020\020mF\013Mf \030H\0104\011\001$o7]\006\010\020\004\024\002A\000 \020\004Z-T\020R,\016\023/PA\003C\033l'\0038\023\021s8\023.\037wV'\004!&:{j\006-ZtPAm6)\013\016\\x=Hh\006bo\000Z(\ry\002eJ\"\nw\006\021\001^uSm\037xZV`E9n\031\032:\025\027\004\03704<`\004o)vF\035S\1771N+GJ{^lVbm5:\027%R:\036CF7IH~pA$S\026\0136\0320\004#n\023}\031\020W\014~\004]b-L\\R]\021yE;\021y`\001D.=1g_RK_5\026rb\0252H\037#+\031S^oK\036\026upa[\026\011\002-_,j54$\0310\005B.!\036]\004\022\033\024h+]^\013\032\036[Bv\034fTDL:\030tW[TIU\0173\033i\016\013I]\0101L:HpKw/DM\031\\1aWC\005xcG4*h\016.,|3d\\7Zp$\030l,5lI~cx\0119\025\0318??&@@0\010\000\0020A\000.C\003\020b\013\030\002 `\032T\010\006\011@J4)D(0\011\001@5(\020\024\023\005P,F\"Qdu9]\004\004\n\010b\035\030\006``\032T\010\013\011E\010\026#\021(r:\\nB\002Q(P\020\023LW#]^r5L$\003\000x\014\003*A\0001\030]\002d2\025\016'+Mh (\035,&c%F !P$\005\023=^t\030 @\020P\n\004\001\000@\035\021QB\037\003D\005\030\022\006`y\033GFo`\033RwZ4thj/b\035B\006\0042\005\r\024^f*\020\n^Vl>J+;Sr\033;h9eh7\026\177I}y.adf\014x9xq;\024h\017`CE\0223\002\020a\1776&9\036Xp\014\\\016\027|uc\002aAz\016=\007L!v\031\027@~_,'>M\010\036\023}GtZ\022g7\0346'\021{\003*QI\034`; \001ScyE\011nvG8\011g]fD2Bax\010|_cCgn\007\034\177J\031\024\\\026[\004\027fH0\006[\010Kzllj{#\177n\037>(&jtwVw\020\000F\036O\007k\0253y;+QH\010Y\027.8dR?\010lo\011\004\020@R-D=wFB\007vjR\022\r\001@9O@>,\017sgI\010y7 \016\\Q\013k2e\r\024\005k\001UZ.\027'h\020#(uL Y4Q\034X\020cS}\003\003z\002\001@ \000\011B\004\001;L\014s\010,`\011\003\000jP \030&\002)Q&\021!@$\006\001U @PL\026A2\031\nG\023Uft\020\020(#\010t`\033\003\000jP ,&\024 Y\014E#Ijs:\010\nE\"@@N2]\016v{IV1\021L\004\0200\r*\004\001Dc$\013\021HT9\035.7!\001\"u0[\r\0263%Jd\020\020h\022\002I^o:\014\020 \010(\005\002\000@ \016 z5~n\002+(=\022>G_\004+z|\020i[1\017\017\027E_R0Z{\001Aa+\010\026\177x\035G\017.\025u\010[]Tx6#R\011\006\005THT\003BR\010B<8EI\177\021\004S+L98/\0043n\026UMN\001,|+T7N`xR}Ad\023kF\177\rs\032\017\005\\Q}\025:Sk\035Br\004U?>l=Rc8\025(\0375GD3 7\\\004WqyRJ?Cd-G>Z\010\010fE\031O\006TD\"fB%qz:m)?\034z\001uP/x\022n\007`Lq-^\005$_\016[\035+Q0\006\0351>?`\177E\013\037d\022'YC_\011E OW\037hK\"\037@`t:o\011;\037>M\001mR\001B,!oC\021`UF_\000S8e\033\002-j]\020\017G\032t8 \031Ll[:IMT\027D$k![i7\014St4\016{\"6v*\000\177L\013mKK\0271i\001\000`\020\000\004a\002\000GF\010\014$LJ\013J~E%\033(\032lDv?\021\n\037J\rN\177E\011r r%ya\010x\034r\017>]\0322\027\001'3**8$)\031.]-MJ<5\031\036AP\"n\023/Z\000)d*#zD_\004\024\000_\033\025\014\034x/r7-\031s^H\177Rd\037\\\022V\r\037 0\0017DbE\021L\003\002:&\017\\[\03393u\025vcgym\033R\021\007i\0315u\037D\ru$r/zb\034HSg!\017s\002<\022\002\035SU/rTw\000/]\016]\025a^\032=\023\014;\032mZ jI\003Wz$N^\r\010\024P+!wi8-v3BD\027\022@c\\3u\022\002\035:n\"G;\010ZNv=;gT\023CIlZ#]N/.@M!daU,\004\023WDz,\001UA\035\026\0079CZqjC=/L&Q,Hg\013O\024\021D;WN;\023oP\010FN4\002k\020I\026\003v~|A~h@\001\007\000Xl\036;j=4\013\036\n\035a\032-\020\011K6xJ`]6h\010\006\001\000\000&\010\017<`a\030Bf\000H\030\006U\002\001B0\022%\n1\011\014\002\0000\r*\004\005\004a\024\023\005Xt4[-w\023\024b\023\030\004 `\032T\010\013\011BH7K\011Jr*\034NW\033Pb)\030\011``\032T\010\003\011H\010&\0131hi6[n&)\001\006y1\031.%#Ijs:\010\011V{\011Rl2H\n&{=h0@b (\014\004\001#6l'\00553y5+\010;l\004A:&#`Xgl\"\017^Kw4R]O-\n\001jZ\0274\"\030@@0\010\000\0020A\000-#\002hb\013\030\002 `\032T\010\006\011@I\024)D$0\010\001@5(\020\024\023\004PL\026cQRm7\\LS\010L`\021\003\000jP ,&\n!^,&+I(r:\\nC\011\010` \003\000jP \014&\031!\030-G#%Zo9\031$\004\033eDe9\025\016'+Mh )\033mw!B\004\001\005\000P \010\004\001#\002.d*\\`zWt\011N)UUsT\024x\\\036DV\0011X8k8q,S\03227t\025o6`\011\002[;2A;\034bQ\030_k$#4\022uN\0352\014w\000A\025@*'\\!oS\016#x{Uf\02155\002\035KOEPD!\022\002=\026\003\005$W\027L\017uZL\005~-\033P:d\005T,F\023\006\037VJcp$\001Gjvd\006=O^#_3B\005\011zC+qW2\025\023\\*55.Ayu^\032xN\003\027-G6{ZO#$o\011UAF\007\017>J\037\")o\03038\016S\\CoD@D\\#\003m T\004\032\007s\013i+KGC\006W>\000e\013 `\rCE\031>rTJ\031Iq6\n\007pO(=\006\034-MH`.2V2W(yMa|%1m\021O \025cy{77869\025\001\000`\020\000\004a\002\000RF\010\rlb\013\030\002 `\032T\010\006\011@H$)D\"0\007A@5(\020\016\023\004\020N'+Mfe6\034f\021\031@\"\006\001U @PL\024B2[\n6K\035\\ '\025F\023A@l\006\001U @XL^B2[\n6K\035\\ 'XM&+\rh (\035,&c%fh4[Lr\002\rJr:\032,fK\rBt2H\010\027+QPo9\032.GIDJ0\021A@5(\020\006\023\016\020LVbMRg7\010\011v\023)Jc:\010\n\007+\011Xi9Z\r\026s\034@C L$3\001\004\014\011\025!I\0107\\\032\001\004@\"a#]Jb6X.7#\025d@1\031-G\033%Nn\027\030LS\004\006\022\002@` \014!8?6_{P$\006ws7\027\020:Yp\026bZh\011OF\022T\007\033~2l\035r/_~t&?h=f\021OH6ApIqm_\013evlY|JE~m\027E@\002}0--BH\013U\"D\004\003\000@\000\023\004\010\002B\030 63\010,`\011\003\000jP \030&\002!\021&\021\011@\036\006\001U @8L\020B9\035.7\033\025Xs\030Df\001\010\030\006U\002\002B0R\011Jl)Z,vq\001\034V\030M\006\003\020\030\006U\002\002b2Z\011Jl)Z,vq\001&e1].&)\001&e9\035LW\021\001\006e9\035\r\0263%Fa:\031$\004\013Uhh7\\M\027#db!\030\007``\032T\010\003\011F\010&+1&i3[D\005\033\025Fu9\031$\005\033\025dv2\\D\004\032\004b#\030\010 `I*\014HC=aP\010$\002\026\n\035lV\0235Bs:\031.$\003\011Jl9Z,vq9De\030 1\020\024\006\002\000k\000\"'DKp\004!\037y\0349\n\007|>\021n\003\001-\022\013\037\rH\011\001#4!9`j?\026\023\"\026 0\n?}:MF\022,dvm\026f=\034]kZ@\033mfto\0141\n8\014e8\002SY\037fD@E_i( :\032\026XuY@C\020$\014p\n\011qY\003\026{\031\031n\016\006e9R\005\000IBCPp|#FYSu\023A0G+\032vh\014\030J_5\\w2S>hF\001t\004El*Qy?_t$+t+}|\005.ik-\000=\005L\011W\0314!IJV\022\020\\\001\022g\021%_C\003P\000\r/\017qW=\024 \031\017\010\035\007\0309D@@0\010\000\0020@tF\0041D\0260\004A@5(\020\014\023\001\025*3\011\020`\"\003\000jP (&\033\"\032,vKQBl\020\024m\026;9Bt:\\LR\002Qdu9]\004\004\033<\\1\010L\001p0\r*\004\005Da\004\"M(C H\010S\011B\003\007\001 0\020\005\001Y\001TsfAq\023;~C\n\031Mt\014_y/5\010-\027!h`3\004s\0000rRQ\011\032-s\nVrI9\000\001\007vNJb\"\rdIL?3\003$#s\026lT';!NedJ:\0207`nk\007\177{C=|.l\034oM\017@Li^\037\034GPx>\000\0316\016%\010'8Z*TSy\022MvhT;p\011\030\000e\000U\016p\004\00412\003j\"hMhv\033-/+!5b\002\000@f\010\020\006t0@j&\020Y@\022\006\001U @0L\004u9L!S\000,\014\003*A\001\001\030\021*t0Z\006\0219@*\006\001U @8L\034S0[\016B\0021Bk2H\0106KQr1\022\014\004 0\r*\004\005\004c4#%Ni:\030-B\002MRg7\030.G+IJ *\034NW\033P@C7KF\021\011@\036\006\001U @XL\020D)U\0104\011\00101\030EF\001 \030\006U\002\000b0j\021&T\020\024Mv{Q\006A\020\026\006\023\011\004`\037\003\002%(2\"\rw\006@!\020\010X$c0P\014FK\035fi3]\016'+Mh.1[mS\004\010\002\n\001 @\020\010\003%F\023-\\z)w\003D45*\006zO\no$Da\033EZ|teg)?0(1422\177q\033TUa|PF\0363gI\017n[o,K4\np31Iw$DFunB\0034n\002GZ25\\+\010\031N}\026 wp,l\\l Y\024\017#\005\026\030\022Cr\025+ lja\002\017\031Ah\035]\036)JA\"hh\000e[\014\002}#l\024V\004&5\006J8#K\010,WBN\0265ET\013?-\004sr\023\004&)\017[\026\011\024VI\033\036k\\\011l,\037S\022zI\010}Q\033\036\005.\"KH^+yd?Bg2F\020CL\r\026Hi[\025fQca3FiKg'\033\037]\024e\001\0020+\010\014p\021l~Gg9\013Y2\002k4M*HCg\011XD IY8n^ \030\004\000\001\030 :#\002\030b\013\030\002 `\032T\010\006\011@JU\031DH0\021\001@5(\020\024\023\rQ\r\026;%ha6\010\n6K\035\\a:\035.&)\001(r:\\nB\002\r^.\030D&\000x\030\006U\002\002b0B\021&T!P$\004)Ha\001C@P\030\010\002\177\023GEr.yL&\030uD\017tpZ\177\177\003#e(-q=y\022)DHEX\0077`pkp\032o\"oW\037H.S\030G81`joY [a\034\"vNu \003ag\007\026hA(DFB~sz;BRG\n\017yms\027a(8}ir32\031d`^k8mk*3\025*IOi\024\000r-g\026`\005M\003b_K-2r+<_#OrThw\003cq\004\014_|N~By6v\005cs\021/\\\002.\000D\017{%N\004\023{\021.$JFI\027Jb4w\034^p\001%=\031BuW(\n\013'2\024\005\\(d20`\011[?\027\001FM\026\0342A\024&hy\007d\nB^lW\010FBvUVnAnO\033CYJHm\002%\026#\014E9}Qnh,2\014DeB;0\020\014\002\000\000L\020 \np`L\030Bf\000H\030\006U\002\001B0\023\rB1\011\014\002\0000\r*\004\005\004a\024)5\006e9\035\r\0263db\022\030\004\000`\032T\010\013\011B)\024!\001\006e7\035\014W\021D*0\011A@5(\020\006\023\006\021%T\033\025dt4YO\022\002\r\0020A\000! \024\010\002\001\000.S:3F\000U:\022%\025+X7G\033%&\002aH\022Qw\014\024F[\"\016Gl\n\001!;}_(M\010\026jX\007%.P\021(c(\010\005|m8\004\007\020(f\rI\\\027\000$A\023y4\016\0270hE\\9.V\017,1\rMi]Ef\"7#X\0224!e\034egJOf\"\003>lUc!)HEnl\r9~\031\025iE\022Lqa\n[ \005*&\016$ey\002dEb3em\036[z\034\0260ta>56Z'\033\177!37\002mR\001K&\036F_dB~Mi@\035\\|3~O\034~o'G@\034\r$_j3pR6&T*Y\033_\007\"\033nG\006\nx\007M\033\023\001LKg=0L-\0212U\006\020X>:\026\035\023e?wCUT@MQ\023e1b=K=\032#AAB\014\017M\013T\177d)\\kXNc\\@'B2y<\025G\rjh\025h4\rW[*\032\013nB,\004\003\000@\000\023\004\010\002\\\030\023\006\020Y@\022\006\001U @0L\004c0L\"#\000@\014\003*A\001!\030%\n-!Y.'#%Ly\030DF\001\000\030\006U\002\002b0J%\010 !Y-g#\025d1\nL\00200\r*\004\001DaD)5\006e9\035\r\0263d@R L\020 \010(\005\002\000@ \reBn%}{AKVbFr\"\003e*\037\r\020\004h}\006\007 \030\020\016/Mxs5^\005\032\022RuFZ&2\000\rE~/\006|.\027t{$\006PQW}ds\001~01W\006r}4OVl\n*\0002\001^U)J@\0332^>+Ts\016n\031[4 r=CEPg\014]sa\032dv9#8O\024R`K=\0249\003KL\022\036\034\0160m\0318i\025\020&h:r!0;oK \003vRb\013a/8\021\035\004\027wc\nc\014\014 nvV\030\016\007C-?F\021\007?OWxVso!\002A,yO\025\031}pr7W!%\034\034~UYu/\0179^+\034&;M3'&\024\023ra\017\033:UD;3yVJGi7G[*?D\035o0\002\nc\010Q?BDaj\031{\001\000`\020\000\004a\002\000Pf\010\rPb\024\030\004@`\032T\010\n\011BhVsQdu9]\005fs\025h1 \014\007`0\r*\004\005E\006w;]n.2[NG\023Uft\027\033LW!=\016C!P+t\032A& 4[L6{I`.\020\030O\022\003IJf\027\010\005\006c%Zi:\034d\006c%Bb\027\n&\022)@F\006\001U @XL8(1J$\003\021@`0\020\021-g#Ijs:\013Mf+P@L4[-\027#\025H1\031L\006\0200\r*\004\001De$+9hr:\\nBs9Jt\020\020mFK\025\\t\020\020lW\023QRf4Xl\027#%^n\020\020.W#!^r4]\017\023\004\006\022\002@` \011\033Ri6r1)=5\005P\17715=O:E/39\022S\017.B\023IVXyTiU\"jB5\025\\K!ro\\:b\035\\\036\\\\\022\024\"?\nPK\004WJ%9\177\016\001blr{\\2\025\0224D$\013`\014__j)i[_f\007[[GY~)\020(\036X`5\030kvqL5R`?\010\010\031\\Ui(\002 \001~tJ(3PA&g\010\005}0wv\0369C=\003Y.Go\016V\022;\035a7*SPY~\037L-j|F*n\0035\000tj,XV#F\000E\026h\034\020Iol9\004oC_F9@*'\031\036^{b` \010\014a\002\000T\006\010\016\014b\013\030\002 `\032T\010\006\011@JU\031D(0\011\001@5(\020\024\023\005Q-g#Ijs:\013Mf+Pb;\030\016 `\032T\010\013\011LNw;\\\\e7\035\016'+Mh.7\031.Bz\r S\020\032-f\033=dp\027\010\014'I\001de3\013D\002C1Rm4]\0162\0031Ra1\013E\023\011\024`#\003\000jP ,&\034\024\030e\022\001Dr9\034H\010VsQdu9]\005fs\025h &\032-VKQJd\030NF\003@\030\006U\002\000b3\n\025\\t9\035.7!9\\e:\010\n6+\rjr2H\n6+Ile9\010\0106+Ihi3\032,6\013QRo7\010\010\027+QPo9\032.GIB\003\007\001 0\020\0064Q\003\032\025\0038OL\037/\033d&\037}ky\007IO3fF\023\024\030}l\0225@^),\014pCa=L#&+\022x.Wz-Ej5\177_\004\014\005X\010\006\001\000\000&\010\016p`N\030Bf\000H\030\006U\002\001B0\022U&1\010\014\001`0\r*\004\005\004`t+Eji3\030/\003\0114`+\003\000jP ,&$\"\\.VK\031Bx\020\024lV\033Ude\020\020lW\023QRf4Xl\027#\024@A:]\r\006{IRt\0106d\"FD`\024\036\1776@FxN\010b\020pz\027f:>\005p.\000\037\0028ilq\"RP#\022Yq5Tkp-O\026g@/h \024;}]\000B=N\010\000(O~>~\000fu\007:-J|\021|\001a\031\nUr17D\037\0275-x&A\0149L&V&\nbD<;WJzns\r\n{bv+u\006KFP\007NQ\016=|)}p1w\020\020\014\002\000\000L\020\036AA41\005L\001\0200\r*\004\003\004`%*Lb\034\030\006@`\032T\010\n\011DhW\013URf0^\004\005\033\025Fu9\031$\004K9F.\030K&\002X\030\006U\002\000b2\"\025bu4YL\027A\001&e1].&)\001\016l7XL\026a\001JB:\\m\026s\025fs\020\020h\022iDa\001D@P\030\010\002ug\013d\000&-DhU\036\0228%\016W?'hMq}\017\036\002s TL\011\\vB\027\023\036\025\\=GP&J\014V]qN\032:\027v~)Si4sL(kNOn\024s\rW\034|\035~\000lfj>M1/\001w|f5&\035\nbG\001\003zN\"Dt\022\036\033\021%RR\010pmp8Y\006=dEej\013\034\0064g\035\037hrn\\\024f!\016\017Ccs\001\\vN\031p\\.#\006a\001\000`\020\000\004a\001pL\n3\010,`\011\003\000jP \030&\002*Tf\021a@4\006\001U @PL&E8]-\0263\005p )Y,7+IJ $[L2qDL0\022\001@5(\020\006\023\016Q.\027+%La<\010\n6+\rjr2H\014T\023Ufi7\031.7\031\001\006A\026L&\010\014$\005\001@@\031bxfx\027[]{i\035%>Z\006Eis h\031\030z\010[\025NS\010h#<\026C.\026\n\003\0201Ut-\024BQ\"P=12`l27xJ\006\023\023s}(g\025\"&gi\035AJPpQ\007&\022\034\r\rHh\001\\ZP6(.phC\025|TIE*k\n\017\034\023hoS7p.\032uCWg/EpX\0350\014E$',_\"\035sS:V_\025\016\014 \025@${\177#t`\030\010\006\001\000\000&\010\016p`N\030Bf\000H\030\006U\002\001B0\022U&1\013L\002P0\r*\004\005\004ad+Eji3\030/\002\002MJc:\\LS\011\030`$\003\000jP ,&\035\"\\.VK\031Bx\020\024lV\033Ude\020\031('+MRn2\\n2\002\r\002-\031\014\020\030H\n\003\001\0009\007\023LL\021;Q\007&#-\035]VGY\024W-{\034y\020\016iN-8\021FY\022U#\0075Q\"M\034iI:\027\027s~ #k\016R\027/o\0215Ug.g@yq\021oitZi0r\0230oj\n8g5\032Wz;\021Q\007\\l.Rr@4\034|.)\014\027D=\024t\030\035AX%\033\016,\000\026$-\006\002\017\006hAW8\027hHy\004Z2 H\020\027;q\004\035<+\024EL\016E@\016=\001SK%\025dFP{(Z|(@Eh\027\0335L\010n&\031\023h&\022\017i\034C2V`\024]P~_=*09SUr\005\034(NtRY8zI\031)Np*`CF$z\020S[pqCA\036\016,K7\010\020\001PgEz\033\\J3\036*:\010\rvm-\022\037PoVjUb97\016Y\027\025:\035\017ydu\031n \037cU\033HQh\010\032 A\r\011'\011`c\034Xw?@63\031\036Gl9\024\0307}\\\0273\"dRq\031E\002\037yy65\022=5MZOm;\021/lLt\027wB\003DwP\007#\003\nAo\007K\035S>aiM\010\010\006\001\000\000&\010\020\004\0060:L!3\000$\014\003*A\000a\030\011*S\030F\006\0010\030\006U\002\002B0z\035(E\020\020mw\023A^r0]\r\026{8b'\030\011 `\032T\010\013\011GHu\"\024@C\003Hz\1772LT:j\030Ok\026_gd,6\026aPk\r{n,E9 \026!-7NOvqp.C\021B$c|\"\037n$wU}yM\030C.\026~c5\023SH6<\027H\001,h]+-Ay0v\037dK\021D\035@\010Bu .:68fu\"C1$+H_*+U<@>\r\007b:NA{as!\004g.]p\020\014\002\000\000L\020\036AA41\005L\001\0200\r*\004\003\004`$R@b\037\030\007 `\032T\010\n\011EH7K\011Jr*\034NW\033P@J0\\\014\026q0@I7\030ec\011(`(\003\000jP \014&!!^,&+I(r:\\nB\002)\002P SD\005\033\025Fu9\031$\005\033\025dv2\\D\004\032\004a\001D@P\030\010\002X&Qh7\006{+\000,\011>;3\010\026%g~~ZH\rRqU\02552\017WA\006e~\004B\\W3790wF6\031]\037/\000\017\017y\021\\6\0309y3sd#b\032\011Q\037gOK\006&JCa?\004x(\007\020[\n|\022w\007b(l\031XZ\177\032_C\016\037\007L\\slt/j\"uE=*TkQNo;/H[KF\030]Ps\022\035_\020\0228\0344\001\"\013i\027\031\002mI\001\000`\020\000\004a\002\000`\006\007\001D\0260\004A@5(\020\014\023\001\025*3\010``\026\003\000jP (&\017#U\010R\002\r^r8\033n&\013QRo7\014$s\001\024\014\003*A\0011\030y\016T\"H\0107K\011Jr*\034NW\033P@S7[\016W#%^n9K\004\004K9F.\030GF\001`\030\006U\002\000b1*\035(E\020\020o\026\023\025dT9\035.7!\001$o7]\004\003)B\004\001\005\000P \010\004\001<\011\033GxSqu\027\000{\003kYe4\002z\036\010J\022 7\016=R*'UavGYkrI[>w\026}\nm&*rp\034J\034wk|$\024\"@;J\177d0\013;8'rsH\0235#CW\025\016.*_@\031fZS:q\027,r \032\n1\rA{~\\Ei$w\017Na\001\006.{a@]vk0pz\025J\036\036&A)Uy:\035L{\034\177\026G\014z\013v5YZR,\177b\037\014\013|\026T\025\032G(\rPFyl*\010%\002i\016C(x3e\\nwx~\007W\\\026(\035-K\003m\032%7!<1f\006\014<$\033$-Bsr9\025-[~4\016?=sN{\006|9\010gW*>\014e}\003\037s\016yQ\026\036q\003\031[mdpg\001E\034\005@\034m\013U95$-\010\011T\023\033\025u+_\nL\017nG\0261{8\037MK(\037y|#-z\016i^,~\031?D\020\020\014\002\000\000L\020\035\031A\n1\005L\001\0200\r*\004\003\004`%*Lb\030\030\005@`\032T\010\n\011Chu\"\024@C7\\N\006{IBt4[mc\010p`\032\003\000jP \014&\023#U\010R\002\rrb2\\JG\023Uft\020\024Mv{Pa\001D@P\030\010\002qf'n[9Cqb|WQ\026}\030=\rY292<\n\n\035G]\0221S)5ugEo\020X\017f]oOiJ&\n\021r\032:Q<\006\nY\nm!#\007W\026.M$\011-s/.YAP0\024\0268u\032MlZ=\002,k~QNwl|\030)C|rb9\\w=N{ 1JY$I\036\\ZA=\016N\022V%\177a45'M\nmzjg0 \"?\r2c\027L:9juXw:q&|~m;\024\0208\002\027m\034H\r\001R-=\177/O\\v7SB\017$l`\000k>\0367$v9\025b]\rC[>\024l\025Jg\014D\027)?D]=(\000-{B:,o0J8fq^&Tj\014p\037#R(t}pl\005Yfs[95kp~|f;\011x\031\034K\1778\024\036h\032ek\004\003\031K\002fz\023\r\000)I\037M\025\006w\001\000`\020\000\004a\002\000_&\006iD\0260\004A@5(\020\014\023\001\020HS\010d`\027\003\000jP (&\020#[\rv\023\005XS4Ymb\0039l-9X&\021Y@2\006\001U @XL$P9\032-V\013Ir ![\014\027\033L@3\020\020h\023\011\030`$\003\000jP \014&\035#[\rv\023\005XS4Ymb\002Adi6X.'I\001\006l0\\n2\001L@C L\020 \010(\005\002\000@ \011\ny,ek0\030\000$r~Gb\027\0177QD\003y,%S\036\030r\010/n\025QzoU3\001jPlejj'\014:rU\0368G\002Q\010\005\014\014I3({WqNx5-t'W\033r[H/\0256#>oaWP\032\0246tY\024uJ]p*=KD\0020lH::\nHc\025L\020So%;9\002\032\005$k/\007nHH#(8~\013#f6\001\034\177us^L[npR1Xi\024.\0147\034\017\0242\031}Cv \177\"\002)I'\023h={\035,o^@j\007deO$\\O\022\026`\011]\004k?\010o>1^\030\037\005~\016x-H\026+yUH7E\021*!p9\022;afD\"xPWmk\1773p~c0-\021\026;LDDP\n@(jWW=,@3]dm;\0118ZqP\023Jw\030;)@uW\001\000`\020\000\004a\002\000Yf\0059D\0260\004A@5(\020\014\023\001\020HS\010d`\027\003\000jP (&\020#[\rv\023\005XS4Ymb\0039l-9X&\021\001@\034\006\001U @XL\016R7[nB\002\r\0021\rL\003\0200\r*\004\001Db$;1^b0[\n6K\035\\ )\033mw!\001\006A\030 @\020P\n\004\001\000@\033 w\0323\rg(|4|)}{xbp2+/TH\017|%+\005f*\021\002/>\006\036GDg33cMn<6H\025{QXL:5/\024`\014Z_'\016-\0112u1b1`\007PO!#\0064+\r'~\033]$1 \025\016;j\016v\020]w~:*/\024E{`6u&S\0049=D\026G?Ur)'oy\034C\036\002\001@ \000\011B\004\001\\L\020\032AD\0260\004A@5(\020\014\023\001\020h\023\010,`\011\003\000jP &\002'SF\021\001@\034\006\001U @8L\016T7\\MvsQ^1\014\014\002`0\r*\004\005\004atk\005Rl\"[LvK9J $[L2qDR0\023A@5(\020\026\023\020\020lW\023QRf4Xl\027#%^n\020\020.W#!^r4]\017\022\002\021Rv4\\m\026{8b\023\030\004 `\032T\010\003\011BMV\013%Xe7\031m\026s\024b \030\007@`I*\014HC=aP\010$\002\026\010Xl\024\0035Bi6\031-f;%\\e\027\030mviB\004\001\005\000P \010\004\001)S\rn\034b]e\022&\\>-+!^:1-\013\013pMc!v)W\037w\005Oakz5\1775Z\023=}a\r(\r\0246Q,dP\034%\031)nL'H&\014\031O\033v('h&`h\006aB[\r,YcBE\000k%\013q{\rY~\025X\027\177Lc 2b#nseLTV?blJ\014*9fhPtX\020\002\006E;i\024\011=U*K4V6h5k\036EW\036/0\016\021\023G\036aQ Vo\014AE$'~%*\\M28S]g[(\013>\020;eMS\021+\027\010{\001z\0342\016H]\030sN*R\013S\027<]x8@\027\026\030$#Z12]T\002`\031.FB1^k/a8\031pmC\032\005S\032z\031:>\001V\025v6Ua(G_\016q\0076O1o\020\020\014\002\000\000L\020 \n,a\001^\014!3\000$\014\003*A\000a\030\011\010E\030D\006\000p\030\006U\002\002\0020:!Bm1\035.&9D 0\007\001@5(\020\016\023\003R\014\026k\011jr3L'#\001`\014\003*A\001!\031E(C\020\025\016'+MhC2[NF+H@f7\\D\005\033\025Fu9\032.GI\001Rn\020\021\014\027#\004@N2]\016v{IVs\020\021mV\022 b\"\030\010\000`\032T\010\013\011F*D\031\001(r:\\nD\033\025\\t2\\D\004\0331Bs9H\006\002\002\r\0021\024L\004p0$U\006$!^ph\004\022\001\013\006L6+Ihi3\032,6\013QJ@:\034NW\033QFe7\035\014W\0219He\030 1\020\024\006\002\000o_-\000%\013U\n!z#w],($@x\n\017V\"\027!>\035\000R\177&\000\021tRRf\\C$I\00698\025VW$Q\0266w)IOi#O%\003]n\002\001@ \000\011B\004\001%L\020\033aD\0260\004A@5(\020\014\023\001\021\010S\010@`\016\003\000jP &\007$\030-V\023Udg\030D\006\000p\030\006U\002\001b0:!Bm1\035.&9Dt0\034\001@5(\020\024\023\030U\0102\002Qdu9]\0106+9he9\010\014f{H@S2XnW\023%hy\020\032-b\002\021Bt0H\011f+Qno9\032n2\002\035Zb$\014$#\001\000\014\003*A\0011\030e(C\020\025\016'+MhC2[NF+H@C6\030.7\031\000h !P&\022I@N\006\004JPdD\033n\r\000B \0210iFe9\035\r\0263%Fa:\031(\007#Ijs:\030lVsQJr\027\031\014S\004\006\022\002@` \013y=GV\033\036v nM+u2\033\034b*w\r4d\005\027\")b\007k+\030TU-%K#z\010s\034\022.sPfU\014z5\016A#TLe77\022{-;l]U`IiCbmAu\024ie\023!d\014&0T\036ERw<\rma^@7\025x\027KJF`U.NahJ\022S-?\000+w\004\022\023bRE}av\026\031Vuq2\"\027vLz3+C(tRL(|~&bT\004\003\000@\000\023\004\010\002Z\030 93\010,`\011\003\000jP \030&\002-\020&\021)@&\006\001U @@L\030W2\\nF+I\\ !X.\006)D$0\010\001@5(\020\016\023\004Pl\027\003\024@T7]mc\010h`\030\003\000jP (&\021*\032\014\027;QJ ![mg\033UXt4[Ls\011 `&\003\000jP ,&\037!Y.'#%Li1X.FK=\\ )Y.'3%Fe9H\010FKYRs4[mc\011\004`\037\003\000jP \014&\030*\032\014\027;QJ (\031.'\033=\\a6\010\010&\013MRc\020\020h\023\011 `&\003\002%(2\"\rw\006@!\020\010X2p2\\N6{9Bl\026XL\027\033%F@:\032\014\027;QJ.1[mS\004\006\022\002@` \013er&S6p\n\004|\010+f$%\006Z2j|o!>At?9nu971TLn,WP\036\032LQ}#34Br\0270\\D\007KZXE\014M\027`F{fy-\004\030Mm\r\022\001h^\023\027p\r'\005:+\001t\004\034\035\023b\021vP?\0303)\007\014GSD\004(b?T\026\007\007.{]\013|@\nD.*`Y89\000m<<9q'^\024^BHOXm6\030hod9\031\004\004\003\000@\000\023\004\010\002`\030 :\023\010,`\011\003\000jP \030&\002-\020&\021)@&\006\001U @@L\030W2\\nF+I\\ !X.\006)D$0\010\001@5(\020\016\023\004Pl\027\003\024@T7]mc\010h`\030\003\000jP (&\021*\032\014\027;QJ ![mg\033UXt4[Ls\011 `&\003\000jP ,&\037!Y.'#%Li1X.FK=\\ )Y.'3%Fe9H\010FKYRs4[mc\011\020`\"\003\000jP \014&\033*\032\014\027;QJ (\031.'\033=\\a6\010\010g\023\025Jm0Z-B\002\r\0021\025L\005\0200$U\006$!^ph\004\022\001\013\007\016\006+Ifo7\030-Bk\031de2[,\026K1\000t4\030.w#\024\\c7[&\010\014$\005\001@@\032FN_)0J\031\0137\017%\017X\006\0246nSJ#0B\027ORh6\036{\0117pR+TPt\035\020EF\031\036?\025=R}wpf\016\035C:T=mg\035\0262\006X!#9\011,>9Q~B4\036q\017P\007\n0\002G\007<=_\031$\031=-u0\013g7/2\024QW-gSp\031%%U/p0dR\177O&Dm+*v8\022\002\r)F\036cZQf\022\037kg7e-1\003KH\010\006\001\000\000&\010\020\005<0@sf\020Y@\022\006\001U @0L\004Z L\"S\000L\014\003*A\001\001\0301.e9]\014W\0238@C0\\\014S\010H`\020\003\000jP \034&\011!X.\006)\001(o;[F\021Q@0\006\001U @PL\"T4\030.w#\024@C7[N7+1hi7\031f\022A@L\006\001U @XL>C2\\NFK\031Rc0]\r\026{8@S2\\NfK\rJs\020\021\r\0273%fi7[F\022\031@B\006\001U @\030L4T4\030.w#\024@P2\\N6{9Bl\020\024\016&+5Ru6H\0104\011DT0\024\001A\022T\031\021\006{C \020H\004,\0338\031.'\033=\\a6\013.\007\023\025Zi:[(\007#!Bw:\031%f\033=Z0@b (\014\004\001I36?\000:\023\0379F\013^\n\017<&E6\001;}q\034,Q\033\020\"\026c0wm\177\004\017Q\024{K\033!&_f\001G\006f[\\g$E9\035P\031\rrHET\0336l|~$&g+\010\0117.doH5|7HcgWG1\030\"S@q\007<,Bh\020nVj+=bU2&\037x|\000p>\031Yq\001\024\021:};@\016J\030\006f\023D\"S])7rL7&d\025K5\010:@@0\010\000\0020A\000+S\004\007\0341\005L\001\0200\r*\004\003\004`%R\004b\025\030\004``\032T\010\010\011C\nv+Mhe9\033D\004\033\005`e\030DF\001\000\030\006U\002\001b0J\rBp2H\nF{]\\1\016L\00300\r*\004\005\004bE#!Bw:\031$\004\033=\\s:[\016FK9N 1Xf\022A@L\006\001U @XL>C2\\NFK\031Rc0]\r\026{8@S2\\NfK\rJs\020\021\r\0273%fi7[F\022\011@>\006\001U @\030L0T4\030.w#\024@P9\031-VKUZ )Y.'3\025d !P&\022A@L\006\004JPdD\033n\r\000B \0210e`r2[-\027+4Zs2\\Nf+I\000t4\030.w#\024\\c7[&\010\014$\005\001@@\032#1YU\013kpK9vj\002A1#g\016r$\010Uk4\035q`n*\026#{c\004ATtRz\nm 4Bt\rI\001BG\036R'KKJ@\011bq\034\014^>BU\027sdD76B\024S8\021\037\027K6=;6u\003\r\007*\036E*a6PB/rQt#T;(-T@tJ!C`{W\023i\004\030>P34`n\nF .JK\000bmS3\031w\011T\014\"\016\031_u{Fd\004\\\030_uEV)[\013i\034Ao('\002\011\"\0119?O\0206p\005\004\024g.L!wX?)\005ZTOW85G\007\001dlVv\013Frh{oMG,\025UL\"\0115Pph\010\006\001\000\000&\010\020\00440@bf\020Y@\022\006\001U @0L\004Z L\"S\000L\014\003*A\001\001\0301.e9]\014W\0238@C0\\\014S\010P`\022\003\000jP \034&\013\"\035.&\023\005\\v4[\rF)D\0360\006A@5(\020\024\023\003\025\r\006\013]he\030G&\001X\030\006U\002\002b1\"QPa;]\014R\002\rJr:\032,fK\rBt4[mc\010|`\035\003\000jP \014&\026*\032\014\027;QJ *\032-V+Mha6\\\r\026s\034@C L\020\030H\n\003\001\0005E5CaBEC\024}##m#\034vl\034bp`\035~p\027u\002>NiIp\037\013\024pX\034s*\030\030\027\177Z>.tNPR Ty[#A\014<\034{\011\024\r\026[tckgY!G'A\003ir%md\037o\002GN0k\020 \017l|Q_\0078Qy\013,8d@\013$A\003\030}W}_U\\]\n|\"\006p\020\014\002\000\000L\020 C\034`W\030Cf\000h\030\006U\002\002B02QPa;]\014S\011\004`\037\003\000jP ,&\030*\032\014\027;QJ *[M\0273\025ds0[\004\004\032\004@R7[nC\011\004`\037\003\000jP \014&\030*\032\014\027;QJ *[M\0273\025ds0[\004\004\032\004@R7[nC\004\010\020\n\001 A\000\010\003E\011\002[87}6M\011\035?w<>6:7Xx:\"\010z6a\025\005S/\035K%5'\032\013\000]0[K{\ny#h@.\027WSro6d!gM{#om)p**\030q\032\034-4[,!Vb\037_(_kSm\020TY\0074S\025K\035'-T\036CTztp}p\025E&qYNI\026>K<]wm\017{EjV V&^YM4f(|Z*\030+\003\014g\034kA\013<[\014Jc\022\033\017w\037eVUR\\d\026%\003H\025(\034N\011om|hnD\020)P\010yt\032\026/uN,\032?dG\177\035sH\003NdU>vH\014L\0177n\00602em|JJ\025=\005TNxUh!\014J\011\031DVVPymDEv)\020\0031=:\017|C=S\037\024'U(hB\030n\007\002g<\004+gUQ\016\027\022C8;i\013\"zX\0344KEh\026U7\013_ WaHQ=g\006HXu\010o~\036M|&\010{Db\006YM\004xz}tfA\023q\"hNVBgH*it^\034f\033\003ri_\024Wt\001*%&T0YDan\016\002a4R\036\037T\016qs4TTr\025!)\011bCEZYT'2k\\\017_m\014~\011!\\\017pMT\030\026F\177T;x8F9J&xd9jE\037xFys\032\031B4Cl\0201W%&|D[$9LJ\022\017Jv\025ojD@B \r^PgDk}j\007?>)\031\n\0042{R\177o\020FI\034&$\0141\010QA\014L^\014\005\031\"t\"\016bQT|b\r^q\005=fP[XTLr|.>vSP\036I\037dWjH\026o\031\023owpH]\027\031&+\"\021\003s>t\030}Qr2~U\010W\016\n|BT[S}v\0035\014f|%\rNW,\003t7mg4I\033N?gE>b2\010\016aRH\020\004\024?G>$Q\014\014Q1\010c\000?\ra*lr_\023rA\031\030mx\010\022.%\004%{\011\035Z7)<\036\0013\007bgFwpw\025>\017Zg~j\011#;)\024Bzq\"g(CuK\022HM/\003\034j:\021\011]\000\027'\007\n{&m\005\022\026f*\\\022>r\034\rA?\013TQCeOs\024!>\027\007\\\035\003R\037JIril2eY#;F7wH\024\000z%$G#'ba(0@]m%s\013lpFbC\"A\013!OF\006^cj\024\016vk-\014J a:Y\002}r-b;\032q\014\035\001\npZ\034&'a*2}[r2fioY\020p\"~)XXN?$\017zdPV23oG\005\003Zq. n:r\016\024\022L02\0245\016\020i\033+M{|^$fWEz\17774|2MbZCrR\026\\+vKqZ=\024\001K\002>DV\0219hH\rrE(ek76?=W0\023rSCmV\017\n\r\177a~\r\004\nQ\016;Pi'%L\\0\\\007V\003Z\021E$\n\\\034\021\025'\177\017P\023\027\0240\010o:8\026%\016}`}8$::\014\025g\003_ f\006sco\0040y7u\003-]KE\030\033q]\004i=0`A]4Li#\030V]8d&\021\"92'<\026~}c\035}Mf&nViX\031*`KRwD&5}tPv\025\"~\000\004(\020EL'Fl\020Ty?\036C'\017\r\017\016\020j|t@4Kn\013G2DF\022?=6FQl8Yn${\036C\0357\022ST#\023p\"L|$\016\024Y\021\027eDWQo\035bg-\000|\020\010n\002\\Wrs\013bZ\034\010qS&[N\023&?]`\035=\026AD\006=D\017BO\030 \0229}5{1%\"L\022f\037\033J\\\032SI\005DvZc(|H8]_4>%_JW*Tt^[E\021\002+\025d-a\022#\025\030l\rq\\G4\0000Zm@Dsz9ZB;\030\017SoJ\035D)\n45B)d.\006t\022)0\036H)\035rpK3X[\014\014p0\027\026\004~fa$\023\000Gz''s|Aw0\177%+[\035\022Q@2*D986IbVo\034\\/x'\005\003Cps\005(I\001$.ACmCI}\026-\013+\005_\001\022X\0009}\023;\030\030~\005\\\005\020Q}.<\\\036'L)h9Z;@\002?@\027*E\027:2X\177O\r-aarTZ4k\001\020p\011\002%\036K\005\013m/]P\022\rC`o\022Du+cL\021.(\001\025\003F\r\nn\026+#y8=\010\020\007\033\007\016%M1\037)f8\026[\027.3)\020\014gQ\004\025\030e\004rGeByA\023M*L%n\001\"\006\016\027,\n\00068K!}Tq1\006I\007UL(w\007HVWo?q\016\177\013#^[Ut6\007,$J\\w\nGa]/\002L\011NN\005M\017}js|D\003h\1778\011\010r .\031#Y5'\035n\017\nhbevh\r\013\021W\rmX\000cFYfz\002%1QKa}J\023e#8\013Snp:\\2h\016ghX\037Uf2n\017.\022Sg\027N3\r8|D\017>gB6qR\0014\r\031'&N?G~Bi!qL*e/1tD_L+\006\0168]K7<4A|m\006a\037mQ?k\010-\r[`%\024vv;v\032\014.\026o79 \0045\"\036@M\031!\007H5L\002uiJp)\032nNmQl(J;\006\013\n'b$E\026-q`DgW \030\004\000\001\030 @\033yB\003.\030Bf\000H\030\006U\002\001B0\022\r\0021\005L\001\0200\r*\004\004\004`$z8b\020\030\003@`\032T\010\007\011AjF{I^n:\033f\021Q@0\006\001U @PL\"T9\030,F+I\nn3Z-f)\001\022n1KF\022I@N\006\001U @XL@C2\\NFK\031Rc0]\r\026{8@A:]\r\006{IRt:>GL?D\024\007\026-q5a\nHZe)\177-m\177F\037\023/\011;V&#e\177%*8]U\0078/=\017}xWP3.Q)I;%+dN2$sj\004pM\020\n\010b\020LY`\021+M\002yd\037~1et\007#=2&!v\007Qpv\004\016w\"z]O)*\024W8\035i|t\027\004\024\036\020P$X_\006\013|f(\036<:W})Q>%I[I\033\021\026=;/\006amoxDjPw\020&Q\r\037MGXLA*\021\0047n2p\022\003\026qx\007(\007\000\006\0273G\ns\034|e\007qR\011U|$*u|[^\034\037j%&ya\007;\0002k\032\033\0024Vd\010I)<\004\003\000@\000\023\004\007x07\014!3\000$\014\003*A\000a\030\011*S\030I&\002\030\030\006U\002\002B1bU\\i:\031,B\002Mha:\031.2\002A^s:\030-B\002MJr;\032,6)D20\013A@5(\020\026\023\010\035nw99js8\034ef\033=Z/!T\n3\010t`\033\003\000jP \014&\024*Tj\005\031\001 r7Y\016V\033QRo7\010\0104\011\000b0@b (\014\004\001Fk.={\022LqJi\035aFaXP0a4\005\000\023abN\006b(,:dO3H\002\004}$]4\036[\036\010D\003-iFFjj\030_\"Mm(iZg(_Xz*\027Jh\037\037`mb\022>A=t\014\013\0111J^\177&Qa\024\013t\004\034=kt\177v;GozUT<7*\"j3MTk\037+%M%KT$DeR\"48P7m8\030*Pw~8y\0325b\013s\000p$# Rlr63|gu+p6?Z\027lG\031SG4\011/pv'c4\022\177Md|\rM\007;w^mbs'n\002\001@ \000\011B\004\001%\014\020\033YDH0\021\001@5(\020\016\023\rUL\026c%\006e9\035\004\0053\005Xi2\030.FK=\\ '\031.G;=dk\030Ef\001(\030\006U\002\002B0rYBl4PlW\023PX $[L2qDj0\031A@5(\020\026\023\026\025L\026c%\006e9\035\004\004\0331Bs9H\0062\002A^l4Xo\022\002YBl4Y\014\027#%^n\020\020.W#!^r4]\017\023\011\004`\037\003\000jP \014&\0304\035\016G\001h^/;]nrsYBl4XlW\023P\\c7[%s\011\000`\036\003\002%(2\"\rw\006@!\020\010X\"i7\031Mt\003YBl4XlW\023P\\c7[&\010\014$\005\001@@\0349BF,\034t56\0204\005TWa\\NY\035/\037\036S?\03612K,-&gfT% \\\004&Ai\030Q`Su;M|{=-;_\033\034\"IL?9BI<\026KR\025J\007\026Nle\011$Nha.VQ(\037jB 44c,?l\006M<x\031\004G5\027\033+\r\033vmCm\"a7)Ox>#E7rM\033D\014#T\011 /N9\021uWg:@@0\010\000\0020A\000;3\004\007\0241\005L\001\0200\r*\004\003\004`%*Lb\027\030\005 `\032T\010\n\011CJf+IRS4Ymba\001\022n1KF\021y@:\006\001U @XL,V2\\M\025\033%Nn\020\025\016'+Mh '\031.G;=dk\030NF\003@\030\006U\002\002b3\011!F)\020\014'\023Id@V2\\M\025\033%Nn\026\010\011\026s\014\\ \026H\010f{H@a:]\r\006{IRz2Y\004\007+MJ 7[MGIE\n0!A@5(\020\006\023\036\025LW\023%&i3[D\004\0331Bs9H\006\022\002Ajb6\032,2\002Adi6X.'I\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\020\013$\0049La\002\000B@(\020\004\002\000na\032KMSs'l<`GDs<=n\033\00216eu]\022\024,l\006\034LY\006k\000`F\n)Q\031G~\010\031T>n\rr\033jS8*N*\011}|#45l\005xao\002\022JWcP\013\001u\r\016\0378{\014\01736)H\014\016=_r\0100Voy\011DHApN\000]FV\030Hm]3/\000\020!ZFU454#0&Y+\020IJ@54\177J\n8\025LU4\014\035Jx_\011oG@`\177pOw)-\031Lm?PyI\007U:\177\017JM>I2xmO\rl}\000&\000\r.\004z\017\022|gK2T\014-N9]d]3nYB4_Qs;\032s`2?\021z+MY&{-*r\0035$1Gvt)],9\\$\\QJ#`\026g6\037\035m\032pzY\017\001\nA\011*|\014OQbV\021\014\016wm*Shc\rS\"Tm\010T<\033{\025W\037K\035Z\023h\010\006\001\000\000&\010\020\004l0@if\0219@*\006\001U @PL\034V2\\M\025\033%Nn\026\010\011\026s\014\\1\017L\003P0\r*\004\005Dbe3\025di)Z,vq\001(r:\\nB\0029Jt;[n&YDv0\034A@5(\020\026\023\031\025\014W\0235f 7YD\007+MJ 0]\004\006CQhp9NEr{]nw\027\035LW\023%fi3[Ef\033=Z/)\024\010\022\001!F)\030\014\006\022q@X\006\001U @\030LJC6\030.7\031\000b (\035,&c%F (\034M\026k\005dy\020\023h5\032@@R2\\n\006{9He9\014\020\030H\n\003\001\000.=Ushtw/sKsRKyM2=n\033+=U\026c\0262%,Q:/-KX\021\017(M\neip\023gY4Do\036B2\177u4Ql^fr2+\000'\037A\0006R[\003\003\0268-u]%(P\022\022Xbsi\031!-MjB!\004b\037P^\003\0075\036bpm\010Z\006Wx\016b 1^\1774mz\014\003.\011<9#Hf\021a\n\036z\032o\023l &<_\024m,c:\000P\020\014\002\000\000L\020\036iA>1\005L\001\0200\r*\004\003\004`%*Lb\027\030\005 `\032T\010\n\011CJf+IRS4Ymba\001\022n1KF\0239@j\006\001U @XL\\C6\030.7\031\000d (\035,&c%F (\034M\026k\005dy\020\020lW\023QRf4Xl\027#%^n\020\020.W#!^r4]\017\023\004\006\022\002@` \0132j\027#\006ZD8\034\001WO\034a~B\010Lf\006&\0114.jT%\027bip\013Oj\007A\020(\037\"xT\032|PX\r7sdYz\003\003g*wX6oXt_ry!$_A_)<#jox3\007p\005'roBG\026)!\"]\rQM$wd\014D\007;\n`\0225g/OL\004\033\007suM\025|\177RmB\036U!\002\023\021\003\030q\017\006qu\006$D8\0222'\003}\\W6vj\034I(i\034n4\003M435\026MjGk\016C3J!\002MAP1:\02420.mi+\014c|Z\031`B5\006oy\0113E|D,\nB|\010}y\006^b*\011B\017b\022W\003t$\004\003\000@\000\023\004\010\0026\030 4s\010\\`\025\003\000jP (&\016+\031.&JMRg7\013\004\004K9F.\030Gf\001h\030\006U\002\002b12YJr4Tm\026;8@T9\035.7!\001\034e:\035mw\023,b;\030\016 `\032T\010\013\011LJF+IZs\020\033lb\003Ufe\020\030.B\003!ht8\034g\"y=nw;KNf+IRs4Ymbs\r^m\027TJ\004\011\000Pc\024L\006\003\0118`,\003\000jP \014&%![\014\027\033L@2\020\024\016V\0231Rc\020\024\016&K5Bri\037x-++\nw?9V\006r\035:\0036(b\021;\011~'\177\030\030\010\006\001\000\000&\010\0174`_\030Bf\000H\030\006U\002\001B0\022U&1\013L\002P0\r*\004\005\004ae3\025di)Z,vq0@I7\030ec\011\\`5\003\000jP ,&.![\014\027\033L@3\020\024\016V\0231Rc\020\024\016&K5Br\010\014\005t&=Nd#Ow\000K\001y\027n*T\021\000ZW0\006\037<\037si\035f4\000U\010o]52\013\"1Q8w\021\037\031t&1NW\016Hso'P^\007.|\025\020c#D\006|`\037xV\010kw\001$m$da\\{\014hAK)i0^9v|}M?DD)O8v\n{!z\023EHL2KP\026B\026N\002\001@ \000\011B\004\001(\014\020\034\011D\0260\004A@5(\020\014\023\001\025*3\010\\`\025\003\000jP (&\016+\031.&JMRg7\013\004\004K9F.\030O\006\003P\030\006U\002\002b3\032\rXa9\\d\003\031\001 u1\033\r\026\031\001 r4[,\027\023d@C2\\NFK\031Rc0]\r\026{8@A:]\r\006{IRta]uJJ'E\007 iW<\007u\013S5\007\021JchU:y}E}rT@'/U\027\033U\006\006}l\002G\031\014y]b^6d4X?\011Xt9\011'W\014p\020\014\002\000\000L\020 \016la\001e\014!3\000$\014\003*A\000a\030\011*S\030Ef\001(\030\006U\002\002B0rYJr4Tm\026;8X $[L2qD>0\016A@5(\020\026\023\013\025LW\023%&i3[D\005#Ijs:\010\011f+Qno9\032f\023Q@p\006\001U @XLb(1J$\003\011dr9\020\025LW\023%&i3[EB\002%\\c\027\010\005R\002\031^r\020\030.W#!^r4^LV!\001js2H\rvs1r1\"L\01000\r*\004\001DgE3\025di)Z,vq\001\006l0\\n2\001L@P:XMFK\014@P9\032-V\013Ir !Y.'#%Li1X.FK=\\ ].FC=di:\036$\002i\001\0163\030 @\020P\n\004\001\000@\031;Tq%|<\007c!s<679o?\014K.(\022\030\023~\0032\037kPHB^Q>\"Ja`EH\004on\024aH9.b\006\004\"\006>jeFu\033j\021 c_\"m4Ta,i\014r23!h^\024K7\001\000\010Xh^Y\032#&y\003\013Y\025P,.yP,3QXgWWp!\022!9l\\Ng\031\033\\\032tL*\0348/\004\004i<1^I\"\035\006d\025.\021\r:/n\031\030\"TQ\0212e,\017\0240Dz\033ME\017Z*@\017owK\025.>\nH\014`A>+\023_jHr}!\r\027x\004\\/v\010\024+(yNn5]\r7q\0034le#\006L,U1\0367!&\\\016:S_\003\010,\013Sr+pgC=B;3U\177;+ez iT#\030S\023e=\030z9s\010oUBY3`w\031&$:\006kahI&[cK\034x\032$v\025W\"M\013J^.\002\001@ \000\011B\004\001\033\014\020\0329D.0\nA@5(\020\024\023\007\025LW\023%&i3[EB\002%\\c\027\014#s\000t\014\003*A\0011\030Y,e9\032*6K\035\\ *\034NW\033P@N2]\016v{IV1\035L\007\0200\r*\004\005Df%#\025dm9H\rv1\001js2H\014\027!\001Pt:\034\0163Q<^w;]eg3\025di9Z,vq9Fo6Kj%\002\004@(1J&\003\001D\\0\026\001@5(\020\006\023\022PmF\013Mf \031H\n\007+\011Xi1H\n\007\023%Za9\036$\004z\r&P\020\024LW\033A^n2\031.#\004\006\022\002@` \017\017\020\020\016Ann^\032#K8mi^\013M/R<11kc*o \002*\004;\014NE-9;\022w\032[kMM]=A@&Y}VX\002LZb8scx}eZ\031\024soX\"y{vl\030]7\034\036@>5aw\0350\007c([ZxF\020y|\021\030\\Gk!OYgF}V3C\004\"-\023\024es\007o'A ^?\002v`B\r\177BE3?GK<\013ZZ\\\005B[JT\004\003\000@\000\023\004\010\002P\030 8\023\010,`\011\003\000jP \030&\002*Tf\0219@*\006\001U @PL\034V2\\M\025\033%Nn\026\010\011\026s\014\\1\036\014\007 0\r*\004\005Df4\0331Bs9H\006B\002Ajb6\032,2\002Adi6X.'I\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\020\013$\0049Hb:\030\016\000`\032T\010\013\011L%\006\031$@1\034N'\002\002YJr4Tm\026;8X $[L2q\000Z #\033n\"\003\005jt4\033n&KiJd\020\035.6)\001^n6\036&\021y@:\006\001U @XL,V2\\M\025\033%Nn\020\025\016'+Mh '\031.G;=dk\030 1\020\024\006\002\000]<\034L\177g\011.BU\027\020:_s\017b_m\001\017c\010\027X\021\033N\031AgU\025\030E+FC\030\034vJ\0137uuU\\Yd$LR\005D<~\000@\033xp,1J\0012w3:E-^X\"vb8a\006x\024&rRC\017(1 ;EZ*UAP-\rnY?JWbl%4\003C6Z&h1a}t\006\037l\000zHz22q^8P,bcQ\030\035!Z81cf\000\024G` \030\004\000\001\030 @\035YB\003J\030Bf\000H\030\006U\002\001B0\022U&1\013L\002P0\r*\004\005\004ae3\025di)Z,vq0@I7\030ec\010|`\035\003\000jP ,&\026+\031.&JMRg7\010\nG\023Uft\020\023LW#]^r5L'#\001`\014\003*A\0011\031DPc\024H\006\023Idr +\031.&JMRg7\013\004\004K9F.\020\013$\0043=d 0].FC=di=\031,B\003Ufe\020\033mfcdbE\030\020``\032T\010\003\011O\nf+IRS4Ymb\002\rXa9\\d\003!\001 u1\033\r\026\031\001 r4[,\027\023d@C2\\NFK\031Rc0]\r\026{8@A:]\r\006{IRtNd\013\rR\036e#^.\001\037\004dv44! \014jkQ5O\003-Uo'\027\177\001\014\010<81M\014e\006P}\0239~F\014\033\032[\177IK2~x\014\004\003\000@\000\023\004\007R0/L!3\000$\014\003*A\000a\030\011*S\030H\006\001p\030\006U\002\002B1:I&A\020\021\014\027#\004@S2XnW\023%hy\026\010\011\026s\014\\1\027\014\005@0\r*\004\005DdU\033\025Fu9\031$\005\033\025dv2\\D\004\033\025dt4YM\026\033\005hi7[D\004\013Uhh7\\M\027#da\001B@O`\004K\034z`kP3rjU\011AUuB(\005l\014VkQba_\035k\032^\014E \017K\004 \0249\033|<\021bE A\r\020Z\0147*z6\022\010\nZv4 3 -\011\027I%I,b\022YY\010\003c~x\ne[\004\003Y.\001\033El>M]iN\032B\022VL\022\027\021m\034R\032\011KI!\nf8\036\nu)\025!1f:4`V\0319f8m8\031i\016.t[Vd\007O0\020\014\002\000\000L\020 \0114a\001O\014\"s\000T\014\003*A\001!\0309,e9\032*6K\035\\,\020\022-f\0318b\037\030\007 `\032T\010\013\011EJf+IRS4Ymb\002Qdu9]\004\004s\025hw7\\M3\011l`9\003\000jP ,&2*\031.&kL@o3\010\016W\033\024@a:\010\r\007#Q`s\035\013ew;]n.;\031.&KMRg7\013L6{4^R(\020$\002C\014R0\030\014$S\001\014\014\003*A\0001\030q&e1].&)\001&e9\035LW\021\001\036C)T\004\005\023\025fp7[LF+Ha\001D@P\030\010\002pQLY\020Pw:f\n4<\027v\033X:Sz\177\024\027D0gnO}\027cNj6\017&Qu\030y\003PK\"/9lkn!Q\030:\022\014!\025w\011\0247W_4L;\177\\\177r+-&\033dW3\034,\030\036\006jQ\\B\000\011\035D\"S\r$cI6=O0\003\037D=\023/P!\036\024qk3!K'E4rH*i\025\"q\026\036:h\023W\034e;\001iV=B^lk\\\0101\026\037\001\000`\020\000\004a\002\000M\006\010\r\024b\027\030\005 `\032T\010\n\011CJf+IRS4Ymba\001\022n1KF\021y@:\006\001U @XL,V2\\M\025\033%Nn\020\025\016'+Mh '\031.G;=dk\030Nf\003H\030\006U\002\002b3\022QJr6\\d\006{\030@u9Y$\006\013P@h:\035\016\007\031h^/;]nrsYJr4\\m\026;8\\c7[%w\023AB \024\030e\023\001@b,\030\n@`\032T\010\003\011Hjf+IRS4Ymb\002QRm2H\n7#\005Zp4[Lr\002\005jt4\033n&KQr !P&\010\014$\005\001@@\032!LuOB\000\010+\026\026:h\011\021\021\r\030W`JZm{![9Y|Np.rCt@K\007a\030(rNfzT\\\030yBcuS4\031M^C8\0365AnP\nOg\001q\035P$w\020ItFQ?Q\030!#S6\n_2y\023\001Q\001\030\0374\032r(B\003\024^AiyfY('oKU\013~.!\014F]);g\013PDCs\r~y\016M:Y\011'&mL|\0050Y\0335Lw\025l\"p\\ Ll#([|r\016Jn{\177#4;91w\001\000`\020\000\004a\002\000\\&\006\011D\0260\004A@5(\020\014\023\001\025*3\0104`\013\003\000jP (&\004+\022*4\011D^0\026A@5(\020\026\023\023\025M\027\033\004@I7\035\014W\0239Bt4[mf\0130@S2\\NfK\rJ \\n6{\rRa:\032-vqD$0\010\001@5(\020\006\023\004Qj\002\002I^o:\010\006#\004\010\002\n\001 @\020\010\002R\0018-5,\"\003a+5\011L\027Hd\001|_ffuH2^o2\006\177\rTV($4Lb\027\006+FV:(Bu;\0218 \0038:n{Fn;{jx\026NA\023$XA\032\177\"]E\011.,\016Rh\\HiqPL\016'9iVU`\nmD Qa}[$/i\rxF9Soe#L=(\026\025+\007-]\031s-\0214P\020kuZNph\\\031\011=sE|,\002NK\016_Z\003\007%\014\017^0|5\034|?Ku\031QHJ\013wd\n7Qk=YV\017MlD\006vaI\03468E`\011^eg\017EDr\177AQKZ#x- \025Ggm\017p!5\024`%d9\0116v?.=\034b26XaHTtc\016z\"u-\005\030\"\036\r\006$7aJW]\n}s41Uzlv(O%+;\004kC#K\022RX2Bm\014@ny\003;XM\030`\014\030ddjg` \030\004\000\001\030 @\027\011AB1\005L\001\0200\r*\004\003\004`%*Lb\r\030\002``\032T\010\n\011A\ndJM\0021\027L\005P0\r*\004\005Dde3%fa\020\022-g#\025dn0]\r\026{9Bl\020\024lW\023YRc2H\010\027\033M^c4X.FK=\\1\011\014\002\0000\r*\004\001Da\024:@@R7[nB\001La\002\000B@(\020\004\002\000];\010;ASU#3wnpH|4\020Q\007U\024tU\030tsC\033Ff\0321PU~Br$iO#$E\014HL>&\022\027{q}F8 \004Ea8\ra>x)L+sD\026\027\027RK3~J.\024Zn&c\007\010uIT\010Lu2\021]#Z3IR+\\_\034*C6T^\034G#/Z\0308\023\002U%7#at \020\034\005:~[\0022^Fldn~NX;,z7},F$\013\031\017\001b5;HI4\003^h\004P\022L\025\\fIAy9\032q4@7H\006\007R\001[4y\034kw\005<}uo0\nD\030'c\177 \034H:=C5)GaZGLBp>.#\\BFbU\026\011\00331l+,\027g\003Qg'lI>}\007l\013C\032^\007\003\037GPJCf!\037KSD-cTEH7\013zm\006-ma\006\025r~\011\032\036)B\024~.,:'J%bIC(\010\006\001\000\000&\010\017<`a\030Bf\000H\030\006U\002\001B0\022U&1\006L\00100\r*\004\005\004`E2%&A\030Kf\002h\030\006U\002\002b22YRs0H\011\026sQJr7\030.FK=\\a6\010\n6+Ili1Y$\004\013Mfo1Z,\027#%^n\030DF\001\000\030\006U\002\000b0J\035 )\033mw!\000h0@b (\014\004\0018u|\021z\007\033cc\000\017kg\024z\\\004\007}\011|v+4b\031z]eV\013\003v\027\016\021\033 7\037!W.&\033\037vMG[1\rH\0302\000a\034\016A+pp\003 \014F|\177[I[\0221ftjyOi4-F]\002Fy\031g\033|\"O\017\000/`\n\022\035D us%'*\\\032$\021o=eS6nSA|_U\005eDZnajBu\r\032@GR+\033G7:0\023]\033 @@0\010\000\0020@{f\006\011D\0260\004A@5(\020\014\023\001\025*3\0104`\013\003\000jP (&\004+\022*4\011D^0\026A@5(\020\026\023\023\025M\027\033\004@I7\035\014W\0239Bt4[mf\0130@S2\\NfK\rJ \\n6{\rRa:\032-vqD$0\010\001@5(\020\006\023\004Qj\002\002I^o:\010\006S\004\006\022\002@` \nh\001]rP\037\006\021\010q\0030\033cp:\022\017\027\027/@D\010H5]<\025,>\032+\023_XG\022Zg\024v\025bO}P\000y\016XSap3p\026\023pX#:^JL\n^m\003QPo\0117x\006\\\007J])eC\022B\"~\035D(fN\036\004v]3Gxk\037-\003GP.\017U\032YFL\033Ldc,7b&Y\014\004\003\000@\000\023\004\010\003\035\030 1C\010,`\011\003\000jP \030&\002*Tf\020i@\026\006\001U @@L\010U:\030-\003\010\\`\025\003\000jP \034&\016)X-G!\001\030a5Y$\004\033%hy\030F\006\0010\030\006U\002\002B0zaFe9\035\004\004*h@bc[G\0054G\032'S`1\013wSu\016\031;D,\017w\r(\002w8\03540\0165\005\023A\003.' 6\n\011\032\016\177R\014(E\0270\025e*5mM\031E-r\036\016\031>VT/yE\013*T'\026iAm'\033C\022+h\024\035\0308j\011Z\010xDuh6\002\000{6\002??GQ}\177 \000E6h\0075\0301%\nij{2K\004\001\013f\016x|*\003F\r-\r\"W\032_)k\000\025L~`*I>6c:\017z\003zC~:9\006\004\005@|t\010.LEP%/qWx9gS\025\000\030\005\013|\014Y@sWVm9X-g0!nnG\034O2\022C17W\026T\035rV9\\p,\034oc2\023N\030C3\005\034tvy_k\r:>\035\032%kU-+:)\024%\r}$|w\034 \030\004\000\001\030 @\024Y@v1\020L\003p0\r*\004\005\004c\005C\rJr:\010\011\026sQJr7\030.FK=\\a6\010\011\026s\014\\1\013\014\002@0\r*\004\005DaUC\rJr:\010\n&{=h !P&\010\020\004\024\002A\000 \020\005\"haEG#CtAvp\031\020\rXh[\02450X6^\027l+Iz\010+h\010o\036\"N+\025;[\003! \031wB\025\030h\022\017\035FB:NR\017L,=eU\025&EBTN]\\yR;!\001;w\017\025`VWD\004\022;-$jc]A($\031\011\177?8\027Z2E\025*(\020\022r4L>O?\036P\005\000hF^r\023\002 S '\013+\000djz\r1LTL\010SB\037.t\0142'\026cooEFK0YpZ?UH\030>Q\"^(8\021K\006E\020\0043Vrj\024(ZLun jDW;/I~\006\034 IWlH=hj~Y\002J\035R\032\nD1J}\030YA\024=\r\025[&=d\036\004}l\005\005Aj ox&\022-[\"\033a\007#wN`2W\"M6`Of:\020BO92`f)/\033U\014S&E>G\001HUoEL9*RCEQ\022bKh@@0\010\000\0020@sF\004\001DB0\017A@5(\020\024\023\014\026\0146+Ih $[NF+I\\a:\032-vs\005X $[L2qD60\014A@5(\020\026\023\011\026\0146+Ih )\033mw!\001\006A\020\014&\003\021Pa\001D@P\030\010\003->\033b\"\032M\013nG\026\nYvP\000Le\024\021\024HW\023= !U3\024#.xn\\1yl3\016mSt\033xYA,I\021\\'T+SE\021^\032\010\004\027V[%EDLTXN\0339\005B\025\036)r#[L\"-@o|;\011N,k3vg$DX`Ob@R\016\001\007\014TJ\016\031 \017HV\006\034+\010jU#q\030\026w2D\016)\034WI\016K\031a\001\027\016\023+he{k\001\000`\020\000\004a\002\000SF\003qDB0\017A@5(\020\024\023\014\026\0146+Ih $[NF+I\\a:\032-vs\005X $[L2qD20\013A@5(\020\026\023\010\026\0146+Ih )\033mw!\001\006A\020\035F\023\004\010\002\n\001 @\020\010\003\003\022U|Vc)X\004H\007a>Dq>\004efR\027k\r\\{3=kW#\021R\0146ElFxh\0318\032\025)\030\014so9Qcpt\031LgzI\024@(\023\027%\007\n\032\014 \036L\005:_\031\033z\037zHD\0023vsj7\022g'Q\035d5\007#9=\001x0pp\016;06Dw=<#\017B)bqO>\025\007/yExU\0179wh\010\006\001\000\000&\010\020\005T0-\014!3\000$\014\003*A\000a\030\011.W\030DF\001\000\030\006U\002\002B0K\011JT)\025*5#\025H1\rL\003\0200\r*\004\001Db&\023\025(R*TjF+\020@R7[nB\002\r\002s\030FF\001@\030\006U\002\000b1\013\011JT)\025*5#\025H )\033mw!\001\006A\030 @\020P\n\004\001\000@\032K#Mt\023\005\016\nP\rz\022Vpe\023m%y5k%\r\016Q[\004SDpk!\006\001\005pH\177m\032\017Ry\003\r0Q\020~G7\022X}?\0229V}\037j\037\r/_AMufxuyVz%\026='K-`t\014t\030<\035t<\177\037hM9v\023#Xh;>JTyr\004_\005O\037\011\n&`2\034e,q.\037{\010\023\017U11|c\002@s%5g\003xUe?Q%vw\007\"_b?\005@dgVTd\023\034@r-4k|\014,$m>c~\031\007 c8fqpE3M\007_%<9\rr\033->i\002fgM-\014\034X]_\011\032\177|tv\000d\022s{]M\177~.0:;1T<;W4aQ\034j:1~V!z\027\013eZ}zAe|k_o\0116IoC\021Az\031\001\\\\q\0017R,CN\031n1=0; j--) { + ret[base + j] = (byte)(l & 0xff); + l >>>= 8; + } + } + return ret; + }} diff --git a/src/org/ibex/net/ssl/SwingVerifyCallback.java b/src/org/ibex/net/ssl/SwingVerifyCallback.java new file mode 100644 index 0000000..832ef2f --- /dev/null +++ b/src/org/ibex/net/ssl/SwingVerifyCallback.java @@ -0,0 +1,99 @@ +package org.ibex.net.ssl; + +import javax.swing.*; + +import java.awt.*; + +import org.ibex.net.SSL; +import org.ibex.crypto.*; + +public class SwingVerifyCallback extends JDialog implements SSL.VerifyCallback { + private Component owner; + + public SwingVerifyCallback(Component owner) { + this.owner = owner; + } + /* + super(owner,"Certificate Verification",true); + setModal(true); + + JTextPane tp = new JTextPane(); + doc = tp.getStyledDocument(); + JScrollPane sp = new JScrollPane(); + sp.setPreferredSize(new Dimension(400,300)); + sp.setViewportView(tp); + sp.setAutoscrolls(false); + + this.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); + JComponent bottom = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton accept = new JButton("Accept"); + JButton reject = new JButton("Reject"); + accept.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { + accepted = true; + hide(); + }}); + reject.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { + accepted = false; + hide(); + }}); + bottom.add(accept); + bottom.add(reject); + getContentPane().add(BorderLayout.CENTER,sp); + getContentPane().add(BorderLayout.SOUTH,bottom); + pack(); + }*/ + + public static String prettyFingerprint(byte[] fp) { + StringBuffer sb = new StringBuffer(fp.length*3); + for(int i=0;i0) sb.append(":"); + sb.append("0123456789abcdef".charAt((fp[i] & 0xf0) >>> 4)); + sb.append("0123456789abcdef".charAt((fp[i] & 0x0f) >>> 0)); + } + return sb.toString(); + } + + public synchronized boolean checkCerts(X509.Certificate[] certs, String hostname, SSL.Exn exn) { + final boolean[] ret = new boolean[1]; + JTextArea ta = new JTextArea(); + ta.append("Subject: " + certs[0].subject + "\n"); + ta.append("Issuer: " + certs[0].issuer + "\n"); + ta.append("Start Date: " + certs[0].startDate + "\n"); + ta.append("End Date: " + certs[0].endDate + "\n"); + ta.append("MD5: " + prettyFingerprint(certs[0].getMD5Fingerprint()) + "\n"); + ta.append("SHA1: " + prettyFingerprint(certs[0].getSHA1Fingerprint()) + "\n"); + ta.setEditable(false); + ta.setOpaque(false); + JScrollPane sp = new JScrollPane(ta); + sp.setPreferredSize(new Dimension(300,150)); + final Object[] messages = new Object[] { + "The SSL Certificate the server presented could not be verified.", + exn.getMessage(), + sp, + }; + Runnable r = new Runnable() { public void run() { + int n = JOptionPane.showOptionDialog( + owner, + messages, + "Confirm Server Certificate", + 0, + JOptionPane.WARNING_MESSAGE, + null, + new Object[] { "Accept", "Reject" }, + "Accept"); + ret[0] = n == 0; + + } }; + if(SwingUtilities.isEventDispatchThread()) { + r.run(); + } else { + try { + SwingUtilities.invokeAndWait(r); + } catch(Exception e) { + e.printStackTrace(); + } + } + return ret[0]; + } + +} diff --git a/src/org/ibex/net/ssl/Test.java b/src/org/ibex/net/ssl/Test.java new file mode 100644 index 0000000..aa43ccb --- /dev/null +++ b/src/org/ibex/net/ssl/Test.java @@ -0,0 +1,32 @@ +package org.ibex.net.ssl; + +import org.ibex.net.SSL; +import java.io.*; + +public class Test { + public static void main(String[] args) throws Exception { + SSL.debugOn = true; + if(args.length < 2) { System.err.println("Usage: SSL host port"); } + String host = args[0]; + int port = Integer.parseInt(args[1]); + SSL ssl = new SSL(host,port); + //ssl.setTLS(false); + ssl.getOutputStream().write(SSL.getBytes("GET / HTTP/1.0\r\nHost: " + host + "\r\n\r\n")); + cat(ssl.getInputStream()); + ssl.close(); + + // try to resume + ssl = new SSL(host,port,ssl.getSessionState()); + ssl.getOutputStream().write(SSL.getBytes("GET / HTTP/1.0\r\nHost: " + host + "\r\n\r\n")); + cat(ssl.getInputStream()); + ssl.close(); + } + private static void cat(InputStream is) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String line; + int count = 100; + try { + while((line = br.readLine()) != null && --count >= 0) System.out.println(line); + } catch(SSL.PrematureCloseExn e) { /* ignore */ } + } +} diff --git a/src/org/ibex/net/ssl/rootcerts.dat b/src/org/ibex/net/ssl/rootcerts.dat new file mode 100644 index 0000000..c97bfab Binary files /dev/null and b/src/org/ibex/net/ssl/rootcerts.dat differ