diff --git a/applications/external/esubghz_chat/LICENSE b/applications/external/esubghz_chat/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/applications/external/esubghz_chat/LICENSE @@ -0,0 +1,674 @@ + 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/applications/external/esubghz_chat/application.fam b/applications/external/esubghz_chat/application.fam new file mode 100644 index 000000000..70b8c6c70 --- /dev/null +++ b/applications/external/esubghz_chat/application.fam @@ -0,0 +1,15 @@ +App( + appid="esubghz_chat", + name="Enhanced Sub-Ghz Chat", + apptype=FlipperAppType.EXTERNAL, + entry_point="esubghz_chat", + requires=[ + "gui", + "subghz", + ], + stack_size=8 * 1024, + fap_category="Sub-GHz", + fap_icon="assets/chat_10px.png", + fap_icon_assets="assets", + fap_icon_assets_symbol="esubghz_chat", +) diff --git a/applications/external/esubghz_chat/assets/Loading_24.png b/applications/external/esubghz_chat/assets/Loading_24.png new file mode 100644 index 000000000..93a59fe68 Binary files /dev/null and b/applications/external/esubghz_chat/assets/Loading_24.png differ diff --git a/applications/external/esubghz_chat/assets/chat_10px.png b/applications/external/esubghz_chat/assets/chat_10px.png new file mode 100644 index 000000000..9ee8343c1 Binary files /dev/null and b/applications/external/esubghz_chat/assets/chat_10px.png differ diff --git a/applications/external/esubghz_chat/crypto/aes.c b/applications/external/esubghz_chat/crypto/aes.c new file mode 100644 index 000000000..2be72d571 --- /dev/null +++ b/applications/external/esubghz_chat/crypto/aes.c @@ -0,0 +1,483 @@ +/****************************************************************************** +* +* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL +* +* This is a simple and straightforward implementation of the AES Rijndael +* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus +* of this work was correctness & accuracy. It is written in 'C' without any +* particular focus upon optimization or speed. It should be endian (memory +* byte order) neutral since the few places that care are handled explicitly. +* +* This implementation of Rijndael was created by Steven M. Gibson of GRC.com. +* +* It is intended for general purpose use, but was written in support of GRC's +* reference implementation of the SQRL (Secure Quick Reliable Login) client. +* +* See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html +* +* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE +* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. +* +*******************************************************************************/ + +#include "aes.h" + +static int aes_tables_inited = 0; // run-once flag for performing key + // expasion table generation (see below) +/* + * The following static local tables must be filled-in before the first use of + * the GCM or AES ciphers. They are used for the AES key expansion/scheduling + * and once built are read-only and thread safe. The "gcm_initialize" function + * must be called once during system initialization to populate these arrays + * for subsequent use by the AES key scheduler. If they have not been built + * before attempted use, an error will be returned to the caller. + * + * NOTE: GCM Encryption/Decryption does NOT REQUIRE AES decryption. Since + * GCM uses AES in counter-mode, where the AES cipher output is XORed with + * the GCM input, we ONLY NEED AES encryption. Thus, to save space AES + * decryption is typically disabled by setting AES_DECRYPTION to 0 in aes.h. + */ + // We always need our forward tables +static uchar FSb[256]; // Forward substitution box (FSb) +static uint32_t FT0[256]; // Forward key schedule assembly tables +static uint32_t FT1[256]; +static uint32_t FT2[256]; +static uint32_t FT3[256]; + +#if AES_DECRYPTION // We ONLY need reverse for decryption +static uchar RSb[256]; // Reverse substitution box (RSb) +static uint32_t RT0[256]; // Reverse key schedule assembly tables +static uint32_t RT1[256]; +static uint32_t RT2[256]; +static uint32_t RT3[256]; +#endif /* AES_DECRYPTION */ + +static uint32_t RCON[10]; // AES round constants + +/* + * Platform Endianness Neutralizing Load and Store Macro definitions + * AES wants platform-neutral Little Endian (LE) byte ordering + */ +#define GET_UINT32_LE(n,b,i) { \ + (n) = ( (uint32_t) (b)[(i) ] ) \ + | ( (uint32_t) (b)[(i) + 1] << 8 ) \ + | ( (uint32_t) (b)[(i) + 2] << 16 ) \ + | ( (uint32_t) (b)[(i) + 3] << 24 ); } + +#define PUT_UINT32_LE(n,b,i) { \ + (b)[(i) ] = (uchar) ( (n) ); \ + (b)[(i) + 1] = (uchar) ( (n) >> 8 ); \ + (b)[(i) + 2] = (uchar) ( (n) >> 16 ); \ + (b)[(i) + 3] = (uchar) ( (n) >> 24 ); } + +/* + * AES forward and reverse encryption round processing macros + */ +#define AES_FROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \ +{ \ + X0 = *RK++ ^ FT0[ ( Y0 ) & 0xFF ] ^ \ + FT1[ ( Y1 >> 8 ) & 0xFF ] ^ \ + FT2[ ( Y2 >> 16 ) & 0xFF ] ^ \ + FT3[ ( Y3 >> 24 ) & 0xFF ]; \ + \ + X1 = *RK++ ^ FT0[ ( Y1 ) & 0xFF ] ^ \ + FT1[ ( Y2 >> 8 ) & 0xFF ] ^ \ + FT2[ ( Y3 >> 16 ) & 0xFF ] ^ \ + FT3[ ( Y0 >> 24 ) & 0xFF ]; \ + \ + X2 = *RK++ ^ FT0[ ( Y2 ) & 0xFF ] ^ \ + FT1[ ( Y3 >> 8 ) & 0xFF ] ^ \ + FT2[ ( Y0 >> 16 ) & 0xFF ] ^ \ + FT3[ ( Y1 >> 24 ) & 0xFF ]; \ + \ + X3 = *RK++ ^ FT0[ ( Y3 ) & 0xFF ] ^ \ + FT1[ ( Y0 >> 8 ) & 0xFF ] ^ \ + FT2[ ( Y1 >> 16 ) & 0xFF ] ^ \ + FT3[ ( Y2 >> 24 ) & 0xFF ]; \ +} + +#define AES_RROUND(X0,X1,X2,X3,Y0,Y1,Y2,Y3) \ +{ \ + X0 = *RK++ ^ RT0[ ( Y0 ) & 0xFF ] ^ \ + RT1[ ( Y3 >> 8 ) & 0xFF ] ^ \ + RT2[ ( Y2 >> 16 ) & 0xFF ] ^ \ + RT3[ ( Y1 >> 24 ) & 0xFF ]; \ + \ + X1 = *RK++ ^ RT0[ ( Y1 ) & 0xFF ] ^ \ + RT1[ ( Y0 >> 8 ) & 0xFF ] ^ \ + RT2[ ( Y3 >> 16 ) & 0xFF ] ^ \ + RT3[ ( Y2 >> 24 ) & 0xFF ]; \ + \ + X2 = *RK++ ^ RT0[ ( Y2 ) & 0xFF ] ^ \ + RT1[ ( Y1 >> 8 ) & 0xFF ] ^ \ + RT2[ ( Y0 >> 16 ) & 0xFF ] ^ \ + RT3[ ( Y3 >> 24 ) & 0xFF ]; \ + \ + X3 = *RK++ ^ RT0[ ( Y3 ) & 0xFF ] ^ \ + RT1[ ( Y2 >> 8 ) & 0xFF ] ^ \ + RT2[ ( Y1 >> 16 ) & 0xFF ] ^ \ + RT3[ ( Y0 >> 24 ) & 0xFF ]; \ +} + +/* + * These macros improve the readability of the key + * generation initialization code by collapsing + * repetitive common operations into logical pieces. + */ +#define ROTL8(x) ( ( x << 8 ) & 0xFFFFFFFF ) | ( x >> 24 ) +#define XTIME(x) ( ( x << 1 ) ^ ( ( x & 0x80 ) ? 0x1B : 0x00 ) ) +#define MUL(x,y) ( ( x && y ) ? pow[(log[x]+log[y]) % 255] : 0 ) +#define MIX(x,y) { y = ( (y << 1) | (y >> 7) ) & 0xFF; x ^= y; } +#define CPY128 { *RK++ = *SK++; *RK++ = *SK++; \ + *RK++ = *SK++; *RK++ = *SK++; } + +/****************************************************************************** + * + * AES_INIT_KEYGEN_TABLES + * + * Fills the AES key expansion tables allocated above with their static + * data. This is not "per key" data, but static system-wide read-only + * table data. THIS FUNCTION IS NOT THREAD SAFE. It must be called once + * at system initialization to setup the tables for all subsequent use. + * + ******************************************************************************/ +void aes_init_keygen_tables( void ) +{ + int i, x, y, z; // general purpose iteration and computation locals + int pow[256]; + int log[256]; + + if (aes_tables_inited) return; + + // fill the 'pow' and 'log' tables over GF(2^8) + for( i = 0, x = 1; i < 256; i++ ) { + pow[i] = x; + log[x] = i; + x = ( x ^ XTIME( x ) ) & 0xFF; + } + // compute the round constants + for( i = 0, x = 1; i < 10; i++ ) { + RCON[i] = (uint32_t) x; + x = XTIME( x ) & 0xFF; + } + // fill the forward and reverse substitution boxes + FSb[0x00] = 0x63; +#if AES_DECRYPTION // whether AES decryption is supported + RSb[0x63] = 0x00; +#endif /* AES_DECRYPTION */ + + for( i = 1; i < 256; i++ ) { + x = y = pow[255 - log[i]]; + MIX(x,y); + MIX(x,y); + MIX(x,y); + MIX(x,y); + FSb[i] = (uchar) ( x ^= 0x63 ); +#if AES_DECRYPTION // whether AES decryption is supported + RSb[x] = (uchar) i; +#endif /* AES_DECRYPTION */ + + } + // generate the forward and reverse key expansion tables + for( i = 0; i < 256; i++ ) { + x = FSb[i]; + y = XTIME( x ) & 0xFF; + z = ( y ^ x ) & 0xFF; + + FT0[i] = ( (uint32_t) y ) ^ ( (uint32_t) x << 8 ) ^ + ( (uint32_t) x << 16 ) ^ ( (uint32_t) z << 24 ); + + FT1[i] = ROTL8( FT0[i] ); + FT2[i] = ROTL8( FT1[i] ); + FT3[i] = ROTL8( FT2[i] ); + +#if AES_DECRYPTION // whether AES decryption is supported + x = RSb[i]; + + RT0[i] = ( (uint32_t) MUL( 0x0E, x ) ) ^ + ( (uint32_t) MUL( 0x09, x ) << 8 ) ^ + ( (uint32_t) MUL( 0x0D, x ) << 16 ) ^ + ( (uint32_t) MUL( 0x0B, x ) << 24 ); + + RT1[i] = ROTL8( RT0[i] ); + RT2[i] = ROTL8( RT1[i] ); + RT3[i] = ROTL8( RT2[i] ); +#endif /* AES_DECRYPTION */ + } + aes_tables_inited = 1; // flag that the tables have been generated +} // to permit subsequent use of the AES cipher + +/****************************************************************************** + * + * AES_SET_ENCRYPTION_KEY + * + * This is called by 'aes_setkey' when we're establishing a key for + * subsequent encryption. We give it a pointer to the encryption + * context, a pointer to the key, and the key's length in bytes. + * Valid lengths are: 16, 24 or 32 bytes (128, 192, 256 bits). + * + ******************************************************************************/ +int aes_set_encryption_key( aes_context *ctx, + const uchar *key, + uint keysize ) +{ + uint i; // general purpose iteration local + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer + + for( i = 0; i < (keysize >> 2); i++ ) { + GET_UINT32_LE( RK[i], key, i << 2 ); + } + + switch( ctx->rounds ) + { + case 10: + for( i = 0; i < 10; i++, RK += 4 ) { + RK[4] = RK[0] ^ RCON[i] ^ + ( (uint32_t) FSb[ ( RK[3] >> 8 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( RK[3] >> 16 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( RK[3] >> 24 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( RK[3] ) & 0xFF ] << 24 ); + + RK[5] = RK[1] ^ RK[4]; + RK[6] = RK[2] ^ RK[5]; + RK[7] = RK[3] ^ RK[6]; + } + break; + + case 12: + for( i = 0; i < 8; i++, RK += 6 ) { + RK[6] = RK[0] ^ RCON[i] ^ + ( (uint32_t) FSb[ ( RK[5] >> 8 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( RK[5] >> 16 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( RK[5] >> 24 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( RK[5] ) & 0xFF ] << 24 ); + + RK[7] = RK[1] ^ RK[6]; + RK[8] = RK[2] ^ RK[7]; + RK[9] = RK[3] ^ RK[8]; + RK[10] = RK[4] ^ RK[9]; + RK[11] = RK[5] ^ RK[10]; + } + break; + + case 14: + for( i = 0; i < 7; i++, RK += 8 ) { + RK[8] = RK[0] ^ RCON[i] ^ + ( (uint32_t) FSb[ ( RK[7] >> 8 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( RK[7] >> 16 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( RK[7] >> 24 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( RK[7] ) & 0xFF ] << 24 ); + + RK[9] = RK[1] ^ RK[8]; + RK[10] = RK[2] ^ RK[9]; + RK[11] = RK[3] ^ RK[10]; + + RK[12] = RK[4] ^ + ( (uint32_t) FSb[ ( RK[11] ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( RK[11] >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( RK[11] >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( RK[11] >> 24 ) & 0xFF ] << 24 ); + + RK[13] = RK[5] ^ RK[12]; + RK[14] = RK[6] ^ RK[13]; + RK[15] = RK[7] ^ RK[14]; + } + break; + + default: + return -1; + } + return( 0 ); +} + +#if AES_DECRYPTION // whether AES decryption is supported + +/****************************************************************************** + * + * AES_SET_DECRYPTION_KEY + * + * This is called by 'aes_setkey' when we're establishing a + * key for subsequent decryption. We give it a pointer to + * the encryption context, a pointer to the key, and the key's + * length in bits. Valid lengths are: 128, 192, or 256 bits. + * + ******************************************************************************/ +int aes_set_decryption_key( aes_context *ctx, + const uchar *key, + uint keysize ) +{ + int i, j; + aes_context cty; // a calling aes context for set_encryption_key + uint32_t *RK = ctx->rk; // initialize our RoundKey buffer pointer + uint32_t *SK; + int ret; + + cty.rounds = ctx->rounds; // initialize our local aes context + cty.rk = cty.buf; // round count and key buf pointer + + if (( ret = aes_set_encryption_key( &cty, key, keysize )) != 0 ) + return( ret ); + + SK = cty.rk + cty.rounds * 4; + + CPY128 // copy a 128-bit block from *SK to *RK + + for( i = ctx->rounds - 1, SK -= 8; i > 0; i--, SK -= 8 ) { + for( j = 0; j < 4; j++, SK++ ) { + *RK++ = RT0[ FSb[ ( *SK ) & 0xFF ] ] ^ + RT1[ FSb[ ( *SK >> 8 ) & 0xFF ] ] ^ + RT2[ FSb[ ( *SK >> 16 ) & 0xFF ] ] ^ + RT3[ FSb[ ( *SK >> 24 ) & 0xFF ] ]; + } + } + CPY128 // copy a 128-bit block from *SK to *RK + memset( &cty, 0, sizeof( aes_context ) ); // clear local aes context + return( 0 ); +} + +#endif /* AES_DECRYPTION */ + +/****************************************************************************** + * + * AES_SETKEY + * + * Invoked to establish the key schedule for subsequent encryption/decryption + * + ******************************************************************************/ +int aes_setkey( aes_context *ctx, // AES context provided by our caller + int mode, // ENCRYPT or DECRYPT flag + const uchar *key, // pointer to the key + uint keysize ) // key length in bytes +{ + // since table initialization is not thread safe, we could either add + // system-specific mutexes and init the AES key generation tables on + // demand, or ask the developer to simply call "gcm_initialize" once during + // application startup before threading begins. That's what we choose. + if( !aes_tables_inited ) return ( -1 ); // fail the call when not inited. + + ctx->mode = mode; // capture the key type we're creating + ctx->rk = ctx->buf; // initialize our round key pointer + + switch( keysize ) // set the rounds count based upon the keysize + { + case 16: ctx->rounds = 10; break; // 16-byte, 128-bit key + case 24: ctx->rounds = 12; break; // 24-byte, 192-bit key + case 32: ctx->rounds = 14; break; // 32-byte, 256-bit key + default: return(-1); + } + +#if AES_DECRYPTION + if( mode == DECRYPT ) // expand our key for encryption or decryption + return( aes_set_decryption_key( ctx, key, keysize ) ); + else /* ENCRYPT */ +#endif /* AES_DECRYPTION */ + return( aes_set_encryption_key( ctx, key, keysize ) ); +} + +/****************************************************************************** + * + * AES_CIPHER + * + * Perform AES encryption and decryption. + * The AES context will have been setup with the encryption mode + * and all keying information appropriate for the task. + * + ******************************************************************************/ +int aes_cipher( aes_context *ctx, + const uchar input[16], + uchar output[16] ) +{ + int i; + uint32_t *RK, X0, X1, X2, X3, Y0, Y1, Y2, Y3; // general purpose locals + + RK = ctx->rk; + + GET_UINT32_LE( X0, input, 0 ); X0 ^= *RK++; // load our 128-bit + GET_UINT32_LE( X1, input, 4 ); X1 ^= *RK++; // input buffer in a storage + GET_UINT32_LE( X2, input, 8 ); X2 ^= *RK++; // memory endian-neutral way + GET_UINT32_LE( X3, input, 12 ); X3 ^= *RK++; + +#if AES_DECRYPTION // whether AES decryption is supported + + if( ctx->mode == DECRYPT ) + { + for( i = (ctx->rounds >> 1) - 1; i > 0; i-- ) + { + AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 ); + AES_RROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 ); + } + + AES_RROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 ); + + X0 = *RK++ ^ \ + ( (uint32_t) RSb[ ( Y0 ) & 0xFF ] ) ^ + ( (uint32_t) RSb[ ( Y3 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) RSb[ ( Y2 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) RSb[ ( Y1 >> 24 ) & 0xFF ] << 24 ); + + X1 = *RK++ ^ \ + ( (uint32_t) RSb[ ( Y1 ) & 0xFF ] ) ^ + ( (uint32_t) RSb[ ( Y0 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) RSb[ ( Y3 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) RSb[ ( Y2 >> 24 ) & 0xFF ] << 24 ); + + X2 = *RK++ ^ \ + ( (uint32_t) RSb[ ( Y2 ) & 0xFF ] ) ^ + ( (uint32_t) RSb[ ( Y1 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) RSb[ ( Y0 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) RSb[ ( Y3 >> 24 ) & 0xFF ] << 24 ); + + X3 = *RK++ ^ \ + ( (uint32_t) RSb[ ( Y3 ) & 0xFF ] ) ^ + ( (uint32_t) RSb[ ( Y2 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) RSb[ ( Y1 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) RSb[ ( Y0 >> 24 ) & 0xFF ] << 24 ); + } + else /* ENCRYPT */ + { +#endif /* AES_DECRYPTION */ + + for( i = (ctx->rounds >> 1) - 1; i > 0; i-- ) + { + AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 ); + AES_FROUND( X0, X1, X2, X3, Y0, Y1, Y2, Y3 ); + } + + AES_FROUND( Y0, Y1, Y2, Y3, X0, X1, X2, X3 ); + + X0 = *RK++ ^ \ + ( (uint32_t) FSb[ ( Y0 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( Y1 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( Y2 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( Y3 >> 24 ) & 0xFF ] << 24 ); + + X1 = *RK++ ^ \ + ( (uint32_t) FSb[ ( Y1 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( Y2 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( Y3 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( Y0 >> 24 ) & 0xFF ] << 24 ); + + X2 = *RK++ ^ \ + ( (uint32_t) FSb[ ( Y2 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( Y3 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( Y0 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( Y1 >> 24 ) & 0xFF ] << 24 ); + + X3 = *RK++ ^ \ + ( (uint32_t) FSb[ ( Y3 ) & 0xFF ] ) ^ + ( (uint32_t) FSb[ ( Y0 >> 8 ) & 0xFF ] << 8 ) ^ + ( (uint32_t) FSb[ ( Y1 >> 16 ) & 0xFF ] << 16 ) ^ + ( (uint32_t) FSb[ ( Y2 >> 24 ) & 0xFF ] << 24 ); + +#if AES_DECRYPTION // whether AES decryption is supported + } +#endif /* AES_DECRYPTION */ + + PUT_UINT32_LE( X0, output, 0 ); + PUT_UINT32_LE( X1, output, 4 ); + PUT_UINT32_LE( X2, output, 8 ); + PUT_UINT32_LE( X3, output, 12 ); + + return( 0 ); +} +/* end of aes.c */ diff --git a/applications/external/esubghz_chat/crypto/aes.h b/applications/external/esubghz_chat/crypto/aes.h new file mode 100644 index 000000000..d4d28db52 --- /dev/null +++ b/applications/external/esubghz_chat/crypto/aes.h @@ -0,0 +1,81 @@ +/****************************************************************************** +* +* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL +* +* This is a simple and straightforward implementation of the AES Rijndael +* 128-bit block cipher designed by Vincent Rijmen and Joan Daemen. The focus +* of this work was correctness & accuracy. It is written in 'C' without any +* particular focus upon optimization or speed. It should be endian (memory +* byte order) neutral since the few places that care are handled explicitly. +* +* This implementation of Rijndael was created by Steven M. Gibson of GRC.com. +* +* It is intended for general purpose use, but was written in support of GRC's +* reference implementation of the SQRL (Secure Quick Reliable Login) client. +* +* See: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html +* +* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE +* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. +* +*******************************************************************************/ + +#ifndef AES_HEADER +#define AES_HEADER + +/******************************************************************************/ +#define AES_DECRYPTION 0 // whether AES decryption is supported +/******************************************************************************/ + +#include + +#define ENCRYPT 1 // specify whether we're encrypting +#define DECRYPT 0 // or decrypting + +#if defined(_MSC_VER) + #include + typedef UINT32 uint32_t; +#else + #include +#endif + +typedef unsigned char uchar; // add some convienent shorter types +typedef unsigned int uint; + + +/****************************************************************************** + * AES_INIT_KEYGEN_TABLES : MUST be called once before any AES use + ******************************************************************************/ +void aes_init_keygen_tables( void ); + + +/****************************************************************************** + * AES_CONTEXT : cipher context / holds inter-call data + ******************************************************************************/ +typedef struct { + int mode; // 1 for Encryption, 0 for Decryption + int rounds; // keysize-based rounds count + uint32_t *rk; // pointer to current round key + uint32_t buf[68]; // key expansion buffer +} aes_context; + + +/****************************************************************************** + * AES_SETKEY : called to expand the key for encryption or decryption + ******************************************************************************/ +int aes_setkey( aes_context *ctx, // pointer to context + int mode, // 1 or 0 for Encrypt/Decrypt + const uchar *key, // AES input key + uint keysize ); // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) + // returns 0 for success + +/****************************************************************************** + * AES_CIPHER : called to encrypt or decrypt ONE 128-bit block of data + ******************************************************************************/ +int aes_cipher( aes_context *ctx, // pointer to context + const uchar input[16], // 128-bit block to en/decipher + uchar output[16] ); // 128-bit output result block + // returns 0 for success + +#endif /* AES_HEADER */ diff --git a/applications/external/esubghz_chat/crypto/gcm.c b/applications/external/esubghz_chat/crypto/gcm.c new file mode 100644 index 000000000..6b1cba211 --- /dev/null +++ b/applications/external/esubghz_chat/crypto/gcm.c @@ -0,0 +1,511 @@ +/****************************************************************************** +* +* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL +* +* This is a simple and straightforward implementation of AES-GCM authenticated +* encryption. The focus of this work was correctness & accuracy. It is written +* in straight 'C' without any particular focus upon optimization or speed. It +* should be endian (memory byte order) neutral since the few places that care +* are handled explicitly. +* +* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. +* +* It is intended for general purpose use, but was written in support of GRC's +* reference implementation of the SQRL (Secure Quick Reliable Login) client. +* +* See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf +* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ +* gcm/gcm-revised-spec.pdf +* +* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE +* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. +* +*******************************************************************************/ + +#include "gcm.h" +#include "aes.h" + +/****************************************************************************** + * ==== IMPLEMENTATION WARNING ==== + * + * This code was developed for use within SQRL's fixed environmnent. Thus, it + * is somewhat less "general purpose" than it would be if it were designed as + * a general purpose AES-GCM library. Specifically, it bothers with almost NO + * error checking on parameter limits, buffer bounds, etc. It assumes that it + * is being invoked by its author or by someone who understands the values it + * expects to receive. Its behavior will be undefined otherwise. + * + * All functions that might fail are defined to return 'ints' to indicate a + * problem. Most do not do so now. But this allows for error propagation out + * of internal functions if robust error checking should ever be desired. + * + ******************************************************************************/ + +/* Calculating the "GHASH" + * + * There are many ways of calculating the so-called GHASH in software, each with + * a traditional size vs performance tradeoff. The GHASH (Galois field hash) is + * an intriguing construction which takes two 128-bit strings (also the cipher's + * block size and the fundamental operation size for the system) and hashes them + * into a third 128-bit result. + * + * Many implementation solutions have been worked out that use large precomputed + * table lookups in place of more time consuming bit fiddling, and this approach + * can be scaled easily upward or downward as needed to change the time/space + * tradeoff. It's been studied extensively and there's a solid body of theory and + * practice. For example, without using any lookup tables an implementation + * might obtain 119 cycles per byte throughput, whereas using a simple, though + * large, key-specific 64 kbyte 8-bit lookup table the performance jumps to 13 + * cycles per byte. + * + * And Intel's processors have, since 2010, included an instruction which does + * the entire 128x128->128 bit job in just several 64x64->128 bit pieces. + * + * Since SQRL is interactive, and only processing a few 128-bit blocks, I've + * settled upon a relatively slower but appealing small-table compromise which + * folds a bunch of not only time consuming but also bit twiddling into a simple + * 16-entry table which is attributed to Victor Shoup's 1996 work while at + * Bellcore: "On Fast and Provably Secure MessageAuthentication Based on + * Universal Hashing." See: http://www.shoup.net/papers/macs.pdf + * See, also section 4.1 of the "gcm-revised-spec" cited above. + */ + +/* + * This 16-entry table of pre-computed constants is used by the + * GHASH multiplier to improve over a strictly table-free but + * significantly slower 128x128 bit multiple within GF(2^128). + */ +static const uint64_t last4[16] = { + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0 }; + +/* + * Platform Endianness Neutralizing Load and Store Macro definitions + * GCM wants platform-neutral Big Endian (BE) byte ordering + */ +#define GET_UINT32_BE(n,b,i) { \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); } + +#define PUT_UINT32_BE(n,b,i) { \ + (b)[(i) ] = (uchar) ( (n) >> 24 ); \ + (b)[(i) + 1] = (uchar) ( (n) >> 16 ); \ + (b)[(i) + 2] = (uchar) ( (n) >> 8 ); \ + (b)[(i) + 3] = (uchar) ( (n) ); } + + +/****************************************************************************** + * + * GCM_INITIALIZE + * + * Must be called once to initialize the GCM library. + * + * At present, this only calls the AES keygen table generator, which expands + * the AES keying tables for use. This is NOT A THREAD-SAFE function, so it + * MUST be called during system initialization before a multi-threading + * environment is running. + * + ******************************************************************************/ +int gcm_initialize( void ) +{ + aes_init_keygen_tables(); + return( 0 ); +} + + +/****************************************************************************** + * + * GCM_MULT + * + * Performs a GHASH operation on the 128-bit input vector 'x', setting + * the 128-bit output vector to 'x' times H using our precomputed tables. + * 'x' and 'output' are seen as elements of GCM's GF(2^128) Galois field. + * + ******************************************************************************/ +static void gcm_mult( gcm_context *ctx, // pointer to established context + const uchar x[16], // pointer to 128-bit input vector + uchar output[16] ) // pointer to 128-bit output vector +{ + int i; + uchar lo, hi, rem; + uint64_t zh, zl; + + lo = (uchar)( x[15] & 0x0f ); + hi = (uchar)( x[15] >> 4 ); + zh = ctx->HH[lo]; + zl = ctx->HL[lo]; + + for( i = 15; i >= 0; i-- ) { + lo = (uchar) ( x[i] & 0x0f ); + hi = (uchar) ( x[i] >> 4 ); + + if( i != 15 ) { + rem = (uchar) ( zl & 0x0f ); + zl = ( zh << 60 ) | ( zl >> 4 ); + zh = ( zh >> 4 ); + zh ^= (uint64_t) last4[rem] << 48; + zh ^= ctx->HH[lo]; + zl ^= ctx->HL[lo]; + } + rem = (uchar) ( zl & 0x0f ); + zl = ( zh << 60 ) | ( zl >> 4 ); + zh = ( zh >> 4 ); + zh ^= (uint64_t) last4[rem] << 48; + zh ^= ctx->HH[hi]; + zl ^= ctx->HL[hi]; + } + PUT_UINT32_BE( zh >> 32, output, 0 ); + PUT_UINT32_BE( zh, output, 4 ); + PUT_UINT32_BE( zl >> 32, output, 8 ); + PUT_UINT32_BE( zl, output, 12 ); +} + + +/****************************************************************************** + * + * GCM_SETKEY + * + * This is called to set the AES-GCM key. It initializes the AES key + * and populates the gcm context's pre-calculated HTables. + * + ******************************************************************************/ +int gcm_setkey( gcm_context *ctx, // pointer to caller-provided gcm context + const uchar *key, // pointer to the AES encryption key + const uint keysize) // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) +{ + int ret, i, j; + uint64_t hi, lo; + uint64_t vl, vh; + unsigned char h[16]; + + memset( ctx, 0, sizeof(gcm_context) ); // zero caller-provided GCM context + memset( h, 0, 16 ); // initialize the block to encrypt + + // encrypt the null 128-bit block to generate a key-based value + // which is then used to initialize our GHASH lookup tables + if(( ret = aes_setkey( &ctx->aes_ctx, ENCRYPT, key, keysize )) != 0 ) + return( ret ); + if(( ret = aes_cipher( &ctx->aes_ctx, h, h )) != 0 ) + return( ret ); + + GET_UINT32_BE( hi, h, 0 ); // pack h as two 64-bit ints, big-endian + GET_UINT32_BE( lo, h, 4 ); + vh = (uint64_t) hi << 32 | lo; + + GET_UINT32_BE( hi, h, 8 ); + GET_UINT32_BE( lo, h, 12 ); + vl = (uint64_t) hi << 32 | lo; + + ctx->HL[8] = vl; // 8 = 1000 corresponds to 1 in GF(2^128) + ctx->HH[8] = vh; + ctx->HH[0] = 0; // 0 corresponds to 0 in GF(2^128) + ctx->HL[0] = 0; + + for( i = 4; i > 0; i >>= 1 ) { + uint32_t T = (uint32_t) ( vl & 1 ) * 0xe1000000U; + vl = ( vh << 63 ) | ( vl >> 1 ); + vh = ( vh >> 1 ) ^ ( (uint64_t) T << 32); + ctx->HL[i] = vl; + ctx->HH[i] = vh; + } + for (i = 2; i < 16; i <<= 1 ) { + uint64_t *HiL = ctx->HL + i, *HiH = ctx->HH + i; + vh = *HiH; + vl = *HiL; + for( j = 1; j < i; j++ ) { + HiH[j] = vh ^ ctx->HH[j]; + HiL[j] = vl ^ ctx->HL[j]; + } + } + return( 0 ); +} + + +/****************************************************************************** + * + * GCM processing occurs four phases: SETKEY, START, UPDATE and FINISH. + * + * SETKEY: + * + * START: Sets the Encryption/Decryption mode. + * Accepts the initialization vector and additional data. + * + * UPDATE: Encrypts or decrypts the plaintext or ciphertext. + * + * FINISH: Performs a final GHASH to generate the authentication tag. + * + ****************************************************************************** + * + * GCM_START + * + * Given a user-provided GCM context, this initializes it, sets the encryption + * mode, and preprocesses the initialization vector and additional AEAD data. + * + ******************************************************************************/ +int gcm_start( gcm_context *ctx, // pointer to user-provided GCM context + int mode, // GCM_ENCRYPT or GCM_DECRYPT + const uchar *iv, // pointer to initialization vector + size_t iv_len, // IV length in bytes (should == 12) + const uchar *add, // ptr to additional AEAD data (NULL if none) + size_t add_len ) // length of additional AEAD data (bytes) +{ + int ret; // our error return if the AES encrypt fails + uchar work_buf[16]; // XOR source built from provided IV if len != 16 + const uchar *p; // general purpose array pointer + size_t use_len; // byte count to process, up to 16 bytes + size_t i; // local loop iterator + + // since the context might be reused under the same key + // we zero the working buffers for this next new process + memset( ctx->y, 0x00, sizeof(ctx->y ) ); + memset( ctx->buf, 0x00, sizeof(ctx->buf) ); + ctx->len = 0; + ctx->add_len = 0; + + ctx->mode = mode; // set the GCM encryption/decryption mode + ctx->aes_ctx.mode = ENCRYPT; // GCM *always* runs AES in ENCRYPTION mode + + if( iv_len == 12 ) { // GCM natively uses a 12-byte, 96-bit IV + memcpy( ctx->y, iv, iv_len ); // copy the IV to the top of the 'y' buff + ctx->y[15] = 1; // start "counting" from 1 (not 0) + } + else // if we don't have a 12-byte IV, we GHASH whatever we've been given + { + memset( work_buf, 0x00, 16 ); // clear the working buffer + PUT_UINT32_BE( iv_len * 8, work_buf, 12 ); // place the IV into buffer + + p = iv; + while( iv_len > 0 ) { + use_len = ( iv_len < 16 ) ? iv_len : 16; + for( i = 0; i < use_len; i++ ) ctx->y[i] ^= p[i]; + gcm_mult( ctx, ctx->y, ctx->y ); + iv_len -= use_len; + p += use_len; + } + for( i = 0; i < 16; i++ ) ctx->y[i] ^= work_buf[i]; + gcm_mult( ctx, ctx->y, ctx->y ); + } + if( ( ret = aes_cipher( &ctx->aes_ctx, ctx->y, ctx->base_ectr ) ) != 0 ) + return( ret ); + + ctx->add_len = add_len; + p = add; + while( add_len > 0 ) { + use_len = ( add_len < 16 ) ? add_len : 16; + for( i = 0; i < use_len; i++ ) ctx->buf[i] ^= p[i]; + gcm_mult( ctx, ctx->buf, ctx->buf ); + add_len -= use_len; + p += use_len; + } + return( 0 ); +} + +/****************************************************************************** + * + * GCM_UPDATE + * + * This is called once or more to process bulk plaintext or ciphertext data. + * We give this some number of bytes of input and it returns the same number + * of output bytes. If called multiple times (which is fine) all but the final + * invocation MUST be called with length mod 16 == 0. (Only the final call can + * have a partial block length of < 128 bits.) + * + ******************************************************************************/ +int gcm_update( gcm_context *ctx, // pointer to user-provided GCM context + size_t length, // length, in bytes, of data to process + const uchar *input, // pointer to source data + uchar *output ) // pointer to destination data +{ + int ret; // our error return if the AES encrypt fails + uchar ectr[16]; // counter-mode cipher output for XORing + size_t use_len; // byte count to process, up to 16 bytes + size_t i; // local loop iterator + + ctx->len += length; // bump the GCM context's running length count + + while( length > 0 ) { + // clamp the length to process at 16 bytes + use_len = ( length < 16 ) ? length : 16; + + // increment the context's 128-bit IV||Counter 'y' vector + for( i = 16; i > 12; i-- ) if( ++ctx->y[i - 1] != 0 ) break; + + // encrypt the context's 'y' vector under the established key + if( ( ret = aes_cipher( &ctx->aes_ctx, ctx->y, ectr ) ) != 0 ) + return( ret ); + + // encrypt or decrypt the input to the output + if( ctx->mode == ENCRYPT ) + { + for( i = 0; i < use_len; i++ ) { + // XOR the cipher's ouptut vector (ectr) with our input + output[i] = (uchar) ( ectr[i] ^ input[i] ); + // now we mix in our data into the authentication hash. + // if we're ENcrypting we XOR in the post-XOR (output) + // results, but if we're DEcrypting we XOR in the input + // data + ctx->buf[i] ^= output[i]; + } + } + else + { + for( i = 0; i < use_len; i++ ) { + // but if we're DEcrypting we XOR in the input data first, + // i.e. before saving to ouput data, otherwise if the input + // and output buffer are the same (inplace decryption) we + // would not get the correct auth tag + + ctx->buf[i] ^= input[i]; + + // XOR the cipher's ouptut vector (ectr) with our input + output[i] = (uchar) ( ectr[i] ^ input[i] ); + } + } + gcm_mult( ctx, ctx->buf, ctx->buf ); // perform a GHASH operation + + length -= use_len; // drop the remaining byte count to process + input += use_len; // bump our input pointer forward + output += use_len; // bump our output pointer forward + } + return( 0 ); +} + +/****************************************************************************** + * + * GCM_FINISH + * + * This is called once after all calls to GCM_UPDATE to finalize the GCM. + * It performs the final GHASH to produce the resulting authentication TAG. + * + ******************************************************************************/ +int gcm_finish( gcm_context *ctx, // pointer to user-provided GCM context + uchar *tag, // pointer to buffer which receives the tag + size_t tag_len ) // length, in bytes, of the tag-receiving buf +{ + uchar work_buf[16]; + uint64_t orig_len = ctx->len * 8; + uint64_t orig_add_len = ctx->add_len * 8; + size_t i; + + if( tag_len != 0 ) memcpy( tag, ctx->base_ectr, tag_len ); + + if( orig_len || orig_add_len ) { + memset( work_buf, 0x00, 16 ); + + PUT_UINT32_BE( ( orig_add_len >> 32 ), work_buf, 0 ); + PUT_UINT32_BE( ( orig_add_len ), work_buf, 4 ); + PUT_UINT32_BE( ( orig_len >> 32 ), work_buf, 8 ); + PUT_UINT32_BE( ( orig_len ), work_buf, 12 ); + + for( i = 0; i < 16; i++ ) ctx->buf[i] ^= work_buf[i]; + gcm_mult( ctx, ctx->buf, ctx->buf ); + for( i = 0; i < tag_len; i++ ) tag[i] ^= ctx->buf[i]; + } + return( 0 ); +} + + +/****************************************************************************** + * + * GCM_CRYPT_AND_TAG + * + * This either encrypts or decrypts the user-provided data and, either + * way, generates an authentication tag of the requested length. It must be + * called with a GCM context whose key has already been set with GCM_SETKEY. + * + * The user would typically call this explicitly to ENCRYPT a buffer of data + * and optional associated data, and produce its an authentication tag. + * + * To reverse the process the user would typically call the companion + * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided + * authentication tag. The GCM_AUTH_DECRYPT function calls this function + * to perform its decryption and tag generation, which it then compares. + * + ******************************************************************************/ +int gcm_crypt_and_tag( + gcm_context *ctx, // gcm context with key already setup + int mode, // cipher direction: GCM_ENCRYPT or GCM_DECRYPT + const uchar *iv, // pointer to the 12-byte initialization vector + size_t iv_len, // byte length if the IV. should always be 12 + const uchar *add, // pointer to the non-ciphered additional data + size_t add_len, // byte length of the additional AEAD data + const uchar *input, // pointer to the cipher data source + uchar *output, // pointer to the cipher data destination + size_t length, // byte length of the cipher data + uchar *tag, // pointer to the tag to be generated + size_t tag_len ) // byte length of the tag to be generated +{ /* + assuming that the caller has already invoked gcm_setkey to + prepare the gcm context with the keying material, we simply + invoke each of the three GCM sub-functions in turn... + */ + gcm_start ( ctx, mode, iv, iv_len, add, add_len ); + gcm_update ( ctx, length, input, output ); + gcm_finish ( ctx, tag, tag_len ); + return( 0 ); +} + + +/****************************************************************************** + * + * GCM_AUTH_DECRYPT + * + * This DECRYPTS a user-provided data buffer with optional associated data. + * It then verifies a user-supplied authentication tag against the tag just + * re-created during decryption to verify that the data has not been altered. + * + * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption + * and authentication tag generation. + * + ******************************************************************************/ +int gcm_auth_decrypt( + gcm_context *ctx, // gcm context with key already setup + const uchar *iv, // pointer to the 12-byte initialization vector + size_t iv_len, // byte length if the IV. should always be 12 + const uchar *add, // pointer to the non-ciphered additional data + size_t add_len, // byte length of the additional AEAD data + const uchar *input, // pointer to the cipher data source + uchar *output, // pointer to the cipher data destination + size_t length, // byte length of the cipher data + const uchar *tag, // pointer to the tag to be authenticated + size_t tag_len ) // byte length of the tag <= 16 +{ + uchar check_tag[16]; // the tag generated and returned by decryption + int diff; // an ORed flag to detect authentication errors + size_t i; // our local iterator + /* + we use GCM_DECRYPT_AND_TAG (above) to perform our decryption + (which is an identical XORing to reverse the previous one) + and also to re-generate the matching authentication tag + */ + gcm_crypt_and_tag( ctx, DECRYPT, iv, iv_len, add, add_len, + input, output, length, check_tag, tag_len ); + + // now we verify the authentication tag in 'constant time' + for( diff = 0, i = 0; i < tag_len; i++ ) + diff |= tag[i] ^ check_tag[i]; + + if( diff != 0 ) { // see whether any bits differed? + memset( output, 0, length ); // if so... wipe the output data + return( GCM_AUTH_FAILURE ); // return GCM_AUTH_FAILURE + } + return( 0 ); +} + +/****************************************************************************** + * + * GCM_ZERO_CTX + * + * The GCM context contains both the GCM context and the AES context. + * This includes keying and key-related material which is security- + * sensitive, so it MUST be zeroed after use. This function does that. + * + ******************************************************************************/ +void gcm_zero_ctx( gcm_context *ctx ) +{ + // zero the context originally provided to us + memset( ctx, 0, sizeof( gcm_context ) ); +} diff --git a/applications/external/esubghz_chat/crypto/gcm.h b/applications/external/esubghz_chat/crypto/gcm.h new file mode 100644 index 000000000..063154195 --- /dev/null +++ b/applications/external/esubghz_chat/crypto/gcm.h @@ -0,0 +1,187 @@ +/****************************************************************************** +* +* THIS SOURCE CODE IS HEREBY PLACED INTO THE PUBLIC DOMAIN FOR THE GOOD OF ALL +* +* This is a simple and straightforward implementation of AES-GCM authenticated +* encryption. The focus of this work was correctness & accuracy. It is written +* in straight 'C' without any particular focus upon optimization or speed. It +* should be endian (memory byte order) neutral since the few places that care +* are handled explicitly. +* +* This implementation of AES-GCM was created by Steven M. Gibson of GRC.com. +* +* It is intended for general purpose use, but was written in support of GRC's +* reference implementation of the SQRL (Secure Quick Reliable Login) client. +* +* See: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf +* http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ \ +* gcm/gcm-revised-spec.pdf +* +* NO COPYRIGHT IS CLAIMED IN THIS WORK, HOWEVER, NEITHER IS ANY WARRANTY MADE +* REGARDING ITS FITNESS FOR ANY PARTICULAR PURPOSE. USE IT AT YOUR OWN RISK. +* +*******************************************************************************/ +#ifndef GCM_HEADER +#define GCM_HEADER + +#define GCM_AUTH_FAILURE 0x55555555 // authentication failure + +#include "aes.h" // gcm_context includes aes_context + +#if defined(_MSC_VER) + #include + typedef unsigned int size_t;// use the right type for length declarations + typedef UINT32 uint32_t; + typedef UINT64 uint64_t; +#else + #include +#endif + + +/****************************************************************************** + * GCM_CONTEXT : GCM context / holds keytables, instance data, and AES ctx + ******************************************************************************/ +typedef struct { + int mode; // cipher direction: encrypt/decrypt + uint64_t len; // cipher data length processed so far + uint64_t add_len; // total add data length + uint64_t HL[16]; // precalculated lo-half HTable + uint64_t HH[16]; // precalculated hi-half HTable + uchar base_ectr[16]; // first counter-mode cipher output for tag + uchar y[16]; // the current cipher-input IV|Counter value + uchar buf[16]; // buf working value + aes_context aes_ctx; // cipher context used +} gcm_context; + + +/****************************************************************************** + * GCM_CONTEXT : MUST be called once before ANY use of this library + ******************************************************************************/ +int gcm_initialize( void ); + + +/****************************************************************************** + * GCM_SETKEY : sets the GCM (and AES) keying material for use + ******************************************************************************/ +int gcm_setkey( gcm_context *ctx, // caller-provided context ptr + const uchar *key, // pointer to cipher key + const uint keysize // size in bytes (must be 16, 24, 32 for + // 128, 192 or 256-bit keys respectively) +); // returns 0 for success + + +/****************************************************************************** + * + * GCM_CRYPT_AND_TAG + * + * This either encrypts or decrypts the user-provided data and, either + * way, generates an authentication tag of the requested length. It must be + * called with a GCM context whose key has already been set with GCM_SETKEY. + * + * The user would typically call this explicitly to ENCRYPT a buffer of data + * and optional associated data, and produce its an authentication tag. + * + * To reverse the process the user would typically call the companion + * GCM_AUTH_DECRYPT function to decrypt data and verify a user-provided + * authentication tag. The GCM_AUTH_DECRYPT function calls this function + * to perform its decryption and tag generation, which it then compares. + * + ******************************************************************************/ +int gcm_crypt_and_tag( + gcm_context *ctx, // gcm context with key already setup + int mode, // cipher direction: ENCRYPT (1) or DECRYPT (0) + const uchar *iv, // pointer to the 12-byte initialization vector + size_t iv_len, // byte length if the IV. should always be 12 + const uchar *add, // pointer to the non-ciphered additional data + size_t add_len, // byte length of the additional AEAD data + const uchar *input, // pointer to the cipher data source + uchar *output, // pointer to the cipher data destination + size_t length, // byte length of the cipher data + uchar *tag, // pointer to the tag to be generated + size_t tag_len ); // byte length of the tag to be generated + + +/****************************************************************************** + * + * GCM_AUTH_DECRYPT + * + * This DECRYPTS a user-provided data buffer with optional associated data. + * It then verifies a user-supplied authentication tag against the tag just + * re-created during decryption to verify that the data has not been altered. + * + * This function calls GCM_CRYPT_AND_TAG (above) to perform the decryption + * and authentication tag generation. + * + ******************************************************************************/ +int gcm_auth_decrypt( + gcm_context *ctx, // gcm context with key already setup + const uchar *iv, // pointer to the 12-byte initialization vector + size_t iv_len, // byte length if the IV. should always be 12 + const uchar *add, // pointer to the non-ciphered additional data + size_t add_len, // byte length of the additional AEAD data + const uchar *input, // pointer to the cipher data source + uchar *output, // pointer to the cipher data destination + size_t length, // byte length of the cipher data + const uchar *tag, // pointer to the tag to be authenticated + size_t tag_len ); // byte length of the tag <= 16 + + +/****************************************************************************** + * + * GCM_START + * + * Given a user-provided GCM context, this initializes it, sets the encryption + * mode, and preprocesses the initialization vector and additional AEAD data. + * + ******************************************************************************/ +int gcm_start( gcm_context *ctx, // pointer to user-provided GCM context + int mode, // ENCRYPT (1) or DECRYPT (0) + const uchar *iv, // pointer to initialization vector + size_t iv_len, // IV length in bytes (should == 12) + const uchar *add, // pointer to additional AEAD data (NULL if none) + size_t add_len ); // length of additional AEAD data (bytes) + + +/****************************************************************************** + * + * GCM_UPDATE + * + * This is called once or more to process bulk plaintext or ciphertext data. + * We give this some number of bytes of input and it returns the same number + * of output bytes. If called multiple times (which is fine) all but the final + * invocation MUST be called with length mod 16 == 0. (Only the final call can + * have a partial block length of < 128 bits.) + * + ******************************************************************************/ +int gcm_update( gcm_context *ctx, // pointer to user-provided GCM context + size_t length, // length, in bytes, of data to process + const uchar *input, // pointer to source data + uchar *output ); // pointer to destination data + + +/****************************************************************************** + * + * GCM_FINISH + * + * This is called once after all calls to GCM_UPDATE to finalize the GCM. + * It performs the final GHASH to produce the resulting authentication TAG. + * + ******************************************************************************/ +int gcm_finish( gcm_context *ctx, // pointer to user-provided GCM context + uchar *tag, // ptr to tag buffer - NULL if tag_len = 0 + size_t tag_len ); // length, in bytes, of the tag-receiving buf + + +/****************************************************************************** + * + * GCM_ZERO_CTX + * + * The GCM context contains both the GCM context and the AES context. + * This includes keying and key-related material which is security- + * sensitive, so it MUST be zeroed after use. This function does that. + * + ******************************************************************************/ +void gcm_zero_ctx( gcm_context *ctx ); + + +#endif /* GCM_HEADER */ diff --git a/applications/external/esubghz_chat/crypto_wrapper.c b/applications/external/esubghz_chat/crypto_wrapper.c new file mode 100644 index 000000000..397bc13d1 --- /dev/null +++ b/applications/external/esubghz_chat/crypto_wrapper.c @@ -0,0 +1,191 @@ +#include +#include +#include + +#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL +#include "crypto/gcm.h" +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + +#include "crypto_wrapper.h" + +DICT_DEF2(ESubGhzChatReplayDict, uint64_t, uint32_t) + +struct ESugGhzChatCryptoCtx { + uint8_t key[KEY_BITS / 8]; +#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL + gcm_context gcm_ctx; +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + ESubGhzChatReplayDict_t replay_dict; + uint64_t run_id; + uint32_t counter; +}; + +struct ESubGhzChatCryptoMsg { + uint64_t run_id; + uint32_t counter; + uint8_t iv[IV_BYTES]; + uint8_t tag[TAG_BYTES]; + uint8_t data[0]; +} __attribute__ ((packed)); + +void crypto_init(void) +{ +#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL + /* init the GCM and AES tables */ + gcm_initialize(); +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ +} + +void crypto_explicit_bzero(void *s, size_t len) +{ + memset(s, 0, len); + asm volatile("" ::: "memory"); +} + +ESubGhzChatCryptoCtx *crypto_ctx_alloc(void) +{ + ESubGhzChatCryptoCtx *ret = malloc(sizeof(ESubGhzChatCryptoCtx)); + + if (ret != NULL) { + memset(ret, 0, sizeof(ESubGhzChatCryptoCtx)); + ESubGhzChatReplayDict_init(ret->replay_dict); + ret->run_id = 0; + ret->counter = 1; + } + + return ret; +} + +void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx) +{ + crypto_ctx_clear(ctx); + ESubGhzChatReplayDict_clear(ctx->replay_dict); + free(ctx); +} + +void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx) +{ + crypto_explicit_bzero(ctx->key, sizeof(ctx->key)); +#ifndef FURI_HAL_CRYPTO_ADVANCED_AVAIL + crypto_explicit_bzero(&(ctx->gcm_ctx), sizeof(ctx->gcm_ctx)); +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + ESubGhzChatReplayDict_reset(ctx->replay_dict); + ctx->run_id = 0; + ctx->counter = 1; +} + +static uint64_t crypto_calc_run_id(FuriString *flipper_name, uint32_t tick) +{ + const char *fn = furi_string_get_cstr(flipper_name); + size_t fn_len = strlen(fn); + + uint8_t h_in[fn_len + sizeof(uint32_t)]; + memcpy(h_in, fn, fn_len); + memcpy(h_in + fn_len, &tick, sizeof(uint32_t)); + + uint8_t h_out[256]; + sha256(h_in, fn_len + sizeof(uint32_t), h_out); + + uint64_t run_id; + memcpy(&run_id, h_out, sizeof(uint64_t)); + + return run_id; +} + +bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key, + FuriString *flipper_name, uint32_t tick) +{ + ctx->run_id = crypto_calc_run_id(flipper_name, tick); + ctx->counter = 1; + + memcpy(ctx->key, key, KEY_BITS / 8); +#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL + return true; +#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + return (gcm_setkey(&(ctx->gcm_ctx), key, KEY_BITS / 8) == 0); +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ +} + +void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key) +{ + memcpy(key, ctx->key, KEY_BITS / 8); +} + +bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len, + uint8_t *out) +{ + if (in_len < MSG_OVERHEAD + 1) { + return false; + } + + struct ESubGhzChatCryptoMsg *msg = (struct ESubGhzChatCryptoMsg *) in; + + // check if message is stale, if yes, discard + uint32_t *counter = ESubGhzChatReplayDict_get(ctx->replay_dict, + msg->run_id); + if (counter != NULL) { + if (*counter >= __ntohl(msg->counter)) { + return false; + } + } + + // decrypt and auth message +#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL + bool ret = (furi_hal_crypto_gcm_decrypt_and_verify(ctx->key, + msg->iv, + (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES, + msg->data, out, + in_len - MSG_OVERHEAD, + msg->tag) == FuriHalCryptoGCMStateOk); +#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + bool ret = (gcm_auth_decrypt(&(ctx->gcm_ctx), + msg->iv, IV_BYTES, + (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES, + msg->data, out, + in_len - MSG_OVERHEAD, + msg->tag, TAG_BYTES) == 0); +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + + // if auth was successful update replay dict + if (ret) { + ESubGhzChatReplayDict_set_at(ctx->replay_dict, msg->run_id, + __ntohl(msg->counter)); + } + + return ret; +} + +bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len, + uint8_t *out) +{ + struct ESubGhzChatCryptoMsg *msg = (struct ESubGhzChatCryptoMsg *) out; + + // fill message header + msg->run_id = ctx->run_id; + msg->counter = __htonl(ctx->counter); + furi_hal_random_fill_buf(msg->iv, IV_BYTES); + + // encrypt message and store tag in header +#ifdef FURI_HAL_CRYPTO_ADVANCED_AVAIL + bool ret = (furi_hal_crypto_gcm_encrypt_and_tag(ctx->key, + msg->iv, + (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES, + in, msg->data, + in_len, + msg->tag) == FuriHalCryptoGCMStateOk); +#else /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + bool ret = (gcm_crypt_and_tag(&(ctx->gcm_ctx), ENCRYPT, + msg->iv, IV_BYTES, + (uint8_t *) msg, RUN_ID_BYTES + COUNTER_BYTES, + in, msg->data, + in_len, + msg->tag, TAG_BYTES) == 0); +#endif /* FURI_HAL_CRYPTO_ADVANCED_AVAIL */ + + // increase internal counter + if (ret) { + ctx->counter++; + } + + return ret; +} diff --git a/applications/external/esubghz_chat/crypto_wrapper.h b/applications/external/esubghz_chat/crypto_wrapper.h new file mode 100644 index 000000000..cfa992e75 --- /dev/null +++ b/applications/external/esubghz_chat/crypto_wrapper.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define RUN_ID_BYTES (sizeof(uint64_t)) +#define COUNTER_BYTES (sizeof(uint32_t)) +#define KEY_BITS 256 +#define IV_BYTES 12 +#define TAG_BYTES 16 + +#define MSG_OVERHEAD (RUN_ID_BYTES + COUNTER_BYTES + IV_BYTES + TAG_BYTES) + +typedef struct ESugGhzChatCryptoCtx ESubGhzChatCryptoCtx; + +void crypto_init(void); + +/* Function to clear sensitive memory. */ +void crypto_explicit_bzero(void *s, size_t len); + +ESubGhzChatCryptoCtx *crypto_ctx_alloc(void); +void crypto_ctx_free(ESubGhzChatCryptoCtx *ctx); + +void crypto_ctx_clear(ESubGhzChatCryptoCtx *ctx); + +bool crypto_ctx_set_key(ESubGhzChatCryptoCtx *ctx, const uint8_t *key, + FuriString *flipper_name, uint32_t tick); +void crypto_ctx_get_key(ESubGhzChatCryptoCtx *ctx, uint8_t *key); + +bool crypto_ctx_decrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len, + uint8_t *out); +bool crypto_ctx_encrypt(ESubGhzChatCryptoCtx *ctx, uint8_t *in, size_t in_len, + uint8_t *out); + +#ifdef __cplusplus +} +#endif diff --git a/applications/external/esubghz_chat/esubghz_chat.c b/applications/external/esubghz_chat/esubghz_chat.c new file mode 100644 index 000000000..0c0bd5c1f --- /dev/null +++ b/applications/external/esubghz_chat/esubghz_chat.c @@ -0,0 +1,737 @@ +#include +#include +#include +#include + +#include "esubghz_chat_i.h" + +#define CHAT_LEAVE_DELAY 10 +#define TICK_INTERVAL 50 +#define MESSAGE_COMPLETION_TIMEOUT 500 +#define TIMEOUT_BETWEEN_MESSAGES 500 + +#define KBD_UNLOCK_CNT 3 +#define KBD_UNLOCK_TIMEOUT 1000 + +/* Callback for RX events from the Sub-GHz worker. Records the current ticks as + * the time of the last reception. */ +static void have_read_cb(void* context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + state->last_time_rx_data = furi_get_tick(); +} + +/* Sets the header for the chat input field depending on whether or not a + * message preview exists. */ +void set_chat_input_header(ESubGhzChatState *state) +{ + if (strlen(state->msg_preview) == 0) { + text_input_set_header_text(state->text_input, "Message"); + } else { + text_input_set_header_text(state->text_input, + state->msg_preview); + } +} + +/* Appends the latest message to the chat box and prepares the message preview. + */ +void append_msg(ESubGhzChatState *state, const char *msg) +{ + /* append message to text box */ + furi_string_cat_printf(state->chat_box_store, "\n%s", msg); + + /* prepare message preview */ + strncpy(state->msg_preview, msg, MSG_PREVIEW_SIZE); + state->msg_preview[MSG_PREVIEW_SIZE] = 0; + set_chat_input_header(state); + + /* reset text box contents and focus */ + text_box_set_text(state->chat_box, + furi_string_get_cstr(state->chat_box_store)); + text_box_set_focus(state->chat_box, TextBoxFocusEnd); +} + +/* Decrypts a message for post_rx(). */ +static bool post_rx_decrypt(ESubGhzChatState *state, size_t rx_size) +{ + bool ret = crypto_ctx_decrypt(state->crypto_ctx, + state->rx_buffer, rx_size, + (uint8_t*) state->rx_str_buffer); + + if (ret) { + state->rx_str_buffer[rx_size - (MSG_OVERHEAD)] = 0; + } else { + state->rx_str_buffer[0] = 0; + } + + return ret; +} + +/* Post RX handler, decrypts received messages and calls append_msg(). */ +static void post_rx(ESubGhzChatState *state, size_t rx_size) +{ + furi_assert(state); + + if (rx_size == 0) { + return; + } + + furi_check(rx_size <= RX_TX_BUFFER_SIZE); + + /* decrypt if necessary */ + if (!state->encrypted) { + memcpy(state->rx_str_buffer, state->rx_buffer, rx_size); + state->rx_str_buffer[rx_size] = 0; + + /* remove trailing newline if it is there, for compat with CLI + * Sub-GHz chat */ + if (state->rx_str_buffer[rx_size - 1] == '\n') { + state->rx_str_buffer[rx_size - 1] = 0; + } + } else { + /* if decryption fails output an error message */ + if (!post_rx_decrypt(state, rx_size)) { + strcpy(state->rx_str_buffer, "ERR: Decryption failed!"); + } + } + + /* append message to text box and prepare message preview */ + append_msg(state, state->rx_str_buffer); + + /* send notification (make the flipper vibrate) */ + notification_message(state->notification, &sequence_single_vibro); +} + +/* Reads the message from msg_input, encrypts it if necessary and then + * transmits it. */ +void tx_msg_input(ESubGhzChatState *state) +{ + /* encrypt message if necessary */ + size_t msg_len = strlen(furi_string_get_cstr(state->msg_input)); + size_t tx_size = msg_len; + if (state->encrypted) { + tx_size += MSG_OVERHEAD; + furi_check(tx_size <= sizeof(state->tx_buffer)); + + crypto_ctx_encrypt(state->crypto_ctx, + (uint8_t *) + furi_string_get_cstr(state->msg_input), + msg_len, + state->tx_buffer); + } else { + tx_size += 2; + furi_check(tx_size <= sizeof(state->tx_buffer)); + memcpy(state->tx_buffer, + furi_string_get_cstr(state->msg_input), + msg_len); + + /* append \r\n for compat with Sub-GHz CLI chat */ + state->tx_buffer[msg_len] = '\r'; + state->tx_buffer[msg_len + 1] = '\n'; + } + + /* transmit */ + subghz_tx_rx_worker_write(state->subghz_worker, state->tx_buffer, + tx_size); +} + +/* Displays whether or not encryption has been enabled in the text box. Also + * clears the text input buffer to remove the password and starts the Sub-GHz + * worker. After starting the worker a join message is transmitted. */ +void enter_chat(ESubGhzChatState *state) +{ + furi_string_cat_printf(state->chat_box_store, "\nEncrypted: %s", + (state->encrypted ? "yes" : "no")); + + subghz_tx_rx_worker_start(state->subghz_worker, state->subghz_device, + state->frequency); + + /* concatenate the name prefix and join message */ + furi_string_set(state->msg_input, state->name_prefix); + furi_string_cat_str(state->msg_input, " joined chat."); + + /* encrypt and transmit message */ + tx_msg_input(state); + + /* clear message input buffer */ + furi_string_set_char(state->msg_input, 0, 0); +} + +/* Sends a leave message */ +void exit_chat(ESubGhzChatState *state) +{ + /* concatenate the name prefix and leave message */ + furi_string_set(state->msg_input, state->name_prefix); + furi_string_cat_str(state->msg_input, " left chat."); + + /* encrypt and transmit message */ + tx_msg_input(state); + + /* clear message input buffer */ + furi_string_set_char(state->msg_input, 0, 0); + + /* wait for leave message to be delivered */ + furi_delay_ms(CHAT_LEAVE_DELAY); +} + +/* Whether or not to display the locked message. */ +static bool kbd_lock_msg_display(ESubGhzChatState *state) +{ + return (state->kbd_lock_msg_ticks != 0); +} + +/* Whether or not to hide the locked message again. */ +static bool kbd_lock_msg_reset_timeout(ESubGhzChatState *state) +{ + if (state->kbd_lock_msg_ticks == 0) { + return false; + } + + if (furi_get_tick() - state->kbd_lock_msg_ticks > KBD_UNLOCK_TIMEOUT) { + return true; + } + + return false; +} + +/* Resets the timeout for the locked message and turns off the backlight if + * specified. */ +static void kbd_lock_msg_reset(ESubGhzChatState *state, bool backlight_off) +{ + state->kbd_lock_msg_ticks = 0; + state->kbd_lock_count = 0; + + if (backlight_off) { + notification_message(state->notification, + &sequence_display_backlight_off); + } +} + +/* Locks the keyboard. */ +static void kbd_lock(ESubGhzChatState *state) +{ + state->kbd_locked = true; + kbd_lock_msg_reset(state, true); +} + +/* Unlocks the keyboard. */ +static void kbd_unlock(ESubGhzChatState *state) +{ + state->kbd_locked = false; + kbd_lock_msg_reset(state, false); +} + +/* Custom event callback for view dispatcher. Just calls scene manager. */ +static bool esubghz_chat_custom_event_callback(void* context, uint32_t event) +{ + FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_custom_event_callback"); + furi_assert(context); + ESubGhzChatState* state = context; + return scene_manager_handle_custom_event(state->scene_manager, event); +} + +/* Navigation event callback for view dispatcher. Just calls scene manager. */ +static bool esubghz_chat_navigation_event_callback(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_navigation_event_callback"); + furi_assert(context); + ESubGhzChatState* state = context; + return scene_manager_handle_back_event(state->scene_manager); +} + +/* Tick event callback for view dispatcher. Called every TICK_INTERVAL. Resets + * the locked message if necessary. Retrieves a received message from the + * Sub-GHz worker and calls post_rx(). Then calls the scene manager. */ +static void esubghz_chat_tick_event_callback(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "esubghz_chat_tick_event_callback"); + + furi_assert(context); + ESubGhzChatState* state = context; + + /* reset locked message if necessary */ + if (kbd_lock_msg_reset_timeout(state)) { + kbd_lock_msg_reset(state, true); + } + + /* if the maximum message size was reached or the + * MESSAGE_COMPLETION_TIMEOUT has expired, retrieve a message and call + * post_rx() */ + size_t avail = 0; + while ((avail = subghz_tx_rx_worker_available(state->subghz_worker)) > + 0) { + volatile uint32_t since_last_rx = furi_get_tick() - + state->last_time_rx_data; + if (avail < RX_TX_BUFFER_SIZE && since_last_rx < + MESSAGE_COMPLETION_TIMEOUT) { + break; + } + + size_t rx_size = subghz_tx_rx_worker_read(state->subghz_worker, + state->rx_buffer, RX_TX_BUFFER_SIZE); + post_rx(state, rx_size); + } + + /* call scene manager */ + scene_manager_handle_tick_event(state->scene_manager); +} + +/* Hooks into the view port's draw callback to overlay the keyboard locked + * message. */ +static void esubghz_hooked_draw_callback(Canvas* canvas, void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_draw_callback"); + + furi_assert(canvas); + + furi_assert(context); + ESubGhzChatState* state = context; + + /* call original callback */ + state->orig_draw_cb(canvas, state->view_dispatcher); + + /* display if the keyboard is locked */ + if (state->kbd_locked) { + canvas_set_font(canvas, FontPrimary); + elements_multiline_text_framed(canvas, 42, 30, "Locked"); + } + + /* display the unlock message if necessary */ + if (kbd_lock_msg_display(state)) { + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 8, 99, 48); + elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); + } +} + +/* Hooks into the view port's input callback to handle the user locking the + * keyboard. */ +static void esubghz_hooked_input_callback(InputEvent* event, void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "esubghz_hooked_input_callback"); + + furi_assert(event); + + furi_assert(context); + ESubGhzChatState* state = context; + + /* if the keyboard is locked no key presses are forwarded */ + if (state->kbd_locked) { + /* key has been pressed, display the unlock message and + * initiate the timer */ + if (state->kbd_lock_count == 0) { + state->kbd_lock_msg_ticks = furi_get_tick(); + } + + /* back button has been pressed, increase the lock counter */ + if (event->key == InputKeyBack && event->type == + InputTypeShort) { + state->kbd_lock_count++; + } + + /* unlock the keyboard */ + if (state->kbd_lock_count >= KBD_UNLOCK_CNT) { + kbd_unlock(state); + } + + /* do not handle the event */ + return; + } + + if (event->key == InputKeyOk) { + /* if we are in the chat view and no input is ongoing, allow + * locking */ + if (state->view_dispatcher->current_view == + text_box_get_view(state->chat_box) && + !(state->kbd_ok_input_ongoing)) { + /* lock keyboard upon long press of Ok button */ + if (event->type == InputTypeLong) { + kbd_lock(state); + } + + /* do not handle any Ok key events to prevent blocking + * of other keys */ + return; + } + + /* handle ongoing inputs when changing to chat view */ + if (event->type == InputTypePress) { + state->kbd_ok_input_ongoing = true; + } else if (event->type == InputTypeRelease) { + state->kbd_ok_input_ongoing = false; + } + } + + if (event->key == InputKeyLeft) { + /* if we are in the chat view and no input is ongoing, allow + * switching to msg input */ + if (state->view_dispatcher->current_view == + text_box_get_view(state->chat_box) && + !(state->kbd_left_input_ongoing)) { + /* go to msg input upon short press of Left button */ + if (event->type == InputTypeShort) { + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_GotoMsgInput); + } + + /* do not handle any Left key events to prevent + * blocking of other keys */ + return; + } + + /* handle ongoing inputs when changing to chat view */ + if (event->type == InputTypePress) { + state->kbd_left_input_ongoing = true; + } else if (event->type == InputTypeRelease) { + state->kbd_left_input_ongoing = false; + } + } + + if (event->key == InputKeyRight) { + /* if we are in the chat view and no input is ongoing, allow + * switching to key display */ + if (state->view_dispatcher->current_view == + text_box_get_view(state->chat_box) && + !(state->kbd_right_input_ongoing)) { + /* go to key display upon short press of Right button + */ + if (event->type == InputTypeShort) { + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_GotoKeyDisplay); + } + + /* do not handle any Right key events to prevent + * blocking of other keys */ + return; + } + + /* handle ongoing inputs when changing to chat view */ + if (event->type == InputTypePress) { + state->kbd_right_input_ongoing = true; + } else if (event->type == InputTypeRelease) { + state->kbd_right_input_ongoing = false; + } + } + + /* call original callback */ + state->orig_input_cb(event, state->view_dispatcher); +} + +static bool helper_strings_alloc(ESubGhzChatState *state) +{ + furi_assert(state); + + state->name_prefix = furi_string_alloc(); + if (state->name_prefix == NULL) { + return false; + } + + state->msg_input = furi_string_alloc(); + if (state->msg_input == NULL) { + furi_string_free(state->name_prefix); + return false; + } + + return true; +} + +static void helper_strings_free(ESubGhzChatState *state) +{ + furi_assert(state); + + furi_string_free(state->name_prefix); + furi_string_free(state->msg_input); +} + +static bool chat_box_alloc(ESubGhzChatState *state) +{ + furi_assert(state); + + state->chat_box = text_box_alloc(); + if (state->chat_box == NULL) { + return false; + } + + state->chat_box_store = furi_string_alloc(); + if (state->chat_box_store == NULL) { + text_box_free(state->chat_box); + return false; + } + + furi_string_reserve(state->chat_box_store, CHAT_BOX_STORE_SIZE); + furi_string_set_char(state->chat_box_store, 0, 0); + text_box_set_text(state->chat_box, + furi_string_get_cstr(state->chat_box_store)); + text_box_set_focus(state->chat_box, TextBoxFocusEnd); + + return true; +} + +static void chat_box_free(ESubGhzChatState *state) +{ + furi_assert(state); + + text_box_free(state->chat_box); + furi_string_free(state->chat_box_store); +} + +int32_t esubghz_chat(void) +{ + /* init the crypto system */ + crypto_init(); + + int32_t err = -1; + + FURI_LOG_I(APPLICATION_NAME, "Starting..."); + + /* allocate necessary structs and buffers */ + + ESubGhzChatState *state = malloc(sizeof(ESubGhzChatState)); + if (state == NULL) { + goto err_alloc; + } + memset(state, 0, sizeof(*state)); + + state->scene_manager = scene_manager_alloc( + &esubghz_chat_scene_event_handlers, state); + if (state->scene_manager == NULL) { + goto err_alloc_sm; + } + + state->view_dispatcher = view_dispatcher_alloc(); + if (state->view_dispatcher == NULL) { + goto err_alloc_vd; + } + + if (!helper_strings_alloc(state)) { + goto err_alloc_hs; + } + + state->menu = menu_alloc(); + if (state->menu == NULL) { + goto err_alloc_menu; + } + + state->text_input = text_input_alloc(); + if (state->text_input == NULL) { + goto err_alloc_ti; + } + + state->hex_key_input = byte_input_alloc(); + if (state->hex_key_input == NULL) { + goto err_alloc_hki; + } + + if (!chat_box_alloc(state)) { + goto err_alloc_cb; + } + + state->key_display = dialog_ex_alloc(); + if (state->key_display == NULL) { + goto err_alloc_kd; + } + + state->nfc_popup = popup_alloc(); + if (state->nfc_popup == NULL) { + goto err_alloc_np; + } + + state->subghz_worker = subghz_tx_rx_worker_alloc(); + if (state->subghz_worker == NULL) { + goto err_alloc_worker; + } + + state->nfc_worker = nfc_worker_alloc(); + if (state->nfc_worker == NULL) { + goto err_alloc_nworker; + } + + state->nfc_dev_data = malloc(sizeof(NfcDeviceData)); + if (state->nfc_dev_data == NULL) { + goto err_alloc_ndevdata; + } + memset(state->nfc_dev_data, 0, sizeof(NfcDeviceData)); + + state->crypto_ctx = crypto_ctx_alloc(); + if (state->crypto_ctx == NULL) { + goto err_alloc_crypto; + } + + /* set the have_read callback of the Sub-GHz worker */ + subghz_tx_rx_worker_set_callback_have_read(state->subghz_worker, + have_read_cb, state); + + /* enter suppress charge mode */ + furi_hal_power_suppress_charge_enter(); + + /* init internal device */ + subghz_devices_init(); + state->subghz_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME); + + /* set chat name prefix */ + furi_string_printf(state->name_prefix, "%s", + furi_hal_version_get_name_ptr()); + + /* get notification record, we use this to make the flipper vibrate */ + /* no error handling here, don't know how */ + state->notification = furi_record_open(RECORD_NOTIFICATION); + + /* hook into the view port's draw and input callbacks */ + state->orig_draw_cb = state->view_dispatcher->view_port->draw_callback; + state->orig_input_cb = state->view_dispatcher->view_port->input_callback; + view_port_draw_callback_set(state->view_dispatcher->view_port, + esubghz_hooked_draw_callback, state); + view_port_input_callback_set(state->view_dispatcher->view_port, + esubghz_hooked_input_callback, state); + + view_dispatcher_enable_queue(state->view_dispatcher); + + /* set callbacks for view dispatcher */ + view_dispatcher_set_event_callback_context(state->view_dispatcher, state); + view_dispatcher_set_custom_event_callback( + state->view_dispatcher, + esubghz_chat_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + state->view_dispatcher, + esubghz_chat_navigation_event_callback); + view_dispatcher_set_tick_event_callback( + state->view_dispatcher, + esubghz_chat_tick_event_callback, + TICK_INTERVAL); + + /* add our two views to the view dispatcher */ + view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Menu, + menu_get_view(state->menu)); + view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_Input, + text_input_get_view(state->text_input)); + view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_HexKeyInput, + byte_input_get_view(state->hex_key_input)); + view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_ChatBox, + text_box_get_view(state->chat_box)); + view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_KeyDisplay, + dialog_ex_get_view(state->key_display)); + view_dispatcher_add_view(state->view_dispatcher, ESubGhzChatView_NfcPopup, + popup_get_view(state->nfc_popup)); + + /* get the GUI record and attach the view dispatcher to the GUI */ + /* no error handling here, don't know how */ + Gui *gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui(state->view_dispatcher, gui, + ViewDispatcherTypeFullscreen); + + /* switch to the frequency input scene */ + scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_FreqInput); + + /* run the view dispatcher, this call only returns when we close the + * application */ + view_dispatcher_run(state->view_dispatcher); + + /* if it is running, stop the Sub-GHz worker */ + if (subghz_tx_rx_worker_is_running(state->subghz_worker)) { + exit_chat(state); + subghz_tx_rx_worker_stop(state->subghz_worker); + } + + /* if it is running, stop the NFC worker */ + nfc_worker_stop(state->nfc_worker); + + err = 0; + + /* close GUI record */ + furi_record_close(RECORD_GUI); + + /* remove our two views from the view dispatcher */ + view_dispatcher_remove_view(state->view_dispatcher, + ESubGhzChatView_Menu); + view_dispatcher_remove_view(state->view_dispatcher, + ESubGhzChatView_Input); + view_dispatcher_remove_view(state->view_dispatcher, + ESubGhzChatView_HexKeyInput); + view_dispatcher_remove_view(state->view_dispatcher, + ESubGhzChatView_ChatBox); + view_dispatcher_remove_view(state->view_dispatcher, + ESubGhzChatView_KeyDisplay); + view_dispatcher_remove_view(state->view_dispatcher, + ESubGhzChatView_NfcPopup); + + /* close notification record */ + furi_record_close(RECORD_NOTIFICATION); + + /* clear the key and potential password */ + crypto_explicit_bzero(state->text_input_store, + sizeof(state->text_input_store)); + crypto_explicit_bzero(state->hex_key_input_store, + sizeof(state->hex_key_input_store)); + crypto_explicit_bzero(state->key_hex_str, sizeof(state->key_hex_str)); + crypto_ctx_clear(state->crypto_ctx); + + /* clear nfc data */ + if (state->nfc_dev_data->parsed_data != NULL) { + furi_string_free(state->nfc_dev_data->parsed_data); + } + crypto_explicit_bzero(state->nfc_dev_data, sizeof(NfcDeviceData)); + + /* deinit devices */ + subghz_devices_deinit(); + + /* exit suppress charge mode */ + furi_hal_power_suppress_charge_exit(); + + /* free everything we allocated */ + + crypto_ctx_free(state->crypto_ctx); + +err_alloc_crypto: + free(state->nfc_dev_data); + +err_alloc_ndevdata: + nfc_worker_free(state->nfc_worker); + +err_alloc_nworker: + subghz_tx_rx_worker_free(state->subghz_worker); + +err_alloc_worker: + popup_free(state->nfc_popup); + +err_alloc_np: + dialog_ex_free(state->key_display); + +err_alloc_kd: + chat_box_free(state); + +err_alloc_cb: + byte_input_free(state->hex_key_input); + +err_alloc_hki: + text_input_free(state->text_input); + +err_alloc_ti: + menu_free(state->menu); + +err_alloc_menu: + helper_strings_free(state); + +err_alloc_hs: + view_dispatcher_free(state->view_dispatcher); + +err_alloc_vd: + scene_manager_free(state->scene_manager); + +err_alloc_sm: + free(state); + +err_alloc: + if (err != 0) { + FURI_LOG_E(APPLICATION_NAME, "Failed to launch (alloc error)!"); + } else { + FURI_LOG_I(APPLICATION_NAME, "Clean exit."); + } + + return err; +} diff --git a/applications/external/esubghz_chat/esubghz_chat_i.h b/applications/external/esubghz_chat/esubghz_chat_i.h new file mode 100644 index 000000000..b29e6292d --- /dev/null +++ b/applications/external/esubghz_chat/esubghz_chat_i.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crypto_wrapper.h" +#include "scenes/esubghz_chat_scene.h" + +#include +#include "esubghz_chat_icons.h" + +#define APPLICATION_NAME "ESubGhzChat" + +#define DEFAULT_FREQ 433920000 + +#define KEY_READ_POPUP_MS 3000 + +#define RX_TX_BUFFER_SIZE 1024 + +#define CHAT_BOX_STORE_SIZE 4096 +#define TEXT_INPUT_STORE_SIZE 256 +#define MSG_PREVIEW_SIZE 32 + +#define KEY_HEX_STR_SIZE ((KEY_BITS / 8) * 3) + +typedef struct { + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + NotificationApp* notification; + + // UI elements + Menu* menu; + TextBox* chat_box; + FuriString* chat_box_store; + TextInput* text_input; + char text_input_store[TEXT_INPUT_STORE_SIZE + 1]; + ByteInput* hex_key_input; + uint8_t hex_key_input_store[KEY_BITS / 8]; + DialogEx* key_display; + char key_hex_str[KEY_HEX_STR_SIZE + 1]; + Popup* nfc_popup; + + // for Sub-GHz + uint32_t frequency; + SubGhzTxRxWorker* subghz_worker; + const SubGhzDevice* subghz_device; + + // for NFC + NfcWorker* nfc_worker; + NfcDeviceData* nfc_dev_data; + + // message assembly before TX + FuriString* name_prefix; + FuriString* msg_input; + + // message preview + char msg_preview[MSG_PREVIEW_SIZE + 1]; + + // encryption + bool encrypted; + ESubGhzChatCryptoCtx* crypto_ctx; + + // RX and TX buffers + uint8_t rx_buffer[RX_TX_BUFFER_SIZE]; + uint8_t tx_buffer[RX_TX_BUFFER_SIZE]; + char rx_str_buffer[RX_TX_BUFFER_SIZE + 1]; + volatile uint32_t last_time_rx_data; + + // for locking + ViewPortDrawCallback orig_draw_cb; + ViewPortInputCallback orig_input_cb; + bool kbd_locked; + uint32_t kbd_lock_msg_ticks; + uint8_t kbd_lock_count; + + // for ongoing inputs + bool kbd_ok_input_ongoing; + bool kbd_left_input_ongoing; + bool kbd_right_input_ongoing; +} ESubGhzChatState; + +typedef enum { + ESubGhzChatEvent_FreqEntered, + ESubGhzChatEvent_KeyMenuNoEncryption, + ESubGhzChatEvent_KeyMenuPassword, + ESubGhzChatEvent_KeyMenuHexKey, + ESubGhzChatEvent_KeyMenuGenKey, + ESubGhzChatEvent_KeyMenuReadKeyFromNfc, + ESubGhzChatEvent_KeyReadPopupFailed, + ESubGhzChatEvent_KeyReadPopupSucceeded, + ESubGhzChatEvent_PassEntered, + ESubGhzChatEvent_HexKeyEntered, + ESubGhzChatEvent_MsgEntered, + ESubGhzChatEvent_GotoMsgInput, + ESubGhzChatEvent_GotoKeyDisplay, + ESubGhzChatEvent_KeyDisplayBack, + ESubGhzChatEvent_KeyDisplayShare, +} ESubGhzChatEvent; + +typedef enum { + ESubGhzChatView_Menu, + ESubGhzChatView_Input, + ESubGhzChatView_HexKeyInput, + ESubGhzChatView_ChatBox, + ESubGhzChatView_KeyDisplay, + ESubGhzChatView_NfcPopup, +} ESubGhzChatView; + +void set_chat_input_header(ESubGhzChatState* state); +void append_msg(ESubGhzChatState* state, const char* msg); +void tx_msg_input(ESubGhzChatState* state); +void enter_chat(ESubGhzChatState* state); diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_chat_box.c b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_box.c new file mode 100644 index 000000000..49c30ae87 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_box.c @@ -0,0 +1,65 @@ +#include "../esubghz_chat_i.h" + +/* Prepares the text box scene. */ +void scene_on_enter_chat_box(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_box"); + + furi_assert(context); + ESubGhzChatState* state = context; + + text_box_reset(state->chat_box); + text_box_set_text(state->chat_box, + furi_string_get_cstr(state->chat_box_store)); + text_box_set_focus(state->chat_box, TextBoxFocusEnd); + + view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_ChatBox); +} + +/* Handles scene manager events for the text box scene. */ +bool scene_on_event_chat_box(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_box"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to message input scene */ + case ESubGhzChatEvent_GotoMsgInput: + if (!scene_manager_previous_scene( + state->scene_manager)) { + view_dispatcher_stop(state->view_dispatcher); + } + consumed = true; + break; + case ESubGhzChatEvent_GotoKeyDisplay: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_KeyDisplay); + consumed = true; + break; + } + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the text box scene. */ +void scene_on_exit_chat_box(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_box"); + + furi_assert(context); + ESubGhzChatState* state = context; + + text_box_reset(state->chat_box); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_chat_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_input.c new file mode 100644 index 000000000..3f18dd8d7 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_chat_input.c @@ -0,0 +1,118 @@ +#include "../esubghz_chat_i.h" + +/* If no message was entred this simply emits a MsgEntered event to the scene + * manager to switch to the text box. If a message was entered it is appended + * to the name string. The result is encrypted, if encryption is enabled, and + * then copied into the TX buffer. The contents of the TX buffer are then + * transmitted. The sent message is appended to the text box and a MsgEntered + * event is sent to the scene manager to switch to the text box view. */ +static bool chat_input_validator(const char *text, FuriString *error, + void *context) +{ + UNUSED(error); + + furi_assert(context); + ESubGhzChatState* state = context; + + /* no message, just switch to the text box view */ + if (strlen(text) == 0) { + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_MsgEntered); + return true; + } + + /* concatenate the name prefix and the actual message */ + furi_string_set(state->msg_input, state->name_prefix); + furi_string_cat_str(state->msg_input, ": "); + furi_string_cat_str(state->msg_input, text); + + /* append the message to the chat box and prepare message preview */ + append_msg(state, furi_string_get_cstr(state->msg_input)); + + /* encrypt and transmit message */ + tx_msg_input(state); + + /* clear message input buffer */ + furi_string_set_char(state->msg_input, 0, 0); + + /* switch to text box view */ + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_MsgEntered); + + return true; +} + +/* Prepares the message input scene. */ +void scene_on_enter_chat_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_chat_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + state->text_input_store[0] = 0; + text_input_reset(state->text_input); + /* use validator for scene change to get around minimum length + * requirement */ + text_input_set_result_callback( + state->text_input, + NULL, + NULL, + state->text_input_store, + sizeof(state->text_input_store), + true); + text_input_set_validator( + state->text_input, + chat_input_validator, + state); + set_chat_input_header(state); + + view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input); +} + +/* Handles scene manager events for the message input scene. */ +bool scene_on_event_chat_input(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_chat_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to text box scene */ + case ESubGhzChatEvent_MsgEntered: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_ChatBox); + consumed = true; + break; + } + break; + + case SceneManagerEventTypeBack: + /* stop the application if the user presses back here */ + view_dispatcher_stop(state->view_dispatcher); + consumed = true; + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the password input scene. */ +void scene_on_exit_chat_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_chat_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + text_input_reset(state->text_input); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_freq_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_freq_input.c new file mode 100644 index 000000000..85f954fb0 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_freq_input.c @@ -0,0 +1,127 @@ +#include "../esubghz_chat_i.h" + +/* Sends FreqEntered event to scene manager and displays the frequency in the + * text box. */ +static void freq_input_cb(void *context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + furi_string_cat_printf(state->chat_box_store, "Frequency: %lu", + state->frequency); + + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_FreqEntered); +} + +/* Validates the entered frequency. */ +static bool freq_input_validator(const char *text, FuriString *error, + void *context) +{ + furi_assert(text); + furi_assert(error); + + furi_assert(context); + ESubGhzChatState* state = context; + + int ret = sscanf(text, "%lu", &(state->frequency)); + if (ret != 1) { + furi_string_printf(error, "Please enter\nfrequency\nin Hz!"); + return false; + } + + if (!subghz_devices_is_frequency_valid(state->subghz_device, + state->frequency)) { + furi_string_printf(error, "Frequency\n%lu\n is invalid!", + state->frequency); + return false; + } + +#ifdef FW_ORIGIN_Official + if (!furi_hal_region_is_frequency_allowed(state->frequency)) { +#else /* FW_ORIGIN_Official */ + if (!furi_hal_subghz_is_tx_allowed(state->frequency)) { +#endif /* FW_ORIGIN_Official */ + furi_string_printf(error, "TX forbidden\non frequency\n%lu!", + state->frequency); + return false; + } + + return true; +} + +/* Prepares the frequency input scene. */ +void scene_on_enter_freq_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_freq_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + snprintf(state->text_input_store, TEXT_INPUT_STORE_SIZE, "%lu", + (uint32_t) DEFAULT_FREQ); + text_input_reset(state->text_input); + text_input_set_result_callback( + state->text_input, + freq_input_cb, + state, + state->text_input_store, + sizeof(state->text_input_store), + true); + text_input_set_validator( + state->text_input, + freq_input_validator, + state); + text_input_set_header_text( + state->text_input, + "Frequency"); + + view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input); +} + +/* Handles scene manager events for the frequency input scene. */ +bool scene_on_event_freq_input(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_freq_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to password input scene */ + case ESubGhzChatEvent_FreqEntered: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_KeyMenu); + consumed = true; + break; + } + break; + + case SceneManagerEventTypeBack: + /* stop the application if the user presses back here */ + view_dispatcher_stop(state->view_dispatcher); + consumed = true; + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the frequency input scene. */ +void scene_on_exit_freq_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_freq_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + text_input_reset(state->text_input); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_hex_key_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_hex_key_input.c new file mode 100644 index 000000000..560f5478c --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_hex_key_input.c @@ -0,0 +1,91 @@ +#include "../esubghz_chat_i.h" + +/* Sets the entered bytes as the key, enters the chat and sends a HexKeyEntered + * event to the scene manager. */ +static void hex_key_input_cb(void* context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + /* initiate the crypto context */ + bool ret = crypto_ctx_set_key(state->crypto_ctx, + state->hex_key_input_store, state->name_prefix, + furi_get_tick()); + + /* cleanup */ + crypto_explicit_bzero(state->hex_key_input_store, + sizeof(state->hex_key_input_store)); + + if (!ret) { + crypto_ctx_clear(state->crypto_ctx); + return; + } + + state->encrypted = true; + + enter_chat(state); + + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_HexKeyEntered); +} + +/* Prepares the hex key input scene. */ +void scene_on_enter_hex_key_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_hex_key_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + byte_input_set_result_callback(state->hex_key_input, + hex_key_input_cb, + NULL, + state, + state->hex_key_input_store, + sizeof(state->hex_key_input_store)); + + view_dispatcher_switch_to_view(state->view_dispatcher, + ESubGhzChatView_HexKeyInput); +} + +/* Handles scene manager events for the hex key input scene. */ +bool scene_on_event_hex_key_input(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_hex_key_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to message input scene */ + case ESubGhzChatEvent_HexKeyEntered: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_ChatInput); + consumed = true; + break; + } + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the hex key input scene. */ +void scene_on_exit_hex_key_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_hex_key_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + crypto_explicit_bzero(state->hex_key_input_store, + sizeof(state->hex_key_input_store)); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_display.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_display.c new file mode 100644 index 000000000..fbc9e37e2 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_display.c @@ -0,0 +1,128 @@ +#include "../esubghz_chat_i.h" + +void key_display_result_cb(DialogExResult result, void* context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + switch(result) { + case DialogExResultLeft: + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyDisplayBack); + break; + + case DialogExResultCenter: + if (state->encrypted) { + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyDisplayShare); + } + break; + + default: + break; + } +} + +/* Prepares the key display scene. */ +void scene_on_enter_key_display(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_display"); + + furi_assert(context); + ESubGhzChatState* state = context; + + if (state->encrypted) { + uint8_t key[KEY_BITS / 8]; + crypto_ctx_get_key(state->crypto_ctx, key); + snprintf(state->key_hex_str, KEY_HEX_STR_SIZE, + "%02hX%02hX%02hX%02hX" + "%02hX%02hX%02hX%02hX\n" + "%02hX%02hX%02hX%02hX" + "%02hX%02hX%02hX%02hX\n" + "%02hX%02hX%02hX%02hX" + "%02hX%02hX%02hX%02hX\n" + "%02hX%02hX%02hX%02hX" + "%02hX%02hX%02hX%02hX", + key[0], key[1], key[2], key[3], + key[4], key[5], key[6], key[7], + key[8], key[9], key[10], key[11], + key[12], key[13], key[14], key[15], + key[16], key[17], key[18], key[19], + key[20], key[21], key[22], key[23], + key[24], key[25], key[26], key[27], + key[28], key[29], key[30], key[31]); + crypto_explicit_bzero(key, sizeof(key)); + } else { + strcpy(state->key_hex_str, "No Key"); + } + + dialog_ex_reset(state->key_display); + + dialog_ex_set_text(state->key_display, state->key_hex_str, 64, 2, + AlignCenter, AlignTop); + + dialog_ex_set_icon(state->key_display, 0, 0, NULL); + + dialog_ex_set_left_button_text(state->key_display, "Back"); + + if (state->encrypted) { + dialog_ex_set_center_button_text(state->key_display, "Share"); + } + + dialog_ex_set_result_callback(state->key_display, + key_display_result_cb); + dialog_ex_set_context(state->key_display, state); + + view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_KeyDisplay); +} + +/* Handles scene manager events for the key display scene. */ +bool scene_on_event_key_display(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_display"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to message input scene */ + case ESubGhzChatEvent_KeyDisplayBack: + if (!scene_manager_previous_scene( + state->scene_manager)) { + view_dispatcher_stop(state->view_dispatcher); + } + consumed = true; + break; + + /* open key sharing popup */ + case ESubGhzChatEvent_KeyDisplayShare: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_KeySharePopup); + consumed = true; + break; + } + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the key display scene. */ +void scene_on_exit_key_display(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_display"); + + furi_assert(context); + ESubGhzChatState* state = context; + + dialog_ex_reset(state->key_display); + crypto_explicit_bzero(state->key_hex_str, sizeof(state->key_hex_str)); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_menu.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_menu.c new file mode 100644 index 000000000..3665003e1 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_menu.c @@ -0,0 +1,194 @@ +#include "../esubghz_chat_i.h" + +typedef enum { + ESubGhzChatKeyMenuItems_NoEncryption, + ESubGhzChatKeyMenuItems_Password, + ESubGhzChatKeyMenuItems_HexKey, + ESubGhzChatKeyMenuItems_GenKey, + ESubGhzChatKeyMenuItems_ReadKeyFromNfc, +} ESubGhzChatKeyMenuItems; + +static void key_menu_cb(void* context, uint32_t index) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + uint8_t key[KEY_BITS / 8]; + + switch(index) { + case ESubGhzChatKeyMenuItems_NoEncryption: + state->encrypted = false; + enter_chat(state); + + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyMenuNoEncryption); + break; + + case ESubGhzChatKeyMenuItems_Password: + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyMenuPassword); + break; + + case ESubGhzChatKeyMenuItems_HexKey: + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyMenuHexKey); + break; + + case ESubGhzChatKeyMenuItems_GenKey: + /* generate a random key */ + furi_hal_random_fill_buf(key, KEY_BITS / 8); + + /* initiate the crypto context */ + bool ret = crypto_ctx_set_key(state->crypto_ctx, key, + state->name_prefix, furi_get_tick()); + + /* cleanup */ + crypto_explicit_bzero(key, sizeof(key)); + + if (!ret) { + crypto_ctx_clear(state->crypto_ctx); + return; + } + + /* set encrypted flag and enter the chat */ + state->encrypted = true; + enter_chat(state); + + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyMenuGenKey); + break; + + case ESubGhzChatKeyMenuItems_ReadKeyFromNfc: + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyMenuReadKeyFromNfc); + break; + + default: + break; + } +} + +/* Prepares the key menu scene. */ +void scene_on_enter_key_menu(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_menu"); + + furi_assert(context); + ESubGhzChatState* state = context; + + menu_reset(state->menu); + + menu_add_item( + state->menu, + "No encryption", + NULL, + ESubGhzChatKeyMenuItems_NoEncryption, + key_menu_cb, + state + ); + menu_add_item( + state->menu, + "Password", + NULL, + ESubGhzChatKeyMenuItems_Password, + key_menu_cb, + state + ); + menu_add_item( + state->menu, + "Hex Key", + NULL, + ESubGhzChatKeyMenuItems_HexKey, + key_menu_cb, + state + ); + menu_add_item( + state->menu, + "Generate Key", + NULL, + ESubGhzChatKeyMenuItems_GenKey, + key_menu_cb, + state + ); + menu_add_item( + state->menu, + "Read Key from NFC", + NULL, + ESubGhzChatKeyMenuItems_ReadKeyFromNfc, + key_menu_cb, + state + ); + + view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Menu); +} + +/* Handles scene manager events for the key menu scene. */ +bool scene_on_event_key_menu(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_menu"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to message input scene */ + case ESubGhzChatEvent_KeyMenuNoEncryption: + case ESubGhzChatEvent_KeyMenuGenKey: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_ChatInput); + consumed = true; + break; + + /* switch to password input scene */ + case ESubGhzChatEvent_KeyMenuPassword: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_PassInput); + consumed = true; + break; + + /* switch to hex key input scene */ + case ESubGhzChatEvent_KeyMenuHexKey: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_HexKeyInput); + consumed = true; + break; + + /* switch to hex key read scene */ + case ESubGhzChatEvent_KeyMenuReadKeyFromNfc: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_KeyReadPopup); + consumed = true; + break; + } + + break; + + case SceneManagerEventTypeBack: + /* stop the application if the user presses back here */ + view_dispatcher_stop(state->view_dispatcher); + consumed = true; + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the key menu scene. */ +void scene_on_exit_key_menu(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_menu"); + + furi_assert(context); + ESubGhzChatState* state = context; + + menu_reset(state->menu); +} + diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_read_popup.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_read_popup.c new file mode 100644 index 000000000..d06b14632 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_read_popup.c @@ -0,0 +1,251 @@ +#include "../esubghz_chat_i.h" + +typedef enum { + KeyReadPopupState_Idle, + KeyReadPopupState_Detecting, + KeyReadPopupState_Reading, + KeyReadPopupState_Fail, + KeyReadPopupState_Success, +} KeyReadPopupState; + +static bool read_worker_cb(NfcWorkerEvent event, void* context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + view_dispatcher_send_custom_event(state->view_dispatcher, event); + + return true; +} + +static void key_read_popup_timeout_cb(void* context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + uint32_t cur_state = scene_manager_get_scene_state( + state->scene_manager, ESubGhzChatScene_KeyReadPopup); + + /* done displaying our failure */ + if (cur_state == KeyReadPopupState_Fail) { + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyReadPopupFailed); + /* done displaying our success, enter chat */ + } else if (cur_state == KeyReadPopupState_Success) { + enter_chat(state); + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_KeyReadPopupSucceeded); + } +} + +static bool key_read_popup_handle_key_read(ESubGhzChatState *state) +{ + NfcDeviceData *dev_data = state->nfc_dev_data; + + if (dev_data->mf_ul_data.data_read < KEY_BITS / 8) { + return false; + } + + /* initiate the crypto context */ + bool ret = crypto_ctx_set_key(state->crypto_ctx, + dev_data->mf_ul_data.data, state->name_prefix, + furi_get_tick()); + + /* cleanup */ + crypto_explicit_bzero(dev_data->mf_ul_data.data, KEY_BITS / 8); + + if (!ret) { + crypto_ctx_clear(state->crypto_ctx); + return false; + } + + /* set encrypted flag */ + state->encrypted = true; + + return true; +} + +static void key_read_popup_set_state(ESubGhzChatState *state, KeyReadPopupState + new_state) +{ + uint32_t cur_state = scene_manager_get_scene_state( + state->scene_manager, ESubGhzChatScene_KeyReadPopup); + if (cur_state == new_state) { + return; + } + + if (new_state == KeyReadPopupState_Detecting) { + popup_reset(state->nfc_popup); + popup_disable_timeout(state->nfc_popup); + popup_set_text(state->nfc_popup, "Tap Flipper\n to sender", 97, + 24, AlignCenter, AlignTop); + popup_set_icon(state->nfc_popup, 0, 8, &I_NFC_manual_60x50); + notification_message(state->notification, + &sequence_blink_start_cyan); + } else if (new_state == KeyReadPopupState_Reading) { + popup_reset(state->nfc_popup); + popup_disable_timeout(state->nfc_popup); + popup_set_header(state->nfc_popup, "Reading key\nDon't " + "move...", 85, 24, AlignCenter, AlignTop); + popup_set_icon(state->nfc_popup, 12, 23, &I_Loading_24); + notification_message(state->notification, + &sequence_blink_start_yellow); + } else if (new_state == KeyReadPopupState_Fail) { + nfc_worker_stop(state->nfc_worker); + + popup_reset(state->nfc_popup); + popup_set_header(state->nfc_popup, "Failure!", 64, 2, + AlignCenter, AlignTop); + popup_set_text(state->nfc_popup, "Failed\nto read\nkey.", 78, + 16, AlignLeft, AlignTop); + popup_set_icon(state->nfc_popup, 21, 13, &I_Cry_dolph_55x52); + + popup_set_timeout(state->nfc_popup, KEY_READ_POPUP_MS); + popup_set_context(state->nfc_popup, state); + popup_set_callback(state->nfc_popup, + key_read_popup_timeout_cb); + popup_enable_timeout(state->nfc_popup); + + notification_message(state->notification, + &sequence_blink_stop); + } else if (new_state == KeyReadPopupState_Success) { + nfc_worker_stop(state->nfc_worker); + + popup_reset(state->nfc_popup); + popup_set_header(state->nfc_popup, "Key\nread!", 13, 22, + AlignLeft, AlignBottom); + popup_set_icon(state->nfc_popup, 32, 5, &I_DolphinNice_96x59); + + popup_set_timeout(state->nfc_popup, KEY_READ_POPUP_MS); + popup_set_context(state->nfc_popup, state); + popup_set_callback(state->nfc_popup, + key_read_popup_timeout_cb); + popup_enable_timeout(state->nfc_popup); + + notification_message(state->notification, &sequence_success); + notification_message(state->notification, + &sequence_blink_stop); + } + + scene_manager_set_scene_state(state->scene_manager, + ESubGhzChatScene_KeyReadPopup, new_state); + + view_dispatcher_switch_to_view(state->view_dispatcher, + ESubGhzChatView_NfcPopup); +} + +/* Prepares the key share read scene. */ +void scene_on_enter_key_read_popup(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_read_popup"); + + furi_assert(context); + ESubGhzChatState* state = context; + + key_read_popup_set_state(state, KeyReadPopupState_Detecting); + + state->nfc_dev_data->parsed_data = furi_string_alloc(); + if (state->nfc_dev_data->parsed_data == NULL) { + /* can't do anything here, crash */ + furi_check(0); + } + + nfc_worker_start(state->nfc_worker, NfcWorkerStateRead, + state->nfc_dev_data, read_worker_cb, state); +} + +/* Handles scene manager events for the key read popup scene. */ +bool scene_on_event_key_read_popup(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_read_popup"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* card detected */ + case NfcWorkerEventCardDetected: + key_read_popup_set_state(state, + KeyReadPopupState_Reading); + consumed = true; + break; + + /* no card detected */ + case NfcWorkerEventNoCardDetected: + key_read_popup_set_state(state, + KeyReadPopupState_Detecting); + consumed = true; + break; + + /* key probably read */ + case NfcWorkerEventReadMfUltralight: + if (key_read_popup_handle_key_read(state)) { + key_read_popup_set_state(state, + KeyReadPopupState_Success); + } else { + key_read_popup_set_state(state, + KeyReadPopupState_Fail); + } + consumed = true; + break; + + /* close the popup and go back */ + case ESubGhzChatEvent_KeyReadPopupFailed: + if (!scene_manager_previous_scene( + state->scene_manager)) { + view_dispatcher_stop(state->view_dispatcher); + } + consumed = true; + break; + + /* success, go to chat input */ + case ESubGhzChatEvent_KeyReadPopupSucceeded: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_ChatInput); + consumed = true; + break; + + /* something else happend, treat as failure */ + default: + key_read_popup_set_state(state, + KeyReadPopupState_Fail); + consumed = true; + break; + } + + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the key read popup scene. */ +void scene_on_exit_key_read_popup(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_read_popup"); + + furi_assert(context); + ESubGhzChatState* state = context; + + popup_reset(state->nfc_popup); + scene_manager_set_scene_state(state->scene_manager, + ESubGhzChatScene_KeyReadPopup, KeyReadPopupState_Idle); + + notification_message(state->notification, &sequence_blink_stop); + + nfc_worker_stop(state->nfc_worker); + + crypto_explicit_bzero(state->nfc_dev_data->mf_ul_data.data, KEY_BITS / 8); + if (state->nfc_dev_data->parsed_data != NULL) { + furi_string_free(state->nfc_dev_data->parsed_data); + } + memset(state->nfc_dev_data, 0, sizeof(NfcDeviceData)); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_key_share_popup.c b/applications/external/esubghz_chat/scenes/esubghz_chat_key_share_popup.c new file mode 100644 index 000000000..d5c40dced --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_key_share_popup.c @@ -0,0 +1,88 @@ +#include "../esubghz_chat_i.h" + +static void prepare_nfc_dev_data(ESubGhzChatState *state) +{ + NfcDeviceData *dev_data = state->nfc_dev_data; + + dev_data->protocol = NfcDeviceProtocolMifareUl; + furi_hal_random_fill_buf(dev_data->nfc_data.uid, 7); + dev_data->nfc_data.uid_len = 7; + dev_data->nfc_data.atqa[0] = 0x44; + dev_data->nfc_data.atqa[1] = 0x00; + dev_data->nfc_data.sak = 0x00; + + dev_data->mf_ul_data.type = MfUltralightTypeNTAG215; + dev_data->mf_ul_data.version.header = 0x00; + dev_data->mf_ul_data.version.vendor_id = 0x04; + dev_data->mf_ul_data.version.prod_type = 0x04; + dev_data->mf_ul_data.version.prod_subtype = 0x02; + dev_data->mf_ul_data.version.prod_ver_major = 0x01; + dev_data->mf_ul_data.version.prod_ver_minor = 0x00; + dev_data->mf_ul_data.version.storage_size = 0x11; + dev_data->mf_ul_data.version.protocol_type = 0x03; + + /* Add 16 to the size for config pages */ + dev_data->mf_ul_data.data_size = (KEY_BITS / 8) + 16; + crypto_ctx_get_key(state->crypto_ctx, dev_data->mf_ul_data.data); +} + +/* Prepares the key share popup scene. */ +void scene_on_enter_key_share_popup(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_key_share_popup"); + + furi_assert(context); + ESubGhzChatState* state = context; + + popup_reset(state->nfc_popup); + + popup_disable_timeout(state->nfc_popup); + + popup_set_header(state->nfc_popup, "Sharing...", 67, 13, AlignLeft, + AlignTop); + popup_set_icon(state->nfc_popup, 0, 3, &I_NFC_dolphin_emulation_47x61); + popup_set_text(state->nfc_popup, "Sharing\nKey via\nNFC", 90, 28, + AlignCenter, AlignTop); + + prepare_nfc_dev_data(state); + nfc_worker_start(state->nfc_worker, NfcWorkerStateMfUltralightEmulate, + state->nfc_dev_data, NULL, NULL); + + notification_message(state->notification, + &sequence_blink_start_magenta); + + view_dispatcher_switch_to_view(state->view_dispatcher, + ESubGhzChatView_NfcPopup); +} + +/* Handles scene manager events for the key share popup scene. */ +bool scene_on_event_key_share_popup(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_key_share_popup"); + + furi_assert(context); + ESubGhzChatState* state = context; + + UNUSED(state); + UNUSED(event); + + return false; +} + +/* Cleans up the key share popup scene. */ +void scene_on_exit_key_share_popup(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_key_share_popup"); + + furi_assert(context); + ESubGhzChatState* state = context; + + popup_reset(state->nfc_popup); + + notification_message(state->notification, &sequence_blink_stop); + + nfc_worker_stop(state->nfc_worker); + + crypto_explicit_bzero(state->nfc_dev_data->mf_ul_data.data, KEY_BITS / 8); + memset(state->nfc_dev_data, 0, sizeof(NfcDeviceData)); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_pass_input.c b/applications/external/esubghz_chat/scenes/esubghz_chat_pass_input.c new file mode 100644 index 000000000..6dcf15c74 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_pass_input.c @@ -0,0 +1,127 @@ +#include "../esubghz_chat_i.h" + +/* Sends PassEntered event to scene manager and enters the chat. */ +static void pass_input_cb(void *context) +{ + furi_assert(context); + ESubGhzChatState* state = context; + + crypto_explicit_bzero(state->text_input_store, + sizeof(state->text_input_store)); + + enter_chat(state); + + view_dispatcher_send_custom_event(state->view_dispatcher, + ESubGhzChatEvent_PassEntered); +} + +/* If a password was entered this derives a key from the password using a + * single pass of SHA256 and initiates the AES-GCM context for encryption. If + * the initiation fails, the password is rejected. */ +static bool pass_input_validator(const char *text, FuriString *error, + void *context) +{ + furi_assert(text); + furi_assert(error); + + furi_assert(context); + ESubGhzChatState* state = context; + + if (strlen(text) == 0) { + furi_string_printf(error, "Enter a\npassword!"); + return false; + } + + unsigned char key[KEY_BITS / 8]; + + /* derive a key from the password */ + sha256((unsigned char *) text, strlen(text), key); + + /* initiate the crypto context */ + bool ret = crypto_ctx_set_key(state->crypto_ctx, key, + state->name_prefix, furi_get_tick()); + + /* cleanup */ + crypto_explicit_bzero(key, sizeof(key)); + + if (!ret) { + crypto_ctx_clear(state->crypto_ctx); + furi_string_printf(error, "Failed to\nset key!"); + return false; + } + + state->encrypted = true; + + return true; +} + +/* Prepares the password input scene. */ +void scene_on_enter_pass_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_enter_pass_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + state->text_input_store[0] = 0; + text_input_reset(state->text_input); + text_input_set_result_callback( + state->text_input, + pass_input_cb, + state, + state->text_input_store, + sizeof(state->text_input_store), + true); + text_input_set_validator( + state->text_input, + pass_input_validator, + state); + text_input_set_header_text( + state->text_input, + "Password"); + + view_dispatcher_switch_to_view(state->view_dispatcher, ESubGhzChatView_Input); +} + +/* Handles scene manager events for the password input scene. */ +bool scene_on_event_pass_input(void* context, SceneManagerEvent event) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_event_pass_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + bool consumed = false; + + switch(event.type) { + case SceneManagerEventTypeCustom: + switch(event.event) { + /* switch to message input scene */ + case ESubGhzChatEvent_PassEntered: + scene_manager_next_scene(state->scene_manager, + ESubGhzChatScene_ChatInput); + consumed = true; + break; + } + break; + + default: + consumed = false; + break; + } + + return consumed; +} + +/* Cleans up the password input scene. */ +void scene_on_exit_pass_input(void* context) +{ + FURI_LOG_T(APPLICATION_NAME, "scene_on_exit_pass_input"); + + furi_assert(context); + ESubGhzChatState* state = context; + + text_input_reset(state->text_input); + crypto_explicit_bzero(state->text_input_store, + sizeof(state->text_input_store)); +} diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_scene.c b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.c new file mode 100644 index 000000000..5efb8ea10 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.c @@ -0,0 +1,30 @@ +#include "esubghz_chat_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) scene_on_enter_##name, +void (*const esubghz_chat_scene_on_enter_handlers[])(void*) = { +#include "esubghz_chat_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) scene_on_event_##name, +bool (*const esubghz_chat_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "esubghz_chat_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) scene_on_exit_##name, +void (*const esubghz_chat_scene_on_exit_handlers[])(void* context) = { +#include "esubghz_chat_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers esubghz_chat_scene_event_handlers = { + .on_enter_handlers = esubghz_chat_scene_on_enter_handlers, + .on_event_handlers = esubghz_chat_scene_on_event_handlers, + .on_exit_handlers = esubghz_chat_scene_on_exit_handlers, + .scene_num = ESubGhzChatScene_MAX, +}; diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_scene.h b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.h new file mode 100644 index 000000000..45663e6dd --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) ESubGhzChatScene_##id, +typedef enum { +#include "esubghz_chat_scene_config.h" + ESubGhzChatScene_MAX +} ESubGhzChatScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers esubghz_chat_scene_event_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void scene_on_enter_##name(void*); +#include "esubghz_chat_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool scene_on_event_##name(void* context, SceneManagerEvent event); +#include "esubghz_chat_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void scene_on_exit_##name(void* context); +#include "esubghz_chat_scene_config.h" +#undef ADD_SCENE diff --git a/applications/external/esubghz_chat/scenes/esubghz_chat_scene_config.h b/applications/external/esubghz_chat/scenes/esubghz_chat_scene_config.h new file mode 100644 index 000000000..85981c898 --- /dev/null +++ b/applications/external/esubghz_chat/scenes/esubghz_chat_scene_config.h @@ -0,0 +1,9 @@ +ADD_SCENE(esubghz_chat, freq_input, FreqInput) +ADD_SCENE(esubghz_chat, key_menu, KeyMenu) +ADD_SCENE(esubghz_chat, pass_input, PassInput) +ADD_SCENE(esubghz_chat, hex_key_input, HexKeyInput) +ADD_SCENE(esubghz_chat, key_read_popup, KeyReadPopup) +ADD_SCENE(esubghz_chat, chat_input, ChatInput) +ADD_SCENE(esubghz_chat, chat_box, ChatBox) +ADD_SCENE(esubghz_chat, key_display, KeyDisplay) +ADD_SCENE(esubghz_chat, key_share_popup, KeySharePopup)