initial import
authoradam <adam@megacz.com>
Tue, 16 Mar 2004 03:44:55 +0000 (03:44 +0000)
committeradam <adam@megacz.com>
Tue, 16 Mar 2004 03:44:55 +0000 (03:44 +0000)
darcs-hash:20040316034455-5007d-e1b2e40ab2996883a86457a312792dd0991995c6.gz

18 files changed:
Makefile [new file with mode: 0644]
doc/COPYING [new file with mode: 0644]
src/org/ibex/crypto/DER.java [new file with mode: 0644]
src/org/ibex/crypto/Digest.java [new file with mode: 0644]
src/org/ibex/crypto/HMAC.java [new file with mode: 0644]
src/org/ibex/crypto/MD2.java [new file with mode: 0644]
src/org/ibex/crypto/MD5.java [new file with mode: 0644]
src/org/ibex/crypto/PKCS1.java [new file with mode: 0644]
src/org/ibex/crypto/RC4.java [new file with mode: 0644]
src/org/ibex/crypto/RSA.java [new file with mode: 0644]
src/org/ibex/crypto/SHA1.java [new file with mode: 0644]
src/org/ibex/crypto/X509.java [new file with mode: 0644]
src/org/ibex/net/SSL.java [new file with mode: 0644]
src/org/ibex/net/ssl/GenCompactCAList.java [new file with mode: 0644]
src/org/ibex/net/ssl/RootCerts.java [new file with mode: 0644]
src/org/ibex/net/ssl/SwingVerifyCallback.java [new file with mode: 0644]
src/org/ibex/net/ssl/Test.java [new file with mode: 0644]
src/org/ibex/net/ssl/rootcerts.dat [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..71f3e48
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,52 @@
+JAVAC = javac
+sources = $(shell find src -name '*.java')
+classes = $(sources:src/%.java=build/%.class)
+dats = com/brian_web/net/ssl/rootcerts.dat
+
+jar_sources = \
+       $(shell find src/com/brian_web/{der,x509,crypto} -name '*.java') \
+       src/com/brian_web/net/SSL.java \
+       src/com/brian_web/net/ssl/RootCerts.java
+jar_classes = $(jar_sources:src/%.java=build/%.class)
+jar = BriSSL.jar
+
+all: $(classes) $(dats:%=build/%)
+
+$(classes): $(sources)
+       @mkdir -p build
+       $(JAVAC) -d build $(sources)
+
+build/%.dat: src/%.dat
+       @mkdir -p `dirname $@`
+       cp $^ $@
+               
+$(jar): $(classes) 
+       cd build && jar cf ../$@  $(jar_classes:build/%.class=%*.class)
+       
+test: all
+       java -cp build com.brian_web.net.ssl.Test www.paypal.com 443
+
+clean: 
+       rm -rf build/*
+
+# This stuff is only for Brian to use 
+# We should probably verify this file somehow
+tmp/.havecacertrs:
+       @mkdir -p tmp
+       wget -O - http://ftp.debian.org/debian/pool/main/c/ca-certificates/ca-certificates_20020323.tar.gz | gzip -dc | tar -C tmp -xf-
+       cd tmp/ca-certificates/mozilla && \
+               make all \
+               for f in *.pem; do \
+                       openssl x509 -in "$$f" -out "$$f.der" -outform der; \
+               done
+       touch $@
+
+update-rootcerts: tmp/.havecacerts src/com/brian_web/net/ssl/GenCompactCAList.java
+       java -cp build com.brian_web.net.ssl.GenCompactCAList binary tmp/ca-certificates/mozilla/*.der > src/com/brian_web/net/ssl/rootcerts.dat        
+       java -cp build com.brian_web.net.ssl.GenCompactCAList class tmp/ca-certificates/mozilla/*.der > src/com/brian_web/net/ssl/RootCerts.java
+
+sizecheck:
+       @for c in $(jar_classes); do \
+               for f in `echo $$c|sed 's,\.class$$,,;'`*.class; do gzip -c $$f; done | wc -c | tr -d '\n'; \
+               echo -e "\t`echo $$c | sed 's,build/com/brian_web,,;s,\.class$$,,;s,/,.,g;'`"; \
+       done | sort -rn | awk '{ sum += $$1; print }  END { print sum,"Total"; }'
diff --git a/doc/COPYING b/doc/COPYING
new file mode 100644 (file)
index 0000000..6ecac4b
--- /dev/null
@@ -0,0 +1,544 @@
+src/com/brian_web/*
+-------------------
+src/com/brian_web/net/SSL.java is Copyright 2004 Brian Alliet and
+Copyright 2003 Adam Megacz. It is released under the GNU Lesser
+General Public License with the exception of the portion of clause 6a
+after the semicolon (aka the "obnoxious relink clause")
+
+src/com/brian_web/{x509,der} are Copyright 2004 Brian Alliet and
+Copyright 2000 The Legion Of The Bouncy Castle 
+(http://www.bouncycastle.org) and is released under the Bouncy
+Castle License below.
+
+The rest of src/com/brian_web is Copyright 2004 Brian Alliet
+are released under the GNU Lesser General Public License (below).
+
+src/org/bouncycastle/*
+----------------------
+src/org/bouncycastle is Copyright 2000 The Legion Of The Bouncy Castle
+(http://www.bouncycastle.org) and is released under the following license:
+
+-- Bouncy Castle License --
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+-- End --
+
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                           NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/src/org/ibex/crypto/DER.java b/src/org/ibex/crypto/DER.java
new file mode 100644 (file)
index 0000000..4fba365
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * org.ibex.der.* - By Brian Alliet
+ * Copyright (C) 2004 Brian Alliet
+ * 
+ * Based on Bouncy Castle by The Legion Of The Bouncy Castle
+ * Copyright (c) 2000 The Legion Of The Bouncy Castle 
+ * (http://www.bouncycastle.org)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+package org.ibex.der;
+import java.io.IOException;
+import java.io.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.math.BigInteger;
+
+public class DER {
+    public class Null {
+        final static Null instance = new Null();
+        private Null() { /* noop */ }
+        public boolean equals(Object o) { return o == this; }
+    }
+
+    public class TaggedObject {
+        public final Object object;
+        public final int tag;
+        public TaggedObject(int tag, Object object) { this.tag = tag; this.object = object; }
+    }
+
+    public class UnknownObject {
+        public final byte[] data;
+        public final int tag;
+        public UnknownObject(int tag,byte[] data) { this.tag = tag; this.data = data; }
+    }
+
+    public class BitString {
+        public final int paddingBits;
+        public final byte[] data;
+    
+        public BitString(int paddingBits, byte[] data) {
+            this.paddingBits = paddingBits;
+            this.data = data;
+        }
+    }
+
+    public class Exception extends java.io.IOException {
+        public Exception(String s) { super(s); }
+    }
+
+    public class InputStream extends FilterInputStream {
+        private static final int MAX_OBJECT_SIZE = 4*1024*1024;
+    
+        private int limit;
+        public int bytesLeft() { return limit < 0 ? Integer.MAX_VALUE : limit-pos; }
+        private int pos;
+        public int getPos() { return pos; }
+    
+        public InputStream(InputStream is) { this(is,-1); } 
+        public InputStream(InputStream is, int limit) {
+            super(is);
+            this.limit = limit;
+        }
+    
+        public int read() throws IOException {
+            if(limit >= 0 && pos >= limit) return -1;
+            int n = super.read();
+            if(n != -1) {
+                pos++;
+            }
+            return n;
+        }
+    
+        public int read(byte[] buf, int start, int len) throws IOException {
+            if(limit >= 0) {
+                if(pos >= limit) return -1;
+                len = Math.min(len,limit-pos);
+            }
+            int n = super.read(buf,start,len);
+            if(n != -1) {
+                pos += n;
+            }
+            return n;
+        }
+    
+        protected void readFully(byte[] buf) throws IOException {
+            int pos = 0;
+            int left = buf.length;
+            while(left > 0) {
+                int n = read(buf,pos,left);
+                if(n == -1) throw new EOFException();
+                pos += n;
+                left -=n;
+            }
+        }
+    
+        protected int readByte() throws IOException {
+            int n = read();
+            if(n == -1) throw new EOFException();
+            return n;
+        }
+    
+        // From bouncycastle
+        private int readLength() throws IOException {
+            int length = read();
+            if (length < 0) throw new IOException("EOF found when length expected");
+            if (length == 0x80) return -1;      // indefinite-length encoding
+            if (length > 127) {
+                int size = length & 0x7f;
+                length = 0;
+                for (int i = 0; i < size; i++) {
+                    int next = read();
+                    if (next < 0) throw new IOException("EOF found reading length");    
+                    length = (length << 8) + next;
+                }
+            }
+            return length;
+        }
+        
+        public InputStream getSequenceStream() throws IOException {
+            int tag = readByte();
+            int length = readLength();
+            if(length < 0) throw new Exception("Indefinite length objects not supported");
+            if(tag != (CONSTRUCTED|0x10)) throw new Exception("Constructed Sequence expected");
+            return new InputStream(this,length);
+        }
+    
+        private static final int CONSTRUCTED = 0x20;    
+        public Object readObject() throws IOException {
+            int tag = readByte();        
+            int length = readLength();
+            if(length < 0) throw new Exception("Indefinite length objects not supported");
+            if(length > MAX_OBJECT_SIZE) throw new Exception("Object too large");
+        
+            switch(tag) {
+                case 0x01: return buildBoolean(length); // Boolean
+                case 0x02: return buildInteger(length); // Integer
+                case 0x03: return buildBitString(length); // Bit String
+                case 0x04: return buildOctetString(length); // Octet String
+                case 0x05: return Null.instance; // NULL
+                case 0x06: return buildObjectIdentifier(length); // Object Identifier
+                
+                case 0x13: // PrintableString
+                    // It is incorrect to treat this as an IA5String but the T.61 standard is way too old and backwards
+                    // to be worth supporting
+                case 0x14: // T.61 String 
+                case 0x16: // IA5String
+                    return buildIA5String(length);
+                case 0x17: return buildTime(length,false);// UTC Time
+                case 0x18: return buildTime(length,true); // Generalizd Time
+                
+                case CONSTRUCTED | 0x10: // Constructed Sequence
+                case CONSTRUCTED | 0x11: // Constructed Set
+                    { 
+                        return buildSequence(length);
+                    }
+                default: {
+                    if((tag & 0x80) != 0) {
+                        if ((tag & 0x1f) == 0x1f) throw new Exception("Unsupported high tag ecountered");
+                        // tagged object - bottom 5 bits are tag
+                        if(length == 0)
+                            return new TaggedObject(tag&0x1,((tag & CONSTRUCTED) == 0) ? (Object)Null.instance : (Object)new Vector());
+                        if((tag & CONSTRUCTED) == 0)
+                            return new TaggedObject(tag&0x1f,buildOctetString(length));
+                 
+                        InputStream dis = new InputStream(this,length);
+                        Object o = dis.readObject();
+                        if(dis.bytesLeft() == 0) return new TaggedObject(tag&0x1f,o);
+                        Vector v = new Vector();
+                        v.add(o);
+                        return buildSequence(dis,v);
+                    } else {
+                        return new UnknownObject(tag,readBytes(length));
+                    }
+                }     
+            }
+        }
+
+        protected Vector buildSequence(int length) throws IOException { return buildSequence(new InputStream(this,length),new Vector()); }
+        protected Vector buildSequence(InputStream dis,Vector v) throws IOException {
+            try {
+                for(;;) v.add(dis.readObject());
+            } catch(EOFException e) { 
+                return v; 
+            }
+        }    
+    
+        protected byte[] readBytes(int length) throws IOException {
+            byte[] buf = new byte[length];
+            readFully(buf);
+            return buf;
+        }
+    
+        protected BigInteger buildInteger(int length) throws IOException { return new BigInteger(readBytes(length)); }
+    
+        // From bouncycastle
+        protected String buildObjectIdentifier(int length) throws IOException {
+            byte[] bytes = readBytes(length);
+            StringBuffer    objId = new StringBuffer();
+            int             value = 0;
+            boolean         first = true;
+
+            for (int i = 0; i != bytes.length; i++)
+                {
+                    int b = bytes[i] & 0xff;
+
+                    value = value * 128 + (b & 0x7f);
+                    if ((b & 0x80) == 0)             // end of number reached
+                        {
+                            if (first)
+                                {
+                                    switch (value / 40)
+                                        {
+                                            case 0:
+                                                objId.append('0');
+                                                break;
+                                            case 1:
+                                                objId.append('1');
+                                                value -= 40;
+                                                break;
+                                            default:
+                                                objId.append('2');
+                                                value -= 80;
+                                        }
+                                    first = false;
+                                }
+
+                            objId.append('.');
+                            objId.append(Integer.toString(value));
+                            value = 0;
+                        }
+                }
+            return objId.toString();
+        }
+    
+        protected String buildIA5String(int length) throws IOException {
+            byte[] buf = readBytes(length);
+            char[] buf2 = new char[buf.length];
+            for(int i=0;i<buf.length;i++) buf2[i] = (char)(buf[i]&0xff);
+            return new String(buf2);
+        }
+    
+        private static final SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
+        protected Date buildTime(int length, boolean generalized) throws IOException {
+            String s = buildIA5String(length);
+            if(!generalized && s.length() > 0) {
+                if(s.charAt(0) < '5') s = "20" + s;
+                else s = "19" + s;
+            }
+            switch(s.length()) {
+                case 13: s = s.substring(0,12) + "00GMT+00:00"; break; //  YYYYMMDDhhmmZ
+                case 15: s = s.substring(0,14) + "GMT+00:00";   break; // YYYYMMDDhhmmssZ
+                case 17: s = s.substring(0,12) + "00GMT" + s.substring(12,15) + ":" + s.substring(15,17); break;  // YYYYMMDDhhmm+hh'mm'
+                case 19: s = s.substring(0,14) + "GMT" + s.substring(14, 17) + ":" + s.substring(17, 19); // YYYYMMDDhhmmss+hh'mm'
+                default: throw new Exception("Unknown time format " + s);
+            }
+            try {
+                return dateF.parse(s);
+            } catch(ParseException e) {
+                throw new Exception("Coudln't parse time: " + e.getMessage());
+            }
+        }
+    
+        protected BitString buildBitString(int length) throws IOException {
+            if(length < 1) throw new Exception("bit string too short");
+            int padding = read();
+            if(padding == -1) throw new IOException("unexpected eof");
+            return new BitString(padding,readBytes(length-1));
+        }
+    
+        protected byte[] buildOctetString(int length) throws IOException { return readBytes(length); }
+    
+        protected Boolean buildBoolean(int length) throws IOException {
+            byte[] bytes = readBytes(length);
+            return bytes[0] != 0 ? Boolean.TRUE : Boolean.FALSE;
+        }
+    
+        /*
+          public static void main(String[] args) throws Exception {
+          InputStream is = new InputStream(new FileInputStream(args[0]));
+          try {
+          for(;;) dump(is.readObject(),"");
+          } catch(EOFException e) {
+          System.err.println("EOF");
+          }
+          }
+          public static void dump(Object o, String indent) {
+          if(o instanceof Vector) {
+          Vector v = (Vector) o;
+          System.out.println(indent + "Sequence/Set");
+          for(int i=0;i<v.size();i++) {
+          dump(v.elementAt(i),indent + i + ":  ");
+          }
+          } else if(o instanceof TaggedObject){
+          dump(((TaggedObject)o).object,indent + "Tagged object: ");
+          } else if(o instanceof byte[]) {
+          System.err.println(indent + "<Byte Array>");
+          } else {
+          System.err.println(indent + o.toString());
+          }
+          }*/
+    }
+}
diff --git a/src/org/ibex/crypto/Digest.java b/src/org/ibex/crypto/Digest.java
new file mode 100644 (file)
index 0000000..15506fe
--- /dev/null
@@ -0,0 +1,133 @@
+/* Copyright (c) 2000 The Legion Of The Bouncy Castle 
+ * (http://www.bouncycastle.org)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package com.brian_web.crypto;
+
+/**
+ * base implementation of MD4 family style digest as outlined in
+ * "Handbook of Applied Cryptography", pages 344 - 347.
+ */
+abstract class Digest
+{
+    private byte[]  xBuf;
+    private int     xBufOff;
+
+    private long    byteCount;
+
+    /**
+     * Standard constructor
+     */
+    protected Digest()
+    {
+        xBuf = new byte[4];
+        xBufOff = 0;
+    }
+
+    public void update(
+        byte in)
+    {
+        xBuf[xBufOff++] = in;
+
+        if (xBufOff == xBuf.length)
+        {
+            processWord(xBuf, 0);
+            xBufOff = 0;
+        }
+
+        byteCount++;
+    }
+
+    public void update(
+        byte[]  in,
+        int     inOff,
+        int     len)
+    {
+        //
+        // fill the current word
+        //
+        while ((xBufOff != 0) && (len > 0))
+        {
+            update(in[inOff]);
+
+            inOff++;
+            len--;
+        }
+
+        //
+        // process whole words.
+        //
+        while (len > xBuf.length)
+        {
+            processWord(in, inOff);
+
+            inOff += xBuf.length;
+            len -= xBuf.length;
+            byteCount += xBuf.length;
+        }
+
+        //
+        // load in the remainder.
+        //
+        while (len > 0)
+        {
+            update(in[inOff]);
+
+            inOff++;
+            len--;
+        }
+    }
+
+    protected void finish()
+    {
+        long    bitLength = (byteCount << 3);
+
+        //
+        // add the pad bytes.
+        //
+        update((byte)128);
+
+        while (xBufOff != 0)
+        {
+            update((byte)0);
+        }
+
+        processLength(bitLength);
+
+        processBlock();
+    }
+
+    public void reset()
+    {
+        byteCount = 0;
+
+        xBufOff = 0;
+        for ( int i = 0; i < xBuf.length; i++ ) {
+            xBuf[i] = 0;
+        }
+    }
+
+    protected abstract void processWord(byte[] in, int inOff);
+
+    protected abstract void processLength(long bitLength);
+
+    protected abstract void processBlock();
+}
diff --git a/src/org/ibex/crypto/HMAC.java b/src/org/ibex/crypto/HMAC.java
new file mode 100644 (file)
index 0000000..f6ec2f8
--- /dev/null
@@ -0,0 +1,38 @@
+package com.brian_web.crypto;
+
+public class HMAC extends Digest {
+    private final Digest h;
+    private final byte[] digest;
+    private final byte[] k_ipad = new byte[64];
+    private final byte[] k_opad = new byte[64];
+    
+    public int getDigestSize() { return h.getDigestSize(); }
+    public HMAC(Digest h, byte[] key) {
+        this.h = h;
+        if(key.length > 64) {
+            h.reset();
+            h.update(key,0,key.length);
+            key = new byte[h.getDigestSize()];
+            h.doFinal(key,0);
+        }
+        digest = new byte[h.getDigestSize()];
+        for(int i=0;i<64;i++) {
+            byte b = i < key.length ? key[i] : 0;
+            k_ipad[i] = (byte)(b ^ 0x36);
+            k_opad[i] = (byte)(b ^ 0x5C);
+        }
+        reset();
+    }
+    public void reset() {
+        h.reset();
+        h.update(k_ipad,0,64);
+    }
+    public void update(byte[] b, int off, int len) { h.update(b,off,len); }
+    public void doFinal(byte[] out, int off){
+        h.doFinal(digest,0);
+        h.update(k_opad,0,64);
+        h.update(digest,0,digest.length);
+        h.doFinal(out,off);
+        reset();
+    }
+}
diff --git a/src/org/ibex/crypto/MD2.java b/src/org/ibex/crypto/MD2.java
new file mode 100644 (file)
index 0000000..7e5da64
--- /dev/null
@@ -0,0 +1,230 @@
+/* Copyright (c) 2000 The Legion Of The Bouncy Castle 
+ * (http://www.bouncycastle.org)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package com.brian_web.crypto;
+
+/* implementation of MD2
+ * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992
+ */
+public class MD2 implements Digest
+{
+    private static final int DIGEST_LENGTH = 16;
+
+    /* X buffer */
+    private byte[]   X = new byte[48];
+    private int     xOff;
+\r    /* M buffer */
+\r    private byte[]   M = new byte[16];
+    private int     mOff;
+\r    /* check sum */
+\r    private byte[]   C = new byte[16];
+    //private int COff;
+
+    public MD2()
+    {
+        reset();
+    }
+
+    /**
+     * return the size, in bytes, of the digest produced by this message digest.
+     *
+     * @return the size, in bytes, of the digest produced by this message digest.
+     */
+    public int getDigestSize()
+    {
+        return DIGEST_LENGTH;
+    }
+    /**
+     * close the digest, producing the final digest value. The doFinal
+     * call leaves the digest reset.
+     *
+     * @param out the array the digest is to be copied into.
+     * @param outOff the offset into the out array the digest is to start at.
+     */
+    public void doFinal(byte[] out, int outOff)
+    {
+        // add padding
+        byte paddingByte = (byte)(M.length-mOff);
+        for (int i=mOff;i<M.length;i++)
+        {
+            M[i] = paddingByte;
+        }
+        //do final check sum
+        processCheckSum(M);
+        // do final block process
+        processBlock(M);
+
+        processBlock(C);
+
+        System.arraycopy(X,xOff,out,outOff,16);
+
+        reset();
+    }
+    /**
+     * reset the digest back to it's initial state.
+     */
+    public void reset()
+    {
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+        mOff = 0;
+        for (int i = 0; i != M.length; i++)
+        {
+            M[i] = 0;
+        }
+        //COff = 0;
+        for (int i = 0; i != C.length; i++)
+        {
+            C[i] = 0;
+        }
+    }
+    /**
+     * update the message digest with a single byte.
+     *
+     * @param in the input byte to be entered.
+     */
+    public void update(byte in)
+    {
+        M[mOff++] = in;
+
+        if (mOff == 16)
+        {
+            processCheckSum(M);
+            processBlock(M);
+            mOff = 0;
+        }
+    }
+
+    /**
+     * update the message digest with a block of bytes.
+     *
+     * @param in the byte array containing the data.
+     * @param inOff the offset into the byte array where the data starts.
+     * @param len the length of the data.
+     */
+    public void update(byte[] in, int inOff, int len)
+    {
+        //
+        // fill the current word
+        //
+        while ((mOff != 0) && (len > 0))
+        {
+            update(in[inOff]);
+            inOff++;
+            len--;
+        }
+
+        //
+        // process whole words.
+        //
+        while (len > 16)
+        {
+            System.arraycopy(in,inOff,M,0,16);
+            processCheckSum(M);
+            processBlock(M);
+            len -= 16;
+            inOff += 16;
+        }
+
+        //
+        // load in the remainder.
+        //
+        while (len > 0)
+        {
+            update(in[inOff]);
+            inOff++;
+            len--;
+        }
+    }
+    protected void processCheckSum(byte[] m)
+    {
+        int L = C[15];
+        for (int i=0;i<16;i++)
+        {
+            C[i] ^= S[(m[i] ^ L) & 0xff];
+            L = C[i];
+        }
+    }
+    protected void processBlock(byte[] m)
+    {
+        for (int i=0;i<16;i++)
+        {
+            X[i+16] = m[i];
+            X[i+32] = (byte)(m[i] ^ X[i]);
+        }
+        // encrypt block
+        int t = 0;
+
+        for (int j=0;j<18;j++)
+        {
+            for (int k=0;k<48;k++)
+            {
+                t = X[k] ^= S[t];
+                t = t & 0xff;
+            }
+            t = (t + j)%256;
+        }
+     }
+     // 256-byte random permutation constructed from the digits of PI
+    private static final byte[] S = {
+      (byte)41,(byte)46,(byte)67,(byte)201,(byte)162,(byte)216,(byte)124,
+      (byte)1,(byte)61,(byte)54,(byte)84,(byte)161,(byte)236,(byte)240,
+      (byte)6,(byte)19,(byte)98,(byte)167,(byte)5,(byte)243,(byte)192,
+      (byte)199,(byte)115,(byte)140,(byte)152,(byte)147,(byte)43,(byte)217,
+      (byte)188,(byte)76,(byte)130,(byte)202,(byte)30,(byte)155,(byte)87,
+      (byte)60,(byte)253,(byte)212,(byte)224,(byte)22,(byte)103,(byte)66,
+      (byte)111,(byte)24,(byte)138,(byte)23,(byte)229,(byte)18,(byte)190,
+      (byte)78,(byte)196,(byte)214,(byte)218,(byte)158,(byte)222,(byte)73,
+      (byte)160,(byte)251,(byte)245,(byte)142,(byte)187,(byte)47,(byte)238,
+      (byte)122,(byte)169,(byte)104,(byte)121,(byte)145,(byte)21,(byte)178,
+      (byte)7,(byte)63,(byte)148,(byte)194,(byte)16,(byte)137,(byte)11,
+      (byte)34,(byte)95,(byte)33,(byte)128,(byte)127,(byte)93,(byte)154,
+      (byte)90,(byte)144,(byte)50,(byte)39,(byte)53,(byte)62,(byte)204,
+      (byte)231,(byte)191,(byte)247,(byte)151,(byte)3,(byte)255,(byte)25,
+      (byte)48,(byte)179,(byte)72,(byte)165,(byte)181,(byte)209,(byte)215,
+      (byte)94,(byte)146,(byte)42,(byte)172,(byte)86,(byte)170,(byte)198,
+      (byte)79,(byte)184,(byte)56,(byte)210,(byte)150,(byte)164,(byte)125,
+      (byte)182,(byte)118,(byte)252,(byte)107,(byte)226,(byte)156,(byte)116,
+      (byte)4,(byte)241,(byte)69,(byte)157,(byte)112,(byte)89,(byte)100,
+      (byte)113,(byte)135,(byte)32,(byte)134,(byte)91,(byte)207,(byte)101,
+      (byte)230,(byte)45,(byte)168,(byte)2,(byte)27,(byte)96,(byte)37,
+      (byte)173,(byte)174,(byte)176,(byte)185,(byte)246,(byte)28,(byte)70,
+      (byte)97,(byte)105,(byte)52,(byte)64,(byte)126,(byte)15,(byte)85,
+      (byte)71,(byte)163,(byte)35,(byte)221,(byte)81,(byte)175,(byte)58,
+      (byte)195,(byte)92,(byte)249,(byte)206,(byte)186,(byte)197,(byte)234,
+      (byte)38,(byte)44,(byte)83,(byte)13,(byte)110,(byte)133,(byte)40,
+      (byte)132, 9,(byte)211,(byte)223,(byte)205,(byte)244,(byte)65,
+      (byte)129,(byte)77,(byte)82,(byte)106,(byte)220,(byte)55,(byte)200,
+      (byte)108,(byte)193,(byte)171,(byte)250,(byte)36,(byte)225,(byte)123,
+      (byte)8,(byte)12,(byte)189,(byte)177,(byte)74,(byte)120,(byte)136,
+      (byte)149,(byte)139,(byte)227,(byte)99,(byte)232,(byte)109,(byte)233,
+      (byte)203,(byte)213,(byte)254,(byte)59,(byte)0,(byte)29,(byte)57,
+      (byte)242,(byte)239,(byte)183,(byte)14,(byte)102,(byte)88,(byte)208,
+      (byte)228,(byte)166,(byte)119,(byte)114,(byte)248,(byte)235,(byte)117,
+      (byte)75,(byte)10,(byte)49,(byte)68,(byte)80,(byte)180,(byte)143,
+      (byte)237,(byte)31,(byte)26,(byte)219,(byte)153,(byte)141,(byte)51,
+      (byte)159,(byte)17,(byte)131,(byte)20
+    };
+}
diff --git a/src/org/ibex/crypto/MD5.java b/src/org/ibex/crypto/MD5.java
new file mode 100644 (file)
index 0000000..12f1ffd
--- /dev/null
@@ -0,0 +1,267 @@
+/* Copyright (c) 2000 The Legion Of The Bouncy Castle 
+ * (http://www.bouncycastle.org)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package com.brian_web.crypto;
+
+/**
+ * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347.
+ */
+public class MD5
+    extends Digest
+{
+    private static final int    DIGEST_LENGTH = 16;
+
+    private int     H1, H2, H3, H4;         // IV's
+
+    private int[]   X = new int[16];
+    private int     xOff;
+
+    /**
+     * Standard constructor
+     */
+    public MD5()
+    {
+        reset();
+    }
+
+    public int getDigestSize()
+    {
+        return DIGEST_LENGTH;
+    }
+
+    protected void processWord(
+        byte[]  in,
+        int     inOff)
+    {
+        X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8)
+            | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); 
+
+        if (xOff == 16)
+        {
+            processBlock();
+        }
+    }
+
+    protected void processLength(
+        long    bitLength)
+    {
+        if (xOff > 14)
+        {
+            processBlock();
+        }
+
+        X[14] = (int)(bitLength & 0xffffffff);
+        X[15] = (int)(bitLength >>> 32);
+    }
+
+    private void unpackWord(
+        int     word,
+        byte[]  out,
+        int     outOff)
+    {
+        out[outOff]     = (byte)word;
+        out[outOff + 1] = (byte)(word >>> 8);
+        out[outOff + 2] = (byte)(word >>> 16);
+        out[outOff + 3] = (byte)(word >>> 24);
+    }
+
+    public void doFinal(
+        byte[]  out,
+        int     outOff)
+    {
+        finish();
+
+        unpackWord(H1, out, outOff);
+        unpackWord(H2, out, outOff + 4);
+        unpackWord(H3, out, outOff + 8);
+        unpackWord(H4, out, outOff + 12);
+
+        reset();
+    }
+
+    /**
+     * reset the chaining variables to the IV values.
+     */
+    public void reset()
+    {
+        super.reset();
+
+        H1 = 0x67452301;
+        H2 = 0xefcdab89;
+        H3 = 0x98badcfe;
+        H4 = 0x10325476;
+
+        xOff = 0;
+
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+
+    /*
+     * rotate int x left n bits.
+     */
+    private int rotateLeft(
+        int x,
+        int n)
+    {
+        return (x << n) | (x >>> (32 - n));
+    }
+
+    /*
+     * F, G, H and I are the basic MD5 functions.
+     */
+    private int F(
+        int u,
+        int v,
+        int w)
+    {
+        return (u & v) | (~u & w);
+    }
+
+    private int G(
+        int u,
+        int v,
+        int w)
+    {
+        return (u & w) | (v & ~w);
+    }
+
+    private int H(
+        int u,
+        int v,
+        int w)
+    {
+        return u ^ v ^ w;
+    }
+
+    private int K(
+        int u,
+        int v,
+        int w)
+    {
+        return v ^ (u | ~w);
+    }
+
+    protected void processBlock()
+    {
+        int a = H1;
+        int b = H2;
+        int c = H3;
+        int d = H4;
+
+        //
+        // Round 1 - F cycle, 16 times.
+        //
+        a = rotateLeft((a + F(b, c, d) + X[ 0] + 0xd76aa478), 7) + b;
+        d = rotateLeft((d + F(a, b, c) + X[ 1] + 0xe8c7b756), 12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[ 2] + 0x242070db), 17) + d;
+        b = rotateLeft((b + F(c, d, a) + X[ 3] + 0xc1bdceee), 22) + c;
+        a = rotateLeft((a + F(b, c, d) + X[ 4] + 0xf57c0faf), 7) + b;
+        d = rotateLeft((d + F(a, b, c) + X[ 5] + 0x4787c62a), 12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[ 6] + 0xa8304613), 17) + d;
+        b = rotateLeft((b + F(c, d, a) + X[ 7] + 0xfd469501), 22) + c;
+        a = rotateLeft((a + F(b, c, d) + X[ 8] + 0x698098d8), 7) + b;
+        d = rotateLeft((d + F(a, b, c) + X[ 9] + 0x8b44f7af), 12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[10] + 0xffff5bb1), 17) + d;
+        b = rotateLeft((b + F(c, d, a) + X[11] + 0x895cd7be), 22) + c;
+        a = rotateLeft((a + F(b, c, d) + X[12] + 0x6b901122), 7) + b;
+        d = rotateLeft((d + F(a, b, c) + X[13] + 0xfd987193), 12) + a;
+        c = rotateLeft((c + F(d, a, b) + X[14] + 0xa679438e), 17) + d;
+        b = rotateLeft((b + F(c, d, a) + X[15] + 0x49b40821), 22) + c;
+
+        //
+        // Round 2 - G cycle, 16 times.
+        //
+        a = rotateLeft((a + G(b, c, d) + X[ 1] + 0xf61e2562), 5) + b;
+        d = rotateLeft((d + G(a, b, c) + X[ 6] + 0xc040b340), 9) + a;
+        c = rotateLeft((c + G(d, a, b) + X[11] + 0x265e5a51), 14) + d;
+        b = rotateLeft((b + G(c, d, a) + X[ 0] + 0xe9b6c7aa), 20) + c;
+        a = rotateLeft((a + G(b, c, d) + X[ 5] + 0xd62f105d), 5) + b;
+        d = rotateLeft((d + G(a, b, c) + X[10] + 0x02441453), 9) + a;
+        c = rotateLeft((c + G(d, a, b) + X[15] + 0xd8a1e681), 14) + d;
+        b = rotateLeft((b + G(c, d, a) + X[ 4] + 0xe7d3fbc8), 20) + c;
+        a = rotateLeft((a + G(b, c, d) + X[ 9] + 0x21e1cde6), 5) + b;
+        d = rotateLeft((d + G(a, b, c) + X[14] + 0xc33707d6), 9) + a;
+        c = rotateLeft((c + G(d, a, b) + X[ 3] + 0xf4d50d87), 14) + d;
+        b = rotateLeft((b + G(c, d, a) + X[ 8] + 0x455a14ed), 20) + c;
+        a = rotateLeft((a + G(b, c, d) + X[13] + 0xa9e3e905), 5) + b;
+        d = rotateLeft((d + G(a, b, c) + X[ 2] + 0xfcefa3f8), 9) + a;
+        c = rotateLeft((c + G(d, a, b) + X[ 7] + 0x676f02d9), 14) + d;
+        b = rotateLeft((b + G(c, d, a) + X[12] + 0x8d2a4c8a), 20) + c;
+
+        //
+        // Round 3 - H cycle, 16 times.
+        //
+        a = rotateLeft((a + H(b, c, d) + X[ 5] + 0xfffa3942), 4) + b;
+        d = rotateLeft((d + H(a, b, c) + X[ 8] + 0x8771f681), 11) + a;
+        c = rotateLeft((c + H(d, a, b) + X[11] + 0x6d9d6122), 16) + d;
+        b = rotateLeft((b + H(c, d, a) + X[14] + 0xfde5380c), 23) + c;
+        a = rotateLeft((a + H(b, c, d) + X[ 1] + 0xa4beea44), 4) + b;
+        d = rotateLeft((d + H(a, b, c) + X[ 4] + 0x4bdecfa9), 11) + a;
+        c = rotateLeft((c + H(d, a, b) + X[ 7] + 0xf6bb4b60), 16) + d;
+        b = rotateLeft((b + H(c, d, a) + X[10] + 0xbebfbc70), 23) + c;
+        a = rotateLeft((a + H(b, c, d) + X[13] + 0x289b7ec6), 4) + b;
+        d = rotateLeft((d + H(a, b, c) + X[ 0] + 0xeaa127fa), 11) + a;
+        c = rotateLeft((c + H(d, a, b) + X[ 3] + 0xd4ef3085), 16) + d;
+        b = rotateLeft((b + H(c, d, a) + X[ 6] + 0x04881d05), 23) + c;
+        a = rotateLeft((a + H(b, c, d) + X[ 9] + 0xd9d4d039), 4) + b;
+        d = rotateLeft((d + H(a, b, c) + X[12] + 0xe6db99e5), 11) + a;
+        c = rotateLeft((c + H(d, a, b) + X[15] + 0x1fa27cf8), 16) + d;
+        b = rotateLeft((b + H(c, d, a) + X[ 2] + 0xc4ac5665), 23) + c;
+
+        //
+        // Round 4 - K cycle, 16 times.
+        //
+        a = rotateLeft((a + K(b, c, d) + X[ 0] + 0xf4292244), 6) + b;
+        d = rotateLeft((d + K(a, b, c) + X[ 7] + 0x432aff97), 10) + a;
+        c = rotateLeft((c + K(d, a, b) + X[14] + 0xab9423a7), 15) + d;
+        b = rotateLeft((b + K(c, d, a) + X[ 5] + 0xfc93a039), 21) + c;
+        a = rotateLeft((a + K(b, c, d) + X[12] + 0x655b59c3), 6) + b;
+        d = rotateLeft((d + K(a, b, c) + X[ 3] + 0x8f0ccc92), 10) + a;
+        c = rotateLeft((c + K(d, a, b) + X[10] + 0xffeff47d), 15) + d;
+        b = rotateLeft((b + K(c, d, a) + X[ 1] + 0x85845dd1), 21) + c;
+        a = rotateLeft((a + K(b, c, d) + X[ 8] + 0x6fa87e4f), 6) + b;
+        d = rotateLeft((d + K(a, b, c) + X[15] + 0xfe2ce6e0), 10) + a;
+        c = rotateLeft((c + K(d, a, b) + X[ 6] + 0xa3014314), 15) + d;
+        b = rotateLeft((b + K(c, d, a) + X[13] + 0x4e0811a1), 21) + c;
+        a = rotateLeft((a + K(b, c, d) + X[ 4] + 0xf7537e82), 6) + b;
+        d = rotateLeft((d + K(a, b, c) + X[11] + 0xbd3af235), 10) + a;
+        c = rotateLeft((c + K(d, a, b) + X[ 2] + 0x2ad7d2bb), 15) + d;
+        b = rotateLeft((b + K(c, d, a) + X[ 9] + 0xeb86d391), 21) + c;
+
+        H1 += a;
+        H2 += b;
+        H3 += c;
+        H4 += d;
+
+        //
+        // reset the offset and clean out the word buffer.
+        //
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+}
diff --git a/src/org/ibex/crypto/PKCS1.java b/src/org/ibex/crypto/PKCS1.java
new file mode 100644 (file)
index 0000000..f5d0759
--- /dev/null
@@ -0,0 +1,42 @@
+package com.brian_web.crypto;
+
+import java.security.SecureRandom;
+
+public class PKCS1 {
+    private final RSA rsa;
+    private final SecureRandom srand;
+    public PKCS1(RSA rsa) { this(rsa,new SecureRandom()); }
+    public PKCS1(RSA rsa,SecureRandom srand) { this.rsa = rsa; this.srand = srand; }
+    
+    public byte[] encode(byte[] in) {
+        int size = rsa.getInputBlockSize();
+        if(in.length > size -  11) throw new IllegalArgumentException("message too long");
+        byte[] buf = new byte[size];
+        byte[] rand = new byte[size - in.length - 2];
+        srand.nextBytes(rand);
+        for(int i=0;i<rand.length;i++) while(rand[i] == 0) rand[i] = (byte)srand.nextInt();
+        int p=0;
+        buf[p++] = 0x02;
+        System.arraycopy(rand,0,buf,p,rand.length);
+        p+=rand.length;
+        buf[p++]  = 0x0;
+        System.arraycopy(in,0,buf,p,in.length);
+
+        return rsa.process(buf);
+    }
+    
+    public byte[] decode(byte[] in) throws Exn {
+        byte[] buf = rsa.process(in);
+        if(buf.length < 10) throw new Exn("Data too short");
+        if(buf[0] != 2 && buf[0] != 1) throw new Exn("Data not in correct format " + (buf[0]&0xff));
+        int start = 9;
+        while(start < buf.length && buf[start] != 0) start++;
+        if(start == buf.length) throw new Exn("No null separator");
+        start++;
+        byte[] ret = new byte[buf.length - start];
+        System.arraycopy(buf,start,ret,0,ret.length);
+        return ret;
+    }
+    
+    public static class Exn extends Exception { public Exn(String s) { super(s); } }
+}
diff --git a/src/org/ibex/crypto/RC4.java b/src/org/ibex/crypto/RC4.java
new file mode 100644 (file)
index 0000000..6899ae1
--- /dev/null
@@ -0,0 +1,35 @@
+package com.brian_web.crypto;
+
+
+public class RC4 {
+    private final byte[] s = new byte[256];
+    private int x,y;
+    
+    public RC4(byte[] k) {
+        for(int i=0;i<256;i++) s[i] = (byte)i;
+        for(int i=0,j=0;i<256;i++) {
+            j = (j + (s[i]&0xff) + (k[i%k.length]&0xff))&0xff;
+            byte tmp = s[i];
+            s[i] = s[j];
+            s[j] = tmp;
+        }
+    }
+    
+    public void process(byte[] in, int ip, byte[] out, int op, int len) {
+        int x = this.x;
+        int y = this.y;
+        byte[] s = this.s;
+        for(int i=0;i<len;i++) {
+            x = (x + 1) & 0xff;
+            y = (y + (s[x]&0xff)) & 0xff;
+            byte tmp = s[x];
+            s[x] = s[y];
+            s[y] = tmp;
+            int t = ((s[x]&0xff) + (s[y]&0xff))&0xff;
+            int k = s[t];
+            out[op+i] = (byte)((in[ip+i]&0xff)^k);
+        }
+        this.x = x;
+        this.y = y;
+    }
+}
diff --git a/src/org/ibex/crypto/RSA.java b/src/org/ibex/crypto/RSA.java
new file mode 100644 (file)
index 0000000..31d613d
--- /dev/null
@@ -0,0 +1,51 @@
+package com.brian_web.crypto;
+
+import java.math.BigInteger;
+
+public class RSA {
+    private final BigInteger pq;
+    private final BigInteger e;
+    private final boolean reverse;
+    
+    public RSA(BigInteger pq, BigInteger e, boolean reverse) {
+        this.pq = pq;
+        this.e = e;
+        this.reverse = reverse;
+    }
+    
+    public int getInputBlockSize() { return (pq.bitLength()+7) / 8 - (reverse ? 0 : 1); }
+    public int getOutputBlockSize() { return (pq.bitLength()+7) / 8 - (reverse ? 1 : 0); }
+    
+    public byte[] process(byte[] in) {
+        // output block is the same size as the modulus (rounded up)
+        int outSize = getOutputBlockSize();
+        BigInteger t = new BigInteger(1,in);
+        BigInteger c = t.modPow(e,pq);
+        byte[] cbytes = c.toByteArray();
+        if(cbytes.length > outSize || (reverse && cbytes[0] == 0)) {
+            if(cbytes[0] != 0) throw new RuntimeException("should never happen");
+            byte[] buf = new byte[outSize];
+            System.arraycopy(cbytes,1,buf,0,outSize);
+            return buf;
+        } else if(!reverse && cbytes.length < outSize) {
+            // output needs to be exactly outSize in length
+            byte[] buf = new byte[outSize];
+            System.arraycopy(cbytes,0,buf,outSize-cbytes.length,cbytes.length);
+            return buf;
+        } else {
+            return cbytes;
+        }
+    }
+
+    public static class PublicKey {
+        public final BigInteger modulus;
+        public final BigInteger exponent;
+        
+        public PublicKey(Object o) {
+            Vector seq = (Vector) o;
+            modulus = (BigInteger) seq.elementAt(0);
+            exponent = (BigInteger) seq.elementAt(1);
+        }
+    }
+
+}
diff --git a/src/org/ibex/crypto/SHA1.java b/src/org/ibex/crypto/SHA1.java
new file mode 100644 (file)
index 0000000..e2ab369
--- /dev/null
@@ -0,0 +1,246 @@
+/* Copyright (c) 2000 The Legion Of The Bouncy Castle 
+ * (http://www.bouncycastle.org)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package com.brian_web.crypto;
+
+/**
+ * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349.
+ *
+ * It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5
+ * is the "endienness" of the word processing!
+ */
+public class SHA1
+    extends Digest
+{
+    private static final int    DIGEST_LENGTH = 20;
+
+    private int     H1, H2, H3, H4, H5;
+
+    private int[]   X = new int[80];
+    private int     xOff;
+
+    /**
+     * Standard constructor
+     */
+    public SHA1()
+    {
+        reset();
+    }
+
+    public int getDigestSize()
+    {
+        return DIGEST_LENGTH;
+    }
+
+    protected void processWord(
+        byte[]  in,
+        int     inOff)
+    {
+        X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16)
+                    | ((in[inOff + 2] & 0xff) << 8) | ((in[inOff + 3] & 0xff)); 
+
+        if (xOff == 16)
+        {
+            processBlock();
+        }
+    }
+
+    private void unpackWord(
+        int     word,
+        byte[]  out,
+        int     outOff)
+    {
+        out[outOff]     = (byte)(word >>> 24);
+        out[outOff + 1] = (byte)(word >>> 16);
+        out[outOff + 2] = (byte)(word >>> 8);
+        out[outOff + 3] = (byte)word;
+    }
+
+    protected void processLength(
+        long    bitLength)
+    {
+        if (xOff > 14)
+        {
+            processBlock();
+        }
+
+        X[14] = (int)(bitLength >>> 32);
+        X[15] = (int)(bitLength & 0xffffffff);
+    }
+
+    public void doFinal(
+        byte[]  out,
+        int     outOff)
+    {
+        finish();
+
+        unpackWord(H1, out, outOff);
+        unpackWord(H2, out, outOff + 4);
+        unpackWord(H3, out, outOff + 8);
+        unpackWord(H4, out, outOff + 12);
+        unpackWord(H5, out, outOff + 16);
+
+        reset();
+    }
+
+    /**
+     * reset the chaining variables
+     */
+    public void reset()
+    {
+        super.reset();
+
+        H1 = 0x67452301;
+        H2 = 0xefcdab89;
+        H3 = 0x98badcfe;
+        H4 = 0x10325476;
+        H5 = 0xc3d2e1f0;
+
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+
+    private int f(
+        int    u,
+        int    v,
+        int    w)
+    {
+        return ((u & v) | ((~u) & w));
+    }
+
+    private int h(
+        int    u,
+        int    v,
+        int    w)
+    {
+        return (u ^ v ^ w);
+    }
+
+    private int g(
+        int    u,
+        int    v,
+        int    w)
+    {
+        return ((u & v) | (u & w) | (v & w));
+    }
+
+    private int rotateLeft(
+        int    x,
+        int    n)
+    {
+        return (x << n) | (x >>> (32 - n));
+    }
+
+    protected void processBlock()
+    {
+        //
+        // expand 16 word block into 80 word block.
+        //
+        for (int i = 16; i <= 79; i++)
+        {
+            X[i] = rotateLeft((X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]), 1);
+        }
+
+        //
+        // set up working variables.
+        //
+        int     A = H1;
+        int     B = H2;
+        int     C = H3;
+        int     D = H4;
+        int     E = H5;
+
+        //
+        // round 1
+        //
+        for (int j = 0; j <= 19; j++)
+        {
+            int     t = rotateLeft(A, 5) + f(B, C, D) + E + X[j] + 0x5a827999;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        //
+        // round 2
+        //
+        for (int j = 20; j <= 39; j++)
+        {
+            int     t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + 0x6ed9eba1;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        //
+        // round 3
+        //
+        for (int j = 40; j <= 59; j++)
+        {
+            int     t = rotateLeft(A, 5) + g(B, C, D) + E + X[j] + 0x8f1bbcdc;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        //
+        // round 4
+        //
+        for (int j = 60; j <= 79; j++)
+        {
+            int     t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + 0xca62c1d6;
+
+            E = D;
+            D = C;
+            C = rotateLeft(B, 30);
+            B = A;
+            A = t;
+        }
+
+        H1 += A;
+        H2 += B;
+        H3 += C;
+        H4 += D;
+        H5 += E;
+
+        //
+        // reset the offset and clean out the word buffer.
+        //
+        xOff = 0;
+        for (int i = 0; i != X.length; i++)
+        {
+            X[i] = 0;
+        }
+    }
+}
diff --git a/src/org/ibex/crypto/X509.java b/src/org/ibex/crypto/X509.java
new file mode 100644 (file)
index 0000000..6fed220
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ * com.brian_web.x509.* - By Brian Alliet
+ * Copyright (C) 2004 Brian Alliet
+ * 
+ * Based on Bouncy Castle by The Legion Of The Bouncy Castle
+ * Copyright (c) 2000 The Legion Of The Bouncy Castle 
+ * (http://www.bouncycastle.org)
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a 
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation 
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
+ * and/or sell copies of the Software, and to permit persons to whom the 
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+package com.brian_web.x509;
+
+import com.brian_web.der.*;
+import com.brian_web.crypto.*;
+
+import java.io.*;
+import java.util.*;
+
+public class X509 {
+    public static class Certificate {
+        public static final String RSA_ENCRYPTION           = "1.2.840.113549.1.1.1";
+        public static final String MD2_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.2";
+        public static final String MD4_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.3";
+        public static final String MD5_WITH_RSA_ENCRYPTION  = "1.2.840.113549.1.1.4";
+        public static final String SHA1_WITH_RSA_ENCRYPTION = "1.2.840.113549.1.1.5";
+    
+        public static final String BASIC_CONSTRAINTS = "2.5.29.19";
+    
+        private final byte[] certBytes;
+        private final byte[] tbsCertBytes;
+    
+        public final Number version;    
+        public final Number serialNo;
+        public final X509Name issuer;
+        public final Date startDate;
+        public final Date endDate;
+        public final X509Name subject;
+    
+        public final AlgorithmIdentifier publicKeyAlgorithm;
+        public final DERBitString publicKey;
+    
+        public final Object issuerUniqueID;
+        public final Object subjectUniqueID;
+    
+        public final Vector extensions;
+    
+        public final DERBitString signature;
+        public final AlgorithmIdentifier signatureAlgorithm;
+    
+        public final BC basicContraints;
+
+    
+        public Certificate(InputStream is) throws IOException {
+            int i;
+            RecordingInputStream certIS = new RecordingInputStream(is);
+            DERInputStream certSequence = new DERInputStream(certIS).getSequenceStream();
+            RecordingInputStream tbsCertIS = new RecordingInputStream(certSequence);
+        
+            try {
+                Vector tbsSequence = (Vector) new DERInputStream(tbsCertIS).readObject();
+                tbsCertBytes = tbsCertIS.getBytes();
+                signatureAlgorithm = new AlgorithmIdentifier(certSequence.readObject());
+                signature = (DERBitString) certSequence.readObject();
+            
+                i=0;
+                if(tbsSequence.elementAt(i) instanceof DERTaggedObject)
+                    version = (Number)((DERTaggedObject)tbsSequence.elementAt(i++)).object;
+                else
+                    version = new Integer(0);
+            
+                serialNo = (Number) tbsSequence.elementAt(i++);
+                AlgorithmIdentifier signatureAlgorithm2 = new AlgorithmIdentifier(tbsSequence.elementAt(i++));
+                if(!signatureAlgorithm2.equals(signatureAlgorithm))
+                    throw new DERException("AlgoritmIdentifier mismatch " + signatureAlgorithm + " vs " + signatureAlgorithm2);
+                issuer = new X509Name(tbsSequence.elementAt(i++));
+            
+                Vector validity = (Vector) tbsSequence.elementAt(i++);
+                startDate = (Date) validity.elementAt(0);
+                endDate = (Date) validity.elementAt(1);
+            
+                subject = new X509Name(tbsSequence.elementAt(i++));
+            
+                Vector publicKeyInfo = (Vector) tbsSequence.elementAt(i++);
+                publicKeyAlgorithm = new AlgorithmIdentifier(publicKeyInfo.elementAt(0));
+                publicKey = (DERBitString) publicKeyInfo.elementAt(1);
+          
+                Object issuerUniqueID_=null,subjectUniqueID_=null;
+                Vector extensions_=null;
+                for(;i < tbsSequence.size();i++) {
+                    DERTaggedObject to = (DERTaggedObject) tbsSequence.elementAt(i);
+                    switch(to.tag) {
+                        case 1: issuerUniqueID_ = to.object; break;
+                        case 2: subjectUniqueID_ = to.object; break;
+                        case 3: extensions_ = (Vector) to.object; break;
+                    }
+                }
+                issuerUniqueID = issuerUniqueID_;
+                subjectUniqueID = subjectUniqueID_;
+                extensions = extensions_;
+            
+                BC bc = null;
+            
+                if(extensions != null) {
+                    for(Enumeration e = extensions.elements(); e.hasMoreElements(); ) {
+                        Vector extension = (Vector) e.nextElement();
+                        String oid = (String) extension.elementAt(0);
+                        byte[] data = (byte[]) extension.elementAt(extension.size()-1);
+                        if(oid.equals(BASIC_CONSTRAINTS))
+                            bc = new BC(new DERInputStream(new ByteArrayInputStream(data)).readObject());
+                    }
+                }
+                basicContraints = bc;
+            } catch(RuntimeException e) {
+                e.printStackTrace();
+                throw new DERException("Invalid x509 Certificate");
+            }
+            certBytes = certIS.getBytes();
+        }
+    
+    
+        public String getSubjectField(String fieldID) { return subject.get(fieldID); }
+        public String getCN() { return getSubjectField(X509Name.CN); }
+    
+        public boolean isValid() {
+            Date now = new Date();
+            return !now.after(endDate) && !now.before(startDate);
+        }
+    
+        public RSAPublicKey getRSAPublicKey() throws DERException {
+            if(!RSA_ENCRYPTION.equals(publicKeyAlgorithm.id)) throw new DERException("This isn't an RSA public key");
+            try {
+                return new RSAPublicKey(new DERInputStream(new ByteArrayInputStream(publicKey.data)).readObject());
+            } catch(IOException e) {
+                throw new DERException(e.getMessage());
+            } catch(RuntimeException e) {
+                throw new DERException("Invalid RSA Public Key " + e.getMessage());
+            }
+        }
+    
+        public boolean isSignedBy(Certificate signer) throws DERException {
+            return isSignedWith(signer.getRSAPublicKey());
+        }
+        public boolean isSignedWith(RSAPublicKey rsapk) throws DERException {
+            try {
+                Digest digest;
+                if(signatureAlgorithm.id.equals(MD5_WITH_RSA_ENCRYPTION)) digest = new MD5();
+                else if(signatureAlgorithm.id.equals(SHA1_WITH_RSA_ENCRYPTION)) digest = new SHA1();
+                else if(signatureAlgorithm.id.equals(MD2_WITH_RSA_ENCRYPTION)) digest = new MD2();
+                else throw new DERException("Unknown signing algorithm: " + signatureAlgorithm.id);
+                        
+                PKCS1 pkcs1 = new PKCS1(new RSA(rsapk.modulus,rsapk.exponent,true));
+                byte[] d = pkcs1.decode(signature.data);
+            
+                Vector v = (Vector) new DERInputStream(new ByteArrayInputStream(d)).readObject();
+                byte[] signedDigest = (byte[]) v.elementAt(1);
+                            
+                if(signedDigest.length != digest.getDigestSize()) return false;
+            
+                digest.update(tbsCertBytes,0,tbsCertBytes.length);
+                byte[] ourDigest = new byte[digest.getDigestSize()];
+                digest.doFinal(ourDigest,0);
+            
+                for(int i=0;i<digest.getDigestSize();i++) if(ourDigest[i] != signedDigest[i]) return false;
+                return true;
+            }
+            catch(RuntimeException e) { e.printStackTrace(); return false; }
+            catch(PKCS1.Exn e) { e.printStackTrace(); return false; }
+            catch(IOException e) { e.printStackTrace(); return false; }
+        }
+    
+        public byte[] getMD5Fingerprint() { return getFingerprint(new MD5()); }
+        public byte[] getSHA1Fingerprint() { return getFingerprint(new SHA1()); }
+        public byte[] getFingerprint(Digest h) {
+            h.update(certBytes,0,certBytes.length);
+            byte[] digest = new byte[h.getDigestSize()];
+            h.doFinal(digest,0);
+            return digest;
+        }
+    
+        private class RecordingInputStream extends FilterInputStream {
+            public ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            private boolean on = true;
+            public void on() { on = true; }
+            public void off() { on = false; }
+            public RecordingInputStream(InputStream is) { super(is); }
+            public int read() throws IOException {
+                int n = super.read();
+                if(n != -1 && on) baos.write(n);
+                return n;
+            }
+            public int read(byte[] buf, int off, int len) throws IOException {
+                int n = super.read(buf,off,len);
+                if(n != -1 && on) baos.write(buf,off,n);
+                return n;
+            }
+            public byte[] getBytes() { return baos.toByteArray(); }
+        }
+    
+        public static class BC {
+            public final boolean isCA;
+            public final Number pathLenConstraint;
+            BC(Object o) {
+                Vector seq = (Vector) o;
+                isCA = seq.size() > 0 ? ((Boolean) seq.elementAt(0)).booleanValue() : false;
+                pathLenConstraint = seq.size() > 1 ? (Number) seq.elementAt(1) : null;
+            }
+        }
+    
+        public static class AlgorithmIdentifier {
+            public final String id;
+            public final Object parameters;
+        
+            AlgorithmIdentifier(Object o) {
+                Vector seq = (Vector) o;
+                id = (String) seq.elementAt(0);
+                parameters = seq.elementAt(1);
+            }
+            public boolean equals(Object o_) {
+                if(o_ == this) return true;
+                if(!(o_ instanceof AlgorithmIdentifier)) return false;
+                AlgorithmIdentifier o = (AlgorithmIdentifier) o_;
+                return o.id.equals(id) && o.parameters.equals(parameters);
+            }
+            public int hashCode() { return id.hashCode() ^ parameters.hashCode(); }
+        }
+    
+        /*public static void main(String[] args) throws Exception {
+          Certificate cert = new Certificate(new FileInputStream(args[0]));
+          System.err.println("CN: " + cert.getCN());
+          System.err.println("Subject: " + cert.subject);
+          System.err.println("Issuer: " + cert.issuer);
+          System.err.println("Start Date: " + cert.startDate);
+          System.err.println("End Date: " + cert.endDate);
+          System.err.println("SHA1 Fingerprint: " + prettyBytes(cert.getSHA1Fingerprint()));
+          RSAPublicKey key = cert.getRSAPublicKey();
+          System.err.println("Modulus: " + prettyBytes(key.modulus.toByteArray()));
+          System.err.println("Exponent: " + key.exponent);
+          System.err.println("Signature: " + prettyBytes(cert.signature.data));
+          }
+    
+          public static String prettyBytes(byte[] fp) {
+          StringBuffer sb = new StringBuffer(fp.length*3);
+          for(int i=0;i<fp.length;i++) {
+          if(i>0) sb.append(":");
+          sb.append("0123456789abcdef".charAt((fp[i] & 0xf0) >>> 4));
+          sb.append("0123456789abcdef".charAt((fp[i] & 0x0f) >>> 0));
+          }
+          return sb.toString();
+          }*/
+    }
+
+    public static class Name {
+        // Some common OIDs
+        public static final String C = "2.5.4.6";
+        public static final String O = "2.5.4.10";
+        public static final String T = "2.5.4.12";
+        public static final String SN = "2.5.4.5";
+        public static final String L = "2.5.4.7";
+        public static final String ST = "2.5.4.8";
+        public static final String OU = "2.5.4.11";
+        public static final String CN = "2.5.4.3";
+        public static final String E = "1.2.840.113549.1.9.1";
+    
+        private final Vector keys = new Vector();
+        private final Vector values = new Vector();
+    
+        public Name(Object seq_) throws DERException {
+            try {
+                Vector seq = (Vector) seq_;
+                for(Enumeration e = seq.elements();e.hasMoreElements();) {
+                    Vector component = (Vector) ((Vector)e.nextElement()).elementAt(0);
+                    keys.add(component.elementAt(0));
+                    values.add(component.elementAt(1));
+                }
+            } catch(RuntimeException e) {
+                e.printStackTrace();
+                throw new DERException("Invalid Name " + e.toString());
+            }
+        }
+    
+        public boolean equals(Object o_) {
+            if(o_ instanceof String) return toString().equals(o_);
+            if(!(o_ instanceof Name)) return false;
+            Name o = (Name) o_;
+            if(keys.size() != o.keys.size()) return false;
+            int size = keys.size();
+            for(int i=0;i<size;i++) {
+                String oid = (String) keys.elementAt(i);
+                String oid2 = (String) o.keys.elementAt(i);
+                if(!oid.equals(oid2)) return false;
+            
+                String val1 = (String) values.elementAt(i);
+                String val2 = (String) o.values.elementAt(i);
+                if(val1.equals(val2)) continue;
+            
+                val1 = val1.trim().toLowerCase();
+                val2 = val2.trim().toLowerCase();
+                if(val1.equals(val2)) continue;
+            
+                val1 = removeExtraSpaces(val1);
+                val2 = removeExtraSpaces(val2);
+                if(val1.equals(val2)) continue;
+            
+                return false;
+            }
+            return true;
+        }
+    
+        public int hashCode() { return keys.hashCode() ^ values.hashCode(); }
+    
+        public String get(String fieldID) {
+            int i = keys.indexOf(fieldID);
+            return i == -1 ? null : (String)values.elementAt(i);
+        }
+    
+        public String[] getOIDs() {
+            String[] ret = new String[keys.size()];
+            keys.copyInto(ret);
+            return ret;
+        }
+    
+        public String[] getValues() {
+            String[] ret = new String[values.size()];
+            values.copyInto(ret);
+            return ret;
+        }
+    
+        private static String removeExtraSpaces(String s) {
+            if(s.indexOf(' ') == -1) return s;
+            StringBuffer sb = new StringBuffer(s.length());
+            int l = s.length();
+            boolean inWhitespace = false;
+            for(int i=0;i<l;i++) {
+                if(s.charAt(i) == ' ') {
+                    if(inWhitespace) continue;
+                    inWhitespace = true;
+                } else if(inWhitespace) {
+                    inWhitespace = false;
+                }
+                sb.append(s.charAt(i));
+            }
+            return sb.toString();
+        }
+    
+        private final static Hashtable oidMap = new Hashtable();
+        static {
+            oidMap.put(Name.C,"C");
+            oidMap.put(Name.O,"O");
+            oidMap.put(Name.T,"T");
+            oidMap.put(Name.SN,"SN");
+            oidMap.put(Name.L,"L");
+            oidMap.put(Name.ST,"ST");
+            oidMap.put(Name.OU,"OU");
+            oidMap.put(Name.CN,"CN");
+            oidMap.put(Name.E,"E");
+        }
+    
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            int size = keys.size();
+            for(int i=0;i<size;i++) {
+                if(sb.length() > 0) sb.append(",");
+                String fieldID = (String) keys.elementAt(i);
+                String fieldName = (String) oidMap.get(fieldID);
+                sb.append(fieldName != null ? fieldName : fieldID).append("=").append(values.elementAt(i));
+            }
+            return sb.toString();
+        }
+    }
+}
diff --git a/src/org/ibex/net/SSL.java b/src/org/ibex/net/SSL.java
new file mode 100644 (file)
index 0000000..7078e81
--- /dev/null
@@ -0,0 +1,1019 @@
+/*
+ * org.ibex.net.SSL - By Brian Alliet
+ * Copyright (C) 2004 Brian Alliet
+ * 
+ * Based on TinySSL by Adam Megacz
+ * Copyright (C) 2003 Adam Megacz <adam@xwt.org> 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.der.DER.Exception;
+import org.ibex.der.DER.InputStream;
+import org.ibex.x509.X509Certificate;
+import org.ibex.x509.RSAPublicKey;
+import org.ibex.x509.X509Name;
+import org.ibex.crypto.HMAC;
+import org.ibex.crypto.PKCS1;
+import org.ibex.crypto.RC4;
+import org.ibex.crypto.RSA;
+import org.ibex.crypto.Digest;
+import org.ibex.crypto.MD5;
+import org.ibex.crypto.SHA1;
+
+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<pad1.length; i++) pad1[i] = (byte)0x36;
+        for(int i=0; i<pad2.length; i++) pad2[i] = (byte)0x5C;
+        for(int i=0; i<pad1_sha.length; i++) pad1_sha[i] = (byte)0x36;
+        for(int i=0; i<pad2_sha.length; i++) pad2_sha[i] = (byte)0x5C;
+    }
+    
+    private final static Hashtable caKeys = new Hashtable();
+    private static VerifyCallback verifyCallback;
+    
+    //
+    // Constructors
+    //
+    public SSL(String host) throws IOException { this(host,443); }
+    public SSL(String host, int port) throws IOException { this(host,port,true); }
+    public SSL(String host, int port, boolean negotiate) throws IOException { this(host,port,negotiate,null); }
+    public SSL(String host, int port, State state) throws IOException { this(host,port,true,state); }
+    public SSL(String host, int port, boolean negotiate, State state) throws IOException {
+        super(host,port);
+        hostname = host;
+        rawIS = new DataInputStream(new BufferedInputStream(super.getInputStream()));
+        rawOS = new DataOutputStream(new BufferedOutputStream(super.getOutputStream()));
+        sslIS = new SSLInputStream();
+        sslOS = new SSLOutputStream();
+        if(negotiate) negotiate(state);
+    }
+
+    public synchronized void setTLS(boolean b) { if(negotiated!=0) throw new IllegalStateException("already negotiated"); tls = b; }
+    
+    public void negotiate() throws IOException { negotiate(null); }
+    public synchronized void negotiate(State state) throws IOException {
+        if(negotiated != 0) throw new IllegalStateException("already negotiated");
+        
+        handshakesBuffer = new ByteArrayOutputStream();
+        
+        try {
+            sendClientHello(state != null ? state.sessionID : null);
+            flush();
+            debug("sent ClientHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
+            
+            receiveServerHello();
+            debug("got ServerHello (" + (tls?"TLSv1.0":"SSLv3.0")+")");
+            
+            boolean resume = 
+                state != null && sessionID.length == state.sessionID.length && 
+                eq(state.sessionID,0,sessionID,0,sessionID.length);
+            
+            if(resume) 
+                negotiateResume(state);
+            else
+                negotiateNew();
+            
+            // we're done with these now
+            clientRandom = serverRandom = preMasterSecret = null;
+            handshakesBuffer = null;
+            
+            log("Negotiation with " + hostname + " complete (" + (tls?"TLSv1.0":"SSLv3.0")+")");
+        } finally {
+            if((negotiated & 3) != 3) {
+                negotiated = 0;
+                try { super.close(); } catch(IOException e) { /* ignore */ }
+                closed = true;
+            }
+        }
+    }
+    
+    private void negotiateResume(State state) throws IOException {
+        masterSecret = state.masterSecret;
+        
+        initCrypto();
+        log("initializec crypto");
+        
+        receiveChangeCipherSpec();
+        debug("Received ChangeCipherSpec");
+        negotiated |= 2;
+        receieveFinished();
+        debug("Received Finished");
+        
+        sendChangeCipherSpec();
+        debug("Sent ChangeCipherSpec");
+        negotiated |= 1;
+        sendFinished();
+        debug("Sent Finished");
+    }
+    
+    private void negotiateNew() throws IOException {
+        X509Certificate[] certs = receiveServerCertificates();
+        debug("got Certificate");
+        
+        boolean gotCertificateRequest = false;
+        OUTER: for(;;) {
+            byte[] buf = readHandshake();
+            switch(buf[0]) {
+            case 14: // ServerHelloDone
+                if(buf.length != 4) throw new Exn("ServerHelloDone contained trailing garbage");
+                debug("got ServerHelloDone");
+                break OUTER;
+            case 13: // CertificateRequest
+                debug("Got a CertificateRequest message but we don't suport client certificates");
+                gotCertificateRequest = true;
+                break;
+            default:
+                throw new Exn("unknown handshake type " + buf[0]);
+            }
+        }
+        
+        if(gotCertificateRequest)
+            sendHandshake((byte)11,new byte[3]); // send empty cert list
+        
+        try {
+            if(!hostname.equalsIgnoreCase(certs[0].getCN()))
+                throw new Exn("Certificate is for " + certs[0].getCN() + " not " + hostname);
+            verifyCerts(certs);
+        } catch(Exn e) {
+            if(verifyCallback == null) throw e;
+            synchronized(SSL.class) {
+                if(!verifyCallback.checkCerts(certs,hostname,e)) throw e;
+            }
+        }
+        
+        computeMasterSecret();
+        
+        sendClientKeyExchange(certs[0]);
+        debug("sent ClientKeyExchange");
+        
+        initCrypto();
+        
+        sendChangeCipherSpec();
+        debug("sent ChangeCipherSpec");
+        negotiated |= 1;
+        sendFinished();
+        debug("sent Finished");
+        flush();
+        
+        receiveChangeCipherSpec();
+        debug("got ChangeCipherSpec");
+        negotiated |= 2;
+        receieveFinished();
+        debug("got Finished");
+    }
+    
+    public State getSessionState() {
+        if((negotiated&3)!=3 || !closed || warnings != 0) return null;
+        return new State(sessionID,masterSecret);
+    }
+    public boolean isActive() { return !closed; }
+    public boolean isNegotiated() { return (negotiated&3) == 3; }
+    
+    private void sendClientHello(byte[] sessionID) throws IOException {
+        if(sessionID != null && sessionID.length > 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 X509Certificate[] 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-3;i+=((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0)) count++;
+        if(count == 0) throw new Exn("server didn't provide any certificates");
+        X509Certificate[] certs = new X509Certificate[count];
+        count = 0;
+        while(p < buf.length) {
+            int len = ((buf[p+0]&0xff)<<16)|((buf[p+1]&0xff)<<8)|((buf[p+2]&0xff)<<0);
+            p += 3;
+            if(p + len > buf.length) throw new Exn("Certificate message cut short");
+            certs[count++] = new X509Certificate(new ByteArrayInputStream(buf,p,len));
+            p += len;
+        }
+        return certs;
+    }
+    
+    private void sendClientKeyExchange(X509Certificate serverCert) throws IOException {
+        byte[] encryptedPreMasterSecret;
+        RSAPublicKey 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<crap.length; j++) crap[j] = (byte)(((byte)0x41) + ((byte)i));
+                keyMaterial = concat(new byte[][] { keyMaterial,
+                        md5(new byte[][] { masterSecret,
+                                sha1(new byte[][] { crap, masterSecret, serverRandom, clientRandom }) }) });
+            }            
+        }
+
+        byte[] clientWriteMACSecret = new byte[mac.length];
+        byte[] serverWriteMACSecret = new byte[mac.length];
+        byte[] clientWriteKey = new byte[16];
+        byte[] serverWriteKey = new byte[16];
+        
+        int p = 0;
+        System.arraycopy(keyMaterial, p, clientWriteMACSecret, 0, mac.length); p += mac.length;
+        System.arraycopy(keyMaterial, p, serverWriteMACSecret, 0, mac.length); p += mac.length;
+        System.arraycopy(keyMaterial, p, clientWriteKey, 0, 16); p += 16; 
+        System.arraycopy(keyMaterial, p, serverWriteKey, 0, 16); p += 16;
+        
+        Digest inner;
+        
+        writeRC4 = new RC4(clientWriteKey);
+        inner = sha ? (Digest)new SHA1() : (Digest)new MD5();
+        clientWriteMACDigest = tls ? (Digest) new HMAC(inner,clientWriteMACSecret) : (Digest)new SSLv3HMAC(inner,clientWriteMACSecret);
+        
+        readRC4 = new RC4(serverWriteKey);
+        inner = sha ? (Digest)new SHA1() : (Digest)new MD5();
+        serverWriteMACDigest = tls ? (Digest)new HMAC(inner,serverWriteMACSecret) : (Digest)new SSLv3HMAC(inner,serverWriteMACSecret);
+    }
+    
+    private void sendFinished() throws IOException {
+        byte[] handshakes = handshakesBuffer.toByteArray();
+        if(tls) {
+            sendHandshake((byte)20, tlsPRF(
+                    12,
+                    masterSecret,
+                    getBytes("client finished"),
+                    concat(md5(handshakes),sha1(handshakes))));
+            
+        } else {
+            sendHandshake((byte)20, concat(new byte[][] { 
+                    md5(new byte[][] { masterSecret, pad2, 
+                                       md5(new byte[][] { handshakes, new byte[] { (byte)0x43, (byte)0x4C, (byte)0x4E, (byte)0x54 },
+                                                          masterSecret, pad1 }) }),
+                    sha1(new byte[][] { masterSecret, pad2_sha,
+                                       sha1(new byte[][] { handshakes, new byte[] { (byte)0x43, (byte)0x4C, (byte)0x4E, (byte)0x54 },
+                                                          masterSecret, pad1_sha } ) })
+                }));
+        }
+    }
+        
+    private void receiveChangeCipherSpec() throws IOException {    
+        int size = readRecord((byte)20);
+        if(size == -1) throw new Exn("got eof when expecting a ChangeCipherSpec message");
+        if(size != 1 || readRecordBuf[0] != 0x01) throw new Exn("Invalid ChangeCipherSpec message");
+    }
+    
+    private void receieveFinished() throws IOException {
+        byte[] handshakes = handshakesBuffer.toByteArray();
+        byte[] buf = readHandshake();
+        if(buf[0] != 20) throw new Exn("expected a Finished message");
+        byte[] expected;
+        
+        if(tls) {
+            if(buf.length != 4 + 12) throw new Exn("Finished message too short");
+            expected = tlsPRF(
+                    12,masterSecret,
+                    getBytes("server finished"),
+                    concat(md5(handshakes),sha1(handshakes)));
+        } else {
+            if(buf.length != 4 + 16 +20) throw new Exn("Finished message too short");
+            expected = concat(new byte[][] {
+                    md5(new byte[][] { masterSecret, pad2,
+                            md5(new byte[][] { handshakes, new byte[] { (byte)0x53, (byte)0x52, (byte)0x56, (byte)0x52 },
+                                    masterSecret, pad1 }) }),
+                                    sha1(new byte[][] { masterSecret, pad2_sha,
+                                            sha1(new byte[][] { handshakes, new byte[] { (byte)0x53, (byte)0x52, (byte)0x56, (byte)0x52 },
+                                                    masterSecret, pad1_sha } ) } ) } );
+        }
+        if(!eq(expected,0,buf,4,expected.length)) throw new Exn("server finished message mismatch");
+    }
+    
+    private void flush() throws IOException { rawOS.flush(); }
+
+    private void sendHandshake(byte type, byte[] payload) throws IOException {
+        if(payload.length > (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<macLength;i++)
+                    if(mac[i] != readRecordBuf[len-macLength+i])
+                        throw new Exn("mac mismatch");
+                len -= macLength;
+                serverSequenceNumber++;
+            }
+            
+            if(proto == reqProto) return len;
+            
+            switch(proto) {
+                case 21: { // ALERT
+                    if(len != 2) throw new Exn("invalid lengh for alert");
+                    int level = readRecordBuf[0];
+                    int desc = readRecordBuf[1];
+                    if(level == 1) {
+                        if(desc == 0) { // CloseNotify
+                            debug("Server requested connection closure");
+                            try {
+                                sendCloseNotify();
+                            } catch(SocketException e) { /* incomplete close, thats ok */ }
+                            closed = true;
+                            super.close();
+                            return -1;
+                        } else {
+                            warnings++;
+                            log("SSL ALERT WARNING: desc: " + desc);
+                        }
+                    } else if(level == 2) {
+                        throw new Exn("SSL ALERT FATAL: desc: " +desc);
+                    } else {
+                        throw new Exn("invalid alert level");
+                    }
+                    break;
+                }
+                case 22: { // Handshake
+                    int type = readRecordBuf[0];
+                    int hslen = ((readRecordBuf[1]&0xff)<<16)|((readRecordBuf[2]&0xff)<<8)|((readRecordBuf[3]&0xff)<<0);
+                    if(hslen > 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<inputs.length; i++) masterMD5.update(inputs[i], 0, inputs[i].length);
+        byte[] ret = new byte[masterMD5.getDigestSize()];
+        masterMD5.doFinal(ret, 0);
+        return ret;
+    }
+    
+    private byte[] sha1(byte[] in)  { return sha1(new byte[][] { in }); }
+    private byte[] sha1(byte[][] inputs) {
+        masterSHA1.reset();
+        for(int i=0; i<inputs.length; i++) masterSHA1.update(inputs[i], 0, inputs[i].length);
+        byte[] ret = new byte[masterSHA1.getDigestSize()];
+        masterSHA1.doFinal(ret, 0);
+        return ret;
+    }
+    
+    /*  RFC-2246
+     PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR P_SHA-1(S2, label + seed);
+     L_S = length in bytes of secret;
+     L_S1 = L_S2 = ceil(L_S / 2);
+     
+     The secret is partitioned into two halves (with the possibility of
+     one shared byte) as described above, S1 taking the first L_S1 bytes
+     and S2 the last L_S2 bytes.
+     
+     P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
+     HMAC_hash(secret, A(2) + seed) +
+     HMAC_hash(secret, A(3) + seed) + ...
+     
+     A(0) = seed
+     A(i) = HMAC_hash(secret, A(i-1))
+     */           
+    private byte[] tlsPRF(int size,byte[] secret, byte[] label, byte[] seed) {
+        if(size > 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<size;i++) ret[i] = (byte)(md5out[i] ^ shaout[i]);
+        return ret;
+    }
+
+    public static class SSLv3HMAC implements Digest {
+        private final Digest h;
+        private final byte[] digest;
+        private final byte[] key;
+        private final int padSize;
+        
+        public int getDigestSize() { return h.getDigestSize(); }
+        
+        public SSLv3HMAC(Digest h, byte[] key) {
+            this.h = h;
+            this.key = key;
+            switch(h.getDigestSize()) {
+                case 16: padSize = 48; break;
+                case 20: padSize = 40; break;
+                default: throw new IllegalArgumentException("unsupported digest size");
+            }
+            digest = new byte[h.getDigestSize()];
+            reset();
+        }
+        public void reset() {
+            h.reset();
+            h.update(key,0,key.length);
+            h.update(pad1,0,padSize);
+        }
+        public void update(byte[] b, int off, int len) { h.update(b,off,len); }
+        public void doFinal(byte[] out, int off){
+            h.doFinal(digest,0);
+            h.update(key,0,key.length);
+            h.update(pad2,0,padSize);
+            h.update(digest,0,digest.length);
+            h.doFinal(out,off);
+            reset();
+        }
+    }
+    
+    //
+    // Static Methods
+    //
+    
+    private static SecureRandom random = new SecureRandom();
+    public static synchronized void randomBytes(byte[] buf, int off, int len) {
+        byte[] bytes =  new byte[len];
+        random.nextBytes(bytes);
+        System.arraycopy(bytes,0,buf,off,len);
+    }
+    
+    public static byte[] concat(byte[] a, byte[] b) { return concat(new byte[][] { a, b }); }
+    public static byte[] concat(byte[] a, byte[] b, byte[] c) { return concat(new byte[][] { a, b, c }); }
+    public static byte[] concat(byte[][] inputs) {
+        int total = 0;
+        for(int i=0; i<inputs.length; i++) total += inputs[i].length;
+        byte[] ret = new byte[total];
+        for(int i=0,pos=0; i<inputs.length;pos+=inputs[i].length,i++)
+            System.arraycopy(inputs[i], 0, ret, pos, inputs[i].length);
+        return ret;
+    }
+    
+    public static byte[] getBytes(String s) {
+        try {
+            return s.getBytes("US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            return null; // will never happen
+        }
+    }
+    
+    public static boolean eq(byte[] a, int aoff, byte[] b, int boff, int len){
+        for(int i=0;i<len;i++) if(a[aoff+i] != b[boff+i]) return false;
+        return true;
+    }
+    
+    //
+    // InputStream/OutputStream/Socket interfaces
+    //
+    public OutputStream getOutputStream() { return sslOS; }
+    public InputStream getInputStream() { return sslIS; }
+    public synchronized void close() throws IOException {
+        if(!closed) {
+            if(negotiated != 0) {
+                sendCloseNotify();
+                flush();
+                // don't bother sending a close_notify back to the server 
+                // this is an incomplete close which is allowed by the spec
+            }
+            super.close();
+            closed = true;
+        }
+    }
+    
+    private int read(byte[] buf, int off, int len) throws IOException {
+        if(pendingLength == 0) {
+            if(closed) return -1;
+            int readLen = readRecord((byte)23);
+            if(readLen == -1) return -1; // EOF
+            len = min(len,readLen);
+            System.arraycopy(readRecordBuf,0,buf,off,len);
+            if(readLen > 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(X509Certificate[] 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_(X509Certificate[] certs) throws DER.Exception, Exn {
+        boolean ignoreLast = false;
+        for(int i=0;i<certs.length;i++) {
+            debug("Cert " + i + ": " + certs[i].subject + " ok");
+            if(!certs[i].isValid())
+                throw new Exn("Certificate " + i + " in certificate chain is not valid (" + certs[i].startDate + " - " + certs[i].endDate + ")");
+            if(i != 0) {
+                X509Certificate.BC bc = certs[i].basicContraints;
+                if(bc == null) {
+                    if(i == certs.length - 1) {
+                        ignoreLast = true;
+                        break;
+                    }
+                    throw new Exn("CA-cert lacks Basic Constraints");
+                } else {
+                    if(!bc.isCA) throw new Exn("non-CA certificate used for signing");
+                    if(bc.pathLenConstraint != null && bc.pathLenConstraint.longValue() < i-1) throw new Exn("CA cert can't be used this deep");
+                }
+            }
+            if(i != certs.length - 1) {
+                if(!certs[i].issuer.equals(certs[i+1].subject))
+                    throw new Exn("Issuer for certificate " + i + " does not match next in chain");
+                if(!certs[i].isSignedBy(certs[i+1]))
+                    throw new Exn("Certificate " + i + " in chain is not signed by the next certificate");
+            }
+        }
+        
+        X509Certificate cert = certs[ignoreLast ? certs.length - 2 : certs.length-1];
+        
+        RSAPublicKey pks = (RSAPublicKey) caKeys.get(cert.issuer);
+        if(pks == null) throw new Exn("Certificate is signed by an unknown CA (" + cert.issuer + ")");
+        if(!cert.isSignedWith(pks)) throw new Exn("Certificate is not signed by its CA");
+        log("" + cert.subject + " is signed by " + cert.issuer);
+    }
+    
+    public static void addCACert(byte[] b) throws IOException { addCACert(new ByteArrayInputStream(b)); }
+    public static void addCACert(InputStream is) throws IOException { addCACert(new X509Certificate(is)); }
+    public static void addCACert(X509Certificate cert) throws DER.Exception { addCAKey(cert.subject,cert.getRSAPublicKey()); }
+    public static void addCAKey(X509Name subject, RSAPublicKey pks)  {
+        synchronized(caKeys) {
+            if(caKeys.get(subject) != null)
+                throw new IllegalArgumentException(subject.toString() + " already exists!");
+            caKeys.put(subject,pks);
+        }
+    }
+    
+    static {
+        try {
+            // This will force a <clinit> 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();
+                    X509Name subject = new X509Name(seq2.elementAt(0));
+                    RSAPublicKey pks = new RSAPublicKey(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(X509Certificate[] 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 (file)
index 0000000..51dd29e
--- /dev/null
@@ -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];
+        DEREncodableVector vec = new DEREncodableVector();
+        for(int i=1;i<args.length;i++) {
+            X509CertificateStructure x509 = new X509CertificateStructure((ASN1Sequence) new ASN1InputStream(new FileInputStream(args[i])).readObject());
+            X509Name subject = x509.getSubject();
+            SubjectPublicKeyInfo pki = x509.getSubjectPublicKeyInfo();
+            RSAPublicKeyStructure rsa = new RSAPublicKeyStructure((ASN1Sequence) pki.getPublicKey());
+            DEREncodableVector vec2 = new DEREncodableVector();
+            vec2.add(subject);
+            vec2.add(rsa);
+            vec.add(new DERSequence(vec2));
+        }
+        if(format.equals("binary")) {
+            DEROutputStream dos = new DEROutputStream(System.out);
+            dos.writeObject(new DERSequence(vec));
+            dos.close();
+        } else if(format.equals("class")){
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            DEROutputStream dos = new DEROutputStream(baos);
+            dos.writeObject(new DERSequence(vec));
+            dos.close();
+            baos.close();            
+            byte[] buf = baos.toByteArray();
+            StringBuffer sb = new StringBuffer();
+            for(int i=0;i<buf.length;i+=7) {
+                long l = 0;
+                for(int j=0;j<7;j++) {
+                    l <<= 8;
+                    byte b = (i+j < buf.length) ? buf[i+j] : -1;
+                    l |= (b & 0xffL);
+                }
+                for(int j=0;j<8;j++) {
+                    char c = (char) ((l>>>(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<len; i += 8) {\n" + 
+                    "            long l = 0;\n" + 
+                    "            for(int j=0;j<8;j++) {\n" + 
+                    "                l <<= 7;\n" + 
+                    "                l |= (s.charAt(i + j) & 0x7fL);\n" + 
+                    "            }\n" + 
+                    "            int base = (i / 8) * 7;\n" + 
+                    "            for(int j=6; j>=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 (file)
index 0000000..18438a0
--- /dev/null
@@ -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#`<U(08!\031K\002P@\016\0206\006F\036y\022;UX\rS\006V7Sv\007\034C3@1\"\r#_<f4\024,eA+\026)a\014`(\0034D$`\033\005 *J%C%\035V\032[~p6(t\021NIq>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<b\013\030\002 `\032T\010\006\011@JU\031DN0\022A@5(\020\024\023\017\020-V+IRc0[D\004+a`r2\\n2\002\r^m8\030-gI0@I7\030ec\011\030`$\003\000jP ,&\035 [,W\023%Fa7\010\010WCAde9\\d\005#\025Fh7\033mF{\035Re9L%s\0014\014\003*A\0001\031\031\002m2\\M\026\033\005\\ \"^\016\007\023\025fs\020\020lW\023QRf4Xl\027#\024@A:]\r\006{IRt<L\020\030H\n\003\001\0002>$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\\<f\007|[l\030=\011e[yZ\017\006\033\0313)dmln\010VdH\033\013\037\020\020\014\002\000\000L\020 \r\034a\001K\014!3\000$\014\003*A\000a\030\011*S\030If\002(\030\006U\002\002B1r\005Ze9\032,6\0138@E<\034\016&+Mf ![mW\003\005\\y\026\010\011\026s\014\\1\023\014\004@0\r*\004\005DcT\0135Jr4Xl\026q\001\nx8\034LW\033L@T2Xm\006s=Xo3Z,W\031Dl0\032\001@5(\020\006\023\026P-V+IRc0[D\004+a`r2\\n2\002\035Xo1\030-B\002\rJr:\032,fK\rBt2H\010\027+QPo9\032.GIB\004\001\005\000P \010\004\001p\022\011LbwoVJ9\\*8J\037\026&\036\024r)\"3\025BBm!_F\r/=\027JCvPbnWWC\000\0017jA6H)^qU\004\\f\0231m\n\034*&i3-(e14;\002u$\001@Q\013\005\011(# _\036RVs |\031q\0250Q47E\022I2e\027\036H\0350m\177\011\017kpz)t\033z\017J\023F\014iuW\002@2_?F\013>[\03393u\025vcgym\033R\021\007i\0315u\037D\ru$r/zb\034HS<kF\020'\00582\030f\n~\014*Z+.X\026 :9S*e^2\0020o;(=vw8<i\013Y)%\002g+e\026oD\"o6y@p\000\007_\032yIAZ3d&gW~\0251cVY\021ZZwd\005C=\034\006,-\030(ec\021\011Q9-~G{fY2Rh\033g7E\\*K7T@\026x+\017Xm<~(H\010}5P\020\014\002\000\000L\020 \013\\`g\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\013 `\032T\010\003\011IH&\0131hi6[n&)\001\006y1\031.%#Ijs:\010\0106{\021J )Z,vs%\\g\020\024Mv{Pa\002\000B@(\020\004\002\000d\0343!@J\034zm~3/b\006_Xy=\001\030u4\177~3MgMV$LPXl'l_!\034s\000<V-gO\nF}B=qD[\035'p'\020Ei\000\034zP*Fh_hQ6<\013V>g!\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\011K6<vj$E\024\005F+R\033i\014q\023\177@b]kF\\,dm\002\".\034g@`fa8dk2l6>xJ`]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<? Bce{^SB\034\016|DAy\"`/\\i\024V}\010D 2?PD9q|+6\032-+FS\001\026G#\n n5^Aj/`Ju\036\002/\026f\011\r\026\006)#ZF+UjEItG\0206\011^\000Z\"P\027XmBh$c^\004)%q\030$\035q\011@^0=>\"\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<w\037 \"@St9LS\n\032-rca:2C%((0e\016WVV%CbS)0rRg\\\017aM/_rR\011Kx!^U`px#x\001\026(\rKM8;\034QMtwk>\035KOEPD!\022\002=\026\003\005$W\027L\017uZL\005~-\033P:d\005T,F\023\006\037VJcp$\001Gjvd\006=<p^W/(\003W\027PCr\177~\025vZ7OIdlr:snu\0227gIH\013\027\023o\027\022#)Ocr$`B\\\003f%\033n\030uls[PcS..t1LwV@`cp<#.k\177r=\016.JPj:6!\\\023FG\020\020\014\002\000\000L\020 \rda\001T\014!3\000$\014\003*A\000a\030\011\006A\030Bf\000H\030\006U\002\002\0020\022=\0341\010\014\001`0\r*\004\003D`u#=do7\035\rs\010``\026\003\000jP (&\017!\030-fZ\025\\g4[LR\002%\\c\027\014%\023\001\034\014\003*A\0011\031\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\020\021\r\0273%fi7[F\021\031@\"\006\001U @\030L\024b0[M6+9Ni7\031&\022\001@<\006\004JPdD\033n\r\000B \0210EFa \030L\026s-Jn3Z-f)9Fo6L\020 \010(\005\002\000@ \r<\013PM \011:\177B\023\027bPuH,< .VmL\036u<\010B3^\034)U\0251EB\003yu\036_/ \023&#\nC{z<G\033f:ww*nP\030G\013\014\030(Ndo\033*I=%st}:\007^bvdReLm\036IMov95t\017Vhe\011D$B\011\025\017\0262\0275b\027,?53\r\023\024dL?p\016brlMIt+'\031q\031\023yj\016:\0353)M`/=,_\005#8V`C>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<?\\g2+#XD\016vgOY,\020\002`\021\000('p\014IGl*H\007\005_:)X\020{\010h5i`\022`D\025`,hYX\030~3?9\021)oPO$t5l!IPd>\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\011<E\026\011\020\013~b\0241;-'BF\0311E9G\032X\026\000o \003\037xb\002##(2Cyw\022_Vn{|&RN` \030\004\000\001\030 @\033IB\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\021A@,\006\001U @PL\036C2\\ND+9Ni7\031$\004K9F.\030J&\0028\030\006U\002\002b2\002\rJr:\032,fK\rBt4[mb\002\005jt4\033n&KQr \"\032.fKMRo7\014\"3\000D\014\003*A\0001\030)Fe9\035\014Vs\035Rn2L$\003\000x\014\011\025!I\0107\\\032\001\004@\"a\013\rB@1Y.'#\025\\g4[LRs\r^m\030 @\020P\n\004\001\000@\035Z&]\"\024PZ<Vo[_]\037]\006g*b7^\0320Lo\011Ig,Oi\r\001S\005O0.Zq~\003N$|G\010#\0317\025]\031?\035f%g$1a#zL\013D\021Zm\037\021$AxHHhhjQ:\030\021\007\020\035Fj[.6?\014gPu!i\010us\013\026%i]l#Fsd2Zc@ hw\021kD$pR\000-=3Hlj)\027\036L \032aL=\017)`\003\016HsNn\037S3.\000Sbx\001\010I2r1\004p\177C73(**mk3\0321\022*\001-G\021!,\\0FH\\P\013\002\n3\"?!_u\177s\037\014T O\030z\005\002\007\035`~('e\035o0rN`)\006b80bF\013\001Q\006*M!]\003\024UnP\r:d-_uymE=0R\005t\005u#1u8}YYB{q\010l5:u\021Yi%,{\023E\004sG\023s</\"\036\002\001@ \000\011B\003{\030\033&\020Y@\022\006\001U @0L\004D\"L#C\000h\014\003*A\001!\030M\010e:]\0166\033!J *\031-F+-^m\020\020(s\010t`\033\003\000jP ,&\024*\031-F*MJc\020\025\016'+Mh !Y-g#\025d1\020L\003p0\r*\004\001Dc\004#\025jt9Xm\006)\001(e6\031-6{4@R7[nB\002\r\0020@b (\014\004\001]\002K\037\020-Nr\022kZ.\037?),0\004\025bD\033nv\032N\033%u;\007/bzQ\r>qY\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\033E<XHFE\r*\030]>Z|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<Hh5W8\003@\025v<pH\003Iu\0167\030\022|9\0212\020\032 k>\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\031<c-=}r$O\177$\nO^B4\007LclJ,0\\b\023\035m)]^#\011vaCdBVh\031\035sd+8\0066)\035\001\000 3\004\010\003:\030 5\023\010,`\011\003\000jP \030&\002:\\f\020i@\026\006\001U @@L\010U:\030-\003\010\\`\025\003\000jP \034&\016)X-G!\001\030a5Y$\004\033%hy\030I\006\002\020\030\006U\002\002B1Z\021Rg4]\014\026a\001&i3[L\027#Ude\020\025\016'+Mh ![ec\010D`\017\003\000jP ,&\010\"\024jD\032\004@X\031\014\"c\000P\014\003*A\0001\0305\010S*\010\n&{=hC H\013\003\021DB0\017AA\022T\031\021\006{C \020H\004,\0221X(\006#%Ns4YnG\023Uft\027\030mviB\004\001\005\000P \010\004\001\\:|\021L\003V-\032`\030Cr7_\011aM:\\\0053M7\031f@(J@\"mUSvC  `s\022%\036\0248\016\013jw=\024\026G\007~5+c1y&t\r\036BL>d`^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[<NOz.Lk+\033(-\033T kPTB\r3\\[j\034*\026{\007\017kn\024\026\025\031PP)9\031s\r\036)\005m'hc\020]-ey:lchK~Cmf\035w>?\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\n<XA!3&E8bp#\"*uu\030T`8WF#E@eC1[\030%-0`5\014-gj>c\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<K\025\011<NIWtU\002A\\XKkU$\010Ug?cpz6fYfVm\022R%,o,o/dOdJYzc|J\000^y\r\017R\\\024\004\003\000@\000\023\004\010\002I\030 7#\010P`\022\003\000jP (&\013\"[NG\023Uft\027\033LW!D~0\036A@5(\020\026\024\033\035nw99Jn:\034NW\033P\\n2]\005u\032M\030_!T\n2\003%\\c7\\N\002q\001Dy\020\034LV18@(6\032-VKQf 6\032,\026\0218R1\022L\00400\r*\004\005DcBC\014R \031\014\006\003\001\001\nn:\034NW\033P\\n2]\004\004c%Zi:\031,C\011h`8\003\000jP \014&1\"[NG\023Uft\027\033LW!\001&e1].&)\001&e9\035LW\021\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\030 1\020\024\006\002\000cp+tsGcNx\030\020`~I0\177iLrrh^Du\030-2f!\030^haQ\025:CWL\0003M\nys\027\016\"M\\|m\036t\020\016\033U5\"*\032\002l^\177U\0173U.WF$\rDV\014}\013%Pa,HE-\032M\011d)N=+\0360v .\023rl|\034\001{N\033}\037\032\\@h\030\005Yjg\011Bs~B\\mH.@\006ms\030)Z7\035Cty^\031\002\011b  \030\004\000\001\030 @\034)B\0034\030E\006\001\020\030\006U\002\002B0Z\025\\t9\035.7!9\\e:\014(\003\001x\014\003*A\0011!]nw;KLVsQdu9]\005fs\025h/!T\n5yH`4\034\010\r\026s\r^r8\013D\006\023d@r2YEb\001!Xi6Z.G\031\001Xi0XEbIDJ0\021A@5(\020\026\023\016\n\0142I\000b9\034N$\004+9hr:\\nBs9Jt\020\023\r\026k%he2\014&3\001D\014\003*A\0001\031)\nn:\034NW\033P\\n2]\004\004\033\025dt4YM\026\033\005hi7[D\004\013Uhh7\\M\027#d@(\031\014\006CA$a\002\000B@(\020\004\002\000VS):HJ\r2u(d\0008T,d\025\ni=\r|\026J&c]X\003ZJg[^\010\014\003M\005H40\0335\031/:^\\\035S\003,N\026\032N_\0023P8>[[GY~)\020(\036X`5\030kvqL5R`?\010\010\031\\Ui(\002 \001~tJ(3PA&g\010\005}0w<u+Mr0om;)I+BE\020i*n.2(\033g\\EiL\014q\177\027[B'\002vDR#\037n\002Q=Dhlr~\032?<8F,\013A7R,dd\016f3n)bW^\032f*M:<k'\026,y$\007y\027i\n\013\026Q\021N\002LJXWvdd&\003\\I@\035k[?ww\020BUA\005\030\n?\027Z\"HiC8fSxz\014]=\036B[o<\001\016\nHJA8\002 &Io\001dK\"#?|\021z9lU\005A\007\023;vy\033\037C\014\010,&2\raVjp\000~\010\010\006\001\000\000&\010\020\005,0@r&\020Y@\022\006\001U @0L\004U)L\"C\000H\014\003*A\001!\030-\nn:\034NW\033P\\n2]\006\024AA\014\006\001U @XP~w;]ef+9hr:\\nBs9Jt\027PmFK\025\\t/Ph\025z%\\f7Kh5\002L@i7\030mw\023@\\ 1\036$\007\023\025L.\020\033\r\026k%hs\020\033\r\026\013\010\\1\022L\00400\r*\004\005DcBC\014R \030N'\023I\001\nn:\034NW\033P\\n2]\004\004c%Zi:\031,C\011L`1\003\000jP \014&*\"[NG\023Uft\027\033LW!\001\006l4Y-g!\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\030 0p\024\006\002\000d\016S\025qD/_V\011my\003oH\031\177Q43&\0137AT\023~\006G*\011}O]/\034~\006\024QJ+xu8\r\016dO+0!Zk4\177\033^\010{Y^0\022K3Xa\033w_R\006\014\n\032If<U|>v\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}<b`dj\035\013\020AQmOyr37D\024d]Q~P \\~##S8R8f\002&\006\025\016 l [Y\017fB)}rfqi`\003\023z\035L{F\r%\r\025d\025_\0226SM2As\001=\026Z3?_\004Jl\n2\"3\023#dL\033\007 \004+j4MY?\016o\014!\177~n\011\025FrA^A\016-8\rD,\0310n*K7\031\006\002\000@f\010\016t`O\030Bf\000H\030\006U\002\001B0\022U&1\010\014\001`0\r*\004\005\004`t+Eji3\030/\003\0118`,\003\000jP ,&%\"\\.VK\031Bx\020\024\016&+5Ru6H\0106+Ihi3\032,6\013QJ  ].FC=di:\036&\010\014$\005\001@@\031j\010\032\034\006f\002\001<\014\032Q^\0205!PvD\033DOzM-MO\0042#P\rxT\"H\011^N\005Ao\033\027fR<fn^kW<~\031\0260?X[<U3SqJ!PJ~=4$+x\022\020zcv4JpF\006\034zB8]`\027MHG.e~do!\">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<L\020\030H\n\003\001\0000+[\naN\0101;T\011Q4>\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<R\003\006e\0037be#\024q=XuH#=\177:|{#S\005\017\177A<}Lt\r?\0112\007pwK-Q\022,B\035vEf\011E5\026\022d`\022\r\017\024\010k\024\016LEFXM\013\\\0246 8\014U$\0066M\026/q\016\0239|\011s[P$CH\0344\036\027\030+\n\000H\027eRwxZ_(nqf\007\031\0039js\027))uL+(I\024''\017<~\001UVoH7P\020\014\002\000\000L\020 \025da\001T\014!3\000$\014\003*A\000a\030\011\006A\030Bf\000H\030\006U\002\002\0020\022=\0341\010\014\001`0\r*\004\003D`u#=do7\035\rs\010``\026\003\000jP (&\017#\033n'\"\025\\g4[LR\002%\\c\027\014%\023\001\034\014\003*A\0011\031\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\020\021\r\0273%fi7[F\021\031@\"\006\001U @\030L\024f7\\NF+9Ni7\031&\022\001@<\006\004JPdD\033n\r\000B \0210EFa \031Mw\023QJn3Z-f)9Fo6L\020 \020(\005\002\001\000 \014U{\014nN\000xqk?8F\rhi\001aoRb@\022\014Pu\006l{]o`\033,\032BP\025\034yzW&W\024\022Rn>\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<pN~|v\024\\\011\021sav\002g\03531O\026La],\007KW1\022b\016\017\007\033#D&FS;3l8\030\014;\022\006 \006R\0368mwy\004yva3,oAdG\007!W\\p\006=&\016\010YaH/G\011+K\n`WU\\WB\025[9869z0\007\003dQ\021\014,\001{\004=q\n\031\003lky\027\0307Of\013h\005\023J6\013^\000E\014T/\"({@\024pY\033\020>\035<+<n[\006d(~WC\027Z`\020\020)(\\\010\020\003z2L\032G_q\035G.G\031(%/V6&\003y\003z>\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<XLW\022Qdu9]\004\005\033=Xu:\032-vsLX $[L2qDF0\020A@5(\020\006\023\r\021jD)\001\006y1\031.%#Ijs:\010\010vc=Da6\010\n&{=h0@b (\014\004\001\025\007h\026o\002B9h=1q\014nt.\016\027,\022M\000lz\016{%\030\010TS\016\006d$\022|EaH\032=\037MCbOB7\024\034l\007}J.S\003gj?K51\024i+qVl!O\000s;6).=d1e)#G\025i8\002sY\010.?fR,k\016AZd5\005*\022\000'\0364p~\001BUaUD\035\013xia5)\033$M\0058\022\020\003xH+G<m\003p)<vbL@@0\010\000\0020@wf\005\011D\0260\004A@5(\020\014\023\001\022J\003\010|`\035\003\000jP (&\026!^,&+I(r:\\nB\002)Bp0[EB\002%\\c\027\014$\023\000|\014\003*A\0001\030a\006y1\031.%#Ijs:\010\011$\nA\002N\020\024Mv{P@C L\020\030H\n\003\001\000-u_f)\016\020;\"?V=3Hs\003\037\020\024Xw-5>\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<P)E3FXKa gn?'\037hA\0264\021,\036\033J#8?%|\014e&Sz+\177j\025J(zZ\022\032\005X4\023\032\0238T\014hWo?\013\005O`)ze+ HYBw\005\010D]\"=\013*co<Y6\177sTT#H,a\023\000\036HIi++4\r\0001N8J\022xp\003\001\000`\020\000\004a\002\000[f\005yD\0260\004A@5(\020\014\023\001\020HS\010d`\027\003\000jP (&\020#[\rv\023\005XS4Ymb\0039l-9X&\021!@$\006\001U @XL\026P0\\NFs\025ds\020\020h\023\010|`\035\003\000jP \014&\026#[\rv\023\005XS4Ymb\002ABr:\033LW\023L@C L\020 \010(\005\002\000@ \r\0213p2V\022B'Q_\020)Hi5hc8<\016`\r\0068G\024fq\013\007\017\010##\006\030\032\024\005a1zc\024A\023v\031T|\001xc\004&\\3%w\030\001+ \0004!Yg&\014e\000\004 NF\017av7\013UG<\031.q\rs\0300\016(\001R;'Zx\024\033L\007R\026|{\177:_[\020Pk\013\037Y\177\032\neL\020zDt`\036@\0019g%\002gTklM\"[X@h^i\r]IEgszl\004K\005mQ\002\000F$\000VnxrvSFEW=1\025Sr\033$Va,C\026\000O\014c<Q(\004J\014$+\0261W#\"E\003!,scjA\003\0067H\ro7w\035S\014xf,d?[\032/\033#rGU%,7\006\013oAw#\022rnM@\014!\026\ny\013;f)L\nBP\023\025#\013Z}}_`/\017KD8F/\013\"Y)4`YG\023\010\035\013\nV=\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@1\020\020h\023\011\030`$\003\000jP \014&\035#[\rv\023\005XS4Ymb\002Adi6X.'I\001\006l0\\n2\001D@C L\020 \010(\005\002\000@ \013i\000jGhJ\032`E\014$9\021Qa[k@BR\007\025f\031-\030hW5{\017d\033g\006\006)\022\031[eVC\031^v\003s\025s\037W7\034MRs\\E\020A\023I8|h\030\001!&[\027\002fm\035\017yL=\0102Z\035'>\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<X uJ\030\034 \017F[\032)s6{\"\013$\017w]4|Bt2\033dp1y\027}\007)S;}\021\020WF$\035hH`\026\002@F\027f?y&`D\011w*\033LhK-\025VMsL\013mL%K^b+N@~\022a\026\027/!c\030Sp]\013jVY3J\1778@?W*Ao&_k\004Ol3\003H%?]SB\006;\177FV\011\\Im#h\023E9\021)\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@2\020\020h\023\011\030`$\003\000jP \014&\035#[\rv\023\005XS4Ymb\002Adi6X.'I\001\006l0\\n2\001H@C L\020 \010(\005\002\000@ \011\0243}oz\0211a:\005]|l/d\026},\r\035)0%@\004t\177\016Bn\017tT>{ 1JY$I\036\\ZA=\016N\022V%\177a45'M\nmzjg0 \"?\r2c\027L:9juXw:q&|~m;\024\0208\002\027m\034H\r\001R-=\177/O\\v7SB<v\r\"\033JT\017{x\011mv\027\177\0348Cqt\004V_0NKSc\023\0222\025@\005\177w\007!L%!VO:Z\nnFpP\022(=u\002$t\\$\034~a\020.{K#2\022$\035\003!|q\030$6\026\007\000st(>\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<xj Q%y_\014{a\0119\022\027,{\037 \037\017\022(\013>\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<C9|H\036NUUcY\"'%1dpo\003\017\021\006JlTMbijKC2\023LM~'LW\027{\011zDGI\023b-Vn\035\ri\032\021CQFqhk\023T\035\006t\0061-+5YU<\005RZ\027'\020\n\034:~\" M\036s*$b8)h7<\017\r\026XX\026\0319;bv\035X|A\021UB*6tC\034{\033]\027\027\002\036\"\"(XbgM:c. IA\014@dWQ#*I\r3\020UWAM\037ra+\002l\014Pi <\027OS>=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)<gv\024}\027\017c\"fOji]=Q&;\023'.GT.=?{kf\020\031\014I}&7uQ>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_<ku\177+p\027[)`vGA\r\005_g5'\"\025bX'e\014,+\r9YLd\177)\037\024\032&\025\002&]RdFYG);\037PB\013%@Ej\016\010i\034Bh\030G@G7[5Ie0I(0N 3\023w\010eO\031:(OC~\0071\013\021r\0143\\(C+.7Co$794\0140`W\037QU4P~@\020A,\002\0176Mk:Qb\014g\034u|U\177kH~f(` \030\004\000\001\030 @\024YB\003<\030Bf\000H\030\006U\002\001B0\022\021\n1\010\014\001`0\r*\004\004\004`tC\005Zb:\\Ls\010@`\016\003\000jP \034&\007$\030-V\023Udg\030NF\003@\030\006U\002\002B3\nQ\006 *\034NW\033Q\006e7\035\014W\021\001Lo9\010\n6+\rjr4]\017\022\003%\\ \"\030.F\011\001\034e:\035mw\023-f #[,$ADD0\020\001@5(\020\026\023\014U\0102\002Qdu9]\0106+9he9\010\0106c\005fs\020\014$\004\032\004b)\030\011``I*\014HC=aP\010$\002\026\r\030lW\023QRf4Xl\027#\025\000t9\035.7#\rJn:\031.\"s\021J0@b (\014\004\0010\024zvG5N]W[\026vEw\036{cXg\011\020\"]7!Uv/q\030o{:0j{\016;G>-\000%\013U\n!z#w],($@x\n\017V\"\027!>\035\000R\177&\000\021tRRf\\C$I\0069<eTQdE\005G\026d\004\020S\003%.t\"p\013(6\033*=p\032\nwiET$FQ2sqE|h3\014Mb#7X2\0332(\034W@bDd(l\034Pn@6\017@@0\010\000\0020A\000)3\004\006x1\005L\001\0200\r*\004\003\004`$\"\024b\020\030\003@`\032T\010\010\011Ai\006\0135Du9\031f\021\001@\034\006\001U @8L\016H0[,'+IN1\035\014\007\0000\r*\004\005\004f\025\"\014@T9\035.7\"\rJn:\031.\"\003\031^r\020\024lV\033Udi:\036$\006K8@D0]\014\022\0029Jt;[n&[L@G6XI\003\011\010` \003\000jP ,&\031*\020d\005#Ijs:\020lVsQJr\020\020mF\013Mf \031\010\0104\011DR0\023AA\022T\031\021\006{C \020H\004,\0321Y.'#%Li1X.F*\001hr:\\nF\033\025\\t2\\Ef#\024a\001D@P\030\010\00348t;& \001%c\003\000C7x`\0079Zc+'\032%&\024/jbkFB}!F1=,kn*\"\"v*CU\016\\{3(lP4rw\026IX<\011E/\025V-ca!3\000r$NA5\031 \0277no_7\034\016\030Rp\005)|S/\"Z\026ZO!2=\rR\004VW\0237:B-.\026^?_\005D{p)Fl\017<kc\022OV\"lpgF9{~\025s5:q\036\r\001\000`\020\000\004a\002\000Rf\010\rpb\013\030\002 `\032T\010\006\011@HD)D 0\007\001@5(\020\020\023\003R\014\026k\011jr3L\"\003\0008\014\003*A\000q\030\035\020a6XNW\023\034b:\030\016\000`\032T\010\n\011L*D\031\001(r:\\nD\033\025\\t2\\D\0063=d )Y,7+IRt<H\r\026q\001\010a:\030$\004s\025hw7\\M7\031\001\016m1\022\006\022\021@@\006\001U @XL2T!H\nG\023Uft!Y-g#\025d ![\014\027\033L@3\020\020h\023\011$`'\003\002%(2\"\rw\006@!\020\010X4c2\\NFK\031Rc0]\014T\003Qdu9]\0146+9he9\013LF)B\003\011\001 0\020\005ZiA\032A%`l7Y  \032C@q\036LPI\032j\001X\037<.s]YN\006ky\004T\027QF\033(VMlS@'0G\002\013y#/\034P\002mqX\003Z\177]\020.=Wjm\027TXO?WLt\031Q)3*b-&pGH%%\030mH?)J,q\024\n\0258i\"\r_]{\\0ctaVaTU8ztO\0345p\024>8\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 4<v/u\025\\:|\032\033\036\013}sk#\025j\002&{y0M\010BQl&z\006>Bt\rI\001BG\036R'KKJ@\011bq\034\014^>BU\027sdD76B\024S8\021\037\027K<?M(u\0069mHe\032c\021\011\002o01\raf?U\001q_\"\011\033g '#C8\010\006\001\000\000&\010\020\005&0@q\006\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\021i@6\006\001U @PL(T4\030.w#\024@C7[N7+1hi7\031d\006\033\014b(\030\011@`\032T\010\013\011Gh6+Ihi3\032,6\013QRo7\010\n6+Ili1Y.2\002\021Rv4\\m\026{8b\031\030\005``\032T\010\003\011D\nFC\005nt2H\n6+Ile9\010\0104\011DL0\022\001A\022T\031\021\006{C \020H\004,\0279Y.'3\025d-1Y.'#M\000t4\030.w#\024\\c7[&\010\014$\005\001@@\032:\"A]H\177UM>6=;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_<oRK\\vl\024qHp]=%\003`&)\017\033\001,5\013y>\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\005<wZ]\036U\0364\027?Ndz>S/\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<U3\0375)$n255K\027\"8\0070'a[\036\037V:@<\005)\013:v;{\021moS\036y\021\0337\0258\017k\031\004)gCqSwD\013kD5F4Is\021rM \021\"(\001@M\026Fc.]\013\r0t=\020$x\026*=&ey\023G\037%(<&&\030-CS /\"!Y\017\016`WAo_d*x\0025AeNf:X3\030Z)!$d2d\036\027\0267ZjVp[\030a\".,R,d \"#)*GkOvb\030i@wKK\\$\005><\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\r<gpb3\007O;\027*e0c=g\036JFR_\r14\036lZ\0245{29q=%^mJ\035h}\007O\035IY\003T4Ja`R>C`o\022Du+cL\021.(\001\025\003F\r\nn\026+#y8=\010\020\007\033\007\016%M1\037)f8\026[\027.<UXd@\000^ N\021laN?8;\004`6!@y\011trD\006a\013x_gB\000cXU\000J\0022R.c=.gVc!dj%\002\036(Vjx\0037\027\010\004>3)\020\014gQ\004\025\030e\004rGeByA\023M*L%n\001\"\006\016\027,\n\00068K!}Tq1\006I\007U<X\022w:$0\\'[}x@\003\013C\002.\rC2Q7(\007Bi5Sng~M'\"\014@T2c@\0174h\034 \011\\Kn=@a|gLA\011p7q!GBfsT\004vg\026\033~T]eIdNwSA\022gv#}&}w:5\004}+=h@4\005\032RU\023x_/Hg\035\007:j^\027)[gs\\$?\011\016\016\026u\011cIF,h,959&NCj,Y?M6\000\007u6i~\020doWTPn\177!{\027q\023w4P Me%\013\177._XVO#H\003Bef\010yG\005\0315\013`\020\014t>L(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\037Uf<lkBO\011zkT\027_\025QGP\010K(Q@\034a\rq\\\0372\005!\033Zv1?SnLi\\^dR8uaQ&)*6=N\"\033C\000\035];\035\177msUC|7)w,3N&\024N>2n\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$<O\023&6z\031V\010lL)\006Fwi{\022\025FK\026=Rf_\022\032(S\030EMy\0329\\\022L&E\024P$-2v0\037[)\026*\036\013\\\002\027T#=d\016Il8\0115\r !B27y\024\030\0119xc9>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<H\010FKYRs4[mc\010T`\023\003\000jP \014&\014:\034L\026#\025de7\031m\026s\024b\"\030\010\000`I*\014HC=aP\010$\002\026\011Xl\024\003Qda2\031.&+9Ni7\031%f\033=Z0A\000! \024\010\002\001\0003d_J\001\0379$7v8\006gk?>:>GL?D\024\007\026-q5a\nHZe)\177-m\177F\037\023/\011;V&#e\177%*8]U\0078/=\017}xWP3.<Z\024\036<,\005\030@A\034Z<D\026\031z\016=< Aq_8M\020\036jEo\022Z#n0FYdd\027n\014*\034miD?b^(\007uV\013>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<<sI\036\0263Z1\021S?+XHT\004BE\036XQ\nB\005\177aW0\014?'2b\024*#}_P'@@0\010\000\0020@}\006\0061D\0260\004A@5(\020\014\023\001\025*3\011\024`#\003\000jP (&\034*[M\027#\025H )]\014\027#\025f (\033n7#\005X )Y.'3%Fe\030F&\0018\030\006U\002\002b1\003]nw\027\035.7\003L\\c7[%t\032A&1\nL\00200\r*\004\001DaE*M S\020\024Mv{P@C L\020\030H\n\003\001\000-u\022-1GZX$Yy!Z`\004\177EMg0\0217uc\017\023|5e\004&*~r7\014\024\"edX$xF\021=\033\177\nO\004RITEOj\0379\rSF&.}(1!\032s^\036\033%F7U&}o9\ne=JDyzx6MS+n$rle\017tyo\026q\003Xp(\017\031i(}Ro`G\001\026\026\\f_zaf_y\022\006^*\021\027P?5 \001t\021[X\022v\032\020\020\014\002\000\000L\020 \n(a\001]L$C\001\010\014\003*A\000q\030m,a6\032(6+Ih +\030-FK\021Bt4[mb\0029Jt;[n&YD.0\nA@5(\020\024\023\007\025L\026c%\006e9\035\005B\002%\\c\027\014&S\001L\014\003*A\0011\0311,a6\032(6+Ih ![\014\027\033L@1\020\024\rvc%Fy\020\025L\026c%Ha:\032-vq\001\002u:\032\rw\023%hy\030H&\001x\030\006U\002\000b1C!ht8\016Er{]nw\027\035L\026c%Fe9\035\005f\033=Z/\030H\006\001p\030\022*C\022\020o84\002\011\000EB\026K9Lo \035L\026c%Fe9\035\005f\033=Z0@b (\014\004\001X,`O(Mb-:S\013m\006z`]'*\007\000fwSU\r$/\006\031 _as'/\026+AV$vX4\024Z(\007.\000\001DD!H#q\177\010Hs<p\037k?4FDor;SQ?g%D\000u2h7)_\005EB:\032Gtg\177\016p2\006@j\001n\001k;H)Q!\0207\030\036pa~H\n0;DV\002Cc\004opA\002h\\jW$5lrP\0073Qn\003:?8@@0\010\000\0020A\000)#\004\006v1\022\014\004 0\r*\004\003Dc53\005Xi!Y.'!\001,a6\032,F\013QRo7\010\011f+Qno9\032f\0219@*\006\001U @PL\034V0[\r\024\033\025dt\026\010\011\026s\014\\1\032L\00600\r*\004\005DeE3\005Xi!Y.'!\001\006l0\\n2\001H@P7[\r\026\033d@V0[\r\026#\005hi7[D\004\013Uhh7\\M\027#db!\030\007``\032T\010\003\011F\r\007#Q`:\027Knw;\\\\v0[\r\026\033\025dt\027\030mvi<b \030\007@`I*\014HC=aP\010$\002\026\010Z-f3=\000v0[\r\026\033\025dt\027\030mviB\003\011\001 0\020\0068tqe95<Bf$Ukj{\007 ;snl}JT:eJG\007\001*]g.06\036\027\n5nLaO\034;\027T\014'Z%!\002$FX)=3>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<<v\004J/ll\022\033K~\004\004 c\011\024BB0`Z;\0336\025gO;^O\004\016\032\014#\016[MP-\\UBq\016\n{US\031/\036G\002\027\016W\010*0$L\n4tJfK]|-h6\023\007gSq4\nSx\010\006\001\000\000&\010\020\005\0020@lF\022!@D\006\001U @8L6V0[\r\024\033\025dt\020\025L\026c%Ha:\032-vq\001\034e:\035mw\023,b\027\030\005 `\032T\010\n\011CJf\0131RC2\\NBa\001\022n1KF\022a@T\006\001U @XLFC6\030.7\031\000b +\030-FK\021Bt4[mb\002\005jt4\033n&KQr \026H\011t\032M 1\020L\003p0\r*\004\001Dc\006CQhp\035\013ew;]n.;\030-FK\rJr:\013Mf+P^1\020\014\003`0$U\006$!^ph\004\022\001\013\004-\026s\031^@;\030-FK\rJr:\013L6{4a\001D@P\030\010\003\017\014\027ix4\004\034v=kq\023%@QH\naB\007\026\020k\037Ruj>\026KR\025J\007\026Nle\011$Nh<xGUWq&\016.t:=\021'\007-c8\000\005o*f\0229nL0[\021/d\000l-)J\017\")$(I$VPG![4TDslL:u)v\027\037\023\177=}\004He\036x(;-&*\001\025g7#Ko\017B\033s\007\n<\016\177`)oh'\1771A\177iEY\001\000`\020\000\004a\001vL\013s\010,`\011\003\000jP \030&\002*Tf\0219@*\006\001U @PL\034V2\\M\025\033%Nn\026\010\011\026s\014\\1\033L\006P0\r*\004\005Ded\0331Bs9H\006B\002Ajb6\032,2\002Adi6X.'I\001\006e9\035\r\0263%Fa:\032-vq\001\002u:\032\rw\023%hy\030 1\020\024\006\002\000h,N_3c!.-\024\036NJBS\037F5}y\014Qahi\011\022\0074&\013Vok\034`\010\\e\000x'24=r4eNGrzu\177S\006:Xd\010Mv\000XNA\001Im#\002$tB|S\013b{|\026C\006vTyq\000tQN_O6G{\011^y\014\n3%#w}'K\003\n\036J.N{Y+\036\036\023\\p'M5\034M\033v0g7Ve\033<6f}\001(\007Gp#j  \030\004\000\001\030 =S\002|b\013\030\002 `\032T\010\006\011@JU\031D.0\nA@5(\020\024\023\007\025LW\023%&i3[EB\002%\\c\027\014&s\001T\014\003*A\0011\0319\006l0\\n2\001D@P:XMFK\014@P9\032-V\013Ir !Y.'#%Li1X.FK=\\  ].FC=di:\036&\010\014$\005\001@@\034QM}[#+\030%YJ!cv3wW\030o.o\036C \001)\010;t8\022khhD\013JsT(\0232\"t\032\037f\031-`eC VB\006\020yP\0326\020P\\{{\"`rk70>a.VQ(\037jB 44c,?l\006M<<hgD\0029Q GDN\"UM\021{`\005\000m7\010y\011&v6dXkL/\003.-C^t\0339J`{\030.E\"yFUnQY{-X\010\006\001\000\000&\010\020\005 0@p&\020Y@\022\006\001U @0L\004U)L\"s\000T\014\003*A\001!\0309,e9\032*6K\035\\,\020\022-f\0318b<\030\016@`\032T\010\013\011Lh6c\005fs\020\014$\005\003UDl4Xd\005\003IRm0\\O\022\002\rJr:\032,fK\rBt4[mb\002\005jt4\033n&KQr \026H\010s\021Dt0\034\001@5(\020\026\023\030J\0142I\000b9\034N\004\0053\025di)Z,vq0@I7\030eb\0014@F7\\D\006\013Uhh7\\M\027S\025H :\\lR\003=\\l<L#s\000t\014\003*A\0011\030Y,e9\032*6K\035\\ *\034NW\033P@N2]\016v{IV0@b (\014\004\001*h.Wa16q\003j2Z }ql1e%\033\001lN\030V\001/\033\026xi^R\033\033NU0)+SoPpr\010EK\n?#wR\016yV2\037FpC$T\013\035 M=9\027.-]U\026'p\nM[\031'{<^\021)5\020`n8J0W\037H\"ztel:8>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\022<u_r\024`\000|\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\006\"\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\000Sb\000\022\013PYg\r\000~\011G\006.<\007H!\017\014s7\027tf_L\020\020\014\023_\027j,F0<,\011\025EK@\005R,f&#zmB-.<A\036!\031g\002{L.A\002g#\005\004\011K\025&O$\023tcj *\"\013\004#'R[\017\000:E{\030[\031\0363>2'\003}<o\"1q\0366zhh7js\006dU\036+\026\022_T#\007\013,HTa\177\006[\036Rh9[N?]` \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\021\001 u1\033\r\026\031\001 r4[,\027\023d@C2\\NFK\031Rc0]\r\026{8@A:]\r\006{IRt<H\005R\002\034f0A\000! \024\010\002\001\000+a n\013*,mYw\022o\026('ni7n\005A\033\017YX*cfYyCz3'h6.E\010q8;\n#'\0135q/x\0322\035 ^#\0161=,Zu57E\027q\0068N\023O\001(yYEt\023l\007{\035\030\010o\013VK\010\177\004:\002\006SC&\000g/n<\007H\003]cI\rnR@Y\005^\017\022V4S\030QF\006\"{\005u\"0\004nyoaR\007d3\000A\005\02552\01366\027 f\014[[?u\000UY\024\003E5;\031\r;S8r\0026N1;;m\011\024* \036\004 *k\0255S#)4\0311f\020F=\017f?dg ,cG1A\010wXcvi>\\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&K5Br<H\011t\032M  )\031.7\003=\\d2\\F\010\014$\005\001@@\032\014S\014ba?Q\006G`\025z\013\036ZR\014[e\026w\005\017i<9\017K@1\013/icx}\010Cd:\ng!\032IChRi\004+:\004R@GxdoS\011\177\030T\016\022j{Hnm\017\0165\025\005\023\004\031`\034\001\001\033W.=\013]UOF7S\024C\"b1JME!{\032E\017bI*.gEd\001\"hc>i\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<H\0106+Ihi3\032,6\013QRo7\010\010\027+QPo9\032.GIB\003\011\001 0\020\006%8YO<C8P\004)4\0107`D\006oFWWZHT\004>\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{IRt<H\005R\002\034d1\035\014\007\0000\r*\004\005Df\022C\014R \030N'\023A\001,e9\032*6K\035\\,\020\022-f\0318@-\020\021Mw\021\001Bu:\032\rw\023%te2\010\016W\033\024@o7\033\017\023\010|`\035\003\000jP ,&\026+\031.&JMRg7\010\nG\023Uft\020\023LW#]^r5L\020\030H\n\003\001\0003\013m\010E:\\4t\025=\035eTLLGk\031AB\034\026BH\010fj\034\177TBH\020N\0136gxR;!O\010\\\036;|i\177\022._\003OB\030~'c\017\037\"#|XeYN\034d \001\r\014\014\002rd\016\021.|>a]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{IRt<H\005R\002\034f0A\000! \024\010\002\001\000+9:(ESF,j~\030}T2\017+3YK(|AdO\011DT_\001feDV;m\004\017Iu\007m7:oEyh\020\177\034N@3\\|\"HC\0274Jh\023[H\0130OiC.T<TXl\027\004*k7Lc6J6\002z*\022&\003r8iZ#dAvB\014R{\014SD\n`<\"^s(V\024T\020nh\031[U\035\013\031p\0107\nG\034,\011!#\014f5`]\0019O\023:?\035y+\003t,1\030riy\032K\010R\003\\ Kri\024q\000\0200\026xy9Fq:,`F\003y\020\006m\0331\026\027:\005O@Ff8\006YTi_+\022#-vqn\027L'\016\r\026%ZP\013alR!lmf\004\177ul.>Nd\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<d\010(Jnt=\004@\026\025$\0177;\023\007(\010\006\001\000\000&\010\020\005:0&L!3\000$\014\003*A\000a\030\011*S\030C&\000X\030\006U\002\002B0\"Y\022S L%s\0014\014\003*A\0011\031\031,i9X$\004K9he9\033L\027#%^n0[\004\005\033\025dv4XlR\002\005fs7Xm\026\013QRo7\014\020 \010(\005\002\000@ \014(u\032s e<\022\005QXlg\031}'(\032YD\0020L\002x\"%O\020;-jN%b&+(_]Ul\031xJB9\037OH;O\"%=S\n+4mp/?G(Z-BwBO f\036q4H\021EjH\\\025`#Q\020G`pz #\001H4\034\025ek.x ;aJ5Kw\0028e8Vf.m\014L\n8J1\022Mty\"\032\n`#{T\r\024t26hPMf\011\026$B/4\"T\r4\022RX&C\0355)vt\n#\022\037,\025zr\007`;\027Y\031Qr9\036ms#%\000\030=Q\177s('\033u`\037jJH \004fJBqU\001\"x+AkX\032_\003%pi2\036\032q'8'XQ|_w+\004plj\011V-;\"\020\0313C]>\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<n\017fU\017L\024\007,`Im('\025LH8fKd\020>\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@b<H\010E\032Pb\030\030\005@`\032T\010\003\011Ck\006\033\025dt\020\021+\"\003\011r \"\024jC\011\004`\037\003\002%(2\"\rw\006@!\020\010X$c0P\014FK\035fi3]\016'+Mh.1[mS\004\010\002\n\001 @\020\010\002ZT\0147VK\177^[h:*]X[r\037Bj\035#-,\022s61?\021\036\177\022.aI>c[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\014<L/\177\013\005wwBf=A\0319a9\rRHin\016\"\011fH\031nC]\017lnLJq|\030\037%\027(Aip&.=2|\017\036sG\032,J\035:bV\177\035Ef!L`{\02055OJJ'\026< &#j#\rU|8C`\030|v]dg\013Dva\"\007tK)\"\016O\003p]LGf`2dI[0!&fE\032j\034u'\017{GH\031UX6\035\004\027\036sJ\007\006\036o\023\004j\033\001x.\011ar\026f_\004#\027yoQH\177\032[u&\030[J\031z5#9\0178Z+3\023p\001U<\0061\025}mvA3\001~p\\p\001b:F\024a|^!^jU|\034c\027ci\027\020C#\036B~(\036j xx\006?4\002P\0307/iw,r\1771\020/(R&b\025]\021M${` \030\004\000\001\030 :\023\002\014b!\030\007``\032T\010\n\011F\013\006\033\025dt\020\022-g#\025dn0]\r\026{9Bl\020\022-f\0318b\036\030\007\000`\032T\010\013\011E+\006\033\025dt\020\024Mv{P@C H\016c\011\000b0\031\r\006\010\014$\005\001@@\024pN\017%)S\006.\016a'RE=M\\\016Q{\023\0318\020\032\0333\003i]\021qk\027Lt=%_\r,0\007\033\000.6\020\n>6ElFxh\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<C0\0117\030uDUXGD\000h5Z'\023\n\002\001@ \000\017\177\177\177";
+    static {
+        try {
+            org.ibex.net.SSL.addCompactCAKeys(new java.io.ByteArrayInputStream(unpack(DATA)));
+        } catch(Exception e) {
+            System.err.println("Error loading root CA keys: " + e.getMessage());
+        }
+    }
+    public static void load() { /* force clinit  */ }
+    private static byte[] unpack(String s) {
+        int len = s.length();
+        if(len % 8 != 0) throw new IllegalArgumentException("not a multiple of 8");
+        byte[] ret = new byte[(len / 8) * 7];
+        for(int i=0; i<len; i += 8) {
+            long l = 0;
+            for(int j=0;j<8;j++) {
+                l <<= 7;
+                l |= (s.charAt(i + j) & 0x7fL);
+            }
+            int base = (i / 8) * 7;
+            for(int j=6; j>=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 (file)
index 0000000..3d2d5bb
--- /dev/null
@@ -0,0 +1,99 @@
+package org.ibex.net.ssl;
+
+import javax.swing.*;
+
+import java.awt.*;
+
+import org.ibex.net.SSL;
+import org.ibex.x509.X509Certificate;
+
+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;i<fp.length;i++) {
+            if(i>0) 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(X509Certificate[] 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 (file)
index 0000000..aa43ccb
--- /dev/null
@@ -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 (file)
index 0000000..c97bfab
Binary files /dev/null and b/src/org/ibex/net/ssl/rootcerts.dat differ