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