Просмотр исходного кода

Add esubghz_chat from https://github.com/twisted-pear/esubghz_chat

git-subtree-dir: esubghz_chat
git-subtree-mainline: eb7941a2a927a2a15d4988c13d8a2df907267578
git-subtree-split: 36e14b8b3d8b53d149366193764d064a97b4da77
Willy-JL 2 лет назад
Родитель
Сommit
472607f4cc
42 измененных файлов с 4819 добавлено и 0 удалено
  1. 3 0
      esubghz_chat/.gitignore
  2. 1 0
      esubghz_chat/.gitsubtree
  3. 674 0
      esubghz_chat/LICENSE
  4. 97 0
      esubghz_chat/README.md
  5. 15 0
      esubghz_chat/application.fam
  6. BIN
      esubghz_chat/assets/Cry_dolph_55x52.png
  7. BIN
      esubghz_chat/assets/DolphinNice_96x59.png
  8. BIN
      esubghz_chat/assets/Loading_24.png
  9. BIN
      esubghz_chat/assets/NFC_dolphin_emulation_47x61.png
  10. BIN
      esubghz_chat/assets/NFC_manual_60x50.png
  11. BIN
      esubghz_chat/assets/Nfc_14px.png
  12. BIN
      esubghz_chat/assets/Pin_back_arrow_10x8.png
  13. BIN
      esubghz_chat/assets/WarningDolphin_45x42.png
  14. BIN
      esubghz_chat/assets/chat_10px.png
  15. BIN
      esubghz_chat/assets/chat_14px.png
  16. BIN
      esubghz_chat/assets/hex_14px.png
  17. BIN
      esubghz_chat/assets/keyboard_14px.png
  18. BIN
      esubghz_chat/assets/u2f_14px.png
  19. 21 0
      esubghz_chat/bgloader_api.h
  20. 483 0
      esubghz_chat/crypto/aes.c
  21. 81 0
      esubghz_chat/crypto/aes.h
  22. 511 0
      esubghz_chat/crypto/gcm.c
  23. 187 0
      esubghz_chat/crypto/gcm.h
  24. 229 0
      esubghz_chat/crypto_wrapper.c
  25. 48 0
      esubghz_chat/crypto_wrapper.h
  26. 874 0
      esubghz_chat/esubghz_chat.c
  27. 126 0
      esubghz_chat/esubghz_chat_i.h
  28. 25 0
      esubghz_chat/helpers/nfc_helpers.h
  29. 65 0
      esubghz_chat/helpers/radio_device_loader.c
  30. 15 0
      esubghz_chat/helpers/radio_device_loader.h
  31. 67 0
      esubghz_chat/scenes/esubghz_chat_chat_box.c
  32. 118 0
      esubghz_chat/scenes/esubghz_chat_chat_input.c
  33. 128 0
      esubghz_chat/scenes/esubghz_chat_freq_input.c
  34. 89 0
      esubghz_chat/scenes/esubghz_chat_hex_key_input.c
  35. 130 0
      esubghz_chat/scenes/esubghz_chat_key_display.c
  36. 196 0
      esubghz_chat/scenes/esubghz_chat_key_menu.c
  37. 303 0
      esubghz_chat/scenes/esubghz_chat_key_read_popup.c
  38. 140 0
      esubghz_chat/scenes/esubghz_chat_key_share_popup.c
  39. 125 0
      esubghz_chat/scenes/esubghz_chat_pass_input.c
  40. 30 0
      esubghz_chat/scenes/esubghz_chat_scene.c
  41. 29 0
      esubghz_chat/scenes/esubghz_chat_scene.h
  42. 9 0
      esubghz_chat/scenes/esubghz_chat_scene_config.h

+ 3 - 0
esubghz_chat/.gitignore

@@ -0,0 +1,3 @@
+*~
+*.swp
+*.swo

+ 1 - 0
esubghz_chat/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/twisted-pear/esubghz_chat main

+ 674 - 0
esubghz_chat/LICENSE

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ 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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 <https://www.gnu.org/licenses/>.
+
+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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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
+<https://www.gnu.org/licenses/>.
+
+  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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.

+ 97 - 0
esubghz_chat/README.md

@@ -0,0 +1,97 @@
+# Enhanced Sub-GHz Chat
+
+This is a plugin for the Flipper Zero that reimplements the Sub-GHz chat
+feature that is available on the CLI. In addition it allows for basic
+encryption of messages.
+
+The plugin has been tested on the official firmware (version 0.89.0) and on
+Unleashed (version unlshd-062). Currently it does not work properly on
+unlshd-061.
+
+## Warning
+
+This plugin is in the early stages of development. It will inevitably have
+bugs. You have been warned.
+
+## Usage
+
+Once opened the plugin will ask for the method of deriving the key. If
+"No encryption" is selected, the encryption is disabled. If "Generate Key" is
+selected, a random key is generated. Otherwise, the plugin will ask for the
+selected input method. Currently a password and a hex key, as well as reading
+the key from another Flipper via NFC are supported.
+
+On the next screen the plugin will ask for a frequency to operate on which must
+be entered in HZ.
+
+Finally the a message can be input. After the message is confirmed, the plugin
+will switch to the chat view, where sent and received messages are displayed.
+To view the chat view without entering a message, enter nothing. To go back to
+entering a message press the back button.
+
+In the chat view the keyboard can be locked by pressing and holding the OK
+button for a few seconds. To unlock the keyboard again quickly press the back
+button three times. By pressing the Right button the key display is opened.
+Here the currently used key is displayed in hex. This can be used to input the
+same key on another flipper. There is also an option to share the current key
+via NFC in the key display.
+
+Pressing the back button when entering the frequency, when selecting the method
+for deriving the key or when entering a message will terminate the plugin.
+
+## Interoperability
+
+When encryption is disabled, the plugin can interoperate with the Sub-GHz chat
+available on the Flipper's CLI. However, the CLI sends a sequence of escape
+characters that are not interpreted by this plugin and will be displayed in the
+chat view.
+
+## Encryption
+
+Messages are encrypted using 256 bit AES in GCM mode. Each message gets its own
+random IV. On reception the tag generated by GCM is verified and the message
+discarded if it doesn't match.
+
+A simple mechanism to prevent replay attacks is implemented. Each time the app
+enters an encrypted chat an ID is generated by SHA-256 hashing the Flipper's
+name with the current system ticks. Each message is prefixed with this ID and a
+counter. A receiving Flipper will check if the counter on a received message is
+higher than the last counter received with this ID. If it is not, the message
+is discarded and an error is displayed. ID and counter are included in the GCM
+tag's computation and are therefore authenticated with the used key.
+
+If a password is used, the key for the encryption is derived from the password
+by applying SHA-256 to the password once.
+
+Note that deriving the key with SHA-256 means that the security of your
+messages depends entirely on the strength of the password. The plugin does not
+use an elaborate key derivation function (KDF) to strengthen the password. A
+weak passwords means weak encryption.
+
+Furthermore, the key is the same among all participants. That means that each
+user with the key can impersonate every other user.
+
+The same key is used for all messages. This means that no forward-secrecy is
+provided. If the key is compromised, all previous messages are compromised as
+well.
+
+If you do not understand the implications of the caveats mentioned here, do not
+expect to gain any security by using encryption.
+
+## Acknowledgements
+
+The implementations of AES and GCM are taken directly from
+https://github.com/mko-x/SharedAES-GCM. They were released to the public domain
+by Markus Kosmal.
+
+The app icon was made by [xMasterX](https://github.com/xMasterX). The icon for
+the hexadecimal key input was taken from [QtRoS](https://github.com/QtRoS) hex
+viewer app, which can be found here:
+https://github.com/QtRoS/flipper-zero-hex-viewer. Other icons and graphics were
+taken from the Flipper Zero firmware.
+
+The icons used in the key input method menu were picked by
+[Willy-JL](https://github.com/Willy-JL).
+
+Support for the external radio was also contributed by
+[xMasterX](https://github.com/xMasterX).

+ 15 - 0
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",
+)

BIN
esubghz_chat/assets/Cry_dolph_55x52.png


BIN
esubghz_chat/assets/DolphinNice_96x59.png


BIN
esubghz_chat/assets/Loading_24.png


BIN
esubghz_chat/assets/NFC_dolphin_emulation_47x61.png


BIN
esubghz_chat/assets/NFC_manual_60x50.png


BIN
esubghz_chat/assets/Nfc_14px.png


BIN
esubghz_chat/assets/Pin_back_arrow_10x8.png


BIN
esubghz_chat/assets/WarningDolphin_45x42.png


BIN
esubghz_chat/assets/chat_10px.png


BIN
esubghz_chat/assets/chat_14px.png


BIN
esubghz_chat/assets/hex_14px.png


BIN
esubghz_chat/assets/keyboard_14px.png


BIN
esubghz_chat/assets/u2f_14px.png


+ 21 - 0
esubghz_chat/bgloader_api.h

@@ -0,0 +1,21 @@
+#include <furi.h>
+#include <flipper_application/flipper_application.h>
+
+#define APP_BASE_ARGS "run_in_background"
+
+typedef enum {
+	BGLoaderMessageType_AppReattached,
+	BGLoaderMessageType_LoaderBackground,
+	BGLoaderMessageType_LoaderExit,
+} BGLoaderMessageType;
+
+typedef struct {
+	BGLoaderMessageType type;
+} BGLoaderMessage;
+
+typedef struct {
+	FlipperApplication *fap;
+	FuriThread *thread;
+	FuriMessageQueue *to_app;
+	FuriMessageQueue *to_loader;
+} BGLoaderApp;

+ 483 - 0
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 */

+ 81 - 0
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 <string.h>
+
+#define ENCRYPT         1       // specify whether we're encrypting
+#define DECRYPT         0       // or decrypting
+
+#if defined(_MSC_VER)
+    #include <basetsd.h>
+    typedef UINT32 uint32_t;
+#else
+    #include <inttypes.h>
+#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 */

+ 511 - 0
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 ) );
+}

+ 187 - 0
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 <basetsd.h>
+    typedef unsigned int size_t;// use the right type for length declarations
+    typedef UINT32 uint32_t;
+    typedef UINT64 uint64_t;
+#else
+    #include <stdint.h>
+#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 */

+ 229 - 0
esubghz_chat/crypto_wrapper.c

@@ -0,0 +1,229 @@
+#include <furi_hal.h>
+#include <lib/mlib/m-dict.h>
+#include <toolbox/sha256.h>
+
+#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 */
+
+	// update replay dict and increase internal counter
+	if (ret) {
+		ESubGhzChatReplayDict_set_at(ctx->replay_dict, ctx->run_id,
+				ctx->counter);
+		ctx->counter++;
+	}
+
+	return ret;
+}
+
+size_t crypto_ctx_dump_replay_dict(ESubGhzChatCryptoCtx *ctx,
+		CryptoCtxReplayDictWriter writer, void *writer_ctx)
+{
+	size_t ret = 0;
+	ESubGhzChatReplayDict_it_t i;
+
+	for (ESubGhzChatReplayDict_it(i, ctx->replay_dict);
+			!ESubGhzChatReplayDict_end_p(i);
+			ESubGhzChatReplayDict_next(i), ret++) {
+		ESubGhzChatReplayDict_itref_t *ref =
+			ESubGhzChatReplayDict_ref(i);
+		if (!writer(ref->key, ref->value, writer_ctx)) {
+			break;
+		}
+	}
+
+	return ret;
+}
+
+size_t crypto_ctx_read_replay_dict(ESubGhzChatCryptoCtx *ctx,
+		CryptoCtxReplayDictReader reader, void *reader_ctx)
+{
+	size_t ret = 0;
+
+	uint64_t run_id;
+	uint32_t counter;
+
+	while (reader(&run_id, &counter, reader_ctx)) {
+		ESubGhzChatReplayDict_set_at(ctx->replay_dict, run_id,
+				counter);
+		ret++;
+	}
+
+	return ret;
+}

+ 48 - 0
esubghz_chat/crypto_wrapper.h

@@ -0,0 +1,48 @@
+#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);
+
+typedef bool (*CryptoCtxReplayDictWriter)(uint64_t run_id, uint32_t counter,
+		void *context);
+typedef bool (*CryptoCtxReplayDictReader)(uint64_t *run_id, uint32_t *counter,
+		void *context);
+
+size_t crypto_ctx_dump_replay_dict(ESubGhzChatCryptoCtx *ctx,
+		CryptoCtxReplayDictWriter writer, void *writer_ctx);
+size_t crypto_ctx_read_replay_dict(ESubGhzChatCryptoCtx *ctx,
+		CryptoCtxReplayDictReader reader, void *reader_ctx);
+
+#ifdef __cplusplus
+}
+#endif

+ 874 - 0
esubghz_chat/esubghz_chat.c

@@ -0,0 +1,874 @@
+#include <furi_hal.h>
+#include <gui/elements.h>
+#include <gui/gui.h>
+
+#include "helpers/radio_device_loader.h"
+#include "esubghz_chat_i.h"
+
+#include "bgloader_api.h"
+
+#define CHAT_LEAVE_DELAY 10
+#define TICK_INTERVAL 50
+#define MESSAGE_COMPLETION_TIMEOUT 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 information on frequency, encryption and radio type 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, "Frequency: %lu",
+			state->frequency);
+
+	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);
+
+	if (strcmp(state->subghz_device->name, "cc1101_ext") == 0) {
+		furi_string_cat_printf(state->chat_box_store,
+				"\nRadio: External");
+	} else {
+		furi_string_cat_printf(state->chat_box_store,
+				"\nRadio: Internal");
+	}
+
+
+	/* 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);
+}
+
+static void esubghz_chat_check_messages(ESubGhzChatState *state)
+{
+	/* 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);
+	}
+}
+
+/* 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);
+	}
+
+	esubghz_chat_check_messages(state);
+
+	/* 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;
+	}
+
+	/* handle long press of back key to exit for real */
+	if (event->key == InputKeyBack) {
+		if (state->view_dispatcher->current_view ==
+				text_input_get_view(state->text_input)) {
+			if (event->type == InputTypeLong) {
+				state->exit_for_real = true;
+				view_dispatcher_stop(state->view_dispatcher);
+				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 const char *esubghz_get_bgloader_app_path(const char *args)
+{
+	size_t base_args_len = strlen(APP_BASE_ARGS);
+
+	return (args + base_args_len + 1);
+}
+
+static bool esubghz_run_with_bgloader(const char *args)
+{
+	size_t base_args_len = strlen(APP_BASE_ARGS);
+
+	if (args == NULL) {
+		return false;
+	}
+
+	if (strncmp(args, APP_BASE_ARGS, base_args_len) != 0) {
+		return false;
+	}
+
+	if (strlen(args) < base_args_len + 2) {
+		return false;
+	}
+
+	if (args[base_args_len] != ':') {
+		return false;
+	}
+
+	const char *app_path = esubghz_get_bgloader_app_path(args);
+	return furi_record_exists(app_path);
+}
+
+static void esubghz_attach_to_gui(ESubGhzChatState *state)
+{
+	Gui *gui = furi_record_open(RECORD_GUI);
+	view_dispatcher_attach_to_gui(state->view_dispatcher, gui,
+			ViewDispatcherTypeFullscreen);
+}
+
+static void esubghz_detach_from_gui(ESubGhzChatState *state)
+{
+	gui_remove_view_port(state->view_dispatcher->gui,
+			state->view_dispatcher->view_port);
+	state->view_dispatcher->gui = NULL;
+	furi_record_close(RECORD_GUI);
+}
+
+static void esubghz_bgloader_loop(ESubGhzChatState *state, const char
+		*bg_app_path)
+{
+	while (true) {
+		view_dispatcher_run(state->view_dispatcher);
+		
+		if (state->exit_for_real) {
+			/* exit for real */
+			break;
+		}
+		
+		BGLoaderApp *bg_app = furi_record_open(bg_app_path);
+		
+		/* signal loader that we're ready to go to background */
+		BGLoaderMessage msg;
+		msg.type = BGLoaderMessageType_LoaderBackground;
+		furi_check(furi_message_queue_put(bg_app->to_loader, &msg,
+					FuriWaitForever) == FuriStatusOk);
+		
+		esubghz_detach_from_gui(state);
+
+		while (true) {
+			/* wait for loader to wake us up again */
+			if (furi_message_queue_get(bg_app->to_app, &msg,
+						TICK_INTERVAL) != FuriStatusOk)
+			{
+				/* check for messages on timeout */
+				esubghz_chat_check_messages(state);
+				continue;
+			}
+			if (msg.type == BGLoaderMessageType_AppReattached) {
+				break;
+			} else {
+				furi_check(0);
+			}
+		}
+		
+		furi_record_close(bg_app_path);
+		
+		esubghz_attach_to_gui(state);
+        }
+}
+
+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(const char *args)
+{
+	/* 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 default frequency */
+	state->frequency = DEFAULT_FREQ;
+
+	/* in the first few views there is no background support */
+	state->exit_for_real = true;
+
+	/* 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 = radio_device_loader_set(state->subghz_device,
+			SubGhzRadioDeviceTypeExternalCC1101);
+
+	subghz_devices_reset(state->subghz_device);
+	subghz_devices_idle(state->subghz_device);
+
+	/* 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 key menu scene */
+	scene_manager_next_scene(state->scene_manager, ESubGhzChatScene_KeyMenu);
+
+	/* run the view dispatcher, this call only returns when we close the
+	 * application */
+	if (!esubghz_run_with_bgloader(args)) {
+		view_dispatcher_run(state->view_dispatcher);
+	} else {
+		const char *bg_app_path = esubghz_get_bgloader_app_path(args);
+		esubghz_bgloader_loop(state, bg_app_path);
+	}
+
+	/* 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 */
+	radio_device_loader_end(state->subghz_device);
+
+	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;
+}

+ 126 - 0
esubghz_chat/esubghz_chat_i.h

@@ -0,0 +1,126 @@
+#pragma once
+
+#include <furi.h>
+#include <gui/view_dispatcher_i.h>
+#include <gui/view_port_i.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/byte_input.h>
+#include <gui/modules/dialog_ex.h>
+#include <gui/modules/menu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/text_input.h>
+#include <notification/notification_messages.h>
+#include <lib/nfc/nfc_worker.h>
+#include <lib/subghz/subghz_tx_rx_worker.h>
+#include <toolbox/sha256.h>
+
+#include "crypto_wrapper.h"
+#include "scenes/esubghz_chat_scene.h"
+
+#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;
+
+	// for background support
+	bool exit_for_real;
+} 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);

+ 25 - 0
esubghz_chat/helpers/nfc_helpers.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define NFC_MAX_BYTES 256
+#define NFC_CONFIG_PAGES 4
+
+struct FreqNfcEntry {
+	uint32_t frequency;
+	uint32_t unused1;
+	uint32_t unused2;
+	uint32_t unused3;
+} __attribute__ ((packed));
+
+struct ReplayDictNfcEntry {
+	uint64_t run_id;
+	uint32_t counter;
+	uint32_t unused;
+} __attribute__ ((packed));
+
+#ifdef __cplusplus
+}
+#endif

+ 65 - 0
esubghz_chat/helpers/radio_device_loader.c

@@ -0,0 +1,65 @@
+#include "radio_device_loader.h"
+
+#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
+#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
+
+static void radio_device_loader_power_on() {
+    uint8_t attempts = 0;
+    while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
+        furi_hal_power_enable_otg();
+        //CC1101 power-up time
+        furi_delay_ms(10);
+    }
+}
+
+static void radio_device_loader_power_off() {
+    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
+}
+
+bool radio_device_loader_is_connect_external(const char* name) {
+    bool is_connect = false;
+    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
+
+    if(!is_otg_enabled) {
+        radio_device_loader_power_on();
+    }
+
+    const SubGhzDevice* device = subghz_devices_get_by_name(name);
+    if(device) {
+        is_connect = subghz_devices_is_connect(device);
+    }
+
+    if(!is_otg_enabled) {
+        radio_device_loader_power_off();
+    }
+    return is_connect;
+}
+
+const SubGhzDevice* radio_device_loader_set(
+    const SubGhzDevice* current_radio_device,
+    SubGhzRadioDeviceType radio_device_type) {
+    const SubGhzDevice* radio_device;
+
+    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
+       radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
+        radio_device_loader_power_on();
+        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
+        subghz_devices_begin(radio_device);
+    } else if(current_radio_device == NULL) {
+        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+    } else {
+        radio_device_loader_end(current_radio_device);
+        radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+    }
+
+    return radio_device;
+}
+
+void radio_device_loader_end(const SubGhzDevice* radio_device) {
+    furi_assert(radio_device);
+    radio_device_loader_power_off();
+    // Code below is not used (and will cause crash) since its called from tx_rx worker end!
+    //if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) {
+    //    subghz_devices_end(radio_device);
+    //}
+}

+ 15 - 0
esubghz_chat/helpers/radio_device_loader.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include <lib/subghz/devices/devices.h>
+
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
+const SubGhzDevice* radio_device_loader_set(
+    const SubGhzDevice* current_radio_device,
+    SubGhzRadioDeviceType radio_device_type);
+
+void radio_device_loader_end(const SubGhzDevice* radio_device);

+ 67 - 0
esubghz_chat/scenes/esubghz_chat_chat_box.c

@@ -0,0 +1,67 @@
+#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)) {
+				/* error condition, exit for real */
+				state->exit_for_real = true;
+				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);
+}

+ 118 - 0
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);
+}

+ 128 - 0
esubghz_chat/scenes/esubghz_chat_freq_input.c

@@ -0,0 +1,128 @@
+#include "../esubghz_chat_i.h"
+
+/* Sends FreqEntered event to scene manager and enters the chat. */
+static void freq_input_cb(void *context)
+{
+	furi_assert(context);
+	ESubGhzChatState* state = context;
+
+	enter_chat(state);
+
+	/* starting from here running in background is supported */
+	state->exit_for_real = false;
+
+	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",
+			state->frequency);
+	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 message input scene */
+		case ESubGhzChatEvent_FreqEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_ChatInput);
+			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);
+}

+ 89 - 0
esubghz_chat/scenes/esubghz_chat_hex_key_input.c

@@ -0,0 +1,89 @@
+#include "../esubghz_chat_i.h"
+
+/* Sets the entered bytes as the key 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;
+
+	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 frequency input scene */
+		case ESubGhzChatEvent_HexKeyEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_FreqInput);
+			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));
+}

+ 130 - 0
esubghz_chat/scenes/esubghz_chat_key_display.c

@@ -0,0 +1,130 @@
+#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)) {
+				/* error condition, exit for real */
+				state->exit_for_real = true;
+				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));
+}

+ 196 - 0
esubghz_chat/scenes/esubghz_chat_key_menu.c

@@ -0,0 +1,196 @@
+#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;
+
+		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;
+
+		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);
+
+	/* clear the crypto CTX in case we got back from password or hex key
+	 * input */
+	crypto_ctx_clear(state->crypto_ctx);
+
+	menu_add_item(
+		state->menu,
+		"No encryption",
+		&I_chat_14px,
+		ESubGhzChatKeyMenuItems_NoEncryption,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Password",
+		&I_keyboard_14px,
+		ESubGhzChatKeyMenuItems_Password,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Hex Key",
+		&I_hex_14px,
+		ESubGhzChatKeyMenuItems_HexKey,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Generate Key",
+		&I_u2f_14px,
+		ESubGhzChatKeyMenuItems_GenKey,
+		key_menu_cb,
+		state
+	);
+	menu_add_item(
+		state->menu,
+		"Read Key from NFC",
+		&I_Nfc_14px,
+		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 frequency input scene */
+		case ESubGhzChatEvent_KeyMenuNoEncryption:
+		case ESubGhzChatEvent_KeyMenuGenKey:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_FreqInput);
+			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);
+}
+

+ 303 - 0
esubghz_chat/scenes/esubghz_chat_key_read_popup.c

@@ -0,0 +1,303 @@
+#include "../esubghz_chat_i.h"
+#include "../helpers/nfc_helpers.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 */
+	} else if (cur_state == KeyReadPopupState_Success) {
+		view_dispatcher_send_custom_event(state->view_dispatcher,
+				ESubGhzChatEvent_KeyReadPopupSucceeded);
+	}
+}
+
+struct ReplayDictNfcReaderContext {
+	uint8_t *cur;
+	uint8_t *max;
+};
+
+static bool replay_dict_nfc_reader(uint64_t *run_id, uint32_t *counter, void
+		*context)
+{
+	struct ReplayDictNfcReaderContext *ctx = (struct
+			ReplayDictNfcReaderContext *) context;
+
+	if (ctx->cur + sizeof(struct ReplayDictNfcEntry) > ctx->max) {
+		return false;
+	}
+
+	struct ReplayDictNfcEntry *entry = (struct ReplayDictNfcEntry *)
+		ctx->cur;
+	*run_id = entry->run_id;
+	*counter = __ntohl(entry->counter);
+
+	ctx->cur += sizeof(struct ReplayDictNfcEntry);
+
+	return true;
+}
+
+static bool key_read_popup_handle_key_read(ESubGhzChatState *state)
+{
+	NfcDeviceData *dev_data = state->nfc_dev_data;
+
+	/* check for config pages */
+	if (dev_data->mf_ul_data.data_read < NFC_CONFIG_PAGES * 4) {
+		return false;
+	}
+
+	size_t data_read = dev_data->mf_ul_data.data_read - (NFC_CONFIG_PAGES *
+			4);
+
+	/* check if key was transmitted */
+	if (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;
+	}
+
+	/* read the frequency */
+	if (data_read >= (KEY_BITS / 8) + sizeof(struct FreqNfcEntry)) {
+		struct FreqNfcEntry *freq_entry = (struct FreqNfcEntry *)
+			(dev_data->mf_ul_data.data + (KEY_BITS / 8));
+		state->frequency = __ntohl(freq_entry->frequency);
+	}
+
+	/* read the replay dict */
+	struct ReplayDictNfcReaderContext rd_ctx = {
+		.cur = dev_data->mf_ul_data.data + (KEY_BITS / 8) +
+			sizeof(struct FreqNfcEntry),
+		.max = dev_data->mf_ul_data.data + (data_read < NFC_MAX_BYTES ?
+				data_read : NFC_MAX_BYTES)
+	};
+
+	crypto_ctx_read_replay_dict(state->crypto_ctx, replay_dict_nfc_reader,
+			&rd_ctx);
+
+	/* 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 frequency input */
+		case ESubGhzChatEvent_KeyReadPopupSucceeded:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_FreqInput);
+			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));
+}

+ 140 - 0
esubghz_chat/scenes/esubghz_chat_key_share_popup.c

@@ -0,0 +1,140 @@
+#include "../esubghz_chat_i.h"
+#include "../helpers/nfc_helpers.h"
+
+struct ReplayDictNfcWriterContext {
+	uint8_t *cur;
+	uint8_t *max;
+};
+
+static bool replay_dict_nfc_writer(uint64_t run_id, uint32_t counter, void
+		*context)
+{
+	struct ReplayDictNfcWriterContext *ctx = (struct
+			ReplayDictNfcWriterContext *) context;
+
+	struct ReplayDictNfcEntry entry = {
+		.run_id = run_id,
+		.counter = __htonl(counter),
+		.unused = 0
+	};
+
+	if (ctx->cur + sizeof(entry) > ctx->max) {
+		return false;
+	}
+
+	memcpy(ctx->cur, &entry, sizeof(entry));
+	ctx->cur += sizeof(entry);
+
+	return true;
+}
+
+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;
+
+	size_t data_written = 0;
+
+	/* write key */
+	crypto_ctx_get_key(state->crypto_ctx, dev_data->mf_ul_data.data);
+	data_written += (KEY_BITS / 8);
+
+	/* write frequency */
+	struct FreqNfcEntry *freq_entry = (struct FreqNfcEntry *)
+		(dev_data->mf_ul_data.data + data_written);
+	freq_entry->frequency = __htonl(state->frequency);
+	freq_entry->unused1 = 0;
+	freq_entry->unused2 = 0;
+	freq_entry->unused3 = 0;
+	data_written += sizeof(struct FreqNfcEntry);
+
+	/* write the replay dict */
+	struct ReplayDictNfcWriterContext wr_ctx = {
+		.cur = dev_data->mf_ul_data.data + data_written,
+		.max = dev_data->mf_ul_data.data + NFC_MAX_BYTES
+	};
+
+	size_t n_entries = crypto_ctx_dump_replay_dict(state->crypto_ctx,
+			replay_dict_nfc_writer, &wr_ctx);
+	data_written += n_entries * sizeof(struct ReplayDictNfcEntry);
+
+	/* calculate size of data, add 16 for config pages */
+	dev_data->mf_ul_data.data_size = data_written + (NFC_CONFIG_PAGES * 4);
+}
+
+/* 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));
+}

+ 125 - 0
esubghz_chat/scenes/esubghz_chat_pass_input.c

@@ -0,0 +1,125 @@
+#include "../esubghz_chat_i.h"
+
+/* Sends PassEntered event to scene manager. */
+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));
+
+	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 frequency input scene */
+		case ESubGhzChatEvent_PassEntered:
+			scene_manager_next_scene(state->scene_manager,
+					ESubGhzChatScene_FreqInput);
+			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));
+}

+ 30 - 0
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,
+};

+ 29 - 0
esubghz_chat/scenes/esubghz_chat_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// 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

+ 9 - 0
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)