From 7b208d54971cde2b11765f96594e1d96aca8c396 Mon Sep 17 00:00:00 2001 From: bewue <> Date: Fri, 16 Oct 2020 23:52:52 +0200 Subject: [PATCH] initial commit --- .gitignore | 4 + LICENSE | 678 ++++++++++++++++++ README.md | 13 + src/org/bitbatzen/tlsserverscanner/Main.java | 37 + src/org/bitbatzen/tlsserverscanner/Util.java | 239 ++++++ .../bitbatzen/tlsserverscanner/gui/Area.java | 70 ++ .../tlsserverscanner/gui/AreaControls.java | 278 +++++++ .../tlsserverscanner/gui/AreaHosts.java | 161 +++++ .../tlsserverscanner/gui/AreaLog.java | 93 +++ .../tlsserverscanner/gui/AreaResults.java | 297 ++++++++ .../tlsserverscanner/gui/DialogConfirm.java | 93 +++ .../tlsserverscanner/gui/DialogMessage.java | 79 ++ .../tlsserverscanner/gui/DialogNewHost.java | 113 +++ .../tlsserverscanner/gui/DialogSearch.java | 340 +++++++++ .../gui/DialogSelectCipherSuites.java | 355 +++++++++ .../gui/DialogSelectCipherSuitesData.java | 13 + .../gui/DialogSelectProtocols.java | 173 +++++ .../gui/DialogViewCertificates.java | 193 +++++ .../gui/HostListCellRenderer.java | 57 ++ .../tlsserverscanner/gui/HostListItem.java | 124 ++++ .../tlsserverscanner/gui/HostListModel.java | 40 ++ .../tlsserverscanner/gui/MainWindow.java | 199 +++++ .../tlsserverscanner/gui/MyMenuBar.java | 376 ++++++++++ .../tlsserverscanner/gui/StatisticUtil.java | 269 +++++++ .../tlsserverscanner/scantask/Cert.java | 206 ++++++ .../scantask/IScanTaskHandlerListener.java | 28 + .../scantask/IScanTaskListener.java | 26 + .../tlsserverscanner/scantask/SSLUtil.java | 162 +++++ .../tlsserverscanner/scantask/ScanData.java | 156 ++++ .../tlsserverscanner/scantask/ScanTask.java | 513 +++++++++++++ .../scantask/ScanTaskHandler.java | 357 +++++++++ 31 files changed, 5742 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/org/bitbatzen/tlsserverscanner/Main.java create mode 100644 src/org/bitbatzen/tlsserverscanner/Util.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/Area.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/AreaControls.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/AreaHosts.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/AreaLog.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/AreaResults.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogConfirm.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogMessage.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogNewHost.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogSearch.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuites.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuitesData.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogSelectProtocols.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/DialogViewCertificates.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/HostListCellRenderer.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/HostListItem.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/HostListModel.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/MainWindow.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/MyMenuBar.java create mode 100644 src/org/bitbatzen/tlsserverscanner/gui/StatisticUtil.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/Cert.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskHandlerListener.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskListener.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/SSLUtil.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/ScanData.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/ScanTask.java create mode 100644 src/org/bitbatzen/tlsserverscanner/scantask/ScanTaskHandler.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3741e00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/bin/ +.settings +.project +.classpath diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8a83a04 --- /dev/null +++ b/LICENSE @@ -0,0 +1,678 @@ +Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state 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 program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a70942a --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# TLSServerScanner + +Scan TLS servers to get information about the supported TLS techniques. +TLSServerScanner is designed to handle a lot of hosts (> 1000) to get +meaningful statistics. + +- Selectable cipher suites for fetching the certificates +- Selectable cipher suites for testing +- Selectable TLS protocol versions for testing +- View and save the fetched certificates +- Full-Text search in scan results and certificates +- Statistics display +- Multithreaded scans diff --git a/src/org/bitbatzen/tlsserverscanner/Main.java b/src/org/bitbatzen/tlsserverscanner/Main.java new file mode 100644 index 0000000..22a9b76 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/Main.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner; + +import javax.swing.SwingUtilities; + +import org.bitbatzen.tlsserverscanner.gui.MainWindow; + + +public class Main { + + + public static void main(String args[]) throws Exception { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + new MainWindow(); + } + }); + } +} \ No newline at end of file diff --git a/src/org/bitbatzen/tlsserverscanner/Util.java b/src/org/bitbatzen/tlsserverscanner/Util.java new file mode 100644 index 0000000..80009e4 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/Util.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner; + +import java.awt.Color; +import java.awt.Font; +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.bitbatzen.tlsserverscanner.gui.HostListItem; +import org.bitbatzen.tlsserverscanner.scantask.Cert; + + +public class Util { + + public static final String T_APP_NAME = "TLSServerScanner"; + public static final String T_APP_VERSION = "1.0.1"; + public static final String T_APP_LICENSE = "GPLv3"; + public static final String T_AUTOR = "Benjamin W."; + public static final String T_CONTACT_EMAIL = "bitbatzen@gmail.com"; + public static final String T_CODE_URL = "https://github.com/bewue/tlsserverscanner"; + + public static final String LF = System.lineSeparator(); + public static final String BR = "
"; + + public static final String COLOR_END_TAG = ""; + public static final String FONT_END_TAG = ""; + + + public static String getJavaVersionString() { + return System.getProperty("java.version"); + } + + public static int getMajorJavaVersion() { + String[] javaVersionElements = System.getProperty("java.version").split("\\."); + return Integer.parseInt(javaVersionElements[1]); + } + + public static int extractPort(String hostAndPort) { + int port = -1; + try { + URI uri = new URI("test://" + hostAndPort); + port = uri.getPort(); + } + catch (URISyntaxException ex) { + return -1; + } + + return isPortValid(port) ? port : -1; + } + + public static String extractHost(String hostAndPort) { + String host = null; + try { + URI uri = new URI("test://" + hostAndPort); + host = uri.getHost(); + } + catch (URISyntaxException ex) { + return null; + } + + return host; + } + + public static boolean isPortValid(int port) { + return (port >= 0 && port <= 65535); + } + + public static boolean checkHostString(String hostAndPort) { + return (extractHost(hostAndPort) != null && extractPort(hostAndPort) != -1); + } + + public static String getBGColorTag(Color c) { + String hex = Integer.toHexString(c.getRGB()).substring(2).toUpperCase(); + return ""; + } + + public static String getFGColorTag(Color c) { + String hex = Integer.toHexString(c.getRGB()).substring(2).toUpperCase(); + return ""; + } + + public static String getFontFamilyTag(Font font) { + String tag = ""; + return tag; + } + + public static void saveHostlistToFile(String hostlist, File file) throws Exception { + OutputStreamWriter osw = null; + try { + FileOutputStream fos = new FileOutputStream(file); + osw = new OutputStreamWriter(fos, "UTF-8"); + osw.write(hostlist); + osw.flush(); + } + catch (Exception e) { + throw e; + } + finally { + close(osw); + } + } + + /** + * @param hostlistToFill + * @param file + * @return 0 on success or the line number with the syntax error + * @throws Exception + */ + public static int loadHostlistFromFile(List hostlistToFill, File file) throws Exception { +// List hostlist = new ArrayList(); + BufferedReader br = null; + try { + FileReader fileReader = new FileReader(file); + br = new BufferedReader(fileReader); + String line; + int lineCounter = 1; + while ((line = br.readLine()) != null) { + if (checkHostString(line)) { + hostlistToFill.add(line); + } + else { + return lineCounter; + } + lineCounter++; + } + } + catch (Exception e) { + throw e; + } + finally { + close(br); + } + + return 0; + } + + public static void saveCertificatesToDirectory(List hostlist, File directory) throws Exception { + FileOutputStream fos = null; + try { + for (HostListItem host : hostlist) { + if (host.getScanTask() == null || host.getScanTask().getScanData().getCertAvailable() == false) { + continue; + } + + String id = host.getHost() + "_" + host.getPort(); + File subDirectory = new File(directory.getPath() + File.separator + id); + subDirectory.mkdir(); + + Cert[] certs = host.getScanTask().getScanData().certs; + for (int i = 0; i < certs.length; i++) { + Cert cert = certs[i]; + String filename = ""; + if (i == 0) { + filename = id + ".cert"; + } + else { + filename = "issuer_" + i + ".cert"; + } + + File file = new File(subDirectory.getPath() + File.separator + filename); + byte[] buffer = cert.getCert().getEncoded(); + fos = new FileOutputStream(file); + fos.write(buffer); + fos.close(); + fos.flush(); + } + } + } + catch (Exception e) { + throw e; + } + finally { + close(fos); + } + } + + public static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } + catch (IOException e) { + } + } + } + + public static String bytesToHex(byte[] bytes, boolean addWhitespaces) { + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int i = 0; i < bytes.length; i++) { + int v = bytes[i] & 0xFF; + hexChars[i * 2] = hexArray[v >>> 4]; + hexChars[i * 2 + 1] = hexArray[v & 0x0F]; + } + + if (addWhitespaces) { + StringBuilder sb = new StringBuilder(128); + for (int i = 0; i < hexChars.length; i++) { + if (i % 2 == 0) { + sb.append(hexChars[i]); + } + else { + sb.append(hexChars[i] + " "); + } + } + + return sb.toString(); + } + else { + return new String(hexChars); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/Area.java b/src/org/bitbatzen/tlsserverscanner/gui/Area.java new file mode 100644 index 0000000..fba0bde --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/Area.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.JPanel; + +import org.bitbatzen.tlsserverscanner.scantask.*; + + +public class Area implements IScanTaskHandlerListener { + + public final static int BORDER_WIDTH = 2; + + protected ScanTaskHandler scanTaskHandler; + + protected JPanel panel; + + protected MainWindow mainWindow; + + + public Area(MainWindow mainWindow) { + this.mainWindow = mainWindow; + + panel = new JPanel(); + + scanTaskHandler = null; + } + + public JPanel getPanel() { + return panel; + } + + public void setPanelBounds(int x, int y, int width, int height) { + panel.setBounds(x, y, width, height); + } + + public void setScanTaskHandler(ScanTaskHandler scanTaskHandler) { + this.scanTaskHandler = scanTaskHandler; + scanTaskHandler.addListener(this); + } + + public ScanTaskHandler getScanTaskHandler() { + return scanTaskHandler; + } + + @Override + public synchronized void onScanTaskHandlerMessage(ScanTask scanTask, String message) { + } + + @Override + public synchronized void onScanTaskHandlerDone() { + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/AreaControls.java b/src/org/bitbatzen/tlsserverscanner/gui/AreaControls.java new file mode 100644 index 0000000..b49039c --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/AreaControls.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JToggleButton; +import javax.swing.SwingUtilities; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.gui.DialogSelectCipherSuites.DialogType; +import org.bitbatzen.tlsserverscanner.scantask.*; + + +public class AreaControls extends Area implements ActionListener { + + private static final int SPACER_SMALL = 5; + + private JButton buttonNewHost; + private JButton buttonDeleteHosts; + + private JButton buttonStartScan; + private JButton buttonStopScan; + + private JToggleButton buttonStatistic; + + private JToggleButton buttonPlainText; +// private JButton buttonClearLog; + + + public AreaControls(MainWindow mainWindow) { + super(mainWindow); + + panel.setBackground(MainWindow.COLOR_BG_CONTROLS); + + BoxLayout layout = new BoxLayout(panel, BoxLayout.X_AXIS); + panel.setLayout(layout); + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + + // button new host + buttonNewHost = new JButton("+"); + buttonNewHost.addActionListener(this); + panel.add(buttonNewHost); + panel.add(Box.createRigidArea(new Dimension(SPACER_SMALL, 0))); + // button delete host(s) + buttonDeleteHosts = new JButton("-"); + buttonDeleteHosts.addActionListener(this); + panel.add(buttonDeleteHosts); + +// panel.add(Box.createRigidArea(new Dimension(57, 0))); + + // button statistic + buttonStatistic = new JToggleButton("Statistic"); + buttonStatistic.addActionListener(this); + panel.add(buttonStatistic); + +// panel.add(Box.createRigidArea(new Dimension(50, 0))); + + // button start scan + buttonStartScan = new JButton("Start"); + buttonStartScan.addActionListener(this); + panel.add(buttonStartScan); + panel.add(Box.createRigidArea(new Dimension(SPACER_SMALL, 0))); + // button stop scan + buttonStopScan = new JButton("Stop"); + buttonStopScan.addActionListener(this); + panel.add(buttonStopScan); + + // button plain text + buttonPlainText = new JToggleButton("Plain Text"); + buttonPlainText.addActionListener(this); + panel.add(buttonPlainText); + + updateGUIElements(); + } + + private void onClickNewHost() { + new DialogNewHost(mainWindow); + } + + private void onClickDeleteHosts() { + if (mainWindow.getAreaHosts().getHostListItemCount() == 0) { + mainWindow.showMessageDialog("Delete Hosts(s)", "No host(s) to delete!"); + return; + } + else if (mainWindow.getAreaHosts().getSelectedItem() == null) { + mainWindow.showMessageDialog("Delete Hosts(s)", "No host selected!"); + return; + } + else if (mainWindow.showConfirmDialog("Delete Host(s)", "Delete selected host(s)?")) { + mainWindow.getAreaHosts().removeSelectedHosts(); + } + } + + private void onClickStartScan() { + HostListItem[] hostListItems = mainWindow.getAreaHosts().getHostListItems(); + if (hostListItems.length == 0) { + mainWindow.showMessageDialog("Start Scan", "Hostlist is empty!"); + return; + } + else { + String duplicate = mainWindow.getAreaHosts().checkHostDuplicates(); + if (duplicate != null) { + String text = "Warning:" + Util.BR + Util.BR + "Duplicate in list:" + Util.BR + duplicate + ""; + if (mainWindow.showConfirmDialog("Start Scan", text) == false) { + return; + } + } + } + + boolean certCollectingDisabled = mainWindow.getMyMenuBar().getCertCollectingDisabled(); + boolean certVerificationDisabled = mainWindow.getMyMenuBar().getCertVerificationDisabled(); + + String startScanPopupText = getStartScanPopupText(certCollectingDisabled, certVerificationDisabled); + if (mainWindow.showConfirmDialog("Start Scan", startScanPopupText) == false) { + return; + } + + ScanTaskHandler scanTaskHandler = new ScanTaskHandler(); + scanTaskHandler.init(certCollectingDisabled == false, certVerificationDisabled == false); + scanTaskHandler.setCipherSuitesForCollectingCert(DialogSelectCipherSuites.getSelectedCipherSuites(DialogType.CIPHER_SUITES_FOR_COLLECTING_CERT)); + scanTaskHandler.setCipherSuitesToTest(DialogSelectCipherSuites.getSelectedCipherSuites(DialogType.CIPHER_SUITES_TO_TEST)); + scanTaskHandler.setProtocolsToTest(DialogSelectProtocols.getSelectedProtocols()); + setScanTaskHandler(scanTaskHandler); + + // create the ScanTasks + for (HostListItem hli : hostListItems) { + ScanTask scanTask = new ScanTask(hli.getHost(), hli.getPort()); + scanTaskHandler.addScanTask(scanTask); + hli.setScanTask(scanTask); + } + + mainWindow.getAreaHosts().setScanTaskHandler(scanTaskHandler); + mainWindow.getAreaLog().setScanTaskHandler(scanTaskHandler); + mainWindow.getAreaResults().setScanTaskHandler(scanTaskHandler); + + mainWindow.getAreaResults().updateView(); + mainWindow.getAreaHosts().updateAllHosts(); + + scanTaskHandler.startScans(); + + updateGUIElements(); + } + + private void onClickStopScan() { + if (mainWindow.showConfirmDialog("Stop Scan", "Stop the current scan?")) { + scanTaskHandler.stopScans(); + updateGUIElements(); + } + } + + private void onClickStatistic() { + mainWindow.getAreaResults().updateView(); + } + + private void onClickPlainText() { + mainWindow.getAreaResults().setPlainTextEnabled(buttonPlainText.isSelected()); + } + +// private void onClickClearLog() { +// mainWindow.getAreaLog().clearLog(); +// } + + public void unselectStatisticButtons() { + buttonStatistic.setSelected(false); + } + + public boolean getStatisticSelected() { + return buttonStatistic.isSelected(); + } + + public void updateGUIElements() { + boolean scanRunning = (scanTaskHandler != null && scanTaskHandler.getScansRunning()); + + buttonNewHost.setEnabled(!scanRunning); + buttonDeleteHosts.setEnabled(!scanRunning); + + buttonStartScan.setEnabled(!scanRunning); + buttonStopScan.setEnabled(scanRunning); + } + + private String getStartScanPopupText(boolean certCollectingDisabled, boolean certVerificationDisabled) { + String hostString = (mainWindow.getAreaHosts().getHostListItemCount() == 1) ? "host" : "hosts"; + String certCollectingString = certCollectingDisabled ? "- Certificate collecting is disabled!" + : "- Certificate collecting is enabled!"; + String certVerString = certVerificationDisabled ? Util.getBGColorTag(Color.RED) + "DISABLED!" + Util.COLOR_END_TAG + : "enabled!"; + + int cipherSuitesToTestCount = DialogSelectCipherSuites.getSelectedCipherSuites(DialogType.CIPHER_SUITES_TO_TEST).size(); + + String text = "" + + "- " + mainWindow.getAreaHosts().getHostListItemCount() + " " + hostString + " to scan!" + + "
- " + cipherSuitesToTestCount + " cipher suites to test!" + + "
- " + DialogSelectProtocols.getSelectedProtocols().size() + " protocol versions to test!" + + "
" + certCollectingString + + "
- Certificate verification is " + certVerString + + "

Current scan data will be lost!" + + "
Start scan?"; + + return text; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == buttonNewHost) { + onClickNewHost(); + } + else if (e.getSource() == buttonDeleteHosts) { + onClickDeleteHosts(); + } + else if (e.getSource() == buttonStartScan) { + onClickStartScan(); + } + else if (e.getSource() == buttonStopScan) { + onClickStopScan(); + } + else if (e.getSource() == buttonStatistic) { + onClickStatistic(); + } + else if (e.getSource() == buttonPlainText) { + onClickPlainText(); + } +// else if (e.getSource() == buttonClearLog) { +// onClickClearLog(); +// } + } + + @Override + public void setPanelBounds(int x, int y, int width, int height) { + super.setPanelBounds(x, y, width, height); + + buttonNewHost.setBounds(buttonNewHost.getX(), buttonNewHost.getY(), 45, buttonNewHost.getHeight()); + buttonDeleteHosts.setBounds(buttonDeleteHosts.getX(), buttonDeleteHosts.getY(), 45, buttonDeleteHosts.getHeight()); + + int posXButtonStatistic = ((MainWindow.HOST_AREA_WIDTH - buttonDeleteHosts.getX() + buttonStatistic.getWidth() / 2)) / 2; + buttonStatistic.setBounds(posXButtonStatistic, buttonStatistic.getY(), buttonStatistic.getWidth(), buttonStatistic.getHeight()); + + buttonStartScan.setBounds(MainWindow.HOST_AREA_WIDTH, buttonStartScan.getY(), buttonStartScan.getWidth(), buttonStartScan.getHeight()); + int posXButtonStopScan = buttonStartScan.getX() + buttonStartScan.getWidth() + SPACER_SMALL; + buttonStopScan.setBounds(posXButtonStopScan, buttonStopScan.getY(), buttonStopScan.getWidth(), buttonStopScan.getHeight()); + + int posXButtonPlainText = Math.max(width - buttonPlainText.getWidth() - 5, buttonStopScan.getX() + buttonStopScan.getWidth() + 20); + buttonPlainText.setBounds(posXButtonPlainText, buttonPlainText.getY(), buttonPlainText.getWidth(), buttonPlainText.getHeight()); + } + + @Override + public synchronized void onScanTaskHandlerDone() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + updateGUIElements(); + } + }); + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/AreaHosts.java b/src/org/bitbatzen/tlsserverscanner/gui/AreaHosts.java new file mode 100644 index 0000000..8153f68 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/AreaHosts.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import org.bitbatzen.tlsserverscanner.Util; + + +public class AreaHosts extends Area { + + private JScrollPane scrollPane; + private JList list; + + private HostListModel listModel; + + + public AreaHosts(final MainWindow mainWindow) { + super(mainWindow); + + listModel = new HostListModel(); + list = new JList(listModel); + list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + list.setLayoutOrientation(JList.HORIZONTAL_WRAP); + list.setVisibleRowCount(-1); + list.setCellRenderer(new HostListCellRenderer()); + list.setBackground(MainWindow.COLOR_BG_HOSTS); + + list.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + mainWindow.getAreaControls().unselectStatisticButtons(); + mainWindow.getAreaResults().updateView(); + } + }); + list.addFocusListener(new FocusListener() { + @Override + public void focusLost(FocusEvent e) { + } + @Override + public void focusGained(FocusEvent e) { + mainWindow.getAreaControls().unselectStatisticButtons(); + mainWindow.getAreaResults().updateView(); + } + }); + + scrollPane = new JScrollPane(list); + panel.add(scrollPane); + } + + public void addHost(String host, int port) { + listModel.addElement(new HostListItem(host, port, listModel)); + updateAllHosts(); + } + + public void updateAllHosts() { + for (int i = 0; i < listModel.size(); i++) { + listModel.get(i).update(); + } + } + + public HostListItem[] getHostListItems() { + HostListItem[] hostItems = new HostListItem[listModel.size()]; + for (int i = 0; i < listModel.size(); i++) { + hostItems[i] = listModel.get(i); + } + + return hostItems; + } + + public String getHostList() { + StringBuilder sb = new StringBuilder(2000); + for (int i = 0; i < listModel.size(); i++) { + sb.append(listModel.get(i).getHostWithPort() + Util.LF); + } + + return sb.toString(); + } + + public int getHostListItemCount() { + return listModel.size(); + } + + public List getSelectedHosts() { + List hosts = new ArrayList<>(); + int[] selectedHosts = list.getSelectedIndices(); + for (int i = 0; i < selectedHosts.length; i++) { + hosts.add(listModel.get(selectedHosts[i])); + } + + return hosts; + } + + public void removeSelectedHosts() { + int[] selectedHosts = list.getSelectedIndices(); + for (int i = selectedHosts.length-1; i >= 0; i--) { + listModel.removeElementAt(selectedHosts[i]); + } + + updateAllHosts(); + mainWindow.getAreaResults().updateView(); + } + + public void removeAllHosts() { + listModel.clear(); + mainWindow.getAreaResults().updateView(); + } + + public String checkHostDuplicates() { + for (int i = 0; i < listModel.size(); i++) { + String h1 = listModel.get(i).getHostWithPort(); + for (int k = 0; k < listModel.size(); k++) { + String h2 = listModel.get(k).getHostWithPort(); + if (i != k && h1.equals(h2)) { + return h1; + } + } + } + + return null; + } + + public void setPanelBounds(int x, int y, int width, int height) { + panel.setBounds(x, y, width, height); + + list.setBounds(0, 0, width, height); + list.setFixedCellWidth(width - BORDER_WIDTH - 1); + + scrollPane.setBounds(0, 0, width, height); + } + + public HostListItem getSelectedItem() { + return (HostListItem) list.getSelectedValue(); + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/AreaLog.java b/src/org/bitbatzen/tlsserverscanner/gui/AreaLog.java new file mode 100644 index 0000000..5c4a5ea --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/AreaLog.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.text.DefaultCaret; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.*; + + +public class AreaLog extends Area { + + private JTextArea textArea; + private JScrollPane scrollPane; + + private static final int LOG_TRIM_SIZE = 10000; + private static final int LOG_TRIM_UPDATE_SIZE = 15000; + private StringBuilder log; + + + public AreaLog(MainWindow mainWindow) { + super(mainWindow); + + panel.setLayout(null); + textArea = new JTextArea(); + textArea.setEditable(false); + textArea.setFont(MainWindow.FONT_LOG); + textArea.setBackground(MainWindow.COLOR_BG_LOG); + + DefaultCaret caret = (DefaultCaret) textArea.getCaret(); + caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); + + scrollPane = new JScrollPane(textArea); + panel.add(scrollPane); + + log = new StringBuilder(LOG_TRIM_UPDATE_SIZE + 300); + } + + public void addLogMessage(String message) { + if (message != null && message.equals("") == false) { + if (log.length() >= LOG_TRIM_UPDATE_SIZE) { + int newStart = log.length() - LOG_TRIM_SIZE; + int correctedStart = log.indexOf(Util.LF, newStart); + log = log.replace(0, correctedStart, "..."); + } + + log.append(message + Util.LF); + textArea.setText(log.toString()); + } + } + + public void clearLog() { + log.setLength(0); + textArea.setText(""); + } + + @Override + public void setPanelBounds(int x, int y, int width, int height) { + super.setPanelBounds(x, y, width, height); + + textArea.setBounds(0, 0, width, height); + scrollPane.setBounds(0, 0, width, height); + } + + @Override + public synchronized void onScanTaskHandlerMessage(ScanTask scanTask, final String message) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + addLogMessage(message); + } + }); + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/AreaResults.java b/src/org/bitbatzen/tlsserverscanner/gui/AreaResults.java new file mode 100644 index 0000000..ede5e63 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/AreaResults.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.JEditorPane; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; +import javax.swing.text.DefaultCaret; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.*; +import org.bitbatzen.tlsserverscanner.scantask.ScanTask.State; + + +public class AreaResults extends Area { + + private static final String H1_S = ""; + private static final String H1_E = ""; + private static final String H2_S = ""; + private static final String H2_E = ""; + private static final String BR = Util.BR; + + private JEditorPane textArea; + private JScrollPane scrollPane; + + private ScanTask currentScanTask; + + private boolean plainTextEnabled; + + + public AreaResults(MainWindow mainWindow) { + super(mainWindow); + + panel.setLayout(null); + textArea = new JEditorPane(); + textArea.setFont(MainWindow.FONT_MEDIUM); + textArea.setEditable(false); + + setPlainTextEnabled(false); + + textArea.setBackground(MainWindow.COLOR_BG_RESULTS); + + DefaultCaret caret = (DefaultCaret) textArea.getCaret(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + + scrollPane = new JScrollPane(textArea); + panel.add(scrollPane); + + currentScanTask = null; + } + + @Override + public void setPanelBounds(int x, int y, int width, int height) { + super.setPanelBounds(x, y, width, height); + + textArea.setBounds(0, 0, width, height); + scrollPane.setBounds(0, 0, width, height); + } + + private String getScanTaskStateMessage(ScanTask scanTask) { + ScanTask.State state = ScanTask.getState(scanTask); + if (state == ScanTask.State.ERROR) { + return ScanTask.getStateTag(state) + " " + scanTask.getScanData().getErrorInfo(); + } + else { + return ScanTask.getStateTag(state); + } + } + + public void setPlainTextEnabled(boolean enabled) { + plainTextEnabled = enabled; + String contentType = plainTextEnabled ? "text/txt" : "text/html"; + textArea.setContentType(contentType); + + updateView(); + } + + public void updateView() { + String result = ""; + if (mainWindow.getAreaControls().getStatisticSelected()) { + result = getHTMLStatistic(); + } + else { + HostListItem item = mainWindow.getAreaHosts().getSelectedItem(); + if (item != null) { + if (item.getScanTask() != null) { + currentScanTask = item.getScanTask(); + result = getHTMLHostResults(currentScanTask); + } + else { + result = getHTMLHostInfo(item); + } + } + } + + if (plainTextEnabled) { + result = result.replace("
", Util.LF); + result = result.replaceAll("\\<.*?>", ""); + } + + textArea.setText(result); + } + + private String getHTMLHostInfo(HostListItem item) { + String text = Util.getFontFamilyTag(MainWindow.FONT_MEDIUM) + + H1_S + item.getHost() + ":" + item.getPort() + H1_E + BR + + Util.getBGColorTag(ScanTask.getStateColor(ScanTask.State.NONE)) + H2_S + getScanTaskStateMessage(item.getScanTask()) + H2_E + Util.COLOR_END_TAG + + Util.FONT_END_TAG; + return text; + } + + private void appendCipherSuites(StringBuilder sb, ScanTask scanTask, boolean supported) { + HashMap map = scanTask.getScanData().cipherSuitesTested; + for (Map.Entry entry : map.entrySet()) { + String cipherSuite = scanTask.getScanTaskHandler().getCipherSuiteToTest(entry.getKey()); + if (entry.getValue() == supported) { + sb.append(cipherSuite + BR); + } + } + } + + private void appendProtocols(StringBuilder sb, ScanTask scanTask, boolean supported) { + HashMap map = scanTask.getScanData().protocolsTested; + for (Map.Entry entry : map.entrySet()) { + String cipherSuite = scanTask.getScanTaskHandler().getProtocolToTest(entry.getKey()); + if (entry.getValue() == supported) { + sb.append(cipherSuite + BR); + } + } + } + + private String getHTMLHostResults(ScanTask scanTask) { + if (scanTask == null) { + return ""; + } + + ScanTask.State state = ScanTask.getState(scanTask); + ScanData sd = scanTask.getScanData(); + StringBuilder sb = new StringBuilder(2500); + + sb.append(Util.getFontFamilyTag(MainWindow.FONT_MEDIUM)); + + sb.append(H1_S + sd.getHostWithPort() + " (" + sd.getHostAddress() + ")" + H1_E + BR); + sb.append(Util.getBGColorTag(ScanTask.getStateColor(state)) + + H2_S + getScanTaskStateMessage(scanTask) + H2_E + BR + Util.COLOR_END_TAG); + sb.append(BR); + + if (scanTask.getScanTaskHandler().getProtocolsToTestCount() > 0) { + sb.append(H2_S + "Supported Protocols:" + H2_E + BR); + appendProtocols(sb, scanTask, true); + sb.append(BR); + + sb.append(H2_S + "NOT Supported Protocols:" + H2_E + BR); + appendProtocols(sb, scanTask, false); + sb.append(BR); + sb.append(BR); + } + + if (scanTask.getScanTaskHandler().getCipherSuitesToTestCount() > 0) { + sb.append(H2_S + "Supported Cipher Suites:" + H2_E + BR); + appendCipherSuites(sb, scanTask, true); + sb.append(BR); + + sb.append(H2_S + "NOT Supported Cipher Suites:" + H2_E + BR); + appendCipherSuites(sb, scanTask, false); + sb.append(BR); + sb.append(BR); + } + + if (scanTask.getScanData().getCertAvailable() == false) { + return sb.toString(); + } + + sb.append(H2_S + "Cipher Suite Chosen By Server:" + H2_E + BR); + sb.append(sd.cipherSuiteChosenByServer + BR); + sb.append(BR); + + sb.append(H2_S + "Certificate Public Key Algorithm:" + H2_E + BR); + sb.append(sd.certs[0].getPublicKeyAlgorithmWithKeyLength() + BR); + sb.append(BR); + + sb.append(H2_S + "Certificate Signature Algorithm:" + H2_E + BR); + sb.append(sd.certs[0].getSignatureAlgorithm() + BR); + sb.append(BR); + + sb.append(H2_S + "Certificate Extensions:" + H2_E + BR); + List oids = sd.certs[0].getExtensionOIDsWithName(); + for (String oid : oids) { + sb.append(oid + BR); + } + sb.append(BR); + + return sb.toString(); + } + + private String getHTMLStatistic() { + StringBuilder sb = new StringBuilder(2500); + + sb.append(Util.getFontFamilyTag(MainWindow.FONT_MEDIUM)); + + sb.append(H1_S + "Statistic" + H1_E + BR); + + if (scanTaskHandler == null) { + sb.append(H2_S + ScanTask.getStateTag(State.WAIT) + H2_E); + return sb.toString(); + } + + ScanTask.State state = scanTaskHandler.getState(); + String colorTag = Util.getBGColorTag(ScanTask.getStateColor(state)); + String stateTag = ScanTask.getStateTag(state); + sb.append(colorTag + H2_S + stateTag + H2_E + BR + Util.COLOR_END_TAG); + sb.append(BR); + sb.append(H2_S + "Hosts Completed (" + scanTaskHandler.getScansCompletedCount() + "/" + scanTaskHandler.getScanTasksCount() + ")" + H2_E + BR); + if (scanTaskHandler.getScansFailedCount() == 0) { + sb.append(H2_S + "Hosts Failed (" + scanTaskHandler.getScansFailedCount() + ")" + H2_E + BR); + } + else { + sb.append(H2_S + "Hosts Failed " + Util.getBGColorTag(ScanTask.getStateColor(State.ERROR)) + + "(" + scanTaskHandler.getScansFailedCount() + ")" + Util.COLOR_END_TAG + H2_E + BR); + } + sb.append(BR); + + if (scanTaskHandler.getProtocolsToTestCount() > 0) { + sb.append(H2_S + "Protocols:" + H2_E + BR); + StatisticUtil.appendProtocolStatistic(sb, scanTaskHandler); + sb.append(BR); + } + + if (scanTaskHandler.getCipherSuitesToTestCount() > 0) { + sb.append(H2_S + "Cipher Suites:" + H2_E + BR); + StatisticUtil.appendCipherSuiteStatistic(sb, scanTaskHandler); + sb.append(BR); + } + + if (scanTaskHandler.getCertCollectingEnabled() == false) { + return sb.toString(); + } + + sb.append(H2_S + "Cipher Suite Chosen By Server:" + H2_E + BR); + StatisticUtil.appendCipherSuiteChosenFromServerStatistic(sb, scanTaskHandler); + sb.append(BR); + + sb.append(H2_S + "Certificate Public Key Algorithm:" + H2_E + BR); + StatisticUtil.appendCertificatePubKeyAlgorithmStatistic(sb, scanTaskHandler); + sb.append(BR); + + sb.append(H2_S + "Certificate Public Key Algorithm (with key length):" + H2_E + BR); + StatisticUtil.appendCertificatePubKeyAlgorithmWithKeyLengthStatistic(sb, scanTaskHandler); + sb.append(BR); + + sb.append(H2_S + "Certificate Signature Algorithm:" + H2_E + BR); + StatisticUtil.appendCertificateSignatureAlgorithmStatistic(sb, scanTaskHandler); + sb.append(BR); + + sb.append(H2_S + "Certificate Extensions:" + H2_E + BR); + StatisticUtil.appendCertificateExtensionStatistic(sb, scanTaskHandler); + sb.append(BR); + + sb.append(H2_S + "Certificate Root CAs (Organisation):" + H2_E + BR); + StatisticUtil.appendCertificateRootCAOrganisationStatistic(sb, scanTaskHandler); + sb.append(BR); + + return sb.toString(); + } + + @Override + public synchronized void onScanTaskHandlerMessage(final ScanTask scanTask, String message) { + if (currentScanTask == scanTask || mainWindow.getAreaControls().getStatisticSelected()) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + updateView(); + } + }); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogConfirm.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogConfirm.java new file mode 100644 index 0000000..bef1282 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogConfirm.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JDialog; +import javax.swing.JPanel; + +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.event.*; + + +public class DialogConfirm extends JDialog implements ActionListener { + + private MainWindow mainWindow; + + private JLabel infoLabel; + + private JButton buttonOk; + private JButton buttonCancel; + + private boolean returnValue = false; + + + public DialogConfirm(MainWindow mainWindow, String title, String text) { + super(mainWindow.getFrame(), title, true); + + this.mainWindow = mainWindow; + + setResizable(false); + + infoLabel = new JLabel(text); + infoLabel.setFont(MainWindow.FONT_MEDIUM.deriveFont(Font.BOLD)); + infoLabel.setBorder(BorderFactory.createEmptyBorder(20, 30, 15, 30)); + infoLabel.setHorizontalAlignment(JLabel.CENTER); + getContentPane().add(infoLabel, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 20, 10, 20)); + + buttonOk = new JButton("Ok"); + buttonOk.addActionListener(this); + bottomPanel.add(buttonOk); + + buttonCancel = new JButton("Cancel"); + buttonCancel.addActionListener(this); + bottomPanel.add(buttonCancel); + + getContentPane().add(bottomPanel, BorderLayout.SOUTH); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + pack(); + + mainWindow.setDefaultPopupPosition(this); + } + + public boolean showDialog() { + setVisible(true); + return returnValue; + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == buttonOk) { + returnValue = true; + setVisible(false); + dispose(); + } + else if (e.getSource() == buttonCancel) { + returnValue = false; + setVisible(false); + dispose(); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogMessage.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogMessage.java new file mode 100644 index 0000000..0016628 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogMessage.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import java.awt.BorderLayout; +import java.awt.Font; +import java.awt.event.*; + + +public class DialogMessage extends JDialog implements ActionListener { + + private MainWindow mainWindow; + + private JTextArea infoArea; + + private JButton buttonOk; + + + public DialogMessage(MainWindow mainWindow, String title, String text) { + super(mainWindow.getFrame(), title, true); + + this.mainWindow = mainWindow; + + setResizable(false); + + infoArea = new JTextArea(text); + infoArea.setEditable(false); + infoArea.setFont(MainWindow.FONT_MEDIUM.deriveFont(Font.BOLD)); + infoArea.setBorder(BorderFactory.createEmptyBorder(20, 30, 15, 30)); + getContentPane().add(infoArea, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 20, 10, 20)); + + buttonOk = new JButton("Ok"); + buttonOk.addActionListener(this); + bottomPanel.add(buttonOk); + + getContentPane().add(bottomPanel, BorderLayout.SOUTH); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + pack(); + + mainWindow.setDefaultPopupPosition(this); + } + + public void showDialog() { + setVisible(true); + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == buttonOk) { + setVisible(false); + dispose(); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogNewHost.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogNewHost.java new file mode 100644 index 0000000..581128a --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogNewHost.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.bitbatzen.tlsserverscanner.Util; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.event.*; + + +public class DialogNewHost extends JDialog implements ActionListener { + + private MainWindow mainWindow; + + private JTextField textField; + + private JLabel infoLabel; + + private JButton buttonOk; + private JButton buttonCancel; + + + public DialogNewHost(MainWindow mainWindow) { + super(mainWindow.getFrame(), "New Host", true); + + this.mainWindow = mainWindow; + + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); + + textField = new JTextField(26); + textField.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + textField.setFont(MainWindow.FONT_MEDIUM); + topPanel.add(textField, BorderLayout.NORTH); + + JLabel syntaxLabel = new JLabel("(syntax: host:port)"); + syntaxLabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); + syntaxLabel.setFont(MainWindow.FONT_SMALL); + syntaxLabel.setForeground(Color.GRAY); + topPanel.add(syntaxLabel, BorderLayout.SOUTH); + getContentPane().add(topPanel, BorderLayout.NORTH); + + infoLabel = new JLabel(" "); + infoLabel.setFont(MainWindow.FONT_MEDIUM.deriveFont(Font.BOLD)); + infoLabel.setForeground(Color.RED); + infoLabel.setHorizontalAlignment(JLabel.CENTER); + getContentPane().add(infoLabel, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(); + bottomPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5)); + + buttonOk = new JButton("Ok"); + buttonOk.addActionListener(this); + bottomPanel.add(buttonOk); + + buttonCancel = new JButton("Cancel"); + buttonCancel.addActionListener(this); + bottomPanel.add(buttonCancel); + + getContentPane().add(bottomPanel, BorderLayout.SOUTH); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + pack(); + + mainWindow.setDefaultPopupPosition(this); + + setVisible(true); + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == buttonOk) { + String host = Util.extractHost(textField.getText()); + int port = Util.extractPort(textField.getText()); + if (host == null || port == -1) { + infoLabel.setText("Invalid Syntax!"); + return; + } + + mainWindow.getAreaHosts().addHost(host, port); + setVisible(false); + dispose(); + } + else if (e.getSource() == buttonCancel) { + setVisible(false); + dispose(); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogSearch.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogSearch.java new file mode 100644 index 0000000..195993a --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogSearch.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.text.DefaultCaret; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.Cert; +import org.bitbatzen.tlsserverscanner.scantask.ScanData; +import org.bitbatzen.tlsserverscanner.scantask.ScanTaskHandler; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class DialogSearch extends JDialog implements ActionListener { + + private class SearchResult { + public HostListItem hostListItem; + public List resultTypes = new ArrayList<>(); + } + + private MainWindow mainWindow; + + private JEditorPane taResults; + + private JTextField tfSearch; + + private JCheckBox cbHostname; + private JCheckBox cbCipherSuiteChosenByServer; + private JCheckBox cbCertificate; + private JCheckBox cbFullCertificateChain; + private JCheckBox cbSupportedCipherSuites; + private JCheckBox cbSupportedProtocols; + + private JButton buttonSearch; + + private JLabel labelSearchInfo; + + private List results; + + + public DialogSearch(MainWindow mainWindow) { + super(mainWindow.getFrame(), "Full-Text Search", false); + this.mainWindow = mainWindow; + + results = new ArrayList<>(); + + final Dimension winSize = new Dimension(650, 550); + final Dimension resultsSize = new Dimension(1000, 250); + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setResizable(false); + setSize(winSize); + + // results area + taResults = new JEditorPane(); + taResults.setFont(MainWindow.FONT_MEDIUM); + taResults.setEditable(false); + taResults.setContentType("text/html"); + DefaultCaret caret = (DefaultCaret) taResults.getCaret(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + + JScrollPane scrollPaneResults = new JScrollPane(taResults); + scrollPaneResults.setPreferredSize(resultsSize); + + JPanel panelSearchOptions = new JPanel(); + BoxLayout layout = new BoxLayout(panelSearchOptions, BoxLayout.Y_AXIS); + panelSearchOptions.setLayout(layout); + panelSearchOptions.setBorder(BorderFactory.createEmptyBorder(15, 25, 25, 25)); + + tfSearch = new JTextField(); + tfSearch.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + tfSearch.setFont(MainWindow.FONT_MEDIUM); + panelSearchOptions.add(tfSearch); + + panelSearchOptions.add(Box.createRigidArea(new Dimension(0, 5))); + + cbHostname = new JCheckBox("Hostname"); + cbHostname.setSelected(true); + panelSearchOptions.add(cbHostname); + + cbCipherSuiteChosenByServer = new JCheckBox("Cipher Suite Chosen By Server"); + cbCipherSuiteChosenByServer.setSelected(true); + panelSearchOptions.add(cbCipherSuiteChosenByServer); + + cbCertificate = new JCheckBox("Certificate"); + cbCertificate.setSelected(true); + panelSearchOptions.add(cbCertificate); + + cbFullCertificateChain = new JCheckBox("Full Certificate Chain"); + cbFullCertificateChain.setSelected(true); + panelSearchOptions.add(cbFullCertificateChain); + + cbSupportedCipherSuites = new JCheckBox("Supported Cipher Suites"); + cbSupportedCipherSuites.setSelected(true); + panelSearchOptions.add(cbSupportedCipherSuites); + + cbSupportedProtocols = new JCheckBox("Supported Protocols"); + cbSupportedProtocols.setSelected(true); + panelSearchOptions.add(cbSupportedProtocols); + + panelSearchOptions.add(Box.createRigidArea(new Dimension(0, 10))); + + buttonSearch = new JButton("Search"); + buttonSearch.addActionListener(this); + panelSearchOptions.add(buttonSearch); + + panelSearchOptions.add(Box.createRigidArea(new Dimension(0, 10))); + + labelSearchInfo = new JLabel(" "); + panelSearchOptions.add(labelSearchInfo); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(panelSearchOptions, BorderLayout.NORTH); + getContentPane().add(scrollPaneResults, BorderLayout.SOUTH); + + Dimension parentSize = mainWindow.getFrame().getSize(); + Point p = mainWindow.getFrame().getLocation(); + setLocation(p.x + parentSize.width / 2 - getWidth() / 2, p.y + 50); + setVisible(true); + } + + private void search() { + String search = tfSearch.getText(); + if (search.equals("")) { + mainWindow.showMessageDialog("Error", "No text in search box!"); + return; + } + + search = search.toLowerCase(); + results.clear(); + + // hostnames + if (cbHostname.isSelected()) { + HostListItem[] hostListItems = mainWindow.getAreaHosts().getHostListItems(); + for (HostListItem host : hostListItems) { + if (host.getHostWithPort().toLowerCase().contains(search)) { + addResult(host, "Hostname"); + } + } + } + + ScanTaskHandler scanTaskHandler = mainWindow.getAreaControls().getScanTaskHandler(); + if (scanTaskHandler == null) { + setResultsText(); + return; + } + + HostListItem[] hostListItems = mainWindow.getAreaHosts().getHostListItems(); + + for (HostListItem hostListItem : hostListItems) { + if (hostListItem.getScanTask() == null) { + continue; + } + + ScanData sd = hostListItem.getScanTask().getScanData(); + + // server-side chosen cipher suites + if (cbCipherSuiteChosenByServer.isSelected()) { + if (sd.cipherSuiteChosenByServer.toLowerCase().contains(search)) { + addResult(hostListItem, "CipherSuiteChosenByServer"); + } + } + + // certificates + if (cbCertificate.isSelected()) { + if (sd.getCertAvailable()) { + findInCertificate(hostListItem, search, sd.certs[0], "Certificate"); + } + } + + // full certificate chain + if (cbFullCertificateChain.isSelected()) { + if (sd.getCertAvailable() && sd.certs.length > 1) { + for (int i = 1; i < sd.certs.length; i++) { + if (findInCertificate(hostListItem, search, sd.certs[i], "FullCertificateChain")) { + break; + } + } + } + } + + // supported cipher suites + if (cbSupportedCipherSuites.isSelected()) { + HashMap cipherSuitesTested = sd.cipherSuitesTested; + for (Map.Entry entry : cipherSuitesTested.entrySet()) { + if (entry.getValue()) { + // (supported) + String cipherSuite = scanTaskHandler.getCipherSuiteToTest(entry.getKey()); + if (cipherSuite.toLowerCase().contains(search)) { + addResult(hostListItem, "SupportedCipherSuites"); + break; + } + } + } + } + + // supported protocols + if (cbSupportedProtocols.isSelected()) { + HashMap protocolsTested = sd.protocolsTested; + for (Map.Entry entry : protocolsTested.entrySet()) { + if (entry.getValue()) { + // (supported) + String protocol = scanTaskHandler.getProtocolToTest(entry.getKey()); + if (protocol.toLowerCase().contains(search)) { + addResult(hostListItem, "SupportedProtocols"); + break; + } + } + } + } + } + + + setResultsText(); + } + + private boolean findInCertificate(HostListItem hostListItem, String searchString, Cert cert, String resultType) { + if (cert == null) { + return false; + } + + if (cert.getCert().toString().toLowerCase().contains(searchString) + || cert.getSubjectName().toLowerCase().contains(searchString) + || cert.getIssuerName().toLowerCase().contains(searchString) + || cert.getPublicKeyAlgorithmWithKeyLength().toLowerCase().contains(searchString) + || cert.getSignatureAlgorithm().toLowerCase().contains(searchString)) { + + addResult(hostListItem, resultType); + return true; + } + else { + List oids = cert.getExtensionOIDsWithName(); + for (String oid : oids) { + if (oid.toLowerCase().contains(searchString)) { + addResult(hostListItem, resultType); + return true; + } + } + } + + return false; + } + + private void addResult(HostListItem hostListItem, String resultType) { + for (SearchResult sr : results) { + if (sr.hostListItem == hostListItem) { + sr.resultTypes.add(resultType); + return; + } + } + + // create new entry + SearchResult sr = new SearchResult(); + sr.hostListItem = hostListItem; + sr.resultTypes.add(resultType); + results.add(sr); + } + + private void setResultsText() { + if (results.size() == 0) { + taResults.setText(""); + } + else { + StringBuilder sb = new StringBuilder(); + sb.append(Util.getFontFamilyTag(MainWindow.FONT_MEDIUM)); + + for (SearchResult sr : results) { + sb.append("" + sr.hostListItem.getHostWithPortAndIndex() + ""); + List resultTypes = sr.resultTypes; + sb.append(" ("); + + for (int i = 0; i < resultTypes.size(); i++) { + String rt = resultTypes.get(i); + if (i < resultTypes.size() -1) { + sb.append(rt + ", "); + } + else { + sb.append(rt); + } + } + + sb.append(")"); + sb.append(Util.BR); + } + + taResults.setText(sb.toString()); + } + + if (results.size() == 1) { + labelSearchInfo.setText(results.size() + " hit"); + } + else { + labelSearchInfo.setText(results.size() + " hits"); + } + } + + @Override + public void actionPerformed(ActionEvent arg0) { + if (arg0.getSource() == buttonSearch) { + search(); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuites.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuites.java new file mode 100644 index 0000000..89e49f6 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuites.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.SSLUtil; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class DialogSelectCipherSuites extends JDialog implements ActionListener, WindowListener { + + public enum DialogType { + CIPHER_SUITES_TO_TEST, + CIPHER_SUITES_FOR_COLLECTING_CERT + } + + private MainWindow mainWindow; + + private JButton buttonOk; + + private JLabel labelCiphersInfo; + + private JCheckBox cbToggleAll; + private JCheckBox cbSelectFiltered; + + private JTextField tfFilter; + private JLabel labelFilterInfo; + + private List cipherSuites; + + private boolean selectionChanged; + + private DialogType dialogType; + + private static DialogSelectCipherSuitesData[] data = null; + + + public DialogSelectCipherSuites(MainWindow mainWindow, DialogType dialogType, String title) { + super(mainWindow.getFrame(), title, true); + + String header = ""; + if (dialogType == DialogType.CIPHER_SUITES_TO_TEST) { + header = "Select cipher suites to test!"; + } + else if (dialogType == DialogType.CIPHER_SUITES_FOR_COLLECTING_CERT) { + header = "Select supported cipher suites for" + + Util.BR + "fetching the certificates!"; + } + + this.mainWindow = mainWindow; + this.dialogType = dialogType; + selectionChanged = false; + + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + addWindowListener(this); + setSize(550, 630); + + JPanel panel = new JPanel(); + BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS); + panel.setLayout(layout); + panel.setBorder(BorderFactory.createEmptyBorder(15, 25, 25, 25)); + + // header label + JLabel labelHeader = new JLabel("" + header + ""); + labelHeader.setFont(MainWindow.FONT_HUGE); + labelHeader.setForeground(MainWindow.COLOR_DIALOG_HEADER); + labelHeader.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(labelHeader); + + // info label + labelCiphersInfo = new JLabel("Available cipher suites are depending on the installed" + + "
version of the java runtime environment! (current: " + Util.getJavaVersionString() + ")"); + labelCiphersInfo.setFont(MainWindow.FONT_SMALL); + labelCiphersInfo.setForeground(MainWindow.COLOR_HINT); + labelCiphersInfo.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(labelCiphersInfo); + + // checkbox toggle all + cbToggleAll = new JCheckBox("Toggle All"); + cbToggleAll.setSelected(getData().toggleAllSelected); + cbToggleAll.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + selectionChanged = true; + for (JCheckBox cb : cipherSuites) { + cb.setSelected(cbToggleAll.isSelected()); + } + } + }); + panel.add(cbToggleAll); + + panel.add(Box.createRigidArea(new Dimension(0, 5))); + + // checkbox select filtered + cbSelectFiltered = new JCheckBox("Select Filtered"); + cbSelectFiltered.setSelected(getData().selectFilteredSelected); + cbSelectFiltered.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent arg0) { + selectionChanged = true; + onFilterChanged(cbSelectFiltered.isSelected()); + } + }); + panel.add(cbSelectFiltered); + + panel.add(Box.createRigidArea(new Dimension(0, 5))); + + // filter + tfFilter = new JTextField(getData().filterSting); + tfFilter.setFont(MainWindow.FONT_MEDIUM); + tfFilter.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); + tfFilter.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + onFilterChanged(cbSelectFiltered.isSelected()); + } + @Override + public void insertUpdate(DocumentEvent e) { + onFilterChanged(cbSelectFiltered.isSelected()); + } + @Override + public void changedUpdate(DocumentEvent e) { + onFilterChanged(cbSelectFiltered.isSelected()); + } + }); + panel.add(tfFilter); + + labelFilterInfo = new JLabel("(filter syntax: whitespace seperated values (aes ecdhe))"); + labelFilterInfo.setFont(MainWindow.FONT_SMALL); + labelFilterInfo.setForeground(Color.GRAY); + labelFilterInfo.setBorder(BorderFactory.createEmptyBorder(5, 0, 20, 0)); + panel.add(labelFilterInfo); + + // available cipher suites + JScrollPane scrollPane = new JScrollPane(panel); + getContentPane().add(scrollPane, BorderLayout.CENTER); + scrollPane.setPreferredSize(new Dimension(450, 600)); + + List availableCipherSuites = SSLUtil.getAvailableCipherSuites(); + Collections.sort(availableCipherSuites); + + cipherSuites = new ArrayList(); + for (String s : availableCipherSuites) { + JCheckBox cb = new JCheckBox(s, getData().selectedCipherSuites.contains(s)); + cb.addActionListener(this); + cipherSuites.add(cb); + panel.add(cb); + } + + panel.add(Box.createRigidArea(new Dimension(0, 20))); + + // button ok + buttonOk = new JButton("Ok"); + buttonOk.addActionListener(this); + panel.add(buttonOk); + + onFilterChanged(false); + + Dimension parentSize = mainWindow.getFrame().getSize(); + Point p = mainWindow.getFrame().getLocation(); + setLocation(p.x + parentSize.width / 2 - getWidth() / 2, p.y + 50); + setVisible(true); + } + + private static DialogSelectCipherSuitesData getData(DialogType dialogType) { + if (data == null) { + data = new DialogSelectCipherSuitesData[2]; + data[0] = new DialogSelectCipherSuitesData(); + data[1] = new DialogSelectCipherSuitesData(); + data[1].selectedCipherSuites = SSLUtil.getAvailableCipherSuites(); + } + + if (dialogType == DialogType.CIPHER_SUITES_TO_TEST) { + return data[0]; + } + else { + return data[1]; + } + } + + private DialogSelectCipherSuitesData getData() { + return getData(dialogType); + } + + public static List getSelectedCipherSuites(DialogType dialogType) { + return getData(dialogType).selectedCipherSuites; + } + + private void setElementsEnabled(boolean enabled) { + cbSelectFiltered.setEnabled(enabled); + cbToggleAll.setEnabled(enabled); + tfFilter.setEnabled(enabled); + labelFilterInfo.setEnabled(enabled); + labelCiphersInfo.setEnabled(enabled); + for (JCheckBox cb : cipherSuites) { + cb.setEnabled(enabled); + } + } + + private void onFilterChanged(boolean selectFiltered) { + if (selectFiltered) { + selectionChanged = true; + } + + String[] filter = tfFilter.getText().split(" "); + for (JCheckBox cb : cipherSuites) { + boolean filtered = true; + for (String s : filter) { + if (cb.getText().contains(s.toUpperCase()) == false) { + filtered = false; + break; + } + } + + if (filtered) { + cb.setForeground(Color.BLACK); + } + else { + cb.setForeground(Color.LIGHT_GRAY); + } + + if (selectFiltered) { + cb.setSelected(filtered); + } + } + } + + private boolean checkSelectedCipherSuitesCount() { + if (dialogType == DialogType.CIPHER_SUITES_FOR_COLLECTING_CERT) { + int count = 0; + for (JCheckBox cb : cipherSuites) { + if (cb.isSelected()) { + count++; + } + } + if (count == 0) { + mainWindow.showMessageDialog("Cipher Suites", "At least one cipher suite has to be selected!"); + return false; + } + else { + return true; + } + } + else { + return true; + } + } + + private void save() { + DialogSelectCipherSuitesData data = getData(); + + data.selectFilteredSelected = cbSelectFiltered.isSelected(); + data.toggleAllSelected = cbToggleAll.isSelected(); + data.filterSting = tfFilter.getText(); + + data.selectedCipherSuites.clear(); + for (JCheckBox cb : cipherSuites) { + if (cb.isSelected()) { + data.selectedCipherSuites.add(cb.getText()); + } + } + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == buttonOk) { + if (checkSelectedCipherSuitesCount()) { + save(); + setVisible(false); + dispose(); + } + } + else if (e.getSource() instanceof JCheckBox) { + selectionChanged = true; + } + } + + @Override + public void windowActivated(WindowEvent arg0) { + } + + @Override + public void windowClosed(WindowEvent arg0) { + } + + @Override + public void windowClosing(WindowEvent arg0) { + if (selectionChanged) { + if (mainWindow.showConfirmDialog("Close", "Save changes?")) { + if (checkSelectedCipherSuitesCount()) { + save(); + } + else { + return; + } + } + } + + setVisible(false); + dispose(); + } + + @Override + public void windowDeactivated(WindowEvent arg0) { + } + + @Override + public void windowDeiconified(WindowEvent arg0) { + } + + @Override + public void windowIconified(WindowEvent arg0) { + } + + @Override + public void windowOpened(WindowEvent arg0) { + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuitesData.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuitesData.java new file mode 100644 index 0000000..997aa47 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectCipherSuitesData.java @@ -0,0 +1,13 @@ +package org.bitbatzen.tlsserverscanner.gui; + +import java.util.ArrayList; +import java.util.List; + + +public class DialogSelectCipherSuitesData { + + public boolean toggleAllSelected = false; + public boolean selectFilteredSelected = false; + public String filterSting = ""; + public List selectedCipherSuites = new ArrayList<>(); +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectProtocols.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectProtocols.java new file mode 100644 index 0000000..248bee3 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogSelectProtocols.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.SSLUtil; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.List; + + +public class DialogSelectProtocols extends JDialog implements ActionListener, WindowListener { + + private MainWindow mainWindow; + + private JButton buttonOk; + + private JLabel labelProtocolsInfo; + + private List protocols; + + private boolean selectionChanged; + + private static List selectedProtocols = new ArrayList<>(); + + + public DialogSelectProtocols(MainWindow mainWindow) { + super(mainWindow.getFrame(), "Protocols", true); + + this.mainWindow = mainWindow; + selectionChanged = false; + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + addWindowListener(this); + setSize(500, 350); + + JPanel panel = new JPanel(); + BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS); + panel.setLayout(layout); + panel.setBorder(BorderFactory.createEmptyBorder(15, 25, 25, 25)); + + // header label + JLabel labelHeader = new JLabel("Select protocols to test!"); + labelHeader.setFont(MainWindow.FONT_HUGE); + labelHeader.setForeground(MainWindow.COLOR_DIALOG_HEADER); + labelHeader.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(labelHeader); + + // info label + labelProtocolsInfo = new JLabel("Available protocols are depending on the installed" + + "
version of the java runtime environment! (current: " + Util.getJavaVersionString() + ")"); + labelProtocolsInfo.setFont(MainWindow.FONT_SMALL); + labelProtocolsInfo.setForeground(MainWindow.COLOR_HINT); + labelProtocolsInfo.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + panel.add(labelProtocolsInfo); + + // available protocols + JScrollPane scrollPane = new JScrollPane(panel); + getContentPane().add(scrollPane, BorderLayout.CENTER); + scrollPane.setPreferredSize(new Dimension(450, 600)); + + List availableProtocols = SSLUtil.getAvailableProtocols(); +// Collections.sort(availableProtocols); + + protocols = new ArrayList(); + for (String s : availableProtocols) { + JCheckBox cb = new JCheckBox(s, selectedProtocols.contains(s)); + cb.addActionListener(this); + protocols.add(cb); + panel.add(cb); + } + + panel.add(Box.createRigidArea(new Dimension(0, 20))); + + // button ok + buttonOk = new JButton("Ok"); + buttonOk.addActionListener(this); + panel.add(buttonOk); + + Dimension parentSize = mainWindow.getFrame().getSize(); + Point p = mainWindow.getFrame().getLocation(); + setLocation(p.x + parentSize.width / 2 - getWidth() / 2, p.y + 50); + setVisible(true); + } + + public static final List getSelectedProtocols() { + return selectedProtocols; + } + + private void save() { + selectedProtocols.clear(); + for (JCheckBox cb : protocols) { + if (cb.isSelected()) { + selectedProtocols.add(cb.getText()); + } + } + } + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == buttonOk) { + save(); + setVisible(false); + dispose(); + } + else if (e.getSource() instanceof JCheckBox) { + selectionChanged = true; + } + } + + @Override + public void windowActivated(WindowEvent arg0) { + } + + @Override + public void windowClosed(WindowEvent arg0) { + } + + @Override + public void windowClosing(WindowEvent arg0) { + if (selectionChanged) { + if (mainWindow.showConfirmDialog("Close", "Save changes?")) { + save(); + } + } + } + + @Override + public void windowDeactivated(WindowEvent arg0) { + } + + @Override + public void windowDeiconified(WindowEvent arg0) { + } + + @Override + public void windowIconified(WindowEvent arg0) { + } + + @Override + public void windowOpened(WindowEvent arg0) { + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/DialogViewCertificates.java b/src/org/bitbatzen/tlsserverscanner/gui/DialogViewCertificates.java new file mode 100644 index 0000000..d92ab3f --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/DialogViewCertificates.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.DefaultListModel; +import javax.swing.JDialog; +import javax.swing.JEditorPane; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.DefaultCaret; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.Cert; +import org.bitbatzen.tlsserverscanner.scantask.ScanData; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.text.SimpleDateFormat; +import java.util.List; + + +public class DialogViewCertificates extends JDialog { + + private static final String BR = Util.BR; + private static final String H2_S = ""; + private static final String H2_E = ""; + + private MainWindow mainWindow; + + private SimpleDateFormat dateFormat; + + private JList certificatesList; + private JEditorPane textArea; + + private HostListItem host; + + + public DialogViewCertificates(MainWindow mainWindow, String title, HostListItem host) { + super(mainWindow.getFrame(), title, false); + this.mainWindow = mainWindow; + this.host = host; + + dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + + final Dimension winSize = new Dimension(800, 600); + final Dimension resultsSize = new Dimension(550, 600); + final Dimension certListSize = new Dimension(240, 600); + + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setResizable(false); + setSize(winSize); + + // results area + textArea = new JEditorPane(); + textArea.setFont(MainWindow.FONT_MEDIUM); + textArea.setEditable(false); + DefaultCaret caret = (DefaultCaret) textArea.getCaret(); + caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); + + JScrollPane scrollPaneResults = new JScrollPane(textArea); + scrollPaneResults.setPreferredSize(resultsSize); + + // certificates list + DefaultListModel listModel = new DefaultListModel(); + certificatesList = new JList(listModel); + certificatesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + certificatesList.setLayoutOrientation(JList.VERTICAL_WRAP); + certificatesList.setVisibleRowCount(-1); + certificatesList.setBackground(new Color(220, 220, 220)); + JScrollPane scrollPaneCertList = new JScrollPane(certificatesList); + scrollPaneCertList.setPreferredSize(certListSize); + + certificatesList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + showCertificate(certificatesList.getSelectedIndex()); + } + }); + + String[] certNames = host.getScanTask().getScanData().getCertNames(); + for (String name : certNames) { + listModel.addElement(name); + } + + certificatesList.setSelectedIndex(0); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(scrollPaneCertList, BorderLayout.WEST); + getContentPane().add(scrollPaneResults, BorderLayout.EAST); + + Dimension parentSize = mainWindow.getFrame().getSize(); + Point p = mainWindow.getFrame().getLocation(); + setLocation(p.x + parentSize.width / 2 - getWidth() / 2, p.y + 50); + setVisible(true); + } + + private void showCertificate(int index) { + String result = getHTMLCertificateData(host, host.getScanTask().getScanData().certs[index]); + + boolean plainTextEnabled = false; + String contentType = plainTextEnabled ? "text/txt" : "text/html"; + textArea.setContentType(contentType); + if (plainTextEnabled) { + result = result.replace("
", Util.LF); + result = result.replaceAll("\\<.*?>", ""); + } + + textArea.setText(result); + } + + private String getHTMLCertificateData(HostListItem host, Cert cert) { + ScanData sd = host.getScanTask().getScanData(); + StringBuilder sb = new StringBuilder(2500); + + sb.append(Util.getFontFamilyTag(MainWindow.FONT_MEDIUM)); + + sb.append(H2_S + "Certificate Format: " + H2_E + BR); + sb.append("Type: " + cert.getType() + BR); + sb.append("Version: " + cert.getVersion() + BR); + sb.append(BR); + + String certVerification = ""; + if (host.getScanTask().getScanTaskHandler().getCertAndHostnameVerificationEnabled() == false) { + certVerification = H2_S + Util.getBGColorTag(Color.RED) + "(certificate verification is disabled!)" + Util.COLOR_END_TAG + H2_E; + } + + boolean isSelfSigned = sd.getIsCertSelfSigned(); + String selfSignedString = isSelfSigned ? "(self-signed) " : ""; + sb.append(H2_S + "Subject: " + selfSignedString + H2_E + certVerification + BR); + sb.append(Cert.formatCertName2(cert.getSubjectName())); + sb.append(BR); + + sb.append(H2_S + "Issuer: " + selfSignedString + H2_E + BR); + sb.append(Cert.formatCertName2(cert.getIssuerName())); + sb.append(BR); + + sb.append(H2_S + "Not Before: " + H2_E + BR); + sb.append(dateFormat.format(cert.getNotBefore()) + BR); + sb.append(BR); + + sb.append(H2_S + "Not After: " + H2_E + BR); + sb.append(dateFormat.format(cert.getNotAfter()) + BR); + sb.append(BR); + + String[] alternativeNames = cert.getSubjectAlternativeNames(); + if (alternativeNames != null && alternativeNames.length > 0) { + sb.append(H2_S + "Subject Alternative Names:" + H2_E + BR); + for (String s : alternativeNames) { + sb.append(s + BR); + } + sb.append(BR); + } + + sb.append(H2_S + "Extensions: " + H2_E + BR); + List oids = cert.getExtensionOIDsWithName(); + for (String oid : oids) { + sb.append(oid + BR); + } + sb.append(BR); + + sb.append(H2_S + "Public Key Algorithm: " + H2_E + BR); + sb.append(cert.getPublicKeyAlgorithmWithKeyLength() + BR); + sb.append(BR); + + sb.append(H2_S + "Signature Algorithm: " + H2_E + BR); + sb.append(cert.getSignatureAlgorithm() + BR); + sb.append(BR); + + return sb.toString(); + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/HostListCellRenderer.java b/src/org/bitbatzen/tlsserverscanner/gui/HostListCellRenderer.java new file mode 100644 index 0000000..a787a48 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/HostListCellRenderer.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; + +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + + +public class HostListCellRenderer extends JLabel implements ListCellRenderer { + + + public HostListCellRenderer() { + setOpaque(true); + setFont(MainWindow.FONT_BIG.deriveFont(Font.BOLD)); + } + + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) { + + HostListItem item = (HostListItem) value; + setText(item.getText()); + + if (isSelected) { + setBackground(Color.DARK_GRAY); + setForeground(Color.WHITE); + } + else { + setBackground(item.getBGColor()); + setForeground(Color.DARK_GRAY); + } + + return this; + } +} + diff --git a/src/org/bitbatzen/tlsserverscanner/gui/HostListItem.java b/src/org/bitbatzen/tlsserverscanner/gui/HostListItem.java new file mode 100644 index 0000000..ea272a4 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/HostListItem.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.awt.Color; + +import javax.swing.SwingUtilities; + +import org.bitbatzen.tlsserverscanner.scantask.IScanTaskListener; +import org.bitbatzen.tlsserverscanner.scantask.ScanTask; + + +public class HostListItem implements IScanTaskListener { + + private String host; + private int port; + + private int index; + + private String text; + + private Color bgColor; + + private ScanTask scanTask; + private HostListModel listModel; + + + public HostListItem(String host, int port, HostListModel listModel) { + this.host = host; + this.port = port; + this.listModel = listModel; + + scanTask = null; + bgColor = MainWindow.COLOR_BG_HOSTS; + + setText(ScanTask.getStateTag(ScanTask.State.NONE)); + + index = listModel.getIndex(this); + listModel.update(index); + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getHostWithPort() { + return host + ":" + port; + } + + public String getHostWithPortAndIndex() { + return getIndexString() + " " + getHostWithPort(); + } + + public void setScanTask(ScanTask scanTask) { + this.scanTask = scanTask; + scanTask.addListener(this); + } + + public String getText() { + return text; + } + + public ScanTask getScanTask() { + return scanTask; + } + + public Color getBGColor() { + return bgColor; + } + + public String getIndexString() { + int hostCount = Integer.toString(listModel.size()).length(); + int indexCount = Integer.toString(index + 1).length(); + String spacer = ""; + for (int i = 0; i < hostCount - indexCount; i++) { + spacer += "0"; + } + + return "[" + spacer + (index + 1) + "]"; + } + + public void update() { + ScanTask.State state = ScanTask.getState(scanTask); + bgColor = ScanTask.getStateColor(state); + index = listModel.getIndex(this); + setText(ScanTask.getStateTag(state)); + + listModel.update(index); + } + + private void setText(String state) { + text = getIndexString() + state + " " + getHostWithPort(); + } + + @Override + public synchronized void onScanTaskStateChanged(ScanTask scanTask, String message) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + update(); + } + }); + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/HostListModel.java b/src/org/bitbatzen/tlsserverscanner/gui/HostListModel.java new file mode 100644 index 0000000..f52e2ff --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/HostListModel.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import javax.swing.DefaultListModel; + + +public class HostListModel extends DefaultListModel { + + public void update(int index) { + fireContentsChanged(this, index, index); + } + + public int getIndex(HostListItem element) { + for (int i = 0; i < getSize(); i++) { + if (get(i) == element) { + return i; + } + } + + return -1; + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/MainWindow.java b/src/org/bitbatzen/tlsserverscanner/gui/MainWindow.java new file mode 100644 index 0000000..94e9668 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/MainWindow.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Point; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.util.Locale; + +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.UIManager; + +import org.bitbatzen.tlsserverscanner.Util; + + +public class MainWindow implements ComponentListener { + + public static final Font FONT_LOG = new Font("Monospaced", Font.PLAIN, 13); + public static final Font FONT_SMALL = new Font("Monospaced", Font.PLAIN, 12); + public static final Font FONT_MEDIUM = new Font("Monospaced", Font.PLAIN, 14); + public static final Font FONT_BIG = new Font("Monospaced", Font.PLAIN, 16); + public static final Font FONT_HUGE = new Font("Monospaced", Font.PLAIN, 18); + + public static final Color COLOR_HINT = Color.BLUE; + public static final Color COLOR_DIALOG_HEADER = Color.DARK_GRAY; + + public static final Color COLOR_BG_RESULTS = new Color(255, 255, 255); + public static final Color COLOR_BG_HOSTS = new Color(255, 255, 255); + public static final Color COLOR_BG_LOG = new Color(220, 220, 220); + public static final Color COLOR_BG_CONTROLS = new Color(220, 220, 220); + public static final Color COLOR_BG_MENUBAR = new Color(220, 220, 220); + + public static final int HOST_AREA_WIDTH = 350; + + private AreaLog areaLog; + private AreaControls areaControls; + private AreaHosts areaHosts; + private AreaResults areaResults; + private MyMenuBar myMenuBar; + + private JPanel containerPanel; + + private JFrame frame; + + + public MainWindow() { + areaControls = new AreaControls(this); + areaLog = new AreaLog(this); + areaHosts = new AreaHosts(this); + areaResults = new AreaResults(this); + + initWindow(); + + showAppInfoDialog(true); + } + + public AreaLog getAreaLog() { + return areaLog; + } + + public AreaControls getAreaControls() { + return areaControls; + } + + public AreaHosts getAreaHosts() { + return areaHosts; + } + + public AreaResults getAreaResults() { + return areaResults; + } + + public MyMenuBar getMyMenuBar() { + return myMenuBar; + } + + public JFrame getFrame() { + return frame; + } + + private void initWindow() { + Locale.setDefault(java.util.Locale.ENGLISH); + JComponent.setDefaultLocale(Locale.ENGLISH); + UIManager.put("OptionPane.messageFont", FONT_MEDIUM); + + frame = new JFrame(Util.T_APP_NAME); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.addComponentListener(this); + frame.setSize(1200, 850); + + containerPanel = new JPanel(); + frame.getContentPane().add(containerPanel); + + containerPanel.add(areaControls.getPanel()); + containerPanel.add(areaHosts.getPanel()); + containerPanel.add(areaResults.getPanel()); + containerPanel.add(areaLog.getPanel()); + + myMenuBar = new MyMenuBar(this); + frame.setJMenuBar(myMenuBar); + + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + + public void setDefaultPopupPosition(JDialog popup) { + Dimension parentSize = frame.getSize(); + Point p = frame.getLocation(); + popup.setLocation(p.x + parentSize.width / 2 - popup.getWidth() / 2, p.y + 150); + } + + public void showAppInfoDialog(boolean isIntro) { + StringBuilder sb = new StringBuilder(); + + sb.append(Util.T_APP_NAME + " " + Util.T_APP_VERSION + Util.LF); + sb.append(Util.LF); + + if (!isIntro) { + sb.append(Util.T_APP_NAME + " is licensed under the " + Util.T_APP_LICENSE + "." + Util.LF); + sb.append(Util.LF); + } + + sb.append("WARNING: Scanning can generate undesired load on the servers!" + Util.LF); + sb.append("DO NOT use this program if you are unsure about the consequences!" + Util.LF); + sb.append(Util.LF); + + if (!isIntro) { + sb.append("Developer: " + Util.T_AUTOR + Util.LF); + sb.append("Contact: " + Util.T_CONTACT_EMAIL + Util.LF); + sb.append("Code: " + Util.T_CODE_URL + Util.LF); + } + + showMessageDialog("About", sb.toString()); + } + + public boolean showConfirmDialog(String title, String text) { + DialogConfirm dialog = new DialogConfirm(this, title, text); + return dialog.showDialog(); + } + + public void showMessageDialog(String title, String text) { + DialogMessage dialog = new DialogMessage(this, title, text); + dialog.showDialog(); + } + + @Override + public void componentResized(ComponentEvent e) { + int cWidth = containerPanel.getWidth(); + int cHeight = containerPanel.getHeight(); + + final int controlAreaHeight = 40; + final int logAreaHeight = 200; + + areaControls.setPanelBounds(0, 0, cWidth, controlAreaHeight); + + areaLog.setPanelBounds(0, cHeight - logAreaHeight, cWidth, logAreaHeight); + + int centerAreasHeight = cHeight - logAreaHeight - controlAreaHeight; + areaHosts.setPanelBounds(0, controlAreaHeight, HOST_AREA_WIDTH, centerAreasHeight); + + int resultsAreaWidth = cWidth - HOST_AREA_WIDTH; + areaResults.setPanelBounds(HOST_AREA_WIDTH, controlAreaHeight, resultsAreaWidth, centerAreasHeight); + } + + @Override + public void componentHidden(ComponentEvent e) { + } + + @Override + public void componentMoved(ComponentEvent e) { + } + + @Override + public void componentShown(ComponentEvent e) { + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/MyMenuBar.java b/src/org/bitbatzen/tlsserverscanner/gui/MyMenuBar.java new file mode 100644 index 0000000..6360fc0 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/MyMenuBar.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JFileChooser; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.gui.DialogSelectCipherSuites.DialogType; +import org.bitbatzen.tlsserverscanner.scantask.ScanTask.State; +import org.bitbatzen.tlsserverscanner.scantask.ScanTaskHandler; + + +public class MyMenuBar extends JMenuBar implements ActionListener { + + // menu file + private JMenuItem itemSaveHostList; + private JMenuItem itemLoadHostList; + private JMenuItem itemQuit; + + // menu options + private JMenuItem itemCertOptions; + private JMenuItem itemSelectCipherSuites; + private JMenuItem itemSelectProtocols; + private JCheckBoxMenuItem itemCBDisableCertCollecting; + private JCheckBoxMenuItem itemCBDisableCertVerification; + + // menu tools + private JMenuItem itemViewCertificates; + private JMenuItem itemSaveSelectedCertificates; + private JMenuItem itemSearch; + + // menu info + private JMenuItem itemAbout; + + private MainWindow mainWindow; + + private Font font; + + + public MyMenuBar(MainWindow mainWindow) { + this.mainWindow = mainWindow; + font = new JMenuItem().getFont().deriveFont(13.0f); + initMenuBar(); + } + + + private void initMenuBar() { + setBackground(MainWindow.COLOR_BG_MENUBAR); + + // file + JMenu menuFile = createMenu("File"); + itemSaveHostList = createMenuItem("Save Hostlist"); + menuFile.add(itemSaveHostList); + + itemLoadHostList = createMenuItem("Load Hostlist"); + menuFile.add(itemLoadHostList); + + itemQuit = createMenuItem("Quit"); + menuFile.add(itemQuit); + + // options + JMenu menuOptions = createMenu("Options"); + itemCertOptions = createMenuItem("Certificate"); + menuOptions.add(itemCertOptions); + + itemSelectCipherSuites = createMenuItem("Cipher Suites"); + menuOptions.add(itemSelectCipherSuites); + + itemSelectProtocols = createMenuItem("Protocols"); + menuOptions.add(itemSelectProtocols); + + // checkbox disable cert. collecting + itemCBDisableCertCollecting = new JCheckBoxMenuItem("Disable Cert. Collecting"); + itemCBDisableCertCollecting.setFont(font); + menuOptions.add(itemCBDisableCertCollecting); + + // checkbox disable cert. verification + itemCBDisableCertVerification = new JCheckBoxMenuItem("Disable Cert. Verification"); + itemCBDisableCertVerification.setFont(font); + menuOptions.add(itemCBDisableCertVerification); + + // tools + JMenu menuTools = createMenu("Tools"); + itemViewCertificates = createMenuItem("View Certificates"); + menuTools.add(itemViewCertificates); + + itemSaveSelectedCertificates = createMenuItem("Save Selected Certificates"); + menuTools.add(itemSaveSelectedCertificates); + + itemSearch = createMenuItem("Full-Text Search"); + menuTools.add(itemSearch); + + // info + JMenu menuInfo = createMenu("Info"); + itemAbout = createMenuItem("About"); + menuInfo.add(itemAbout); + + add(menuFile); + add(menuOptions); + add(menuTools); + add(menuInfo); + } + + public boolean getCertVerificationDisabled() { + return itemCBDisableCertVerification.isSelected(); + } + + public boolean getCertCollectingDisabled() { + return itemCBDisableCertCollecting.isSelected(); + } + + private JMenuItem createMenuItem(String name) { + JMenuItem menuItem = new JMenuItem(name); + menuItem.setFont(font); + menuItem.addActionListener(this); + return menuItem; + } + + private JMenu createMenu(String name) { + JMenu menu = new JMenu(name); + menu.setFont(font); + return menu; + } + + private void onClickSaveHostList() { + String title = "Save Hostlist"; + String hostlist = mainWindow.getAreaHosts().getHostList(); + if (hostlist.equals("")) { + mainWindow.showMessageDialog(title, "Hostlist is empty!"); + return; + } + + final JFileChooser fc = new JFileChooser(title); + fc.setDialogTitle(title); + fc.setApproveButtonText("Save"); + fc.setApproveButtonToolTipText("Save"); + int returnVal = fc.showOpenDialog(mainWindow.getFrame()); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + String prefix = fc.getSelectedFile().getName().toLowerCase().contains(".txt") ? "" : ".txt"; + File file = new File(fc.getSelectedFile().getAbsolutePath() + prefix); + if (file.exists()) { + String text = "Replace " + file.getName() + " ?"; + if (mainWindow.showConfirmDialog(title, text) == false) { + return; + } + } + + try { + Util.saveHostlistToFile(hostlist, file); + } + catch (Exception e) { + mainWindow.showMessageDialog(title, "Failed to save file:" + + Util.BR + e.getMessage() + ""); + } + + mainWindow.showMessageDialog(title, "Saved hostlist to:" + + Util.BR + Util.BR + file.getPath() + ""); + } + } + + private void onClickLoadHostList() { + String title = "Load Hostlist"; + if (mainWindow.getAreaHosts().getHostListItemCount() > 0) { + if (mainWindow.showConfirmDialog(title, "The existing hostlist will be replaced!") == false) { + return; + } + } + + final JFileChooser fc = new JFileChooser(title); + fc.setDialogTitle(title); + int returnVal = fc.showOpenDialog(mainWindow.getFrame()); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + List hostlist = new ArrayList<>(); + + try { + int result = Util.loadHostlistFromFile(hostlist, fc.getSelectedFile()); + if (result != 0) { + mainWindow.showMessageDialog(title, "Syntax error in line " + result + + Util.BR + Util.BR + "(syntax: host:port)"); + return; + } + } + catch (Exception e) { + mainWindow.showMessageDialog(title, "Failed to read file:" + + Util.BR + e.getMessage() + ""); + return; + } + + if (hostlist.size() == 0) { + mainWindow.showMessageDialog(title, "Hostlist is empty!"); + return; + } + + if (hostlist.size() > 200) { + mainWindow.showMessageDialog(title, "" + hostlist.size() + " hosts to load!" + + Util.BR + "This could take a few seconds."); + } + + mainWindow.getAreaHosts().removeAllHosts(); + + for (String h : hostlist) { + String host = Util.extractHost(h); + int port = Util.extractPort(h); + mainWindow.getAreaHosts().addHost(host, port); + } + + mainWindow.showMessageDialog(title, "Added " + hostlist.size() + " hosts!"); + } + } + + private void onClickViewCertificate() { + HostListItem host = mainWindow.getAreaHosts().getSelectedItem(); + if (host == null) { + mainWindow.showMessageDialog("Certificates", "No host selected!"); + return; + } + else if (host.getScanTask() == null || host.getScanTask().getScanData().getCertAvailable() == false) { + mainWindow.showMessageDialog("Certificates", "No certificate found!"); + return; + } + + String title = "Certificates (" + host.getHostWithPort() + ")"; + new DialogViewCertificates(mainWindow, title, host); + } + + private void onClickQuit() { + if (mainWindow.showConfirmDialog("Quit", "Unsaved data will be lost!
Quit?")) { + mainWindow.getFrame().dispose(); + } + } + + private void onClickCertOptions() { + new DialogSelectCipherSuites(mainWindow, DialogType.CIPHER_SUITES_FOR_COLLECTING_CERT, "Certifcate"); + } + + private void onClickSelectCipherSuitesToTest() { + new DialogSelectCipherSuites(mainWindow, DialogType.CIPHER_SUITES_TO_TEST, "Cipher Suites"); + } + + private void onClickSelectProtocols() { + new DialogSelectProtocols(mainWindow); + } + + private void onClickSaveSelectedCertificates() { + String title = "Save Selected Certificates"; + List hosts = mainWindow.getAreaHosts().getSelectedHosts(); + if (hosts.isEmpty()) { + mainWindow.showMessageDialog(title, "No host selected!"); + return; + } + + int certificateCount = 0; + int allCertificatesCount = 0; + for (HostListItem host : hosts) { + if (host.getScanTask() != null && host.getScanTask().getScanData().getCertAvailable()) { + certificateCount++; + allCertificatesCount += host.getScanTask().getScanData().certs.length; + } + } + if (certificateCount == 0) { + mainWindow.showMessageDialog(title, "No certificates found!"); + return; + } + else { + String hostString = certificateCount == 1 ? "host" : "hosts"; + mainWindow.showMessageDialog(title, "Found " + allCertificatesCount + " certificates!" + + " (" + certificateCount + " " + hostString + " selected)"); + } + + + final JFileChooser fc = new JFileChooser(); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setDialogTitle("Select a Folder"); + fc.setApproveButtonText("Save"); + fc.setApproveButtonToolTipText("Save to Folder"); + int returnVal = fc.showOpenDialog(mainWindow.getFrame()); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + File directory = fc.getSelectedFile(); + + try { + Util.saveCertificatesToDirectory(hosts, directory); + } + catch (Exception e) { + mainWindow.showMessageDialog(title, "Failed to save certificates:" + + Util.BR + e.getMessage() + ""); + } + + mainWindow.showMessageDialog(title, "Saved certificates to:" + + Util.BR + Util.BR + directory.getPath() + ""); + } + } + + private void onClickSearch() { + ScanTaskHandler scanTaskHandler = mainWindow.getAreaControls().getScanTaskHandler(); + if (mainWindow.getAreaHosts().getHostListItemCount() == 0) { + mainWindow.showMessageDialog(itemSearch.getText(), "Hostlist is empty!"); + return; + } + else if (scanTaskHandler != null && scanTaskHandler.getState() == State.RUNNING) { + mainWindow.showMessageDialog(itemSearch.getText(), "You have to finish the current scan!"); + return; + } + + new DialogSearch(mainWindow); + } + + private void onClickAbout() { + mainWindow.showAppInfoDialog(false); + } + + @Override + public void actionPerformed(ActionEvent arg0) { + if (arg0.getSource() == itemSaveHostList) { + onClickSaveHostList(); + } + else if (arg0.getSource() == itemLoadHostList) { + onClickLoadHostList(); + } + else if (arg0.getSource() == itemSaveSelectedCertificates) { + onClickSaveSelectedCertificates(); + } + else if (arg0.getSource() == itemQuit) { + onClickQuit(); + } + else if (arg0.getSource() == itemCertOptions) { + onClickCertOptions(); + } + else if (arg0.getSource() == itemSelectCipherSuites) { + onClickSelectCipherSuitesToTest(); + } + else if (arg0.getSource() == itemSelectProtocols) { + onClickSelectProtocols(); + } + else if (arg0.getSource() == itemViewCertificates) { + onClickViewCertificate(); + } + else if (arg0.getSource() == itemSearch) { + onClickSearch(); + } + else if (arg0.getSource() == itemAbout) { + onClickAbout(); + } + } + +} diff --git a/src/org/bitbatzen/tlsserverscanner/gui/StatisticUtil.java b/src/org/bitbatzen/tlsserverscanner/gui/StatisticUtil.java new file mode 100644 index 0000000..93b1a29 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/gui/StatisticUtil.java @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.gui; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.Cert; +import org.bitbatzen.tlsserverscanner.scantask.SSLUtil; +import org.bitbatzen.tlsserverscanner.scantask.ScanTask; +import org.bitbatzen.tlsserverscanner.scantask.ScanTaskHandler; + + +public class StatisticUtil { + + + private static > Map sortByValues(final Map map) { + Comparator valueComparator = new Comparator() { + public int compare(K k1, K k2) { + int compare = map.get(k2).compareTo(map.get(k1)); + if (compare == 0) return 1; + else return compare; + } + }; + + Map sortedByValues = new TreeMap(valueComparator); + sortedByValues.putAll(map); + return sortedByValues; + } + + private static HashMap createEmptyStatisicMap(int size) { + HashMap statisticMap = new HashMap<>(); + for (int i = 0; i < size; i++) { + statisticMap.put(i, 0); + } + + return statisticMap; + } + + private static String getAmountString(float max, float v) { + if (v > 0) { + String percentage = String.format("%.2f", (float) v / (float) max * 100.0f); + return " (" + (int) v + ", " + percentage + "%)"; + } + else { + return " (" + (int) v + ")"; + } + } + + public static void appendCipherSuiteChosenFromServerStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = new HashMap<>(); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + if (st.getScanData().getCertAvailable() == false) { + continue; + } + + String sscCipherSuite = st.getScanData().cipherSuiteChosenByServer; + if (sscCipherSuite.isEmpty() == false) { + if (statisticMap.containsKey(sscCipherSuite)) { + int v = statisticMap.get(sscCipherSuite) + 1; + statisticMap.put(sscCipherSuite, v); + } + else { + statisticMap.put(sscCipherSuite, 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + sb.append(entry.getKey() + " " + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + + public static void appendProtocolStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = createEmptyStatisicMap(scanTaskHandler.getProtocolsToTestCount()); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + HashMap tested = st.getScanData().protocolsTested; + for (Map.Entry entry : tested.entrySet()) { + if (entry.getValue() == true) { + statisticMap.put(entry.getKey(), statisticMap.get(entry.getKey()) + 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + String protocol = scanTaskHandler.getProtocolToTest(entry.getKey()); + sb.append(protocol + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + public static void appendCipherSuiteStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = createEmptyStatisicMap(scanTaskHandler.getCipherSuitesToTestCount()); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + HashMap tested = st.getScanData().cipherSuitesTested; + for (Map.Entry entry : tested.entrySet()) { + if (entry.getValue() == true) { + statisticMap.put(entry.getKey(), statisticMap.get(entry.getKey()) + 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + String cipherSuite = scanTaskHandler.getCipherSuiteToTest(entry.getKey()); + sb.append(cipherSuite + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + public static void appendCertificatePubKeyAlgorithmStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = new HashMap<>(); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + if (st.getScanData().getCertAvailable() == false) { + continue; + } + + String pubKeyAlgorithm = st.getScanData().certs[0].getPublicKeyAlgorithm(); + if (pubKeyAlgorithm.isEmpty() == false) { + if (statisticMap.containsKey(pubKeyAlgorithm)) { + int v = statisticMap.get(pubKeyAlgorithm) + 1; + statisticMap.put(pubKeyAlgorithm, v); + } + else { + statisticMap.put(pubKeyAlgorithm, 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + sb.append(entry.getKey() + " " + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + public static void appendCertificatePubKeyAlgorithmWithKeyLengthStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = new HashMap<>(); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + if (st.getScanData().getCertAvailable() == false) { + continue; + } + + String pubKeyAlgorithm = st.getScanData().certs[0].getPublicKeyAlgorithm(); + if (pubKeyAlgorithm.isEmpty()) { + continue; + } + + pubKeyAlgorithm = st.getScanData().certs[0].getPublicKeyAlgorithmWithKeyLength(); + if (statisticMap.containsKey(pubKeyAlgorithm)) { + int v = statisticMap.get(pubKeyAlgorithm) + 1; + statisticMap.put(pubKeyAlgorithm, v); + } + else { + statisticMap.put(pubKeyAlgorithm, 1); + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + sb.append(entry.getKey() + " " + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + public static void appendCertificateSignatureAlgorithmStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = new HashMap<>(); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + if (st.getScanData().getCertAvailable() == false) { + continue; + } + + String signatureAlgorithm = st.getScanData().certs[0].getSignatureAlgorithm(); + if (signatureAlgorithm.isEmpty() == false) { + if (statisticMap.containsKey(signatureAlgorithm)) { + int v = statisticMap.get(signatureAlgorithm) + 1; + statisticMap.put(signatureAlgorithm, v); + } + else { + statisticMap.put(signatureAlgorithm, 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + sb.append(entry.getKey() + " " + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + public static void appendCertificateExtensionStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = new HashMap<>(); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + if (st.getScanData().getCertAvailable() == false) { + continue; + } + + List extensions = st.getScanData().certs[0].getExtensionOIDs(); + for (String s : extensions) { + if (statisticMap.containsKey(s)) { + int v = statisticMap.get(s) + 1; + statisticMap.put(s, v); + } + else { + statisticMap.put(s, 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + String extension = "(" + entry.getKey() + ") " + SSLUtil.getCertExtensionName(entry.getKey()); + sb.append(extension + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } + + public static void appendCertificateRootCAOrganisationStatistic(StringBuilder sb, ScanTaskHandler scanTaskHandler) { + HashMap statisticMap = new HashMap<>(); + + for (ScanTask st : scanTaskHandler.getScanTasks()) { + if (st.getScanData().getCertAvailable() == false) { + continue; + } + + String rootCAOrg = Cert.getNameValue(st.getScanData().getCertRootName(), "O="); + if (rootCAOrg.isEmpty() == false) { + if (statisticMap.containsKey(rootCAOrg)) { + int v = statisticMap.get(rootCAOrg) + 1; + statisticMap.put(rootCAOrg, v); + } + else { + statisticMap.put(rootCAOrg, 1); + } + } + } + + Map sortedMap = sortByValues(statisticMap); + for (Map.Entry entry : sortedMap.entrySet()) { + sb.append(entry.getKey() + getAmountString(scanTaskHandler.getScanTasks().size(), entry.getValue()) + Util.BR); + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/Cert.java b/src/org/bitbatzen/tlsserverscanner/scantask/Cert.java new file mode 100644 index 0000000..090dc6f --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/Cert.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + +import java.security.cert.X509Certificate; +import java.math.BigInteger; +import java.security.cert.CertificateParsingException; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import javax.crypto.interfaces.DHPublicKey; + +import org.bitbatzen.tlsserverscanner.Util; + + +public class Cert { + + private X509Certificate cert; + + + public Cert(X509Certificate cert) { + this.cert = cert; + } + + public X509Certificate getCert() { + return cert; + } + + public String getSubjectName() { + return cert.getSubjectX500Principal().getName(); + } + + public String getIssuerName() { + return cert.getIssuerX500Principal().getName(); + } + + public static String getNameValue(String certName, String searchTag) { + if (certName == null || certName.isEmpty()) { + return ""; + } + + int begin = certName.indexOf(searchTag); + int end = certName.indexOf(",", begin); + if (end < begin) { + end = certName.length(); + } + + while (certName.charAt(end - 1) == '\\') { + end = certName.indexOf(",", end + 1); + } + + if (begin != -1 && end != -1) { + String subString = certName.substring(begin + searchTag.length(), end); + return subString.replace("\\", ""); + } + else { + return ""; + } + } + + public static String formatCertName(String s) { + if (s == null) { + return ""; + } + + s = s.replace("\\", ""); + return s.replace(",", ", "); + } + + public static String formatCertName2(String s) { + if (s == null) { + return ""; + } + + s = s.replace("\\,", "@@@"); + String formatted = ""; + String[] splitted = s.split(","); + for (String ss : splitted) { + formatted += ss.replace("=", " = ") + Util.BR; + } + + return formatted.replace("@@@", ","); + } + + public String[] getSubjectAlternativeNames() { + try { + Collection> col = cert.getSubjectAlternativeNames(); + if (col == null) { + return null; + } + String[] alts = new String[col.size()]; + int c = 0; + for (List l : col) { + alts[c] = l.get(1).toString(); + c++; + } + return alts; + } + catch (CertificateParsingException e) { + return null; + } + } + + public String getVersion() { + return Integer.toString(cert.getVersion()); + } + + public String getType() { + return cert.getType(); + } + + public BigInteger getSerialNumber() { + return cert.getSerialNumber(); + } + + public Date getNotBefore() { + return cert.getNotBefore(); + } + + public Date getNotAfter() { + return cert.getNotAfter(); + } + + public String getSignatureAlgorithm() { + return cert.getSigAlgName(); + } + + public String getPublicKeyAlgorithm() { + return cert.getPublicKey().getAlgorithm(); + } + + public String getPublicKeyLength() { + if (cert.getPublicKey() instanceof RSAPublicKey) { + RSAPublicKey rsaPubKey = (RSAPublicKey) cert.getPublicKey(); + return Integer.toString(rsaPubKey.getModulus().bitLength()); + } + else if (cert.getPublicKey() instanceof ECPublicKey) { + ECPublicKey ecPubKey = (ECPublicKey) cert.getPublicKey(); + return Integer.toString(ecPubKey.getParams().getCurve().getField().getFieldSize()); + } + else if (cert.getPublicKey() instanceof DHPublicKey) { + DHPublicKey dhPubKey = (DHPublicKey) cert.getPublicKey(); + return Integer.toString(dhPubKey.getParams().getP().bitLength()); + } + else if (cert.getPublicKey() instanceof DSAPublicKey) { + DSAPublicKey dsaPubKey = (DSAPublicKey) cert.getPublicKey(); + return Integer.toString(dsaPubKey.getParams().getP().bitLength()); + } + else { + return "Unkown"; + } + } + + public String getPublicKeyAlgorithmWithKeyLength() { + return getPublicKeyAlgorithm() + " (" + getPublicKeyLength() + ")"; + } + + public List getExtensionOIDs() { + List oids = new ArrayList<>(); + + Set criticalExtensions = cert.getCriticalExtensionOIDs(); + if (criticalExtensions != null && criticalExtensions.isEmpty() == false) { + oids.addAll(criticalExtensions); + } + + Set nonCriticalExtensions = cert.getNonCriticalExtensionOIDs(); + if (nonCriticalExtensions != null && nonCriticalExtensions.isEmpty() == false) { + oids.addAll(nonCriticalExtensions); + } + + return oids; + } + + public List getExtensionOIDsWithName() { + List oidsWithName = new ArrayList<>(); + List oids = getExtensionOIDs(); + for (String oid : oids) { + oidsWithName.add("(" + oid + ") " + SSLUtil.getCertExtensionName(oid)); + } + + return oidsWithName; + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskHandlerListener.java b/src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskHandlerListener.java new file mode 100644 index 0000000..59b1542 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskHandlerListener.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + + +public interface IScanTaskHandlerListener { + + public void onScanTaskHandlerMessage(ScanTask scanTask, String message); + + public void onScanTaskHandlerDone(); +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskListener.java b/src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskListener.java new file mode 100644 index 0000000..0b84b75 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/IScanTaskListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + + +public interface IScanTaskListener { + + public void onScanTaskStateChanged(ScanTask scanTask, String message); +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/SSLUtil.java b/src/org/bitbatzen/tlsserverscanner/scantask/SSLUtil.java new file mode 100644 index 0000000..f2ca6c5 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/SSLUtil.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.bitbatzen.tlsserverscanner.Util; + + +public class SSLUtil { + + private static final HashMap certExtensionsMap = createCertExtensionsMap(); + + private static HostnameVerifier DEFAULT_HOSTNAME_VERIFIER = HttpsURLConnection.getDefaultHostnameVerifier(); + private static SSLSocketFactory DEFAULT_SSL_SOCKET_FACTORY = HttpsURLConnection.getDefaultSSLSocketFactory(); + + private static final TrustManager[] ALL_TRUSTING_TRUST_MANAGER = new TrustManager[] { + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } + }; + + private static final HostnameVerifier ALL_TRUSTING_HOSTNAME_VERIFIER = new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + public static SSLSocketFactory getSSLSocketFactory(boolean certAndHostnameVerificationEnabled) { + HostnameVerifier hostnameVerifier = DEFAULT_HOSTNAME_VERIFIER; + SSLSocketFactory sslSocketFactory = DEFAULT_SSL_SOCKET_FACTORY; + + if (certAndHostnameVerificationEnabled == false) { + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, ALL_TRUSTING_TRUST_MANAGER, new java.security.SecureRandom()); + sslSocketFactory = sc.getSocketFactory(); + } + catch (Exception e) { + e.printStackTrace(); + } + + hostnameVerifier = ALL_TRUSTING_HOSTNAME_VERIFIER; + } + + HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); + HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); + + return HttpsURLConnection.getDefaultSSLSocketFactory(); + } + + public static List getAvailableCipherSuites() { + String[] cipherSuites = HttpsURLConnection.getDefaultSSLSocketFactory().getDefaultCipherSuites(); + List list = new ArrayList<>(); + for (String s : cipherSuites) { + list.add(s); + } + + return list; + } + + public static List getAvailableProtocols() { + List list = new ArrayList<>(); + SSLSocket socket = null; + + try { + socket = (SSLSocket) getSSLSocketFactory(false).createSocket(); + String[] protocols = socket.getSupportedProtocols(); + for (String s : protocols) { + if (s.equals("SSLv2Hello") == false) { + list.add(s); + } + } + return list; + } + catch (IOException e) { + return list; + } + finally { + Util.close(socket); + } + } + + public static String getCertExtensionName(String oid) { + String name = certExtensionsMap.get(oid); + return (name == null) ? "Unkown" : name; + } + + private static HashMap createCertExtensionsMap() { + HashMap map = new HashMap<>(); +// map.put("1.3.6.1.4.1.11129.2.4.2", ""); + map.put("1.3.6.1.5.5.7.1.1", "Authority Info Access"); + + map.put("2.5.29.1", "old Authority Key Identifier"); + map.put("2.5.29.2", "old Primary Key Attributes "); + map.put("2.5.29.3", "Certificate Policies"); + map.put("2.5.29.4", "Primary Key Usage Restriction"); + map.put("2.5.29.9", "Subject Directory Attributes"); + + map.put("2.5.29.14", "Subject Key Identifier"); + map.put("2.5.29.15", "Key Usage"); + map.put("2.5.29.16", "Private Key Usage Period"); + map.put("2.5.29.17", "Subject Alternative Name"); + map.put("2.5.29.18", "Issuer Alternative Name"); + + map.put("2.5.29.19", "Basic Constraints"); + map.put("2.5.29.20", "CRL Number"); + map.put("2.5.29.21", "Reason code"); + map.put("2.5.29.23", "Hold Instruction Code"); + map.put("2.5.29.24", "Invalidity Date"); + + map.put("2.5.29.27", "Delta CRL indicator"); + map.put("2.5.29.28", "Issuing Distribution Point"); + map.put("2.5.29.29", "Certificate Issuer"); + map.put("2.5.29.30", "Name Constraints"); + map.put("2.5.29.31", "CRL Distribution Points"); + + map.put("2.5.29.32", "Certificate Policies"); + map.put("2.5.29.33", "Policy Mappings"); + map.put("2.5.29.35", "Authority Key Identifier"); + map.put("2.5.29.36", "Policy Constraints"); + map.put("2.5.29.37", "Extended key usage"); + + map.put("2.5.29.46", "FreshestCRL"); + map.put("2.5.29.54", "X.509 version 3 certificate extension Inhibit Any-policy"); + return map; + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/ScanData.java b/src/org/bitbatzen/tlsserverscanner/scantask/ScanData.java new file mode 100644 index 0000000..e48af87 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/ScanData.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + +import java.net.InetAddress; +import java.util.HashMap; + + +public class ScanData { + + public String host; + public int port; + + public InetAddress inetAddr; + + public boolean scanStarted; + public boolean scanFinished; + + public boolean scanAborted; + public boolean scanStoppedByUser; + + public enum ScanError { + NONE, + FAILED_TO_RESOLVE_HOSTNAME, + PORT_NOT_REACHABLE, + FAILED_TO_CREATE_SOCKET, + INVALID_SSL_PARAMETER, + SSL_HANDSHAKE_TIMEOUT, + MAYBE_NO_SSL_SERVICE_RUNNING, + INVALID_CERTIFICATE, + FAILED_TO_FETCH_CERTIFICATE + } + + public ScanError scanError; + + public HashMap cipherSuitesTested; + public HashMap protocolsTested; + + public Cert[] certs; + + public String cipherSuiteChosenByServer; + + + public ScanData(String host, int port) { + this.host = host; + this.port = port; + inetAddr = null; + + scanStarted = false; + scanFinished = false; + + scanAborted = false; + scanStoppedByUser = false; + + scanError = ScanError.NONE; + + cipherSuitesTested = new HashMap<>(); + protocolsTested = new HashMap<>(); + + certs = null; + cipherSuiteChosenByServer = ""; + } + + public static String getErrorInfo(ScanError scanError) { + switch (scanError) { + case FAILED_TO_RESOLVE_HOSTNAME: + return "Failed to resolve hostname!"; + case PORT_NOT_REACHABLE: + return "Port not reachable!"; + case FAILED_TO_CREATE_SOCKET: + return "Failed to create ssl socket!"; + case INVALID_SSL_PARAMETER: + return "Invalid ssl parameter!"; + case SSL_HANDSHAKE_TIMEOUT: + return "Timeout during ssl handshake!"; + case MAYBE_NO_SSL_SERVICE_RUNNING: + return "No ssl service running?"; + case INVALID_CERTIFICATE: + return "Invalid certificate!"; + case FAILED_TO_FETCH_CERTIFICATE: + return "Failed to fetch certificate! (Cipher suites not supported?)"; + default: + return ""; + } + } + + public String getErrorInfo() { + return getErrorInfo(scanError); + } + + public boolean isHostnameResolved() { + return (inetAddr != null && inetAddr.getHostAddress() != null && inetAddr.getHostAddress() != ""); + } + + public String getHostAddress() { + if (isHostnameResolved()) { + return inetAddr.getHostAddress(); + } + else { + return ""; + } + } + + public String getHostWithPort() { + return host + ":" + port; + } + + public boolean getCertAvailable() { + return certs != null && certs.length > 0 && certs[0] != null; + } + + public String getCertSubjectName() { + return certs[0].getSubjectName(); + } + + public String[] getCertNames() { + String[] certNames = new String[certs.length]; + for (int i = 0; i < certs.length; i++) { + Cert c = certs[i]; + String name = ""; + name = Cert.getNameValue(c.getSubjectName(), "CN="); + if (name.equals("")) { + name = "Issuer (" + i + ")"; + } + + certNames[i] = name; + } + + return certNames; + } + + public String getCertRootName() { + return certs[certs.length - 1].getIssuerName(); + } + + public boolean getIsCertSelfSigned() { + return getCertSubjectName().equals(getCertRootName()); + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/ScanTask.java b/src/org/bitbatzen/tlsserverscanner/scantask/ScanTask.java new file mode 100644 index 0000000..9318f33 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/ScanTask.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + +import java.awt.Color; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.ScanData.ScanError; + + +public class ScanTask implements Runnable { + + public static final String LOG_SCAN_STOPPED_BY_USER = "Scan was stopped by user!"; + + public enum State { + NONE, + WAIT, + RUNNING, + ERROR, + STOPPED, + DONE + } + + private ScanTaskHandler scanTaskHandler; + + private static final int PORT_REACHABLE_CHECK_TIMEOUT = 8000; + + private static final int SSL_HANDSHAKE_TIMEOUT = 8000; + private Timer timeoutTimer; + + private float progress; + + private Thread thread; + + private List listeners; + + private ScanData sd; + + private static final String ERROR_LOG_TAG = "Error:"; + + private SSLParameters sslParams; + + + public ScanTask(String host, int port) { + sd = new ScanData(host, port); + + scanTaskHandler = null; + + progress = 0.0f; + + timeoutTimer = null; + + listeners = new ArrayList<>(); + + sslParams = new SSLParameters(); + } + + public void addListener(IScanTaskListener listener) { + listeners.add(listener); + } + + public void startThread() { + sd.scanStarted = true; + sd.scanFinished = false; + progress = 0.0f; + + thread = new Thread(this); + thread.start(); + } + + public void stopThread() { + if (sd.scanFinished == false) { + sd.scanStoppedByUser = true; + if (sd.scanStarted) { + sd.scanAborted = true; + finish(LOG_SCAN_STOPPED_BY_USER); + } + else { + stateChanged(null); + } + } + else { + stateChanged(null); + } + } + + public boolean getScanFinished() { + return sd.scanFinished; + } + + public boolean getScanStarted() { + return sd.scanStarted; + } + + public ScanData getScanData() { + return sd; + } + + public float getProgress() { + return progress; + } + + public boolean getScanRunning() { + return sd.scanStarted && !sd.scanFinished; + } + + public void setScanTaskHandler(ScanTaskHandler scanTaskHandler) { + this.scanTaskHandler = scanTaskHandler; + } + + public ScanTaskHandler getScanTaskHandler() { + return scanTaskHandler; + } + + private void abortScan(ScanError scanError) { + if (sd.scanAborted) { + return; + } + + sd.scanAborted = true; + sd.scanError = scanError; + finish(ERROR_LOG_TAG + " " + ScanData.getErrorInfo(scanError)); + } + + @Override public void run() { + stateChanged("Starting Host Check"); + sd.inetAddr = resolveHost(sd.host); + if (sd.isHostnameResolved() == false) { + abortScan(ScanError.FAILED_TO_RESOLVE_HOSTNAME); + return; + } + if (sd.host.equals(sd.inetAddr.getHostAddress()) == false) { + progress += 5.0f; + stateChanged("Resolved Hostname > " + sd.inetAddr.getHostAddress()); + } + if (sd.scanAborted) { + return; + } + + if (isPortReachable(sd.inetAddr.getHostAddress(), sd.port) == false) { + abortScan(ScanError.PORT_NOT_REACHABLE); + return; + } + progress += 5.0f; + + int protocolsToTestCount = scanTaskHandler.getProtocolsToTestCount(); + int cipherSuitesToTestCount = scanTaskHandler.getCipherSuitesToTestCount(); + + if (protocolsToTestCount == 0 && cipherSuitesToTestCount == 0) { + stateChanged("Port Reachable"); + } + else { + stateChanged("Port Reachable, Starting Scan..."); + } + + // certificate collecting + if (scanTaskHandler.getCertCollectingEnabled()) { + startTimeoutTimer(); + testFetchCertificate(sd.inetAddr.getHostAddress(), sd.port, scanTaskHandler.getCipherSuitsForCollectingCert()); + if (sd.scanAborted) { + return; + } + } + + float increment = (100.0f - progress) / (protocolsToTestCount + cipherSuitesToTestCount); + + // protocol tests + if (protocolsToTestCount > 0) { + for (int i = 0; i < protocolsToTestCount; i++) { + startTimeoutTimer(); + testProtocol(sd.inetAddr.getHostAddress(), sd.port, i); + if (sd.scanAborted) { + return; + } + progress += increment; + } + } + + // cipher suite tests + if (cipherSuitesToTestCount > 0) { + for (int i = 0; i < cipherSuitesToTestCount; i++) { + startTimeoutTimer(); + testCipherSuite(sd.inetAddr.getHostAddress(), sd.port, i); + if (sd.scanAborted) { + return; + } + progress += increment; + } + } + + finish("Finished"); + } + + private void onSSLHandshakeTimeout() { + abortScan(ScanError.SSL_HANDSHAKE_TIMEOUT); + } + + private void finish(String logMessage) { + cancelTimeoutTimer(); + progress = 100.0f; + sd.scanFinished = true; + + stateChanged(logMessage); + } + + private void startTimeoutTimer() { + try { + if (timeoutTimer != null) { + timeoutTimer.cancel(); + } + + timeoutTimer = new Timer(); + timeoutTimer.schedule(new TimerTask() { + @Override + public void run() { + onSSLHandshakeTimeout(); + } + }, SSL_HANDSHAKE_TIMEOUT); + } + catch (Exception e) { + } + } + + private void cancelTimeoutTimer() { + if (timeoutTimer == null) { + return; + } + + try { + timeoutTimer.cancel(); + } + catch (Exception e) { + } + } + + private void setDefault(SSLParameters sslParams) { + sslParams.setAlgorithmConstraints(null); + sslParams.setCipherSuites(null); + sslParams.setEndpointIdentificationAlgorithm(null); + sslParams.setNeedClientAuth(false); + sslParams.setWantClientAuth(false); + } + + private SSLSession trySSLHandshake(String hostIP, int port, SSLParameters sslParams) throws SSLHandshakeException { + SSLSocket socket = null; + try { + socket = (SSLSocket) scanTaskHandler.getSSLSocketFactory().createSocket(hostIP, port); + } + catch (Exception e) { + abortScan(ScanError.FAILED_TO_CREATE_SOCKET); + Util.close(socket); + return null; + } + + try { + socket.setSSLParameters(sslParams); + } + catch (IllegalArgumentException iae) { + abortScan(ScanError.INVALID_SSL_PARAMETER); + return null; + } + + try { + socket.startHandshake(); + SSLSession sslSession = socket.getSession(); + return sslSession; + } + catch (SSLHandshakeException sslhe) { + if (sslhe.getMessage().contains("ValidatorException")) { + abortScan(ScanError.INVALID_CERTIFICATE); + } + + throw sslhe; + } + catch (Exception e) { + abortScan(ScanError.MAYBE_NO_SSL_SERVICE_RUNNING); + return null; + } + finally { + Util.close(socket); + } + } + + private boolean isSSLSessionValid(SSLSession sslSession) { + return !sd.scanAborted && sslSession != null + && sslSession.getCipherSuite().equals("SSL_NULL_WITH_NULL_NULL") == false; + } + + private void testFetchCertificate(String hostIP, int port, String[] availableCipherSuites) { + setDefault(sslParams); + + if (availableCipherSuites != null && availableCipherSuites.length > 0) { + sslParams.setCipherSuites(availableCipherSuites); + } + + try { + SSLSession sslSession = trySSLHandshake(hostIP, port, sslParams); + + if (isSSLSessionValid(sslSession)) { + if (sd.getCertAvailable() == false) { + sd.certs = getCerts(sslSession); + sd.cipherSuiteChosenByServer = sslSession.getCipherSuite(); + stateChanged("Fetched Certificates"); + } + } + } + catch (SSLHandshakeException sslhe) { + abortScan(ScanError.FAILED_TO_FETCH_CERTIFICATE); + } + } + + private void testProtocol(String hostIP, int port, int protocolIndex) { + setDefault(sslParams); + + String protocol = scanTaskHandler.getProtocolToTest(protocolIndex); + sslParams.setProtocols(new String[] {protocol} ); + + try { + SSLSession sslSession = trySSLHandshake(hostIP, port, sslParams); + + if (isSSLSessionValid(sslSession)) { + int index = scanTaskHandler.getProtocolToTestIndex(sslSession.getProtocol()); + sd.protocolsTested.put(index, true); + stateChanged(sslSession.getProtocol() + " > SUPPORTED"); + } + } + catch (SSLHandshakeException sslhe) { + int index = scanTaskHandler.getProtocolToTestIndex(protocol); + sd.protocolsTested.put(index, false); + stateChanged(null); + } + } + + private void testCipherSuite(String hostIP, int port, int cipherSuiteIndex) { + setDefault(sslParams); + + String cipherSuite = scanTaskHandler.getCipherSuiteToTest(cipherSuiteIndex); + sslParams.setCipherSuites(new String[] {cipherSuite} ); + + try { + SSLSession sslSession = trySSLHandshake(hostIP, port, sslParams); + + if (isSSLSessionValid(sslSession)) { + int index = scanTaskHandler.getCipherSuiteToTestIndex(sslSession.getCipherSuite()); + sd.cipherSuitesTested.put(index, true); + stateChanged(sslSession.getCipherSuite() + " > SUPPORTED"); + } + } + catch (SSLHandshakeException sslhe) { + if (sslhe.getMessage().contains("ValidatorException") == false) { + sd.cipherSuitesTested.put(cipherSuiteIndex, false); + stateChanged(null); + } + } + } + + private void stateChanged(String message) { + for (IScanTaskListener listener : listeners) { + String m = null; + if (message != null && message.equals("") == false) { + m = "[" + sd.host + ":" + sd.port + "] [" + message + "]"; + } + listener.onScanTaskStateChanged(this, m); + } + } + + private InetAddress resolveHost(String host) { + try { + InetAddress inetAddr = InetAddress.getByName(host); + return inetAddr; + } + catch (UnknownHostException uhe) { + return null; + } + } + + private boolean isPortReachable(String host, int port) { + Socket socket = null; + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(host, port), PORT_REACHABLE_CHECK_TIMEOUT); + return true; + } + catch (Exception e) { + return false; + } + finally { + Util.close(socket); + } + } + + private Cert[] getCerts(SSLSession sslSession) { + java.security.cert.Certificate[] servercerts; + try { + servercerts = sslSession.getPeerCertificates(); + } + catch (SSLPeerUnverifiedException e) { + return null; + } + + if (servercerts[0] instanceof java.security.cert.X509Certificate) { + Cert[] certs = new Cert[servercerts.length]; + for (int i = 0; i < servercerts.length; i++) { + certs[i] = new Cert((java.security.cert.X509Certificate) servercerts[i]); + } + + return certs; + } + else { + return null; + } + } + + public static State getState(ScanTask scanTask) { + if (scanTask == null) { + return State.NONE; + } + + if (scanTask.getScanStarted() == false) { + if (scanTask.getScanData().scanStoppedByUser) { + return State.STOPPED; + } + else { + return State.WAIT; + } + } + else { + if (scanTask.getScanFinished()) { + if (scanTask.getScanData().scanAborted) { + if (scanTask.getScanData().scanStoppedByUser) { + return State.STOPPED; + } + else { + return State.ERROR; + } + } + else { + return State.DONE; + } + } + else { + return State.RUNNING; + } + } + } + + public static Color getStateColor(State state) { + switch (state) { + case NONE: + case WAIT: + return Color.WHITE; + case STOPPED: + return Color.LIGHT_GRAY; + case RUNNING: + return new Color(255, 255, 20); + case ERROR: + return new Color(230, 0, 0); + case DONE: + return new Color(50, 200, 50); + default: + return Color.WHITE; + } + } + + public static String getStateTag(State state) { + switch (state) { + case NONE: + case WAIT: + return "[WAIT]"; + case RUNNING: + return"[RUNNING]"; + case ERROR: + return "[ERROR]"; + case DONE: + return "[DONE]"; + case STOPPED: + return "[STOPPED]"; + default: + return "[WAIT]"; + } + } +} diff --git a/src/org/bitbatzen/tlsserverscanner/scantask/ScanTaskHandler.java b/src/org/bitbatzen/tlsserverscanner/scantask/ScanTaskHandler.java new file mode 100644 index 0000000..3181781 --- /dev/null +++ b/src/org/bitbatzen/tlsserverscanner/scantask/ScanTaskHandler.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2015 Benjamin W. (bitbatzen@gmail.com) + * + * This file is part of TLSServerScanner. + * + * TLSServerScanner is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TLSServerScanner is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TLSServerScanner. If not, see . + */ + +package org.bitbatzen.tlsserverscanner.scantask; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import javax.net.ssl.SSLSocketFactory; + +import org.bitbatzen.tlsserverscanner.Util; +import org.bitbatzen.tlsserverscanner.scantask.ScanTask.State; + + +public class ScanTaskHandler implements IScanTaskListener { + + private int maxParallelScans = 10; + + private long scanStartTime; + + private List scanTasks; + + private List cipherSuitesForCollectingCert; + private List cipherSuitesToTest; + private List protocolsToTest; + + private boolean certAndHostnameVerificationEnabled; + private boolean certCollectingEnabled; + private SSLSocketFactory sslSocketFactory; + + private SimpleDateFormat logDateFormat; + + private List listeners; + + private Timer finishMessageDelayTimer; + + private final static int FINISH_MESSAGE_DELAY_TIME = 500; + + private boolean scansStarted; + private boolean scansStoppedByUser; + + private static final String LOG_PREFIX = "+++++++"; + + + public ScanTaskHandler() { + scanStartTime = 0; + scanTasks = new ArrayList<>(); + cipherSuitesForCollectingCert = new ArrayList<>(); + cipherSuitesToTest = new ArrayList<>(); + protocolsToTest = new ArrayList<>(); + + scansStarted = false; + scansStoppedByUser = false; + + logDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + + listeners = new ArrayList<>(); + + sslSocketFactory = null; + } + + public void init(boolean certCollectingEnabled, boolean certAndHostnameVerificationEnabled) { + this.certCollectingEnabled = certCollectingEnabled; + this.certAndHostnameVerificationEnabled = certAndHostnameVerificationEnabled; + sslSocketFactory = SSLUtil.getSSLSocketFactory(certAndHostnameVerificationEnabled); + } + + public SSLSocketFactory getSSLSocketFactory() { + return sslSocketFactory; + } + + public boolean getCertCollectingEnabled() { + return certCollectingEnabled; + } + + public boolean getCertAndHostnameVerificationEnabled() { + return certAndHostnameVerificationEnabled; + } + + public void addListener(IScanTaskHandlerListener listener) { + listeners.add(listener); + } + + public void addScanTask(ScanTask scanTask) { + scanTask.addListener(this); + scanTask.setScanTaskHandler(this); + scanTasks.add(scanTask); + } + + public void setCipherSuitesForCollectingCert(List cipherSuites) { + cipherSuitesForCollectingCert.clear(); + for (String s : cipherSuites) { + cipherSuitesForCollectingCert.add(s); + } + } + + public String[] getCipherSuitsForCollectingCert() { + String[] cipherSuites = new String[cipherSuitesForCollectingCert.size()]; + for (int i = 0; i < cipherSuitesForCollectingCert.size(); i++) { + cipherSuites[i] = cipherSuitesForCollectingCert.get(i); + } + + return cipherSuites; + } + + public void setCipherSuitesToTest(List cipherSuites) { + cipherSuitesToTest.clear(); + for (String s : cipherSuites) { + cipherSuitesToTest.add(s); + } + } + public String getCipherSuiteToTest(int index) { + return cipherSuitesToTest.get(index); + } + public int getCipherSuiteToTestIndex(String cipherSuite) { + return cipherSuitesToTest.indexOf(cipherSuite); + } + public int getCipherSuitesToTestCount() { + return cipherSuitesToTest.size(); + } + + + public void setProtocolsToTest(List protocols) { + protocolsToTest.clear(); + for (String s : protocols) { + protocolsToTest.add(s); + } + } + public String getProtocolToTest(int index) { + return protocolsToTest.get(index); + } + public int getProtocolToTestIndex(String protocol) { + return protocolsToTest.indexOf(protocol); + } + public int getProtocolsToTestCount() { + return protocolsToTest.size(); + } + + public List getScanTasks() { + return scanTasks; + } + + public void startScans() { + if (sslSocketFactory == null) { + log(null, "InternalError: sslSocketFactory == null"); + return; + } + + scansStarted = true; + scansStoppedByUser = false; + scanStartTime = System.currentTimeMillis(); + + String hostString = (getScanTasksCount() == 1) ? "host" : "hosts"; + log(null, LOG_PREFIX + " Starting scan for " + scanTasks.size() + " " + hostString); + + checkStartScans(); + } + + public void stopScans() { + if (getScansFinished() == false) { + for (ScanTask st : scanTasks) { + st.stopThread(); + } + + log(null, LOG_PREFIX + " " + ScanTask.LOG_SCAN_STOPPED_BY_USER + Util.LF); + scansStoppedByUser = true; + } + } + + public boolean getScansRunning() { + if (scansStarted) { + if (getScansFinished()) { + return false; + } + else { + return !scansStoppedByUser; + } + } + else { + return false; + } + } + + public int getScansFailedCount() { + int counter = 0; + for (ScanTask st : scanTasks) { + if (st.getScanData().scanAborted && st.getScanData().scanStoppedByUser == false) { + counter++; + } + } + + return counter; + } + + public int getScansCompletedCount() { + int counter = 0; + for (ScanTask st : scanTasks) { + if (st.getScanFinished() && st.getScanData().scanAborted == false) { + counter++; + } + } + + return counter; + } + + public int getScansRunningCount() { + int runningScans = 0; + for (ScanTask st : scanTasks) { + if (st.getScanRunning()) { + runningScans++; + } + } + + return runningScans; + } + + public int getScanTasksCount() { + return scanTasks.size(); + } + + public ScanTask.State getState() { + if (scansStarted) { + if (getScansFinished() && !scansStoppedByUser) { + return State.DONE; + } + else if (scansStoppedByUser) { + return State.STOPPED; + } + else { + return State.RUNNING; + } + } + else { + return State.WAIT; + } + } + + private void checkStartScans() { + int scansToStart = maxParallelScans - getScansRunningCount(); + if (scansToStart <= 0) { + return; + } + + for (ScanTask st : scanTasks) { + if (scansToStart > 0 && st.getScanStarted() == false && st.getScanFinished() == false) { + st.setScanTaskHandler(this); + st.startThread(); + scansToStart--; + } + } + } + + private boolean getScansFinished() { + for (ScanTask st : scanTasks) { + if (st.getScanFinished() == false) { + return false; + } + } + + return true; + } + + @Override + public synchronized void onScanTaskStateChanged(ScanTask scanTask, String message) { + if (scansStoppedByUser) { + return; + } + + if (message != null && message.equals("") == false) { + float progress = 0; + for (ScanTask st : scanTasks) { + progress += st.getProgress(); + } + + progress = (progress / (scanTasks.size() * 100.0f)) * 100.0f; + log(scanTask, getProgressString((int) progress) + message); + } + + if (scanTask.getScanFinished() && scanTask.getScanData().scanStoppedByUser == false) { + checkStartScans(); + } + + if (getScansFinished()) { + sendAllScanTasksFinishedMessageWithDelay(FINISH_MESSAGE_DELAY_TIME); + } + } + + private void sendAllScanTasksFinishedMessageWithDelay(int delayInMillis) { + try { + if (finishMessageDelayTimer != null) { + finishMessageDelayTimer.cancel(); + } + + finishMessageDelayTimer = new Timer(); + finishMessageDelayTimer.schedule(new TimerTask() { + @Override + public void run() { + sendAllScanTasksFinishedMessage(); + } + }, delayInMillis); + } + catch (Exception e) { + } + } + + private void sendAllScanTasksFinishedMessage() { + if (scansStoppedByUser == false) { + String hostString = (getScanTasksCount() == 1) ? "host" : "hosts"; + float duration = (System.currentTimeMillis() - scanStartTime - FINISH_MESSAGE_DELAY_TIME) / 1000.0f; + log(null, LOG_PREFIX + " Finished " + scanTasks.size() + " " + hostString + " in " + duration + " seconds " + Util.LF); + } + for (IScanTaskHandlerListener listener : listeners) { + listener.onScanTaskHandlerDone(); + } + } + + private String getProgressString(int progress) { + if (progress < 10) { + return "[" + (int) progress + " %] "; + } + else if (progress < 100) { + return "[" + (int) progress + " %] "; + } + + return "[" + (int) progress + " %] "; + } + + private void log(ScanTask scanTask, String message) { + if (message != null && message != "") { + for (IScanTaskHandlerListener listener : listeners) { + String date = logDateFormat.format(new Date()); + String m = "[" + date + "] " + message; + listener.onScanTaskHandlerMessage(scanTask, m); + } + } + } +}